{"id":15893941,"url":"https://github.com/chinanf-boy/devdocs-egoist-explain","last_synced_at":"2026-01-26T21:52:55.129Z","repository":{"id":90548179,"uuid":"136000322","full_name":"chinanf-boy/devdocs-egoist-explain","owner":"chinanf-boy","description":"explain: \u003cdevdocs-desktop\u003e  相对不复杂 的 electron 应用","archived":false,"fork":false,"pushed_at":"2018-06-04T09:22:03.000Z","size":14,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-03T04:59:48.289Z","etag":null,"topics":["devdocs","explain"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chinanf-boy.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-04T09:14:17.000Z","updated_at":"2023-09-08T17:41:19.000Z","dependencies_parsed_at":"2023-07-21T17:55:23.438Z","dependency_job_id":null,"html_url":"https://github.com/chinanf-boy/devdocs-egoist-explain","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chinanf-boy/devdocs-egoist-explain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fdevdocs-egoist-explain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fdevdocs-egoist-explain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fdevdocs-egoist-explain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fdevdocs-egoist-explain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chinanf-boy","download_url":"https://codeload.github.com/chinanf-boy/devdocs-egoist-explain/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fdevdocs-egoist-explain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28789240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T21:49:50.245Z","status":"ssl_error","status_checked_at":"2026-01-26T21:48:29.455Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devdocs","explain"],"created_at":"2024-10-06T08:14:06.626Z","updated_at":"2026-01-26T21:52:50.120Z","avatar_url":"https://github.com/chinanf-boy.png","language":null,"funding_links":["https://patreon.com/yobrave"],"categories":[],"sub_categories":[],"readme":"# devdocs-desktop [![explain](http://llever.com/explain.svg)](https://github.com/chinanf-boy/Source-Explain)\n\n「 Desktop client for devdocs.io 」\n\n应该第一个 `electron 应用` explain, 所以选了个相对程度但不复杂的\n\nExplanation\n\n\u003e \"version\": \"1.0.0\"\n\n[github source](https://github.com/egoist/devdocs-desktop)\n\n[中文](./readme.md) | ~~[english](./readme.en.md)~~\n\n---\n\n\u003cspan\u003e 穷 \u003c/span\u003e\n\n\u003ca href=\"https://patreon.com/yobrave\"\u003e\n\u003cimg src=\"https://c5.patreon.com/external/logo/become_a_patron_button@2x.png\" height=\"30\"\u003e\u003c/a\u003e \n\n---\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [package.json](#packagejson)\n  - [1.  `postinstall`](#1--postinstall)\n  - [2.  electron-builder](#2--electron-builder)\n  - [3.  `build`](#3--build)\n- [app/index.js](#appindexjs)\n  - [electron-控制-方式](#electron-%E6%8E%A7%E5%88%B6-%E6%96%B9%E5%BC%8F)\n  - [require](#require)\n  - [应用信息](#%E5%BA%94%E7%94%A8%E4%BF%A1%E6%81%AF)\n  - [唯一应用](#%E5%94%AF%E4%B8%80%E5%BA%94%E7%94%A8)\n  - [关闭和显示](#%E5%85%B3%E9%97%AD%E5%92%8C%E6%98%BE%E7%A4%BA)\n  - [主窗口](#%E4%B8%BB%E7%AA%97%E5%8F%A3)\n    - [renderer/index.html](#rendererindexhtml)\n  - [login](#login)\n  - [ready](#ready)\n  - [激活](#%E6%BF%80%E6%B4%BB)\n  - [获得焦点](#%E8%8E%B7%E5%BE%97%E7%84%A6%E7%82%B9)\n  - [退出前](#%E9%80%80%E5%87%BA%E5%89%8D)\n  - [自定义协议](#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8D%8F%E8%AE%AE)\n  - [基本启动](#%E5%9F%BA%E6%9C%AC%E5%90%AF%E5%8A%A8)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n---\n\n## package.json\n\n``` js\n  \"main\": \"app/index.js\", // 主入口\n  \"scripts\": {\n    \"postinstall\": \"electron-builder install-app-deps\",\n    \"test\": \"npm run lint\",\n    \"lint\": \"xo\",\n    \"app\": \"DEBUG=devdocs-desktop:* electron app/index.js\",\n    \"pack\": \"build --dir\",\n    \"dist\": \"build\",\n    \"release\": \"build\"\n  },\n```\n\n### 1.  `postinstall`\n\n在安装软件包后运行。 \n\n\u003e `electron-builder install-app-deps` 下载 应用的 依赖\n\n### 2.  [electron-builder](https://github.com/electron-userland/electron-builder)\n\n一个完整的解决方案，打包并构建一个准备发布的电子应用程序，带有“自动更新”支持\n\n### 3.  `build`\n\n提供 使用 `elertron-builder` 的快捷, 同时依赖`package.json`-`build`字段的定义\n\n\u003cdetails\u003e\n\n\u003csummary\u003e 本应用 build 字段 细节 \u003c/summary\u003e\n\n``` js\n \"build\": {\n    \"appId\": \"com.egoistian.devdocs\", // id\n    \"productName\": \"DevDocs\", // name\n    \"compression\": \"maximum\", // 压缩级别\n    \"asar\": true, // 是否使用 Electro n的存档格式将应用程序的源代码打包到档案中。\n    \"mac\": {\n      \"category\": \"public.app-category.developer-tools\" // 应用程序类别类型\n    },\n    \"win\": {\n      \"target\": [ // 目标封装类型\n        \"nsis\", // Windows的默认目标\n        \"zip\",\n        \"portable\" // 便携式应用，而无需安装\n      ]\n    },\n    \"nsis\": {\n      \"oneClick\": false // 是创建一键安装还是辅助\n    },\n    \"linux\": {\n      \"synopsis\": \"DevDocs desktop app\", // 简短说明\n      \"category\": \"Development\", // 应用程序类别\n      \"target\": [ // 目标封装类型\n        \"AppImage\", // 让Linux应用随处运行 \n        \"deb\", // Debian软件包选项\n        \"tar.xz\"\n      ]\n    }\n  }\n```\n\n- [build 全配置](https://www.electron.build/configuration/configuration) `en`\n- [AppImage](https://appimage.org/)\n\n\u003c/details\u003e\n\n## app/index.js\n\n### electron-控制-方式\n\n既然是 `electron` 应用\n\n就有必要说明一下, [electron 控制网页 的 方式](./electron.md)\n\n### require\n\n``` js\nconst path = require('path')\nconst { app, BrowserWindow, Menu } = require('electron')\nconst debug = require('debug')('devdocs-desktop:index')\nconst createMenu = require('./menu')\nconst config = require('./config')\nconst tray = require('./tray')\nconst updater = require('./updater')\nconst { toggleGlobalShortcut } = require('./utils')\nconst login = require('./login')\n\nrequire('electron-debug')() // 专用 调试信息\nrequire('electron-context-menu')({ // 菜单\n  showInspectElement: true // 能右键 点出 dev调试器\n})\n\n```\n\n- [x] [config](./config.md)\n\n每次操作都是一次 文件级别的 存储, 应用退出也不会变\n\n### 应用信息\n\n``` js\n//改变当前应用\napp.setAppUserModelId('com.egoistian.devdocs')\n\nlet mainWindow\nlet isQuitting = false\nlet urlToOpen\n\n```\n\n### 唯一应用\n\n``` js\n// https://electronjs.org/docs/api/app#appmakesingleinstancecallback\nconst isAlreadyRunning = app.makeSingleInstance(() =\u003e {\n  // 这将确保只有一个应用程序的实例正在运行, 其余的实例全部会被终止并退出。\n  if (mainWindow) {\n    if (mainWindow.isMinimized()) {\n      mainWindow.restore()\n    }\n\n    mainWindow.show()\n  }\n})\n\nif (isAlreadyRunning) {\n  // 已经有了 , 退出\n  app.quit()\n}\n\n```\n\n### 关闭和显示\n\n``` js\nfunction toggleWindow() { // 关闭和显示 主窗口\n  if (mainWindow.isVisible()) {\n    mainWindow.hide()\n  } else {\n    mainWindow.show()\n  }\n}\n\n```\n\n### 主窗口\n\n``` js\nfunction createMainWindow() { // 创建主窗口\n  const lastWindowState = config.get('lastWindowState')\n\n  const win = new BrowserWindow({\n    title: app.getName(),\n    x: lastWindowState.x, // 开启的位置\n    y: lastWindowState.y,\n    width: lastWindowState.width, // 窗口大小\n    height: lastWindowState.height,\n    minWidth: 600,\n    minHeight: 400,\n    show: false, // 最初的可见性状态将为visible 尽管窗口实际上是隐藏的。\n    titleBarStyle: 'hidden', // 窗口标题栏的样式\n    backgroundColor: '#ffffff'\n  })\n\n  if (process.platform === 'darwin') { // 如果是 macOS 系统\n    win.setSheetOffset(24)\n  }\n\n  const url = `file://${path.join(__dirname, 'renderer', 'index.html')}`\n\n  win.loadURL(url) // 加载 web页面\n\n  win.on('close', e =\u003e {\n    if (!isQuitting) {\n      e.preventDefault()\n\n      if (process.platform === 'darwin') {\n        // 一般 在 macos 中 需要 Command + Q 才能完全退出程序\n        app.hide()\n      } else {\n        win.hide()\n      }\n    }\n  })\n\n  return win\n}\n\n```\n\n#### renderer/index.html\n\n- [index.html](renderer.md)\n\n- [BrowserWindow 参数](https://electronjs.org/docs/api/browser-window#new-browserwindowoptions)\n\n### login\n\n登录信息\n\n``` js\napp.on('login', (event, webContents, request, authInfo, cb) =\u003e {\n  debug('app.on(login)')\n  event.preventDefault()\n  login(cb)\n})\n\n```\n\n- [x] [login](./login.md)\n\n并没有怎么用到\n\n### ready\n\n应用准备好就, 开启\n\n``` js\napp.on('ready', () =\u003e {\n  \n  // 快捷键\n  const shortcut = config.get('shortcut')\n  for (const name in shortcut) {\n    const accelerator = shortcut[name]\n    if (accelerator) {\n      toggleGlobalShortcut({\n        name,\n        accelerator,\n        registered: false,\n        action: toggleWindow\n      })\n    }\n  }\n\n    // 菜单\n  Menu.setApplicationMenu(\n    createMenu({\n      toggleWindow\n    })\n  )\n  // 主窗口\n  mainWindow = createMainWindow()\n  // 将图标和上下文菜单添加到系统的通知区域。\n  tray.create(mainWindow)\n\n// 加载页面时，ready-to-show如果窗口尚未显示，则渲染器进程首次渲染页面时会发出事件\n  mainWindow.once('ready-to-show', () =\u003e {\n    mainWindow.show()\n    updater.init() // 更新初始化\n    if (urlToOpen) {\n      mainWindow.webContents.send('link', urlToOpen)\n    }\n  })\n})\n\n```\n\n- [x] [toggleGlobalShortcut](./util.md)\n\n添加或删除全局快捷键\n\n- [x] [createMenu](./menu.md)\n\n应用菜单列表\n\n- [x] [updater](./updater.md)\n\n自动更新触发\n\n- [x] [tray](./tray.md)\n\n系统菜单程序列表的图标\n\n- [ready-to-show](https://electronjs.org/docs/api/browser-window#using-ready-to-show-event)\n\n- [webContents.send](https://electronjs.org/docs/api/web-contents#contentssendchannel-arg1-arg2-)\n\n除了`ipcMain` 之外, 为了有目的性的发送, 窗口实例也是具有向 `页面进程` 发送信息的能力\n\n### 激活\n\n``` js\n// 应用程序激活时\napp.on('activate', () =\u003e {\n  mainWindow.show()\n})\n\n```\n\n### 获得焦点\n\n``` js\nlet hasOpenedOnce\n// 在 browserWindow 获得焦点时发出。\napp.on('browser-window-focus', () =\u003e {\n  if (hasOpenedOnce) {\n    mainWindow.webContents.send('focus-webview')\n  } else {\n    hasOpenedOnce = true\n  }\n})\n\n```\n\n### 退出前\n\n``` js\n// 退出前\napp.on('before-quit', () =\u003e {\n  isQuitting = true\n\n  if (!mainWindow.isFullScreen()) { // 只要不是全屏,就保存位置\n   // 因为是 elertron-store 库, 所以其实是保存下来的\n    config.set('lastWindowState', mainWindow.getBounds())\n  }\n})\n\n```\n\n### 自定义协议\n\n``` js\n// devdocs://\n// 应用内部都是已 这个链接开头\napp.setAsDefaultProtocolClient('devdocs')\n```\n\n### 基本启动\n\n应用程序完成基本启动时发出\n\n``` js\napp.on('will-finish-launching', () =\u003e {\n  //当用户想要在应用中打开一个 URL 时发出\n  app.on('open-url', (e, url) =\u003e {\n    // 1. 第一次 应用还没有准备好的时候, 窗口没有生成\n    if (mainWindow) {\n      // 3. 有了, 就请求\n      mainWindow.webContents.send('link', url)\n    } else {\n      // 2. 先保存\n      urlToOpen = url\n    }\n  })\n})\n\n```\n\n\u003e 您通常会在此处设置侦听器`open-file`和 `open-url`事件\n\n- [will-finish-launching](https://electronjs.org/docs/api/app#event-will-finish-launching)\n\n- [open-url](https://electronjs.org/docs/api/app#%E4%BA%8B%E4%BB%B6-open-url-macos)\n\n- [webContents.send](https://electronjs.org/docs/api/web-contents#contentssendchannel-arg1-arg2-)\n\n除了`ipcMain` 之外, 为了有目的性的发送, 窗口实例也是具有向 `页面进程` 发送信息的能力\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchinanf-boy%2Fdevdocs-egoist-explain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchinanf-boy%2Fdevdocs-egoist-explain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchinanf-boy%2Fdevdocs-egoist-explain/lists"}