├── .nvmrc ├── docs ├── spike │ ├── analyses.md │ ├── export.md │ ├── ui.md │ ├── plugins.md │ ├── auto.md │ ├── sexy.md │ ├── webview.md │ ├── icon.md │ ├── terminal.md │ ├── documents.md │ ├── git.md │ ├── cli.md │ ├── browser.md │ ├── slides.md │ ├── python.md │ ├── file-save.md │ ├── editor.md │ ├── open-files.md │ ├── mindmap.md │ ├── multiple-windows.md │ ├── search-server.md │ ├── chinese-improve.md │ ├── storage.md │ ├── spell-check.md │ ├── shortcuts.md │ ├── communication.md │ └── nlp.md ├── CNAME ├── phodit.jpg └── adr │ ├── 0003-支持批注.md │ ├── 0008-另存为功能.md │ ├── 0016-引入操作日志.md │ ├── 0009-搜索引擎或者是数据库.md │ ├── 0010-全文搜索文本支持.md │ ├── 0015-可配置的搜索菜单.md │ ├── 0017-支持记住文件的行号.md │ ├── 0019-支持选择本地图片.md │ ├── 0031-屏保或定时休息提醒.md │ ├── 0025-添加复制、粘贴图片功能.md │ ├── 0032-touchbar-支持.md │ ├── 0006-新增复制图片下载到本地的功能.md │ ├── 0024-添加不同的显示字体切换.md │ ├── 0029-[feat]-添加审校功能.md │ ├── 0018-支持内容中的待办事项列表-todo.md │ ├── 0005-段落折叠功能.md │ ├── 0028-[bug]-修改重命名文件后的保存问题.md │ ├── 0030-进度条:根据行数和当前行数来显示百分比.md │ ├── 0033-multiple-instances.md │ ├── 0020-submline-text-类似的管口滚动.md │ ├── 0014-实现重命名文件等对话框.md │ ├── 0027-粘贴内容到编辑器时转为-markdown-格式.md │ ├── 0004-统计打字速度及显示活跃时间.md │ ├── 0022-[tabs]-支持.md │ ├── 0001-系统进行-discover-设计.md │ ├── 0023-[pdf]-支持编译文章成-pdf.md │ ├── 0013-使用-toast-来实现简单的提醒功能.md │ ├── 0007-autocomplete->-冒号后自动换行并加-item-标识符.md │ ├── 0026-添加粘贴图片到目录的支持.md │ ├── 0011-监测到文件变化自动更新目录.md │ ├── 0012-使用-pandoc-来转换-slide.md │ ├── 0021-自动发布系统-dsl.md │ ├── 0002-中文-nlp-相关功能.md │ └── README.md ├── CNAME ├── components ├── interact │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── styles.css │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── tsconfig.app.json │ │ ├── app │ │ │ ├── interact-bar.component.html │ │ │ ├── app.module.ts │ │ │ ├── interact-bar.component.css │ │ │ └── interact-bar.component.ts │ │ ├── tsconfig.spec.json │ │ ├── tslint.json │ │ ├── browserslist │ │ ├── main.ts │ │ ├── index.html │ │ ├── test.ts │ │ ├── karma.conf.js │ │ └── polyfills.ts │ ├── e2e │ │ ├── src │ │ │ ├── app.po.ts │ │ │ └── app.e2e-spec.ts │ │ ├── tsconfig.e2e.json │ │ └── protractor.conf.js │ ├── .editorconfig │ ├── tsconfig.json │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── tslint.json │ └── angular.json ├── tree-view │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── README.md │ ├── src │ │ ├── index.js │ │ ├── index.css │ │ └── App.js │ ├── .gitignore │ ├── package.json │ └── package-lock.json └── header │ ├── stencil.config.js │ ├── .editorconfig │ ├── src │ ├── components │ │ └── phodit-header │ │ │ ├── phodit-header.css │ │ │ ├── phodit-header.tsx │ │ │ └── phodit-header.spec.ts │ ├── index.html │ └── components.d.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── LICENSE │ ├── .github │ ├── ISSUE_TEMPLATE.md │ └── CONTRIBUTING.md │ ├── package-lock.json │ ├── package.json │ └── readme.md ├── _config.yml ├── assets ├── hot-words.txt ├── imgs │ ├── phodit.sketch │ └── icons │ │ ├── mac │ │ └── icon.icns │ │ ├── png │ │ ├── 16x16.png │ │ ├── 24x24.png │ │ ├── 32x32.png │ │ ├── 48x48.png │ │ ├── 64x64.png │ │ ├── 128x128.png │ │ ├── 256x256.png │ │ ├── 512x512.png │ │ └── 1024x1024.png │ │ └── win │ │ └── icon.ico ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── css │ ├── themes │ │ ├── dark-theme.css │ │ ├── monokai-sublime.css │ │ ├── monokai-bright.css │ │ └── default.css │ ├── styles.css │ └── normalize.css ├── libs │ └── clipboard.min.js └── simplemde │ └── simplemde.min.css ├── .adr.json ├── src ├── common │ ├── interface │ │ ├── IFileSave.ts │ │ └── IFileOpen.ts │ └── constants │ │ └── event.constants.ts ├── native │ ├── features │ │ ├── git │ │ │ ├── index.ts │ │ │ ├── spawn-git.ts │ │ │ └── status.ts │ │ └── pandoc │ │ │ ├── spawn-pandoc.ts │ │ │ ├── index.ts │ │ │ ├── html.ts │ │ │ ├── word.ts │ │ │ ├── slide.ts │ │ │ └── pdf.ts │ ├── ui │ │ ├── menu.dock.ts │ │ ├── touch-bar.ts │ │ └── menu.ts │ ├── main.ts │ ├── pages │ │ ├── html.page.ts │ │ ├── help.page.ts │ │ └── silde.page.ts │ ├── support │ │ └── file-support.ts │ ├── phodit-ipc.ts │ └── phodit.ts └── render │ ├── support │ ├── event.util.ts │ ├── file.utils.ts │ └── markdown.utils.ts │ ├── key.event.ts │ ├── worker │ └── suggest.worker.ts │ ├── plugins │ └── terminal.ts │ ├── menu.right.ts │ └── renderer.ts ├── .gitmodules ├── .editorconfig ├── tsconfig.json ├── tslint.json ├── .travis.yml ├── .gitignore ├── LICENSE ├── test ├── input-test.js ├── webview-test.js ├── command-test.js ├── global-setup.js ├── basic-test.js └── application-test.js ├── views ├── slide.html ├── index.html └── help.html ├── README.md ├── package.json └── CHANGELOG.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v15.2.1 2 | -------------------------------------------------------------------------------- /docs/spike/analyses.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | www.phodit.com 2 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | www.phodit.com -------------------------------------------------------------------------------- /components/interact/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: phodal/mifa-jekyll 2 | -------------------------------------------------------------------------------- /assets/hot-words.txt: -------------------------------------------------------------------------------- 1 | 微前端 2 | 微前端架构 3 | 微服务 4 | DevOps 5 | -------------------------------------------------------------------------------- /docs/spike/export.md: -------------------------------------------------------------------------------- 1 | 文件导出 2 | === 3 | 4 | pandoc 5 | --- 6 | -------------------------------------------------------------------------------- /.adr.json: -------------------------------------------------------------------------------- 1 | {"language":"zh-cn","path":"docs/adr/","prefix":"","digits":4} -------------------------------------------------------------------------------- /docs/spike/ui.md: -------------------------------------------------------------------------------- 1 | UI 2 | === 3 | 4 | https://pqx.github.io/react-ui-tree/ 5 | -------------------------------------------------------------------------------- /docs/phodit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/docs/phodit.jpg -------------------------------------------------------------------------------- /docs/spike/plugins.md: -------------------------------------------------------------------------------- 1 | 插件篇 2 | === 3 | 4 | 考虑: 5 | 6 | https://github.com/ternjs/tern 7 | -------------------------------------------------------------------------------- /assets/imgs/phodit.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/phodit.sketch -------------------------------------------------------------------------------- /docs/spike/auto.md: -------------------------------------------------------------------------------- 1 | 自动更新 2 | === 3 | 4 | [官方更新文档](https://electronjs.org/docs/tutorial/updates) 5 | -------------------------------------------------------------------------------- /docs/spike/sexy.md: -------------------------------------------------------------------------------- 1 | 经验 2 | === 3 | 4 | https://blog.dcpos.ch/how-to-make-your-electron-app-sexy 5 | -------------------------------------------------------------------------------- /assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/spike/webview.md: -------------------------------------------------------------------------------- 1 | 嵌入 WebView 2 | === 3 | 4 | https://electronjs.org/docs/api/webview-tag 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/imgs/icons/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/mac/icon.icns -------------------------------------------------------------------------------- /assets/imgs/icons/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/16x16.png -------------------------------------------------------------------------------- /assets/imgs/icons/png/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/24x24.png -------------------------------------------------------------------------------- /assets/imgs/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/32x32.png -------------------------------------------------------------------------------- /assets/imgs/icons/png/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/48x48.png -------------------------------------------------------------------------------- /assets/imgs/icons/png/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/64x64.png -------------------------------------------------------------------------------- /assets/imgs/icons/win/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/win/icon.ico -------------------------------------------------------------------------------- /docs/spike/icon.md: -------------------------------------------------------------------------------- 1 | 图标生成 2 | === 3 | 4 | https://github.com/manojsinghnegiwd/electron-icon-generator 5 | 6 | -------------------------------------------------------------------------------- /assets/imgs/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/128x128.png -------------------------------------------------------------------------------- /assets/imgs/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/256x256.png -------------------------------------------------------------------------------- /assets/imgs/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/512x512.png -------------------------------------------------------------------------------- /components/interact/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/common/interface/IFileSave.ts: -------------------------------------------------------------------------------- 1 | export interface IFileSave { 2 | isTempFile: boolean; 3 | data: any; 4 | } 5 | -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/imgs/icons/png/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/imgs/icons/png/1024x1024.png -------------------------------------------------------------------------------- /components/interact/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /components/interact/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/components/interact/src/favicon.ico -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /components/tree-view/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/phodit/HEAD/components/tree-view/public/favicon.ico -------------------------------------------------------------------------------- /docs/spike/terminal.md: -------------------------------------------------------------------------------- 1 | Terminal 2 | === 3 | 4 | [https://github.com/xtermjs/xterm.js/](https://github.com/xtermjs/xterm.js/) 5 | -------------------------------------------------------------------------------- /src/native/features/git/index.ts: -------------------------------------------------------------------------------- 1 | import gitStatus from "./status"; 2 | 3 | export const git = { 4 | status: gitStatus, 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/interface/IFileOpen.ts: -------------------------------------------------------------------------------- 1 | export interface IFileOpen { 2 | file: string; 3 | isTempFile: boolean; 4 | data: any; 5 | } 6 | -------------------------------------------------------------------------------- /components/tree-view/README.md: -------------------------------------------------------------------------------- 1 | # Phodit UI Tree 2 | 3 | ## Setup 4 | 5 | ``` 6 | yarn install 7 | ``` 8 | 9 | ``` 10 | yarn build 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/spike/documents.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Build cross-platform desktop apps with Electron ](https://github.com/feross/electron-workshop/blob/master/README.md) 4 | -------------------------------------------------------------------------------- /docs/spike/git.md: -------------------------------------------------------------------------------- 1 | Git 相关资源 2 | === 3 | 4 | nodegit 需要跨平台编译,不考虑 5 | 6 | 考虑使用 [git-it-electron](https://github.com/jlord/git-it-electron) 类似的方式来实现。 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/native/ui/menu.dock.ts: -------------------------------------------------------------------------------- 1 | export const dockMenu = [ 2 | { 3 | label: "New Window", 4 | click() { 5 | console.log("New Window"); 6 | }, 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /docs/spike/cli.md: -------------------------------------------------------------------------------- 1 | 命令行 2 | === 3 | 4 | 5 | https://github.com/akameco/electron-terminal-open 6 | 7 | 8 | https://binary-studio.com/2017/09/21/implementing-cli-electron-apps/ 9 | -------------------------------------------------------------------------------- /docs/spike/browser.md: -------------------------------------------------------------------------------- 1 | 浏览器 2 | === 3 | 4 | 返回上一页 5 | --- 6 | 7 | 默认打开方式 8 | --- 9 | 10 | https://stackoverflow.com/questions/31749625/make-a-link-from-electron-open-in-browser 11 | -------------------------------------------------------------------------------- /src/native/main.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import Phodit from "./phodit"; 3 | 4 | function initMain() { 5 | const phodit = new Phodit(); 6 | phodit.init(); 7 | } 8 | 9 | initMain(); 10 | -------------------------------------------------------------------------------- /docs/spike/slides.md: -------------------------------------------------------------------------------- 1 | Slides 功能 2 | === 3 | 4 | https://github.com/hakimel/reveal.js/ 5 | 6 | https://github.com/webpro/reveal-md 7 | 8 | 9 | 相关编辑器: 10 | 11 | https://github.com/sunya9/reveal-editor 12 | -------------------------------------------------------------------------------- /docs/spike/python.md: -------------------------------------------------------------------------------- 1 | 嵌入 Python 2 | === 3 | 4 | 在 Electron 中嵌入 Python 环境: 5 | 6 | https://github.com/fyears/electron-python-example 7 | 8 | https://www.fyears.org/2017/02/electron-as-gui-of-python-apps-updated.html 9 | -------------------------------------------------------------------------------- /docs/adr/0003-支持批注.md: -------------------------------------------------------------------------------- 1 | # 3. 支持批注 2 | 3 | 日期: 2018-07-23 4 | 5 | ## 状态 6 | 7 | 2018-07-23 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0008-另存为功能.md: -------------------------------------------------------------------------------- 1 | # 8. 另存为功能 2 | 3 | 日期: 2018-07-25 4 | 5 | ## 状态 6 | 7 | 2018-07-25 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /src/native/features/git/spawn-git.ts: -------------------------------------------------------------------------------- 1 | const exec = require("child_process").exec; 2 | 3 | export default function spawnGit(command: any, options: any, callback: any) { 4 | exec("git " + command, options, callback); 5 | } 6 | -------------------------------------------------------------------------------- /components/tree-view/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('tree-view')); 7 | -------------------------------------------------------------------------------- /docs/adr/0016-引入操作日志.md: -------------------------------------------------------------------------------- 1 | # 16. 引入操作日志 2 | 3 | 日期: 2018-08-15 4 | 5 | ## 状态 6 | 7 | 2018-08-15 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /src/native/features/pandoc/spawn-pandoc.ts: -------------------------------------------------------------------------------- 1 | import {exec} from "child_process"; 2 | 3 | export default function spawnPandoc(command: any, options: any, callback: any) { 4 | exec("pandoc " + command, options, callback); 5 | } 6 | -------------------------------------------------------------------------------- /docs/adr/0009-搜索引擎或者是数据库.md: -------------------------------------------------------------------------------- 1 | # 9. 搜索引擎或者是数据库 2 | 3 | 日期: 2018-07-27 4 | 5 | ## 状态 6 | 7 | 2018-07-27 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0010-全文搜索文本支持.md: -------------------------------------------------------------------------------- 1 | # 10. 全文搜索文本支持 2 | 3 | 日期: 2018-07-27 4 | 5 | ## 状态 6 | 7 | 2018-07-27 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0015-可配置的搜索菜单.md: -------------------------------------------------------------------------------- 1 | # 15. 可配置的搜索菜单 2 | 3 | 日期: 2018-08-03 4 | 5 | ## 状态 6 | 7 | 2018-08-03 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0017-支持记住文件的行号.md: -------------------------------------------------------------------------------- 1 | # 17. 支持记住文件的行号 2 | 3 | 日期: 2018-08-22 4 | 5 | ## 状态 6 | 7 | 2018-08-22 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0019-支持选择本地图片.md: -------------------------------------------------------------------------------- 1 | # 19. 支持选择本地图片 2 | 3 | 日期: 2018-08-23 4 | 5 | ## 状态 6 | 7 | 2018-08-23 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0031-屏保或定时休息提醒.md: -------------------------------------------------------------------------------- 1 | # 31. 屏保或定时休息提醒 2 | 3 | 日期: 2019-01-17 4 | 5 | ## 状态 6 | 7 | 2019-01-17 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0025-添加复制、粘贴图片功能.md: -------------------------------------------------------------------------------- 1 | # 25. 添加复制、粘贴图片功能 2 | 3 | 日期: 2018-10-31 4 | 5 | ## 状态 6 | 7 | 2018-10-31 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0032-touchbar-支持.md: -------------------------------------------------------------------------------- 1 | # 32. Touchbar 支持 2 | 3 | 日期: 2019-01-28 4 | 5 | ## 状态 6 | 7 | 2019-01-28 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/spike/file-save.md: -------------------------------------------------------------------------------- 1 | 存储策略 2 | === 3 | 4 | 临时文件存储 5 | --- 6 | 7 | https://github.com/bruce/node-temp 8 | 9 | 10 | 11 | https://github.com/eligrey/FileSaver.js/ 12 | 13 | https://github.com/electron/electron/issues/2721 14 | -------------------------------------------------------------------------------- /docs/adr/0006-新增复制图片下载到本地的功能.md: -------------------------------------------------------------------------------- 1 | # 6. 新增复制图片下载到本地的功能 2 | 3 | 日期: 2018-07-24 4 | 5 | ## 状态 6 | 7 | 2018-07-24 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0024-添加不同的显示字体切换.md: -------------------------------------------------------------------------------- 1 | # 24. 添加不同的显示字体切换 2 | 3 | 日期: 2018-09-26 4 | 5 | ## 状态 6 | 7 | 2018-09-26 提议 8 | 9 | ## 背景 10 | 11 | 长时间写作时,容易对字体产生疲劳 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0029-[feat]-添加审校功能.md: -------------------------------------------------------------------------------- 1 | # 29. [FEAT] 添加审校功能 2 | 3 | 日期: 2019-01-11 4 | 5 | ## 状态 6 | 7 | 2019-01-11 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0018-支持内容中的待办事项列表-todo.md: -------------------------------------------------------------------------------- 1 | # 18. 支持内容中的待办事项列表 todo 2 | 3 | 日期: 2018-08-22 4 | 5 | ## 状态 6 | 7 | 2018-08-22 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0005-段落折叠功能.md: -------------------------------------------------------------------------------- 1 | # 5. 段落折叠功能 2 | 3 | 日期: 2018-07-24 4 | 5 | ## 状态 6 | 7 | 2018-07-24 提议 8 | 9 | 2019-01-15 通过 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /docs/adr/0028-[bug]-修改重命名文件后的保存问题.md: -------------------------------------------------------------------------------- 1 | # 28. [BUG] 修改重命名文件后的保存问题 2 | 3 | 日期: 2018-12-28 4 | 5 | ## 状态 6 | 7 | 2018-12-28 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0030-进度条:根据行数和当前行数来显示百分比.md: -------------------------------------------------------------------------------- 1 | # 30. 进度条:根据行数和当前行数来显示百分比 2 | 3 | 日期: 2019-01-14 4 | 5 | ## 状态 6 | 7 | 2019-01-14 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0033-multiple-instances.md: -------------------------------------------------------------------------------- 1 | # 33. multiple instances 2 | 3 | 日期: 2021-08-27 4 | 5 | ## 状态 6 | 7 | 2021-08-27 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /src/native/features/pandoc/index.ts: -------------------------------------------------------------------------------- 1 | import html from "./html"; 2 | import word from "./word"; 3 | import slide from "./slide"; 4 | import pdf from "./pdf"; 5 | 6 | export const pandoc = { 7 | html, 8 | word, 9 | slide, 10 | pdf 11 | }; 12 | -------------------------------------------------------------------------------- /docs/adr/0020-submline-text-类似的管口滚动.md: -------------------------------------------------------------------------------- 1 | # 20. Submline Text 类似的管口滚动 2 | 3 | 日期: 2018-08-24 4 | 5 | ## 状态 6 | 7 | 2018-08-24 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0014-实现重命名文件等对话框.md: -------------------------------------------------------------------------------- 1 | # 14. 实现重命名文件等对话框 2 | 3 | 日期: 2018-08-01 4 | 5 | ## 状态 6 | 7 | 2018-08-01 提议 8 | 9 | 2018-08-22 完成 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /docs/adr/0027-粘贴内容到编辑器时转为-markdown-格式.md: -------------------------------------------------------------------------------- 1 | # 27. 粘贴内容到编辑器时,转为 markdown 格式 2 | 3 | 日期: 2018-12-07 4 | 5 | ## 状态 6 | 7 | 2018-12-07 提议 8 | 9 | ## 背景 10 | 11 | 在这里补充上下文... 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0004-统计打字速度及显示活跃时间.md: -------------------------------------------------------------------------------- 1 | # 4. 统计打字速度及显示活跃时间 2 | 3 | 日期: 2018-07-23 4 | 5 | ## 状态 6 | 7 | 2018-07-23 提议 8 | 9 | 2018-09-10 已弃用 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /docs/adr/0022-[tabs]-支持.md: -------------------------------------------------------------------------------- 1 | # 22. [Tabs] 支持 2 | 3 | 日期: 2018-09-06 4 | 5 | ## 状态 6 | 7 | 2018-09-06 提议 8 | 9 | ## 背景 10 | 11 | https://github.com/adamschwartz/chrome-tabs 12 | 13 | ## 决策 14 | 15 | 在这里补充上决策信息... 16 | 17 | ## 后果 18 | 19 | 在这里记录结果... 20 | -------------------------------------------------------------------------------- /docs/adr/0001-系统进行-discover-设计.md: -------------------------------------------------------------------------------- 1 | # 1. 系统进行 Discover 设计 2 | 3 | 日期: 2018-07-16 4 | 5 | ## 状态 6 | 7 | 2018-07-16 提议 8 | 9 | 2018-09-10 完成 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /docs/adr/0023-[pdf]-支持编译文章成-pdf.md: -------------------------------------------------------------------------------- 1 | # 23. [PDF] 支持编译文章成 PDF 2 | 3 | 日期: 2018-09-10 4 | 5 | ## 状态 6 | 7 | 2018-09-10 提议 8 | 9 | 2019-01-15 完成 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /docs/adr/0013-使用-toast-来实现简单的提醒功能.md: -------------------------------------------------------------------------------- 1 | # 13. 使用 Toast 来实现简单的提醒功能 2 | 3 | 日期: 2018-08-01 4 | 5 | ## 状态 6 | 7 | 2018-08-01 提议 8 | 9 | 2018-08-22 完成 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /docs/spike/editor.md: -------------------------------------------------------------------------------- 1 | CodeMirror 2 | === 3 | 4 | 自动完成 5 | 6 | https://stackoverflow.com/questions/41953077/codemirror-editor-show-hint-after-specific-key-pattern-like 7 | 8 | Ace 编辑器 9 | === 10 | 11 | [https://github.com/ajaxorg/ace/](https://github.com/ajaxorg/ace/) 12 | -------------------------------------------------------------------------------- /components/interact/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "target": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /components/interact/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nlp"] 2 | path = nlp 3 | url = https://github.com/phodal/phodit-nlp 4 | [submodule "editor"] 5 | path = editor 6 | url = https://github.com/phodal/phodit-mde 7 | [submodule "views/reveal.js"] 8 | path = views/reveal.js 9 | url = https://github.com/hakimel/reveal.js/ 10 | -------------------------------------------------------------------------------- /docs/adr/0007-autocomplete->-冒号后自动换行并加-item-标识符.md: -------------------------------------------------------------------------------- 1 | # 7. AutoComplete -> 冒号后自动换行并加 item 标识符 2 | 3 | 日期: 2018-07-24 4 | 5 | ## 状态 6 | 7 | 2018-07-24 提议 8 | 9 | 2019-01-15 完成 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 在这里补充上决策信息... 18 | 19 | ## 后果 20 | 21 | 在这里记录结果... 22 | -------------------------------------------------------------------------------- /components/interact/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /components/header/stencil.config.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | namespace: 'phodit-header', 3 | outputTargets:[ 4 | { 5 | type: 'dist' 6 | }, 7 | { 8 | type: 'dist-custom-elements-bundle', 9 | }, 10 | { 11 | type: 'www', 12 | serviceWorker: false 13 | } 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /docs/spike/open-files.md: -------------------------------------------------------------------------------- 1 | 打开窗口 2 | === 3 | 4 | [使用 Electron 调用系统对话框](https://segmentfault.com/a/1190000008530168) 5 | 6 | 树形结构 7 | --- 8 | 9 | https://codeburst.io/practical-recursion-implementing-a-file-tree-view-in-react-electron-af62e7b46d26 10 | 11 | 12 | 代码:https://github.com/mperitz/react-filetree-electron 13 | -------------------------------------------------------------------------------- /docs/adr/0026-添加粘贴图片到目录的支持.md: -------------------------------------------------------------------------------- 1 | # 26. 添加粘贴图片到目录的支持 2 | 3 | 日期: 2018-12-07 4 | 5 | ## 状态 6 | 7 | 2018-12-07 提议 8 | 9 | 2018-12-10 已弃用 10 | 11 | ## 背景 12 | 13 | 当前的 Electron 并不支持复制、粘贴图片 14 | 15 | https://electronjs.org/docs/api/clipboard 16 | 17 | ## 决策 18 | 19 | 在这里补充上决策信息... 20 | 21 | ## 后果 22 | 23 | 在这里记录结果... 24 | -------------------------------------------------------------------------------- /docs/spike/mindmap.md: -------------------------------------------------------------------------------- 1 | 思维导图 2 | === 3 | 4 | Atom 插件:https://atom.io/packages/markdown-mindmap 5 | 6 | 百度脑图: 7 | 8 | https://github.com/fex-team/kityminder-core 9 | 10 | https://github.com/fex-team/kityminder-editor 11 | 12 | 库 13 | --- 14 | 15 | Markmap 16 | 17 | https://github.com/dundalek/markmap 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/adr/0011-监测到文件变化自动更新目录.md: -------------------------------------------------------------------------------- 1 | # 11. 监测到文件变化,自动更新目录 2 | 3 | 日期: 2018-07-29 4 | 5 | ## 状态 6 | 7 | 2018-07-29 提议 8 | 9 | 2018-09-10 通过 10 | 11 | 2018-09-10 完成 12 | 13 | ## 背景 14 | 15 | 在这里补充上下文... 16 | 17 | https://github.com/bevry/watchr 18 | 19 | ## 决策 20 | 21 | 在这里补充上决策信息... 22 | 23 | ## 后果 24 | 25 | 在这里记录结果... 26 | -------------------------------------------------------------------------------- /docs/adr/0012-使用-pandoc-来转换-slide.md: -------------------------------------------------------------------------------- 1 | # 12. 使用 pandoc 来转换 slide 2 | 3 | 日期: 2018-07-30 4 | 5 | ## 状态 6 | 7 | 2018-07-30 提议 8 | 9 | 2018-08-22 完成 10 | 11 | ## 背景 12 | 13 | 在这里补充上下文... 14 | 15 | ## 决策 16 | 17 | 将 reveal.js 和生成的 index.html 文件放置到 tmp 目录中,再读取。 18 | 19 | 每次更新后,替换掉旧的文件。 20 | 21 | ## 后果 22 | 23 | 在这里记录结果... 24 | -------------------------------------------------------------------------------- /components/interact/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /components/header/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /components/tree-view/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React App 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /components/header/src/components/phodit-header/phodit-header.css: -------------------------------------------------------------------------------- 1 | #phodit-editor { 2 | width: 100%; 3 | height: 20px; 4 | background: #fdfdfd; 5 | border-top: 1px solid #ddd; 6 | border-bottom: 1px solid #ddd; 7 | } 8 | 9 | .close-button { 10 | float: right; 11 | right: 10px; 12 | position: relative; 13 | line-height: 20px; 14 | } 15 | -------------------------------------------------------------------------------- /src/native/features/git/status.ts: -------------------------------------------------------------------------------- 1 | import spawnGit from "./spawn-git"; 2 | 3 | export default function gitStatus(path: string) { 4 | spawnGit("status --porcelain", {cwd: path}, function(err: any, stdout: any, stdrr: any) { 5 | if (err) { 6 | return; 7 | } 8 | // console.log(stdout.trim()); 9 | 10 | return stdout.trim(); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /docs/adr/0021-自动发布系统-dsl.md: -------------------------------------------------------------------------------- 1 | # 21. 自动发布系统 DSL 2 | 3 | 日期: 2018-09-04 4 | 5 | ## 状态 6 | 7 | 2018-09-04 提议 8 | 9 | ## 背景 10 | 11 | 保存 cookies 12 | 13 | https://blog.csdn.net/changhuzhao/article/details/78773279 14 | 15 | 官方 cookie 文档 16 | 17 | https://electronjs.org/docs/api/cookies 18 | 19 | ## 决策 20 | 21 | 在这里补充上决策信息... 22 | 23 | ## 后果 24 | 25 | 在这里记录结果... 26 | -------------------------------------------------------------------------------- /components/interact/src/app/interact-bar.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /docs/adr/0002-中文-nlp-相关功能.md: -------------------------------------------------------------------------------- 1 | # 2. 中文 NLP 相关功能 2 | 3 | 日期: 2018-07-20 4 | 5 | ## 状态 6 | 7 | 2018-07-20 提议 8 | 9 | 2019-01-15 完成 10 | 11 | ## 背景 12 | 13 | 希望在写作的时候,能自动提醒作者(即我)找到对应文章的链接,并做一些相关的自动匹配 14 | 15 | Editor 能支持以下的功能: 16 | 17 | - 查询、遍历先前博客的内容,并给出相关的文章 18 | - 自动寻找文章的关键词 19 | - 能支持直接寻找外部链接,如输入 Wiki 时,能查询 20 | 21 | ## 决策 22 | 23 | ## 后果 24 | 25 | 在这里记录结果... 26 | -------------------------------------------------------------------------------- /components/header/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | www/ 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.lock 8 | *.tmp 9 | *.tmp.* 10 | log.txt 11 | *.sublime-project 12 | *.sublime-workspace 13 | 14 | .stencil/ 15 | .idea/ 16 | .vscode/ 17 | .sass-cache/ 18 | .versions/ 19 | node_modules/ 20 | $RECYCLE.BIN/ 21 | 22 | .DS_Store 23 | Thumbs.db 24 | UserInterfaceState.xcuserstate 25 | .env 26 | -------------------------------------------------------------------------------- /src/native/features/pandoc/html.ts: -------------------------------------------------------------------------------- 1 | import spawnPandoc from "./spawn-pandoc"; 2 | 3 | export default function html(path: string) { 4 | spawnPandoc(`-s ${path} -t html5 -o index.html`, {cwd: path}, function(err: any, stdout: any, stdrr: any) { 5 | if (err) { 6 | return; 7 | } 8 | console.log(stdout.trim()); 9 | 10 | return stdout.trim(); 11 | }); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /docs/spike/multiple-windows.md: -------------------------------------------------------------------------------- 1 | 多窗口示例 2 | === 3 | 4 | [https://github.com/akabekobeko/examples-electron/blob/master/multiple-windows/src/js/main/App.js](https://github.com/akabekobeko/examples-electron/blob/master/multiple-windows/src/js/main/App.js) 5 | 6 | 打开新窗口示例 7 | --- 8 | 9 | https://stackoverflow.com/questions/42044898/open-a-new-window-to-display-a-html-file-using-a-menu-click-electron 10 | -------------------------------------------------------------------------------- /components/interact/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to interact!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /components/interact/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /components/tree-view/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /src/render/support/event.util.ts: -------------------------------------------------------------------------------- 1 | export function createEvent(name: string, data: any) { 2 | let event: CustomEvent; 3 | if (typeof data === "string") { 4 | event = new CustomEvent(name, { 5 | detail: data, 6 | }); 7 | } else { 8 | event = new CustomEvent(name, { 9 | detail: JSON.stringify(data), 10 | }); 11 | } 12 | 13 | window.document.dispatchEvent(event); 14 | } 15 | -------------------------------------------------------------------------------- /components/interact/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/interact/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /docs/spike/search-server.md: -------------------------------------------------------------------------------- 1 | 搜索功能 2 | === 3 | 4 | ## Cluster 5 | 6 | http://nodejs.cn/api/cluster.html 7 | 8 | ## Express 9 | 10 | https://github.com/frankhale/electron-with-express 11 | 12 | ## MultiThreading 13 | 14 | 方式 -> 使用 WebWorkers: 15 | 16 | https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers 17 | 18 | WebWorker 相关库: 19 | 20 | https://github.com/GoogleChromeLabs/comlink 21 | 22 | 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "sourceMap": true, 6 | "outDir": "dist", 7 | "baseUrl": ".", 8 | "target": "es6", 9 | "paths": { 10 | "*": [ 11 | "node_modules/*" 12 | ] 13 | }, 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "include": [ 20 | "src/**/*" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /components/interact/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/native/features/pandoc/word.ts: -------------------------------------------------------------------------------- 1 | import spawnPandoc from "./spawn-pandoc"; 2 | import {shell} from "electron"; 3 | 4 | export default function word(path: string) { 5 | const newPath = path.replace(/\.md/, '.docx'); 6 | spawnPandoc(`-s ${path} -o ${newPath}`, {}, function(err: any, stdout: any, stdrr: any) { 7 | if (err) { 8 | console.error(err); 9 | return; 10 | } 11 | 12 | shell.openPath(newPath).then(() => {}) 13 | return stdout.trim(); 14 | }); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /components/interact/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es2015", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/spike/chinese-improve.md: -------------------------------------------------------------------------------- 1 | 中文增强 2 | === 3 | 4 | 1. 字数统计 5 | 2. 中文分词和拼写检查 6 | 7 | 8 | 拼写检测相关 9 | --- 10 | 11 | https://segmentfault.com/a/1190000008233147 12 | 13 | https://github.com/topics/spell 14 | 15 | 相关库 16 | --- 17 | 18 | 语言检测 19 | 20 | https://github.com/wooorm/franc 21 | 22 | Hunspell 的 Node.js 绑定: 23 | 24 | https://github.com/Wulf/nodehun 25 | 26 | Hunspell 兼容的拼写检查: 27 | 28 | https://github.com/wooorm/nspell 29 | 30 | Hunspell 字典 31 | 32 | https://github.com/wooorm/dictionaries 33 | -------------------------------------------------------------------------------- /components/header/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stencil Component Starter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/render/key.event.ts: -------------------------------------------------------------------------------- 1 | document.onkeydown = (event: any) => { 2 | event = event || window.event; 3 | let isEscape = false; 4 | if ("key" in event) { 5 | isEscape = (event.key === "Escape" || event.key === "Esc"); 6 | } else { 7 | isEscape = (event.keyCode === 27); 8 | } 9 | if (isEscape) { 10 | document.querySelector("interact-bar").setAttribute("style", "display: none;"); 11 | 12 | document.getElementById("terminal-section").setAttribute("style", "display: none;"); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "max-line-length": { 5 | "options": [ 6 | 120 7 | ] 8 | }, 9 | "new-parens": true, 10 | "no-arg": true, 11 | "no-var-requires": false, 12 | "no-bitwise": true, 13 | "no-console": false, 14 | "object-literal-sort-keys": false, 15 | "no-conditional-assignment": true, 16 | "no-consecutive-blank-lines": false 17 | }, 18 | "jsRules": { 19 | "max-line-length": { 20 | "options": [ 21 | 120 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/native/features/pandoc/slide.ts: -------------------------------------------------------------------------------- 1 | import spawnPandoc from "./spawn-pandoc"; 2 | const tmp = require("tmp"); 3 | 4 | export default function slide(path: string) { 5 | const tmpobj = tmp.fileSync(); 6 | const newPath = tmpobj.name; 7 | 8 | return new Promise(function (resolve, reject) { 9 | spawnPandoc(` -t revealjs -s ${path} -o ${newPath} -V theme=simple`, {}, function (err: any, stdout: any, stdrr: any) { 10 | if (err) { 11 | reject(err); 12 | return; 13 | } 14 | 15 | stdout.trim(); 16 | resolve(tmpobj.name); 17 | }); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /docs/spike/storage.md: -------------------------------------------------------------------------------- 1 | 存储 2 | === 3 | 4 | 官方: 5 | 6 | [JSON-Storage](https://github.com/electron-userland/electron-json-storage) 7 | 8 | 其它: 9 | 10 | - [electron-settings](https://github.com/nathanbuchar/electron-settings) 11 | - [electron-store](https://github.com/sindresorhus/electron-store) 12 | - [electron-storage](https://github.com/Cocycles/electron-storage) 13 | 14 | 15 | SQLite 16 | --- 17 | 18 | [electron-boilerplate-sqlite](https://github.com/sjmelia/electron-boilerplate-sqlite) 19 | 20 | 21 | 22 | 客户端存储 23 | --- 24 | 25 | [store.js](https://github.com/marcuswestin/store.js) 26 | -------------------------------------------------------------------------------- /components/header/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "allowUnreachableCode": false, 5 | "declaration": false, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "moduleResolution": "node", 12 | "module": "esnext", 13 | "target": "es2017", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "jsx": "react", 17 | "jsxFactory": "h" 18 | }, 19 | "include": [ 20 | "src", 21 | "types/jsx.d.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /components/interact/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Interact 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | -------------------------------------------------------------------------------- /src/native/pages/html.page.ts: -------------------------------------------------------------------------------- 1 | export function openHtmlPage(BrowserWindow: any, filePath: any) { 2 | let htmlPage: any = null; 3 | 4 | function buildHtmlPage() { 5 | if (htmlPage) { 6 | htmlPage.focus(); 7 | return; 8 | } 9 | 10 | htmlPage = new BrowserWindow({ 11 | height: 768, 12 | width: 1024, 13 | title: "网页", 14 | backgroundColor: "#fff", 15 | }); 16 | 17 | htmlPage.on("closed", () => { 18 | htmlPage = null; 19 | }); 20 | 21 | htmlPage.loadFile(filePath); 22 | htmlPage.once("ready-to-show", () => htmlPage.show()); 23 | // htmlPage.webContents.openDevTools(); 24 | } 25 | 26 | buildHtmlPage(); 27 | } 28 | -------------------------------------------------------------------------------- /components/interact/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /src/native/features/pandoc/pdf.ts: -------------------------------------------------------------------------------- 1 | import spawnPandoc from "./spawn-pandoc"; 2 | import { shell } from "electron"; 3 | 4 | export default function pdf(path: string) { 5 | const pdfPath = path.replace(/\.md/, '.pdf'); 6 | 7 | const additional = `--pdf-engine=xelatex -V -VCJKoptions=BoldFont="Hei" -VCJKmainfont="Hei"`; 8 | spawnPandoc(`-s ${path} -o ${pdfPath} ${additional}`, {}, (err: any, stdout: any, stdrr: any) => { 9 | if (err) { 10 | console.error(err); 11 | return; 12 | } 13 | 14 | console.info(stdout); 15 | console.info(stdrr); 16 | 17 | // tslint:disable-next-line:no-empty 18 | shell.openPath(pdfPath).then(() => { 19 | }) 20 | return stdout.trim(); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /components/interact/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import {Injector, NgModule} from '@angular/core'; 3 | 4 | import {InteractBar} from './interact-bar.component'; 5 | import {createCustomElement} from "@angular/elements"; 6 | import {FormsModule} from "@angular/forms"; 7 | 8 | @NgModule({ 9 | declarations: [InteractBar], 10 | imports: [BrowserModule, FormsModule], 11 | entryComponents: [InteractBar] 12 | }) 13 | export class AppModule { 14 | constructor(private injector: Injector) { 15 | const interactBar = createCustomElement(InteractBar, {injector}); 16 | customElements.define('interact-bar', interactBar); 17 | } 18 | 19 | ngDoBootstrap() { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/interact/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /docs/spike/spell-check.md: -------------------------------------------------------------------------------- 1 | 中文拼写检查 2 | === 3 | 4 | CodeMirrorSpellChecker 5 | 6 | https://github.com/sparksuite/codemirror-spell-checker/blob/master/src/js/spell-checker.js 7 | 8 | 字典库: 9 | 10 | [http://download.huzheng.org/zh_CN/](http://download.huzheng.org/zh_CN/) 11 | 12 | 13 | Node.js 版本: 14 | 15 | https://github.com/Wulf/nodehun 16 | 17 | [Hunspell 词干提取器](https://www.elastic.co/guide/cn/elasticsearch/guide/current/hunspell.html) 18 | 19 | 20 | 中文分词: 21 | 22 | https://github.com/yanyiwu/nodejieba 23 | 24 | 敏感词: 25 | 26 | https://github.com/aojiaotage/text-censor 27 | 28 | Language Tool 29 | --- 30 | 31 | https://www.languagetool.org/download/ 32 | 33 | 在线 API: 34 | 35 | http://wiki.languagetool.org/public-http-api 36 | -------------------------------------------------------------------------------- /src/native/pages/help.page.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export function createHelpPage(BrowserWindow: any) { 4 | let helpWindow: any = null; 5 | 6 | function openAboutWindow() { 7 | if (helpWindow) { 8 | helpWindow.focus(); 9 | return; 10 | } 11 | 12 | helpWindow = new BrowserWindow({ 13 | height: 800, 14 | width: 600, 15 | title: "Markdown 帮助", 16 | backgroundColor: "#ddd", 17 | }); 18 | 19 | helpWindow.on("closed", () => { 20 | helpWindow = null; 21 | }); 22 | 23 | helpWindow.loadFile(path.join(__dirname, "../../../views/help.html")); 24 | 25 | helpWindow.once("ready-to-show", () => helpWindow.show()); 26 | } 27 | 28 | openAboutWindow(); 29 | } 30 | -------------------------------------------------------------------------------- /components/interact/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | osx_image: xcode10.2 3 | #dist: bionic 4 | #os: linux 5 | language: node_js 6 | node_js: 7 | - '15' 8 | 9 | cache: 10 | directories: 11 | - node_modules 12 | - "$HOME/.electron" 13 | - "$HOME/.cache" 14 | 15 | addons: 16 | apt: 17 | packages: 18 | - libgnome-keyring-dev 19 | - icnsutils 20 | - xvfb 21 | 22 | install: 23 | - npm install -g xvfb-maybe 24 | - npm install -g yarn 25 | - npm install 26 | 27 | services: 28 | - xvfb 29 | 30 | before_script: 31 | - export DISPLAY=':99.0' 32 | - Xvfb :99 -screen 0 1024x600x24 > /dev/null 2>&1 & 33 | - sleep 3 # give xvfb some time to start 34 | 35 | script: 36 | - PROGRESS="--no-progress" npm run build:native 37 | - PROGRESS="--no-progress" npm run test 38 | -------------------------------------------------------------------------------- /components/tree-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-view", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "classnames": "^2.2.6", 7 | "react": "^16.4.1", 8 | "react-dom": "^16.4.1", 9 | "react-scripts": "1.1.4", 10 | "react-ui-tree": "https://github.com/phodal/react-ui-tree" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start && yarn copyfile", 14 | "build": "react-scripts build --watch && yarn copyfile", 15 | "copyfile": "mkdir -p ../../dist/component/tree-view && cp build/static/js/*.js ../../dist/component/tree-view/tree-view.js && cp build/static/css/*.css ../../dist/component/tree-view/tree-view.css", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 30 | node_modules 31 | Phodit-darwin-x64/ 32 | componets/**/dist 33 | assets/data 34 | logo.png 35 | *.tgz 36 | build 37 | .idea 38 | .DS_Store 39 | -------------------------------------------------------------------------------- /src/native/support/file-support.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | const storage = require("electron-json-storage"); 4 | 5 | export function dirTree(filename: string) { 6 | let stats; 7 | try { 8 | stats = fs.lstatSync(filename); 9 | } catch (e) { 10 | storage.remove("storage.last.path"); 11 | return; 12 | } 13 | const info: any = { 14 | filename, 15 | module: path.basename(filename), 16 | }; 17 | 18 | if (stats.isDirectory()) { 19 | info.collapsed = true; 20 | info.children = fs.readdirSync(filename).filter((child: string) => { 21 | return child !== ".git" && child !== ".DS_Store" && child !== ".idea"; 22 | }).map((child) => dirTree(filename + "/" + child)); 23 | } else { 24 | info.leaf = true; 25 | } 26 | 27 | return info; 28 | } 29 | -------------------------------------------------------------------------------- /components/interact/src/app/interact-bar.component.css: -------------------------------------------------------------------------------- 1 | .interact-bar { 2 | background: #5e6772; 3 | width: 100%; 4 | max-width: 100%; 5 | height: 64px; 6 | font-size: 12px; 7 | display: inline-block; 8 | } 9 | 10 | .interact-bar .form-group { 11 | display: flex; 12 | } 13 | 14 | .interact-bar label { 15 | color: #fff; 16 | line-height: 64px; 17 | padding-left: 10px; 18 | padding-right: 10px; 19 | width: 120px; 20 | } 21 | 22 | .interact-bar input { 23 | width: calc(100% - 120px); 24 | height: 48px; 25 | margin-top: 10px; 26 | font-size: 16px; 27 | line-height: 64px; 28 | display: inline-block; 29 | font-weight: 400; 30 | color: #5e6772; 31 | background: #fff; 32 | border: 0; 33 | border-radius: 3px; 34 | outline: 0; 35 | text-indent: 4px; 36 | transition: all .3s ease-in-out; 37 | } 38 | -------------------------------------------------------------------------------- /components/interact/src/app/interact-bar.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output, ViewEncapsulation} from '@angular/core'; 2 | 3 | @Component({ 4 | // tslint:disable-next-line:component-selector 5 | selector: 'interact-bar', 6 | templateUrl: './interact-bar.component.html', 7 | styleUrls: ['./interact-bar.component.css'], 8 | encapsulation: ViewEncapsulation.Emulated 9 | }) 10 | // tslint:disable-next-line:component-class-suffix 11 | export class InteractBar { 12 | @Input() set filename(value: string) { 13 | this.renameModel.name = value; 14 | } 15 | 16 | @Output() action = new EventEmitter(); 17 | renameModel = { 18 | name: this.filename 19 | }; 20 | 21 | onSubmit() { 22 | if (!this.renameModel.name) { 23 | return; 24 | } 25 | this.action.emit(this.renameModel.name); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/native/ui/touch-bar.ts: -------------------------------------------------------------------------------- 1 | import {BrowserWindow} from "electron"; 2 | 3 | const {TouchBar} = require('electron'); 4 | 5 | import {EventConstants} from '../../common/constants/event.constants'; 6 | 7 | const {TouchBarButton, TouchBarSpacer} = TouchBar; 8 | 9 | export default function buildTouchBar(mainWindow: BrowserWindow) { 10 | let dark = false; 11 | const theme = new TouchBarButton({ 12 | label: '🎰 Theme', 13 | backgroundColor: '#384452', 14 | click: () => { 15 | mainWindow.webContents.send(EventConstants.PHODIT.TOGGLE_THEME, {}); 16 | dark = !dark; 17 | if(dark) { 18 | theme.backgroundColor = '#384452'; 19 | } else { 20 | theme.backgroundColor = '#fff'; 21 | } 22 | } 23 | }); 24 | 25 | return new TouchBar({ 26 | items: [ 27 | theme, 28 | new TouchBarSpacer({size: 'large'}), 29 | ] 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /components/interact/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/render/worker/suggest.worker.ts: -------------------------------------------------------------------------------- 1 | const blogpostData = require("../../assets/data/output.json"); 2 | 3 | const lunr = require("lunr"); 4 | const dataWithIndex: any[] = []; 5 | let lunrIdx: any; 6 | 7 | lunrIdx = lunr(function() { 8 | this.field("title", {boost: 10}); 9 | this.field("content"); 10 | 11 | for (const item of blogpostData) { 12 | this.add(item); 13 | dataWithIndex[item.id] = item; 14 | } 15 | }); 16 | 17 | export class SuggestWorker { 18 | public search(arg: string) { 19 | const searchResults = lunrIdx.search(arg); 20 | const response = []; 21 | 22 | for (const result of searchResults) { 23 | const blogpost = dataWithIndex[result.ref]; 24 | response.push({ 25 | text: `[${blogpost.title}](https://www.phodal.com/blog/${blogpost.slug})`, 26 | displayText: blogpost.title, 27 | }); 28 | } 29 | 30 | return response; 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /components/header/src/components/phodit-header/phodit-header.tsx: -------------------------------------------------------------------------------- 1 | import {Component, State, h} from '@stencil/core'; 2 | 3 | @Component({ 4 | tag: 'phodit-header', 5 | styleUrl: 'phodit-header.css' 6 | }) 7 | export class PhoditHeader { 8 | @State() showCloseHeader = false; 9 | 10 | componentDidLoad() { 11 | var that = this; 12 | window.document.addEventListener('phodit.editor.git.commit', function () { 13 | that.showCloseHeader = true; 14 | }) 15 | } 16 | 17 | handleClick() { 18 | var event = new CustomEvent("phodit.editor.hidden.terminal", {}); 19 | window.document.dispatchEvent(event); 20 | } 21 | 22 | render() { 23 | if (this.showCloseHeader) { 24 | return (
25 | 26 | 27 | 28 |
); 29 | } 30 | 31 | return (
); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/css/themes/dark-theme.css: -------------------------------------------------------------------------------- 1 | /* #22272e */ 2 | 3 | 4 | [data-theme^="dark"] .CodeMirror, 5 | [data-theme^="dark"] #input .editor-statusbar, 6 | [data-theme^="dark"] #input .editor-toolbar { 7 | background: #22272e; 8 | } 9 | 10 | #input .CodeMirror.cm-s-django .cm-link { 11 | color: #539bf5; 12 | } 13 | 14 | #input .CodeMirror.cm-s-django, 15 | #input .CodeMirror.cm-s-django .cm-variable-2, 16 | .CodeMirror.cm-s-django .cm-header { 17 | color: #adbac7; 18 | } 19 | 20 | #input .CodeMirror.cm-s-django .cm-quote { 21 | color: #768390; 22 | } 23 | 24 | #input .cm-s-django .cm-header-1 { 25 | font-size: 200%; 26 | line-height: 200%; 27 | } 28 | 29 | /* todo: add more font size */ 30 | #input .cm-s-django .cm-header-2 { 31 | font-size: 160%; 32 | line-height: 160%; 33 | } 34 | 35 | #input .cm-s-django .cm-header-3 { 36 | font-size: 125%; 37 | line-height: 125%; 38 | } 39 | 40 | #input .cm-s-django .cm-header-4 { 41 | font-size: 110%; 42 | line-height: 110%; 43 | } 44 | -------------------------------------------------------------------------------- /src/render/support/file.utils.ts: -------------------------------------------------------------------------------- 1 | export function getFileName(path: string) { 2 | if (!path) { 3 | return path; 4 | } 5 | 6 | const splitArray = path.split("/"); 7 | return splitArray[splitArray.length - 1]; 8 | } 9 | 10 | export function getCodeMirrorMode(file: string) { 11 | file = getFileName(file); 12 | 13 | if (file.toLowerCase() === "makefile") { 14 | return "cmake"; 15 | } 16 | 17 | let format = "gfm"; 18 | if (file.endsWith(".css")) { 19 | format = "css"; 20 | } 21 | if (file.endsWith(".js")) { 22 | format = "js"; 23 | } 24 | if (file.endsWith(".html")) { 25 | format = "xml"; 26 | } 27 | if (file.endsWith(".tex")) { 28 | format = "tex"; 29 | } 30 | if (file.endsWith(".tex")) { 31 | format = "textile"; 32 | } 33 | if (file.endsWith(".xml")) { 34 | format = "xml"; 35 | } 36 | if (file.endsWith(".yml")) { 37 | format = "yaml"; 38 | } 39 | if (file.endsWith(".md")) { 40 | format = "multiplex"; 41 | } 42 | return format; 43 | } 44 | -------------------------------------------------------------------------------- /docs/spike/shortcuts.md: -------------------------------------------------------------------------------- 1 | 快捷键 2 | === 3 | 4 | https://electronjs.org/docs/tutorial/keyboard-shortcuts 5 | 6 | 7 | 菜单 8 | --- 9 | 10 | ```javascript 11 | const {Menu, MenuItem} = require('electron') 12 | const menu = new Menu() 13 | 14 | menu.append(new MenuItem({ 15 | label: 'Print', 16 | accelerator: 'CmdOrCtrl+P', 17 | click: () => { console.log('time to print stuff') } 18 | })) 19 | ``` 20 | 21 | 全局 22 | --- 23 | 24 | ``` 25 | const {app, globalShortcut} = require('electron') 26 | 27 | app.on('ready', () => { 28 | globalShortcut.register('CommandOrControl+X', () => { 29 | console.log('CommandOrControl+X is pressed') 30 | }) 31 | }) 32 | ``` 33 | 34 | BrowserWindow 35 | --- 36 | 37 | [Mousetrap](https://github.com/ccampbell/mousetrap) 38 | 39 | ```javascript 40 | // gmail 风格序列 41 | Mousetrap.bind('g i', () => { console.log('go to inbox') }) 42 | Mousetrap.bind('* a', () => { console.log('select all') }) 43 | 44 | // konami 代码 45 | Mousetrap.bind('up up down down left right left right b a enter', () => { 46 | console.log('konami code') 47 | }) 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /components/interact/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /components/header/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ionic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /components/interact/README.md: -------------------------------------------------------------------------------- 1 | # Interact 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.8. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018~2019 Phodal Huang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/spike/communication.md: -------------------------------------------------------------------------------- 1 | Electron 通讯 2 | --- 3 | 4 | [Electron 文档](https://electronjs.org/docs/api/ipc-main) 5 | 6 | ## [发送消息](https://electronjs.org/docs/api/ipc-main#%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF) 7 | 8 | 当然也有可能从主进程向渲染进程发送消息,查阅webContents.send 获取更多信息。 9 | 10 | * 发送消息时,事件名称为`channel`。 11 | * 回复同步信息时,需要设置`event.returnValue`。 12 | * 将异步消息发送回发件人,需要使用`event.sender.send(...)`。 13 | 14 | 下面是在渲染和主进程之间发送和处理消息的一个例子: 15 | 16 | ```javascript 17 | // 在主进程中. 18 | const {ipcMain} = require('electron') 19 | ipcMain.on('asynchronous-message', (event, arg) => { 20 | console.log(arg) // prints "ping" 21 | event.sender.send('asynchronous-reply', 'pong') 22 | }) 23 | 24 | ipcMain.on('synchronous-message', (event, arg) => { 25 | console.log(arg) // prints "ping" 26 | event.returnValue = 'pong' 27 | }) 28 | ``` 29 | 30 | ```javascript 31 | //在渲染器进程 (网页) 中。 32 | const {ipcRenderer} = require('electron') 33 | console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" 34 | 35 | ipcRenderer.on('asynchronous-reply', (event, arg) => { 36 | console.log(arg) // prints "pong" 37 | }) 38 | ipcRenderer.send('asynchronous-message', 'ping') 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/spike/nlp.md: -------------------------------------------------------------------------------- 1 | 短语预测 2 | === 3 | 4 | https://github.com/talyssonoc/jsT9 5 | 6 | 7 | 高亮 8 | --- 9 | 10 | https://github.com/julmot/mark.js 11 | 12 | 文章智能感知 13 | --- 14 | 15 | Trie 16 | 17 | https://gist.github.com/tpae/72e1c54471e88b689f85ad2b3940a8f0 18 | 19 | https://github.com/pthurlow/triejs 20 | 21 | https://johnresig.com/blog/javascript-trie-performance-analysis/ 22 | 23 | 中文 nlp 24 | --- 25 | 26 | https://www.zhihu.com/question/19929473 27 | 28 | Python 库: 29 | 30 | https://github.com/huyingxi/Synonyms 31 | 32 | Node.js 版本: 33 | 34 | https://github.com/Samurais/node-synonyms 35 | 36 | OpenNLP 中文: 37 | 38 | https://github.com/mbejda/Node-OpenNLP 39 | 40 | Search Engine 41 | --- 42 | 43 | https://github.com/olivernn/lunr.js/ 44 | 45 | lunr.js 不支持中文 46 | 47 | 修改版本: 48 | 49 | https://github.com/codepiano/lunr.js 50 | 51 | https://github.com/weixsong/elasticlunr.js 52 | 53 | 54 | 数据库 55 | --- 56 | 57 | https://github.com/louischatriot/nedb 58 | 59 | https://github.com/techfort/LokiJS 60 | 61 | 自动联想 62 | --- 63 | 64 | https://blog.csdn.net/wendingzhulu/article/details/38168039 65 | 66 | 同义词 67 | --- 68 | 69 | https://github.com/codemirror/CodeMirror/blob/master/demo/complete.html 70 | -------------------------------------------------------------------------------- /components/header/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Resources:** 2 | Before submitting an issue, please consult our [docs](https://stenciljs.com/). 3 | 4 | **Stencil version:** (run `npm list @stencil/core` from a terminal/cmd prompt and paste output below): 5 | 6 | ``` 7 | insert the output from npm list @stencil/core here 8 | ``` 9 | 10 | **I'm submitting a ...** (check one with "x") 11 | [ ] bug report 12 | [ ] feature request 13 | [ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or https://stencil-worldwide.slack.com 14 | 15 | **Current behavior:** 16 | 17 | 18 | **Expected behavior:** 19 | 20 | 21 | **Steps to reproduce:** 22 | 24 | 25 | **Related code:** 26 | 27 | ``` 28 | insert any relevant code here 29 | ``` 30 | 31 | **Other information:** 32 | 33 | -------------------------------------------------------------------------------- /components/header/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phodit-header", 3 | "version": "0.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "phodit-header", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@stencil/core": "^2.7.0" 13 | } 14 | }, 15 | "node_modules/@stencil/core": { 16 | "version": "2.7.0", 17 | "resolved": "https://repo.huaweicloud.com/repository/npm/@stencil/core/-/core-2.7.0.tgz", 18 | "integrity": "sha512-5TyOQQzAkvKACRCvjOF4Uq2T527T8WJ9u+EEwtoBA88b7PFjrgI1wbEFAkYGJLghvKi/ED+arV+NiH3ZnbVKXg==", 19 | "dev": true, 20 | "license": "MIT", 21 | "bin": { 22 | "stencil": "bin/stencil" 23 | }, 24 | "engines": { 25 | "node": ">=12.10.0", 26 | "npm": ">=6.0.0" 27 | } 28 | } 29 | }, 30 | "dependencies": { 31 | "@stencil/core": { 32 | "version": "2.7.0", 33 | "resolved": "https://repo.huaweicloud.com/repository/npm/@stencil/core/-/core-2.7.0.tgz", 34 | "integrity": "sha512-5TyOQQzAkvKACRCvjOF4Uq2T527T8WJ9u+EEwtoBA88b7PFjrgI1wbEFAkYGJLghvKi/ED+arV+NiH3ZnbVKXg==", 35 | "dev": true 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /components/header/src/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | /** 4 | * This is an autogenerated file created by the Stencil compiler. 5 | * It contains typing information for all components that exist in this project. 6 | */ 7 | import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; 8 | export namespace Components { 9 | interface PhoditHeader { 10 | } 11 | } 12 | declare global { 13 | interface HTMLPhoditHeaderElement extends Components.PhoditHeader, HTMLStencilElement { 14 | } 15 | var HTMLPhoditHeaderElement: { 16 | prototype: HTMLPhoditHeaderElement; 17 | new (): HTMLPhoditHeaderElement; 18 | }; 19 | interface HTMLElementTagNameMap { 20 | "phodit-header": HTMLPhoditHeaderElement; 21 | } 22 | } 23 | declare namespace LocalJSX { 24 | interface PhoditHeader { 25 | } 26 | interface IntrinsicElements { 27 | "phodit-header": PhoditHeader; 28 | } 29 | } 30 | export { LocalJSX as JSX }; 31 | declare module "@stencil/core" { 32 | export namespace JSX { 33 | interface IntrinsicElements { 34 | "phodit-header": LocalJSX.PhoditHeader & JSXBase.HTMLAttributes; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/input-test.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./global-setup'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const describe = global.describe; 6 | const it = global.it; 7 | const beforeEach = global.beforeEach; 8 | const afterEach = global.afterEach; 9 | const expect = require('chai').expect; 10 | 11 | describe('input test', function () { 12 | beforeEach(function () { 13 | return helpers 14 | .startApplication({ 15 | args: [path.join(__dirname, '..')], 16 | }) 17 | .then(function (startedApp) { 18 | app = startedApp; 19 | }); 20 | }); 21 | 22 | afterEach(function () { 23 | return helpers.stopApplication(app); 24 | }); 25 | 26 | describe('webContents.sendInputEvent', function () { 27 | xit('triggers a keypress DOM event', async function () { 28 | await app.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' }); 29 | const elem = await app.client.$('.keypress-count'); 30 | console.log(elem); 31 | let text = await elem.getText(); 32 | expect(text).to.equal('A'); 33 | await app.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'B' }); 34 | text = await elem.getText(); 35 | expect(text).to.equal('B'); 36 | }); 37 | }); 38 | }); 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/render/plugins/terminal.ts: -------------------------------------------------------------------------------- 1 | const os = require("os"); 2 | const pty = require("node-pty"); 3 | const Terminal = require("xterm").Terminal; 4 | const FitAddon = require('xterm-addon-fit').FitAddon; 5 | const WebLinksAddon = require('xterm-addon-web-links').WebLinksAddon; 6 | 7 | export function createTerminal(path?: string) { 8 | // Initialize node-pty with an appropriate shell 9 | const shell = process.env[os.platform() === "win32" ? "COMSPEC" : "SHELL"]; 10 | const ptyProcess = pty.spawn(shell, [], { 11 | name: 'xterm-color', 12 | cols: 80, 13 | rows: 15, 14 | cwd: path || process.cwd(), 15 | env: process.env 16 | }); 17 | 18 | // Initialize xterm.js and attach it to the DOM 19 | const xterm = new Terminal({ 20 | cols: 80, 21 | rows: 15, 22 | cursorBlink: true, 23 | }); 24 | const fitaddon = new FitAddon(); 25 | xterm.loadAddon(fitaddon); 26 | xterm.loadAddon(new WebLinksAddon()); 27 | 28 | xterm.open(document.getElementById("terminal")); 29 | fitaddon.fit(); 30 | 31 | // Setup communication between xterm.js and node-pty 32 | xterm.onData((data: any) => { 33 | ptyProcess.write(data); 34 | }); 35 | 36 | ptyProcess.on("data", (data: any) => { 37 | xterm.write(data); 38 | }); 39 | 40 | ptyProcess.write("git status \n"); 41 | } 42 | -------------------------------------------------------------------------------- /test/webview-test.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./global-setup'); 2 | const path = require('path'); 3 | const { expect } = require('chai'); 4 | 5 | const describe = global.describe; 6 | const it = global.it; 7 | const beforeEach = global.beforeEach; 8 | const afterEach = global.afterEach; 9 | 10 | describe(' tags', function () { 11 | helpers.setupTimeout(this); 12 | 13 | let app = null; 14 | 15 | beforeEach(function () { 16 | return helpers 17 | .startApplication({ 18 | args: [path.join(__dirname, '..')] 19 | }) 20 | .then(function (startedApp) { 21 | app = startedApp; 22 | }); 23 | }); 24 | 25 | afterEach(function () { 26 | return helpers.stopApplication(app); 27 | }); 28 | 29 | it('allows the web view to be accessed', async function () { 30 | // waiting for windowHandles ensures waitUntilWindowLoaded doesn't access a nil webContents. 31 | // TODO: this issue should be fixed by waitUntilWindowLoaded instead of this workaround. 32 | await app.client.waitUntilWindowLoaded(); 33 | const elem = await app.client.$('body .editor-statusbar'); 34 | // const text = await elem.getText(); 35 | // expect(text).to.equal('web view'); 36 | app.webContents.getTitle().should.eventually.equal('Web View'); 37 | }); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /assets/css/themes/monokai-sublime.css: -------------------------------------------------------------------------------- 1 | .cm-s-django { 2 | font-size: 1em; 3 | line-height: 1.5em; 4 | font-family: inconsolata, monospace; 5 | letter-spacing: 0.3px; 6 | word-spacing: 1px; 7 | background: #0B2F20; 8 | color: #F8F8F8; 9 | } 10 | .cm-s-django .CodeMirror-lines { 11 | padding: 8px 0; 12 | } 13 | .cm-s-django .CodeMirror-gutters { 14 | box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.5); 15 | -webkit-box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.5); 16 | background-color: #0B2F20; 17 | padding-right: 10px; 18 | z-index: 3; 19 | border: none; 20 | } 21 | .cm-s-django div.CodeMirror-cursor { 22 | border-left: 3px solid #F8F8F8; 23 | } 24 | .cm-s-django .CodeMirror-activeline-background { 25 | background: #0000004A; 26 | } 27 | .cm-s-django .CodeMirror-selected { 28 | background: #245032; 29 | } 30 | .cm-s-django .cm-comment { 31 | font-style: italic; 32 | color: #245032; 33 | } 34 | .cm-s-django .cm-keyword { 35 | color: #96DD3B; 36 | } 37 | .cm-s-django .cm-string { 38 | color: #91BB9E; 39 | } 40 | .cm-s-django .cm-property { 41 | color: #FFB454; 42 | } 43 | .cm-s-django .cm-atom { 44 | color: #FFB454; 45 | } 46 | .cm-s-django .cm-number { 47 | color: #FFB454; 48 | } 49 | .cm-s-django .cm-operator { 50 | color: #96DD3B; 51 | } 52 | .cm-s-django .CodeMirror-linenumber { 53 | color: italic; 54 | } 55 | -------------------------------------------------------------------------------- /test/command-test.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./global-setup'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const describe = global.describe; 6 | const it = global.it; 7 | const beforeEach = global.beforeEach; 8 | const afterEach = global.afterEach; 9 | const expect = require('chai').expect; 10 | 11 | describe('ipc command test', function () { 12 | beforeEach(function () { 13 | return helpers 14 | .startApplication({ 15 | args: [path.join(__dirname, '..')], 16 | }) 17 | .then(function (startedApp) { 18 | app = startedApp; 19 | }); 20 | }); 21 | 22 | afterEach(function () { 23 | return helpers.stopApplication(app); 24 | }); 25 | 26 | describe('electron.ipcRenderer.send', function () { 27 | xit('sends the message to the main process', async function () { 28 | let ipcCount = await app.electron.remote.getGlobal('ipcEventCount'); 29 | expect(ipcCount).to.equal(0); 30 | await app.electron.ipcRenderer.send('ipc-event', 123); 31 | ipcCount = await app.electron.remote.getGlobal('ipcEventCount'); 32 | expect(ipcCount).to.equal(123); 33 | await app.electron.ipcRenderer.send('ipc-event', 456); 34 | ipcCount = await app.electron.remote.getGlobal('ipcEventCount'); 35 | expect(ipcCount).to.equal(579); 36 | }); 37 | }); 38 | }); 39 | 40 | 41 | -------------------------------------------------------------------------------- /components/header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phodit-header", 3 | "version": "0.0.1", 4 | "description": "Stencil Component Starter", 5 | "module": "dist/custom-elements/index.js", 6 | "main": "dist/index.cjs.js", 7 | "types": "dist/types/components.d.ts", 8 | "collection": "dist/collection/collection-manifest.json", 9 | "files": [ 10 | "dist/" 11 | ], 12 | "scripts": { 13 | "build": "stencil build && mkdir -p ../../dist/component/header && cp -a dist/ ../../dist/component/header", 14 | "start": "stencil build --dev --watch --serve", 15 | "test": "jest", 16 | "test.watch": "jest --watch" 17 | }, 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "@stencil/core": "^2.7.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/ionic-team/stencil-component-starter.git" 25 | }, 26 | "author": "Ionic Team", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/ionic-team/stencil" 30 | }, 31 | "homepage": "https://github.com/ionic-team/stencil", 32 | "jest": { 33 | "transform": { 34 | "^.+\\.(ts|tsx)$": "/node_modules/@stencil/core/testing/jest.preprocessor.js" 35 | }, 36 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", 37 | "moduleFileExtensions": [ 38 | "ts", 39 | "tsx", 40 | "js", 41 | "json", 42 | "jsx" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/global-setup.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const chai = require('chai'); 3 | const chaiAsPromised = require('chai-as-promised'); 4 | const chaiRoughly = require('chai-roughly'); 5 | const Application = require('spectron').Application; 6 | 7 | const path = require('path'); 8 | 9 | global.before(function () { 10 | chai.should(); 11 | chai.use(chaiAsPromised); 12 | chai.use(chaiRoughly); 13 | }); 14 | 15 | exports.getElectronPath = function () { 16 | let electronPath = path.join( 17 | __dirname, 18 | '..', 19 | 'node_modules', 20 | '.bin', 21 | 'electron' 22 | ); 23 | if (process.platform === 'win32') electronPath += '.cmd'; 24 | return electronPath; 25 | }; 26 | 27 | exports.setupTimeout = function (test) { 28 | if (process.env.CI) { 29 | test.timeout(30000); 30 | } else { 31 | test.timeout(10000); 32 | } 33 | }; 34 | 35 | exports.startApplication = function (options) { 36 | options.path = exports.getElectronPath(); 37 | if (process.env.CI) options.startTimeout = 30000; 38 | 39 | const app = new Application(options); 40 | return app.start().then(function () { 41 | assert.strictEqual(app.isRunning(), true); 42 | chaiAsPromised.transferPromiseness = app.transferPromiseness; 43 | return app; 44 | }); 45 | }; 46 | 47 | exports.stopApplication = async function (app) { 48 | if (!app || !app.isRunning()) return; 49 | 50 | await app.stop(); 51 | assert.strictEqual(app.isRunning(), false); 52 | }; 53 | -------------------------------------------------------------------------------- /components/header/src/components/phodit-header/phodit-header.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestWindow } from '@stencil/core/testing'; 2 | import { PhoditHeader } from './phodit-header'; 3 | 4 | describe('my-component', () => { 5 | it('should build', () => { 6 | expect(new PhoditHeader()).toBeTruthy(); 7 | }); 8 | 9 | describe('rendering', () => { 10 | let element: any; 11 | let testWindow: TestWindow; 12 | beforeEach(async () => { 13 | testWindow = new TestWindow(); 14 | element = await testWindow.load({ 15 | components: [PhoditHeader], 16 | html: '' 17 | }); 18 | }); 19 | 20 | it('should work without parameters', () => { 21 | expect(element.textContent.trim()).toEqual('Hello, World! I\'m'); 22 | }); 23 | 24 | it('should work with a first name', async () => { 25 | element.first = 'Peter'; 26 | await testWindow.flush(); 27 | expect(element.textContent.trim()).toEqual('Hello, World! I\'m Peter'); 28 | }); 29 | 30 | it('should work with a last name', async () => { 31 | element.last = 'Parker'; 32 | await testWindow.flush(); 33 | expect(element.textContent.trim()).toEqual('Hello, World! I\'m Parker'); 34 | }); 35 | 36 | it('should work with both a first and a last name', async () => { 37 | element.first = 'Peter'; 38 | element.last = 'Parker'; 39 | await testWindow.flush(); 40 | expect(element.textContent.trim()).toEqual('Hello, World! I\'m Peter Parker'); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/native/pages/silde.page.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import {pandoc} from "../features/pandoc"; 3 | import * as fs from "fs"; 4 | import {ipcMain} from "electron"; 5 | 6 | export function createSlidePage(BrowserWindow: any, arg: any) { 7 | let slideWindow: any = null; 8 | 9 | function createSlidePage(data: any) { 10 | if (slideWindow) { 11 | slideWindow.focus(); 12 | return; 13 | } 14 | 15 | slideWindow = new BrowserWindow({ 16 | height: 1440, 17 | width: 960, 18 | title: "Slide", 19 | fullscreen: true, 20 | }); 21 | 22 | slideWindow.on("closed", () => { 23 | slideWindow = null; 24 | }); 25 | 26 | 27 | slideWindow.loadFile(path.join(__dirname, "../../../views/slide.html")); 28 | 29 | slideWindow.once("ready-to-show", () => slideWindow.show()); 30 | slideWindow.webContents.on("did-finish-load", () => { 31 | 32 | }); 33 | 34 | slideWindow.openDevTools(); 35 | 36 | if (!slideWindow.webContents) { 37 | createSlidePage(data); 38 | } 39 | 40 | ipcMain.on("phodit.slide.ready", () => { 41 | if (slideWindow && slideWindow.webContents) { 42 | slideWindow.webContents.send("phodit.slide.send.content", data); 43 | } 44 | }); 45 | } 46 | 47 | pandoc.slide(arg.file).then((filePath: any) => { 48 | fs.readFile(filePath, "utf-8", (err, data) => { 49 | if (err) { 50 | console.log("An error ocurred reading the file :" + err.message); 51 | return; 52 | } 53 | 54 | createSlidePage(data); 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /docs/adr/README.md: -------------------------------------------------------------------------------- 1 | # 架构决策记录 2 | 3 | * [1. 系统进行-discover-设计](0001-系统进行-discover-设计.md) 4 | * [2. 中文-nlp-相关功能](0002-中文-nlp-相关功能.md) 5 | * [3. 支持批注](0003-支持批注.md) 6 | * [4. 统计打字速度及显示活跃时间](0004-统计打字速度及显示活跃时间.md) 7 | * [5. 段落折叠功能](0005-段落折叠功能.md) 8 | * [6. 新增复制图片下载到本地的功能](0006-新增复制图片下载到本地的功能.md) 9 | * [7. autocomplete->-冒号后自动换行并加-item-标识符](0007-autocomplete->-冒号后自动换行并加-item-标识符.md) 10 | * [8. 另存为功能](0008-另存为功能.md) 11 | * [9. 搜索引擎或者是数据库](0009-搜索引擎或者是数据库.md) 12 | * [10. 全文搜索文本支持](0010-全文搜索文本支持.md) 13 | * [11. 监测到文件变化自动更新目录](0011-监测到文件变化自动更新目录.md) 14 | * [12. 使用-pandoc-来转换-slide](0012-使用-pandoc-来转换-slide.md) 15 | * [13. 使用-toast-来实现简单的提醒功能](0013-使用-toast-来实现简单的提醒功能.md) 16 | * [14. 实现重命名文件等对话框](0014-实现重命名文件等对话框.md) 17 | * [15. 可配置的搜索菜单](0015-可配置的搜索菜单.md) 18 | * [16. 引入操作日志](0016-引入操作日志.md) 19 | * [17. 支持记住文件的行号](0017-支持记住文件的行号.md) 20 | * [18. 支持内容中的待办事项列表-todo](0018-支持内容中的待办事项列表-todo.md) 21 | * [19. 支持选择本地图片](0019-支持选择本地图片.md) 22 | * [20. submline-text-类似的管口滚动](0020-submline-text-类似的管口滚动.md) 23 | * [21. 自动发布系统-dsl](0021-自动发布系统-dsl.md) 24 | * [22. [tabs]-支持](0022-[tabs]-支持.md) 25 | * [23. [pdf]-支持编译文章成-pdf](0023-[pdf]-支持编译文章成-pdf.md) 26 | * [24. 添加不同的显示字体切换](0024-添加不同的显示字体切换.md) 27 | * [25. 添加复制、粘贴图片功能](0025-添加复制、粘贴图片功能.md) 28 | * [26. 添加粘贴图片到目录的支持](0026-添加粘贴图片到目录的支持.md) 29 | * [27. 粘贴内容到编辑器时转为-markdown-格式](0027-粘贴内容到编辑器时转为-markdown-格式.md) 30 | * [28. [bug]-修改重命名文件后的保存问题](0028-[bug]-修改重命名文件后的保存问题.md) 31 | * [29. [feat]-添加审校功能](0029-[feat]-添加审校功能.md) 32 | * [30. 进度条:根据行数和当前行数来显示百分比](0030-进度条:根据行数和当前行数来显示百分比.md) 33 | * [31. 屏保或定时休息提醒](0031-屏保或定时休息提醒.md) 34 | * [32. touchbar-支持](0032-touchbar-支持.md) 35 | * [33. multiple-instances](0033-multiple-instances.md) 36 | -------------------------------------------------------------------------------- /components/interact/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interact", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build --prod --output-hashing=none && yarn package", 8 | "package": "mkdir -p ../../dist/component/interact && cat dist/interact/{runtime,polyfills,main}-es2015.js > ../../dist/component/interact/interact.js", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "~11.0.1", 16 | "@angular/common": "~11.0.1", 17 | "@angular/compiler": "~11.0.1", 18 | "@angular/elements": "~11.0.1", 19 | "@angular/core": "~11.0.1", 20 | "@angular/forms": "~11.0.1", 21 | "@angular/platform-browser": "~11.0.1", 22 | "@angular/platform-browser-dynamic": "~11.0.1", 23 | "@angular/router": "~11.0.1", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.2" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1100.2", 30 | "@angular/cli": "~11.0.2", 31 | "@angular/compiler-cli": "~11.0.1", 32 | "@types/jasmine": "~3.6.0", 33 | "@types/node": "^12.11.1", 34 | "codelyzer": "^6.0.0", 35 | "jasmine-core": "~3.6.0", 36 | "jasmine-spec-reporter": "~5.0.0", 37 | "karma": "~5.1.0", 38 | "karma-chrome-launcher": "~3.1.0", 39 | "karma-coverage": "~2.0.3", 40 | "karma-jasmine": "~4.0.0", 41 | "karma-jasmine-html-reporter": "^1.5.0", 42 | "protractor": "~7.0.0", 43 | "ts-node": "~8.3.0", 44 | "tslint": "~6.1.0", 45 | "typescript": "~4.0.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /components/tree-view/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | text-align: left; 6 | } 7 | 8 | .f-no-select, .m-tree { 9 | -webkit-user-select: none; 10 | -khtml-user-select: none; 11 | -moz-user-select: none; 12 | -ms-user-select: none; 13 | user-select: none 14 | } 15 | 16 | .m-tree { 17 | position: relative; 18 | overflow: hidden 19 | } 20 | 21 | .m-draggable { 22 | position: absolute; 23 | opacity: .8; 24 | -webkit-user-select: none; 25 | -khtml-user-select: none; 26 | -moz-user-select: none; 27 | -ms-user-select: none; 28 | user-select: none 29 | } 30 | 31 | .m-node.placeholder > * { 32 | visibility: hidden 33 | } 34 | 35 | .m-node.placeholder { 36 | border: 1px dashed #ccc 37 | } 38 | 39 | .m-node .inner { 40 | position: relative; 41 | cursor: pointer; 42 | padding-left: 10px 43 | } 44 | 45 | .m-node .collapse { 46 | position: absolute; 47 | left: 0; 48 | cursor: pointer 49 | } 50 | 51 | .m-node .caret-right:before { 52 | content: "\25B8" 53 | } 54 | 55 | .m-node .caret-down:before { 56 | content: "\25BE" 57 | } 58 | 59 | .tree { 60 | position: fixed; 61 | top: 0; 62 | left: 0; 63 | bottom: 0; 64 | overflow-x: hidden; 65 | overflow-y: auto; 66 | background-color: #21252b 67 | } 68 | 69 | .m-node.placeholder { 70 | border: 1px dashed #1385e5 71 | } 72 | 73 | .m-node .inner { 74 | color: #9da5b4; 75 | font-size: 12px; 76 | } 77 | 78 | .m-node .node { 79 | display: inline-block; 80 | width: 100%; 81 | padding: 2px 3px; 82 | text-align: left; 83 | } 84 | 85 | .m-node .node.is-active { 86 | background-color: #31363f 87 | } 88 | -------------------------------------------------------------------------------- /assets/css/themes/monokai-bright.css: -------------------------------------------------------------------------------- 1 | .cm-s-monokai-bright { 2 | font-size: 1em; 3 | line-height: 1.5em; 4 | font-family: inconsolata, monospace; 5 | letter-spacing: 0.3px; 6 | word-spacing: 1px; 7 | background: #272822; 8 | color: #F8F8F2; 9 | } 10 | 11 | .cm-s-monokai-bright .CodeMirror-lines { 12 | padding: 8px 0; 13 | } 14 | 15 | .cm-s-monokai-bright .CodeMirror-gutters { 16 | box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.5); 17 | -webkit-box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.5); 18 | background-color: #272822; 19 | padding-right: 10px; 20 | z-index: 3; 21 | border: none; 22 | } 23 | 24 | .cm-s-monokai-bright div.CodeMirror-cursor { 25 | border-left: 3px solid #F8F8F2; 26 | } 27 | 28 | .cm-s-monokai-bright .CodeMirror-activeline-background { 29 | background: #3E3D32; 30 | } 31 | 32 | .cm-s-monokai-bright .CodeMirror-selected { 33 | background: #9D550F; 34 | } 35 | 36 | .cm-s-monokai-bright .cm-comment { 37 | color: #75715E; 38 | } 39 | 40 | .cm-s-monokai-bright .cm-string { 41 | color: #E6DB74; 42 | } 43 | 44 | .cm-s-monokai-bright .cm-number { 45 | color: #66D9EF; 46 | } 47 | 48 | .cm-s-monokai-bright .cm-atom { 49 | color: #66D9EF; 50 | } 51 | 52 | .cm-s-monokai-bright .cm-keyword { 53 | color: #F92672; 54 | } 55 | 56 | .cm-s-monokai-bright .cm-variable { 57 | color: #A6E22E; 58 | } 59 | 60 | .cm-s-monokai-bright .cm-def { 61 | font-style: italic; 62 | color: #FD971F; 63 | } 64 | 65 | .cm-s-monokai-bright .cm-variable-2 { 66 | color: #F92672; 67 | } 68 | 69 | .cm-s-monokai-bright .cm-property { 70 | color: #66D9EF; 71 | } 72 | 73 | .cm-s-monokai-bright .cm-operator { 74 | color: #F92672; 75 | } 76 | 77 | .cm-s-monokai-bright .CodeMirror-linenumber { 78 | color: #75715E; 79 | } 80 | -------------------------------------------------------------------------------- /test/basic-test.js: -------------------------------------------------------------------------------- 1 | // Test for examples included in README.md 2 | const helpers = require('./global-setup'); 3 | const path = require('path'); 4 | const {expect} = require('chai'); 5 | 6 | const describe = global.describe; 7 | const it = global.it; 8 | const beforeEach = global.beforeEach; 9 | const afterEach = global.afterEach; 10 | 11 | describe('example application launch', function () { 12 | helpers.setupTimeout(this); 13 | 14 | let app = null; 15 | 16 | beforeEach(function () { 17 | return helpers 18 | .startApplication({ 19 | args: [path.join(__dirname, '..')] 20 | }) 21 | .then(function (startedApp) { 22 | app = startedApp; 23 | }); 24 | }); 25 | 26 | afterEach(function () { 27 | return helpers.stopApplication(app); 28 | }); 29 | 30 | it('opens a window', async function () { 31 | await app.client.waitUntilWindowLoaded(); 32 | app.browserWindow.focus(); 33 | const windowCount = await app.client.getWindowCount(); 34 | expect(windowCount).to.equal(1); 35 | const isMinimized = await app.browserWindow.isMinimized(); 36 | expect(isMinimized).to.equal(false); 37 | const isDevOpen = await app.browserWindow.isDevToolsOpened(); 38 | expect(isDevOpen).to.equal(false); 39 | const isVisible = await app.browserWindow.isVisible(); 40 | expect(isVisible).to.equal(true); 41 | const isFocused = await app.browserWindow.isFocused(); 42 | expect(isFocused).to.equal(true); 43 | app.browserWindow 44 | .getBounds() 45 | .should.eventually.have.property('width') 46 | .and.be.above(0); 47 | app.browserWindow 48 | .getBounds() 49 | .should.eventually.have.property('height') 50 | .and.be.above(0); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/common/constants/event.constants.ts: -------------------------------------------------------------------------------- 1 | export const EventConstants = { 2 | TEMP_FILE_STATUS: "phodit.temp.file.status", 3 | PHODIT: { 4 | LOADED: "phodit.lifecycle.load", 5 | OPEN_PATH: "phodit.open.path", 6 | RELOAD_PATH: "phodit.reload.path", 7 | SAVE_FILE: "phodit.save.file", 8 | OPEN_ONE_FILE: "phodit.open.one-file", 9 | OPEN_FILE: "phodit.open.file", 10 | SUGGEST_SEND: "phodit.suggest.send", 11 | SUGGEST_TO_EDITOR: "phodit.editor.suggest.receive", 12 | GET_SUGGEST: "phodit.suggest.get", 13 | FULL_SCREEN: "phodit.fullscreen", 14 | UN_FULL_SCREEN: "phodit.unfullscreen", 15 | OPEN_GUIDE: "phodit.open.guide", 16 | SHOW_SLIDES: "phodit.show.echoesworks", 17 | SHOW_WORD: "phodit.show.word", 18 | SHOW_PDF: "phodit.show.pdf", 19 | GIT_STATUS: "phodit.git.status", 20 | OPEN_SYSTEM_PATH: "phodit.system.open.path", 21 | TOGGLE_THEME: "phodit.theme.toggle", 22 | SET_THEME: "phodit.theme.set", 23 | }, 24 | CLIENT: { 25 | SAVE_FILE: "client.save.file", 26 | GET_SUGGEST: "phodit.editor.suggest.get", 27 | FULL_SCREEN: "phodit.editor.fullscreen", 28 | UN_FULL_SCREEN: "phodit.editor.unfullscreen", 29 | SHOW_WORD: "phodit.editor.show.word", 30 | SHOW_PDF: "phodit.editor.show.pdf", 31 | SHOW_TERMINAL: "phodit.editor.git.commit", 32 | HIDDEN_TERMINAL: "phodit.editor.hidden.terminal", 33 | OPEN_GUIDE: "phodit.editor.open.guide", 34 | TREE_OPEN: "tree.pub.open", 35 | FILE_MENU_CLICK: "tree.right.click", 36 | SHOW_SLIDES: "phodit.editor.show.slides", 37 | SEND_MARKDOWN: "phodit.editor.send.result", 38 | GET_RENDERER_MARKDOWN: "phodit.editor.get.result", 39 | SHOW_SIDE: "phodit.editor.showside", 40 | HIDDEN_SIDE: "phodit.editor.hiddenside", 41 | TOGGLE_THEME: "phodit.editor.theme.toggle", 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /assets/css/themes/default.css: -------------------------------------------------------------------------------- 1 | 2 | .CodeMirror .cm-header { 3 | color: #44475a; 4 | } 5 | 6 | .CodeMirror .cm-quote { 7 | color: #090; 8 | } 9 | 10 | .cm-negative { 11 | color: #d44; 12 | } 13 | 14 | .cm-positive { 15 | color: #292; 16 | } 17 | 18 | .cm-header, .cm-strong { 19 | font-weight: bold; 20 | } 21 | 22 | .cm-em { 23 | font-style: italic; 24 | } 25 | 26 | .cm-link { 27 | text-decoration: underline; 28 | } 29 | 30 | .cm-strikethrough { 31 | text-decoration: line-through; 32 | } 33 | 34 | .CodeMirror .cm-keyword { 35 | color: #708; 36 | } 37 | 38 | .CodeMirror .cm-atom { 39 | color: #219; 40 | } 41 | 42 | .CodeMirror .cm-number { 43 | color: #164; 44 | } 45 | 46 | .CodeMirror .cm-def { 47 | color: #00f; 48 | } 49 | 50 | .CodeMirror .cm-variable, 51 | .CodeMirror .cm-punctuation, 52 | .CodeMirror .cm-property, 53 | .CodeMirror .cm-operator { 54 | } 55 | 56 | .CodeMirror .cm-variable-2 { 57 | color: #05a; 58 | } 59 | 60 | .CodeMirror .cm-variable-3, .CodeMirror .cm-type { 61 | color: #085; 62 | } 63 | 64 | .CodeMirror .cm-comment { 65 | color: #a50; 66 | } 67 | 68 | .CodeMirror .cm-string { 69 | color: #a11; 70 | } 71 | 72 | .CodeMirror .cm-string-2 { 73 | color: #f50; 74 | } 75 | 76 | .CodeMirror .cm-meta { 77 | color: #555; 78 | } 79 | 80 | .CodeMirror .cm-qualifier { 81 | color: #555; 82 | } 83 | 84 | .CodeMirror .cm-builtin { 85 | color: #30a; 86 | } 87 | 88 | .CodeMirror .cm-bracket { 89 | color: #997; 90 | } 91 | 92 | .CodeMirror .cm-tag { 93 | color: #170; 94 | } 95 | 96 | .CodeMirror .cm-attribute { 97 | color: #00c; 98 | } 99 | 100 | .CodeMirror .cm-hr { 101 | color: #999; 102 | } 103 | 104 | .CodeMirror .cm-link { 105 | color: #00c; 106 | } 107 | 108 | .CodeMirror .cm-error { 109 | color: #f00; 110 | } 111 | 112 | .cm-invalidchar { 113 | color: #f00; 114 | } 115 | -------------------------------------------------------------------------------- /views/slide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | README 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 27 | 28 | 29 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/render/support/markdown.utils.ts: -------------------------------------------------------------------------------- 1 | const marked = require("marked"); 2 | 3 | class MarkdownImprove { 4 | private file: string; 5 | private marked: any; 6 | 7 | // tslint:disable-next-line:no-shadowed-variable 8 | constructor(file: string, marked?: any) { 9 | this.file = file; 10 | this.marked = marked; 11 | } 12 | 13 | public fixedImagePath(text: any) { 14 | const imgRegex = /!\[[^\]]+\]\(([^)]+)\)/; 15 | const imgRegexGlobal = /!\[[^\]]+\]\(([^)]+)\)/gi; 16 | const that = this; 17 | const matches = text.match(imgRegexGlobal); 18 | if (matches && matches.length > 0) { 19 | matches.forEach((image: any) => { 20 | const originImage = image; 21 | if (imgRegex.test(image)) { 22 | const result = imgRegex.exec(image); 23 | const newFilePath = getFileRelativePath(that.file, result[1]); 24 | image = image.replace(/\(([^)]+)\)/, "(" + newFilePath + ")"); 25 | text = text.replace(originImage, image); 26 | } 27 | }); 28 | } 29 | return text; 30 | } 31 | } 32 | 33 | export function markdownRender(text: string, file: string) { 34 | // Options list 35 | // https://marked.js.org/#/USING_ADVANCED.md 36 | const renderer = new marked.Renderer(); 37 | renderer.listitem = (item: any) => { 38 | return '
  • ' + item + "

  • \n"; 39 | }; 40 | 41 | marked.setOptions({ 42 | renderer, 43 | highlight(code: string) { 44 | return require("highlight.js").highlightAuto(code).value; 45 | }, 46 | footnote: true, 47 | pedantic: false, 48 | gfm: true, 49 | tables: true, 50 | breaks: false, 51 | sanitize: false, 52 | smartLists: true, 53 | smartypants: false, 54 | xhtml: false, 55 | }); 56 | 57 | if (file) { 58 | const markdownImprove = new MarkdownImprove(file); 59 | text = markdownImprove.fixedImagePath(text); 60 | } 61 | return marked(text); 62 | } 63 | 64 | export function removeLastDirectoryPartOf(path: string) { 65 | if (!path) { 66 | return path; 67 | } 68 | 69 | const splitArray = path.split("/"); 70 | splitArray.pop(); 71 | return (splitArray.join("/")); 72 | } 73 | 74 | function getFileRelativePath(path: string, imgFilePath: string) { 75 | return `${removeLastDirectoryPartOf(path)}/${imgFilePath}`; 76 | } 77 | -------------------------------------------------------------------------------- /components/tree-view/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-view", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "classnames": { 8 | "version": "2.2.6", 9 | "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", 10 | "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" 11 | }, 12 | "js-tokens": { 13 | "version": "4.0.0", 14 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 15 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 16 | }, 17 | "js-tree": { 18 | "version": "2.0.1", 19 | "resolved": "https://registry.npmjs.org/js-tree/-/js-tree-2.0.1.tgz", 20 | "integrity": "sha512-otG8ZDEQP9PW+80nyXcrsJYB0Mj6vMy673u0/oYqyOZhIBrniOnR3pyL9D0FIDzpZGCaiGbspgkBHZeJinR1PQ==" 21 | }, 22 | "loose-envify": { 23 | "version": "1.4.0", 24 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 25 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 26 | "requires": { 27 | "js-tokens": "4.0.0" 28 | } 29 | }, 30 | "object-assign": { 31 | "version": "4.1.1", 32 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 33 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 34 | }, 35 | "prop-types": { 36 | "version": "15.6.2", 37 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", 38 | "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", 39 | "requires": { 40 | "loose-envify": "1.4.0", 41 | "object-assign": "4.1.1" 42 | } 43 | }, 44 | "react-ui-tree": { 45 | "version": "4.0.0", 46 | "resolved": "https://registry.npmjs.org/react-ui-tree/-/react-ui-tree-4.0.0.tgz", 47 | "integrity": "sha512-2y649h0psahtYb4NW5KK/zcwnzH0Di4a0PPzsorNyHzLGr5fXGB4MyubMcZ1BGD5nZFIATalvCeSJ/YDvIriVw==", 48 | "requires": { 49 | "classnames": "2.2.6", 50 | "js-tree": "2.0.1", 51 | "prop-types": "15.6.2" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phodit - 私人定制的 Markdown 编辑器 2 | 3 | [![Build Status](https://travis-ci.org/phodal/phodit.svg?branch=master)](https://travis-ci.org/phodal/phodit) ![GitHub package.json version](https://img.shields.io/github/package-json/v/phodal/phodit?style=plastic) [![Markdown Improve](https://img.shields.io/badge/markdown--improve-Phodal-blue.svg)](https://github.com/phodal/markdown-improve) 4 | 5 | > 一个基于 Electron 的私人定制的 Markdown 编辑器 6 | 7 |

    8 | 9 |

    10 | 11 | Screenshots 12 | 13 | ![Screenshots](./docs/phodit.jpg) 14 | 15 | Features 16 | --- 17 | 18 | - **标题折叠** 19 | - 支持所见所得模式 20 | - 编写时图片 21 | - 语法高亮 22 | - 集成 Terminal 23 | - markdown 实时预览 24 | - 工程管理 25 | - 支持使用 Git 来编写 markdown 项目 26 | - 支持全屏 27 | - 语法高亮 28 | - 编辑器主题切换 29 | - 导出支持(pdf、docx) 30 | - 右键选择搜索: Google, Baidu, WIKI, Zhihu, Github 31 | - 微信公众号排版支持 32 | - 支持 Slide 编写(by reveal.js) 33 | - `.md` 和 `.markdown` 文件关联打开 34 | 35 | Tech Stack 36 | --- 37 | 38 | Phodit 是一个使用**微前端架构**开发的 Electron 应用: 39 | 40 | - React.js -> TreeView 41 | - Angular -> Rename box 42 | - Stencil.js + Web Components -> Terminal Header 43 | 44 | 相应的技术栈有: 45 | 46 | - SimpleMDE + CodeMirror -> Editor 47 | - xterm -> Terminal 48 | - marked -> Markdown Parser 49 | - highlight.js -> Code Highlight 50 | - Reveal.js -> Slide 51 | 52 | ### Setup 53 | 54 | requirements: ``node.js`` 55 | 56 | Submodule 57 | 58 | ``` 59 | git submodule init 60 | git submodule update 61 | ``` 62 | 63 | ``` 64 | npm install --python=python2.7 65 | ``` 66 | 67 | ``` 68 | yarn install 69 | yarn build:app 70 | ``` 71 | 72 | #### Components 73 | 74 | Name | Path | Stacks 75 | -----------|------------------------|-------- 76 | editor | ./editor | SimpleMDE 77 | header | ./component/header | Stencil.js 78 | interact | ./component/interact | Angular 79 | tree-view | ./component/tree-view | React 80 | 81 | Setup && build 82 | 83 | ``` 84 | yarn install 85 | yarn build 86 | ``` 87 | 88 | ## License 89 | 90 | [![Phodal's Idea](https://brand.phodal.com/shields/idea-small.svg)](https://ideas.phodal.com/) 91 | 92 | © 2018~2021 A [Phodal Huang](https://www.phodal.com)'s [Idea](https://github.com/phodal/ideas). This code is distributed under the MIT license. See `LICENSE` in this directory. 93 | -------------------------------------------------------------------------------- /src/native/phodit-ipc.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, ipcMain, nativeTheme } from "electron"; 2 | import { EventConstants } from "../common/constants/event.constants"; 3 | import { IFileSave } from "../common/interface/IFileSave"; 4 | import { pandoc } from "./features/pandoc"; 5 | import { createSlidePage } from "./pages/silde.page"; 6 | import Phodit from "./phodit"; 7 | 8 | export default class PhoditIpc { 9 | private app: Phodit; 10 | 11 | constructor(app: Phodit) { 12 | this.app = app; 13 | } 14 | 15 | startListener() { 16 | ipcMain.on(EventConstants.PHODIT.OPEN_FILE, (event: any, arg: any) => { 17 | this.app.openFile(arg); 18 | }); 19 | 20 | ipcMain.on(EventConstants.PHODIT.SAVE_FILE, (event: any, arg: IFileSave) => { 21 | this.app.saveFile(arg.data, arg.isTempFile); 22 | }); 23 | 24 | ipcMain.on(EventConstants.PHODIT.OPEN_GUIDE, (event: any, arg: any) => { 25 | this.app.openAboutPage(); 26 | }); 27 | 28 | ipcMain.on(EventConstants.PHODIT.SHOW_SLIDES, (event: any, arg: any) => { 29 | createSlidePage(BrowserWindow, arg); 30 | }); 31 | 32 | ipcMain.on(EventConstants.PHODIT.FULL_SCREEN, (event: any, arg: any) => { 33 | this.app.mainWindow.setFullScreen(true); 34 | this.app.mainWindow.maximize(); 35 | }); 36 | 37 | ipcMain.on(EventConstants.PHODIT.UN_FULL_SCREEN, (event: any, arg: any) => { 38 | this.app.mainWindow.setFullScreen(false); 39 | this.app.mainWindow.unmaximize(); 40 | }); 41 | 42 | ipcMain.on(EventConstants.PHODIT.RELOAD_PATH, (event: any, arg: any) => { 43 | this.app.reloadPath(); 44 | }); 45 | 46 | ipcMain.on(EventConstants.PHODIT.SET_THEME, (event: any, arg: any) => { 47 | nativeTheme.themeSource = arg.mode; 48 | return nativeTheme.shouldUseDarkColors 49 | }); 50 | 51 | ipcMain.on(EventConstants.PHODIT.TOGGLE_THEME, (event: any, arg: any) => { 52 | if (nativeTheme.shouldUseDarkColors) { 53 | nativeTheme.themeSource = 'light' 54 | } else { 55 | nativeTheme.themeSource = 'dark' 56 | } 57 | 58 | return nativeTheme.shouldUseDarkColors 59 | }); 60 | 61 | ipcMain.on(EventConstants.PHODIT.SHOW_WORD, (event: any, arg: any) => { 62 | pandoc.word(arg); 63 | }); 64 | 65 | ipcMain.on(EventConstants.PHODIT.SHOW_PDF, (event: any, arg: any) => { 66 | pandoc.pdf(arg); 67 | }); 68 | 69 | ipcMain.on(EventConstants.PHODIT.OPEN_SYSTEM_PATH, (event: any, arg: any) => { 70 | require("electron").shell.showItemInFolder(arg); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Phodit 6 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
    33 | 34 |
    35 | 36 | 37 |
    38 | 39 |
    40 |
    41 |
    42 |
    43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | -------------------------------------------------------------------------------- /test/application-test.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./global-setup'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const temp = require('temp').track(); 5 | 6 | const describe = global.describe; 7 | const it = global.it; 8 | const beforeEach = global.beforeEach; 9 | const afterEach = global.afterEach; 10 | const expect = require('chai').expect; 11 | 12 | describe('application loading', function () { 13 | helpers.setupTimeout(this); 14 | let tempPath = null; 15 | let app = null; 16 | 17 | beforeEach(function () { 18 | tempPath = temp.mkdirSync('spectron-temp-dir-'); 19 | 20 | return helpers 21 | .startApplication({ 22 | args: [path.join(__dirname, '..')], 23 | }) 24 | .then(function (startedApp) { 25 | app = startedApp; 26 | }); 27 | }); 28 | 29 | afterEach(function () { 30 | return helpers.stopApplication(app); 31 | }); 32 | 33 | it('launches the application', async function () { 34 | app.browserWindow.getBounds().should.eventually.roughly(5).deep.equal({ 35 | x: 25, 36 | y: 35, 37 | width: 200, 38 | height: 100 39 | }); 40 | app.client.waitUntilTextExists('html', 'Hello'); 41 | app.client.getTitle().should.eventually.equal('Test'); 42 | }); 43 | 44 | describe('browserWindow.capturePage', function () { 45 | it('returns a Buffer screenshot of the given rectangle', async function () { 46 | const buffer = await app.browserWindow.capturePage({ 47 | x: 0, 48 | y: 0, 49 | width: 10, 50 | height: 10 51 | }) 52 | 53 | expect(buffer).to.be.an.instanceof(Buffer); 54 | expect(buffer.length).to.be.above(0); 55 | }); 56 | 57 | it('returns a Buffer screenshot of the entire page when no rectangle is specified', async function () { 58 | const buffer = await app.browserWindow.capturePage(); 59 | expect(buffer).to.be.an.instanceof(Buffer); 60 | expect(buffer.length).to.be.above(0); 61 | }); 62 | }); 63 | 64 | describe('webContents.savePage', function () { 65 | it('saves the page to the specified path', function () { 66 | const filePath = path.join(tempPath, 'page.html'); 67 | return app.webContents 68 | .savePage(filePath, 'HTMLComplete') 69 | .then(function () { 70 | const html = fs.readFileSync(filePath, 'utf8'); 71 | expect(html).to.contain('Phodit'); 72 | expect(html).to.contain('Phodit'); 73 | }); 74 | }); 75 | 76 | it('throws an error when the specified path is invalid', async function () { 77 | await expect( 78 | app.webContents.savePage(tempPath, 'MHTMLfds') 79 | ).to.be.rejectedWith(Error); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /views/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Markdown Guide 6 | 7 | 8 | 9 | 32 | 33 | 34 |
    35 |
    36 |
    37 |

    Markdown Guide

    38 |

    Emphasis

    39 |
    **bold**
    40 | *italics*
    41 | ~~strikethrough~~
    42 |

    Headers

    43 |
    # Big header
    44 | ## Medium header
    45 | ### Small header
    46 | #### Tiny header
    47 |

    Lists

    48 |
    * Generic list item
    49 | * Generic list item
    50 | * Generic list item
    51 | 
    52 | 1. Numbered list item
    53 | 2. Numbered list item
    54 | 3. Numbered list item
    55 |

    Links

    56 |
    [Text to display](http://www.example.com)
    57 |

    Quotes

    58 |
    > This is a quote.
    59 | > It can span multiple lines!
    60 |

    Images   61 | Need to upload an image? Imgur has a great interface. 62 | 63 |

    64 |
    ![](http://www.example.com/image.jpg)
    65 |

    Tables

    66 |
    | Column 1 | Column 2 | Column 3 |
    67 | | -------- | -------- | -------- |
    68 | | John     | Doe      | Male     |
    69 | | Mary     | Smith    | Female   |
    70 |       
    71 | 72 |

    Footnote

    73 |
    PHP[^1] Markdown Extra [^2] 是这样的。
    74 | 
    75 | [^1]: 脚注1
    76 | [^2]: 脚注2
    77 |       
    78 | 79 | Or without aligning the columns... 80 |
    | Column 1 | Column 2 | Column 3 |
    81 | | -------- | -------- | -------- |
    82 | | John | Doe | Male |
    83 | | Mary | Smith | Female |
    84 | 
    85 |

    Displaying code

    86 |
    `var example = "hello!";`
    87 | 88 | Or spanning multiple lines... 89 | 90 |
    ```
    91 | var example = "hello!";
    92 | alert(example);
    93 | ```
    94 |
    95 |
    96 |
    97 | 98 | 99 | -------------------------------------------------------------------------------- /src/native/ui/menu.ts: -------------------------------------------------------------------------------- 1 | export function buildMenu(app: any, funcs: any) { 2 | const template: any[] = [ 3 | { 4 | label: "File", 5 | submenu: [ 6 | { 7 | label: "Open...", 8 | accelerator: "CmdOrCtrl+O", 9 | click() { 10 | funcs.open(); 11 | }, 12 | }, 13 | { 14 | label: "New...", 15 | accelerator: "CmdOrCtrl+N", 16 | click() { 17 | funcs.newFile(); 18 | }, 19 | }, 20 | { 21 | label: "Save...", 22 | accelerator: "CmdOrCtrl+S", 23 | click() { 24 | funcs.saveFileSignal(); 25 | }, 26 | }, 27 | { 28 | label: "Debug...", 29 | accelerator: "CmdOrCtrl+Alt+I", 30 | click() { 31 | funcs.debug(); 32 | }, 33 | }, 34 | { 35 | label: "Reload...", 36 | accelerator: "CmdOrCtrl+R", 37 | click() { 38 | funcs.reload(); 39 | }, 40 | }, 41 | ], 42 | }, 43 | { 44 | label: "Edit", 45 | submenu: [ 46 | {role: "undo"}, 47 | {role: "redo"}, 48 | {type: "separator"}, 49 | {role: "cut"}, 50 | {role: "copy"}, 51 | {role: "paste"}, 52 | {role: "pasteandmatchstyle"}, 53 | {role: "delete"}, 54 | {role: "selectall"}, 55 | ], 56 | }, 57 | { 58 | label: "View", 59 | submenu: [ 60 | {role: "reload"}, 61 | {role: "forcereload"}, 62 | {role: "toggledevtools"}, 63 | {type: "separator"}, 64 | {role: "resetzoom"}, 65 | {role: "zoomin"}, 66 | {role: "zoomout"}, 67 | {type: "separator"}, 68 | {role: "togglefullscreen"}, 69 | ], 70 | }, 71 | { 72 | role: "window", 73 | submenu: [ 74 | {role: "minimize"}, 75 | {role: "close"}, 76 | ], 77 | }, 78 | { 79 | role: "help", 80 | submenu: [ 81 | { 82 | label: "More", 83 | click() { 84 | funcs.openAboutPage(); 85 | }, 86 | }, 87 | ], 88 | }, 89 | ]; 90 | 91 | if (process.platform === "darwin") { 92 | template.unshift({ 93 | label: app.getName(), 94 | submenu: [ 95 | {role: "about"}, 96 | {type: "separator"}, 97 | {role: "hide"}, 98 | {role: "hideothers"}, 99 | {role: "unhide"}, 100 | {type: "separator"}, 101 | {role: "quit"}, 102 | ], 103 | }); 104 | 105 | // Edit menu 106 | (template[1].submenu as any[]).push( 107 | {type: "separator"}, 108 | { 109 | label: "Speech", 110 | submenu: [ 111 | {role: "startspeaking"}, 112 | {role: "stopspeaking"}, 113 | ], 114 | }, 115 | ); 116 | 117 | // Window menu 118 | template[3].submenu = [ 119 | {role: "close"}, 120 | {role: "minimize"}, 121 | {role: "zoom"}, 122 | {type: "separator"}, 123 | {role: "front"}, 124 | ]; 125 | } 126 | 127 | return template; 128 | } 129 | -------------------------------------------------------------------------------- /components/tree-view/src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import cx from 'classnames'; 3 | import Tree from "react-ui-tree"; 4 | 5 | class App extends Component { 6 | state = { 7 | path: null, 8 | active: null, 9 | tree: {} 10 | }; 11 | 12 | diffTree(originTree, newTree) { 13 | if (originTree.hasOwnProperty('collapsed')) { 14 | if (newTree.module === originTree.module) { 15 | newTree.collapsed = originTree.collapsed; 16 | } 17 | 18 | if (originTree.children && newTree.children) { 19 | for (let originIndex in originTree.children) { 20 | for (let newIndex in newTree.children) { 21 | if (originTree.children[originIndex].module === newTree.children[newIndex].module) { 22 | newTree.children[newIndex].collapsed = originTree.children[originIndex].collapsed; 23 | } 24 | 25 | this.diffTree(originTree.children, newTree.children); 26 | } 27 | } 28 | } 29 | } 30 | 31 | return newTree; 32 | } 33 | 34 | componentDidMount() { 35 | let that = this; 36 | window.document.addEventListener('phodit.tree.open', function(data) { 37 | let parsedData = JSON.parse(data.detail); 38 | 39 | that.setState({ 40 | path: parsedData.path, 41 | tree: parsedData.tree 42 | }); 43 | 44 | that.getTreeData(); 45 | }); 46 | } 47 | 48 | renderNode = node => { 49 | return ( 50 | 53 | {node.module} 54 | 55 | ); 56 | }; 57 | 58 | onClickNode = node => { 59 | this.setState({ 60 | active: node 61 | }); 62 | 63 | if (!node.leaf) { 64 | return; 65 | } 66 | 67 | const event = new CustomEvent('tree.pub.open', { 68 | detail: JSON.stringify(node) 69 | }); 70 | window.document.dispatchEvent(event); 71 | }; 72 | 73 | onContextMenuClick = node => { 74 | const event = new CustomEvent('tree.right.click', { 75 | detail: JSON.stringify(node) 76 | }); 77 | window.document.dispatchEvent(event); 78 | }; 79 | 80 | handleChange = tree => { 81 | this.setState({ 82 | tree: tree 83 | }); 84 | this.storageTreeData() 85 | }; 86 | 87 | storageTreeData() { 88 | localStorage.setItem(this.state.path, JSON.stringify(this.state.tree)); 89 | } 90 | 91 | getTreeData() { 92 | const data = localStorage.getItem(this.state.path); 93 | if (!data) return; 94 | 95 | try { 96 | const parsedData = JSON.parse(data); 97 | let diffedTree = this.diffTree(parsedData, this.state.tree); 98 | 99 | this.setState({ 100 | tree: diffedTree 101 | }); 102 | } catch (e) { 103 | console.log(e); 104 | } 105 | } 106 | 107 | render() { 108 | return ( 109 |
    110 |
    111 | 118 |
    119 |
    120 | ); 121 | } 122 | } 123 | 124 | export default App; 125 | -------------------------------------------------------------------------------- /components/interact/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /components/interact/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | // import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phodit", 3 | "version": "1.1.0", 4 | "description": "Phodal's new Markdown Editor", 5 | "main": "dist/native/main.js", 6 | "scripts": { 7 | "build:native": "tsc && cp -a views dist", 8 | "build:packager": "electron-packager --ignore=components --ignore=editor/node_modules --ignore=nlp . Phodit --platform=darwin --icon=assets/imgs/icons/mac/icon.icns --overwrite", 9 | "build:app": "npm run build:native && electron-builder", 10 | "watch": "tsc -w", 11 | "lint": "tslint -c tslint.json -p tsconfig.json --fix", 12 | "start": "yarn build:native && electron ./dist/native/main.js", 13 | "postinstall": "yarn electron-rebuild -p -t \"dev,prod,optional\" && yarn electron-builder install-app-deps", 14 | "rebuild": "electron-rebuild -f -w node-pty", 15 | "test": "xvfb-maybe mocha" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/phodal/phodit.git" 20 | }, 21 | "author": "Phodal HUANG", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/phodal/phodit/issues" 25 | }, 26 | "homepage": "https://github.com/phodal/phodit#readme", 27 | "devDependencies": { 28 | "@types/clipboard": "^2.0.1", 29 | "@types/highlightjs": "^10.1.0", 30 | "@types/node": "12.12.6", 31 | "@types/yargs": "15.0.0", 32 | "assert": "^2.0.0", 33 | "chai": "^4.2.0", 34 | "chai-as-promised": "^7.1.1", 35 | "chai-roughly": "^1.0.0", 36 | "devtron": "^1.4.0", 37 | "electron": "^11.0.2", 38 | "electron-builder": "^22.9.1", 39 | "electron-packager": "^15.1.0", 40 | "electron-rebuild": "^2.3.5", 41 | "mocha": "^8.2.1", 42 | "node-gyp": "^7.1.2", 43 | "spectron": "^12.0.0", 44 | "temp": "^0.9.1", 45 | "tslint": "^6.1.2", 46 | "typescript": "^3.9.5", 47 | "xvfb-maybe": "^0.2.1", 48 | "yargs-parser": "^20.2.4" 49 | }, 50 | "build": { 51 | "appId": "com.phodal.phodit", 52 | "productName": "Phodit", 53 | "asar": "false", 54 | "copyright": "Copyright © 2018 ~ 2021 ${author} (https://ww.phodal.com)", 55 | "fileAssociations": [ 56 | { 57 | "ext": "md", 58 | "name": "Phodit", 59 | "role": "Editor" 60 | }, 61 | { 62 | "ext": "markdown", 63 | "name": "Phodit", 64 | "role": "Editor" 65 | } 66 | ], 67 | "files": [ 68 | "!components/**", 69 | "!editor/**", 70 | "!views/reveal.js/test**", 71 | "!.git/**", 72 | "!.editorconfig" 73 | ], 74 | "directories": { 75 | "output": "build" 76 | }, 77 | "mac": { 78 | "icon": "assets/imgs/icons/mac/icon.icns", 79 | "category": "public.app-category.developer-tools" 80 | }, 81 | "win": { 82 | "icon": "assets/imgs/icons/win/icon.ico" 83 | }, 84 | "nsis": { 85 | "createDesktopShortcut": "always" 86 | }, 87 | "dmg": { 88 | "contents": [ 89 | { 90 | "x": 110, 91 | "y": 150 92 | }, 93 | { 94 | "x": 240, 95 | "y": 150, 96 | "type": "link", 97 | "path": "/Applications" 98 | } 99 | ] 100 | }, 101 | "linux": { 102 | "icon": "assets/imgs/icons/png/1024x1024.png", 103 | "category": "Development" 104 | } 105 | }, 106 | "dependencies": { 107 | "chokidar": "^3.4.0", 108 | "electron-json-storage": "^4.1.8", 109 | "electron-store": "^6.0.1", 110 | "electron-window-state": "^5.0.3", 111 | "highlight.js": "latest", 112 | "marked": "^1.1.0", 113 | "node-pty": "0.10.1", 114 | "sweetalert": "^2.1.2", 115 | "tmp": "^0.2.1", 116 | "winston": "^3.3.1", 117 | "xterm": "4.13.0", 118 | "xterm-addon-fit": "^0.5.0", 119 | "xterm-addon-web-links": "^0.5.0-beta.6" 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /components/header/readme.md: -------------------------------------------------------------------------------- 1 | ![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square) 2 | 3 | # Stencil Component Starter 4 | 5 | This is a starter project for building a standalone Web Component using Stencil. 6 | 7 | Stencil is also great for building entire apps. For that, use the [stencil-app-starter](https://github.com/ionic-team/stencil-app-starter) instead. 8 | 9 | # Stencil 10 | 11 | Stencil is a compiler for building fast web apps using Web Components. 12 | 13 | Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool. Stencil takes TypeScript, JSX, a tiny virtual DOM layer, efficient one-way data binding, an asynchronous rendering pipeline (similar to React Fiber), and lazy-loading out of the box, and generates 100% standards-based Web Components that run in any browser supporting the Custom Elements v1 spec. 14 | 15 | Stencil components are just Web Components, so they work in any major framework or with no framework at all. 16 | 17 | ## Getting Started 18 | 19 | To start building a new web component using Stencil, clone this repo to a new directory: 20 | 21 | ```bash 22 | git clone https://github.com/ionic-team/stencil-component-starter.git my-component 23 | cd my-component 24 | git remote rm origin 25 | ``` 26 | 27 | and run: 28 | 29 | ```bash 30 | npm install 31 | npm start 32 | ``` 33 | 34 | To build the component for production, run: 35 | 36 | ```bash 37 | npm run build 38 | ``` 39 | 40 | To run the unit tests for the components, run: 41 | 42 | ```bash 43 | npm test 44 | ``` 45 | 46 | Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component). 47 | 48 | 49 | ## Naming Components 50 | 51 | When creating new component tags, we recommend _not_ using `stencil` in the component name (ex: ``). This is because the generated component has little to nothing to do with Stencil; it's just a web component! 52 | 53 | Instead, use a prefix that fits your company or any name for a group of related components. For example, all of the Ionic generated web components use the prefix `ion`. 54 | 55 | 56 | ## Using this component 57 | 58 | ### Script tag 59 | 60 | - [Publish to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages) 61 | - Put a script tag similar to this `` in the head of your index.html 62 | - Then you can use the element anywhere in your template, JSX, html etc 63 | 64 | ### Node Modules 65 | - Run `npm install my-component --save` 66 | - Put a script tag similar to this `` in the head of your index.html 67 | - Then you can use the element anywhere in your template, JSX, html etc 68 | 69 | ### In a stencil-starter app 70 | - Run `npm install my-component --save` 71 | - Add an import to the npm packages `import my-component;` 72 | - Then you can use the element anywhere in your template, JSX, html etc 73 | -------------------------------------------------------------------------------- /components/interact/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "interact": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/interact", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "fileReplacements": [ 33 | { 34 | "replace": "src/environments/environment.ts", 35 | "with": "src/environments/environment.prod.ts" 36 | } 37 | ], 38 | "optimization": true, 39 | "outputHashing": "all", 40 | "sourceMap": false, 41 | "extractCss": true, 42 | "namedChunks": false, 43 | "aot": true, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "builder": "@angular-devkit/build-angular:dev-server", 52 | "options": { 53 | "browserTarget": "interact:build" 54 | }, 55 | "configurations": { 56 | "production": { 57 | "browserTarget": "interact:build:production" 58 | } 59 | } 60 | }, 61 | "extract-i18n": { 62 | "builder": "@angular-devkit/build-angular:extract-i18n", 63 | "options": { 64 | "browserTarget": "interact:build" 65 | } 66 | }, 67 | "test": { 68 | "builder": "@angular-devkit/build-angular:karma", 69 | "options": { 70 | "main": "src/test.ts", 71 | "polyfills": "src/polyfills.ts", 72 | "tsConfig": "src/tsconfig.spec.json", 73 | "karmaConfig": "src/karma.conf.js", 74 | "styles": [ 75 | "src/styles.css" 76 | ], 77 | "scripts": [], 78 | "assets": [ 79 | "src/favicon.ico", 80 | "src/assets" 81 | ] 82 | } 83 | }, 84 | "lint": { 85 | "builder": "@angular-devkit/build-angular:tslint", 86 | "options": { 87 | "tsConfig": [ 88 | "src/tsconfig.app.json", 89 | "src/tsconfig.spec.json" 90 | ], 91 | "exclude": [ 92 | "**/node_modules/**" 93 | ] 94 | } 95 | } 96 | } 97 | }, 98 | "interact-e2e": { 99 | "root": "e2e/", 100 | "projectType": "application", 101 | "architect": { 102 | "e2e": { 103 | "builder": "@angular-devkit/build-angular:protractor", 104 | "options": { 105 | "protractorConfig": "e2e/protractor.conf.js", 106 | "devServerTarget": "interact:serve" 107 | }, 108 | "configurations": { 109 | "production": { 110 | "devServerTarget": "interact:serve:production" 111 | } 112 | } 113 | }, 114 | "lint": { 115 | "builder": "@angular-devkit/build-angular:tslint", 116 | "options": { 117 | "tsConfig": "e2e/tsconfig.e2e.json", 118 | "exclude": [ 119 | "**/node_modules/**" 120 | ] 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "defaultProject": "interact", 127 | "cli": { 128 | "analytics": false 129 | } 130 | } -------------------------------------------------------------------------------- /src/render/menu.right.ts: -------------------------------------------------------------------------------- 1 | const {ipcRenderer, remote} = require('electron'); 2 | const { Menu, MenuItem } = remote; 3 | import * as fs from "fs"; 4 | import {EventConstants} from "../common/constants/event.constants"; 5 | 6 | let node: any = null; 7 | 8 | const globalStore = { 9 | eventTarget: {}, 10 | }; 11 | 12 | function createEditorMenu(menu: Electron.Menu) { 13 | menu.append(new MenuItem({ 14 | label: "Google", click() { 15 | const text = (globalStore.eventTarget as any).innerText; 16 | require("electron").shell.openExternal(`https://www.google.com/search?q=${text}`); 17 | }, 18 | })); 19 | 20 | menu.append(new MenuItem({ 21 | label: "Baidu", click() { 22 | const text = (globalStore.eventTarget as any).innerText; 23 | require("electron").shell.openExternal(`https://www.baidu.com/s?wd=${text}`); 24 | }, 25 | })); 26 | 27 | menu.append(new MenuItem({ 28 | label: "GitHub", click() { 29 | const text = (globalStore.eventTarget as any).innerText; 30 | require("electron").shell.openExternal(`https://github.com/search?q=${text}&ref=opensearch`); 31 | }, 32 | })); 33 | 34 | menu.append(new MenuItem({ 35 | label: "Wiki", click() { 36 | const text = (globalStore.eventTarget as any).innerText; 37 | require("electron").shell.openExternal(`https://zh.wikipedia.org/wiki/Special:Search?search=${text}`); 38 | }, 39 | })); 40 | 41 | menu.append(new MenuItem({ 42 | label: "Phodal", click() { 43 | const text = (globalStore.eventTarget as any).innerText; 44 | require("electron").shell.openExternal(`http://www.phodal.com/search/?q=${text}`); 45 | }, 46 | })); 47 | } 48 | 49 | function createFileMenu(menu: Electron.Menu) { 50 | menu.append(new MenuItem({ 51 | label: "Rename", click() { 52 | const filePath = node.filename; 53 | if (!filePath) { 54 | return; 55 | } 56 | const bar = document.querySelector("interact-bar"); 57 | const folderPath = filePath.replace(/[^\/]*$/, ""); 58 | 59 | bar.setAttribute("filename", filePath.split("/").pop()); 60 | bar.setAttribute("style", "display: block;"); 61 | bar.addEventListener("action", (event: any) => { 62 | const newPath = folderPath + event.detail; 63 | console.log(`Rename file from: ${filePath} to ${newPath}`); 64 | fs.rename(filePath, newPath, (err) => { 65 | if (err) { 66 | console.log("ERROR: " + err); 67 | } 68 | }); 69 | bar.setAttribute("style", "display: none;"); 70 | }); 71 | 72 | node = null; 73 | }, 74 | })); 75 | 76 | menu.append(new MenuItem({ 77 | label: "New File", click() { 78 | const filePath = node.filename; 79 | if (!filePath) { 80 | return; 81 | } 82 | const bar = document.querySelector("interact-bar"); 83 | 84 | bar.setAttribute("filename", ""); 85 | bar.setAttribute("style", "display: block;"); 86 | bar.addEventListener("action", (event: any) => { 87 | const newPath = `${filePath}/${event.detail}`; 88 | console.log(`New file from: ${newPath}`); 89 | fs.writeFile(newPath, "", (err) => { 90 | if (err) { 91 | console.log("ERROR: " + err); 92 | } 93 | }); 94 | bar.setAttribute("style", "display: none;"); 95 | }); 96 | 97 | node = null; 98 | }, 99 | })); 100 | 101 | menu.append(new MenuItem({ 102 | label: "Open In Folder", click() { 103 | ipcRenderer.send("phodit.system.open.path", node.filename); 104 | }, 105 | })); 106 | 107 | if (node.hasOwnProperty("collapsed")) { 108 | return; 109 | } 110 | 111 | menu.append(new MenuItem({ 112 | label: "Delete", click() { 113 | console.log("Delete", node.filename); 114 | fs.unlink(node.filename, (err: any) => { 115 | if (err) { 116 | return console.log(err); 117 | } 118 | ipcRenderer.send(EventConstants.PHODIT.RELOAD_PATH); 119 | 120 | node = null; 121 | }); 122 | }, 123 | })); 124 | } 125 | 126 | // FileMenu Click 127 | window.document.addEventListener(EventConstants.CLIENT.FILE_MENU_CLICK, (data: any) => { 128 | const nodeInfo = JSON.parse(data.detail); 129 | node = nodeInfo; 130 | }); 131 | 132 | window.addEventListener("contextmenu", (event: any) => { 133 | event.preventDefault(); 134 | globalStore.eventTarget = event.target; 135 | const menu = new Menu(); 136 | 137 | if (event.target.className === "node" || event.target.className === "node is-active") { 138 | createFileMenu(menu); 139 | } else { 140 | createEditorMenu(menu); 141 | } 142 | 143 | menu.popup({window: remote.getCurrentWindow()}); 144 | }, false); 145 | -------------------------------------------------------------------------------- /components/header/.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to the Stencil Component Starter! :tada: 4 | 5 | 6 | ## Contributing Etiquette 7 | 8 | Please see our [Contributor Code of Conduct](https://github.com/ionic-team/stencil/blob/master/CODE_OF_CONDUCT.md) for information on our rules of conduct. 9 | 10 | 11 | ## Creating an Issue 12 | 13 | * If you have a question about using Stencil, please ask in the [Stencil Worldwide Slack](https://join.slack.com/t/stencil-worldwide/shared_invite/enQtMjQ2MzkyMTY0MTk0LTQ4ODgzYjFjNjdkNDY3YWVhMmNlMTljMWQxNTM3Yjg0ZTIyZTM1MmU2YWE5YzNjNzE1MmQ3ZTk2NjQ1YzM5ZDM group. 14 | 15 | * It is required that you clearly describe the steps necessary to reproduce the issue you are running into. Although we would love to help our users as much as possible, diagnosing issues without clear reproduction steps is extremely time-consuming and simply not sustainable. 16 | 17 | * The issue list of this repository is exclusively for bug reports and feature requests. Non-conforming issues will be closed immediately. 18 | 19 | * Issues with no clear steps to reproduce will not be triaged. If an issue is labeled with "needs reply" and receives no further replies from the author of the issue for more than 5 days, it will be closed. 20 | 21 | * If you think you have found a bug, or have a new feature idea, please start by making sure it hasn't already been [reported](https://github.com/ionic-team/stencil/issues?utf8=%E2%9C%93&q=is%3Aissue). You can search through existing issues to see if there is a similar one reported. Include closed issues as it may have been closed with a solution. 22 | 23 | * Next, [create a new issue](https://github.com/ionic-team/stencil-component-starter/issues/new) that thoroughly explains the problem. Please fill out the populated issue form before submitting the issue. 24 | 25 | 26 | ## Creating a Pull Request 27 | 28 | * We appreciate you taking the time to contribute! Before submitting a pull request, we ask that you please [create an issue](#creating-an-issue) that explains the bug or feature request and let us know that you plan on creating a pull request for it. If an issue already exists, please comment on that issue letting us know you would like to submit a pull request for it. This helps us to keep track of the pull request and make sure there isn't duplicated effort. 29 | 30 | * Looking for an issue to fix? Make sure to look through our issues with the [help wanted](https://github.com/ionic-team/stencil-component-starter/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label! 31 | 32 | ### Setup 33 | 34 | 1. Fork the repo. 35 | 2. Clone your fork. 36 | 3. Make a branch for your change. 37 | 4. Run `npm install` (make sure you have [node](https://nodejs.org/en/) and [npm](http://blog.npmjs.org/post/85484771375/how-to-install-npm) installed first) 38 | 39 | 40 | #### Updates 41 | 42 | 1. Unit test. Unit test. Unit test. Please take a look at how other unit tests are written, and you can't write too many tests. 43 | 2. If there is a `*.spec.ts` file located in the components folder, update it to include a test for your change, if needed. If this file doesn't exist, please notify us. 44 | 3. Run `npm run test` or `npm run test.watch` to make sure all tests are working, regardless if a test was added. 45 | 46 | 47 | ## Commit Message Format 48 | 49 | We have very precise rules over how our git commit messages should be formatted. This leads to readable messages that are easy to follow when looking through the project history. We also use the git commit messages to generate our changelog. (Ok you got us, it's basically Angular's commit message format). 50 | 51 | `type(scope): subject` 52 | 53 | #### Type 54 | Must be one of the following: 55 | 56 | * **feat**: A new feature 57 | * **fix**: A bug fix 58 | * **docs**: Documentation only changes 59 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 60 | * **refactor**: A code change that neither fixes a bug nor adds a feature 61 | * **perf**: A code change that improves performance 62 | * **test**: Adding missing tests 63 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation 64 | 65 | #### Scope 66 | The scope can be anything specifying place of the commit change. For example `renderer`, `compiler`, etc. 67 | 68 | #### Subject 69 | The subject contains succinct description of the change: 70 | 71 | * use the imperative, present tense: "change" not "changed" nor "changes" 72 | * do not capitalize first letter 73 | * do not place a period `.` at the end 74 | * entire length of the commit message must not go over 50 characters 75 | * describe what the commit does, not what issue it relates to or fixes 76 | * **be brief, yet descriptive** - we should have a good understanding of what the commit does by reading the subject 77 | 78 | 79 | #### Adding Documentation 80 | 81 | Please see the [stencil-site](https://github.com/ionic-team/stencil-site) repo to update documentation. 82 | 83 | 84 | ## License 85 | 86 | By contributing your code to the ionic-team/stencil GitHub Repository, you agree to license your contribution under the MIT license. 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.1.0](https://github.com/phodal/phodit/compare/v1.0.0...v1.1.0) (2021-07-19) 2 | 3 | 4 | 5 | # [1.0.0](https://github.com/phodal/phodit/compare/v0.3.0...v1.0.0) (2020-11-26) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * fix mode issues ([ea39f1d](https://github.com/phodal/phodit/commit/ea39f1d37abeeaf827a5b568e29161b707a85102)) 11 | 12 | 13 | ### Features 14 | 15 | * add image preview in articles ([f412be1](https://github.com/phodal/phodit/commit/f412be100adc1230157b14515c3aa4fdc96eff4c)) 16 | * make nvm versions ([bf15da4](https://github.com/phodal/phodit/commit/bf15da4c239d64136d7e09371976109e86abbec0)) 17 | 18 | 19 | 20 | # [0.3.0](https://github.com/phodal/phodit/compare/0.2.0...v0.3.0) (2020-11-23) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * align api change ([4cf3c73](https://github.com/phodal/phodit/commit/4cf3c73a2b821e6830ea8186bbe9b0e4aeee3a30)) 26 | * fix application issue ([c663dd2](https://github.com/phodal/phodit/commit/c663dd2704993c661564eb9ad3ac5d962137b80a)) 27 | * fix catch error issue ([1cf32ea](https://github.com/phodal/phodit/commit/1cf32eaedb0264873938d9be26c74b7927b37bf0)) 28 | * fix file associations error issues ([29e7b10](https://github.com/phodal/phodit/commit/29e7b1054a1fe198816d3574eb444376ca8ee240)) 29 | * fix icons ([ae570b3](https://github.com/phodal/phodit/commit/ae570b34730af7058c4eb643d072ed78f44f2fb7)) 30 | * fix last versions issues ([c16a1b3](https://github.com/phodal/phodit/commit/c16a1b39e13875ac889cfcd1d060f878167491e2)) 31 | * fix lint issues ([6deb045](https://github.com/phodal/phodit/commit/6deb045b79e481528e475b9acd7401dc314b3e61)) 32 | * fix lost install ([6b92c66](https://github.com/phodal/phodit/commit/6b92c66eebb250c103fc5e3200863dc25d115462)) 33 | * fix menu issues ([a92f800](https://github.com/phodal/phodit/commit/a92f800ec0f2482fc84b608e6284d39ccb4feac3)) 34 | * fix rename interact bar error issues ([6e2b46f](https://github.com/phodal/phodit/commit/6e2b46f5ec0e0265db983c21e553ce352cdf55b6)) 35 | * fix test scripts ([c73d386](https://github.com/phodal/phodit/commit/c73d3867aeee01674e490511f3d8949d7948f1f0)) 36 | * fix themes issues ([5028d5b](https://github.com/phodal/phodit/commit/5028d5b531000adfd7139be0caac5863345cee67)) 37 | * fix typoo ([9905eeb](https://github.com/phodal/phodit/commit/9905eeb05d12d51f146246ed2683e357fcbf040c)) 38 | * make it works ([b9bf33f](https://github.com/phodal/phodit/commit/b9bf33f404040b0c32652f2149bf168ec445701a)) 39 | * try to fix highlight issues ([394cf23](https://github.com/phodal/phodit/commit/394cf2306681995d138333e2a079ec6a66e7746b)) 40 | * upgrade moach version ([4687d90](https://github.com/phodal/phodit/commit/4687d90afd220768799437397829616166e6faf6)) 41 | 42 | 43 | ### Features 44 | 45 | * add html verify for content ([b3d3515](https://github.com/phodal/phodit/commit/b3d3515e7a8d2e4ad05db85240c622d506c48c11)) 46 | * add link search addon ([b5b70b1](https://github.com/phodal/phodit/commit/b5b70b1d8e5a3f0c78504dc319e5c7be7e819d95)) 47 | * add new file support ([4e7d60a](https://github.com/phodal/phodit/commit/4e7d60abd785ebdbd988b10faf00ccb714cfe70d)) 48 | * add touchbar toggle theme support ([2da2f13](https://github.com/phodal/phodit/commit/2da2f13fdc4bad6f5ed972389da4586e386af261)) 49 | * fix code mirror not focus issue ([415b60e](https://github.com/phodal/phodit/commit/415b60e52aa3f76d0ba9bb7c8b664e7d30738370)) 50 | * import spectron travis config ([b93ace7](https://github.com/phodal/phodit/commit/b93ace7c8c4b1ff7969d1d7005ae98f544b8953c)) 51 | * make highlight works ([9d0e91a](https://github.com/phodal/phodit/commit/9d0e91ac53072b799929bd3a440ba71489e3110b)) 52 | * make loading to show ([4b8088b](https://github.com/phodal/phodit/commit/4b8088b6cb02cc35ae3ef20065b178015df883de)) 53 | * make tray works ([e9dcb25](https://github.com/phodal/phodit/commit/e9dcb256accb9d719bf9e7b1967498a925f55c7e)) 54 | * try to add input test ([5eaaac2](https://github.com/phodal/phodit/commit/5eaaac2f731df9d805b2dc3dd973c2637cf9a249)) 55 | * try to add touchbar ([8c3d588](https://github.com/phodal/phodit/commit/8c3d58818ee9412cef20c54f889f2b26c9fe59d2)) 56 | * try to add tray ([1003a59](https://github.com/phodal/phodit/commit/1003a59f457467698444af144fea9a4c62e81f73)) 57 | * upgrade electron & node-pty version ([548828b](https://github.com/phodal/phodit/commit/548828b13a624d75007fe9d46ad6006ae9e37d00)) 58 | * upgrade termanil ([87e5693](https://github.com/phodal/phodit/commit/87e5693a79cc7b2815260f6a7a807e78f507e423)) 59 | * upgrade theme api ([f872d87](https://github.com/phodal/phodit/commit/f872d87444cd9b92ea8a679801a1728dafbe1157)) 60 | 61 | 62 | 63 | # [0.1.0](https://github.com/phodal/phodit/compare/0.0.4...0.1.0) (2019-03-11) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * add clipboard types ([fd37253](https://github.com/phodal/phodit/commit/fd37253268fc33b94ad3d5d19b9572c25ab10756)) 69 | * add lost deps ([3dfbb76](https://github.com/phodal/phodit/commit/3dfbb7601cd1047f788ba34be1bf5be18d9bf223)) 70 | * fix checkstyle ([fd8db56](https://github.com/phodal/phodit/commit/fd8db56ee5490186f6767317ac26b4a02a7062d9)) 71 | * fix cli typo ([430be69](https://github.com/phodal/phodit/commit/430be695d16145c90ad3e0fccdc940c55be34798)) 72 | * fix limit issue ([e5b5cea](https://github.com/phodal/phodit/commit/e5b5ceaca0c3306ad86c093dec8525ebba2379e8)) 73 | * fix type issue ([54ace91](https://github.com/phodal/phodit/commit/54ace91a1fc8a5bc78034e78b6eeecabbd0f2807)) 74 | 75 | 76 | ### Features 77 | 78 | * [dark mode] add basic backgroun ([6d0e6c5](https://github.com/phodal/phodit/commit/6d0e6c5806c0e434180d6cde4ae40725ab8e501e)) 79 | * [dark] add basic color ([6691fca](https://github.com/phodal/phodit/commit/6691fcacb2bed66837a006922fc32c856f2be554)) 80 | * [themes] get theme from system ([286c6a0](https://github.com/phodal/phodit/commit/286c6a0f427a119adf1f2eded05ae4074f0281be)) 81 | * add change themes icon ([fdb01f9](https://github.com/phodal/phodit/commit/fdb01f97f277c0963a56289df2122a425d32d7b5)) 82 | * add file assosciation support ([94fd295](https://github.com/phodal/phodit/commit/94fd295c307b32518f41e72574678d951b522f2b)) 83 | * add some build config ([a59b7b4](https://github.com/phodal/phodit/commit/a59b7b4663a04fc1c001603b193c41f039ecc6b0)) 84 | * add toggle themes ([529580a](https://github.com/phodal/phodit/commit/529580a33eed9bd6601ec6cd5503e6eafb580046)) 85 | * change to simplemde event ([93db32b](https://github.com/phodal/phodit/commit/93db32b70cdcb48836825742a5b19ffbc894631c)) 86 | * fix limit ([a4fd7ec](https://github.com/phodal/phodit/commit/a4fd7ec2c01e36d45909f6fc6b068017832c8f34)) 87 | * thinking in copy & paste ([11efcf4](https://github.com/phodal/phodit/commit/11efcf45c5dc7ef9dfab86bdb800bb891a8d5f5e)) 88 | * try to add some model ([3fa1ef7](https://github.com/phodal/phodit/commit/3fa1ef79c6a17f212512d5236ffc191b1a463ffc)) 89 | * update deps ([7fbd88a](https://github.com/phodal/phodit/commit/7fbd88acba3886c1f1945615b1a060a0fc71f5cd)) 90 | * update ss & toc ([8709cf1](https://github.com/phodal/phodit/commit/8709cf13d7b4d694aa7789aba9285b0dcf57a886)) 91 | 92 | 93 | 94 | ## [0.0.4](https://github.com/phodal/phodit/compare/v0.0.3...0.0.4) (2019-01-10) 95 | 96 | 97 | ### Reverts 98 | 99 | * Revert "[T] update deps" ([4825a9a](https://github.com/phodal/phodit/commit/4825a9a5bfe5a7dd77d8a64bef92c50b92f31d62)) 100 | * Revert "[T] resize for codE" ([32add77](https://github.com/phodal/phodit/commit/32add7764b8c536e529210e67d0c7061dc7c6821)) 101 | 102 | 103 | 104 | ## [0.0.3](https://github.com/phodal/phodit/compare/v0.0.2...v0.0.3) (2018-09-02) 105 | 106 | 107 | 108 | ## [0.0.2](https://github.com/phodal/phodit/compare/v0.0.0...v0.0.2) (2018-08-14) 109 | 110 | 111 | 112 | # 0.0.0 (2018-07-26) 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /assets/css/styles.css: -------------------------------------------------------------------------------- 1 | ::selection { 2 | background: transparent; 3 | } 4 | 5 | #input .CodeMirror-code pre.CodeMirror-line { 6 | font-size: 16px; 7 | font-weight: 500; 8 | 9 | font-family: "Source Code Variable", "Noto Serif SC", "PingFangTC-light", serif; 10 | } 11 | 12 | #input .cm-s-django .cm-header-1, 13 | #input .cm-s-django .cm-header-2, 14 | #input .cm-s-django .cm-header-3, 15 | #input .cm-s-easymde .cm-header-1, 16 | #input .cm-s-easymde .cm-header-2, 17 | #input .cm-s-easymde .cm-header-3 { 18 | font-family: "Noto Serif SC", serif; 19 | } 20 | 21 | #input .cm-s-easymde .cm-link { 22 | color: #1abc9c; 23 | } 24 | 25 | #input .CodeMirror .cm-variable-2 { 26 | color: #040507; 27 | font-weight: bold; 28 | font-family: "FZNewShuSong_GB18030-Z10", serif; 29 | } 30 | 31 | #input .CodeMirror .cm-variable-3 { 32 | color: #0a5438; 33 | font-size: 95%; 34 | } 35 | 36 | 37 | .CodeMirror div.CodeMirror-cursor { 38 | border-left: 2px solid #0b85f3; 39 | } 40 | 41 | .header { 42 | height: 22px; 43 | width: 100%; 44 | background: #fff; 45 | position: fixed; 46 | top: 0; 47 | left: 0; 48 | z-index: 1; 49 | } 50 | 51 | .far { 52 | height: 22px; 53 | width: 22px; 54 | } 55 | 56 | #phodit-editor { 57 | height: 100%; 58 | overflow-y: scroll; 59 | margin: 0 auto; 60 | position: relative; 61 | overflow-x: hidden; 62 | } 63 | 64 | #phodit-editor.row { 65 | width: 100%; 66 | height: 100vh; 67 | overflow: hidden; 68 | } 69 | 70 | .row { 71 | position: absolute; 72 | } 73 | 74 | .row .column { 75 | padding: 0; 76 | margin: 0; 77 | } 78 | 79 | textarea#input { 80 | border-radius: 0; 81 | padding: 1.5em; 82 | font-size: 14px; 83 | resize: horizontal; /* user can resize vertically, but width is fixed */ 84 | } 85 | 86 | #input { 87 | width: 100vw; 88 | height: calc(100vh - 22px); 89 | } 90 | 91 | #tree-view .tree, 92 | #tree-view { 93 | background: #384452; 94 | } 95 | 96 | #tree-view .m-node .inner { 97 | color: #d1d8df; 98 | } 99 | 100 | #input .editor-toolbar a, 101 | #input .editor-toolbar button { 102 | color: #384452 !important; 103 | } 104 | 105 | #input-section { 106 | height: calc(100vh - 22px); 107 | max-height: calc(100vh - 22px); 108 | overflow-y: scroll 109 | } 110 | 111 | .CodeMirror-dialog.CodeMirror-dialog-top { 112 | z-index: 12; 113 | position: absolute; 114 | bottom: 30px; 115 | width: 100vw; 116 | border-top: 1px solid #d1d8df; 117 | background: #fff; 118 | } 119 | 120 | #input .ace_search_form, 121 | #input .ace_replace_form { 122 | border: none; 123 | border-radius: 0; 124 | } 125 | 126 | #input .ace_search_form { 127 | padding-right: 2em; 128 | } 129 | 130 | #input .ace_search.right { 131 | bottom: 0; 132 | top: inherit; 133 | width: 100%; 134 | max-width: 100%; 135 | border-radius: 0; 136 | border: 1px solid #d1d8df; 137 | background-color: #fdfdfd; 138 | } 139 | 140 | #output { 141 | padding: 1em; 142 | height: 100%; 143 | max-height: 100%; 144 | overflow-y: scroll; 145 | } 146 | 147 | #input .editor-toolbar { 148 | position: fixed; 149 | opacity: 1; 150 | background: #fdfdfd; 151 | z-index: 2; 152 | width: 100%; 153 | border: none; 154 | border-radius: 0; 155 | border-bottom: 1px solid #d1d8df; 156 | padding: 0 5px; 157 | top: -1px; 158 | } 159 | 160 | #input .editor-toolbar.fullscreen { 161 | padding-top: 10px; 162 | } 163 | 164 | #input .CodeMirror.CodeMirror-fullscreen { 165 | top: 49px; 166 | } 167 | 168 | #input .editor-toolbar.fullscreen:after, 169 | #input .editor-toolbar:before, 170 | #input .editor-toolbar:after { 171 | margin: 0; 172 | } 173 | 174 | #input .CodeMirror { 175 | font-size: 14px; 176 | top: 29px; 177 | position: relative; 178 | height: calc(100% - 30px); 179 | border-radius: 0; 180 | padding: 5px; 181 | } 182 | 183 | #input .editor-statusbar { 184 | padding: 2px 10px; 185 | width: 100%; 186 | left: 0; 187 | bottom: 0; 188 | position: fixed; 189 | background: #fdfdfd; 190 | border-top: 1px solid #d1d8df; 191 | height: 22px; 192 | z-index: 9; 193 | } 194 | 195 | #input .editor-statusbar span.cursor { 196 | float: left; 197 | margin-left: 0; 198 | min-width: auto; 199 | } 200 | 201 | #tree-view .tree { 202 | width: 100%; 203 | padding-left: 10px; 204 | } 205 | 206 | #input .editor-preview-side { 207 | top: 33px; 208 | } 209 | 210 | #input.full-screen .editor-preview-side.editor-preview-active-side { 211 | top: 49px; 212 | border-left: 1px solid #d1d8df; 213 | } 214 | 215 | #input .editor-preview-side.editor-preview-active-side { 216 | top: 32px; 217 | border-left: none; 218 | } 219 | 220 | #input .editor-preview-side pre > code { 221 | font-size: 12px; 222 | padding: 0; 223 | } 224 | 225 | #terminal-section { 226 | position: absolute; 227 | width: 100%; 228 | right: 0; 229 | bottom: 21px; 230 | z-index: 9; 231 | } 232 | 233 | p { 234 | font-size: 14px; 235 | margin-bottom: 15px; 236 | margin-top: 15px; 237 | } 238 | 239 | h1, 240 | h2, 241 | h3, 242 | h4, 243 | h5, 244 | h6 { 245 | margin-top: 3rem; 246 | margin-bottom: 3rem; 247 | } 248 | 249 | h1 { 250 | font-size: 32px; 251 | } 252 | 253 | h2 { 254 | font-size: 28px; 255 | } 256 | 257 | h3 { 258 | font-size: 24px; 259 | } 260 | 261 | h4 { 262 | font-size: 20px; 263 | } 264 | 265 | h5 { 266 | font-size: 15px; 267 | } 268 | 269 | pre.prettyprint { 270 | border: none; 271 | border-left: 0.3rem solid #1abc9c !important; 272 | } 273 | 274 | figure { 275 | margin: 0; 276 | } 277 | 278 | blockquote, dl, figure, form, ol, p, pre, table, ul { 279 | margin-bottom: 16px; 280 | } 281 | 282 | li p { 283 | display: inline-block; 284 | margin: 0; 285 | } 286 | 287 | interact-bar { 288 | background: #d1d8df; 289 | z-index: 999; 290 | position: fixed; 291 | width: 100%; 292 | bottom: 22px; 293 | left: 0; 294 | } 295 | 296 | .CodeMirror-line > span > .cm-quote:first-child { 297 | border-left: 0.3rem solid #1abc9c; 298 | margin-left: 0; 299 | margin-right: 0; 300 | padding: 1rem 1.5rem; 301 | } 302 | 303 | /* Turn on custom 8px wide scrollbar */ 304 | ::-webkit-scrollbar { 305 | width: 8px; /* 1px wider than Lion. */ 306 | /* This is more usable for users trying to click it. */ 307 | background-color: rgba(0, 0, 0, 0); 308 | -webkit-border-radius: 100px; 309 | } 310 | 311 | /* hover effect for both scrollbar area, and scrollbar 'thumb' */ 312 | ::-webkit-scrollbar:hover { 313 | background-color: rgba(0, 0, 0, 0.09); 314 | } 315 | 316 | /* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */ 317 | ::-webkit-scrollbar-thumb:vertical { 318 | /* This is the EXACT color of Mac OS scrollbars. 319 | Yes, I pulled out digital color meter */ 320 | background: rgba(0, 0, 0, 0.5); 321 | -webkit-border-radius: 100px; 322 | } 323 | 324 | ::-webkit-scrollbar-thumb:vertical:active { 325 | background: rgba(0, 0, 0, 0.61); /* Some darker color when you click it */ 326 | -webkit-border-radius: 100px; 327 | } 328 | 329 | #input .CodeMirror { 330 | color: #384452; 331 | } 332 | 333 | .CodeMirror pre { 334 | font-size: 12px; 335 | } 336 | 337 | .editor-preview pre > code { 338 | white-space: pre-wrap; 339 | } 340 | 341 | .CodeMirror-foldmarker { 342 | color: blue; 343 | text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; 344 | font-family: arial; 345 | line-height: .3; 346 | cursor: pointer; 347 | } 348 | 349 | .CodeMirror-foldgutter { 350 | width: .7em; 351 | } 352 | 353 | .CodeMirror-foldgutter-open, 354 | .CodeMirror-foldgutter-folded { 355 | cursor: pointer; 356 | } 357 | 358 | .CodeMirror-foldgutter-open:after { 359 | content: "\25BE"; 360 | } 361 | 362 | .CodeMirror-foldgutter-folded:after { 363 | content: "\25B8"; 364 | } 365 | 366 | [data-theme^="light"], 367 | [data-theme] [data-theme^="light"] { 368 | --main-color: #000; 369 | --background-color: #fff; 370 | --box-color: lightgrey; 371 | } 372 | 373 | [data-theme^="dark"], 374 | [data-theme] [data-theme^="dark"], 375 | [data-theme="light-dark-sidebar"] nav { 376 | --blue-color: #1da1f2; 377 | --main-color: #fff; 378 | --background-color: #000; 379 | --sidebar-color: #333; 380 | --box-color: grey; 381 | } 382 | 383 | [data-theme^="dark"] .editor-toolbar i.separator { 384 | border-left: none; 385 | } 386 | 387 | .column .CodeMirror-widget { 388 | font-size: 24px; 389 | } 390 | 391 | .column .CodeMirror-foldmarker { 392 | color: #b086ff; 393 | text-shadow: 1px 0px 1px #CCCCCC, 0px 1px 1px #EEEEEE, 2px 1px 1px #CCCCCC, 1px 2px 1px #EEEEEE, 3px 2px 1px #CCCCCC, 2px 3px 1px #EEEEEE, 4px 3px 1px #CCCCCC, 3px 4px 1px #EEEEEE, 5px 4px 1px #CCCCCC, 4px 5px 1px #EEEEEE, 6px 5px 1px #CCCCCC, 5px 6px 1px #EEEEEE, 7px 6px 1px #CCCCCC; 394 | top: -2px; 395 | left: 4px; 396 | position: relative; 397 | } 398 | -------------------------------------------------------------------------------- /assets/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /src/native/phodit.ts: -------------------------------------------------------------------------------- 1 | import {app, BrowserWindow, dialog, Menu, nativeImage, OpenDialogReturnValue, shell, Tray} from "electron"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import {EventConstants} from "../common/constants/event.constants"; 5 | import {createHelpPage} from "./pages/help.page"; 6 | import {openHtmlPage} from "./pages/html.page"; 7 | import PhoditIpc from "./phodit-ipc"; 8 | import {dirTree} from "./support/file-support"; 9 | import {buildMenu} from "./ui/menu"; 10 | import {dockMenu} from "./ui/menu.dock"; 11 | import buildTouchBar from "./ui/touch-bar"; 12 | import windowStateKeeper = require("electron-window-state"); 13 | 14 | const tmp = require("tmp"); 15 | const chokidar = require("chokidar"); 16 | 17 | const storage = require("electron-json-storage"); 18 | 19 | export default class Phodit { 20 | mainWindow: Electron.BrowserWindow; 21 | currentFile: string; 22 | tray: Tray = null; 23 | dir: any; 24 | private storage: any; 25 | 26 | constructor() { 27 | this.storage = storage; 28 | } 29 | 30 | init() { 31 | app.dock.setMenu(Menu.buildFromTemplate(dockMenu)); 32 | app.allowRendererProcessReuse = false; 33 | 34 | app.on("ready", this.onAppReady.bind(this)); 35 | 36 | app.on("window-all-closed", () => { 37 | const isMacOS = process.platform === "darwin"; 38 | if (!isMacOS) { 39 | app.quit(); 40 | } 41 | }); 42 | 43 | app.on("activate", () => { 44 | if (this.mainWindow === null) { 45 | this.onAppReady(); 46 | } 47 | }); 48 | 49 | app.on("open-file", (event, arg) => { 50 | this.openFile(arg); 51 | }); 52 | 53 | // TODO: add protocol support 54 | app.setAsDefaultProtocolClient("md"); 55 | app.setAsDefaultProtocolClient("markdown"); 56 | 57 | const phoditIpc = new PhoditIpc(this); 58 | phoditIpc.startListener(); 59 | } 60 | 61 | onAppReady() { 62 | const that = this; 63 | 64 | const mainWindowState = windowStateKeeper({ 65 | defaultHeight: 800, 66 | defaultWidth: 1000, 67 | }); 68 | 69 | const image = nativeImage.createFromPath(__dirname + '../../assets/imgs/icons/mac/icon.icns'); 70 | image.setTemplateImage(true); 71 | 72 | this.mainWindow = new BrowserWindow({ 73 | x: mainWindowState.x, 74 | y: mainWindowState.y, 75 | width: mainWindowState.width, 76 | height: mainWindowState.height, 77 | icon: image, 78 | show: false, 79 | backgroundColor: "#fff", 80 | webPreferences: { 81 | nodeIntegration: true, 82 | nodeIntegrationInWorker: true, 83 | enableRemoteModule: true, 84 | // offscreen: true 85 | }, 86 | }); 87 | 88 | this.setTray(); 89 | 90 | console.info("main config dir:", storage.getDataPath()); 91 | 92 | mainWindowState.manage(this.mainWindow); 93 | this.mainWindow.loadFile(path.join(__dirname, "../../views/index.html")); 94 | this.mainWindow.setTouchBar(buildTouchBar(this.mainWindow)); 95 | 96 | this.mainWindow.on("closed", () => { 97 | this.mainWindow = null; 98 | }); 99 | 100 | this.mainWindow.setDocumentEdited(true); 101 | this.mainWindow.webContents.on("did-finish-load", () => { 102 | setTimeout(() => { 103 | this.initEditor(that); 104 | }, 500); 105 | }); 106 | this.mainWindow.webContents.on("will-navigate", (event: any, url) => { 107 | if (url !== this.mainWindow.webContents.getURL()) { 108 | event.preventDefault(); 109 | const win = new BrowserWindow({show: false}); 110 | win.loadURL(url); 111 | win.show(); 112 | event.newGuest = win; 113 | } 114 | }); 115 | 116 | const menu = Menu.buildFromTemplate(buildMenu(app, that)); 117 | Menu.setApplicationMenu(menu); 118 | } 119 | 120 | private initEditor(that: this) { 121 | storage.get("storage.last.file", (error: any, data: any) => { 122 | if (error) throw error; 123 | if (data && data.file) { 124 | that.openFile(data.file); 125 | } 126 | }); 127 | 128 | storage.get("storage.last.path", (error: any, data: any) => { 129 | if (error) throw error; 130 | if (data && data.file) { 131 | that.openPath(data.file); 132 | } 133 | }); 134 | 135 | this.mainWindow.webContents.setFrameRate(30); 136 | this.mainWindow.webContents.send(EventConstants.PHODIT.LOADED); 137 | 138 | this.mainWindow.show(); 139 | this.mainWindow.focus(); 140 | } 141 | 142 | private setTray() { 143 | const iconPath = path.join(__dirname, '../../assets/imgs/icons/png/16x16.png'); 144 | this.tray = new Tray(nativeImage.createFromPath(iconPath)); 145 | const contextMenu = Menu.buildFromTemplate([ 146 | {label: 'Item1', type: 'radio'}, 147 | {label: 'Exit', type: 'radio', role: "quit" }, 148 | ]); 149 | this.tray.setToolTip('Phodit'); 150 | this.tray.setContextMenu(contextMenu); 151 | } 152 | 153 | openFile(willLoadFile: string, isTempFile: boolean = false) { 154 | const imageRegex = /\.(jpe?g|png|gif|bmp|ico)$/i; 155 | const htmlRegex = /\.(html)$/i; 156 | const wordRegex = /\.(doc?x)$/i; 157 | 158 | if (imageRegex.test(willLoadFile)) { 159 | return this.mainWindow.previewFile(willLoadFile); 160 | } else if (htmlRegex.test(willLoadFile)) { 161 | return openHtmlPage(BrowserWindow, willLoadFile); 162 | } else if (wordRegex.test(willLoadFile)) { 163 | return shell.openPath(willLoadFile).then(() => { 164 | // do something 165 | }); 166 | } 167 | 168 | if (this.mainWindow && !isTempFile) { 169 | const fileName = path.basename(willLoadFile); 170 | this.mainWindow.setTitle(fileName); 171 | this.mainWindow.setRepresentedFilename(willLoadFile); 172 | } else { 173 | this.checkWindow(); 174 | storage.remove("storage.last.path"); 175 | this.mainWindow.setTitle("Untitled"); 176 | } 177 | 178 | storage.set("storage.last.file", {file: willLoadFile}); 179 | this.currentFile = willLoadFile; 180 | fs.readFile(willLoadFile, "utf-8", (err, data) => { 181 | if (err) { 182 | console.log("An error ocurred reading the file :" + err.message); 183 | return; 184 | } 185 | 186 | app.addRecentDocument(willLoadFile); 187 | 188 | this.mainWindow.webContents.send("phodit.open.one-file", { 189 | data, 190 | isTempFile, 191 | file: willLoadFile, 192 | }); 193 | }); 194 | } 195 | 196 | openPath(pathName: any, isWatch = false) { 197 | this.checkWindow(); 198 | 199 | storage.set("storage.last.path", {file: pathName}); 200 | // storage.remove("storage.last.file"); 201 | 202 | let dirFiles: any[] = []; 203 | if (!isWatch) { 204 | chokidar 205 | .watch(pathName, {ignored: /(^|[\/\\])\../}) 206 | .on("unlink", () => { 207 | this.reloadPath(true); 208 | }) 209 | .on("add", () => { 210 | this.reloadPath(true); 211 | }); 212 | } 213 | 214 | fs.readdir(pathName, (err, files) => { 215 | try { 216 | dirFiles = dirTree(pathName); 217 | } catch (e) { 218 | storage.remove("storage.last.path"); 219 | console.log(e); 220 | } 221 | 222 | this.mainWindow.webContents.send(EventConstants.PHODIT.OPEN_PATH, { 223 | path: pathName, 224 | tree: dirFiles, 225 | }); 226 | }); 227 | } 228 | 229 | open() { 230 | this.dir = dialog.showOpenDialog(this.mainWindow, { 231 | filters: [ 232 | {name: "Markdown ", extensions: ["markdown", "md", "txt"]}, 233 | {name: "All Files", extensions: ["*"]}, 234 | ], 235 | properties: ["openFile", "openDirectory", "multiSelections"], 236 | }).then((dialogValue: OpenDialogReturnValue) => { 237 | const fileNames = dialogValue.filePaths; 238 | if (!fileNames) { 239 | return; 240 | } 241 | 242 | if (fileNames.length === 1 && fs.lstatSync(fileNames[0]).isFile()) { 243 | this.openFile(fileNames[0]); 244 | } 245 | 246 | if (fileNames.length === 1 && fs.lstatSync(fileNames[0]).isDirectory()) { 247 | this.openPath(fileNames[0]); 248 | } 249 | }); 250 | } 251 | 252 | saveFileSignal() { 253 | if (this.mainWindow.webContents) { 254 | this.mainWindow.webContents.send(EventConstants.CLIENT.SAVE_FILE); 255 | } else { 256 | dialog.showErrorBox("error", "not open file"); 257 | } 258 | } 259 | 260 | saveFile(data: any, isTempFile: boolean) { 261 | if (!this.currentFile) { 262 | return; 263 | } 264 | if (!isTempFile && !this.currentFile.endsWith(".tmp")) { 265 | fs.writeFileSync(this.currentFile, data); 266 | } else { 267 | dialog.showSaveDialog(this.mainWindow, {}).then((filename: any) => { 268 | isTempFile = false; 269 | 270 | this.mainWindow.webContents.send(EventConstants.TEMP_FILE_STATUS, { 271 | isTempFile: false, 272 | }); 273 | this.currentFile = filename; 274 | this.mainWindow.setTitle(filename); 275 | fs.writeFileSync(filename, data); 276 | }); 277 | } 278 | } 279 | 280 | newFile() { 281 | tmp.file((err: any, lastPath: any, fd: any, cleanupCallback: any) => { 282 | if (err) { 283 | throw err; 284 | } 285 | 286 | this.checkWindow(); 287 | 288 | storage.set("storage.last.file", {file: lastPath}); 289 | this.openFile(lastPath, true); 290 | }); 291 | } 292 | 293 | debug() { 294 | this.mainWindow.webContents.openDevTools(); 295 | } 296 | 297 | reload() { 298 | this.mainWindow.webContents.reload(); 299 | } 300 | 301 | openAboutPage() { 302 | createHelpPage(BrowserWindow); 303 | } 304 | 305 | reloadPath(isWatch = false) { 306 | storage.get("storage.last.path", (error: any, data: any) => { 307 | if (error) { 308 | throw error; 309 | } 310 | 311 | if (data && data.file) { 312 | this.openPath(data.file, isWatch); 313 | } 314 | }); 315 | } 316 | 317 | checkWindow() { 318 | if (!this.mainWindow) { 319 | return this.onAppReady(); 320 | } 321 | if (this.mainWindow && !this.mainWindow.webContents) { 322 | return this.onAppReady(); 323 | } 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /assets/libs/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.0 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])}); -------------------------------------------------------------------------------- /assets/simplemde/simplemde.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * simplemde v1.11.2 3 | * Copyright Next Step Webs, Inc. 4 | * @link https://github.com/NextStepWebs/simplemde-markdown-editor 5 | * @license MIT 6 | */ 7 | .CodeMirror{color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:none;font-variant-ligatures:none}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected,.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}.CodeMirror{height:auto;min-height:300px;border:1px solid #ddd;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px;font:inherit;z-index:1}.CodeMirror-scroll{min-height:300px}.CodeMirror-fullscreen{background:#fff;position:fixed!important;top:50px;left:0;right:0;bottom:0;height:auto;z-index:9}.CodeMirror-sided{width:50%!important}.editor-toolbar{position:relative;opacity:.6;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;padding:0 10px;border-top:1px solid #bbb;border-left:1px solid #bbb;border-right:1px solid #bbb;border-top-left-radius:4px;border-top-right-radius:4px}.editor-toolbar:after,.editor-toolbar:before{display:block;content:' ';height:1px}.editor-toolbar:before{margin-bottom:8px}.editor-toolbar:after{margin-top:8px}.editor-toolbar:hover,.editor-wrapper input.title:focus,.editor-wrapper input.title:hover{opacity:.8}.editor-toolbar.fullscreen{width:100%;height:50px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;padding-top:10px;padding-bottom:10px;box-sizing:border-box;background:#fff;border:0;position:fixed;top:0;left:0;opacity:1;z-index:9}.editor-toolbar.fullscreen::before{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,1)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);position:fixed;top:0;left:0;margin:0;padding:0}.editor-toolbar.fullscreen::after{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(100%,rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);position:fixed;top:0;right:0;margin:0;padding:0}.editor-toolbar a{display:inline-block;text-align:center;text-decoration:none!important;color:#2c3e50!important;width:30px;height:30px;margin:0;border:1px solid transparent;border-radius:3px;cursor:pointer}.editor-toolbar a.active,.editor-toolbar a:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar a:before{line-height:30px}.editor-toolbar i.separator{display:inline-block;width:0;border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;text-indent:-10px;margin:0 6px}.editor-toolbar a.fa-header-x:after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.editor-toolbar a.fa-header-1:after{content:"1"}.editor-toolbar a.fa-header-2:after{content:"2"}.editor-toolbar a.fa-header-3:after{content:"3"}.editor-toolbar a.fa-header-bigger:after{content:"▲"}.editor-toolbar a.fa-header-smaller:after{content:"▼"}.editor-toolbar.disabled-for-preview a:not(.no-disable){pointer-events:none;background:#fff;border-color:transparent;text-shadow:inherit}@media only screen and (max-width:700px){.editor-toolbar a.no-mobile{display:none}}.editor-statusbar{padding:8px 10px;font-size:12px;color:#959694;text-align:right}.editor-statusbar span{display:inline-block;min-width:4em;margin-left:1em}.editor-preview,.editor-preview-side{padding:10px;background:#fafafa;overflow:auto;display:none;box-sizing:border-box}.editor-statusbar .lines:before{content:'lines: '}.editor-statusbar .words:before{content:'words: '}.editor-statusbar .characters:before{content:'characters: '}.editor-preview{position:absolute;width:100%;height:100%;top:0;left:0;z-index:7}.editor-preview-side{position:fixed;bottom:0;width:50%;top:50px;right:0;z-index:9;border:1px solid #ddd}.editor-preview-active,.editor-preview-active-side{display:block}.editor-preview-side>p,.editor-preview>p{margin-top:0}.editor-preview pre,.editor-preview-side pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th,.editor-preview-side table td,.editor-preview-side table th{border:1px solid #ddd;padding:5px}.CodeMirror .CodeMirror-code .cm-tag{color:#63a35c}.CodeMirror .CodeMirror-code .cm-attribute{color:#795da3}.CodeMirror .CodeMirror-code .cm-string{color:#183691}.CodeMirror .CodeMirror-selected{background:#d9d9d9}.CodeMirror .CodeMirror-code .cm-header-1{font-size:200%;line-height:200%}.CodeMirror .CodeMirror-code .cm-header-2{font-size:160%;line-height:160%}.CodeMirror .CodeMirror-code .cm-header-3{font-size:125%;line-height:125%}.CodeMirror .CodeMirror-code .cm-header-4{font-size:110%;line-height:110%}.CodeMirror .CodeMirror-code .cm-comment{background:rgba(0,0,0,.05);border-radius:2px}.CodeMirror .CodeMirror-code .cm-link{color:#7f8c8d}.CodeMirror .CodeMirror-code .cm-url{color:#aab2b3}.CodeMirror .CodeMirror-code .cm-strikethrough{text-decoration:line-through}.CodeMirror .CodeMirror-placeholder{opacity:.5}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:rgba(255,0,0,.15)} -------------------------------------------------------------------------------- /src/render/renderer.ts: -------------------------------------------------------------------------------- 1 | import { EventConstants } from "../common/constants/event.constants"; 2 | import { IFileOpen } from "../common/interface/IFileOpen"; 3 | import "./key.event"; 4 | import "./menu.right"; 5 | import { createTerminal } from "./plugins/terminal"; 6 | import { createEvent } from "./support/event.util"; 7 | import { getCodeMirrorMode } from "./support/file.utils"; 8 | import { markdownRender, removeLastDirectoryPartOf } from "./support/markdown.utils"; 9 | 10 | const {nativeTheme, ipcRenderer} = require("electron"); 11 | 12 | const swal = require("sweetalert"); 13 | 14 | declare global { 15 | // tslint:disable-next-line 16 | interface Window { 17 | easymde: any; 18 | } 19 | } 20 | 21 | class ClientUI { 22 | public state = { 23 | isShowTerminal: false, 24 | hasCreateTerminal: false, 25 | currentFile: "", 26 | isCurrentFileTemp: false, 27 | isOneFile: false, 28 | isPath: false, 29 | }; 30 | 31 | public easymde = new (window as any).EasyMDE({ 32 | spellChecker: false, 33 | autosave: { 34 | enabled: true, 35 | uniqueId: "phodit", 36 | delay: 1000, 37 | }, 38 | promptTexts: { 39 | link: "link", 40 | image: "image", 41 | }, 42 | minHeight: "500px", 43 | maxHeight: "100%", 44 | autoDownloadFontAwesome: false, 45 | syncSideBySidePreviewScroll: false, 46 | renderingConfig: { 47 | singleLineBreaks: false, 48 | codeSyntaxHighlighting: true 49 | }, 50 | element: document.getElementById("input-section"), 51 | }); 52 | 53 | public init() { 54 | (window as any).easymde = this.easymde; 55 | // @ts-ignore 56 | const clipboard = new ClipboardJS(".wechat-button"); 57 | this.easymde.codemirror.focus() 58 | 59 | this.easymde.codemirror.on('optionChange', () => { 60 | this.renderElements(); 61 | }) 62 | this.easymde.codemirror.on('cursorActivity', () => { 63 | this.renderElements(); 64 | }) 65 | this.easymde.codemirror.on('viewportChange', () => { 66 | this.renderElements(); 67 | }) 68 | 69 | clipboard.on("success", (event: any) => { 70 | swal({ 71 | title: "Copy Success", icon: "success", dangerMode: true, 72 | buttons: { 73 | confirm: {text: "OK"}, 74 | }, 75 | }); 76 | event.clearSelection(); 77 | }); 78 | } 79 | 80 | private renderElements() { 81 | this.easymde.codemirror.execCommand('markdownRenderImages'); 82 | } 83 | 84 | public updatePos(currentFile: string) { 85 | const lastPos = localStorage.getItem("line_" + currentFile); 86 | if (lastPos) { 87 | const parsedPos = JSON.parse(lastPos); 88 | this.easymde.codemirror.setCursor(parsedPos.line, parsedPos.ch); 89 | } 90 | } 91 | 92 | public bindEvent() { 93 | const that = this; 94 | // 打开帮助 95 | window.document.addEventListener(EventConstants.CLIENT.OPEN_GUIDE, (data) => { 96 | ipcRenderer.send(EventConstants.PHODIT.OPEN_GUIDE, this.easymde.value()); 97 | }); 98 | 99 | // 全屏 100 | window.document.addEventListener(EventConstants.CLIENT.FULL_SCREEN, (data) => { 101 | document.getElementById("input").classList.add("full-screen"); 102 | ipcRenderer.send(EventConstants.PHODIT.FULL_SCREEN); 103 | }); 104 | 105 | // 取消全屏 106 | window.document.addEventListener(EventConstants.CLIENT.UN_FULL_SCREEN, (data) => { 107 | document.getElementById("input").classList.remove("full-screen"); 108 | ipcRenderer.send(EventConstants.PHODIT.UN_FULL_SCREEN); 109 | }); 110 | 111 | // Terminal show 112 | window.document.addEventListener(EventConstants.CLIENT.SHOW_TERMINAL, () => { 113 | if (!this.state.hasCreateTerminal) { 114 | createTerminal(removeLastDirectoryPartOf(this.state.currentFile)); 115 | this.state.hasCreateTerminal = true; 116 | } 117 | 118 | this.state.isShowTerminal = !this.state.isShowTerminal; 119 | if (this.state.isShowTerminal) { 120 | document.getElementById("terminal-section").setAttribute("style", "display: block;"); 121 | } else { 122 | document.getElementById("terminal-section").setAttribute("style", "display: none;"); 123 | } 124 | }); 125 | 126 | // Terminal hidden 127 | window.document.addEventListener(EventConstants.CLIENT.HIDDEN_TERMINAL, () => { 128 | document.getElementById("terminal-section").setAttribute("style", "display: none;"); 129 | }); 130 | 131 | // Toggle Themes 132 | window.document.addEventListener(EventConstants.CLIENT.TOGGLE_THEME, () => { 133 | ipcRenderer.send(EventConstants.PHODIT.TOGGLE_THEME); 134 | this.toggleTheme(that.easymde.codemirror); 135 | }); 136 | 137 | // ShowSlides 138 | window.document.addEventListener(EventConstants.CLIENT.SHOW_SLIDES, () => { 139 | if (!this.state.currentFile) { 140 | return; 141 | } 142 | ipcRenderer.send(EventConstants.PHODIT.SHOW_SLIDES, { 143 | isTempFile: this.state.isCurrentFileTemp, 144 | file: this.state.currentFile, 145 | data: this.easymde.value(), 146 | }); 147 | }); 148 | 149 | // 隐藏 SIDE 150 | window.document.addEventListener(EventConstants.CLIENT.HIDDEN_SIDE, () => { 151 | document.getElementById("tree-view").setAttribute("style", "display: none;"); 152 | document.querySelector(".wechat-button").setAttribute("data-clipboard-target", ".editor-preview-side"); 153 | }); 154 | 155 | // 展示 SIDE 156 | window.document.addEventListener(EventConstants.CLIENT.SHOW_SIDE, () => { 157 | document.querySelector(".wechat-button").removeAttribute("data-clipboard-target"); 158 | if (this.state.isPath) { 159 | document.getElementById("tree-view").setAttribute("style", "display: block;"); 160 | } 161 | }); 162 | 163 | // 发起获取自动完成请求 164 | window.document.addEventListener(EventConstants.CLIENT.GET_SUGGEST, (data: any) => { 165 | ipcRenderer.send(EventConstants.PHODIT.GET_SUGGEST, data.detail); 166 | }); 167 | 168 | // 打开左侧树型文件 169 | window.document.addEventListener(EventConstants.CLIENT.TREE_OPEN, (event: any) => { 170 | const file = JSON.parse(event.detail).filename; 171 | this.state.currentFile = file; 172 | this.state.isOneFile = true; 173 | 174 | ipcRenderer.send(EventConstants.PHODIT.SAVE_FILE, { 175 | isTempFile: this.state.isCurrentFileTemp, 176 | data: this.easymde.value(), 177 | }); 178 | 179 | ipcRenderer.send(EventConstants.PHODIT.OPEN_FILE, file); 180 | }); 181 | 182 | // 返回 Markdown 渲染结果 183 | window.document.addEventListener(EventConstants.CLIENT.SEND_MARKDOWN, (event: any) => { 184 | const data = markdownRender(event.detail, this.state.currentFile); 185 | createEvent(EventConstants.CLIENT.GET_RENDERER_MARKDOWN, data); 186 | }); 187 | 188 | // Pandoc 转换 189 | window.document.addEventListener(EventConstants.CLIENT.SHOW_WORD, (event: any) => { 190 | swal({ 191 | title: "Open File", text: "Are you want to Open File", icon: "info", dangerMode: true, 192 | buttons: { 193 | cancel: {text: "Cancel", visible: true}, 194 | confirm: {text: "OK"}, 195 | }, 196 | }).then((willDelete: any) => { 197 | if (willDelete) { 198 | ipcRenderer.send(EventConstants.PHODIT.SHOW_WORD, this.state.currentFile); 199 | } 200 | }); 201 | }); 202 | 203 | // Pandoc 转换 204 | window.document.addEventListener(EventConstants.CLIENT.SHOW_PDF, (event: any) => { 205 | swal({ 206 | title: "Open File", text: "Are you want to Open File", icon: "info", dangerMode: true, 207 | buttons: { 208 | cancel: {text: "Cancel", visible: true}, 209 | confirm: {text: "OK"}, 210 | }, 211 | }).then((willDelete: any) => { 212 | if (willDelete) { 213 | ipcRenderer.send(EventConstants.PHODIT.SHOW_PDF, this.state.currentFile); 214 | } 215 | }); 216 | }); 217 | 218 | // 返回获取自动完成请求 219 | ipcRenderer.on(EventConstants.PHODIT.SUGGEST_SEND, (event: any, arg: any) => { 220 | createEvent(EventConstants.PHODIT.SUGGEST_TO_EDITOR, arg); 221 | }); 222 | 223 | // 返回获取自动完成请求 224 | ipcRenderer.on(EventConstants.PHODIT.TOGGLE_THEME, (event: any, arg: any) => { 225 | createEvent(EventConstants.CLIENT.TOGGLE_THEME, arg); 226 | }); 227 | 228 | // 打开文件 229 | ipcRenderer.on(EventConstants.PHODIT.OPEN_ONE_FILE, (event: any, arg: IFileOpen) => { 230 | this.state.currentFile = arg.file; 231 | this.state.isOneFile = false; 232 | this.state.isCurrentFileTemp = arg.isTempFile; 233 | this.easymde.codemirror.setOption("mode", getCodeMirrorMode(this.state.currentFile)); 234 | this.easymde.value(arg.data); 235 | this.updatePos(arg.file); 236 | 237 | localStorage.setItem("currentFile", arg.file); 238 | }); 239 | 240 | // 保存文件 241 | ipcRenderer.on(EventConstants.CLIENT.SAVE_FILE, () => { 242 | ipcRenderer.send(EventConstants.PHODIT.SAVE_FILE, { 243 | isTempFile: this.state.isCurrentFileTemp, 244 | data: this.easymde.value(), 245 | }); 246 | }); 247 | 248 | // 打开某一目录 249 | ipcRenderer.on(EventConstants.PHODIT.OPEN_PATH, (event: any, arg: any) => { 250 | this.state.isPath = true; 251 | document.getElementById("tree-view").setAttribute("style", "display: block"); 252 | (window as any).rootPath = arg.path + '/'; 253 | localStorage.setItem("currentPath", arg.path + '/'); 254 | 255 | createEvent("phodit.tree.open", { 256 | path: arg.path, 257 | tree: arg.tree, 258 | }); 259 | }); 260 | 261 | // 改变临时文件的状态 262 | ipcRenderer.on(EventConstants.TEMP_FILE_STATUS, (event: any, arg: any) => { 263 | this.state.isCurrentFileTemp = arg.isTempFile; 264 | }); 265 | } 266 | 267 | public initTheme() { 268 | if (window.localStorage.os_theme === "dark") { 269 | ipcRenderer.send(EventConstants.PHODIT.SET_THEME, {mode: 'dark'}) 270 | this.easymde.codemirror.setOption('theme', 'django'); 271 | } else { 272 | ipcRenderer.send(EventConstants.PHODIT.SET_THEME, {mode: 'light'}) 273 | this.easymde.codemirror.setOption('theme', 'easymde'); 274 | } 275 | 276 | (window as any).__setTheme(); 277 | } 278 | 279 | public toggleTheme(codemirror: any) { 280 | if (window.localStorage.os_theme === "dark") { 281 | window.localStorage.os_theme = "light"; 282 | codemirror.setOption('theme', 'easymde'); 283 | } else { 284 | window.localStorage.os_theme = "dark"; 285 | codemirror.setOption('theme', 'django'); 286 | } 287 | 288 | (window as any).__setTheme(); 289 | } 290 | } 291 | 292 | const client = new ClientUI(); 293 | client.init(); 294 | client.bindEvent(); 295 | 296 | client.initTheme(); 297 | --------------------------------------------------------------------------------