[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    browser: true,\n    node: true,\n    commonjs: true,\n  },\n  parser: '@babel/eslint-parser',\n  parserOptions: {\n    sourceType: 'module',\n    ecmaFeatures: {jsx: true},\n  },\n  settings: {\n    'import/resolver': {\n      alias: {\n        map: [\n          ['@', './src'],\n          ['@static', './static'],\n          ['@shared', './shared'],\n          ['@pkg', './package.json'],\n        ],\n        extensions: ['.js', '.jsx'],\n      },\n    },\n    react: {version: 'detect'},\n  },\n  extends: [\n    'plugin:react/recommended',\n    'plugin:react-hooks/recommended',\n    'plugin:import/recommended',\n    'standard',\n    'plugin:prettier/recommended',\n  ],\n  globals: {\n    chrome: true,\n  },\n  rules: {\n    'react/react-in-jsx-scope': 'off',\n    'prettier/prettier': [\n      'error',\n      {\n        arrowParens: 'avoid',\n        bracketSpacing: false,\n        printWidth: 100,\n        semi: false,\n        singleQuote: true,\n        endOfLine: 'auto',\n      },\n    ],\n    'react/prop-types': 'off',\n    'react-hooks/exhaustive-deps': 'off',\n    'prefer-promise-reject-errors': [2, {allowEmptyReject: true}],\n    camelcase: ['error', {properties: 'never', ignoreDestructuring: true}],\n  },\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\nout\nsrc/chrome/manifest.json\n\n.DS_Store\n.eslintcache\n.stylelintcache\n.env\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n\t\"arrowParens\": \"avoid\",\n\t\"bracketSpacing\": false,\n\t\"printWidth\": 100,\n\t\"semi\": false,\n\t\"singleQuote\": true\n}"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Jiajun Yan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Raise\n\nA simple (and unofficial) GitHub Trending client that lives in your menubar.\n\n![Raise App Screenshots](./static/screenshots/banner.png)\n\n## 📸 Screenshots\n\n![Raise App Screenshots](./static/screenshots/ui.png)\n\n## 🖥 Installation\n\n### New!! Raise is now available as a Chrome Extension\n\nMore in favor of a Chrome Extension variant? Check it out here:\n\n<a href=\"https://chrome.google.com/webstore/detail/raise-github-trending/gehkhkcbbfeooflhkboaggpgldmhfmag?hl=en&authuser=0\" target=\"_blank\"><img src=\"./static/available-in-chrome-web-store.png\" width=\"200\" /></a>\n\nOtherwise, download from [GitHub Releases](https://github.com/meetyan/raise/releases) and install.\n\nCurrently Raise can run on macOS and Windows machines.\n\n### macOS\n\nIf you use an Intel machine, please download the `.zip` file with its filename containing no architecture. Otherwise use `arm64.zip` if your hardware is armed with Apple Silicon (M1/M2).\n\n### Windows\n\nFor Windows users simply download the package with `.exe` extension.\n\nIf it's your first time to open Raise, you might see a screen saying `Windows protected your PC. Windows SmartScreen prevented an unrecognized app from start. Running this app might put your PC at risk.`. To bypass it, click `More Info` and then click `Run anyway`. This is simply because Raise on Windows is not yet [code signed](https://www.electronjs.org/docs/latest/tutorial/code-signing). Read [this](https://stackoverflow.com/questions/48946680/how-to-avoid-the-windows-defender-smartscreen-prevented-an-unrecognized-app-fro) for your information.\n\n## 🙌🏻 Features\n\n- 🌠 Showcasing GitHub's trending repos and developers\n- 🗺 Simple and intuitive user interface\n- 🌍 Language and date range filtering\n- 🌗 Dark mode\n- 💻 More under development\n\n## 🛠 Tech Involved\n\n- [Electron](https://electronjs.org/)\n- [React](https://reactjs.org/)\n- [Semi Design](https://semi.design/)\n- [GitHub Trending API](https://github.com/huchenme/github-trending-api)\n- [Plausible](https://plausible.io/)\n- [PM2](https://pm2.keymetrics.io/)\n- [Webpack](https://webpack.js.org/)\n\n## 🧑🏻‍💻 How to Develop\n\nRaise is developed on Node.js v16. Other Node.js versions have not been tested.\n\nRun the following commands in `Terminal.app` on macOS or `PowerShell` on Windows:\n\n```bash\n\nyarn\n\nyarn start\n\n```\n\n## 📢 Build and Deploy\n\nTo build and deploy, run the following:\n\n```bash\n\nyarn build\n\nyarn release\n\n```\n\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  presets: [['@babel/preset-env'], '@babel/preset-react'],\n  plugins: [\n    '@babel/plugin-proposal-class-properties',\n    '@babel/plugin-syntax-dynamic-import',\n    [\n      'import',\n      {libraryName: 'licia', libraryDirectory: '', camel2DashComponentName: false},\n      'licia',\n    ],\n  ],\n}\n"
  },
  {
    "path": "build/after-sign-hook.js",
    "content": "// See https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/\n// See https://philo.dev/notarizing-your-electron-application/\n\nrequire('dotenv').config()\nconst fs = require('fs')\nconst path = require('path')\nconst {notarize} = require('electron-notarize')\nconst pkg = require('../package.json')\n\nmodule.exports = async function (params) {\n  if (process.platform !== 'darwin') {\n    return\n  }\n\n  console.log('afterSign hook triggered', params)\n\n  const appId = pkg.build.appId\n\n  const appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`)\n\n  if (!fs.existsSync(appPath)) {\n    console.log('skip')\n    return\n  }\n\n  console.log(`Notarizing ${appId} found at ${appPath}`)\n\n  try {\n    await notarize({\n      appBundleId: appId,\n      appPath: appPath,\n      appleId: process.env.APPLE_ID,\n      appleIdPassword: process.env.APPLE_ID_PASSWORD,\n    })\n  } catch (error) {\n    console.error(error)\n  }\n\n  console.log(`Done notarizing ${appId}`)\n}\n"
  },
  {
    "path": "build/mac/entitlements.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n  </dict>\n</plist>"
  },
  {
    "path": "chrome-manifest.js",
    "content": "/**\n * Dynamically generate manifest.json for Chrome Extension\n */\n\nconst fs = require('fs')\nconst path = require('path')\n\nconst pkg = require('./package.json')\n\nmodule.exports = () => {\n  const manifest = {\n    name: pkg.chromeProductName,\n    description: pkg.chromeDescription,\n    version: pkg.version,\n    manifest_version: 3,\n    permissions: [],\n    action: {\n      default_popup: 'index.html',\n      default_icon: {\n        16: '/static/16.png',\n        32: '/static/32.png',\n        48: '/static/48.png',\n        128: '/static/128.png',\n      },\n    },\n    icons: {\n      16: '/static/16.png',\n      32: '/static/32.png',\n      48: '/static/48.png',\n      128: '/static/128.png',\n    },\n  }\n\n  fs.writeFileSync(path.resolve('./src/chrome/manifest.json'), JSON.stringify(manifest))\n}\n"
  },
  {
    "path": "electron/common.js",
    "content": "import {app, BrowserWindow, Menu, shell, Tray} from 'electron'\nimport path from 'path'\nimport log from 'electron-log'\n\nimport {IPC_FUNCTION, STORAGE_KEY} from '@shared'\nimport pkg from '@pkg'\nimport {INDEX_URL, isMac, ICON, MENUBAR, store, isDev} from './config'\nimport {mb} from './main'\n\n// See https://github.com/electron/electron/issues/19775.\nprocess.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'\n\nexport const browserWindowConfig = {\n  width: MENUBAR.WIDTH,\n  height: MENUBAR.HEIGHT,\n  webPreferences: {preload: path.join(__dirname, './preload.js')},\n}\n\nexport const createWindow = () => {\n  const mainWindow = new BrowserWindow({\n    ...browserWindowConfig,\n    icon: ICON.LOGO,\n  })\n\n  mainWindow.webContents.openDevTools()\n  mainWindow.loadURL(INDEX_URL.DEV)\n\n  return mainWindow\n}\n\nexport const createMenu = () => {\n  const template = [\n    ...(isMac\n      ? [\n          {\n            label: app.name,\n            submenu: [\n              {\n                label: `About ${pkg.productName}`,\n                click: () => {\n                  mb.showWindow()\n                  mb.window.send(IPC_FUNCTION.SHOW_ABOUT_MODAL)\n                },\n              },\n              {type: 'separator'},\n              {\n                label: 'Preferences',\n                click: () => {\n                  mb.showWindow()\n                  mb.window.send(IPC_FUNCTION.SHOW_SETTINGS_MODAL)\n                },\n              },\n              {type: 'separator'},\n              {role: 'hide'},\n              {role: 'hideOthers'},\n              {role: 'unhide'},\n              {type: 'separator'},\n              {role: 'quit'},\n            ],\n          },\n        ]\n      : []),\n    {\n      label: 'View',\n      submenu: [\n        {role: 'reload'},\n        {role: 'forceReload'},\n        ...(isDev ? [{role: 'toggleDevTools'}] : []),\n      ],\n    },\n    {\n      role: 'help',\n      submenu: [\n        {\n          label: 'Website',\n          click: async () => {\n            await shell.openExternal(pkg.repository)\n          },\n        },\n      ],\n    },\n  ]\n\n  const menu = Menu.buildFromTemplate(template)\n  Menu.setApplicationMenu(menu)\n}\n\n/**\n * Creates a right-clickable tray\n * See https://erikmartinjordan.com/menu-contextual-electron\n */\nexport const createTray = () => {\n  const tray = new Tray(ICON.MENU)\n  const contextMenu = Menu.buildFromTemplate([{role: 'quit'}])\n\n  tray.on('right-click', () => tray.popUpContextMenu(contextMenu))\n\n  return tray\n}\n\nexport const showDockIconAtLogin = () => {\n  if (!isMac) return\n\n  const shouldShowDockIcon = store.get(STORAGE_KEY.SHOW_DOCK_ICON)\n  log.info('shouldShowDockIcon', shouldShowDockIcon)\n  // Dock icon persists in the dock except a user disables it\n  if (shouldShowDockIcon === false) {\n    app.dock.hide()\n  }\n}\n"
  },
  {
    "path": "electron/config/constants.js",
    "content": "import path from 'path'\nimport is from 'electron-is'\n\nexport const BROWSER_WINDOW = {\n  WIDTH: 1440,\n  HEIGHT: 900,\n}\n\nexport const MENUBAR = {\n  WIDTH: 400,\n  HEIGHT: 600,\n}\n\nexport const INDEX_URL = {\n  DEV: 'http://localhost:3000',\n  PROD: `file://${path.join(__dirname, './index.html')}`,\n}\n\nexport const isMac = is.macOS()\n\nexport const ICON = {\n  LOGO: path.join(__dirname, './static/logo.png'),\n  MENU: path.join(__dirname, `./static/${isMac ? 'menu-' : ''}logo.png`),\n}\n\nexport const INTERVAL = {\n  UPDATE: 1000 * 60 * 60 * 24, // every 24 hours\n}\n\nexport const isDev = is.dev()\n"
  },
  {
    "path": "electron/config/index.js",
    "content": "export * from './constants'\nexport * from './store'\n"
  },
  {
    "path": "electron/config/store.js",
    "content": "import Store from 'electron-store'\n\nimport {STORAGE_KEY} from '@shared'\n\nexport const store = new Store({\n  defaults: {\n    [STORAGE_KEY.ENABLE_AUTO_UPDATE]: true,\n  },\n})\n"
  },
  {
    "path": "electron/ipc/index.js",
    "content": "import {app} from 'electron'\nimport {autoUpdater} from 'electron-updater'\n\nimport {isMac} from '../config'\n\nexport const handleShowDockIcon = (_, visible) => {\n  if (!isMac) return\n\n  if (visible) {\n    app.dock.show()\n    return\n  }\n\n  app.dock.hide()\n}\n\nexport const handleQuitAndInstall = () => {\n  autoUpdater.quitAndInstall()\n}\n"
  },
  {
    "path": "electron/main.js",
    "content": "import {app, ipcMain} from 'electron'\nimport {menubar} from 'menubar'\n\nimport {IPC_FUNCTION} from '@shared'\nimport pkg from '@pkg'\nimport {INDEX_URL, isMac, ICON, isDev} from './config'\nimport {handleQuitAndInstall, handleShowDockIcon} from './ipc'\nimport {\n  browserWindowConfig,\n  createMenu,\n  createTray,\n  // eslint-disable-next-line no-unused-vars\n  createWindow,\n  showDockIconAtLogin,\n} from './common'\nimport updateManager from './update-manager'\n\napp.setName(pkg.productName)\n\nexport let mb = null\nlet isFirstLoad = true\n\n/**\n * Shows app icon in dock on macOS\n */\nif (isMac) {\n  app.dock.setIcon(ICON.LOGO)\n  app.dock.show()\n}\n\napp.whenReady().then(() => {\n  ipcMain.on(IPC_FUNCTION.SHOW_DOCK_ICON, handleShowDockIcon)\n  ipcMain.on(IPC_FUNCTION.QUIT_AND_INSTALL, handleQuitAndInstall)\n\n  mb = menubar({\n    icon: ICON.MENU,\n    index: isDev ? INDEX_URL.DEV : INDEX_URL.PROD,\n    browserWindow: {...browserWindowConfig, resizable: false},\n    preloadWindow: true,\n    tray: createTray(),\n    tooltip: pkg.productName,\n  })\n\n  createMenu(mb)\n\n  mb.on('ready', () => {\n    updateManager.init()\n    showDockIconAtLogin()\n\n    if (isDev) {\n      createWindow() // enable this if you need an extra window open\n    }\n\n    /**\n     * The setTimeout is used as a hack to show window on ready.\n     * Otherwise the window simply flashes and won't stay shown.\n     * See https://github.com/maxogden/menubar/issues/76.\n     */\n    setTimeout(() => {\n      mb.showWindow()\n    }, 500)\n  })\n\n  mb.on('show', () => {\n    /**\n     * Reloads page after a long period of inactivity.\n     * Checks on every show() call (except when the app loads for the very first time).\n     */\n    if (isFirstLoad) return (isFirstLoad = false)\n    mb.window.send(IPC_FUNCTION.RELOAD_AFTER_INACTIVITY)\n  })\n})\n"
  },
  {
    "path": "electron/preload.js",
    "content": "import {contextBridge, ipcRenderer, shell} from 'electron'\n\nimport {IPC_FUNCTION} from '@shared'\nimport {store} from './config'\n\ncontextBridge.exposeInMainWorld('electron', {\n  storage: {\n    set: (key, val) => store.set(key, val),\n    get: key => store.get(key),\n    store: () => store.store,\n  },\n\n  open: url => shell.openExternal(url),\n\n  /**\n   * Wraps commonly used ipcRenderer methods with the followings.\n   * See: https://github.com/reZach/secure-electron-template/issues/43#issuecomment-772303787\n   */\n  send: (channel, data) => {\n    if (Object.values(IPC_FUNCTION).includes(channel)) {\n      ipcRenderer.send(channel, data)\n    }\n  },\n  receive: (channel, func) => {\n    if (Object.values(IPC_FUNCTION).includes(channel)) {\n      const subscription = (_, ...args) => func(...args)\n      ipcRenderer.on(channel, subscription)\n      return () => {\n        ipcRenderer.removeListener(channel, subscription)\n      }\n    }\n  },\n  receiveOnce: (channel, func) => {\n    if (Object.values(IPC_FUNCTION).includes(channel)) {\n      ipcRenderer.once(channel, (event, ...args) => func(...args))\n    }\n  },\n  removeAllListeners: channel => {\n    if (Object.values(IPC_FUNCTION).includes(channel)) {\n      ipcRenderer.removeAllListeners(channel)\n    }\n  },\n})\n"
  },
  {
    "path": "electron/update-manager.js",
    "content": "import {autoUpdater} from 'electron-updater'\nimport log from 'electron-log'\n\nimport {IPC_FUNCTION, STORAGE_KEY} from '@shared'\nimport {store, INTERVAL} from './config'\nimport {mb} from './main'\n\nautoUpdater.logger = log\nautoUpdater.logger.transports.file.level = 'info'\n\nconst onUpdateDownloaded = () => {\n  autoUpdater.on('update-downloaded', () => {\n    mb.window.send(IPC_FUNCTION.SHOW_UPDATE_NOTIFICATION)\n  })\n}\n\nconst checkForUpdates = () => {\n  log.info('store info', store.store)\n\n  const shouldAutoUpdate = store.get(STORAGE_KEY.ENABLE_AUTO_UPDATE)\n\n  log.info('shouldAutoUpdate', shouldAutoUpdate)\n\n  if (!shouldAutoUpdate) {\n    log.info('AUTO_UPDATE is set to false. Abort auto update...')\n    return\n  }\n\n  autoUpdater.checkForUpdates()\n}\n\nconst init = () => {\n  checkForUpdates()\n\n  /**\n   * Sets interval for periodical checks\n   */\n  setInterval(checkForUpdates, INTERVAL.UPDATE)\n\n  /**\n   * Updater events\n   */\n  onUpdateDownloaded()\n}\n\nexport default {init, checkForUpdates}\n"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@static/*\": [\"./static/*\"],\n      \"@shared\": [\"./shared\"],\n      \"@pkg\": [\"./package.json\"]\n    }\n  },\n  \"typeAcquisition\": {\"include\": [\"chrome\"]},\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"raise\",\n  \"productName\": \"Raise\",\n  \"chromeProductName\": \"Raise - GitHub Trending\",\n  \"version\": \"1.2.1\",\n  \"description\": \"A simple (and unofficial) GitHub Trending client that lives in your menubar\",\n  \"chromeDescription\": \"GitHub Trending at a glance\",\n  \"main\": \"dist/main.js\",\n  \"repository\": \"https://github.com/meetyan/raise.git\",\n  \"homepage\": \"./\",\n  \"author\": \"Jiajun Yan\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"start\": \"concurrently -k \\\"yarn start:main\\\" \\\"yarn start:renderer\\\"\",\n    \"start:main\": \"webpack --mode=development --config webpack/main/webpack.config.js && wait-on tcp:3000 && electron .\",\n    \"start:renderer\": \"webpack server --mode=development --config webpack/renderer/webpack.config.js --hot\",\n    \"start:chrome\": \"webpack --mode=development --config webpack/chrome/webpack.config.js --watch\",\n    \"build\": \"rimraf ./dist && yarn build:main && yarn build:renderer\",\n    \"build:main\": \"webpack --mode=production --config webpack/main/webpack.config.js --progress\",\n    \"build:renderer\": \"webpack --mode=production --config webpack/renderer/webpack.config.js --progress\",\n    \"build:chrome\": \"rimraf ./dist && webpack --mode=production --config webpack/chrome/webpack.config.js --progress\",\n    \"lint\": \"eslint 'src/**/*.{js,jsx}' 'electron/**/*.{js,jsx}' --cache --fix\",\n    \"package\": \"rimraf ./out && electron-builder build --mac --win --publish never\",\n    \"release\": \"rimraf ./out && electron-builder build --mac --win --publish always\"\n  },\n  \"dependencies\": {\n    \"@douyinfe/semi-icons\": \"^2.15.1\",\n    \"@douyinfe/semi-illustrations\": \"^2.16.0\",\n    \"@douyinfe/semi-ui\": \"^2.15.1\",\n    \"ahooks\": \"^3.7.0\",\n    \"axios\": \"^0.27.2\",\n    \"electron-is\": \"^3.0.0\",\n    \"electron-log\": \"^4.4.8\",\n    \"electron-store\": \"^8.1.0\",\n    \"electron-updater\": \"^5.2.1\",\n    \"lodash\": \"^4.17.21\",\n    \"menubar\": \"^9.2.1\",\n    \"nprogress\": \"^0.2.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.17.4\",\n    \"@babel/eslint-parser\": \"7.15.4\",\n    \"@babel/preset-env\": \"^7.16.11\",\n    \"@babel/preset-react\": \"^7.16.7\",\n    \"babel-loader\": \"^8.2.3\",\n    \"babel-plugin-import\": \"1.13.3\",\n    \"concurrently\": \"^7.3.0\",\n    \"copy-webpack-plugin\": \"10.2.0\",\n    \"css-loader\": \"^6.6.0\",\n    \"css-minimizer-webpack-plugin\": \"3.3.1\",\n    \"electron\": \"^19.0.9\",\n    \"electron-builder\": \"^23.3.3\",\n    \"electron-notarize\": \"^1.2.1\",\n    \"eslint\": \"7.32.0\",\n    \"eslint-config-prettier\": \"8.3.0\",\n    \"eslint-config-standard\": \"16.0.3\",\n    \"eslint-import-resolver-alias\": \"1.1.2\",\n    \"eslint-plugin-import\": \"^2.12.0\",\n    \"eslint-plugin-node\": \"11.1.0\",\n    \"eslint-plugin-prettier\": \"^4.0.0\",\n    \"eslint-plugin-promise\": \"5.1.0\",\n    \"eslint-plugin-react\": \"^7.8.2\",\n    \"eslint-plugin-react-hooks\": \"^4.2.0\",\n    \"file-loader\": \"5.1.0\",\n    \"html-webpack-plugin\": \"^5.5.0\",\n    \"mini-css-extract-plugin\": \"2.4.5\",\n    \"node-loader\": \"^2.0.0\",\n    \"node-sass\": \"^7.0.1\",\n    \"postcss\": \"8\",\n    \"postcss-loader\": \"^6.2.1\",\n    \"prettier\": \"^2.5.1\",\n    \"react-dev-utils\": \"12.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"sass-loader\": \"^12.6.0\",\n    \"style-loader\": \"^3.3.1\",\n    \"wait-on\": \"^6.0.1\",\n    \"webpack\": \"^5.69.0\",\n    \"webpack-cli\": \"^4.9.2\",\n    \"webpack-dev-server\": \"^4.7.4\",\n    \"webpack-merge\": \"^5.8.0\"\n  },\n  \"build\": {\n    \"productName\": \"Raise\",\n    \"appId\": \"to.curve.raise\",\n    \"afterSign\": \"./build/after-sign-hook.js\",\n    \"directories\": {\n      \"output\": \"out\"\n    },\n    \"mac\": {\n      \"mergeASARs\": false,\n      \"target\": [\n        {\n          \"target\": \"zip\",\n          \"arch\": [\n            \"x64\",\n            \"arm64\"\n          ]\n        }\n      ],\n      \"type\": \"distribution\",\n      \"hardenedRuntime\": true,\n      \"gatekeeperAssess\": false,\n      \"entitlements\": \"build/mac/entitlements.plist\",\n      \"entitlementsInherit\": \"build/mac/entitlements.plist\",\n      \"publish\": {\n        \"provider\": \"github\",\n        \"owner\": \"meetyan\",\n        \"repo\": \"raise\"\n      }\n    },\n    \"win\": {\n      \"target\": [\n        {\n          \"target\": \"nsis\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\"\n          ]\n        }\n      ],\n      \"publish\": {\n        \"provider\": \"github\",\n        \"owner\": \"meetyan\",\n        \"repo\": \"raise\"\n      }\n    },\n    \"files\": [\n      \"dist/**/*\",\n      \"node_modules/**/*\"\n    ],\n    \"publish\": {\n      \"provider\": \"github\",\n      \"owner\": \"meetyan\"\n    }\n  }\n}"
  },
  {
    "path": "shared/constants.js",
    "content": "export const IPC_FUNCTION = {\n  SHOW_ABOUT_MODAL: 'show-about-modal',\n  SHOW_SETTINGS_MODAL: 'show-settings-modal',\n  SHOW_DOCK_ICON: 'show-dock-icon',\n  RELOAD_AFTER_INACTIVITY: 'reload-after-inactivity',\n  SHOW_UPDATE_NOTIFICATION: 'show-update-notification',\n  QUIT_AND_INSTALL: 'quit-and-install',\n}\n\nexport const STORAGE_KEY = {\n  MODE: 'mode',\n  SHOW_BACK_TOP: 'show-back-top',\n  SHOW_DOCK_ICON: 'show-dock-icon',\n  TRENDING_TYPE: 'trending-type',\n  ENABLE_AUTO_UPDATE: 'enable-auto-update',\n}\n"
  },
  {
    "path": "shared/index.js",
    "content": "export * from './constants'\n"
  },
  {
    "path": "src/app-context.js",
    "content": "import React, {useState, useContext, useEffect} from 'react'\nimport {polyfill} from './utils'\n\nconst {setStorage} = polyfill\n\nexport const AppContext = React.createContext({})\n\nexport const AppProvider = ({value, children}) => {\n  const [context, setContext] = useState(value)\n  useEffect(() => {\n    setContext(value)\n  }, [value])\n  return <AppContext.Provider value={[context, setContext]}>{children}</AppContext.Provider>\n}\n\nexport const useAppContext = () => {\n  return useContext(AppContext)\n}\n\nexport const useContextProp = propName => {\n  const [context, setContext] = useAppContext()\n  const [prop, _setProp] = useState(context[propName])\n\n  useEffect(() => {\n    _setProp(context[propName])\n  }, [context, propName])\n\n  const setProp = val => {\n    _setProp(val)\n    setStorage(propName, val)\n\n    setContext(preCtx => {\n      const data = {\n        ...preCtx,\n        [propName]: val,\n      }\n\n      return data\n    })\n  }\n\n  return [prop, setProp]\n}\n"
  },
  {
    "path": "src/app.js",
    "content": "import React, {useState} from 'react'\nimport {Divider, Layout, Toast, Typography} from '@douyinfe/semi-ui'\n\nimport {MODE, TRENDING_TYPE, Z_INDEX} from '@/config'\nimport {AppProvider} from '@/app-context'\nimport {UpdateNotification, UpperContainer} from '@/components'\nimport Index from '@/pages/index/index'\nimport {polyfill} from '@/utils'\nimport pkg from '@pkg'\nimport {STORAGE_KEY} from '@shared'\n\nimport 'nprogress/nprogress.css'\nimport '@/assets/styles/reset.scss'\nimport '@/assets/styles/global.scss'\nimport styles from '@/app.scss'\n\nconst {Text} = Typography\nconst {Footer} = Layout\n\nToast.config({zIndex: Z_INDEX.TOAST})\n\nconst {REPOSITORIES} = TRENDING_TYPE\nconst {getContextFromStorage} = polyfill\n\nconst App = () => {\n  const [context] = useState({\n    [STORAGE_KEY.MODE]: MODE.LIGHT, // system themes\n    [STORAGE_KEY.SHOW_BACK_TOP]: true,\n    [STORAGE_KEY.SHOW_DOCK_ICON]: true,\n    [STORAGE_KEY.ENABLE_AUTO_UPDATE]: true,\n    ...getContextFromStorage(),\n    [STORAGE_KEY.TRENDING_TYPE]: REPOSITORIES,\n  })\n\n  return (\n    <AppProvider value={context}>\n      <Layout className={styles.layout}>\n        <UpperContainer>\n          <Index />\n        </UpperContainer>\n\n        <Footer id=\"footer\">\n          <Divider />\n          <div className={styles.copyright}>\n            <Text strong>\n              © {new Date().getFullYear()} {pkg.productName}. All rights reserved.\n            </Text>\n          </div>\n        </Footer>\n\n        <UpdateNotification />\n      </Layout>\n    </AppProvider>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "src/app.scss",
    "content": "::-webkit-scrollbar {\n  display: none;\n}\n\n.layout {\n  background-color: var(--semi-color-bg-0);\n  padding: 20px;\n  min-width: 400px;\n}\n\n.copyright {\n  padding-top: 10px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n"
  },
  {
    "path": "src/assets/styles/global.scss",
    "content": "body {\n  color: var(--semi-color-text-0);\n  background-color: var(--semi-color-bg-0);\n}\n\n:global(#nprogress .bar) {\n  z-index: 99999;\n}\n"
  },
  {
    "path": "src/assets/styles/reset.scss",
    "content": "/* stylelint-disable */\n/* http://meyerweb.com/eric/tools/css/reset/\n   v5.0.1 | 20191019\n   License: none (public domain)\n*/\n\nhtml,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\na,\nabbr,\nacronym,\naddress,\nbig,\ncite,\ncode,\ndel,\ndfn,\nem,\nimg,\nins,\nkbd,\nq,\ns,\nsamp,\nsmall,\nstrike,\nstrong,\nsub,\nsup,\ntt,\nvar,\nb,\nu,\ni,\ncenter,\ndl,\ndt,\ndd,\nmenu,\nol,\nul,\nli,\nfieldset,\nform,\nlabel,\nlegend,\ntable,\ncaption,\ntbody,\ntfoot,\nthead,\ntr,\nth,\ntd,\narticle,\naside,\ncanvas,\ndetails,\nembed,\nfigure,\nfigcaption,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\noutput,\nruby,\nsection,\nsummary,\ntime,\nmark,\naudio,\nvideo {\n  margin: 0;\n  padding: 0;\n  border: 0;\n  font-size: 100%;\n  font: inherit;\n  vertical-align: baseline;\n}\n\n/* HTML5 display-role reset for older browsers */\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection {\n  display: block;\n}\n\n/* HTML5 hidden-attribute fix for newer browsers */\n*[hidden] {\n  display: none;\n}\n\nbody {\n  line-height: 1;\n}\n\nmenu,\nol,\nul {\n  list-style: none;\n}\n\nblockquote,\nq {\n  quotes: none;\n}\n\nblockquote::before,\nblockquote::after,\nq::before,\nq::after {\n  content: '';\n  content: none;\n}\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n"
  },
  {
    "path": "src/chrome/plausible.js",
    "content": "// eslint-disable-next-line\n!function(){\"use strict\";var a=window.location,r=window.document,t=window.localStorage,o=r.currentScript,s=o.getAttribute(\"data-api\")||new URL(o.src).origin+\"/api/event\",l=t&&t.plausible_ignore;function p(t){console.warn(\"Ignoring Event: \"+t)}function e(t,e){if(/^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(a.hostname)||\"file:\"===a.protocol)return p(\"localhost\");if(!(window._phantom||window.__nightmare||window.navigator.webdriver||window.Cypress)){if(\"true\"==l)return p(\"localStorage flag\");var i={};i.n=t,i.u=a.href,i.d=o.getAttribute(\"data-domain\"),i.r=r.referrer||null,i.w=window.innerWidth,e&&e.meta&&(i.m=JSON.stringify(e.meta)),e&&e.props&&(i.p=JSON.stringify(e.props));var n=new XMLHttpRequest;n.open(\"POST\",s,!0),n.setRequestHeader(\"Content-Type\",\"text/plain\"),n.send(JSON.stringify(i)),n.onreadystatechange=function(){4==n.readyState&&e&&e.callback&&e.callback()}}}var i=window.plausible&&window.plausible.q||[];window.plausible=e;for(var n,w=0;w<i.length;w++)e.apply(this,i[w]);function d(){n!==a.pathname&&(n=a.pathname,e(\"pageview\"))}var u,c=window.history;c.pushState&&(u=c.pushState,c.pushState=function(){u.apply(this,arguments),d()},window.addEventListener(\"popstate\",d)),\"prerender\"===r.visibilityState?r.addEventListener(\"visibilitychange\",function(){n||\"visible\"!==r.visibilityState||d()}):d()}();"
  },
  {
    "path": "src/chrome/popup.js",
    "content": "/**\n * Set body's background color to either light or dark\n * This prevents popup from flashing on app launch\n */\n\nconst lightColor = '#fff'\nconst darkColor = '#16161a'\nconst MODE = 'mode'\n\nconst getStorage = key => {\n  try {\n    return JSON.parse(window.localStorage.getItem(key)).value\n  } catch (err) {\n    console.log(`An error occurred when getting storage ${key}.`, err)\n    return null\n  }\n}\n\nconst mode = getStorage(MODE)\ndocument.body.style.backgroundColor = mode === 'dark' ? darkColor : lightColor\ndocument.documentElement.style.width = '400px'\ndocument.documentElement.style.minHeight = '599.9px' // not sure why setting height to 600px causes page not scrollable\n"
  },
  {
    "path": "src/components/about-modal/index.js",
    "content": "import React from 'react'\nimport {Divider, Modal, Typography} from '@douyinfe/semi-ui'\n\nimport {VERSION, Z_INDEX} from '@/config'\nimport pkg from '@pkg'\nimport Logo from '@static/logo-without-padding.png'\nimport {polyfill} from '@/utils'\n\nimport styles from './styles.scss'\n\nconst {Text, Title} = Typography\nconst {open} = polyfill\n\nconst AboutModal = ({visible, setVisible}) => {\n  return (\n    <Modal\n      title=\"About\"\n      visible={visible}\n      onOk={() => setVisible(false)}\n      onCancel={() => setVisible(false)}\n      closeOnEsc={true}\n      width={350}\n      height=\"fit-content\"\n      centered\n      footer={null}\n      zIndex={Z_INDEX.MODAL}\n    >\n      <Divider />\n      <div className={styles.aboutModal}>\n        <div className={styles.logo}>\n          <img src={Logo} alt=\"logo\" />\n          <Title className={styles.title} heading={5}>\n            {pkg.productName}\n          </Title>\n        </div>\n        <Text className={`${styles.centerAligned} ${styles.version}`}>Version {VERSION}</Text>\n        <Text className={styles.centerAligned}>\n          A simple (and unofficial) GitHub Trending client that lives in your menubar.\n        </Text>\n        <div className={styles.copyright}>\n          <Text link className={styles.centerAligned} onClick={() => open(pkg.repository)}>\n            An open-source project by Jiajun Yan.\n          </Text>\n          <Text className={styles.centerAligned}>\n            Copyright © {new Date().getFullYear()} Raise. All rights reserved.\n          </Text>\n        </div>\n      </div>\n    </Modal>\n  )\n}\n\nexport default AboutModal\n"
  },
  {
    "path": "src/components/about-modal/styles.scss",
    "content": ".about-modal {\n  display: flex;\n  flex-direction: column;\n  padding: 20px 0;\n\n  .logo {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    margin-bottom: 20px;\n\n    img {\n      width: 80px;\n      height: 80px;\n    }\n  }\n\n  .title {\n    margin-top: 10px;\n  }\n\n  .center-aligned {\n    text-align: center;\n  }\n\n  .version {\n    margin-bottom: 20px;\n  }\n\n  .copyright {\n    display: flex;\n    flex-direction: column;\n    margin: 20px 0 6px;\n  }\n}\n"
  },
  {
    "path": "src/components/developer-content/index.js",
    "content": "import React from 'react'\nimport {Typography, Layout, Card, Space} from '@douyinfe/semi-ui'\nimport {IconBranch, IconCrown} from '@douyinfe/semi-icons'\n\nimport {SkeletonPlaceholder} from '@/components'\nimport {polyfill} from '@/utils'\n\nimport styles from './styles.scss'\n\nconst {Content} = Layout\nconst {Text, Title} = Typography\nconst {open} = polyfill\n\nconst AuthorHeader = ({item}) => (\n  <div className={styles.header}>\n    <img className={styles.avatar} src={item.avatar} onClick={() => open(item.url)} />\n    <Space vertical align=\"start\" spacing={2}>\n      <Title link heading={6} onClick={() => open(item.url)}>\n        {item.name}\n      </Title>\n      <Text className={styles.cursor} onClick={() => open(item.url)}>\n        {item.username}\n      </Text>\n    </Space>\n  </div>\n)\n\nconst DeveloperContent = ({list, loading}) => {\n  return (\n    <Content className={styles.content}>\n      {Array.from({length: 5}).map((_, index) => (\n        <SkeletonPlaceholder key={index} loading={loading} />\n      ))}\n\n      {list.map(item => {\n        if (!item.repo) {\n          return (\n            <Card className={styles.developer} key={item.name}>\n              <div style={{display: 'flex', justifyContent: 'space-between'}}>\n                <AuthorHeader item={item} />\n              </div>\n            </Card>\n          )\n        }\n\n        return (\n          <Card key={item.name} title={<AuthorHeader item={item} />} className={styles.developer}>\n            <Space vertical align=\"start\">\n              <Space>\n                <IconCrown /> <Text>Popular Repo</Text>\n              </Space>\n              <Space>\n                <IconBranch />{' '}\n                <Text link strong onClick={() => open(item.repo.url)}>\n                  {item.repo.name}\n                </Text>\n              </Space>\n\n              {item.repo.description ? (\n                <Text className={styles.description}>{item.repo.description}</Text>\n              ) : null}\n            </Space>\n          </Card>\n        )\n      })}\n    </Content>\n  )\n}\n\nexport default DeveloperContent\n"
  },
  {
    "path": "src/components/developer-content/styles.scss",
    "content": ".content {\n  padding-top: 15px;\n\n  .developer {\n    width: 100%;\n    margin-bottom: 20px;\n    background-color: var(--semi-color-fill-0);\n  }\n\n  .header {\n    display: flex;\n    align-items: center;\n\n    .avatar {\n      width: 50px;\n      height: 50px;\n      border-radius: 50%;\n      margin-right: 10px;\n      cursor: pointer;\n    }\n  }\n\n  .description {\n    margin-top: 10px;\n  }\n\n  .cursor {\n    cursor: pointer;\n  }\n}\n"
  },
  {
    "path": "src/components/filter/index.js",
    "content": "import React, {forwardRef, useImperativeHandle, useRef} from 'react'\nimport {Form} from '@douyinfe/semi-ui'\n\nimport {SINCE_ARRAY, SPOKEN_LANGUAGES, LANGUAGES, SINCE, TRENDING_TYPE, Z_INDEX} from '@/config'\nimport {truncate} from '@/utils'\nimport {useTrendingType} from '@/hooks'\n\nimport styles from './styles.scss'\n\nconst Filter = ({getList}, ref) => {\n  const api = useRef()\n  const [trendingType] = useTrendingType()\n\n  const isRepo = trendingType === TRENDING_TYPE.REPOSITORIES\n\n  useImperativeHandle(ref, () => ({\n    reset() {\n      api.current.reset()\n    },\n  }))\n\n  return (\n    <div className={styles.filter}>\n      <div className={styles.bottom}>\n        <Form\n          labelPosition=\"left\"\n          labelAlign=\"left\"\n          labelWidth={180}\n          onValueChange={getList}\n          getFormApi={formApi => (api.current = formApi)}\n        >\n          {isRepo ? (\n            <Form.Select\n              field=\"spoken_language_code\"\n              initValue=\"any\"\n              label=\"Spoken language\"\n              className={styles.bottomSelect}\n              filter\n              zIndex={Z_INDEX.SELECT}\n              style={{width: '100%'}}\n              emptyContent=\"No matches\"\n            >\n              {SPOKEN_LANGUAGES.map(item => {\n                return (\n                  <Form.Select.Option key={item.name} value={item.urlParam}>\n                    {truncate(item.name)}\n                  </Form.Select.Option>\n                )\n              })}\n            </Form.Select>\n          ) : null}\n\n          <Form.Select\n            field=\"language\"\n            initValue=\"any\"\n            label=\"Language\"\n            className={styles.bottomSelect}\n            filter\n            zIndex={Z_INDEX.SELECT}\n            style={{width: '100%'}}\n            emptyContent=\"No matches\"\n          >\n            {LANGUAGES.map(item => {\n              return (\n                <Form.Select.Option key={item.name} value={item.urlParam}>\n                  {truncate(item.name)}\n                </Form.Select.Option>\n              )\n            })}\n          </Form.Select>\n\n          <Form.Select\n            field=\"since\"\n            initValue={SINCE.DAILY}\n            label=\"Date range\"\n            className={styles.bottomSelect}\n            filter\n            zIndex={Z_INDEX.SELECT}\n            style={{width: '100%'}}\n            emptyContent=\"No matches\"\n          >\n            {SINCE_ARRAY.map(since => {\n              return (\n                <Form.Select.Option key={since.value} value={since.value}>\n                  {since.name}\n                </Form.Select.Option>\n              )\n            })}\n          </Form.Select>\n        </Form>\n      </div>\n    </div>\n  )\n}\n\nexport default forwardRef(Filter)\n"
  },
  {
    "path": "src/components/filter/styles.scss",
    "content": ".filter {\n  display: flex;\n  flex-direction: column;\n\n  .bottom {\n    display: flex;\n    flex-direction: column;\n\n    .bottom-select {\n      width: 100%;\n    }\n  }\n\n  .bottom-select-text {\n    display: block;\n    width: 100%;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "src/components/index.js",
    "content": "export {default as RaiseHeader} from './raise-header'\nexport {default as DeveloperContent} from './developer-content'\nexport {default as RepositoryContent} from './repository-content'\nexport {default as SettingsModal} from './settings-modal'\nexport {default as AboutModal} from './about-modal'\nexport {default as SkeletonPlaceholder} from './skeleton-placeholder'\nexport {default as Filter} from './filter'\nexport {default as UpperContainer} from './upper-container'\nexport {default as UpdateNotification} from './update-notification'\n"
  },
  {
    "path": "src/components/raise-header/index.js",
    "content": "import React, {useEffect, useLayoutEffect, useRef, useState} from 'react'\nimport {Button, Collapsible, Divider, Layout, Typography} from '@douyinfe/semi-ui'\nimport {\n  IconFilter,\n  IconInfoCircle,\n  IconMoon,\n  IconRefresh,\n  IconSetting,\n  IconSun,\n} from '@douyinfe/semi-icons'\n\nimport {Filter, SettingsModal, AboutModal} from '@/components'\nimport {MODE, TRENDING_TYPE, isMac} from '@/config'\nimport {useMode, useOutsideClick, useScrollPosition, useTrendingType} from '@/hooks'\nimport {IPC_FUNCTION} from '@shared'\nimport pkg from '@pkg'\nimport {polyfill} from '@/utils'\n\nimport Logo from '@static/logo-without-padding.png'\nimport styles from './styles.scss'\n\nconst {Header} = Layout\nconst {Text} = Typography\n\nconst {REPOSITORIES, DEVELOPERS} = TRENDING_TYPE\nconst {SHOW_ABOUT_MODAL, SHOW_SETTINGS_MODAL} = IPC_FUNCTION\n\nconst RaiseHeader = ({refresh, getList, resetList}) => {\n  const headerRef = useRef()\n  const filterRef = useRef()\n  const [mode, setMode] = useMode()\n  const [trendingType, setTrendingType] = useTrendingType()\n  const scrollPosition = useScrollPosition()\n\n  const [headerHeight, setHeaderHeight] = useState(0)\n  const [showFilter, setShowFilter] = useState(false)\n  const [settingsModalVisible, setSettingsModalVisible] = useState(false)\n  const [aboutModalVisible, setAboutModalVisible] = useState(false)\n\n  useOutsideClick(headerRef, () => setShowFilter(false))\n\n  const trendingTypeButtonConfig = buttonType => {\n    return trendingType === buttonType ? {type: 'primary', theme: 'solid'} : {}\n  }\n\n  const TrendingButton = ({type}) => {\n    return (\n      <Button\n        {...trendingTypeButtonConfig(type)}\n        className={styles.trendingTypeButton}\n        onClick={() => {\n          if (trendingType === type) return\n          resetList()\n          setTrendingType(type)\n        }}\n      >\n        {type}\n      </Button>\n    )\n  }\n\n  const toggleFilter = () => {\n    setShowFilter(!showFilter)\n  }\n\n  useLayoutEffect(() => {\n    const [headerComponent] = document.getElementsByClassName(styles.header)\n    setHeaderHeight(headerComponent.offsetHeight - 20)\n  }, [])\n\n  useEffect(() => {\n    setShowFilter(false)\n    filterRef.current.reset()\n  }, [trendingType])\n\n  useEffect(() => {\n    const {receive} = polyfill\n    receive(SHOW_ABOUT_MODAL, () => setAboutModalVisible(true))\n    receive(SHOW_SETTINGS_MODAL, () => setSettingsModalVisible(true))\n  }, [])\n\n  return (\n    <>\n      <div ref={headerRef}>\n        <Header\n          className={styles.header}\n          style={{\n            boxShadow: scrollPosition || showFilter ? '0 8px 24px -2px rgba(0, 0, 0, 0.2)' : 'none',\n            ...(isMac ? {backgroundColor: 'initial'} : {}),\n          }}\n        >\n          <div className={styles.top}>\n            <h1 className={styles.heading}>\n              <Text strong>GitHub Trending</Text>\n            </h1>\n\n            <div className={styles.trendingType}>\n              <TrendingButton\n                type={REPOSITORIES}\n                setType={setTrendingType}\n                config={trendingTypeButtonConfig}\n              />\n              <TrendingButton\n                type={DEVELOPERS}\n                setType={setTrendingType}\n                config={trendingTypeButtonConfig}\n              />\n            </div>\n          </div>\n\n          <div className={styles.settings}>\n            <div className={styles.logo}>\n              <img src={Logo} alt=\"logo\" />\n              <Text strong>{pkg.productName}</Text>\n            </div>\n            <div className={styles.top}>\n              <Button\n                theme=\"borderless\"\n                icon={<IconRefresh />}\n                onClick={() => {\n                  setShowFilter(false)\n                  refresh()\n                }}\n              />\n              <Button theme=\"borderless\" icon={<IconFilter />} onClick={toggleFilter} />\n              <Button\n                theme=\"borderless\"\n                icon={mode === MODE.LIGHT ? <IconMoon /> : <IconSun />}\n                onClick={() => setMode(mode === MODE.LIGHT ? MODE.DARK : MODE.LIGHT)}\n              />\n              <Button\n                theme=\"borderless\"\n                icon={<IconInfoCircle />}\n                onClick={() => setAboutModalVisible(true)}\n              />\n              <Button\n                theme=\"borderless\"\n                icon={<IconSetting />}\n                onClick={() => setSettingsModalVisible(true)}\n              />\n            </div>\n          </div>\n\n          <Divider style={{opacity: !scrollPosition || showFilter ? 1 : 0}} />\n\n          <Collapsible isOpen={showFilter} keepDOM>\n            <Filter ref={filterRef} getList={getList} />\n          </Collapsible>\n        </Header>\n      </div>\n\n      <div style={{width: '100%', height: headerHeight || 0}}></div>\n\n      <SettingsModal visible={settingsModalVisible} setVisible={setSettingsModalVisible} />\n      <AboutModal visible={aboutModalVisible} setVisible={setAboutModalVisible} />\n    </>\n  )\n}\n\nexport default RaiseHeader\n"
  },
  {
    "path": "src/components/raise-header/styles.scss",
    "content": ".header {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  padding: 20px 20px 0;\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: 9999;\n  background-color: var(--semi-color-bg-0);\n  backdrop-filter: saturate(180%) blur(30px);\n  transition: all 0.2s;\n\n  .top {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .heading {\n    font-size: 16px;\n    font-weight: 600;\n    display: flex;\n    align-items: center;\n\n    .heading-title {\n      margin-left: 5px;\n    }\n  }\n\n  .trending-type {\n    display: flex;\n\n    .trending-type-button:first-child {\n      border-top-right-radius: 0;\n      border-bottom-right-radius: 0;\n    }\n\n    .trending-type-button:last-child {\n      border-top-left-radius: 0;\n      border-bottom-left-radius: 0;\n    }\n  }\n\n  .settings {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 5px 0 3px 0;\n\n    .logo {\n      display: flex;\n      align-items: center;\n\n      img {\n        width: 16px;\n        height: 16px;\n        margin-right: 4px;\n      }\n    }\n\n    .top {\n      display: flex;\n    }\n  }\n}\n"
  },
  {
    "path": "src/components/repository-content/index.js",
    "content": "import React from 'react'\nimport {Typography, Layout, Card, Space, Tooltip} from '@douyinfe/semi-ui'\nimport {IconBranch, IconSourceControl, IconStar} from '@douyinfe/semi-icons'\n\nimport {URL} from '@/config'\nimport {numberWithCommas, polyfill} from '@/utils'\nimport {SkeletonPlaceholder} from '@/components'\n\nimport styles from './styles.scss'\n\nconst {Content} = Layout\nconst {Text} = Typography\nconst {open} = polyfill\n\nconst RepositoryContent = ({list, loading}) => {\n  return (\n    <Content className={styles.content}>\n      {Array.from({length: 5}).map((_, index) => (\n        <SkeletonPlaceholder key={index} loading={loading} />\n      ))}\n\n      {list.map(item => {\n        return (\n          <Card\n            key={item.name + item.author}\n            title={\n              <div className={styles.repoHeader}>\n                <IconBranch />\n                <div className={styles.repoAuthor}>\n                  <Text link onClick={() => open(`${URL.GITHUB}/${item.author}`)}>\n                    {item.author}\n                  </Text>\n                  {' / '}\n                  <Tooltip content={item.name} position=\"bottom\">\n                    <Text link strong onClick={() => open(item.url)}>\n                      {item.name}\n                    </Text>\n                  </Tooltip>\n                </div>\n              </div>\n            }\n            className={styles.repo}\n            headerExtraContent={\n              <Space spacing={4}>\n                <span className={styles.languageColor} style={{background: item.languageColor}} />\n                <Text type=\"secondary\">{item.language || 'Unknown'}</Text>\n              </Space>\n            }\n          >\n            {item.description ? (\n              <Text className={styles.description}>{item.description}</Text>\n            ) : null}\n\n            <div className={styles.bottomArea}>\n              <div className={styles.top}>\n                <Space>\n                  <Space className={styles.cursor} spacing={4}>\n                    <IconStar />\n                    <Text onClick={() => open(`${item.url}/stargazers`)}>\n                      {numberWithCommas(item.stars)}\n                    </Text>\n                  </Space>\n                  <Space />\n                  <Space className={styles.cursor} spacing={4}>\n                    <IconSourceControl />\n                    <Text onClick={() => open(`${item.url}/network/members.${item.author}`)}>\n                      {numberWithCommas(item.forks)}\n                    </Text>\n                  </Space>\n                </Space>\n\n                <Space spacing={4}>\n                  <IconStar />\n                  <Text>{numberWithCommas(item.currentPeriodStars)} stars today</Text>\n                </Space>\n              </div>\n\n              {item.builtBy?.length ? (\n                <div className={styles.bottom}>\n                  <Space>\n                    <Text>Built by</Text>\n                    <div>\n                      {item.builtBy?.map(builtByAuthor => {\n                        return (\n                          <img\n                            className={styles.avatar}\n                            src={builtByAuthor.avatar}\n                            key={builtByAuthor.avatar}\n                            onClick={() => open(builtByAuthor.href)}\n                          />\n                        )\n                      })}\n                    </div>\n                  </Space>\n                </div>\n              ) : null}\n            </div>\n          </Card>\n        )\n      })}\n    </Content>\n  )\n}\n\nexport default RepositoryContent\n"
  },
  {
    "path": "src/components/repository-content/styles.scss",
    "content": ".content {\n  padding-top: 15px;\n\n  .repo-header {\n    display: flex;\n    align-items: center;\n  }\n\n  .repo-author {\n    margin-left: 5px;\n    width: 220px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .repo {\n    width: 100%;\n    margin-bottom: 20px;\n    background-color: var(--semi-color-fill-0);\n  }\n\n  .language-color {\n    display: inline-block;\n    width: 10px;\n    height: 10px;\n    border-radius: 50%;\n  }\n\n  .description {\n    display: block;\n    margin-bottom: 20px;\n  }\n\n  .bottom-area {\n    display: flex;\n    flex-direction: column;\n\n    .top {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n    }\n\n    .avatar {\n      width: 20px;\n      height: 20px;\n      border-radius: 50%;\n      margin-right: 4px;\n      cursor: pointer;\n\n      &:last-child {\n        margin-right: 0;\n      }\n    }\n\n    .bottom {\n      display: flex;\n      align-items: center;\n      margin-top: 10px;\n    }\n  }\n\n  .cursor {\n    cursor: pointer;\n  }\n}\n"
  },
  {
    "path": "src/components/settings-modal/index.js",
    "content": "import React from 'react'\nimport {Button, Divider, Modal, Space, Switch, Typography} from '@douyinfe/semi-ui'\nimport {IconExternalOpen} from '@douyinfe/semi-icons'\n\nimport {useAutoUpdate, useBackTop, useDockIcon, useMode} from '@/hooks'\nimport {MODE, URL, VERSION, Z_INDEX, isMac, isElectron} from '@/config'\nimport {polyfill} from '@/utils'\nimport pkg from '@pkg'\n\nimport styles from './styles.scss'\n\nconst {Text} = Typography\nconst {open} = polyfill\n\nconst SettingsModal = ({visible, setVisible}) => {\n  const [mode, setMode] = useMode()\n  const [backTop, setBackTop] = useBackTop()\n  const [dockIcon, setDockIcon] = useDockIcon()\n  const [autoUpdate, setAutoUpdate] = useAutoUpdate()\n\n  return (\n    <Modal\n      title=\"Settings\"\n      visible={visible}\n      onOk={() => setVisible(false)}\n      onCancel={() => setVisible(false)}\n      closeOnEsc={true}\n      width={350}\n      height=\"fit-content\"\n      centered\n      footer={null}\n      zIndex={Z_INDEX.MODAL}\n    >\n      <Divider />\n      <div className={styles.settingsModal}>\n        <Space style={{width: '100%'}} vertical spacing=\"medium\" align=\"start\">\n          <div className={styles.settingsItem}>\n            <Text strong>Dark mode</Text>\n            <Switch\n              checked={mode === MODE.DARK}\n              onChange={e => {\n                setMode(e ? MODE.DARK : MODE.LIGHT)\n              }}\n            />\n          </div>\n\n          <div className={styles.settingsItem}>\n            <Text strong>Show back to top button</Text>\n            <Switch checked={backTop} onChange={setBackTop} />\n          </div>\n\n          {isMac && isElectron ? (\n            <div className={styles.settingsItem}>\n              <Text strong>Show app icon in dock</Text>\n              <Switch checked={dockIcon} onChange={setDockIcon} />\n            </div>\n          ) : null}\n\n          <Divider />\n\n          {isElectron ? (\n            <div className={styles.settingsItem}>\n              <Text strong>Automatic updates</Text>\n              <Switch checked={autoUpdate} onChange={setAutoUpdate} />\n            </div>\n          ) : null}\n\n          <div className={styles.settingsItem}>\n            <Text strong>Changelog</Text>\n            <Button icon={<IconExternalOpen />} onClick={() => open(URL.CHANGELOG)}>\n              Open\n            </Button>\n          </div>\n\n          <div className={styles.settingsItem}>\n            <Text strong>Experiencing a bug?</Text>\n            <Button icon={<IconExternalOpen />} onClick={() => open(URL.ISSUE)}>\n              Submit an issue\n            </Button>\n          </div>\n\n          <Divider />\n          <Text type=\"tertiary\" className={styles.version}>\n            {pkg.productName}, version {VERSION}\n          </Text>\n        </Space>\n      </div>\n    </Modal>\n  )\n}\n\nexport default SettingsModal\n"
  },
  {
    "path": "src/components/settings-modal/styles.scss",
    "content": ".settings-modal {\n  padding: 10px 0 20px;\n  box-sizing: border-box;\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n\n.settings-item {\n  width: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.version {\n  margin-bottom: 4px;\n}"
  },
  {
    "path": "src/components/skeleton-placeholder/index.js",
    "content": "import React from 'react'\nimport {Skeleton} from '@douyinfe/semi-ui'\n\nimport styles from './styles.scss'\n\nconst SkeletonPlaceholder = ({loading}) => {\n  return (\n    <Skeleton\n      className={styles.skeleton}\n      placeholder={<Skeleton.Image />}\n      loading={loading}\n      active\n    />\n  )\n}\n\nexport default SkeletonPlaceholder\n"
  },
  {
    "path": "src/components/skeleton-placeholder/styles.scss",
    "content": ".skeleton {\n  width: 100%;\n  height: 200;\n  border-radius: 6;\n  margin-bottom: 20;\n}\n"
  },
  {
    "path": "src/components/update-notification/index.js",
    "content": "import React, {useEffect, useState} from 'react'\nimport {Banner, Button} from '@douyinfe/semi-ui'\n\nimport {IPC_FUNCTION} from '@shared'\nimport {polyfill} from '@/utils'\n\nimport styles from './styles.scss'\n\nconst {send, receive} = polyfill\nconst {QUIT_AND_INSTALL, SHOW_UPDATE_NOTIFICATION} = IPC_FUNCTION\n\nconst UpdateNotification = () => {\n  const [visible, setVisible] = useState(false)\n\n  useEffect(() => {\n    receive(SHOW_UPDATE_NOTIFICATION, () => setVisible(true))\n  }, [])\n\n  if (!visible) return null\n\n  return (\n    <div className={styles.update}>\n      <Banner\n        type=\"info\"\n        className={styles.updateBanner}\n        fullMode={false}\n        icon={null}\n        closeIcon={null}\n        title=\"Update Available\"\n        description=\"An update has been downloaded. Would you like to restart to update now?\"\n      >\n        <div className={styles.updateBtns}>\n          <Button theme=\"solid\" type=\"tertiary\" onClick={() => setVisible(false)}>\n            Later\n          </Button>\n          <Button\n            className={styles.btnConfirm}\n            theme=\"solid\"\n            type=\"primary\"\n            onClick={() => send(QUIT_AND_INSTALL)}\n          >\n            Restart Now\n          </Button>\n        </div>\n      </Banner>\n    </div>\n  )\n}\n\nexport default UpdateNotification\n"
  },
  {
    "path": "src/components/update-notification/styles.scss",
    "content": ".update {\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  z-index: 99999;\n  width: 100vw;\n  background-color: var(--semi-color-bg-0);\n}\n\n.update-banner {\n  padding: 20px;\n}\n\n.update-btns {\n  display: flex;\n  justify-content: flex-end;\n}\n\n.btn-confirm {\n  margin-left: 10px;\n}\n"
  },
  {
    "path": "src/components/upper-container/index.js",
    "content": "import React, {useLayoutEffect, useState} from 'react'\n\nconst UpperContainer = ({children}) => {\n  const [footerHeight, setFooterHeight] = useState(0)\n\n  useLayoutEffect(() => {\n    const footerComponent = document.getElementById('footer')\n    setFooterHeight(footerComponent.offsetHeight)\n  }, [])\n\n  return <div style={{minHeight: `calc(100vh - ${footerHeight}px - 40px)`}}>{children}</div>\n}\n\nexport default UpperContainer\n"
  },
  {
    "path": "src/config/constants.js",
    "content": "import pkg from '@pkg'\nimport {isElectron as checkIsElectron} from '@/lib'\n\nexport const VERSION = pkg.version\n\nexport const TRENDING_TYPE = {\n  REPOSITORIES: 'Repositories',\n  DEVELOPERS: 'Developers',\n}\n\nexport const MODE = {\n  LIGHT: 'light',\n  DARK: 'dark',\n}\n\nexport const SINCE = {\n  DAILY: 'daily',\n  WEEKLY: 'weekly',\n  MONTHLY: 'monthly',\n}\n\nexport const SINCE_MAP = {\n  [SINCE.DAILY]: 'Today',\n  [SINCE.WEEKLY]: 'This week',\n  [SINCE.MONTHLY]: 'This month',\n}\n\nexport const SINCE_ARRAY = [\n  {name: SINCE_MAP[SINCE.DAILY], value: SINCE.DAILY},\n  {name: SINCE_MAP[SINCE.WEEKLY], value: SINCE.WEEKLY},\n  {name: SINCE_MAP[SINCE.MONTHLY], value: SINCE.MONTHLY},\n]\n\nexport const Z_INDEX = {\n  MODAL: 99999,\n  TOAST: 99999,\n  SELECT: 9999,\n}\n\nexport const URL = {\n  GITHUB: 'https://github.com',\n  CHANGELOG: 'https://github.com/meetyan/raise/releases',\n  ISSUE: 'https://github.com/meetyan/raise/issues',\n  SERVER: 'https://trending.curve.to',\n}\n\nexport const ALLOWED_TIME_OF_INACTIVITY = 1000 * 60 * 60 * 3 // 3 hours\n\nexport const isElectron = checkIsElectron()\n\nexport const isMac = window.navigator?.userAgentData?.platform.toUpperCase().includes('MAC')\n\nexport const isChrome = !!process.env.isChrome\n\nexport const isDev = !!process.env.WEBPACK_DEV\n"
  },
  {
    "path": "src/config/index.js",
    "content": "export {default as LANGUAGES} from './languages'\nexport {default as SPOKEN_LANGUAGES} from './spoken-languages'\nexport * from './constants'\n"
  },
  {
    "path": "src/config/languages.js",
    "content": "export default [\n  {urlParam: 'any', name: 'Any'},\n  {urlParam: '1c-enterprise', name: '1C Enterprise'},\n  {urlParam: 'abap', name: 'ABAP'},\n  {urlParam: 'abnf', name: 'ABNF'},\n  {urlParam: 'actionscript', name: 'ActionScript'},\n  {urlParam: 'ada', name: 'Ada'},\n  {urlParam: 'adobe-font-metrics', name: 'Adobe Font Metrics'},\n  {urlParam: 'agda', name: 'Agda'},\n  {urlParam: 'ags-script', name: 'AGS Script'},\n  {urlParam: 'alloy', name: 'Alloy'},\n  {urlParam: 'alpine-abuild', name: 'Alpine Abuild'},\n  {urlParam: 'ampl', name: 'AMPL'},\n  {urlParam: 'angelscript', name: 'AngelScript'},\n  {urlParam: 'ant-build-system', name: 'Ant Build System'},\n  {urlParam: 'antlr', name: 'ANTLR'},\n  {urlParam: 'apacheconf', name: 'ApacheConf'},\n  {urlParam: 'apex', name: 'Apex'},\n  {urlParam: 'api-blueprint', name: 'API Blueprint'},\n  {urlParam: 'apl', name: 'APL'},\n  {urlParam: 'apollo-guidance-computer', name: 'Apollo Guidance Computer'},\n  {urlParam: 'applescript', name: 'AppleScript'},\n  {urlParam: 'arc', name: 'Arc'},\n  {urlParam: 'asciidoc', name: 'AsciiDoc'},\n  {urlParam: 'asn.1', name: 'ASN.1'},\n  {urlParam: 'asp', name: 'ASP'},\n  {urlParam: 'aspectj', name: 'AspectJ'},\n  {urlParam: 'assembly', name: 'Assembly'},\n  {urlParam: 'ats', name: 'ATS'},\n  {urlParam: 'augeas', name: 'Augeas'},\n  {urlParam: 'autohotkey', name: 'AutoHotkey'},\n  {urlParam: 'autoit', name: 'AutoIt'},\n  {urlParam: 'awk', name: 'Awk'},\n  {urlParam: 'ballerina', name: 'Ballerina'},\n  {urlParam: 'batchfile', name: 'Batchfile'},\n  {urlParam: 'befunge', name: 'Befunge'},\n  {urlParam: 'bison', name: 'Bison'},\n  {urlParam: 'bitbake', name: 'BitBake'},\n  {urlParam: 'blade', name: 'Blade'},\n  {urlParam: 'blitzbasic', name: 'BlitzBasic'},\n  {urlParam: 'blitzmax', name: 'BlitzMax'},\n  {urlParam: 'bluespec', name: 'Bluespec'},\n  {urlParam: 'boo', name: 'Boo'},\n  {urlParam: 'brainfuck', name: 'Brainfuck'},\n  {urlParam: 'brightscript', name: 'Brightscript'},\n  {urlParam: 'bro', name: 'Bro'},\n  {urlParam: 'c', name: 'C'},\n  {urlParam: 'c%23', name: 'C#'},\n  {urlParam: 'c%2B%2B', name: 'C++'},\n  {urlParam: 'c-objdump', name: 'C-ObjDump'},\n  {urlParam: 'c2hs-haskell', name: 'C2hs Haskell'},\n  {urlParam: \"cap'n-proto\", name: \"Cap'n Proto\"},\n  {urlParam: 'cartocss', name: 'CartoCSS'},\n  {urlParam: 'ceylon', name: 'Ceylon'},\n  {urlParam: 'chapel', name: 'Chapel'},\n  {urlParam: 'charity', name: 'Charity'},\n  {urlParam: 'chuck', name: 'ChucK'},\n  {urlParam: 'cirru', name: 'Cirru'},\n  {urlParam: 'clarion', name: 'Clarion'},\n  {urlParam: 'clean', name: 'Clean'},\n  {urlParam: 'click', name: 'Click'},\n  {urlParam: 'clips', name: 'CLIPS'},\n  {urlParam: 'clojure', name: 'Clojure'},\n  {urlParam: 'closure-templates', name: 'Closure Templates'},\n  {urlParam: 'cmake', name: 'CMake'},\n  {urlParam: 'cobol', name: 'COBOL'},\n  {urlParam: 'coffeescript', name: 'CoffeeScript'},\n  {urlParam: 'coldfusion', name: 'ColdFusion'},\n  {urlParam: 'coldfusion-cfc', name: 'ColdFusion CFC'},\n  {urlParam: 'collada', name: 'COLLADA'},\n  {urlParam: 'common-lisp', name: 'Common Lisp'},\n  {urlParam: 'common-workflow-language', name: 'Common Workflow Language'},\n  {urlParam: 'component-pascal', name: 'Component Pascal'},\n  {urlParam: 'cool', name: 'Cool'},\n  {urlParam: 'coq', name: 'Coq'},\n  {urlParam: 'cpp-objdump', name: 'Cpp-ObjDump'},\n  {urlParam: 'creole', name: 'Creole'},\n  {urlParam: 'crystal', name: 'Crystal'},\n  {urlParam: 'cson', name: 'CSON'},\n  {urlParam: 'csound', name: 'Csound'},\n  {urlParam: 'csound-document', name: 'Csound Document'},\n  {urlParam: 'csound-score', name: 'Csound Score'},\n  {urlParam: 'css', name: 'CSS'},\n  {urlParam: 'csv', name: 'CSV'},\n  {urlParam: 'cuda', name: 'Cuda'},\n  {urlParam: 'cweb', name: 'CWeb'},\n  {urlParam: 'cycript', name: 'Cycript'},\n  {urlParam: 'cython', name: 'Cython'},\n  {urlParam: 'd', name: 'D'},\n  {urlParam: 'd-objdump', name: 'D-ObjDump'},\n  {urlParam: 'darcs-patch', name: 'Darcs Patch'},\n  {urlParam: 'dart', name: 'Dart'},\n  {urlParam: 'dataweave', name: 'DataWeave'},\n  {urlParam: 'desktop', name: 'desktop'},\n  {urlParam: 'diff', name: 'Diff'},\n  {urlParam: 'digital-command-language', name: 'DIGITAL Command Language'},\n  {urlParam: 'dm', name: 'DM'},\n  {urlParam: 'dns-zone', name: 'DNS Zone'},\n  {urlParam: 'dockerfile', name: 'Dockerfile'},\n  {urlParam: 'dogescript', name: 'Dogescript'},\n  {urlParam: 'dtrace', name: 'DTrace'},\n  {urlParam: 'dylan', name: 'Dylan'},\n  {urlParam: 'e', name: 'E'},\n  {urlParam: 'eagle', name: 'Eagle'},\n  {urlParam: 'easybuild', name: 'Easybuild'},\n  {urlParam: 'ebnf', name: 'EBNF'},\n  {urlParam: 'ec', name: 'eC'},\n  {urlParam: 'ecere-projects', name: 'Ecere Projects'},\n  {urlParam: 'ecl', name: 'ECL'},\n  {urlParam: 'eclipse', name: 'ECLiPSe'},\n  {urlParam: 'edje-data-collection', name: 'Edje Data Collection'},\n  {urlParam: 'edn', name: 'edn'},\n  {urlParam: 'eiffel', name: 'Eiffel'},\n  {urlParam: 'ejs', name: 'EJS'},\n  {urlParam: 'elixir', name: 'Elixir'},\n  {urlParam: 'elm', name: 'Elm'},\n  {urlParam: 'emacs-lisp', name: 'Emacs Lisp'},\n  {urlParam: 'emberscript', name: 'EmberScript'},\n  {urlParam: 'eq', name: 'EQ'},\n  {urlParam: 'erlang', name: 'Erlang'},\n  {urlParam: 'f%23', name: 'F#'},\n  {urlParam: 'factor', name: 'Factor'},\n  {urlParam: 'fancy', name: 'Fancy'},\n  {urlParam: 'fantom', name: 'Fantom'},\n  {urlParam: 'filebench-wml', name: 'Filebench WML'},\n  {urlParam: 'filterscript', name: 'Filterscript'},\n  {urlParam: 'fish', name: 'fish'},\n  {urlParam: 'flux', name: 'FLUX'},\n  {urlParam: 'formatted', name: 'Formatted'},\n  {urlParam: 'forth', name: 'Forth'},\n  {urlParam: 'fortran', name: 'Fortran'},\n  {urlParam: 'freemarker', name: 'FreeMarker'},\n  {urlParam: 'frege', name: 'Frege'},\n  {urlParam: 'g-code', name: 'G-code'},\n  {urlParam: 'game-maker-language', name: 'Game Maker Language'},\n  {urlParam: 'gams', name: 'GAMS'},\n  {urlParam: 'gap', name: 'GAP'},\n  {urlParam: 'gcc-machine-description', name: 'GCC Machine Description'},\n  {urlParam: 'gdb', name: 'GDB'},\n  {urlParam: 'gdscript', name: 'GDScript'},\n  {urlParam: 'genie', name: 'Genie'},\n  {urlParam: 'genshi', name: 'Genshi'},\n  {urlParam: 'gentoo-ebuild', name: 'Gentoo Ebuild'},\n  {urlParam: 'gentoo-eclass', name: 'Gentoo Eclass'},\n  {urlParam: 'gerber-image', name: 'Gerber Image'},\n  {urlParam: 'gettext-catalog', name: 'Gettext Catalog'},\n  {urlParam: 'gherkin', name: 'Gherkin'},\n  {urlParam: 'glsl', name: 'GLSL'},\n  {urlParam: 'glyph', name: 'Glyph'},\n  {urlParam: 'gn', name: 'GN'},\n  {urlParam: 'gnuplot', name: 'Gnuplot'},\n  {urlParam: 'go', name: 'Go'},\n  {urlParam: 'golo', name: 'Golo'},\n  {urlParam: 'gosu', name: 'Gosu'},\n  {urlParam: 'grace', name: 'Grace'},\n  {urlParam: 'gradle', name: 'Gradle'},\n  {urlParam: 'grammatical-framework', name: 'Grammatical Framework'},\n  {urlParam: 'graph-modeling-language', name: 'Graph Modeling Language'},\n  {urlParam: 'graphql', name: 'GraphQL'},\n  {urlParam: 'graphviz-(dot)', name: 'Graphviz (DOT)'},\n  {urlParam: 'groovy', name: 'Groovy'},\n  {urlParam: 'groovy-server-pages', name: 'Groovy Server Pages'},\n  {urlParam: 'hack', name: 'Hack'},\n  {urlParam: 'haml', name: 'Haml'},\n  {urlParam: 'handlebars', name: 'Handlebars'},\n  {urlParam: 'harbour', name: 'Harbour'},\n  {urlParam: 'haskell', name: 'Haskell'},\n  {urlParam: 'haxe', name: 'Haxe'},\n  {urlParam: 'hcl', name: 'HCL'},\n  {urlParam: 'hlsl', name: 'HLSL'},\n  {urlParam: 'html', name: 'HTML'},\n  {urlParam: 'html%2Bdjango', name: 'HTML+Django'},\n  {urlParam: 'html%2Becr', name: 'HTML+ECR'},\n  {urlParam: 'html%2Beex', name: 'HTML+EEX'},\n  {urlParam: 'html%2Berb', name: 'HTML+ERB'},\n  {urlParam: 'html%2Bphp', name: 'HTML+PHP'},\n  {urlParam: 'http', name: 'HTTP'},\n  {urlParam: 'hy', name: 'Hy'},\n  {urlParam: 'hyphy', name: 'HyPhy'},\n  {urlParam: 'idl', name: 'IDL'},\n  {urlParam: 'idris', name: 'Idris'},\n  {urlParam: 'igor-pro', name: 'IGOR Pro'},\n  {urlParam: 'inform-7', name: 'Inform 7'},\n  {urlParam: 'ini', name: 'INI'},\n  {urlParam: 'inno-setup', name: 'Inno Setup'},\n  {urlParam: 'io', name: 'Io'},\n  {urlParam: 'ioke', name: 'Ioke'},\n  {urlParam: 'irc-log', name: 'IRC log'},\n  {urlParam: 'isabelle', name: 'Isabelle'},\n  {urlParam: 'isabelle-root', name: 'Isabelle ROOT'},\n  {urlParam: 'j', name: 'J'},\n  {urlParam: 'jasmin', name: 'Jasmin'},\n  {urlParam: 'java', name: 'Java'},\n  {urlParam: 'java-server-pages', name: 'Java Server Pages'},\n  {urlParam: 'javascript', name: 'JavaScript'},\n  {urlParam: 'jflex', name: 'JFlex'},\n  {urlParam: 'jison', name: 'Jison'},\n  {urlParam: 'jison-lex', name: 'Jison Lex'},\n  {urlParam: 'jolie', name: 'Jolie'},\n  {urlParam: 'json', name: 'JSON'},\n  {urlParam: 'json5', name: 'JSON5'},\n  {urlParam: 'jsoniq', name: 'JSONiq'},\n  {urlParam: 'jsonld', name: 'JSONLD'},\n  {urlParam: 'jsx', name: 'JSX'},\n  {urlParam: 'julia', name: 'Julia'},\n  {urlParam: 'jupyter-notebook', name: 'Jupyter Notebook'},\n  {urlParam: 'kicad-layout', name: 'KiCad Layout'},\n  {urlParam: 'kicad-legacy-layout', name: 'KiCad Legacy Layout'},\n  {urlParam: 'kicad-schematic', name: 'KiCad Schematic'},\n  {urlParam: 'kit', name: 'Kit'},\n  {urlParam: 'kotlin', name: 'Kotlin'},\n  {urlParam: 'krl', name: 'KRL'},\n  {urlParam: 'labview', name: 'LabVIEW'},\n  {urlParam: 'lasso', name: 'Lasso'},\n  {urlParam: 'latte', name: 'Latte'},\n  {urlParam: 'lean', name: 'Lean'},\n  {urlParam: 'less', name: 'Less'},\n  {urlParam: 'lex', name: 'Lex'},\n  {urlParam: 'lfe', name: 'LFE'},\n  {urlParam: 'lilypond', name: 'LilyPond'},\n  {urlParam: 'limbo', name: 'Limbo'},\n  {urlParam: 'linker-script', name: 'Linker Script'},\n  {urlParam: 'linux-kernel-module', name: 'Linux Kernel Module'},\n  {urlParam: 'liquid', name: 'Liquid'},\n  {urlParam: 'literate-agda', name: 'Literate Agda'},\n  {urlParam: 'literate-coffeescript', name: 'Literate CoffeeScript'},\n  {urlParam: 'literate-haskell', name: 'Literate Haskell'},\n  {urlParam: 'livescript', name: 'LiveScript'},\n  {urlParam: 'llvm', name: 'LLVM'},\n  {urlParam: 'logos', name: 'Logos'},\n  {urlParam: 'logtalk', name: 'Logtalk'},\n  {urlParam: 'lolcode', name: 'LOLCODE'},\n  {urlParam: 'lookml', name: 'LookML'},\n  {urlParam: 'loomscript', name: 'LoomScript'},\n  {urlParam: 'lsl', name: 'LSL'},\n  {urlParam: 'lua', name: 'Lua'},\n  {urlParam: 'm', name: 'M'},\n  {urlParam: 'm4', name: 'M4'},\n  {urlParam: 'm4sugar', name: 'M4Sugar'},\n  {urlParam: 'makefile', name: 'Makefile'},\n  {urlParam: 'mako', name: 'Mako'},\n  {urlParam: 'markdown', name: 'Markdown'},\n  {urlParam: 'marko', name: 'Marko'},\n  {urlParam: 'mask', name: 'Mask'},\n  {urlParam: 'mathematica', name: 'Mathematica'},\n  {urlParam: 'matlab', name: 'Matlab'},\n  {urlParam: 'maven-pom', name: 'Maven POM'},\n  {urlParam: 'max', name: 'Max'},\n  {urlParam: 'maxscript', name: 'MAXScript'},\n  {urlParam: 'mediawiki', name: 'MediaWiki'},\n  {urlParam: 'mercury', name: 'Mercury'},\n  {urlParam: 'meson', name: 'Meson'},\n  {urlParam: 'metal', name: 'Metal'},\n  {urlParam: 'minid', name: 'MiniD'},\n  {urlParam: 'mirah', name: 'Mirah'},\n  {urlParam: 'modelica', name: 'Modelica'},\n  {urlParam: 'modula-2', name: 'Modula-2'},\n  {urlParam: 'module-management-system', name: 'Module Management System'},\n  {urlParam: 'monkey', name: 'Monkey'},\n  {urlParam: 'moocode', name: 'Moocode'},\n  {urlParam: 'moonscript', name: 'MoonScript'},\n  {urlParam: 'mql4', name: 'MQL4'},\n  {urlParam: 'mql5', name: 'MQL5'},\n  {urlParam: 'mtml', name: 'MTML'},\n  {urlParam: 'muf', name: 'MUF'},\n  {urlParam: 'mupad', name: 'mupad'},\n  {urlParam: 'myghty', name: 'Myghty'},\n  {urlParam: 'ncl', name: 'NCL'},\n  {urlParam: 'nearley', name: 'Nearley'},\n  {urlParam: 'nemerle', name: 'Nemerle'},\n  {urlParam: 'nesc', name: 'nesC'},\n  {urlParam: 'netlinx', name: 'NetLinx'},\n  {urlParam: 'netlinx%2Berb', name: 'NetLinx+ERB'},\n  {urlParam: 'netlogo', name: 'NetLogo'},\n  {urlParam: 'newlisp', name: 'NewLisp'},\n  {urlParam: 'nextflow', name: 'Nextflow'},\n  {urlParam: 'nginx', name: 'Nginx'},\n  {urlParam: 'nim', name: 'Nim'},\n  {urlParam: 'ninja', name: 'Ninja'},\n  {urlParam: 'nit', name: 'Nit'},\n  {urlParam: 'nix', name: 'Nix'},\n  {urlParam: 'nl', name: 'NL'},\n  {urlParam: 'nsis', name: 'NSIS'},\n  {urlParam: 'nu', name: 'Nu'},\n  {urlParam: 'numpy', name: 'NumPy'},\n  {urlParam: 'objdump', name: 'ObjDump'},\n  {urlParam: 'objective-c', name: 'Objective-C'},\n  {urlParam: 'objective-c%2B%2B', name: 'Objective-C++'},\n  {urlParam: 'objective-j', name: 'Objective-J'},\n  {urlParam: 'ocaml', name: 'OCaml'},\n  {urlParam: 'omgrofl', name: 'Omgrofl'},\n  {urlParam: 'ooc', name: 'ooc'},\n  {urlParam: 'opa', name: 'Opa'},\n  {urlParam: 'opal', name: 'Opal'},\n  {urlParam: 'opencl', name: 'OpenCL'},\n  {urlParam: 'openedge-abl', name: 'OpenEdge ABL'},\n  {urlParam: 'openrc-runscript', name: 'OpenRC runscript'},\n  {urlParam: 'openscad', name: 'OpenSCAD'},\n  {urlParam: 'opentype-feature-file', name: 'OpenType Feature File'},\n  {urlParam: 'org', name: 'Org'},\n  {urlParam: 'ox', name: 'Ox'},\n  {urlParam: 'oxygene', name: 'Oxygene'},\n  {urlParam: 'oz', name: 'Oz'},\n  {urlParam: 'p4', name: 'P4'},\n  {urlParam: 'pan', name: 'Pan'},\n  {urlParam: 'papyrus', name: 'Papyrus'},\n  {urlParam: 'parrot', name: 'Parrot'},\n  {urlParam: 'parrot-assembly', name: 'Parrot Assembly'},\n  {urlParam: 'parrot-internal-representation', name: 'Parrot Internal Representation'},\n  {urlParam: 'pascal', name: 'Pascal'},\n  {urlParam: 'pawn', name: 'PAWN'},\n  {urlParam: 'pep8', name: 'Pep8'},\n  {urlParam: 'perl', name: 'Perl'},\n  {urlParam: 'perl-6', name: 'Perl 6'},\n  {urlParam: 'php', name: 'PHP'},\n  {urlParam: 'pic', name: 'Pic'},\n  {urlParam: 'pickle', name: 'Pickle'},\n  {urlParam: 'picolisp', name: 'PicoLisp'},\n  {urlParam: 'piglatin', name: 'PigLatin'},\n  {urlParam: 'pike', name: 'Pike'},\n  {urlParam: 'plpgsql', name: 'PLpgSQL'},\n  {urlParam: 'plsql', name: 'PLSQL'},\n  {urlParam: 'pod', name: 'Pod'},\n  {urlParam: 'pogoscript', name: 'PogoScript'},\n  {urlParam: 'pony', name: 'Pony'},\n  {urlParam: 'postcss', name: 'PostCSS'},\n  {urlParam: 'postscript', name: 'PostScript'},\n  {urlParam: 'pov-ray-sdl', name: 'POV-Ray SDL'},\n  {urlParam: 'powerbuilder', name: 'PowerBuilder'},\n  {urlParam: 'powershell', name: 'PowerShell'},\n  {urlParam: 'processing', name: 'Processing'},\n  {urlParam: 'prolog', name: 'Prolog'},\n  {urlParam: 'propeller-spin', name: 'Propeller Spin'},\n  {urlParam: 'protocol-buffer', name: 'Protocol Buffer'},\n  {urlParam: 'public-key', name: 'Public Key'},\n  {urlParam: 'pug', name: 'Pug'},\n  {urlParam: 'puppet', name: 'Puppet'},\n  {urlParam: 'pure-data', name: 'Pure Data'},\n  {urlParam: 'purebasic', name: 'PureBasic'},\n  {urlParam: 'purescript', name: 'PureScript'},\n  {urlParam: 'python', name: 'Python'},\n  {urlParam: 'python-console', name: 'Python console'},\n  {urlParam: 'python-traceback', name: 'Python traceback'},\n  {urlParam: 'qmake', name: 'QMake'},\n  {urlParam: 'qml', name: 'QML'},\n  {urlParam: 'r', name: 'R'},\n  {urlParam: 'racket', name: 'Racket'},\n  {urlParam: 'ragel', name: 'Ragel'},\n  {urlParam: 'raml', name: 'RAML'},\n  {urlParam: 'rascal', name: 'Rascal'},\n  {urlParam: 'raw-token-data', name: 'Raw token data'},\n  {urlParam: 'rdoc', name: 'RDoc'},\n  {urlParam: 'realbasic', name: 'REALbasic'},\n  {urlParam: 'reason', name: 'Reason'},\n  {urlParam: 'rebol', name: 'Rebol'},\n  {urlParam: 'red', name: 'Red'},\n  {urlParam: 'redcode', name: 'Redcode'},\n  {urlParam: 'regular-expression', name: 'Regular Expression'},\n  {urlParam: \"ren'py\", name: \"Ren'Py\"},\n  {urlParam: 'renderscript', name: 'RenderScript'},\n  {urlParam: 'restructuredtext', name: 'reStructuredText'},\n  {urlParam: 'rexx', name: 'REXX'},\n  {urlParam: 'rhtml', name: 'RHTML'},\n  {urlParam: 'ring', name: 'Ring'},\n  {urlParam: 'rmarkdown', name: 'RMarkdown'},\n  {urlParam: 'robotframework', name: 'RobotFramework'},\n  {urlParam: 'roff', name: 'Roff'},\n  {urlParam: 'rouge', name: 'Rouge'},\n  {urlParam: 'rpc', name: 'RPC'},\n  {urlParam: 'rpm-spec', name: 'RPM Spec'},\n  {urlParam: 'ruby', name: 'Ruby'},\n  {urlParam: 'runoff', name: 'RUNOFF'},\n  {urlParam: 'rust', name: 'Rust'},\n  {urlParam: 'sage', name: 'Sage'},\n  {urlParam: 'saltstack', name: 'SaltStack'},\n  {urlParam: 'sas', name: 'SAS'},\n  {urlParam: 'sass', name: 'Sass'},\n  {urlParam: 'scala', name: 'Scala'},\n  {urlParam: 'scaml', name: 'Scaml'},\n  {urlParam: 'scheme', name: 'Scheme'},\n  {urlParam: 'scilab', name: 'Scilab'},\n  {urlParam: 'scss', name: 'SCSS'},\n  {urlParam: 'sed', name: 'sed'},\n  {urlParam: 'self', name: 'Self'},\n  {urlParam: 'shaderlab', name: 'ShaderLab'},\n  {urlParam: 'shell', name: 'Shell'},\n  {urlParam: 'shellsession', name: 'ShellSession'},\n  {urlParam: 'shen', name: 'Shen'},\n  {urlParam: 'slash', name: 'Slash'},\n  {urlParam: 'slim', name: 'Slim'},\n  {urlParam: 'smali', name: 'Smali'},\n  {urlParam: 'smalltalk', name: 'Smalltalk'},\n  {urlParam: 'smarty', name: 'Smarty'},\n  {urlParam: 'smt', name: 'SMT'},\n  {urlParam: 'solidity', name: 'Solidity'},\n  {urlParam: 'sourcepawn', name: 'SourcePawn'},\n  {urlParam: 'sparql', name: 'SPARQL'},\n  {urlParam: 'spline-font-database', name: 'Spline Font Database'},\n  {urlParam: 'sqf', name: 'SQF'},\n  {urlParam: 'sql', name: 'SQL'},\n  {urlParam: 'sqlpl', name: 'SQLPL'},\n  {urlParam: 'squirrel', name: 'Squirrel'},\n  {urlParam: 'srecode-template', name: 'SRecode Template'},\n  {urlParam: 'stan', name: 'Stan'},\n  {urlParam: 'standard-ml', name: 'Standard ML'},\n  {urlParam: 'stata', name: 'Stata'},\n  {urlParam: 'ston', name: 'STON'},\n  {urlParam: 'stylus', name: 'Stylus'},\n  {urlParam: 'sublime-text-config', name: 'Sublime Text Config'},\n  {urlParam: 'subrip-text', name: 'SubRip Text'},\n  {urlParam: 'sugarss', name: 'SugarSS'},\n  {urlParam: 'supercollider', name: 'SuperCollider'},\n  {urlParam: 'svg', name: 'SVG'},\n  {urlParam: 'swift', name: 'Swift'},\n  {urlParam: 'systemverilog', name: 'SystemVerilog'},\n  {urlParam: 'tcl', name: 'Tcl'},\n  {urlParam: 'tcsh', name: 'Tcsh'},\n  {urlParam: 'tea', name: 'Tea'},\n  {urlParam: 'terra', name: 'Terra'},\n  {urlParam: 'tex', name: 'TeX'},\n  {urlParam: 'text', name: 'Text'},\n  {urlParam: 'textile', name: 'Textile'},\n  {urlParam: 'thrift', name: 'Thrift'},\n  {urlParam: 'ti-program', name: 'TI Program'},\n  {urlParam: 'tla', name: 'TLA'},\n  {urlParam: 'toml', name: 'TOML'},\n  {urlParam: 'turing', name: 'Turing'},\n  {urlParam: 'turtle', name: 'Turtle'},\n  {urlParam: 'twig', name: 'Twig'},\n  {urlParam: 'txl', name: 'TXL'},\n  {urlParam: 'type-language', name: 'Type Language'},\n  {urlParam: 'typescript', name: 'TypeScript'},\n  {urlParam: 'unified-parallel-c', name: 'Unified Parallel C'},\n  {urlParam: 'unity3d-asset', name: 'Unity3D Asset'},\n  {urlParam: 'unix-assembly', name: 'Unix Assembly'},\n  {urlParam: 'uno', name: 'Uno'},\n  {urlParam: 'unrealscript', name: 'UnrealScript'},\n  {urlParam: 'urweb', name: 'UrWeb'},\n  {urlParam: 'vala', name: 'Vala'},\n  {urlParam: 'vcl', name: 'VCL'},\n  {urlParam: 'verilog', name: 'Verilog'},\n  {urlParam: 'vhdl', name: 'VHDL'},\n  {urlParam: 'vim-script', name: 'Vim script'},\n  {urlParam: 'visual-basic', name: 'Visual Basic'},\n  {urlParam: 'volt', name: 'Volt'},\n  {urlParam: 'vue', name: 'Vue'},\n  {urlParam: 'wavefront-material', name: 'Wavefront Material'},\n  {urlParam: 'wavefront-object', name: 'Wavefront Object'},\n  {urlParam: 'wdl', name: 'wdl'},\n  {urlParam: 'web-ontology-language', name: 'Web Ontology Language'},\n  {urlParam: 'webassembly', name: 'WebAssembly'},\n  {urlParam: 'webidl', name: 'WebIDL'},\n  {urlParam: 'wisp', name: 'wisp'},\n  {urlParam: 'world-of-warcraft-addon-data', name: 'World of Warcraft Addon Data'},\n  {urlParam: 'x10', name: 'X10'},\n  {urlParam: 'xbase', name: 'xBase'},\n  {urlParam: 'xc', name: 'XC'},\n  {urlParam: 'xcompose', name: 'XCompose'},\n  {urlParam: 'xml', name: 'XML'},\n  {urlParam: 'xojo', name: 'Xojo'},\n  {urlParam: 'xpages', name: 'XPages'},\n  {urlParam: 'xpm', name: 'XPM'},\n  {urlParam: 'xproc', name: 'XProc'},\n  {urlParam: 'xquery', name: 'XQuery'},\n  {urlParam: 'xs', name: 'XS'},\n  {urlParam: 'xslt', name: 'XSLT'},\n  {urlParam: 'xtend', name: 'Xtend'},\n  {urlParam: 'yacc', name: 'Yacc'},\n  {urlParam: 'yaml', name: 'YAML'},\n  {urlParam: 'yang', name: 'YANG'},\n  {urlParam: 'yara', name: 'YARA'},\n  {urlParam: 'zephir', name: 'Zephir'},\n  {urlParam: 'zimpl', name: 'Zimpl'},\n]\n"
  },
  {
    "path": "src/config/spoken-languages.js",
    "content": "export default [\n  {urlParam: 'any', name: 'Any'},\n  {urlParam: 'ab', name: 'Abkhazian'},\n  {urlParam: 'aa', name: 'Afar'},\n  {urlParam: 'af', name: 'Afrikaans'},\n  {urlParam: 'ak', name: 'Akan'},\n  {urlParam: 'sq', name: 'Albanian'},\n  {urlParam: 'am', name: 'Amharic'},\n  {urlParam: 'ar', name: 'Arabic'},\n  {urlParam: 'an', name: 'Aragonese'},\n  {urlParam: 'hy', name: 'Armenian'},\n  {urlParam: 'as', name: 'Assamese'},\n  {urlParam: 'av', name: 'Avaric'},\n  {urlParam: 'ae', name: 'Avestan'},\n  {urlParam: 'ay', name: 'Aymara'},\n  {urlParam: 'az', name: 'Azerbaijani'},\n  {urlParam: 'bm', name: 'Bambara'},\n  {urlParam: 'ba', name: 'Bashkir'},\n  {urlParam: 'eu', name: 'Basque'},\n  {urlParam: 'be', name: 'Belarusian'},\n  {urlParam: 'bn', name: 'Bengali'},\n  {urlParam: 'bh', name: 'Bihari languages'},\n  {urlParam: 'bi', name: 'Bislama'},\n  {urlParam: 'bs', name: 'Bosnian'},\n  {urlParam: 'br', name: 'Breton'},\n  {urlParam: 'bg', name: 'Bulgarian'},\n  {urlParam: 'my', name: 'Burmese'},\n  {urlParam: 'ca', name: 'Catalan, Valencian'},\n  {urlParam: 'ch', name: 'Chamorro'},\n  {urlParam: 'ce', name: 'Chechen'},\n  {urlParam: 'ny', name: 'Chichewa, Chewa, Nyanja'},\n  {urlParam: 'zh', name: 'Chinese'},\n  {urlParam: 'cv', name: 'Chuvash'},\n  {urlParam: 'kw', name: 'Cornish'},\n  {urlParam: 'co', name: 'Corsican'},\n  {urlParam: 'cr', name: 'Cree'},\n  {urlParam: 'hr', name: 'Croatian'},\n  {urlParam: 'cs', name: 'Czech'},\n  {urlParam: 'da', name: 'Danish'},\n  {urlParam: 'dv', name: 'Divehi, Dhivehi, Maldivian'},\n  {urlParam: 'nl', name: 'Dutch, Flemish'},\n  {urlParam: 'dz', name: 'Dzongkha'},\n  {urlParam: 'en', name: 'English'},\n  {urlParam: 'eo', name: 'Esperanto'},\n  {urlParam: 'et', name: 'Estonian'},\n  {urlParam: 'ee', name: 'Ewe'},\n  {urlParam: 'fo', name: 'Faroese'},\n  {urlParam: 'fj', name: 'Fijian'},\n  {urlParam: 'fi', name: 'Finnish'},\n  {urlParam: 'fr', name: 'French'},\n  {urlParam: 'ff', name: 'Fulah'},\n  {urlParam: 'gl', name: 'Galician'},\n  {urlParam: 'ka', name: 'Georgian'},\n  {urlParam: 'de', name: 'German'},\n  {urlParam: 'el', name: 'Greek, Modern'},\n  {urlParam: 'gn', name: 'Guarani'},\n  {urlParam: 'gu', name: 'Gujarati'},\n  {urlParam: 'ht', name: 'Haitian, Haitian Creole'},\n  {urlParam: 'ha', name: 'Hausa'},\n  {urlParam: 'he', name: 'Hebrew'},\n  {urlParam: 'hz', name: 'Herero'},\n  {urlParam: 'hi', name: 'Hindi'},\n  {urlParam: 'ho', name: 'Hiri Motu'},\n  {urlParam: 'hu', name: 'Hungarian'},\n  {urlParam: 'ia', name: 'Interlingua (International Auxil...'},\n  {urlParam: 'id', name: 'Indonesian'},\n  {urlParam: 'ie', name: 'Interlingue, Occidental'},\n  {urlParam: 'ga', name: 'Irish'},\n  {urlParam: 'ig', name: 'Igbo'},\n  {urlParam: 'ik', name: 'Inupiaq'},\n  {urlParam: 'io', name: 'Ido'},\n  {urlParam: 'is', name: 'Icelandic'},\n  {urlParam: 'it', name: 'Italian'},\n  {urlParam: 'iu', name: 'Inuktitut'},\n  {urlParam: 'ja', name: 'Japanese'},\n  {urlParam: 'jv', name: 'Javanese'},\n  {urlParam: 'kl', name: 'Kalaallisut, Greenlandic'},\n  {urlParam: 'kn', name: 'Kannada'},\n  {urlParam: 'kr', name: 'Kanuri'},\n  {urlParam: 'ks', name: 'Kashmiri'},\n  {urlParam: 'kk', name: 'Kazakh'},\n  {urlParam: 'km', name: 'Central Khmer'},\n  {urlParam: 'ki', name: 'Kikuyu, Gikuyu'},\n  {urlParam: 'rw', name: 'Kinyarwanda'},\n  {urlParam: 'ky', name: 'Kirghiz, Kyrgyz'},\n  {urlParam: 'kv', name: 'Komi'},\n  {urlParam: 'kg', name: 'Kongo'},\n  {urlParam: 'ko', name: 'Korean'},\n  {urlParam: 'ku', name: 'Kurdish'},\n  {urlParam: 'kj', name: 'Kuanyama, Kwanyama'},\n  {urlParam: 'la', name: 'Latin'},\n  {urlParam: 'lb', name: 'Luxembourgish, Letzeburgesch'},\n  {urlParam: 'lg', name: 'Ganda'},\n  {urlParam: 'li', name: 'Limburgan, Limburger, Limburgish'},\n  {urlParam: 'ln', name: 'Lingala'},\n  {urlParam: 'lo', name: 'Lao'},\n  {urlParam: 'lt', name: 'Lithuanian'},\n  {urlParam: 'lu', name: 'Luba-Katanga'},\n  {urlParam: 'lv', name: 'Latvian'},\n  {urlParam: 'gv', name: 'Manx'},\n  {urlParam: 'mk', name: 'Macedonian'},\n  {urlParam: 'mg', name: 'Malagasy'},\n  {urlParam: 'ms', name: 'Malay'},\n  {urlParam: 'ml', name: 'Malayalam'},\n  {urlParam: 'mt', name: 'Maltese'},\n  {urlParam: 'mi', name: 'Maori'},\n  {urlParam: 'mr', name: 'Marathi'},\n  {urlParam: 'mh', name: 'Marshallese'},\n  {urlParam: 'mn', name: 'Mongolian'},\n  {urlParam: 'na', name: 'Nauru'},\n  {urlParam: 'nv', name: 'Navajo, Navaho'},\n  {urlParam: 'nd', name: 'North Ndebele'},\n  {urlParam: 'ne', name: 'Nepali'},\n  {urlParam: 'ng', name: 'Ndonga'},\n  {urlParam: 'nb', name: 'Norwegian Bokmål'},\n  {urlParam: 'nn', name: 'Norwegian Nynorsk'},\n  {urlParam: 'no', name: 'Norwegian'},\n  {urlParam: 'ii', name: 'Sichuan Yi, Nuosu'},\n  {urlParam: 'nr', name: 'South Ndebele'},\n  {urlParam: 'oc', name: 'Occitan'},\n  {urlParam: 'oj', name: 'Ojibwa'},\n  {urlParam: 'cu', name: 'Church Slavic, Old Slavonic, Chu...'},\n  {urlParam: 'om', name: 'Oromo'},\n  {urlParam: 'or', name: 'Oriya'},\n  {urlParam: 'os', name: 'Ossetian, Ossetic'},\n  {urlParam: 'pa', name: 'Punjabi, Panjabi'},\n  {urlParam: 'pi', name: 'Pali'},\n  {urlParam: 'fa', name: 'Persian'},\n  {urlParam: 'pl', name: 'Polish'},\n  {urlParam: 'ps', name: 'Pashto, Pushto'},\n  {urlParam: 'pt', name: 'Portuguese'},\n  {urlParam: 'qu', name: 'Quechua'},\n  {urlParam: 'rm', name: 'Romansh'},\n  {urlParam: 'rn', name: 'Rundi'},\n  {urlParam: 'ro', name: 'Romanian, Moldavian, Moldovan'},\n  {urlParam: 'ru', name: 'Russian'},\n  {urlParam: 'sa', name: 'Sanskrit'},\n  {urlParam: 'sc', name: 'Sardinian'},\n  {urlParam: 'sd', name: 'Sindhi'},\n  {urlParam: 'se', name: 'Northern Sami'},\n  {urlParam: 'sm', name: 'Samoan'},\n  {urlParam: 'sg', name: 'Sango'},\n  {urlParam: 'sr', name: 'Serbian'},\n  {urlParam: 'gd', name: 'Gaelic, Scottish Gaelic'},\n  {urlParam: 'sn', name: 'Shona'},\n  {urlParam: 'si', name: 'Sinhala, Sinhalese'},\n  {urlParam: 'sk', name: 'Slovak'},\n  {urlParam: 'sl', name: 'Slovenian'},\n  {urlParam: 'so', name: 'Somali'},\n  {urlParam: 'st', name: 'Southern Sotho'},\n  {urlParam: 'es', name: 'Spanish, Castilian'},\n  {urlParam: 'su', name: 'Sundanese'},\n  {urlParam: 'sw', name: 'Swahili'},\n  {urlParam: 'ss', name: 'Swati'},\n  {urlParam: 'sv', name: 'Swedish'},\n  {urlParam: 'ta', name: 'Tamil'},\n  {urlParam: 'te', name: 'Telugu'},\n  {urlParam: 'tg', name: 'Tajik'},\n  {urlParam: 'th', name: 'Thai'},\n  {urlParam: 'ti', name: 'Tigrinya'},\n  {urlParam: 'bo', name: 'Tibetan'},\n  {urlParam: 'tk', name: 'Turkmen'},\n  {urlParam: 'tl', name: 'Tagalog'},\n  {urlParam: 'tn', name: 'Tswana'},\n  {urlParam: 'to', name: 'Tonga (Tonga Islands)'},\n  {urlParam: 'tr', name: 'Turkish'},\n  {urlParam: 'ts', name: 'Tsonga'},\n  {urlParam: 'tt', name: 'Tatar'},\n  {urlParam: 'tw', name: 'Twi'},\n  {urlParam: 'ty', name: 'Tahitian'},\n  {urlParam: 'ug', name: 'Uighur, Uyghur'},\n  {urlParam: 'uk', name: 'Ukrainian'},\n  {urlParam: 'ur', name: 'Urdu'},\n  {urlParam: 'uz', name: 'Uzbek'},\n  {urlParam: 've', name: 'Venda'},\n  {urlParam: 'vi', name: 'Vietnamese'},\n  {urlParam: 'vo', name: 'Volapük'},\n  {urlParam: 'wa', name: 'Walloon'},\n  {urlParam: 'cy', name: 'Welsh'},\n  {urlParam: 'wo', name: 'Wolof'},\n  {urlParam: 'fy', name: 'Western Frisian'},\n  {urlParam: 'xh', name: 'Xhosa'},\n  {urlParam: 'yi', name: 'Yiddish'},\n  {urlParam: 'yo', name: 'Yoruba'},\n  {urlParam: 'za', name: 'Zhuang, Chuang'},\n  {urlParam: 'zu', name: 'Zulu'},\n]\n"
  },
  {
    "path": "src/hooks/index.js",
    "content": "export {default as useMode} from './use-mode'\nexport {default as useDockIcon} from './use-dock-icon'\nexport {default as useOutsideClick} from './use-outside-click'\nexport {default as useScrollPosition} from './use-scroll-position'\nexport * from './use-context-props'\n"
  },
  {
    "path": "src/hooks/use-context-props.js",
    "content": "import {useContextProp} from '@/app-context'\n\nimport {STORAGE_KEY} from '@shared'\n\nexport const useTrendingType = () => {\n  return useContextProp(STORAGE_KEY.TRENDING_TYPE)\n}\n\nexport const useBackTop = () => {\n  return useContextProp(STORAGE_KEY.SHOW_BACK_TOP)\n}\n\nexport const useAutoUpdate = () => {\n  return useContextProp(STORAGE_KEY.ENABLE_AUTO_UPDATE)\n}\n"
  },
  {
    "path": "src/hooks/use-dock-icon.js",
    "content": "import {useContextProp} from '@/app-context'\nimport {polyfill} from '@/utils'\nimport {IPC_FUNCTION, STORAGE_KEY} from '@shared'\n\nconst useDockIcon = () => {\n  const [dockIcon, setDockIcon] = useContextProp(STORAGE_KEY.SHOW_DOCK_ICON)\n\n  const _setDockIcon = visible => {\n    polyfill.send(IPC_FUNCTION.SHOW_DOCK_ICON, visible)\n\n    setDockIcon(visible)\n  }\n\n  return [dockIcon, _setDockIcon]\n}\n\nexport default useDockIcon\n"
  },
  {
    "path": "src/hooks/use-mode.js",
    "content": "import {useContextProp} from '@/app-context'\nimport {MODE} from '@/config'\nimport {STORAGE_KEY} from '@shared'\n\nconst useMode = () => {\n  const [mode, setMode] = useContextProp(STORAGE_KEY.MODE)\n\n  const _setMode = target => {\n    const body = document.body\n    if (target === MODE.LIGHT) {\n      body.removeAttribute('theme-mode')\n      setMode(MODE.LIGHT)\n    } else {\n      body.setAttribute('theme-mode', 'dark')\n      setMode(MODE.DARK)\n    }\n  }\n\n  return [mode, _setMode]\n}\n\nexport default useMode\n"
  },
  {
    "path": "src/hooks/use-outside-click.js",
    "content": "import {useEffect} from 'react'\n\n/**\n * Executes a handler function on click outside of a specific div\n * See: https://stackoverflow.com/questions/32553158/detect-click-outside-react-component\n * @param {*} ref\n * @param {*} handler\n */\nconst useOutsideClick = (ref, handler) => {\n  useEffect(() => {\n    /**\n     * Alert if clicked on outside of element\n     */\n    function handleClickOutside(event) {\n      if (ref.current && !ref.current.contains(event.target)) {\n        handler()\n      }\n    }\n    // Bind the event listener\n    document.addEventListener('mousedown', handleClickOutside)\n    return () => {\n      // Unbind the event listener on clean up\n      document.removeEventListener('mousedown', handleClickOutside)\n    }\n  }, [ref])\n}\n\nexport default useOutsideClick\n"
  },
  {
    "path": "src/hooks/use-scroll-position.js",
    "content": "import {useScroll} from 'ahooks'\n\nconst useScrollPosition = () => {\n  const scrollRef = useScroll()\n  const scrollPosition = scrollRef?.top\n\n  return scrollPosition\n}\n\nexport default useScrollPosition\n"
  },
  {
    "path": "src/index.ejs",
    "content": "<html lang=\"en\">\n\n\t<head>\n\t\t<meta charset=\"UTF-8\">\n\t\t<meta\n\t\t\thttp-equiv=\"X-UA-Compatible\"\n\t\t\tcontent=\"IE=edge\"\n\t\t>\n\t\t<meta\n\t\t\tname=\"viewport\"\n\t\t\tcontent=\"width=device-width, initial-scale=1.0\"\n\t\t>\n\t\t<title>Raise - GitHub Trending</title>\n\t</head>\n\n\t<body>\n\t\t<div id=\"root\"></div>\n\n    <% if (htmlWebpackPlugin.options.isChrome) { %>\n\t\t\t<script src=\"./chrome/popup.js\"></script>\n\t\t\t<script\n\t\t\t\tdefer\n\t\t\t\tdata-domain=\"<%= htmlWebpackPlugin.options.analyticsDomain %>\"\n\t\t\t\tdata-api=\"http://analytics.curve.to/api/event\"\n\t\t\t\tsrc=\"./chrome/plausible.js\">\n\t\t\t</script>\n    <% } %>\n\n    <% if (htmlWebpackPlugin.options.isElectron) { %>\n\t\t\t<script\n\t\t\t\tdefer \n\t\t\t\tdata-domain=\"<%= htmlWebpackPlugin.options.analyticsDomain %>\"\n\t\t\t\tdata-api=\"http://analytics.curve.to/api/event\"\n\t\t\t\tsrc=\"https://analytics.curve.to/js/script.local.js\">\n\t\t\t</script>\n    <% } %>\n\t</body>\n\n</html>\n"
  },
  {
    "path": "src/index.js",
    "content": "import React from 'react'\nimport {createRoot} from 'react-dom/client'\n\nimport App from './app'\n\nconst container = document.querySelector('#root')\nconst root = createRoot(container)\nroot.render(<App />)\n"
  },
  {
    "path": "src/io/index.js",
    "content": "export * from './trending'\n"
  },
  {
    "path": "src/io/interceptor.js",
    "content": "import axios from 'axios'\nimport NProgress from 'nprogress'\n\nNProgress.configure({showSpinner: false})\n\naxios.interceptors.request.use(\n  config => {\n    NProgress.start()\n    return config\n  },\n  error => {\n    NProgress.start()\n    return Promise.reject(error)\n  }\n)\n\naxios.interceptors.response.use(\n  response => {\n    NProgress.done()\n    return response\n  },\n  error => {\n    NProgress.done()\n    return Promise.reject(error)\n  }\n)\n\nexport default axios\n"
  },
  {
    "path": "src/io/trending.js",
    "content": "import {snakeCase} from 'lodash'\n\nimport axios from './interceptor'\nimport {TRENDING_TYPE, URL} from '@/config'\nimport {getTimeStamp} from '@/utils'\n\nlet controller\nexport let lastTimestamp = 0\n\nconst buildUrl = (baseUrl, params = {}) => {\n  const queryString = Object.keys(params)\n    .filter(key => params[key])\n    .map(key => `${snakeCase(key)}=${params[key]}`)\n    .join('&')\n\n  return queryString === '' ? baseUrl : `${baseUrl}?${queryString}`\n}\n\nconst checkResponse = res => {\n  if (res.status !== 200) {\n    throw new Error('Something went wrong')\n  }\n}\n\nconst fetch = async ({params = {}, type, serverUrl = URL.SERVER} = {}) => {\n  if (controller) {\n    controller.abort() // Makes sure that users always get the latest result\n  }\n\n  controller = new AbortController()\n\n  /**\n   * Used to compare between now and inactivity.\n   * Reloads if time of inactivity is too long\n   */\n  lastTimestamp = getTimeStamp()\n\n  const res = await axios({\n    method: 'get',\n    url: buildUrl(`${serverUrl}/${type}`, params),\n    signal: controller.signal,\n  })\n\n  checkResponse(res)\n\n  return res.data\n}\n\nexport const fetchRepositories = (params, serverUrl = URL.SERVER) => {\n  return fetch({params, serverUrl, type: TRENDING_TYPE.REPOSITORIES.toLocaleLowerCase()})\n}\n\nexport const fetchDevelopers = async (params, serverUrl = URL.SERVER) => {\n  return fetch({params, serverUrl, type: TRENDING_TYPE.DEVELOPERS.toLocaleLowerCase()})\n}\n"
  },
  {
    "path": "src/lib/index.js",
    "content": "export {default as isElectron} from './is-electron'\n"
  },
  {
    "path": "src/lib/is-electron.js",
    "content": "const isElectron = () => {\n  // Renderer process\n  if (\n    typeof window !== 'undefined' &&\n    typeof window.process === 'object' &&\n    window.process.type === 'renderer'\n  ) {\n    return true\n  }\n\n  // Main process\n  if (\n    typeof process !== 'undefined' &&\n    typeof process.versions === 'object' &&\n    !!process.versions.electron\n  ) {\n    return true\n  }\n\n  // Detect the user agent when the `nodeIntegration` option is set to false\n  if (\n    typeof navigator === 'object' &&\n    typeof navigator.userAgent === 'string' &&\n    navigator.userAgent.indexOf('Electron') >= 0\n  ) {\n    return true\n  }\n\n  return false\n}\n\nexport default isElectron\n"
  },
  {
    "path": "src/pages/index/index.js",
    "content": "import React, {useEffect, useState} from 'react'\nimport {Typography, BackTop, Toast, Empty} from '@douyinfe/semi-ui'\nimport {IconArrowUp} from '@douyinfe/semi-icons'\nimport {IllustrationNoResult, IllustrationNoResultDark} from '@douyinfe/semi-illustrations'\nimport axios from 'axios'\n\nimport {RaiseHeader, RepositoryContent, DeveloperContent} from '@/components'\nimport {fetchRepositories, fetchDevelopers, lastTimestamp} from '@/io'\nimport {convert, polyfill} from '@/utils'\nimport {ALLOWED_TIME_OF_INACTIVITY, TRENDING_TYPE} from '@/config'\nimport {useBackTop, useMode, useTrendingType} from '@/hooks'\nimport {IPC_FUNCTION} from '@shared'\n\nimport styles from './styles.scss'\n\nconst {Text} = Typography\n\nconst {REPOSITORIES} = TRENDING_TYPE\nconst {RELOAD_AFTER_INACTIVITY} = IPC_FUNCTION\n\nconst Index = () => {\n  const [trendingType] = useTrendingType()\n  const [backTop] = useBackTop()\n  const [mode, setMode] = useMode()\n\n  const [list, setList] = useState([])\n  const [getListParams, setGetListParams] = useState({})\n  const [loading, setLoading] = useState(false)\n  const [empty, setEmpty] = useState(false)\n\n  const isRepo = trendingType === REPOSITORIES\n\n  const Content = isRepo ? RepositoryContent : DeveloperContent\n\n  const resetList = () => setList([])\n\n  const getList = async params => {\n    window.scrollTo({top: 0})\n    setLoading(true)\n    resetList()\n    setGetListParams(params)\n    setEmpty(false)\n\n    let isCancel = false\n\n    try {\n      const fetch = isRepo ? fetchRepositories : fetchDevelopers\n      const res = await fetch(convert(params))\n      setList(res)\n      setEmpty(!res.length)\n    } catch (error) {\n      // Makes sure when a request is canceled, loading is still true for the next getList call\n      if (axios.isCancel(error)) return (isCancel = true)\n\n      console.log('An error occurred when calling getList. Params: ', params, error)\n      Toast.error(\n        'Oops. It looks like an error occurs. The server might be down. Please try again.'\n      )\n    } finally {\n      setLoading(isCancel)\n    }\n  }\n\n  const refresh = () => {\n    getList(getListParams)\n  }\n\n  useEffect(() => {\n    getList()\n  }, [trendingType])\n\n  /**\n   * Recover settings to last state according to context storage\n   * e.g., when a user toggles settings in the settings modal,\n   * a few changes have been made.\n   * After he closes the app and reopens it,\n   * all settings/context will have to be recovered.\n   */\n  useEffect(() => {\n    setMode(mode)\n  }, [])\n\n  useEffect(() => {\n    const {receive} = polyfill\n\n    const removeReloadListener = receive(RELOAD_AFTER_INACTIVITY, () => {\n      const now = new Date().getTime()\n\n      if (lastTimestamp && now - lastTimestamp > ALLOWED_TIME_OF_INACTIVITY) {\n        getList(getListParams)\n      }\n    })\n\n    return () => {\n      removeReloadListener()\n    }\n  }, [getListParams])\n\n  return (\n    <>\n      <RaiseHeader refresh={refresh} getList={getList} resetList={resetList} />\n\n      <Content list={list} getList={getList} loading={loading} />\n\n      {empty ? (\n        <Empty\n          className={styles.empty}\n          image={<IllustrationNoResult style={{width: 150, height: 150}} />}\n          darkModeImage={<IllustrationNoResultDark style={{width: 150, height: 150}} />}\n          description={\n            <Text className={styles.emptyDescription}>\n              {`It looks like we don’t have any trending ${\n                isRepo ? 'repositories' : 'developers'\n              } for your choices.`}\n            </Text>\n          }\n        />\n      ) : null}\n\n      {backTop ? (\n        <BackTop className={styles.backTop}>\n          <IconArrowUp />\n        </BackTop>\n      ) : null}\n    </>\n  )\n}\n\nexport default Index\n"
  },
  {
    "path": "src/pages/index/styles.scss",
    "content": ".back-top {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 30px;\n  width: 30px;\n  border-radius: 100%;\n  background-color: #0077fa;\n  color: #fff;\n  bottom: 20px;\n  right: 10px;\n}\n\n.empty {\n  height: 74vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n\n  .empty-description {\n    display: block;\n    width: 80%;\n    margin: 0 auto;\n  }\n}\n"
  },
  {
    "path": "src/utils/common.js",
    "content": "export const truncate = (str, maxLength = 14) => {\n  return str.length > maxLength ? `${str.substring(0, maxLength)}...` : str\n}\n\nexport const convert = params => {\n  if (!params) return params\n\n  return Object.entries(params)\n    .map(([key, value]) => {\n      value = value === 'any' ? '' : value\n      return [key, value]\n    })\n    .reduce((final, item) => {\n      const [key, value] = item\n      final[key] = value\n      return final\n    }, {})\n}\n\nexport const numberWithCommas = number => {\n  return number?.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')\n}\n\nexport const getTimeStamp = () => new Date().getTime()\n"
  },
  {
    "path": "src/utils/index.js",
    "content": "export {default as polyfill} from './polyfill'\nexport * from './common'\n"
  },
  {
    "path": "src/utils/polyfill/electron/index.js",
    "content": "export * from './storage'\nexport * from './utils'\n"
  },
  {
    "path": "src/utils/polyfill/electron/storage.js",
    "content": "/**\n * Saves storage with electron-store.\n * This storage communicates with electron's main.js\n * rather than browser's window.localStorage\n */\n\nconst {storage} = window.electron || {}\n\nexport const setStorage = (key, value) => {\n  try {\n    storage?.set(key, value)\n  } catch (err) {\n    console.log(`An error occurred when setting storage ${key} with value: `, value, err)\n  }\n}\n\nexport const getStorage = key => {\n  try {\n    return storage?.get(key)\n  } catch (err) {\n    console.log(`An error occurred when getting storage ${key}.`, err)\n    return null\n  }\n}\n\nexport const getContextFromStorage = () => {\n  try {\n    return storage?.store() || {}\n  } catch (err) {\n    console.log('An error occurred when getting context from storage.', err)\n    return {}\n  }\n}\n"
  },
  {
    "path": "src/utils/polyfill/electron/utils.js",
    "content": "const {open, receive, send} = window.electron || {}\n\nexport {open, receive, send}\n"
  },
  {
    "path": "src/utils/polyfill/index.js",
    "content": "import * as web from './web'\nimport * as electron from './electron'\nimport {isElectron} from '@/config'\n\nexport default isElectron ? electron : web\n"
  },
  {
    "path": "src/utils/polyfill/web/index.js",
    "content": "export * from './storage'\nexport * from './utils'\n"
  },
  {
    "path": "src/utils/polyfill/web/storage.js",
    "content": "/**\n * Saves storage with localStorage.\n */\n\nconst storage = window.localStorage\n\nexport const setStorage = (key, value) => {\n  try {\n    storage.setItem(key, JSON.stringify({value}))\n  } catch (err) {\n    console.log(`An error occurred when setting storage ${key} with value: `, value, err)\n  }\n}\n\nexport const getStorage = key => {\n  try {\n    return JSON.parse(storage.getItem(key)).value\n  } catch (err) {\n    console.log(`An error occurred when getting storage ${key}.`, err)\n    return null\n  }\n}\n\nexport const getContextFromStorage = () => {\n  try {\n    const context = Object.keys(storage).reduce((final, key) => {\n      final[key] = getStorage(key)\n      return final\n    }, {})\n\n    return context || {}\n  } catch (err) {\n    console.log('An error occurred when getting context from storage.', err)\n    return {}\n  }\n}\n"
  },
  {
    "path": "src/utils/polyfill/web/utils.js",
    "content": "export const open = window.open\n\nexport const receive = () => () => {}\n\nexport const send = () => {}\n"
  },
  {
    "path": "webpack/chrome/webpack.config.js",
    "content": "/**\n * The Webpack config which chrome extension project uses\n */\n\nconst path = require('path')\nconst webpack = require('webpack')\nconst {merge} = require('webpack-merge')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\n\nconst base = require('../webpack.base.config')\nconst generateChromeManifest = require('../../chrome-manifest')\n\ngenerateChromeManifest()\n\nmodule.exports = (_, argv) => {\n  console.log('webpack config argv =>', argv)\n\n  const DEV = argv.mode === 'development'\n\n  return merge(base(argv), {\n    /**\n     * The field `devtool` fixed unsafe-eval error in dev mode.\n     * See https://stackoverflow.com/questions/48047150/chrome-extension-compiled-by-webpack-throws-unsafe-eval-error\n     */\n    devtool: DEV ? 'cheap-module-source-map' : false,\n    plugins: [\n      new webpack.DefinePlugin({\n        'process.env': {\n          WEBPACK_DEV: DEV,\n          isChrome: true,\n        },\n      }),\n      new HtmlWebpackPlugin({\n        template: path.resolve('./src/index.ejs'),\n        filename: 'index.html',\n        chunks: ['main'],\n        isChrome: true,\n        analyticsDomain: DEV ? 'raise-dev.curve.to' : 'raise-chrome.curve.to',\n      }),\n      new CopyWebpackPlugin({\n        patterns: [\n          {from: './static/chrome', to: './static'},\n          {from: './src/chrome/manifest.json', to: './manifest.json'},\n          {from: './src/chrome', to: './chrome', globOptions: {ignore: ['**/*/manifest.json']}},\n        ],\n      }),\n    ],\n  })\n}\n"
  },
  {
    "path": "webpack/main/webpack.config.js",
    "content": "/**\n * The webpack config which Electron main uses\n */\n\nconst path = require('path')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\n\nmodule.exports = {\n  target: 'electron-main',\n  entry: {\n    main: path.resolve('./electron/main.js'),\n    preload: path.resolve('./electron/preload.js'),\n  },\n  output: {\n    filename: '[name].js',\n    path: path.resolve('./dist'),\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        exclude: /node_modules/,\n        use: 'babel-loader',\n      },\n      {\n        test: /.node$/,\n        loader: 'node-loader',\n      },\n    ],\n  },\n  resolve: {\n    symlinks: false,\n    cacheWithContext: false,\n    alias: {\n      '@shared': path.resolve('./shared'),\n      '@pkg': path.resolve('./package.json'),\n    },\n  },\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n  plugins: [\n    new CopyWebpackPlugin({\n      patterns: [{from: './static', to: './static'}],\n    }),\n  ],\n}\n"
  },
  {
    "path": "webpack/renderer/webpack.config.js",
    "content": "/**\n * The Webpack config which Electron's renderer uses\n */\n\nconst path = require('path')\nconst webpack = require('webpack')\nconst {merge} = require('webpack-merge')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')\n\nconst base = require('../webpack.base.config')\n\nmodule.exports = (_, argv) => {\n  console.log('webpack config argv =>', argv)\n\n  const DEV = argv.mode === 'development'\n  const PROD = !DEV\n\n  return merge(base(argv), {\n    plugins: [\n      new webpack.DefinePlugin({\n        'process.env': {\n          WEBPACK_DEV: DEV,\n        },\n      }),\n      new HtmlWebpackPlugin({\n        template: path.resolve('./src/index.ejs'),\n        filename: 'index.html',\n        chunks: ['main'],\n        isElectron: true,\n        analyticsDomain: DEV ? 'raise-dev.curve.to' : 'raise-desktop.curve.to',\n      }),\n      PROD && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/^runtime.+\\.js$/]),\n    ].filter(Boolean),\n  })\n}\n"
  },
  {
    "path": "webpack/webpack.base.config.js",
    "content": "/**\n * The Webpack config which is shared by Electron's renderer and web\n */\n\nconst path = require('path')\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin')\nconst TerserPlugin = require('terser-webpack-plugin')\n\nconst terserPluginConfig = {\n  extractComments: false,\n  terserOptions: {\n    format: {\n      comments: false,\n      ascii_only: true,\n    },\n    compress: {\n      drop_console: true,\n    },\n  },\n}\n\nconst splitChunksConfig = {\n  chunks: 'all',\n  cacheGroups: {\n    default: {\n      chunks: 'async',\n      priority: 10,\n      minChunks: 2,\n      reuseExistingChunk: true,\n    },\n    defaultVendors: false,\n    commons: {\n      chunks: 'all',\n      test: /[\\\\/]node_modules[\\\\/]/,\n      priority: 20,\n      minChunks: 2,\n      maxSize: 512 * 1024, // 512kb\n      name: 'commons',\n      filename: '[name].[chunkhash:8].js',\n      reuseExistingChunk: true,\n    },\n    core: {\n      chunks: 'all',\n      test: /node_modules[\\\\/](?:core-js|regenerator-runtime|@babel|(?:style|css)-loader)/,\n      priority: 30,\n      name: 'core',\n      filename: '[name].[chunkhash:8].js',\n      reuseExistingChunk: true,\n    },\n    react: {\n      chunks: 'all',\n      test: /node_modules[\\\\/](?:react|react-dom|react-router-dom)/,\n      priority: 100,\n      name: 'react',\n      filename: '[name].[chunkhash:8].js',\n      reuseExistingChunk: true,\n    },\n  },\n}\n\nmodule.exports = argv => {\n  const DEV = argv.mode === 'development'\n  const PROD = !DEV\n\n  return {\n    devtool: DEV ? 'eval-cheap-module-source-map' : false,\n    bail: PROD,\n    cache: DEV\n      ? {type: 'memory'}\n      : {\n          type: 'filesystem',\n          buildDependencies: {config: [__filename]},\n        },\n    entry: ['./src/index.js'],\n    output: {\n      path: path.resolve('./dist'),\n      filename: `[name]${PROD ? '.[contenthash:8]' : ''}.js`,\n      chunkFilename: `[name]${PROD ? '.[contenthash:8]' : ''}.js`,\n      publicPath: PROD ? './' : '',\n    },\n    resolve: {\n      symlinks: false,\n      cacheWithContext: false,\n      alias: {\n        '@': path.resolve('./src'),\n        '@static': path.resolve('./static'),\n        '@shared': path.resolve('./shared'),\n        '@pkg': path.resolve('./package.json'),\n      },\n    },\n    devServer: {\n      static: path.resolve('./dist'),\n      port: 3000,\n    },\n    optimization: PROD\n      ? {\n          runtimeChunk: 'single',\n          chunkIds: 'deterministic',\n          moduleIds: 'deterministic',\n          minimizer: [\n            new TerserPlugin(terserPluginConfig),\n            new CssMinimizerPlugin({test: /\\.css$/}),\n          ],\n          splitChunks: splitChunksConfig,\n        }\n      : undefined,\n    module: {\n      rules: [\n        {\n          test: /\\.(js|jsx)$/,\n          include: [path.resolve('./src')],\n          use: [\n            {\n              loader: 'babel-loader',\n              options: {\n                cacheDirectory: true,\n                cacheCompression: false,\n              },\n            },\n          ],\n        },\n        {\n          test: /\\.css$/,\n          exclude: /node_modules/,\n          use: [\n            DEV ? 'style-loader' : MiniCssExtractPlugin.loader,\n            {\n              loader: 'css-loader',\n              options: {\n                modules: {\n                  exportLocalsConvention: 'camelCase',\n                  localIdentName: '[name]__[local]___[hash:base64:5]',\n                },\n              },\n            },\n            'postcss-loader',\n          ],\n        },\n        {\n          test: /\\.css$/,\n          include: /node_modules/,\n          use: [DEV ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],\n        },\n        {\n          test: /\\.scss$/,\n          use: [\n            'style-loader',\n            {\n              loader: 'css-loader',\n              options: {\n                modules: {\n                  exportLocalsConvention: 'camelCase',\n                  localIdentName: '[name]__[local]___[hash:base64:5]',\n                },\n              },\n            },\n            'postcss-loader',\n            'sass-loader',\n          ],\n        },\n        {\n          test: /\\.(png|jpg|svg|gif)$/,\n          type: 'asset',\n          parser: {\n            dataUrlCondition: {\n              maxSize: 10 * 1024, // 10kb\n            },\n          },\n          generator: {\n            filename: 'assets/images/[name].[hash:8][ext][query]',\n          },\n        },\n      ],\n    },\n    plugins: [\n      PROD &&\n        new MiniCssExtractPlugin({\n          filename: 'assets/styles/[name].[contenthash:8].css',\n          chunkFilename: 'assets/styles/[name].[contenthash:8].css',\n          ignoreOrder: true,\n        }),\n    ].filter(Boolean),\n  }\n}\n"
  }
]