Repository: jiahaog/nativefier
File: app/src/components/menu.ts (TypeScript)

Code before:
  } else {
    menuTemplate = [editMenu, viewMenu, windowMenu, helpMenu];
  }

  try {
    const bookmarkConfigPath = path.join(__dirname, '..', 'bookmarks.json');
    if (fs.existsSync(bookmarkConfigPath)) {
      const bookmarksConfig = JSON.parse(
        fs.readFileSync(bookmarkConfigPath, 'utf-8'),
      );
      const bookmarksMenu: MenuItemConstructorOptions = {
        label: bookmarksConfig['menuLabel'],
        submenu: bookmarksConfig['bookmarks'].map((el) => {
          if (el['type'] === 'link') {
            let accelerator = null;
            if ('shortcut' in el) {
              accelerator = el['shortcut'];
            }
            return {
              label: el['title'],
              click: () => {
                gotoUrl(el['url']);
              },
              accelerator: accelerator,
            };
          } else if (el['type'] === 'separator') {
            return {
              type: 'separator',
            };
          }
        }),
      };
      menuTemplate.splice(menuTemplate.length - 2, 0, bookmarksMenu);
    }
  } catch (err) {
    console.warn(
      'Failed to load & parse bookmarks configuration JSON file.',
      err,
    );
  }

  const menu = Menu.buildFromTemplate(menuTemplate);
  Menu.setApplicationMenu(menu);
}


Review comment (suggestion):
Please also do a little validation that each entry is valid (is either A. a separators, or B. a link with a title & url that can be parsed by `new URL`)

Code after:
          label: 'Quit',
          accelerator: 'Cmd+Q',
          click: appQuit,
        },
      ],
    };
    (windowMenu.submenu as MenuItemConstructorOptions[]).push(
      {
        type: 'separator',
      },
      {
        label: 'Bring All to Front',
        role: 'front',
      },
    );
    menuTemplate = [electronMenu, editMenu, viewMenu, windowMenu, helpMenu];
  } else {
    menuTemplate = [editMenu, viewMenu, windowMenu, helpMenu];
  }

  try {
    const bookmarkConfigPath = path.join(__dirname, '..', 'bookmarks.json');
    if (fs.existsSync(bookmarkConfigPath)) {
      const bookmarksMenuConfig: BookmarksMenuConfig = JSON.parse(
        fs.readFileSync(bookmarkConfigPath, 'utf-8'),
      );
      const bookmarksMenu: MenuItemConstructorOptions = {
        label: bookmarksMenuConfig.menuLabel,
        submenu: bookmarksMenuConfig.bookmarks.map((bookmark) => {
          if (bookmark.type === 'link') {
            if (!('title' in bookmark && 'url' in bookmark)) {
              throw Error(
                'All links in the bookmarks menu must have a title and url.',
              );
            }
            try {
              new URL(bookmark.url);
            } catch (_) {
              throw Error('Bookmark URL "' + bookmark.url + '"is invalid.');
            }
            let accelerator = null;
            if ('shortcut' in bookmark) {
              accelerator = bookmark.shortcut;
            }
            return {
              label: bookmark.title,
              click: () => {
                gotoUrl(bookmark.url);
              },
              accelerator: accelerator,
            };