├── .readme.assets ├── bs.gif ├── dagang.gif ├── html-1730953102113-3.gif ├── html.gif ├── image-20240805221400275.png ├── image-20240805221515120.png ├── image-20240805221953206.png ├── image-20240805222137381.png ├── image-20240805222339944.png ├── image-20240805223213943.png ├── image-20240805232704516.png ├── image-20241105112239154.png ├── image-20241105112257596.png ├── image-20241105112318058.png ├── image-20241107123200610.png ├── image-20241107123228009.png ├── image-20250225203211079.png ├── image-20250225203337558.png ├── image-20250225203344730.png ├── image-20250225203428732.png ├── image-20250301193027925.png ├── image-20250301205630800.png ├── image-20250301205720584.png ├── image-20250301205743126.png ├── image-20250301210251693.png ├── image-20250301210311890.png ├── image-20250301210334232.png ├── image-20250301210427777.png ├── image-20250301210613354.png ├── image-20250301210636083.png ├── image-20250301210652187.png ├── image-20250301210716264.png ├── image-20250301210842142.png ├── image-20250301210925456.png ├── image-20250301211330837.png ├── image-20250301211350357.png ├── image-20250301211818031.png ├── image-20250309185208391.png ├── image-20250309185232078.png ├── image-20250401144300786.png ├── image-20250401144332484.png ├── image-20250401144528653.png ├── image-20250401144553788.png ├── image-20250402111933803.png ├── nt.gif ├── sb.gif ├── titile.gif ├── title.gif ├── wenjian.gif ├── 大纲栏.gif ├── 思维导图.gif ├── 文件栏.gif ├── 标题-1740828423301-1.gif └── 标题.gif ├── LICENSE ├── css ├── drake-ayu.css ├── drake-black.css ├── drake-dark.css ├── drake-google.css ├── drake-jb.css ├── drake-juejin.css ├── drake-light.css ├── drake-material.css ├── drake-purple.css ├── drake-vue.css ├── drake-vue3.css ├── drake.css ├── drake │ ├── JetBrainsMono-Bold.woff2 │ ├── JetBrainsMono-BoldItalic.woff2 │ ├── JetBrainsMono-Italic.woff2 │ ├── JetBrainsMono-Regular.woff2 │ └── font.css ├── typora-mid-d.css ├── typora-mid-d2.css └── typora-mid-l.css ├── font.zip ├── readme.md ├── resources ├── plugin │ ├── article_uploader │ │ ├── Plugin2UploadBridge.js │ │ ├── README.md │ │ ├── controller │ │ │ └── UploadController.js │ │ ├── index.js │ │ ├── uploader │ │ │ ├── BaseUploaderInterface.js │ │ │ ├── CnBlogUploader.js │ │ │ ├── CsdnUploader.js │ │ │ └── WordpressUploader.js │ │ └── utils │ │ │ ├── axios.min.js │ │ │ ├── crypto-js │ │ │ ├── core.js │ │ │ ├── enc-base64.js │ │ │ ├── hmac-sha256.js │ │ │ ├── hmac.js │ │ │ └── sha256.js │ │ │ ├── customNotification.js │ │ │ └── uploadUtils.js │ ├── auto_number.js │ ├── bin │ │ ├── install.c │ │ ├── install.go │ │ ├── install_linux.sh │ │ ├── install_linux_amd_x64 │ │ ├── install_windows.ps1 │ │ ├── install_windows_amd_x64.exe │ │ ├── uninstall_linux.sh │ │ ├── uninstall_windows.ps1 │ │ └── version.json │ ├── blur.js │ ├── cipher │ │ ├── aes_ecb.min.js │ │ └── index.js │ ├── collapse_list.js │ ├── collapse_paragraph.js │ ├── collapse_table.js │ ├── commander.js │ ├── custom │ │ ├── README.md │ │ ├── index.js │ │ └── plugins │ │ │ ├── abc │ │ │ ├── abcjs-basic-min.js │ │ │ └── index.js │ │ │ ├── blockSideBySide.js │ │ │ ├── calendar │ │ │ ├── index.js │ │ │ ├── toastui-calendar.min.css │ │ │ └── toastui-calendar.min.js │ │ │ ├── callouts.js │ │ │ ├── chart │ │ │ ├── chart.min.js │ │ │ └── index.js │ │ │ ├── chat.js │ │ │ ├── chineseSymbolAutoPairer.js │ │ │ ├── drawIO │ │ │ └── index.js │ │ │ ├── echarts │ │ │ ├── echarts.min.js │ │ │ └── index.js │ │ │ ├── imageReviewer.js │ │ │ ├── kanban.js │ │ │ ├── markdownLint │ │ │ ├── MD101.js │ │ │ ├── index.js │ │ │ ├── linter-worker.js │ │ │ └── markdownlint.min.js │ │ │ ├── marp │ │ │ ├── index.js │ │ │ └── marp.min.js │ │ │ ├── quickButton.js │ │ │ ├── redirectLocalRootUrl.js │ │ │ ├── reopenClosedFiles │ │ │ ├── index.js │ │ │ └── remain.json │ │ │ ├── resourceOperation.js │ │ │ ├── scrollBookmarker │ │ │ ├── bookmark.json │ │ │ └── index.js │ │ │ ├── templater.js │ │ │ ├── timeline.js │ │ │ ├── toc.js │ │ │ └── wavedrom │ │ │ ├── index.js │ │ │ └── wavedrom.min.js │ ├── dark.js │ ├── datatables │ │ ├── index.js │ │ └── resource │ │ │ ├── dataTables.min.css │ │ │ ├── dataTables.min.js │ │ │ ├── sort_asc.png │ │ │ ├── sort_asc_disabled.png │ │ │ ├── sort_both.png │ │ │ ├── sort_desc.png │ │ │ └── sort_desc_disabled.png │ ├── easy_modify.js │ ├── editor_width_slider.js │ ├── export_enhance.js │ ├── fence_enhance │ │ ├── index.js │ │ └── resource │ │ │ ├── brace-fold.js │ │ │ ├── comment-fold.js │ │ │ ├── foldcode.js │ │ │ ├── foldgutter.css │ │ │ ├── foldgutter.js │ │ │ ├── indent-fold.js │ │ │ ├── markdown-fold.js │ │ │ └── xml-fold.js │ ├── file_counter.js │ ├── global │ │ ├── core │ │ │ ├── i18n.js │ │ │ ├── index.js │ │ │ ├── plugin.js │ │ │ └── utils │ │ │ │ ├── common │ │ │ │ ├── jszip │ │ │ │ │ └── jszip.min.js │ │ │ │ ├── markdown-it │ │ │ │ │ └── index.js │ │ │ │ ├── node-fetch │ │ │ │ │ ├── https-proxy-agent.js │ │ │ │ │ └── node-fetch.js │ │ │ │ ├── toml │ │ │ │ │ ├── compiler.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── parser.js │ │ │ │ │ └── stringify.js │ │ │ │ └── yaml │ │ │ │ │ └── index.js │ │ │ │ ├── env.js │ │ │ │ ├── index.js │ │ │ │ └── mixin │ │ │ │ ├── contextMenu.js │ │ │ │ ├── diagramParser.js │ │ │ │ ├── dialog.js │ │ │ │ ├── entities.js │ │ │ │ ├── eventHub.js │ │ │ │ ├── exportHelper.js │ │ │ │ ├── extra.js │ │ │ │ ├── hotkeyHub.js │ │ │ │ ├── htmlTemplater.js │ │ │ │ ├── index.js │ │ │ │ ├── migrate.js │ │ │ │ ├── notification.js │ │ │ │ ├── polyfill.js │ │ │ │ ├── progressBar.js │ │ │ │ ├── runtime.js │ │ │ │ ├── searchStringParser.js │ │ │ │ ├── stateRecorder.js │ │ │ │ ├── styleTemplater.js │ │ │ │ └── thirdPartyDiagramParser.js │ │ ├── locales │ │ │ ├── en.json │ │ │ ├── zh-CN.json │ │ │ └── zh-TW.json │ │ ├── settings │ │ │ ├── README.md │ │ │ ├── custom_plugin.default.toml │ │ │ ├── custom_plugin.user.toml │ │ │ ├── settings.default.toml │ │ │ └── settings.user.toml │ │ ├── styles │ │ │ ├── blockSideBySide.css │ │ │ ├── callouts.css │ │ │ ├── chat.css │ │ │ ├── collapse_list.css │ │ │ ├── collapse_paragraph.css │ │ │ ├── collapse_table.css │ │ │ ├── commander.css │ │ │ ├── customize.css │ │ │ ├── dark.css │ │ │ ├── datatables.css │ │ │ ├── drawIO.css │ │ │ ├── fence_enhance.css │ │ │ ├── file_counter.css │ │ │ ├── imageReviewer.css │ │ │ ├── kanban.css │ │ │ ├── markdownLint.css │ │ │ ├── markmap.css │ │ │ ├── marp.css │ │ │ ├── no_image.css │ │ │ ├── pie_menu.css │ │ │ ├── plugin-common-menu.css │ │ │ ├── plugin-common-modal.css │ │ │ ├── plugin-common-notification.css │ │ │ ├── plugin-common-progress-bar.css │ │ │ ├── plugin-common.css │ │ │ ├── plugin-diagram-parser.css │ │ │ ├── quickButton.css │ │ │ ├── read_only.css │ │ │ ├── resize_table.css │ │ │ ├── resourceOperation.css │ │ │ ├── right_click_menu.css │ │ │ ├── ripgrep.css │ │ │ ├── scrollBookmarker.css │ │ │ ├── search_multi.css │ │ │ ├── slash_commands.css │ │ │ ├── text_stylize.css │ │ │ ├── timeline.css │ │ │ ├── toc.css │ │ │ ├── toolbar.css │ │ │ └── window_tab.css │ │ └── user_styles │ │ │ ├── README.md │ │ │ ├── customize.css │ │ │ └── 请读我.md │ ├── go_top.js │ ├── help.js │ ├── hotkeys.js │ ├── index.js │ ├── json_rpc │ │ ├── README.md │ │ ├── index.js │ │ └── node-json-rpc.js │ ├── markmap │ │ ├── index.js │ │ └── resource │ │ │ ├── default.min.css │ │ │ ├── fonts │ │ │ ├── KaTeX_AMS-Regular.woff2 │ │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ │ ├── KaTeX_Main-Bold.woff2 │ │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ │ ├── KaTeX_Main-Italic.woff2 │ │ │ ├── KaTeX_Main-Regular.woff2 │ │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ │ ├── KaTeX_Math-Italic.woff2 │ │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ │ ├── KaTeX_Script-Regular.woff2 │ │ │ ├── KaTeX_Size1-Regular.woff2 │ │ │ ├── KaTeX_Size2-Regular.woff2 │ │ │ ├── KaTeX_Size3-Regular.woff2 │ │ │ ├── KaTeX_Size4-Regular.woff2 │ │ │ └── KaTeX_Typewriter-Regular.woff2 │ │ │ ├── katex.min.css │ │ │ ├── markmap-lib.js │ │ │ ├── markmap-view.js │ │ │ ├── markmap-view.jse │ │ │ └── webfontloader.js │ ├── md_padding │ │ ├── index.js │ │ └── md-padding.min.js │ ├── no_image.js │ ├── pie_menu.js │ ├── preferences.js │ ├── read_only.js │ ├── resize_image.js │ ├── resize_table.js │ ├── right_click_menu.js │ ├── ripgrep.js │ ├── search_multi.js │ ├── slash_commands.js │ ├── test.js │ ├── text_stylize.js │ ├── toolbar.js │ ├── truncate_text.js │ ├── updater.js │ └── window_tab │ │ └── index.js ├── style │ ├── font-awesome-4.7.0 │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ └── window.css └── window.html └── themes.zip /.readme.assets/bs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/bs.gif -------------------------------------------------------------------------------- /.readme.assets/dagang.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/dagang.gif -------------------------------------------------------------------------------- /.readme.assets/html-1730953102113-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/html-1730953102113-3.gif -------------------------------------------------------------------------------- /.readme.assets/html.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/html.gif -------------------------------------------------------------------------------- /.readme.assets/image-20240805221400275.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805221400275.png -------------------------------------------------------------------------------- /.readme.assets/image-20240805221515120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805221515120.png -------------------------------------------------------------------------------- /.readme.assets/image-20240805221953206.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805221953206.png -------------------------------------------------------------------------------- /.readme.assets/image-20240805222137381.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805222137381.png -------------------------------------------------------------------------------- /.readme.assets/image-20240805222339944.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805222339944.png -------------------------------------------------------------------------------- /.readme.assets/image-20240805223213943.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805223213943.png -------------------------------------------------------------------------------- /.readme.assets/image-20240805232704516.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20240805232704516.png -------------------------------------------------------------------------------- /.readme.assets/image-20241105112239154.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20241105112239154.png -------------------------------------------------------------------------------- /.readme.assets/image-20241105112257596.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20241105112257596.png -------------------------------------------------------------------------------- /.readme.assets/image-20241105112318058.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20241105112318058.png -------------------------------------------------------------------------------- /.readme.assets/image-20241107123200610.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20241107123200610.png -------------------------------------------------------------------------------- /.readme.assets/image-20241107123228009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20241107123228009.png -------------------------------------------------------------------------------- /.readme.assets/image-20250225203211079.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250225203211079.png -------------------------------------------------------------------------------- /.readme.assets/image-20250225203337558.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250225203337558.png -------------------------------------------------------------------------------- /.readme.assets/image-20250225203344730.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250225203344730.png -------------------------------------------------------------------------------- /.readme.assets/image-20250225203428732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250225203428732.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301193027925.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301193027925.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301205630800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301205630800.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301205720584.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301205720584.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301205743126.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301205743126.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210251693.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210251693.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210311890.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210311890.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210334232.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210334232.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210427777.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210427777.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210613354.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210613354.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210636083.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210636083.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210652187.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210652187.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210716264.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210716264.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210842142.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210842142.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301210925456.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301210925456.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301211330837.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301211330837.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301211350357.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301211350357.png -------------------------------------------------------------------------------- /.readme.assets/image-20250301211818031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250301211818031.png -------------------------------------------------------------------------------- /.readme.assets/image-20250309185208391.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250309185208391.png -------------------------------------------------------------------------------- /.readme.assets/image-20250309185232078.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250309185232078.png -------------------------------------------------------------------------------- /.readme.assets/image-20250401144300786.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250401144300786.png -------------------------------------------------------------------------------- /.readme.assets/image-20250401144332484.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250401144332484.png -------------------------------------------------------------------------------- /.readme.assets/image-20250401144528653.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250401144528653.png -------------------------------------------------------------------------------- /.readme.assets/image-20250401144553788.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250401144553788.png -------------------------------------------------------------------------------- /.readme.assets/image-20250402111933803.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/image-20250402111933803.png -------------------------------------------------------------------------------- /.readme.assets/nt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/nt.gif -------------------------------------------------------------------------------- /.readme.assets/sb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/sb.gif -------------------------------------------------------------------------------- /.readme.assets/titile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/titile.gif -------------------------------------------------------------------------------- /.readme.assets/title.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/title.gif -------------------------------------------------------------------------------- /.readme.assets/wenjian.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/wenjian.gif -------------------------------------------------------------------------------- /.readme.assets/大纲栏.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/大纲栏.gif -------------------------------------------------------------------------------- /.readme.assets/思维导图.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/思维导图.gif -------------------------------------------------------------------------------- /.readme.assets/文件栏.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/文件栏.gif -------------------------------------------------------------------------------- /.readme.assets/标题-1740828423301-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/标题-1740828423301-1.gif -------------------------------------------------------------------------------- /.readme.assets/标题.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/.readme.assets/标题.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 xyz349925756 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /css/drake/JetBrainsMono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/css/drake/JetBrainsMono-Bold.woff2 -------------------------------------------------------------------------------- /css/drake/JetBrainsMono-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/css/drake/JetBrainsMono-BoldItalic.woff2 -------------------------------------------------------------------------------- /css/drake/JetBrainsMono-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/css/drake/JetBrainsMono-Italic.woff2 -------------------------------------------------------------------------------- /css/drake/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/css/drake/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /css/drake/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'JetBrains Mono'; 3 | src: local('JetBrains Mono'), 4 | url('./JetBrainsMono-Regular.woff2') format('woff2'); 5 | font-display: swap; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | @font-face { 10 | font-family: 'JetBrains Mono'; 11 | src: local('JetBrains Mono'), 12 | url('./JetBrainsMono-Italic.woff2') format('woff2'); 13 | font-display: swap; 14 | font-weight: normal; 15 | font-style: italic; 16 | } 17 | @font-face { 18 | font-family: 'JetBrains Mono'; 19 | src: local('JetBrains Mono'), 20 | url('./JetBrainsMono-Bold.woff2') format('woff2'); 21 | font-display: swap; 22 | font-weight: bold; 23 | font-style: normal; 24 | } 25 | @font-face { 26 | font-family: 'JetBrains Mono'; 27 | src: local('JetBrains Mono'), 28 | url('./JetBrainsMono-BoldItalic.woff2') format('woff2'); 29 | font-display: swap; 30 | font-weight: bold; 31 | font-style: italic; 32 | } 33 | 34 | /*快速自定义配置*/ 35 | :root { 36 | --monospace: "Iosevka Curly", "JetBrains Mono", "Fira Code", "Cascadia Code", Menlo, "Ubuntu Mono", Consolas, HYZhengYuan; /*代码字体*/ 37 | --text-font: var(--monospace); /*正文字体*/ 38 | --title-font: var(--monospace); /*标题字体*/ 39 | --latex-font: var(--monospace); /*LaTeX字体(不含英语)*/ 40 | --text-line-height: 1.6; /*正文行间距*/ 41 | --code-line-height: 1.6; /*代码块行间距*/ 42 | --p-spacing: 0.8rem; /*段间距*/ 43 | --file-tree-text-size: 1.1rem; /*文件树大小*/ 44 | --toc-text-size: 1rem; /*大纲大小*/ 45 | --text-size: 14px; /*字体大小 推荐配置: 应用设置-外观-字体大小*/ 46 | } -------------------------------------------------------------------------------- /font.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/font.zip -------------------------------------------------------------------------------- /resources/plugin/article_uploader/Plugin2UploadBridge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 桥接插件和上传工具。用于转发请求给控制器,消息统计,提示功能 3 | */ 4 | class Plugin2UploadBridge { 5 | constructor(plugin) { 6 | this.plugin = plugin; 7 | this.config = plugin.config; 8 | this.sites = ["cnblog", "csdn", "wordpress"]; 9 | this.utils = null; 10 | this.uploadController = null; 11 | this.notification = null; 12 | } 13 | 14 | lazyLoad = () => { 15 | if (!this.utils) { 16 | const Utils = require('./utils/uploadUtils'); 17 | this.utils = new Utils(this.plugin); 18 | } 19 | 20 | if (!this.uploadController) { 21 | const UploadController = require('./controller/UploadController'); 22 | this.uploadController = new UploadController(this); 23 | this.sites.forEach(site => this.uploadController.register(site)); 24 | } 25 | 26 | if (!this.notification) { 27 | const Notification = require('./utils/customNotification.js').plugin; 28 | this.notification = new Notification(); 29 | } 30 | } 31 | 32 | uploadProxy = async (filePath, type = "all") => { 33 | if (this.config.upload.reconfirm) { 34 | const message = "你确定要上传文章吗"; 35 | const op = { type: "info", title: "上传提示", buttons: ["确定", "取消"], message }; 36 | const { response } = await this.plugin.utils.showMessageBox(op); 37 | if (response === 1) { 38 | return; 39 | } 40 | } 41 | 42 | this.lazyLoad(); 43 | this.notification.showNotification('开始上传,请不要关闭软件', 'info'); 44 | const startTime = new Date(); 45 | 46 | if (type === "all") { 47 | await this.uploadController.uploadToAllPlatforms(filePath); 48 | } else { 49 | await this.uploadController.upload(type, filePath); 50 | } 51 | 52 | const endTime = new Date(); 53 | const duration = ((endTime - startTime) / 1000).toFixed(1); 54 | this.notification.showNotification(`上传成功,耗时${duration}秒`, 'success'); 55 | } 56 | } 57 | 58 | module.exports = Plugin2UploadBridge; 59 | -------------------------------------------------------------------------------- /resources/plugin/article_uploader/controller/UploadController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 控制器,转发请求,注册插件,设置配置 3 | */ 4 | class UploadController { 5 | constructor(bridge) { 6 | this.bridge = bridge; 7 | this.config = bridge.config; 8 | this.utils = bridge.utils; 9 | this.uploaders = new Map(); 10 | this.options = null; 11 | this.pathMap = { 12 | cnblog: "../uploader/CnBlogUploader", 13 | csdn: "../uploader/CsdnUploader", 14 | wordpress: "../uploader/WordpressUploader", 15 | } 16 | 17 | this.init(); 18 | } 19 | 20 | init = () => { 21 | if (!this.options) { 22 | const chrome = require('selenium-webdriver/chrome'); 23 | this.options = new chrome.Options(); 24 | this.options.addArguments( 25 | '--disable-blink-features=AutomationControlled', 26 | '--disable-infobars', 27 | '--disable-extensions', 28 | '--disable-gpu', 29 | '--no-sandbox', 30 | '--disable-dev-shm-usage', 31 | '--disable-javascript' 32 | ); 33 | } 34 | 35 | if (this.config.upload.selenium.headless) { 36 | this.options.addArguments("--headless"); 37 | } 38 | } 39 | 40 | register = (site) => { 41 | const path = this.pathMap[site]; 42 | if (path) { 43 | const uploader = require(path); 44 | const instance = new uploader(this); 45 | const name = instance.getName(); 46 | this.uploaders.set(name, instance); 47 | } 48 | } 49 | 50 | unregister = (name) => this.uploaders.delete(name); 51 | 52 | // 这里对结果不做捕捉,后续根据需求优化 53 | upload = async (platform, filePath) => { 54 | const uploader = this.uploaders.get(platform); 55 | const { title, content, extraData } = this.utils.readAndSplitFile(filePath); 56 | if (uploader) { 57 | await uploader.upload(title, content, extraData, this.options); 58 | } 59 | } 60 | 61 | uploadToAllPlatforms = async (filePath) => { 62 | const { title, content, extraData } = this.utils.readAndSplitFile(filePath); 63 | for (let [name, uploader] of this.uploaders) { 64 | // 上传全部的时候不上传哪些平台,属于脱裤子放屁的需求 65 | const c = this.config.upload[name]; 66 | if (c && c.enabled) { 67 | await uploader.upload(title, content, extraData, this.options); 68 | } 69 | } 70 | } 71 | } 72 | 73 | module.exports = UploadController; 74 | -------------------------------------------------------------------------------- /resources/plugin/article_uploader/index.js: -------------------------------------------------------------------------------- 1 | class ArticleUploaderPlugin extends BasePlugin { 2 | init = () => { 3 | this.staticActions = this.i18n.fillActions([ 4 | { act_value: "upload_to_csdn" }, 5 | { act_value: "upload_to_wordpress" }, 6 | { act_value: "upload_to_cn_blog" }, 7 | { act_value: "upload_to_all_site" }, 8 | ]) 9 | } 10 | 11 | hotkey = () => [ 12 | { hotkey: this.config.UPLOAD_CSDN_HOTKEY, callback: () => this.call("upload_to_csdn") }, 13 | { hotkey: this.config.UPLOAD_CNBLOG_HOTKEY, callback: () => this.call("upload_to_cn_blog") }, 14 | { hotkey: this.config.UPLOAD_WORDPRESS_HOTKEY, callback: () => this.call("upload_to_wordpress") }, 15 | { hotkey: this.config.UPLOAD_ALL_HOTKEY, callback: () => this.call("upload_to_all_site") }, 16 | ] 17 | 18 | call = async action => { 19 | const map = { 20 | upload_to_csdn: "csdn", 21 | upload_to_wordpress: "wordpress", 22 | upload_to_cn_blog: "cnblog", 23 | upload_to_all_site: "all" 24 | } 25 | const act = map[action] 26 | if (act) { 27 | await this.upload(act) 28 | } 29 | } 30 | 31 | upload = async action => { 32 | const uploader = require("./Plugin2UploadBridge"); 33 | this.uploader = new uploader(this); 34 | const filePath = this.utils.getFilePath(); 35 | await this.uploader.uploadProxy(filePath, action); 36 | } 37 | } 38 | 39 | module.exports = { 40 | plugin: ArticleUploaderPlugin 41 | } 42 | -------------------------------------------------------------------------------- /resources/plugin/article_uploader/uploader/BaseUploaderInterface.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 上传到各大平台的接口基类 3 | */ 4 | class BaseUploaderInterface { 5 | constructor(controller) { 6 | this.utils = controller.utils; 7 | this.config = controller.config; 8 | } 9 | 10 | getName() { 11 | throw new Error("应该被子类实现getName方法"); 12 | } 13 | 14 | async upload(title, content, extraData, options) { 15 | throw new Error("应该被子类实现upload方法"); 16 | } 17 | } 18 | 19 | module.exports = BaseUploaderInterface; 20 | -------------------------------------------------------------------------------- /resources/plugin/article_uploader/uploader/WordpressUploader.js: -------------------------------------------------------------------------------- 1 | const BaseUploaderInterface = require("./BaseUploaderInterface"); 2 | 3 | /** 4 | * 上传到wordpress的插件实现 5 | */ 6 | class WordpressUploader extends BaseUploaderInterface { 7 | getName() { 8 | return "wordpress"; 9 | } 10 | 11 | async upload(title, content, extraData, options) { 12 | const { Builder, By, Key, until } = require('selenium-webdriver'); 13 | const chrome = require('selenium-webdriver/chrome'); 14 | require('chromedriver'); 15 | const Notification = require('../utils/customNotification.js').plugin; 16 | const notification = new Notification(); 17 | const { marked } = require('marked'); 18 | 19 | const SELENIUM_WAIT_FIX_TIME_LEVEL1 = 2000; 20 | const SELENIUM_EXPLICIT_WAIT_TIME = 10000; 21 | 22 | let driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build(); 23 | try { 24 | await driver.manage().window().maximize(); 25 | await driver.get(this.config.upload.wordpress.loginUrl); 26 | 27 | await driver.sleep(SELENIUM_WAIT_FIX_TIME_LEVEL1); 28 | let userInput = await driver.wait(until.elementLocated(By.id('user_login')), SELENIUM_EXPLICIT_WAIT_TIME); 29 | await driver.wait(until.elementIsVisible(userInput), SELENIUM_EXPLICIT_WAIT_TIME); 30 | await userInput.sendKeys(this.config.upload.wordpress.username); 31 | let passInput = await driver.wait(until.elementLocated(By.id('user_pass')), SELENIUM_EXPLICIT_WAIT_TIME); 32 | await driver.wait(until.elementIsVisible(passInput), SELENIUM_EXPLICIT_WAIT_TIME); 33 | await passInput.sendKeys(this.config.upload.wordpress.password, Key.RETURN); 34 | await driver.get(`${this.config.upload.wordpress.hostname}/wp-admin/post-new.php`); 35 | 36 | await driver.wait(until.elementLocated(By.name('post_title')), SELENIUM_EXPLICIT_WAIT_TIME); 37 | let titleField = await driver.findElement(By.name('post_title')); 38 | await titleField.sendKeys(title); 39 | 40 | await driver.wait(until.elementLocated(By.id('content_ifr')), SELENIUM_EXPLICIT_WAIT_TIME); 41 | let editorFrame = await driver.findElement(By.id('content_ifr')); 42 | await driver.switchTo().frame(editorFrame); 43 | 44 | let body = await driver.findElement(By.id('tinymce')); 45 | await driver.executeScript("arguments[0].innerHTML = arguments[1]", body, marked(content)); 46 | 47 | await driver.switchTo().defaultContent(); 48 | 49 | await driver.sleep(SELENIUM_WAIT_FIX_TIME_LEVEL1); 50 | 51 | await driver.wait(until.elementLocated(By.id('publish')), SELENIUM_EXPLICIT_WAIT_TIME); 52 | let publishButton = await driver.findElement(By.id('publish')); 53 | await publishButton.click(); 54 | console.log("wordpress博客发布流程已完毕"); 55 | notification.showNotification("WordPress博客发布流程已完毕", "success"); 56 | } catch (error) { 57 | console.log(error); 58 | notification.showNotification('WordPress博客发布失败', 'error'); 59 | } finally { 60 | await driver.quit(); 61 | } 62 | } 63 | } 64 | 65 | module.exports = WordpressUploader; 66 | -------------------------------------------------------------------------------- /resources/plugin/article_uploader/utils/crypto-js/core.js: -------------------------------------------------------------------------------- 1 | (function(e,r){"object"==typeof exports?module.exports=exports=r():"function"==typeof define&&define.amd?define([],r):e.CryptoJS=r()})(this,function(){var e=e||function(e,r){var t={},i=t.lib={},n=i.Base=function(){function e(){}return{extend:function(r){e.prototype=this;var t=new e;return r&&t.mixIn(r),t.hasOwnProperty("init")||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var r in e)e.hasOwnProperty(r)&&(this[r]=e[r]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),o=i.WordArray=n.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=t!=r?t:4*e.length},toString:function(e){return(e||s).stringify(this)},concat:function(e){var r=this.words,t=e.words,i=this.sigBytes,n=e.sigBytes;if(this.clamp(),i%4)for(var o=0;n>o;o++){var c=255&t[o>>>2]>>>24-8*(o%4);r[i+o>>>2]|=c<<24-8*((i+o)%4)}else if(t.length>65535)for(var o=0;n>o;o+=4)r[i+o>>>2]=t[o>>>2];else r.push.apply(r,t);return this.sigBytes+=n,this},clamp:function(){var r=this.words,t=this.sigBytes;r[t>>>2]&=4294967295<<32-8*(t%4),r.length=e.ceil(t/4)},clone:function(){var e=n.clone.call(this);return e.words=this.words.slice(0),e},random:function(r){for(var t=[],i=0;r>i;i+=4)t.push(0|4294967296*e.random());return new o.init(t,r)}}),c=t.enc={},s=c.Hex={stringify:function(e){for(var r=e.words,t=e.sigBytes,i=[],n=0;t>n;n++){var o=255&r[n>>>2]>>>24-8*(n%4);i.push((o>>>4).toString(16)),i.push((15&o).toString(16))}return i.join("")},parse:function(e){for(var r=e.length,t=[],i=0;r>i;i+=2)t[i>>>3]|=parseInt(e.substr(i,2),16)<<24-4*(i%8);return new o.init(t,r/2)}},u=c.Latin1={stringify:function(e){for(var r=e.words,t=e.sigBytes,i=[],n=0;t>n;n++){var o=255&r[n>>>2]>>>24-8*(n%4);i.push(String.fromCharCode(o))}return i.join("")},parse:function(e){for(var r=e.length,t=[],i=0;r>i;i++)t[i>>>2]|=(255&e.charCodeAt(i))<<24-8*(i%4);return new o.init(t,r)}},f=c.Utf8={stringify:function(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(r){throw Error("Malformed UTF-8 data")}},parse:function(e){return u.parse(unescape(encodeURIComponent(e)))}},a=i.BufferedBlockAlgorithm=n.extend({reset:function(){this._data=new o.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=f.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(r){var t=this._data,i=t.words,n=t.sigBytes,c=this.blockSize,s=4*c,u=n/s;u=r?e.ceil(u):e.max((0|u)-this._minBufferSize,0);var f=u*c,a=e.min(4*f,n);if(f){for(var p=0;f>p;p+=c)this._doProcessBlock(i,p);var d=i.splice(0,f);t.sigBytes-=a}return new o.init(d,a)},clone:function(){var e=n.clone.call(this);return e._data=this._data.clone(),e},_minBufferSize:0});i.Hasher=a.extend({cfg:n.extend(),init:function(e){this.cfg=this.cfg.extend(e),this.reset()},reset:function(){a.reset.call(this),this._doReset()},update:function(e){return this._append(e),this._process(),this},finalize:function(e){e&&this._append(e);var r=this._doFinalize();return r},blockSize:16,_createHelper:function(e){return function(r,t){return new e.init(t).finalize(r)}},_createHmacHelper:function(e){return function(r,t){return new p.HMAC.init(e,t).finalize(r)}}});var p=t.algo={};return t}(Math);return e}); -------------------------------------------------------------------------------- /resources/plugin/article_uploader/utils/crypto-js/enc-base64.js: -------------------------------------------------------------------------------- 1 | (function(e,r){"object"==typeof exports?module.exports=exports=r(require("./core")):"function"==typeof define&&define.amd?define(["./core"],r):r(e.CryptoJS)})(this,function(e){return function(){var r=e,t=r.lib,n=t.WordArray,i=r.enc;i.Base64={stringify:function(e){var r=e.words,t=e.sigBytes,n=this._map;e.clamp();for(var i=[],o=0; t>o; o+=3)for(var c=255&r[o>>>2]>>>24-8*(o%4),f=255&r[o+1>>>2]>>>24-8*((o+1)%4),s=255&r[o+2>>>2]>>>24-8*((o+2)%4),a=c<<16|f<<8|s,u=0; 4>u&&t>o+.75*u; u++)i.push(n.charAt(63&a>>>6*(3-u)));var p=n.charAt(64);if(p)for(; i.length%4;)i.push(p);return i.join("")},parse:function(e){var r=e.length,t=this._map,i=t.charAt(64);if(i){var o=e.indexOf(i);-1!=o&&(r=o)}for(var c=[],f=0,s=0; r>s; s++)if(s%4){var a=t.indexOf(e.charAt(s-1))<<2*(s%4),u=t.indexOf(e.charAt(s))>>>6-2*(s%4);c[f>>>2]|=(a|u)<<24-8*(f%4),f++}return n.create(c,f)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}(),e.enc.Base64}); -------------------------------------------------------------------------------- /resources/plugin/article_uploader/utils/crypto-js/hmac-sha256.js: -------------------------------------------------------------------------------- 1 | (function(e,r){"object"==typeof exports?module.exports=exports=r(require("./core"),require("./sha256"),require("./hmac")):"function"==typeof define&&define.amd?define(["./core","./sha256","./hmac"],r):r(e.CryptoJS)})(this,function(e){return e.HmacSHA256}); -------------------------------------------------------------------------------- /resources/plugin/article_uploader/utils/crypto-js/hmac.js: -------------------------------------------------------------------------------- 1 | (function(e,r){"object"==typeof exports?module.exports=exports=r(require("./core")):"function"==typeof define&&define.amd?define(["./core"],r):r(e.CryptoJS)})(this,function(e){(function(){var r=e,t=r.lib,n=t.Base,i=r.enc,o=i.Utf8,a=r.algo;a.HMAC=n.extend({init:function(e, r){e=this._hasher=new e.init,"string"==typeof r&&(r=o.parse(r));var t=e.blockSize,n=4*t;r.sigBytes>n&&(r=e.finalize(r)),r.clamp();for(var i=this._oKey=r.clone(),a=this._iKey=r.clone(),s=i.words,c=a.words,f=0; t>f; f++)s[f]^=1549556828,c[f]^=909522486;i.sigBytes=a.sigBytes=n,this.reset()},reset:function(){var e=this._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var r=this._hasher,t=r.finalize(e);r.reset();var n=r.finalize(this._oKey.clone().concat(t));return n}})})()}); -------------------------------------------------------------------------------- /resources/plugin/article_uploader/utils/crypto-js/sha256.js: -------------------------------------------------------------------------------- 1 | (function(e,r){"object"==typeof exports?module.exports=exports=r(require("./core")):"function"==typeof define&&define.amd?define(["./core"],r):r(e.CryptoJS)})(this,function(e){return function(r){var t=e,n=t.lib,i=n.WordArray,o=n.Hasher,s=t.algo,c=[],a=[];(function(){function e(e){for(var t=r.sqrt(e),n=2; t>=n; n++)if(!(e%n))return!1;return!0}function t(e){return 0|4294967296*(e-(0|e))}for(var n=2,i=0; 64>i;)e(n)&&(8>i&&(c[i]=t(r.pow(n,.5))),a[i]=t(r.pow(n,1/3)),i++),n++})();var f=[],u=s.SHA256=o.extend({_doReset:function(){this._hash=new i.init(c.slice(0))},_doProcessBlock:function(e, r){for(var t=this._hash.words,n=t[0],i=t[1],o=t[2],s=t[3],c=t[4],u=t[5],d=t[6],p=t[7],h=0; 64>h; h++){if(16>h)f[h]=0|e[r+h];else{var y=f[h-15],l=(y<<25|y>>>7)^(y<<14|y>>>18)^y>>>3,m=f[h-2],x=(m<<15|m>>>17)^(m<<13|m>>>19)^m>>>10;f[h]=l+f[h-7]+x+f[h-16]}var v=c&u^~c&d,q=n&i^n&o^i&o,g=(n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22),_=(c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25),b=p+_+v+a[h]+f[h],S=g+q;p=d,d=u,u=c,c=0|s+b,s=o,o=i,i=n,n=0|b+S}t[0]=0|t[0]+n,t[1]=0|t[1]+i,t[2]=0|t[2]+o,t[3]=0|t[3]+s,t[4]=0|t[4]+c,t[5]=0|t[5]+u,t[6]=0|t[6]+d,t[7]=0|t[7]+p},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,i=8*e.sigBytes;return t[i>>>5]|=128<<24-i%32,t[(i+64>>>9<<4)+14]=r.floor(n/4294967296),t[(i+64>>>9<<4)+15]=n,e.sigBytes=4*t.length,this._process(),this._hash},clone:function(){var e=o.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=o._createHelper(u),t.HmacSHA256=o._createHmacHelper(u)}(Math),e.SHA256}); -------------------------------------------------------------------------------- /resources/plugin/article_uploader/utils/uploadUtils.js: -------------------------------------------------------------------------------- 1 | class UploadUtils { 2 | constructor(plugin) { 3 | this.plugin = plugin; 4 | this.CryptoJS = null; 5 | this.yaml = null; 6 | } 7 | 8 | // 懒加载 CryptoJS 模块 9 | lazyLoadCryptoJS = () => { 10 | if (!this.CryptoJS) { 11 | this.CryptoJS = require('./crypto-js/core'); 12 | require('./crypto-js/hmac'); 13 | require('./crypto-js/sha256'); 14 | require('./crypto-js/enc-base64'); 15 | } 16 | } 17 | 18 | // 生成UUID 19 | generateUUID() { 20 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 21 | var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 22 | return v.toString(16); 23 | }); 24 | } 25 | 26 | // 处理文件 27 | readAndSplitFile = (filePath) => { 28 | const Notification = require('../utils/customNotification.js').plugin; 29 | const notification = new Notification(); 30 | try { 31 | const fs = this.plugin.utils.Package.Fs; 32 | const data = fs.readFileSync(filePath, 'utf-8'); 33 | const lines = data.split('\n'); 34 | const title = lines[0].trim().replace(/#/g, '').trim(); 35 | const content = lines.slice(1).join('\n').trim(); 36 | if (title === "" || content === '') { 37 | throw new Error("内容为空"); 38 | } 39 | const extraData = ""; // TODO: 取出标签,分类,封面图等 40 | return { title, content, extraData }; 41 | } catch (error) { 42 | notification.showNotification('文件格式读取失败', "error"); 43 | console.error('Error reading file:', error); 44 | return null; 45 | } 46 | } 47 | 48 | // 获取签名 49 | getSign = (uuid, url) => { 50 | this.lazyLoadCryptoJS(); 51 | const parsedUrl = new URL(url); 52 | const _url = parsedUrl.pathname; 53 | 54 | const ekey = "9znpamsyl2c7cdrr9sas0le9vbc3r6ba"; 55 | const xCaKey = "203803574"; 56 | const toEnc = `POST\napplication/json, text/plain, */*\n\napplication/json;\n\nx-ca-key:${xCaKey}\nx-ca-nonce:${uuid}\n${_url}`; 57 | const hmac = this.CryptoJS.HmacSHA256(toEnc, ekey); 58 | return this.CryptoJS.enc.Base64.stringify(hmac); 59 | } 60 | } 61 | 62 | module.exports = UploadUtils; 63 | -------------------------------------------------------------------------------- /resources/plugin/bin/install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | type Installer struct { 13 | root string 14 | insertFile string 15 | insertContent string 16 | oldMatch string 17 | newMatch string 18 | } 19 | 20 | func newInstaller() (*Installer, error) { 21 | fmt.Println("[1/4] new installer") 22 | curDir, err := os.Getwd() 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &Installer{ 27 | root: filepath.Dir(filepath.Dir(curDir)), 28 | insertFile: "window.html", 29 | insertContent: ``, 30 | oldMatch: ``, 31 | newMatch: ``, 32 | }, nil 33 | } 34 | 35 | func (i *Installer) prepare() (err error) { 36 | fmt.Println("[2/4] prepare") 37 | if err = checkExist(i.root, i.insertFile); err != nil { 38 | return err 39 | } 40 | return nil 41 | } 42 | 43 | func (i *Installer) backupFile() (err error) { 44 | fmt.Println("[3/4] backup window.html") 45 | filePath := filepath.Join(i.root, i.insertFile) 46 | backupFilePath := filePath + ".bak" 47 | if err = copyFile(filePath, backupFilePath); err != nil { 48 | return err 49 | } 50 | return nil 51 | } 52 | 53 | func (i *Installer) run() (err error) { 54 | fmt.Println("[4/4] update window.html") 55 | filePath := filepath.Join(i.root, i.insertFile) 56 | file, err := ioutil.ReadFile(filePath) 57 | if err != nil { 58 | return err 59 | } 60 | if bytes.Contains(file, []byte(i.insertContent)) { 61 | return 62 | } 63 | 64 | match := "" 65 | if bytes.Contains(file, []byte(i.oldMatch)) { 66 | match = i.oldMatch 67 | } else if bytes.Contains(file, []byte(i.newMatch)) { 68 | match = i.newMatch 69 | } 70 | if match == "" { 71 | return fmt.Errorf("has not match") 72 | } 73 | 74 | result := bytes.Replace(file, []byte(match), []byte(match+i.insertContent), 1) 75 | err = ioutil.WriteFile(filePath, result, 0644) 76 | if err != nil { 77 | return err 78 | } 79 | return nil 80 | } 81 | 82 | func copyFile(src, dst string) (err error) { 83 | var srcFd *os.File 84 | var dstFd *os.File 85 | var srcInfo os.FileInfo 86 | 87 | if srcFd, err = os.Open(src); err != nil { 88 | return err 89 | } 90 | defer srcFd.Close() 91 | 92 | if dstFd, err = os.Create(dst); err != nil { 93 | return err 94 | } 95 | defer dstFd.Close() 96 | 97 | if _, err = io.Copy(dstFd, srcFd); err != nil { 98 | return err 99 | } 100 | if srcInfo, err = os.Stat(src); err != nil { 101 | return err 102 | } 103 | if err = os.Chmod(dst, srcInfo.Mode()); err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | func checkExist(root, sub string) error { 110 | if sub != "" { 111 | root = filepath.Join(root, sub) 112 | } 113 | exist, err := pathExists(root) 114 | if !exist { 115 | err = fmt.Errorf("%s is not exist", root) 116 | } 117 | return err 118 | } 119 | 120 | func pathExists(path string) (bool, error) { 121 | _, err := os.Stat(path) 122 | if err == nil { 123 | return true, nil 124 | } 125 | if os.IsNotExist(err) { 126 | return false, err 127 | } 128 | return false, err 129 | } 130 | 131 | func wait() { 132 | fmt.Printf("Press Enter to exit ...") 133 | endKey := make([]byte, 1) 134 | os.Stdin.Read(endKey) 135 | } 136 | 137 | func install() (err error) { 138 | installer, err := newInstaller() 139 | if err != nil { 140 | return err 141 | } 142 | if err = installer.prepare(); err != nil { 143 | return err 144 | } 145 | if err = installer.backupFile(); err != nil { 146 | return err 147 | } 148 | if err = installer.run(); err != nil { 149 | return err 150 | } 151 | fmt.Println("plugin install successfully") 152 | wait() 153 | return nil 154 | } 155 | 156 | func main() { 157 | if err := install(); err != nil { 158 | fmt.Println() 159 | panic(err) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /resources/plugin/bin/install_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rootDir=$(dirname "$(dirname "$PWD")") 4 | pluginDir="$rootDir/plugin" 5 | appPath="$rootDir/app" 6 | appsrcPath="$rootDir/appsrc" 7 | windowHTMLPath="$rootDir/window.html" 8 | windowHTMLBakPath="$rootDir/window.html.bak" 9 | pluginScript='' 10 | newFrameScript='' 11 | oldFrameScript='' 12 | frameScript="" 13 | 14 | panic() { 15 | echo -e "\033[0;31m ERROR: $1 \033[0m" 16 | exit 1 17 | } 18 | 19 | escape() { 20 | sed -E 's/[]\/$*.^|[]/\\&/g' <<<"$1" 21 | } 22 | 23 | echo "[1/9] check command" 24 | for cmd in echo cp cat chmod sed; do 25 | command -v "$cmd" &>/dev/null || panic "cannot find command $cmd, please install it." 26 | done 27 | 28 | echo "[2/9] check sudo" 29 | if [ "$EUID" -ne 0 ]; then 30 | panic "please run this script as root." 31 | fi 32 | 33 | echo "[3/9] check plugin exists" 34 | if ! [ -d "$pluginDir" ]; then 35 | panic "dir plugin does not exist in $rootDir" 36 | fi 37 | 38 | echo "[4/9] check whether window.html exists" 39 | if ! [ -f "$windowHTMLPath" ]; then 40 | panic "window.html does not exist in $rootDir" 41 | fi 42 | 43 | echo "[5/8] check whether app/appsrc exists" 44 | if [ -d "$appsrcPath" ]; then 45 | frameScript=$newFrameScript 46 | elif [ -d "$appPath" ]; then 47 | frameScript=$oldFrameScript 48 | else 49 | panic "appsrc/app does not exist in $rootDir" 50 | fi 51 | 52 | echo "[6/9] check window.html content" 53 | content=$(cat "$windowHTMLPath") 54 | if ! [[ $content == *"$frameScript"* ]]; then 55 | panic "window.html does not contains $frameScript" 56 | fi 57 | if [[ $content == *"$pluginScript"* ]]; then 58 | echo "plugin has already been installed" 59 | exit 60 | fi 61 | 62 | echo "[7/9] backup window.html" 63 | cp "$windowHTMLPath" "$windowHTMLBakPath" 64 | 65 | echo "[8/9] chmod plugin dir" 66 | chmod 0777 "$pluginDir" 67 | 68 | echo "[9/9] update window.html" 69 | escapedFrameScript=$(escape "$frameScript") 70 | escapedPluginScript=$(escape "$pluginScript") 71 | replacement="$escapedFrameScript$escapedPluginScript" 72 | newContent=$(echo -n "$content" | sed "s|$escapedFrameScript|$replacement|") 73 | echo "$newContent" >"$windowHTMLPath" 74 | echo "plugin install successfully" 75 | -------------------------------------------------------------------------------- /resources/plugin/bin/install_linux_amd_x64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/bin/install_linux_amd_x64 -------------------------------------------------------------------------------- /resources/plugin/bin/install_windows.ps1: -------------------------------------------------------------------------------- 1 | $rootDir = (Get-Location).Path | Split-Path -Parent | Split-Path -Parent 2 | $appPath = Join-Path -Path $rootDir -ChildPath "app" 3 | $appsrcPath = Join-Path -Path $rootDir -ChildPath "appsrc" 4 | $windowHTMLPath = Join-Path -Path $rootDir -ChildPath "window.html" 5 | $windowHTMLBakPath = Join-Path -Path $rootDir -ChildPath "window.html.bak" 6 | $pluginScript = "" 7 | $oldFrameScript = "" 8 | $newFrameScript = "" 9 | $frameScript = "" 10 | $banner = @" 11 | ____________________________________________________________________ 12 | ______ __ _ 13 | /_ __/_ ______ ____ _________ _ ____ / /_ ______ _(_)___ 14 | / / / / / / __ \/ __ \/ ___/ __ ``/ / __ \/ / / / / __ ``/ / __ \ 15 | / / / /_/ / /_/ / /_/ / / / /_/ / / /_/ / / /_/ / /_/ / / / / / 16 | /_/ \__, / .___/\____/_/ \__,_/ / .___/_/\__,_/\__, /_/_/ /_/ 17 | /____/_/ /_/ /____/ 18 | Designed by obgnail 19 | https://github.com/obgnail/typora_plugin 20 | ____________________________________________________________________ 21 | "@ 22 | 23 | function finish {[CmdletBinding()]param ($msg) Write-Host $msg; PAUSE; Exit} 24 | function panic {[CmdletBinding()]param ($msg) Write-Error $msg; PAUSE; Exit} 25 | 26 | Write-Host $banner 27 | Write-Host "" 28 | Write-Host "[1/5] check whether file window.html exists in $rootDir" 29 | if (!(Test-Path -Path $windowHTMLPath)) { 30 | panic "window.html does not exist in $rootDir" 31 | } 32 | 33 | Write-Host "[2/5] check whether folder app/appsrc exists in $rootDir" 34 | if (Test-Path -Path $appsrcPath) { 35 | $frameScript = $newFrameScript 36 | } elseif (Test-Path -Path $appPath) { 37 | $frameScript = $oldFrameScript 38 | } else { 39 | panic "appsrc/app does not exist in $rootDir" 40 | } 41 | 42 | $fileContent = Get-Content -Path $windowHTMLPath -Encoding UTF8 -Raw 43 | $replacement = -Join($frameScript, $pluginScript) 44 | 45 | Write-Host "[3/5] check window.html content" 46 | if (!$fileContent.Contains($frameScript)) { 47 | panic "window.html does not contains $frameScript" 48 | } 49 | if ($fileContent.Contains($pluginScript)) { 50 | finish "plugin has already been installed" 51 | } 52 | 53 | Write-Host "[4/5] backup window.html" 54 | Copy-Item -Path $windowHTMLPath -Destination $windowHTMLBakPath 55 | 56 | Write-Host "[5/5] update window.html" 57 | $newFileContent = $fileContent -Replace [Regex]::Escape($frameScript), $replacement 58 | Set-Content -Path $windowHTMLPath -Value $newFileContent -Encoding UTF8 59 | finish "plugin install successfully" 60 | -------------------------------------------------------------------------------- /resources/plugin/bin/install_windows_amd_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/bin/install_windows_amd_x64.exe -------------------------------------------------------------------------------- /resources/plugin/bin/uninstall_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rootDir=$(dirname "$(dirname "$PWD")") 4 | pluginDir="$rootDir/plugin" 5 | appPath="$rootDir/app" 6 | appsrcPath="$rootDir/appsrc" 7 | windowHTMLPath="$rootDir/window.html" 8 | windowHTMLBakPath="$rootDir/window.html.bak" 9 | pluginScript='' 10 | newFrameScript='' 11 | oldFrameScript='' 12 | frameScript="" 13 | 14 | panic() { 15 | echo -e "\033[0;31m ERROR: $1 \033[0m" 16 | exit 1 17 | } 18 | 19 | escape() { 20 | sed -E 's/[]\/$*.^|[]/\\&/g' <<<"$1" 21 | } 22 | 23 | echo "[1/8] check command" 24 | for cmd in echo cp cat sed; do 25 | command -v "$cmd" &>/dev/null || panic "cannot find command $cmd, please install it." 26 | done 27 | 28 | echo "[2/8] check sudo" 29 | if [ "$EUID" -ne 0 ]; then 30 | panic "please run this script as root." 31 | fi 32 | 33 | echo "[3/8] check plugin exists" 34 | if ! [ -d "$pluginDir" ]; then 35 | panic "dir plugin does not exist in $rootDir" 36 | fi 37 | 38 | echo "[4/8] check whether window.html exists" 39 | if ! [ -f "$windowHTMLPath" ]; then 40 | panic "window.html does not exist in $rootDir" 41 | fi 42 | 43 | echo "[5/8] check whether app/appsrc exists" 44 | if [ -d "$appsrcPath" ]; then 45 | frameScript=$newFrameScript 46 | elif [ -d "$appPath" ]; then 47 | frameScript=$oldFrameScript 48 | else 49 | panic "appsrc/app does not exist in $rootDir" 50 | fi 51 | 52 | echo "[6/8] check window.html content" 53 | content=$(cat "$windowHTMLPath") 54 | if ! [[ $content == *"$frameScript"* ]]; then 55 | panic "window.html does not contains $frameScript" 56 | fi 57 | if ! [[ $content == *"$pluginScript"* ]]; then 58 | echo "plugin has already been uninstalled" 59 | exit 60 | fi 61 | 62 | echo "[7/8] delete window.html.bak" 63 | rm -f "$windowHTMLBakPath" 64 | 65 | echo "[8/8] update window.html" 66 | escapedPluginScript=$(escape "$pluginScript") 67 | replacement="" 68 | newContent=$(echo -n "$content" | sed "s|$escapedPluginScript|$replacement|") 69 | echo "$newContent" >"$windowHTMLPath" 70 | echo "plugin uninstall successfully" 71 | -------------------------------------------------------------------------------- /resources/plugin/bin/uninstall_windows.ps1: -------------------------------------------------------------------------------- 1 | $rootDir = (Get-Location).Path | Split-Path -Parent | Split-Path -Parent 2 | $appPath = Join-Path -Path $rootDir -ChildPath "app" 3 | $appsrcPath = Join-Path -Path $rootDir -ChildPath "appsrc" 4 | $windowHTMLPath = Join-Path -Path $rootDir -ChildPath "window.html" 5 | $windowHTMLBakPath = Join-Path -Path $rootDir -ChildPath "window.html.bak" 6 | $pluginScript = "" 7 | $oldFrameScript = "" 8 | $newFrameScript = "" 9 | $frameScript = "" 10 | $banner = @" 11 | ____________________________________________________________________ 12 | ______ __ _ 13 | /_ __/_ ______ ____ _________ _ ____ / /_ ______ _(_)___ 14 | / / / / / / __ \/ __ \/ ___/ __ ``/ / __ \/ / / / / __ ``/ / __ \ 15 | / / / /_/ / /_/ / /_/ / / / /_/ / / /_/ / / /_/ / /_/ / / / / / 16 | /_/ \__, / .___/\____/_/ \__,_/ / .___/_/\__,_/\__, /_/_/ /_/ 17 | /____/_/ /_/ /____/ 18 | Designed by obgnail 19 | https://github.com/obgnail/typora_plugin 20 | ____________________________________________________________________ 21 | "@ 22 | 23 | function finish {[CmdletBinding()]param ($msg) Write-Host $msg; PAUSE; Exit} 24 | function panic {[CmdletBinding()]param ($msg) Write-Error $msg; PAUSE; Exit} 25 | 26 | Write-Host $banner 27 | Write-Host "" 28 | Write-Host "[1/5] check whether file window.html exists in $rootDir" 29 | if (!(Test-Path -Path $windowHTMLPath)) { 30 | panic "window.html does not exist in $rootDir" 31 | } 32 | 33 | Write-Host "[2/5] check whether folder app/appsrc exists in $rootDir" 34 | if (Test-Path -Path $appsrcPath) { 35 | $frameScript = $newFrameScript 36 | } elseif (Test-Path -Path $appPath) { 37 | $frameScript = $oldFrameScript 38 | } else { 39 | panic "appsrc/app does not exist in $rootDir" 40 | } 41 | 42 | $fileContent = Get-Content -Path $windowHTMLPath -Encoding UTF8 -Raw 43 | $replacement = "" 44 | 45 | Write-Host "[3/5] check window.html content" 46 | if (!$fileContent.Contains($frameScript)) { 47 | panic "window.html does not contains $frameScript" 48 | } 49 | if (!$fileContent.Contains($pluginScript)) { 50 | finish "plugin has already been uninstalled" 51 | } 52 | 53 | Write-Host "[4/5] delete window.html.bak" 54 | Remove-Item -Path $windowHTMLBakPath 55 | 56 | Write-Host "[5/5] update window.html" 57 | $newFileContent = $fileContent -Replace [Regex]::Escape($pluginScript), $replacement 58 | Set-Content -Path $windowHTMLPath -Value $newFileContent -Encoding UTF8 59 | finish "plugin uninstall successfully" 60 | -------------------------------------------------------------------------------- /resources/plugin/bin/version.json: -------------------------------------------------------------------------------- 1 | {"url":"https://api.github.com/repos/obgnail/typora_plugin/releases/207074079","assets_url":"https://api.github.com/repos/obgnail/typora_plugin/releases/207074079/assets","upload_url":"https://uploads.github.com/repos/obgnail/typora_plugin/releases/207074079/assets{?name,label}","html_url":"https://github.com/obgnail/typora_plugin/releases/tag/1.13.6","id":207074079,"author":{"login":"github-actions[bot]","id":41898282,"node_id":"MDM6Qm90NDE4OTgyODI=","avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4","gravatar_id":"","url":"https://api.github.com/users/github-actions%5Bbot%5D","html_url":"https://github.com/apps/github-actions","followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers","following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos","events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"node_id":"RE_kwDOJzwCYc4MV7Mf","tag_name":"1.13.6","target_commitish":"master","name":"1.13.6","draft":false,"prerelease":false,"created_at":"2025-03-20T05:45:30Z","published_at":"2025-03-20T06:36:12Z","assets":[{"url":"https://api.github.com/repos/obgnail/typora_plugin/releases/assets/239157071","id":239157071,"node_id":"RA_kwDOJzwCYc4OQT9P","name":"typora-plugin@v1.13.6.zip","label":"","uploader":{"login":"github-actions[bot]","id":41898282,"node_id":"MDM6Qm90NDE4OTgyODI=","avatar_url":"https://avatars.githubusercontent.com/in/15368?v=4","gravatar_id":"","url":"https://api.github.com/users/github-actions%5Bbot%5D","html_url":"https://github.com/apps/github-actions","followers_url":"https://api.github.com/users/github-actions%5Bbot%5D/followers","following_url":"https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github-actions%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/github-actions%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/github-actions%5Bbot%5D/repos","events_url":"https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/github-actions%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"content_type":"application/zip","state":"uploaded","size":3472825,"download_count":200,"created_at":"2025-03-20T06:36:12Z","updated_at":"2025-03-20T06:36:12Z","browser_download_url":"https://github.com/obgnail/typora_plugin/releases/download/1.13.6/typora-plugin%40v1.13.6.zip"}],"tarball_url":"https://api.github.com/repos/obgnail/typora_plugin/tarball/1.13.6","zipball_url":"https://api.github.com/repos/obgnail/typora_plugin/zipball/1.13.6","body":"1. 【feat】【markdownLint】支持自定义检测规则\r\n2. 【feat】【markdownLint】新增自定义的检测规则 MD101:MathBlocks must be surrounded by blank lines(#957)\r\n3. 【feat】【slash_commands】支持获取命令的文字环境(#961)\r\n4. 【feat】【slash_commands】command 类型支持使用 cursorOffset 参数\r\n5. 【chg】【slash_commands】优化代码\r\n\r\n\r\n"} 2 | -------------------------------------------------------------------------------- /resources/plugin/blur.js: -------------------------------------------------------------------------------- 1 | class blurPlugin extends BasePlugin { 2 | beforeProcess = () => { 3 | if (!this.utils.supportHasSelector) { 4 | return this.utils.stopLoadPluginError 5 | } 6 | } 7 | 8 | hotkey = () => [{ hotkey: this.config.HOTKEY, callback: this.call }] 9 | 10 | init = () => { 11 | this.blurType = { BLUR: "blur", HIDE: "hide" }; 12 | this.css_id = "plugin-blur-style"; 13 | this.inBlur = this.config.BLUR_DEFAULT; 14 | } 15 | 16 | process = () => this.run(false); 17 | 18 | call = () => { 19 | this.inBlur = !this.inBlur; 20 | this.run(); 21 | } 22 | 23 | getStyleText = () => { 24 | const selector = "#write > [cid]:not(.md-focus):not(:has(.md-focus)):not(:has(.md-focus-container))"; 25 | const [effect, restore] = (this.config.BLUR_TYPE === this.blurType.HIDE) 26 | ? ["visibility: hidden;", "visibility: visible;"] 27 | : [`filter: blur(${this.config.BLUR_LEVEL}px);`, "filter: initial;"] 28 | 29 | let css = `${selector} { ${effect} }`; 30 | if (this.config.RESRTORE_WHEN_HOVER) { 31 | css += `${selector}:hover { ${restore} }`; 32 | } 33 | return css 34 | } 35 | 36 | run = (showNotification = true) => { 37 | if (this.inBlur) { 38 | this.utils.insertStyle(this.css_id, this.getStyleText()) 39 | } else { 40 | this.utils.removeStyle(this.css_id) 41 | } 42 | if (showNotification) { 43 | const msg = this.i18n.t(this.inBlur ? "modeEnabled" : "modeDisabled") 44 | this.utils.notification.show(msg) 45 | } 46 | } 47 | } 48 | 49 | module.exports = { 50 | plugin: blurPlugin, 51 | } 52 | -------------------------------------------------------------------------------- /resources/plugin/cipher/index.js: -------------------------------------------------------------------------------- 1 | class cipherPlugin extends BasePlugin { 2 | hotkey = () => [ 3 | { hotkey: this.config.ENCRYPT_HOTKEY, callback: () => this.call("encrypt") }, 4 | { hotkey: this.config.DECRYPT_HOTKEY, callback: () => this.call("decrypt") }, 5 | ] 6 | 7 | init = () => { 8 | this.AES_ECB = null 9 | // To prevent decryption failures, users are restricted from modifying the hard-coded secret key 10 | this.key = "n0hLis5FjgQxa3f31sSa2wm37J81g3upTlq9it9WlfK" 11 | this.showMessageBox = this.config.SHOW_HINT_MODAL 12 | this.staticActions = this.i18n.fillActions([ 13 | { act_value: "encrypt", act_hotkey: this.config.ENCRYPT_HOTKEY }, 14 | { act_value: "decrypt", act_hotkey: this.config.DECRYPT_HOTKEY }, 15 | ]) 16 | } 17 | 18 | call = async action => { 19 | const func = this[action] 20 | if (func) { 21 | await this.utils.editCurrentFile(func) 22 | } 23 | } 24 | 25 | encrypt = async raw => { 26 | const { encrypt } = this.lazyLoad() 27 | const isCiphered = this.utils.isBase64(raw) 28 | if (!this.showMessageBox && !isCiphered) { 29 | return encrypt(raw, this.key) 30 | } 31 | 32 | const title = this.i18n.t("msgBox.encrypt.title") 33 | const message = this.i18n.t(isCiphered ? "msgBox.encrypt.onCiphered" : "msgBox.encrypt.onPlain") 34 | const checkboxLabel = this.i18n._t("global", "disableReminder") 35 | const op = { type: "info", title, message, checkboxLabel } 36 | const { response, checkboxChecked } = await this.utils.showMessageBox(op) 37 | if (checkboxChecked) { 38 | this.showMessageBox = false 39 | } 40 | if (response === 0) { 41 | return isCiphered ? raw : encrypt(raw, this.key) 42 | } else if (response === 1) { 43 | return raw 44 | } 45 | } 46 | 47 | decrypt = async ciphered => { 48 | const { decrypt } = this.lazyLoad() 49 | const isCiphered = this.utils.isBase64(ciphered) 50 | if (isCiphered) { 51 | return decrypt(ciphered, this.key) 52 | } 53 | const title = this.i18n.t("msgBox.decrypt.title") 54 | const message = this.i18n.t("msgBox.decrypt.onPlain") 55 | const confirm = this.i18n._t("global", "confirm") 56 | const op = { type: "info", title, message, buttons: [confirm] } 57 | await this.utils.showMessageBox(op) 58 | return ciphered 59 | } 60 | 61 | lazyLoad = () => { 62 | this.AES_ECB = this.AES_ECB || require("./aes_ecb.min.js") 63 | return { encrypt: this.AES_ECB.AES_ECB_ENCRYPT, decrypt: this.AES_ECB.AES_ECB_DECRYPT } 64 | } 65 | } 66 | 67 | module.exports = { 68 | plugin: cipherPlugin, 69 | } 70 | -------------------------------------------------------------------------------- /resources/plugin/collapse_list.js: -------------------------------------------------------------------------------- 1 | /** To improve performance, the logic is moved to CSS, so this plugin uses a very hacky implementation approach. 2 | * 1. Use the pseudo-class ::before as the click button 3 | * 2. ::before uses the left style to float out of the parent element's BoundingClientRect 4 | * 3. When the parent element detects a click, check the mouse position. If the mouse position is outside the parent element's Rect, then determine that the pseudo-class has been clicked 5 | */ 6 | class collapseListPlugin extends BasePlugin { 7 | beforeProcess = () => { 8 | this.className = "plugin-collapsed-list"; 9 | this.selector = '#write [mdtype="list"]'; 10 | const color = this.config.TRIANGLE_COLOR || "var(--meta-content-color)"; 11 | this.triangleStyle = { left: -18, top: 0, height: 9, halfWidth: 7, color: color }; 12 | } 13 | 14 | styleTemplate = () => true 15 | 16 | process = () => { 17 | this.utils.runtime.autoSaveConfig(this); 18 | this.recordCollapseState(false); 19 | this.utils.entities.eWrite.addEventListener("click", ev => { 20 | const parent = ev.target.closest(this.selector); 21 | if (!parent) return; 22 | 23 | const { clientX, clientY } = ev; 24 | const { left: PLeft, top: PTop } = parent.getBoundingClientRect(); 25 | const { left: TLeft, top: TTop, height: THeight, halfWidth: THalfWidth } = this.triangleStyle; 26 | 27 | const left = PLeft + TLeft; 28 | const top = PTop + TTop; 29 | const height = THeight; 30 | const width = 2 * THalfWidth; 31 | if ( 32 | left - width <= clientX 33 | && clientX <= left + width 34 | && top - height <= clientY 35 | && clientY <= top + height 36 | ) { 37 | ev.stopPropagation(); 38 | ev.preventDefault(); 39 | this.toggleCollapse(parent); 40 | } 41 | }) 42 | } 43 | 44 | checkCollapse = ele => ele.classList.contains(this.className); 45 | setCollapse = ele => ele.classList.add(this.className); 46 | cancelCollapse = ele => ele.classList.remove(this.className); 47 | toggleCollapse = ele => ele.classList.toggle(this.className); 48 | 49 | recordCollapseState = (needChange = true) => { 50 | const name = "recordCollapseList"; 51 | if (needChange) { 52 | this.config.RECORD_COLLAPSE = !this.config.RECORD_COLLAPSE; 53 | } 54 | if (this.config.RECORD_COLLAPSE) { 55 | this.utils.stateRecorder.register(name, this.selector, this.checkCollapse, this.setCollapse) 56 | } else { 57 | this.utils.stateRecorder.unregister(name); 58 | } 59 | } 60 | 61 | rollback = start => { 62 | let cur = start; 63 | while (true) { 64 | cur = cur.closest(`.${this.className}`); 65 | if (!cur) return; 66 | this.cancelCollapse(cur); 67 | cur = cur.parentElement; 68 | } 69 | } 70 | 71 | getDynamicActions = () => this.i18n.fillActions([ 72 | { act_value: "record_collapse_state", act_state: this.config.RECORD_COLLAPSE } 73 | ]) 74 | 75 | call = action => { 76 | if (action === "record_collapse_state") { 77 | this.recordCollapseState(true); 78 | } 79 | } 80 | } 81 | 82 | module.exports = { 83 | plugin: collapseListPlugin 84 | } 85 | -------------------------------------------------------------------------------- /resources/plugin/collapse_table.js: -------------------------------------------------------------------------------- 1 | class collapseTablePlugin extends BasePlugin { 2 | styleTemplate = () => true 3 | 4 | init = () => { 5 | this.className = "plugin-collapse-table"; 6 | } 7 | 8 | process = () => { 9 | this.utils.runtime.autoSaveConfig(this); 10 | this.recordCollapseState(false); 11 | 12 | this.utils.decorate(() => File && File.editor && File.editor.tableEdit, "showTableEdit", null, (result, ...args) => { 13 | const $figure = args[0] 14 | if (!$figure || $figure.length === 0 || !$figure.find) return 15 | const $edit = $figure.find(".md-table-edit") 16 | if (!$edit || $edit.length === 0) return 17 | 18 | const icon = $figure.hasClass(this.className) ? "fa fa-plus" : "fa fa-minus" 19 | const span = `
', 15 | setStyleFunc: parser.STYLE_SETTER_SIMPLE({ 16 | height: this.config.DEFAULT_FENCE_HEIGHT, 17 | "background-color": this.config.DEFAULT_FENCE_BACKGROUND_COLOR 18 | }), 19 | lazyLoadFunc: this.lazyLoad, 20 | createFunc: this.create, 21 | updateFunc: null, 22 | destroyFunc: null, 23 | beforeExportToNative: null, 24 | beforeExportToHTML: null, 25 | extraStyleGetter: null, 26 | versionGetter: this.versionGetter, 27 | }) 28 | } 29 | 30 | create = ($wrap, content) => { 31 | const options = { ...this.config.VISUAL_OPTIONS } // set prototype 32 | this.ABCJS.renderAbc($wrap[0], content, options) 33 | } 34 | 35 | versionGetter = () => this.ABCJS && this.ABCJS.signature 36 | 37 | lazyLoad = () => this.ABCJS = require("./abcjs-basic-min.js") 38 | } 39 | 40 | module.exports = { 41 | plugin: abcPlugin 42 | } -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/blockSideBySide.js: -------------------------------------------------------------------------------- 1 | class blockSideBySidePlugin extends BaseCustomPlugin { 2 | hotkey = () => [this.config.hotkey] 3 | 4 | styleTemplate = () => true 5 | 6 | callback = async anchorNode => { 7 | const enable = this.utils.styleTemplater.getStyleContent(this.fixedName); 8 | const func = enable ? "unregister" : "register"; 9 | await this.utils.styleTemplater[func](this.fixedName); 10 | } 11 | } 12 | 13 | module.exports = { 14 | plugin: blockSideBySidePlugin, 15 | }; -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/calendar/index.js: -------------------------------------------------------------------------------- 1 | class calendarPlugin extends BaseCustomPlugin { 2 | init = () => this.Calendar = null 3 | 4 | callback = anchorNode => this.utils.insertText(anchorNode, this.config.TEMPLATE) 5 | 6 | process = () => { 7 | const parser = this.utils.thirdPartyDiagramParser 8 | parser.register({ 9 | lang: this.config.LANGUAGE, 10 | mappingLang: "javascript", 11 | destroyWhenUpdate: false, 12 | interactiveMode: this.config.INTERACTIVE_MODE, 13 | checkSelector: ".plugin-calender-content", 14 | wrapElement: '', 15 | setStyleFunc: parser.STYLE_SETTER({ 16 | height: this.config.DEFAULT_FENCE_HEIGHT, 17 | "background-color": this.config.DEFAULT_FENCE_BACKGROUND_COLOR 18 | }), 19 | lazyLoadFunc: this.lazyLoad, 20 | createFunc: this.create, 21 | updateFunc: null, 22 | destroyFunc: this.destroy, 23 | beforeExportToNative: null, 24 | beforeExportToHTML: null, 25 | extraStyleGetter: null, 26 | versionGetter: this.versionGetter, 27 | }) 28 | } 29 | 30 | create = ($wrap, content) => { 31 | const Calendar = this.Calendar 32 | const calendar = new this.Calendar($wrap[0]) 33 | let option = {} 34 | eval(content) 35 | calendar.setOptions(option) 36 | return calendar 37 | } 38 | 39 | destroy = instance => { 40 | instance.clear() 41 | instance.destroy() 42 | } 43 | 44 | versionGetter = () => "2.1.3" 45 | 46 | lazyLoad = () => { 47 | this.utils.insertStyleFile("plugin-calendar-style", "./plugin/custom/plugins/calendar/toastui-calendar.min.css") 48 | const { Calendar } = require("./toastui-calendar.min.js") 49 | this.Calendar = Calendar 50 | } 51 | } 52 | 53 | module.exports = { 54 | plugin: calendarPlugin 55 | } -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/chart/index.js: -------------------------------------------------------------------------------- 1 | class chartPlugin extends BaseCustomPlugin { 2 | init = () => this.ChartPkg = null 3 | 4 | callback = anchorNode => this.utils.insertText(anchorNode, this.config.TEMPLATE) 5 | 6 | process = () => { 7 | const parser = this.utils.thirdPartyDiagramParser 8 | parser.register({ 9 | lang: this.config.LANGUAGE, 10 | mappingLang: "javascript", 11 | destroyWhenUpdate: false, 12 | interactiveMode: this.config.INTERACTIVE_MODE, 13 | checkSelector: ".plugin-chart-content", 14 | wrapElement: '', 15 | setStyleFunc: parser.STYLE_SETTER({ 16 | height: this.config.DEFAULT_FENCE_HEIGHT, 17 | "background-color": this.config.DEFAULT_FENCE_BACKGROUND_COLOR 18 | }), 19 | lazyLoadFunc: this.lazyLoad, 20 | createFunc: this.create, 21 | updateFunc: null, 22 | destroyFunc: this.destroy, 23 | beforeExportToNative: null, 24 | beforeExportToHTML: this.beforeExportToHTML, 25 | extraStyleGetter: null, 26 | versionGetter: this.versionGetter, 27 | }) 28 | } 29 | 30 | create = ($wrap, content) => { 31 | const $canvas = $wrap.find("canvas") 32 | if ($canvas.length) { 33 | const ctx = $canvas[0].getContext("2d") 34 | return this.drawChart(ctx, content) 35 | } 36 | } 37 | 38 | destroy = instance => { 39 | instance.clear() 40 | instance.destroy() 41 | } 42 | 43 | drawChart = (ctx, content) => { 44 | let config = {} 45 | const Chart = this.ChartPkg.Chart 46 | eval(content) 47 | return new Chart(ctx, config) 48 | } 49 | 50 | beforeExportToHTML = (preview, instance) => { 51 | const img = new Image() 52 | img.src = instance.toBase64Image() 53 | $(preview).html(img) 54 | } 55 | 56 | versionGetter = () => this.ChartPkg && this.ChartPkg.version 57 | 58 | lazyLoad = () => this.ChartPkg = require("./chart.min.js") 59 | } 60 | 61 | module.exports = { 62 | plugin: chartPlugin 63 | } -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/echarts/index.js: -------------------------------------------------------------------------------- 1 | class echartsPlugin extends BaseCustomPlugin { 2 | init = () => { 3 | this.echartsPkg = null 4 | this.exportType = this.config.EXPORT_TYPE.toLowerCase() 5 | } 6 | 7 | callback = anchorNode => this.utils.insertText(anchorNode, this.config.TEMPLATE) 8 | 9 | process = () => { 10 | const parser = this.utils.thirdPartyDiagramParser 11 | parser.register({ 12 | lang: this.config.LANGUAGE, 13 | mappingLang: "javascript", 14 | destroyWhenUpdate: false, 15 | interactiveMode: this.config.INTERACTIVE_MODE, 16 | checkSelector: ".plugin-echarts-content", 17 | wrapElement: '', 18 | setStyleFunc: parser.STYLE_SETTER({ 19 | height: this.config.DEFAULT_FENCE_HEIGHT, 20 | "background-color": this.config.DEFAULT_FENCE_BACKGROUND_COLOR 21 | }), 22 | lazyLoadFunc: this.lazyLoad, 23 | createFunc: this.create, 24 | updateFunc: null, 25 | destroyFunc: this.destroy, 26 | beforeExportToNative: null, 27 | beforeExportToHTML: this.beforeExportToHTML, 28 | extraStyleGetter: null, 29 | versionGetter: this.versionGetter, 30 | }) 31 | } 32 | 33 | create = ($wrap, content) => { 34 | const myChart = this.echartsPkg.init($wrap[0], null, { renderer: this.config.RENDERER }) 35 | this.drawChart(myChart, content) 36 | return myChart 37 | } 38 | 39 | drawChart = (myChart, content, resize = false) => { 40 | // chart.showLoading() 41 | let echarts = this.echartsPkg 42 | let option = {} 43 | eval(content) 44 | myChart.clear() 45 | myChart.setOption(option) 46 | if (resize) { 47 | myChart.resize() 48 | } 49 | // chart.hideLoading() 50 | } 51 | 52 | destroy = instance => { 53 | instance.clear() 54 | instance.dispose() 55 | } 56 | 57 | beforeExportToHTML = (preview, instance) => { 58 | instance.setOption({ animation: false }) 59 | if (this.exportType === "png" || this.exportType === "jpg") { 60 | const img = new Image() 61 | img.src = instance.getDataURL({ type: this.exportType }) 62 | $(preview).html(img) 63 | } else if (this.exportType === "svg") { 64 | const svg = instance.renderToSVGString() 65 | $(preview).html(svg) 66 | } 67 | } 68 | 69 | versionGetter = () => this.echartsPkg && this.echartsPkg.version 70 | 71 | lazyLoad = () => this.echartsPkg = require("./echarts.min.js") 72 | } 73 | 74 | module.exports = { 75 | plugin: echartsPlugin 76 | } -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/markdownLint/linter-worker.js: -------------------------------------------------------------------------------- 1 | let LIB 2 | let RULES 3 | let CUSTOM_RULES 4 | 5 | const linter = { 6 | init: ({ libPath, customRulePaths, config, content }) => { 7 | LIB = require(libPath) 8 | RULES = { "default": true, ...config } 9 | CUSTOM_RULES = customRulePaths.map(e => require(e)) 10 | console.debug(`markdownlint@${LIB.getVersion()} worker is initialized with rules`, RULES) 11 | if (content) { 12 | return linter.check({ content }) 13 | } 14 | }, 15 | check: async ({ content }) => { 16 | const op = { strings: { content }, config: RULES, customRules: CUSTOM_RULES } 17 | const result = await LIB.lint(op) 18 | return result.content.sort((a, b) => a.lineNumber - b.lineNumber) 19 | }, 20 | fix: async ({ content, fixInfo }) => { 21 | if (fixInfo && fixInfo.length) { 22 | return LIB.applyFixes(content, fixInfo) 23 | } 24 | }, 25 | } 26 | 27 | onmessage = async ({ data: { action, payload } }) => { 28 | if (!payload) return 29 | 30 | const fn = linter[action] 31 | if (!fn) { 32 | console.error("get error action:", action) 33 | return 34 | } 35 | const result = await fn(payload) 36 | if (result) { 37 | postMessage({ action, result }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/marp/index.js: -------------------------------------------------------------------------------- 1 | class marpPlugin extends BaseCustomPlugin { 2 | styleTemplate = () => true 3 | 4 | init = () => this.marpPkg = null 5 | 6 | callback = anchorNode => this.utils.insertText(anchorNode, this.config.TEMPLATE) 7 | 8 | process = () => { 9 | const parser = this.utils.thirdPartyDiagramParser 10 | parser.register({ 11 | lang: this.config.LANGUAGE, 12 | mappingLang: "markdown", 13 | destroyWhenUpdate: false, 14 | interactiveMode: this.config.INTERACTIVE_MODE, 15 | checkSelector: ".plugin-marp-content", 16 | wrapElement: '', 17 | setStyleFunc: null, 18 | lazyLoadFunc: this.lazyLoad, 19 | createFunc: this.create, 20 | updateFunc: null, 21 | destroyFunc: this.destroy, 22 | beforeExportToNative: null, 23 | beforeExportToHTML: null, 24 | extraStyleGetter: null, 25 | versionGetter: this.versionGetter, 26 | }) 27 | } 28 | 29 | create = ($wrap, content) => { 30 | const { Marp, marp } = this.marpPkg // more detail: https://github.com/marp-team/marp-core 31 | const shadowRoot = $wrap[0].shadowRoot || $wrap[0].attachShadow({ mode: "open" }) // use shadowDOM to isolate styles 32 | const { html, css } = marp.render(content) 33 | shadowRoot.innerHTML = `` + html 34 | return shadowRoot 35 | } 36 | 37 | destroy = shadowRoot => shadowRoot.innerHTML = "" 38 | 39 | versionGetter = () => "marp-core@3.9.0" 40 | 41 | lazyLoad = () => this.marpPkg = require("./marp.min.js") 42 | } 43 | 44 | module.exports = { 45 | plugin: marpPlugin 46 | } -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/redirectLocalRootUrl.js: -------------------------------------------------------------------------------- 1 | class redirectLocalRootUrlPlugin extends BaseCustomPlugin { 2 | beforeProcess = () => { 3 | if (!this.config.root) { 4 | return this.utils.stopLoadPluginError 5 | } 6 | } 7 | 8 | init = () => { 9 | const { filter_regexp } = this.config 10 | this.filter = filter_regexp ? new RegExp(filter_regexp) : undefined 11 | } 12 | 13 | needRedirect = (filepath = this.utils.getFilePath()) => { 14 | return this.filter ? this.filter.test(filepath) : true 15 | } 16 | 17 | process = () => { 18 | const redirect = typoraRootUrl => { 19 | const dontRedirect = typoraRootUrl || !this.needRedirect() 20 | return dontRedirect 21 | ? typoraRootUrl 22 | : this.utils.Package.Path.resolve(this.utils.getCurrentDirPath(), this.config.root) 23 | } 24 | this.utils.decorate(() => File && File.editor && File.editor.docMenu, "getLocalRootUrl", null, redirect, true) 25 | } 26 | } 27 | 28 | module.exports = { 29 | plugin: redirectLocalRootUrlPlugin 30 | } 31 | -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/reopenClosedFiles/index.js: -------------------------------------------------------------------------------- 1 | class reopenClosedFilesPlugin extends BaseCustomPlugin { 2 | init = () => { 3 | this.windowTabBarPlugin = null; 4 | this.saveFile = this.utils.joinPath("./plugin/custom/plugins/reopenClosedFiles/remain.json"); 5 | } 6 | 7 | hotkey = () => [this.config.hotkey] 8 | 9 | process = () => { 10 | this.utils.eventHub.addEventListener(this.utils.eventHub.eventType.allPluginsHadInjected, async () => { 11 | this.windowTabBarPlugin = this.utils.getPlugin("window_tab"); 12 | if (!this.windowTabBarPlugin) return; 13 | await this.ensureFile(); 14 | if (this.config.auto_reopen_when_init) { 15 | // Redirection is disabled when opening specific files (isDiscardableUntitled === false). 16 | this.utils.loopDetector(this.utils.isDiscardableUntitled, this.callback, 40, 2000, false); 17 | } 18 | setTimeout(() => this.utils.eventHub.addEventListener(this.utils.eventHub.eventType.fileContentLoaded, this.save), 2500); 19 | }) 20 | } 21 | 22 | save = async () => this.windowTabBarPlugin && this.windowTabBarPlugin.saveTabs(this.saveFile); 23 | 24 | ensureFile = async () => await this.utils.Package.FsExtra.ensureFile(this.saveFile); 25 | 26 | callback = anchorNode => this.windowTabBarPlugin && this.windowTabBarPlugin.openSaveTabs(this.saveFile, true); 27 | } 28 | 29 | module.exports = { 30 | plugin: reopenClosedFilesPlugin, 31 | } 32 | -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/reopenClosedFiles/remain.json: -------------------------------------------------------------------------------- 1 | { 2 | "save_tabs": [ 3 | { 4 | "idx": 0, 5 | "path": "E:\\book\\dev\\flask.md", 6 | "active": false, 7 | "scrollTop": 7500 8 | }, 9 | { 10 | "idx": 1, 11 | "path": "E:\\book\\dev\\gitbook.md", 12 | "active": true, 13 | "scrollTop": 5422 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/scrollBookmarker/bookmark.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /resources/plugin/dark.js: -------------------------------------------------------------------------------- 1 | class darkModePlugin extends BasePlugin { 2 | styleTemplate = () => true 3 | 4 | init = () => { 5 | this.class = "plugin-dark" 6 | this.isDarkMode = this.config.DARK_DEFAULT 7 | } 8 | 9 | hotkey = () => [this.config.HOTKEY] 10 | 11 | enableDarkMode = () => this._toggleDarkMode(true) 12 | 13 | disableDarkMode = () => this._toggleDarkMode(false) 14 | 15 | toggleDarkMode = () => { 16 | this._toggleDarkMode(!this.isDarkMode) 17 | const msg = this.i18n.t(this.isDarkMode ? "modeEnabled" : "modeDisabled") 18 | this.utils.notification.show(msg) 19 | } 20 | 21 | _toggleDarkMode = enable => { 22 | document.documentElement.classList.toggle(this.class, enable) 23 | this.isDarkMode = enable 24 | } 25 | 26 | process = () => this.isDarkMode && this.enableDarkMode() 27 | 28 | call = (action, meta) => this.toggleDarkMode() 29 | } 30 | 31 | module.exports = { 32 | plugin: darkModePlugin, 33 | } 34 | -------------------------------------------------------------------------------- /resources/plugin/datatables/resource/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/datatables/resource/sort_asc.png -------------------------------------------------------------------------------- /resources/plugin/datatables/resource/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/datatables/resource/sort_asc_disabled.png -------------------------------------------------------------------------------- /resources/plugin/datatables/resource/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/datatables/resource/sort_both.png -------------------------------------------------------------------------------- /resources/plugin/datatables/resource/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/datatables/resource/sort_desc.png -------------------------------------------------------------------------------- /resources/plugin/datatables/resource/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyz349925756/Typora-Mid/b0abe7d21c9b900642044ed86e67dc470f1c957e/resources/plugin/datatables/resource/sort_desc_disabled.png -------------------------------------------------------------------------------- /resources/plugin/editor_width_slider.js: -------------------------------------------------------------------------------- 1 | class editorWidthSliderPlugin extends BasePlugin { 2 | process = async () => { 3 | this.utils.runtime.autoSaveConfig(this); 4 | await this._setWidth(); 5 | } 6 | 7 | _setWidth = async (width = this.config.WIDTH_RATIO) => { 8 | const { eWrite } = this.utils.entities; 9 | if (width < 0) { 10 | eWrite.style.removeProperty("max-width"); 11 | } else { 12 | eWrite.style.setProperty("max-width", `${width}%`, "important"); 13 | } 14 | } 15 | 16 | setWidth = async () => { 17 | const labelWidth = this.i18n.t("widthProportion") 18 | const labelRecover = this.i18n.t("recover") 19 | 20 | const { eContent, eWrite } = this.utils.entities 21 | const value = parseInt(eWrite.offsetWidth * 100 / eContent.offsetWidth) 22 | const oninput = ev => this._setWidth(ev.target.value) 23 | 24 | const components = [ 25 | { label: labelWidth, type: "range", min: 30, max: 100, step: 1, value, oninput }, 26 | { label: "", type: "checkbox", list: [{ label: labelRecover, value: "recover" }] }, 27 | ] 28 | const op = { title: this.pluginName, components } 29 | const { response, submit: [width, [checkbox]] } = await this.utils.dialog.modalAsync(op) 30 | if (response === 1) { 31 | this.config.WIDTH_RATIO = checkbox === "recover" ? -1 : width 32 | } 33 | await this._setWidth() 34 | } 35 | 36 | call = async (action, meta) => await this.setWidth() 37 | } 38 | 39 | module.exports = { 40 | plugin: editorWidthSliderPlugin, 41 | } 42 | -------------------------------------------------------------------------------- /resources/plugin/fence_enhance/resource/comment-fold.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/5/LICENSE 3 | 4 | (function(mod) { 5 | mod(CodeMirror); 6 | })(function(CodeMirror) { 7 | "use strict"; 8 | 9 | CodeMirror.registerGlobalHelper("fold", "comment", function(mode) { 10 | return mode.blockCommentStart && mode.blockCommentEnd; 11 | }, function(cm, start) { 12 | var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd; 13 | if (!startToken || !endToken) return; 14 | var line = start.line, lineText = cm.getLine(line); 15 | 16 | var startCh; 17 | for (var at = start.ch, pass = 0;;) { 18 | var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1); 19 | if (found == -1) { 20 | if (pass == 1) return; 21 | pass = 1; 22 | at = lineText.length; 23 | continue; 24 | } 25 | if (pass == 1 && found < start.ch) return; 26 | if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) && 27 | (found == 0 || lineText.slice(found - endToken.length, found) == endToken || 28 | !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) { 29 | startCh = found + startToken.length; 30 | break; 31 | } 32 | at = found - 1; 33 | } 34 | 35 | var depth = 1, lastLine = cm.lastLine(), end, endCh; 36 | outer: for (var i = line; i <= lastLine; ++i) { 37 | var text = cm.getLine(i), pos = i == line ? startCh : 0; 38 | for (;;) { 39 | var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos); 40 | if (nextOpen < 0) nextOpen = text.length; 41 | if (nextClose < 0) nextClose = text.length; 42 | pos = Math.min(nextOpen, nextClose); 43 | if (pos == text.length) break; 44 | if (pos == nextOpen) ++depth; 45 | else if (!--depth) { end = i; endCh = pos; break outer; } 46 | ++pos; 47 | } 48 | } 49 | if (end == null || line == end && endCh == startCh) return; 50 | return {from: CodeMirror.Pos(line, startCh), 51 | to: CodeMirror.Pos(end, endCh)}; 52 | }); 53 | 54 | }); -------------------------------------------------------------------------------- /resources/plugin/fence_enhance/resource/foldgutter.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-foldmarker { 2 | color: blue; 3 | text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; 4 | font-family: arial; 5 | line-height: .3; 6 | cursor: pointer; 7 | } 8 | .CodeMirror-foldgutter { 9 | width: .7em; 10 | } 11 | .CodeMirror-foldgutter-open, 12 | .CodeMirror-foldgutter-folded { 13 | cursor: pointer; 14 | } 15 | .CodeMirror-foldgutter-open:after { 16 | content: "\25BE"; 17 | } 18 | .CodeMirror-foldgutter-folded:after { 19 | content: "\25B8"; 20 | } 21 | -------------------------------------------------------------------------------- /resources/plugin/fence_enhance/resource/indent-fold.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/5/LICENSE 3 | 4 | (function(mod) { 5 | mod(CodeMirror); 6 | })(function(CodeMirror) { 7 | "use strict"; 8 | 9 | function lineIndent(cm, lineNo) { 10 | var text = cm.getLine(lineNo) 11 | var spaceTo = text.search(/\S/) 12 | if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1)))) 13 | return -1 14 | return CodeMirror.countColumn(text, null, cm.getOption("tabSize")) 15 | } 16 | 17 | CodeMirror.registerHelper("fold", "indent", function(cm, start) { 18 | var myIndent = lineIndent(cm, start.line) 19 | if (myIndent < 0) return 20 | var lastLineInFold = null 21 | 22 | // Go through lines until we find a line that definitely doesn't belong in 23 | // the block we're folding, or to the end. 24 | for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) { 25 | var indent = lineIndent(cm, i) 26 | if (indent == -1) { 27 | } else if (indent > myIndent) { 28 | // Lines with a greater indent are considered part of the block. 29 | lastLineInFold = i; 30 | } else { 31 | // If this line has non-space, non-comment content, and is 32 | // indented less or equal to the start line, it is the start of 33 | // another block. 34 | break; 35 | } 36 | } 37 | if (lastLineInFold) return { 38 | from: CodeMirror.Pos(start.line, cm.getLine(start.line).length), 39 | to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length) 40 | }; 41 | }); 42 | 43 | }); -------------------------------------------------------------------------------- /resources/plugin/fence_enhance/resource/markdown-fold.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/5/LICENSE 3 | 4 | (function(mod) { 5 | mod(CodeMirror); 6 | })(function(CodeMirror) { 7 | "use strict"; 8 | 9 | CodeMirror.registerHelper("fold", "markdown", function(cm, start) { 10 | var maxDepth = 100; 11 | 12 | function isHeader(lineNo) { 13 | var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)); 14 | return tokentype && /\bheader\b/.test(tokentype); 15 | } 16 | 17 | function headerLevel(lineNo, line, nextLine) { 18 | var match = line && line.match(/^#+/); 19 | if (match && isHeader(lineNo)) return match[0].length; 20 | match = nextLine && nextLine.match(/^[=\-]+\s*$/); 21 | if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2; 22 | return maxDepth; 23 | } 24 | 25 | var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1); 26 | var level = headerLevel(start.line, firstLine, nextLine); 27 | if (level === maxDepth) return undefined; 28 | 29 | var lastLineNo = cm.lastLine(); 30 | var end = start.line, nextNextLine = cm.getLine(end + 2); 31 | while (end < lastLineNo) { 32 | if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break; 33 | ++end; 34 | nextLine = nextNextLine; 35 | nextNextLine = cm.getLine(end + 2); 36 | } 37 | 38 | return { 39 | from: CodeMirror.Pos(start.line, firstLine.length), 40 | to: CodeMirror.Pos(end, cm.getLine(end).length) 41 | }; 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /resources/plugin/global/core/i18n.js: -------------------------------------------------------------------------------- 1 | const getUserLocale = (lang) => { 2 | if (lang === "auto") { 3 | lang = window._options.userLocale 4 | } 5 | switch (lang) { 6 | case "zh-CN": 7 | case "zh-Hans": 8 | return "zh-CN" 9 | case "zh-TW": 10 | case "zh-Hant": 11 | return "zh-TW" 12 | // case "ko": 13 | // case "ko-KR": 14 | // return "ko" 15 | // case "ja": 16 | // case "ja-JP": 17 | // return "ja" 18 | case "Base": 19 | case "en-US": 20 | case "en-BG": 21 | case "en": 22 | default: 23 | return "en" 24 | } 25 | } 26 | 27 | const i18n = { 28 | locale: "", 29 | data: {}, 30 | init: async function (locale) { 31 | try { 32 | locale = getUserLocale(locale) 33 | const path = require("path") 34 | const file = path.join(path.dirname(__dirname), "locales", `${locale}.json`) 35 | const json = await require("fs").promises.readFile(file, "utf8") 36 | this.data = JSON.parse(json) 37 | this.locale = locale 38 | } catch (error) { 39 | console.error("Could not load translations:", error) 40 | } 41 | }, 42 | t: function (field, key, variables) { 43 | const field_ = i18n.data[field] 44 | if (field_ === undefined) { 45 | return key 46 | } 47 | let text = field_[key] 48 | if (text === undefined) { 49 | return key 50 | } 51 | if (variables) { 52 | for (const [k, v] of Object.entries(variables)) { 53 | const placeholder = new RegExp(`{{${k}}}`, "g") 54 | text = text.replace(placeholder, v) 55 | } 56 | } 57 | return text 58 | }, 59 | link: function (parts) { 60 | return parts.join(i18n.locale.startsWith("zh") ? "" : " ") 61 | }, 62 | array: function (field, keys, prefix = "") { 63 | return keys.map(k => i18n.t(field, prefix + k)) 64 | }, 65 | entries: function (field, keys, prefix = "") { 66 | return Object.fromEntries(keys.map(k => [k, i18n.t(field, prefix + k)])) 67 | }, 68 | bind: function (field) { 69 | return { 70 | data: i18n.data[field], 71 | link: i18n.link, 72 | _t: i18n.t, 73 | t: (key, variables) => i18n.t(field, key, variables), 74 | array: (keys, prefix) => i18n.array(field, keys, prefix), 75 | entries: (keys, prefix) => i18n.entries(field, keys, prefix), 76 | fillActions: (actions) => { 77 | for (const act of actions) { 78 | if (!act.act_name && act.act_value) { 79 | act.act_name = i18n.t(field, `act.${act.act_value}`) 80 | } 81 | } 82 | return actions 83 | }, 84 | } 85 | } 86 | } 87 | 88 | module.exports = { 89 | i18n 90 | } 91 | -------------------------------------------------------------------------------- /resources/plugin/global/core/index.js: -------------------------------------------------------------------------------- 1 | const { i18n } = require("./i18n") 2 | const { utils, hook } = require("./utils") 3 | const { BasePlugin, BaseCustomPlugin, LoadPlugins } = require("./plugin") 4 | 5 | async function entry() { 6 | /** 7 | * Initializes global variables. 8 | * The plugin system exposes a total of 8 global variables, but only 3 are actually useful: BasePlugin, BaseCustomPlugin, and LoadPlugins. 9 | * The remaining 4 are exposed by the static class `utils` and should never be referenced by business plugins. 10 | * Furthermore, `utils` is also an instance property of BasePlugin and BaseCustomPlugin, so `utils` itself doesn't need to be exposed. 11 | * If they will never be referenced by business plugins, why are they set as global variables? Answer: For debugging convenience. 12 | **/ 13 | const initVariable = settings => { 14 | global.BasePlugin = BasePlugin 15 | global.BaseCustomPlugin = BaseCustomPlugin 16 | global.LoadPlugins = LoadPlugins 17 | 18 | global.__plugins__ = null 19 | global.__plugin_utils__ = utils 20 | global.__plugin_i18n__ = i18n 21 | global.__plugin_settings__ = settings 22 | global.__global_settings__ = settings.global 23 | 24 | delete settings.global 25 | } 26 | 27 | const initI18N = (locale) => i18n.init(locale) 28 | 29 | const loadPlugins = async () => { 30 | const { enable, disable, stop, error, nosetting } = await LoadPlugins(global.__plugin_settings__, false) 31 | global.__plugins__ = enable 32 | } 33 | 34 | /** 35 | * For Typora versions below 0.9.98, a compatibility warning is issued when running the plugin system. 36 | */ 37 | const showWarn = () => { 38 | const need = global.__global_settings__.SHOW_INCOMPATIBLE_WARNING 39 | const incompatible = utils.compareVersion(utils.typoraVersion, "0.9.98") < 0 40 | if (need && incompatible) { 41 | const msg = i18n.t("global", "incompatibilityWarning") 42 | utils.notification.show(msg, "warning", 5000) 43 | } 44 | } 45 | 46 | const launch = async () => { 47 | const settings = await utils.runtime.readBasePluginSetting() 48 | const enable = settings && settings.global && settings.global.ENABLE 49 | if (!enable) { 50 | console.warn("disable typora plugin") 51 | return 52 | } 53 | 54 | await initI18N(settings.global.LOCALE) 55 | initVariable(settings) 56 | await hook(loadPlugins) 57 | showWarn() 58 | } 59 | 60 | await launch() 61 | } 62 | 63 | module.exports = { 64 | entry 65 | } 66 | -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/common/toml/index.js: -------------------------------------------------------------------------------- 1 | var parser = require('./parser'); 2 | var compiler = require('./compiler'); 3 | var stringify = require('./stringify'); 4 | 5 | module.exports = { 6 | parse: function(input) { 7 | var nodes = parser.parse(input.toString()); 8 | return compiler.compile(nodes); 9 | }, 10 | stringify, 11 | }; 12 | -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/env.js: -------------------------------------------------------------------------------- 1 | const getHook = utils => { 2 | const { i18n } = require("../i18n") 3 | const MIXIN = require("./mixin") 4 | const mixin = Object.fromEntries(Object.entries(MIXIN).map(([name, cls]) => [[name], new cls(utils, i18n)])) 5 | 6 | const { 7 | hotkeyHub, eventHub, stateRecorder, exportHelper, contextMenu, 8 | notification, progressBar, dialog, diagramParser, thirdPartyDiagramParser, 9 | extra, polyfill, 10 | } = mixin 11 | 12 | // monkey patch 13 | // we should use composition to layer various functions, but utils is outdated and has become legacy code. My apologies 14 | Object.assign(utils, mixin, { 15 | /** @deprecated new API: utils.hotkeyHub.register */ 16 | registerHotkey: hotkeyHub.register, 17 | /** @deprecated new API: utils.hotkeyHub.registerSingle */ 18 | registerSingleHotkey: hotkeyHub.registerSingle, 19 | 20 | /** @deprecated new API: utils.eventHub.eventType */ 21 | eventType: eventHub.eventType, 22 | /** @deprecated new API: utils.eventHub.addEventListener */ 23 | addEventListener: eventHub.addEventListener, 24 | 25 | /** @deprecated new API: utils.dialog.modal */ 26 | modal: dialog.modal 27 | }) 28 | 29 | const registerMixin = (...ele) => Promise.all(ele.map(h => h.process && h.process())) 30 | const optimizeMixin = () => Promise.all(Object.values(mixin).map(h => h.afterProcess && h.afterProcess())) 31 | 32 | const registerPreMixin = async () => { 33 | await registerMixin(polyfill) 34 | await registerMixin(extra) 35 | await registerMixin(contextMenu, notification, progressBar, dialog, stateRecorder, hotkeyHub, exportHelper) 36 | } 37 | 38 | const registerPostMixin = async () => { 39 | await registerMixin(eventHub) 40 | await registerMixin(diagramParser, thirdPartyDiagramParser) 41 | eventHub.publishEvent(eventHub.eventType.allPluginsHadInjected) 42 | } 43 | 44 | // Due to the use of async, some events may have been missed (such as afterAddCodeBlock), reload it 45 | const postLoadPlugin = () => { 46 | if (File.getMountFolder() != null) { 47 | setTimeout(utils.reload, 50) 48 | } 49 | } 50 | 51 | return async pluginLoader => { 52 | await registerPreMixin() 53 | await pluginLoader() 54 | await registerPostMixin() 55 | await optimizeMixin() 56 | postLoadPlugin() 57 | } 58 | } 59 | 60 | module.exports = { 61 | getHook 62 | } 63 | -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/mixin/entities.js: -------------------------------------------------------------------------------- 1 | class entities { 2 | constructor(utils) { 3 | this.utils = utils; 4 | this.eWrite = document.querySelector("#write"); 5 | this.eContent = document.querySelector("content"); 6 | this.$eWrite = $(this.eWrite); 7 | this.$eContent = $(this.eContent); 8 | 9 | this.querySelectorAllInWrite = (...args) => this.eWrite.querySelectorAll(...args); 10 | this.querySelectorInWrite = (...args) => this.eWrite.querySelector(...args); 11 | } 12 | } 13 | 14 | module.exports = { 15 | entities 16 | } 17 | -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/mixin/extra.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Register additional HTML and CSS. 3 | */ 4 | class extra { 5 | constructor(utils) { 6 | this.utils = utils; 7 | } 8 | 9 | registerCSS = async () => { 10 | const files = ["plugin-common", "customize"]; 11 | return Promise.all(files.map(f => this.utils.styleTemplater.register(f))); 12 | } 13 | 14 | registerHTML = () => { 15 | this.utils.insertElement(` 16 | ` 24 | $edit.append($(span)) 25 | }) 26 | 27 | this.utils.entities.eWrite.addEventListener("click", ev => { 28 | const btn = ev.target.closest(".plugin-collapse-table-btn"); 29 | if (!btn) return; 30 | const figure = btn.closest("figure"); 31 | if (!figure) return; 32 | this.toggleTable(figure); 33 | }) 34 | } 35 | 36 | call = (action, meta) => { 37 | if (action === "convert_current") { 38 | this.toggleTable(meta.target); 39 | } else if (action === "record_collapse_state") { 40 | this.recordCollapseState(true); 41 | } 42 | } 43 | 44 | getDynamicActions = (anchorNode, meta) => { 45 | const figure = anchorNode.closest("#write .table-figure") 46 | const act_hint = !figure ? this.i18n.t("actHint.convert_current") : "" 47 | meta.target = figure 48 | return this.i18n.fillActions([ 49 | { act_value: "convert_current", act_hint, act_disabled: !figure }, 50 | { act_value: "record_collapse_state", act_state: this.config.RECORD_COLLAPSE } 51 | ]) 52 | } 53 | 54 | toggleTable = figure => { 55 | const table = figure.querySelector("table"); 56 | if (!table) return; 57 | figure.classList.toggle(this.className); 58 | const btn = figure.querySelector(".plugin-collapse-table-btn"); 59 | if (btn) { 60 | const span = btn.querySelector("span"); 61 | span.classList.toggle("fa-plus"); 62 | span.classList.toggle("fa-minus"); 63 | } 64 | } 65 | 66 | rollback = start => { 67 | let cur = start; 68 | while (true) { 69 | cur = cur.closest(`.${this.className}`); 70 | if (!cur) return; 71 | this.toggleTable(cur); 72 | cur = cur.parentElement; 73 | } 74 | } 75 | 76 | checkCollapse = figure => figure.classList.contains(this.className); 77 | 78 | recordCollapseState = (needChange = true) => { 79 | const name = "recordCollapseTable"; 80 | const selector = "#write .table-figure"; 81 | if (needChange) { 82 | this.config.RECORD_COLLAPSE = !this.config.RECORD_COLLAPSE; 83 | } 84 | if (this.config.RECORD_COLLAPSE) { 85 | this.utils.stateRecorder.register(name, selector, this.checkCollapse, this.toggleTable); 86 | } else { 87 | this.utils.stateRecorder.unregister(name); 88 | } 89 | } 90 | } 91 | 92 | module.exports = { 93 | plugin: collapseTablePlugin 94 | } 95 | -------------------------------------------------------------------------------- /resources/plugin/custom/plugins/abc/index.js: -------------------------------------------------------------------------------- 1 | class abcPlugin extends BaseCustomPlugin { 2 | init = () => this.ABCJS = null 3 | 4 | callback = anchorNode => this.utils.insertText(anchorNode, this.config.TEMPLATE) 5 | 6 | process = () => { 7 | const parser = this.utils.thirdPartyDiagramParser 8 | parser.register({ 9 | lang: this.config.LANGUAGE, 10 | mappingLang: this.config.LANGUAGE, 11 | destroyWhenUpdate: false, 12 | interactiveMode: this.config.INTERACTIVE_MODE, 13 | checkSelector: ".plugin-notation-content", 14 | wrapElement: ' 17 | 18 | Processing 19 | 20 | 21 | 22 | `); 23 | } 24 | 25 | process = async () => { 26 | await this.registerCSS(); 27 | this.registerHTML(); 28 | } 29 | } 30 | 31 | module.exports = { 32 | extra 33 | } 34 | -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/mixin/hotkeyHub.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dynamically register and unregister hotkeys. 3 | */ 4 | class hotkeyHub { 5 | constructor(utils) { 6 | this.utils = utils; 7 | this.map = new Map(); 8 | } 9 | 10 | normalize = hotkeyString => { 11 | const modifier = ["ctrl", "shift", "alt"]; 12 | const keyList = hotkeyString.toLowerCase().split("+").map(k => k.trim()); 13 | const modifierKeys = modifier.filter(k => keyList.includes(k)); 14 | const mainKey = keyList.find(k => !modifier.includes(k)) || (hotkeyString.includes("++") ? "+" : " "); 15 | return [...modifierKeys, mainKey].join("+"); 16 | } 17 | 18 | _register = (hotkey, call) => { 19 | if (typeof hotkey === "string" && hotkey.length) { 20 | this.map.set(this.normalize(hotkey), call); 21 | // A callback may correspond to multiple hotkeys. 22 | } else if (hotkey instanceof Array) { 23 | for (const hk of hotkey) { 24 | this._register(hk, call); 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * Does not validate the legality of hotkeyString. The caller needs to ensure that the hotkey is not occupied and has no typos. 31 | * @param {[{string, function}]} hotkeyList: [ { hotkey: "ctrl+shift+c", callback: () => console.log("ctrl+shift+c pressed") }, ] 32 | */ 33 | register = hotkeyList => { 34 | if (!hotkeyList) return; 35 | for (const item of hotkeyList) { 36 | if (item instanceof Array) { 37 | this.register(item); 38 | } else { 39 | this._register(item.hotkey, item.callback); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * @param {string} hotkeyString: "ctrl+shift+c" 46 | */ 47 | unregister = hotkeyString => this.map.delete(this.normalize(hotkeyString)) 48 | 49 | registerSingle = (hotkeyString, callback) => this._register(hotkeyString, callback) 50 | 51 | process = () => { 52 | window.addEventListener("keydown", ev => { 53 | if (ev.key === undefined) return; 54 | const arr = []; 55 | this.utils.metaKeyPressed(ev) && arr.push("ctrl"); 56 | this.utils.shiftKeyPressed(ev) && arr.push("shift"); 57 | this.utils.altKeyPressed(ev) && arr.push("alt"); 58 | arr.push(ev.key.toLowerCase()); 59 | const key = arr.join("+"); 60 | const callback = this.map.get(key); 61 | if (callback) { 62 | callback(); 63 | ev.preventDefault(); 64 | ev.stopPropagation(); 65 | } 66 | }, true) 67 | } 68 | } 69 | 70 | module.exports = { 71 | hotkeyHub 72 | } 73 | -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/mixin/htmlTemplater.js: -------------------------------------------------------------------------------- 1 | /** 2 | * insert html tag 3 | * 3x faster then innerHTML, less memory usage, more secure, but poor readable 4 | * don't use unless element is simple enough or there are secure issues 5 | */ 6 | class htmlTemplater { 7 | constructor(utils) { 8 | this.utils = utils; 9 | this.defaultElement = "div"; 10 | } 11 | 12 | create = template => { 13 | if (!template) return; 14 | if (template instanceof Element) return template 15 | 16 | const el = document.createElement(template.ele || this.defaultElement); 17 | this.setAttributes(el, template); 18 | return el 19 | } 20 | 21 | setAttributes(el, obj) { 22 | for (const [prop, value] of Object.entries(obj)) { 23 | if (value == null) continue; 24 | switch (prop) { 25 | case "ele": 26 | break; 27 | case "class": 28 | case "className": 29 | case "class_": 30 | el.classList.add(...(Array.isArray(value) ? value : value.trim().split(/\s+/g))); 31 | break; 32 | case "text": 33 | el.innerText = value; 34 | break; 35 | case "style": 36 | Object.assign(el.style, value); 37 | break; 38 | case "children": 39 | this.appendElements(el, value); 40 | break; 41 | default: 42 | el.setAttribute(prop, value); 43 | } 44 | } 45 | } 46 | 47 | createList = templates => templates.map(this.create).filter(Boolean) 48 | insert = templates => this.utils.insertElement(this.createList(templates)) 49 | appendElements = (parent, templates) => parent.append(...this.createList(templates)) 50 | } 51 | 52 | module.exports = { 53 | htmlTemplater 54 | } -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/mixin/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("./polyfill"), 3 | ...require("./runtime"), 4 | ...require("./migrate"), 5 | ...require("./hotkeyHub"), 6 | ...require("./eventHub"), 7 | ...require("./stateRecorder"), 8 | ...require("./exportHelper"), 9 | ...require("./styleTemplater"), 10 | ...require("./htmlTemplater"), 11 | ...require("./contextMenu"), 12 | ...require("./notification"), 13 | ...require("./progressBar"), 14 | ...require("./dialog"), 15 | ...require("./diagramParser"), 16 | ...require("./thirdPartyDiagramParser"), 17 | ...require("./entities"), 18 | ...require("./extra"), 19 | ...require("./searchStringParser"), 20 | } -------------------------------------------------------------------------------- /resources/plugin/global/core/utils/mixin/notification.js: -------------------------------------------------------------------------------- 1 | class notification { 2 | constructor(utils) { 3 | this.utils = utils; 4 | this.timer = null; 5 | this.types = { 6 | success: { bgColor: "#e6ffed", iconColor: "#009688", icon: "fa fa-check" }, 7 | info: { bgColor: "#e6f7ff", iconColor: "#448aff", icon: "fa fa-info-circle" }, 8 | warning: { bgColor: "#fffbe6", iconColor: "#f57c00", icon: "fa fa-warning" }, 9 | error: { bgColor: "#ffe6e6", iconColor: "#d32f2f", icon: "fa fa-bug" }, 10 | } 11 | } 12 | 13 | process = async () => { 14 | await this.utils.styleTemplater.register("plugin-common-notification"); 15 | this.utils.insertElement(` 16 |