├── .gitignore ├── LICENSE ├── docs ├── develop.md ├── faqs.md ├── install.md └── use.md ├── ext ├── built-in │ ├── gpt │ │ ├── config.json │ │ ├── gpt.js │ │ ├── gptType.js │ │ ├── index.js │ │ └── random.js │ ├── terminal │ │ └── index.js │ └── textweb │ │ ├── build.sh │ │ ├── init.sh │ │ ├── src │ │ ├── ext │ │ │ ├── wiki.tweb │ │ │ │ ├── ext.json │ │ │ │ └── index.js │ │ │ └── www.baidu.com │ │ │ │ ├── cookie.txt │ │ │ │ ├── ext.json │ │ │ │ └── index.js │ │ ├── index.js │ │ ├── libEncode.js │ │ ├── libMd.js │ │ ├── libPic.js │ │ ├── libRandom.js │ │ ├── package.json │ │ ├── picFail.png │ │ ├── sharedLib.js │ │ └── twextType.js │ │ └── third │ │ └── html-to-md │ │ ├── .babelrc │ │ ├── .editorconfig │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── .prettierrc.js │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README-EN.md │ │ ├── README.md │ │ ├── __test__ │ │ ├── error-boundary │ │ │ └── error-test.test.js │ │ ├── module │ │ │ ├── module.test.js │ │ │ ├── module2.test.js │ │ │ ├── module3.test.js │ │ │ ├── module4.test.js │ │ │ ├── module5.test.js │ │ │ └── module6.test.js │ │ ├── options.js │ │ ├── unit │ │ │ ├── __empty__.test.js │ │ │ ├── __ignore__.test.js │ │ │ ├── __nomatch__.test.js │ │ │ ├── __skip__.test.js │ │ │ ├── a.test.js │ │ │ ├── b.test.js │ │ │ ├── blockquote.test.js │ │ │ ├── code.test.js │ │ │ ├── config.test.js │ │ │ ├── customTag.test.js │ │ │ ├── del.test.js │ │ │ ├── em.test.js │ │ │ ├── empty.test.js │ │ │ ├── h1.test.js │ │ │ ├── h2.test.js │ │ │ ├── h3.test.js │ │ │ ├── h4.test.js │ │ │ ├── h5.test.js │ │ │ ├── h6.test.js │ │ │ ├── hr.test.js │ │ │ ├── i.test.js │ │ │ ├── img.test.js │ │ │ ├── input.test.js │ │ │ ├── li.test.js │ │ │ ├── ol.test.js │ │ │ ├── p.test.js │ │ │ ├── pre.test.js │ │ │ ├── s.test.js │ │ │ ├── space.test.js │ │ │ ├── span.test.js │ │ │ ├── strong.test.js │ │ │ ├── table.test.js │ │ │ └── ul.test.js │ │ └── utils │ │ │ ├── alias-tags.test.js │ │ │ ├── clearComment.test.js │ │ │ ├── generateGetNextValidTag.test.js │ │ │ ├── getLanguage.test.js │ │ │ └── tagListener.test.js │ │ ├── index.html │ │ ├── package.json │ │ ├── prepublish.js │ │ ├── src │ │ ├── AFT.ts │ │ ├── SelfCloseTag.ts │ │ ├── Tag.ts │ │ ├── config.ts │ │ ├── index.ts │ │ ├── tags │ │ │ ├── __Heading__.ts │ │ │ ├── __empty__.ts │ │ │ ├── __ignore__.ts │ │ │ ├── __nomatch__.ts │ │ │ ├── __rawString__.ts │ │ │ ├── __skip__.ts │ │ │ ├── a.ts │ │ │ ├── b.ts │ │ │ ├── blockquote.ts │ │ │ ├── br.ts │ │ │ ├── code.ts │ │ │ ├── del.ts │ │ │ ├── em.ts │ │ │ ├── h1.ts │ │ │ ├── h2.ts │ │ │ ├── h3.ts │ │ │ ├── h4.ts │ │ │ ├── h5.ts │ │ │ ├── h6.ts │ │ │ ├── hr.ts │ │ │ ├── i.ts │ │ │ ├── img.ts │ │ │ ├── input.ts │ │ │ ├── li.ts │ │ │ ├── ol.ts │ │ │ ├── p.ts │ │ │ ├── pre.ts │ │ │ ├── s.ts │ │ │ ├── span.ts │ │ │ ├── strong.ts │ │ │ ├── table.ts │ │ │ ├── tbody.ts │ │ │ ├── td.ts │ │ │ ├── th.ts │ │ │ ├── thead.ts │ │ │ ├── tr.ts │ │ │ └── ul.ts │ │ ├── type.ts │ │ └── utils │ │ │ ├── CONSTANT.ts │ │ │ ├── clearComment.ts │ │ │ ├── escape.ts │ │ │ ├── generateGetNextValidTag.ts │ │ │ ├── getLanguage.ts │ │ │ ├── getRealTagName.ts │ │ │ ├── getTableAlign.ts │ │ │ ├── getTagAttributes.ts │ │ │ ├── getTagConstructor.ts │ │ │ ├── index.ts │ │ │ ├── isIndependentTag.ts │ │ │ ├── isSelfClosing.ts │ │ │ ├── isValidHTMLTags.ts │ │ │ ├── shouldRenderRawInside.ts │ │ │ └── trim.ts │ │ ├── tsconfig.json │ │ ├── webpack.config.js │ │ └── webpack.demo.config.js └── example │ ├── async │ └── index.js │ └── mockingBird │ └── index.js ├── package.json ├── readme.md ├── script ├── build.full.sh ├── build.js ├── build.main.sh ├── init.js └── init.sh ├── src ├── bing.ts ├── bingServer.ts ├── extType.ts ├── extmain.ts ├── fallbackServer.ts ├── logServer.ts ├── main.ts └── random.ts ├── tsconfig.json └── util ├── bing-url.json ├── install.chroot.sh ├── install.native.sh ├── node.chroot ├── node.native ├── uninstall.chroot.sh ├── uninstall.native.sh ├── youdaoExt.sh.chroot ├── youdaoExt.sh.inchroot └── youdaoExt.sh.native /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules 3 | package-lock.json 4 | !package-lock.json/ 5 | .git/ 6 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Wxp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/develop.md: -------------------------------------------------------------------------------- 1 | # 扩展程序开发手册 2 | ## 注意 3 | - 要开发扩展程序,你需要有一定的`javascript`基础;要能较好地阅读本文档,你需要对`typescript`的语法略知一二。 4 | - 扩展程序请使用`ECMAScript module`的javascript写法。 5 | - 扩展程序主机最后只接受`javascript`脚本,使用`typescript`编写的扩展程序需要通过编译才能运行。 6 | - 建议在响应`init`事件时输出扩展程序的当前版本号以便用户日后手动检查更新。 7 | ## 目录结构 8 | 一个最小的扩展程序应该有如下的目录结构。 9 | ``` 10 | ┌[rawName] 11 | ├--index.js 12 | ``` 13 | 即在一个以扩展程序原始名称(上面的`[rawName]`)命名文件夹内需有文件`index.js`。 14 | 安装该扩展程序即只需将上述文件夹放入`/userdisk/youdaoExt/ext`中即可。 15 | ## 扩展程序名称 16 | 扩展程序有两个名称: 原始名称(rawName)和显示名称(applicationName)。 17 | - 请用原始名称来命名用来装扩展程序脚本的文件夹,详见上文`目录结构`。 18 | - 请在`index.js`中导出常量`applicationName`作为扩展程序的显示名称。如未导出,扩展程序主机将取原始名称作为显示名称。 19 | ## 扩展程序工作目录 20 | 请注意,由于`nodejs`中对`worker_thread`的限制(毕竟是一个线程,而不是一个独立的进程),扩展程序的工作目录并非上述以原始名称命名的文件夹,而是装有扩展程序主机代码的文件夹。 21 | 如果你使用安装脚本安装的: 如果你直接安装,该目录应该为`/userdisk/youdaoExt`;如果你在chroot容器中安装,该目录应该为`/youdaoExt`。 22 | 如要使用与扩展程序处于同一目录下的文件,请参考以下例子。 23 | ```js 24 | import * as fs from "node:fs"; 25 | const basePath = process.env['basePath']; 26 | 27 | // 导入模块 28 | 29 | import * as test from "./test.js"; // 正确 30 | import * as test from `${basePath}/test.js`; // 错误 31 | 32 | var test = await import("./test.js"); // 错误 33 | var test = await import(`${basePath}/test.js`); // 正确 34 | 35 | // 访问文件 36 | 37 | var buff = fs.readFileSync("./1.txt"); // 错误 38 | var buff = fs.readFildSync(`${basePath}/1.txt`); // 正确 39 | 40 | ``` 41 | ## 事件 42 | 该项目采用的事件处理方式是**事件驱动型**而非~~事件循环型~~。 43 | 要处理和响应某个事件,只需在上文目录结构中所述的`index.js`文件中导出以事件命名的函数(同步和异步均接受)作为事件处理和响应函数即可。 44 | 下列为扩展程序的事件列表。 45 | - 事件`init`: 在初始化并切换扩展程序到前台时触发。 46 | - 事件`switchOut`: 在扩展程序被切入后台时触发。 47 | - 事件`switchIn`: 在扩展程序被切入前台时触发。 48 | - 事件`exit`: 在用户下达退出扩展程序的命令时触发。 49 | - 事件`input`: 在用户输入内容且该扩展程序在前台时触发,用于响应用户输入。 50 | 事件详细细节(传入参数,声明等)见下文`代码参考`。 51 | ## 代码参考 52 | ### 类型: session 53 | 以下为其typescript声明。 54 | ```ts 55 | interface session{ 56 | cid: string, 57 | createAt:{ 58 | timestamp: string, 59 | path: string, 60 | tone: 'moderate' | 'creative' | 'precise' 61 | }, 62 | path: string, 63 | timestamp: string, 64 | maxNum: number, 65 | nowNum: number 66 | } 67 | ``` 68 | 解释如下。 69 | - `cid`: 创建`signature`时返回的`conversationId`。 70 | - `createAt.timestamp`: 创建`signature`的时间。 71 | - `createAt.path`: 创建`signature`时的http请求路径。 72 | - `createAt.tone`: 创建`signature`时选择的对话风格。 73 | - `path`: 连接`chathub`时的http请求路径。 74 | - `timestamp`: 连接`chathub`的时间。 75 | - `maxNum`: 消息框右下角的最大对话次数。 76 | - `nowNum`: 消息框右下角的当前对话次数。 77 | ### 类型: msg 78 | 以下为其typescript声明。 79 | ```ts 80 | interface msg{ 81 | text: string, 82 | timestamp: string 83 | } 84 | ``` 85 | 解释如下。 86 | - `text`: 用户输入内容。 87 | - `timestamp`: 消息由`penmods`发出的时间。 88 | ### 事件: input 89 | 以下为其导出函数的typescript声明。 90 | ```ts 91 | function input( 92 | session:session, 93 | msg:msg, 94 | update:( 95 | text:string, 96 | nowNum:number, 97 | maxNum:number, 98 | suggestedResponse:string[] 99 | )=>void, 100 | done:()=>void, 101 | isAlive:()=>Promise, 102 | hold:()=>void 103 | ):void 104 | ``` 105 | 以下为解释。 106 | - `session`: 此时的会话对象。 107 | - `msg`: 用户输入数据。 108 | - `update`: 更新消息框中的内容。 109 | - `text`: 要更新到消息框的内容,请使用`markdown`语法。 110 | - `suggestedResponse`: 要更新到参考候选的内容。 111 | - `maxNum`: 要更新到消息框右下角的最大对话次数。 112 | - `nowNum`: 要更新到消息框右下角的当前对话次数。 113 | - `done`: 结束响应消息,此后调用`update`无效。 114 | - `isAlive`: 返回`chathub`连接状态,`true`为保持连接,`false`为已断开。 115 | - `hold`: 调用后,在事件处理和响应函数返回后不自动结束响应消息。 116 | ### 事件: switchOut 117 | 以下为其导出函数的typescript声明。 118 | ```ts 119 | function switchOut( 120 | session:session 121 | ):void 122 | ``` 123 | 解释如下。 124 | - `session`: 此时的会话对象。 125 | ### 事件: init 126 | 以下为其导出函数的typescript声明。 127 | ```ts 128 | function init( 129 | session:session, 130 | cb:( 131 | text:string, 132 | suggestedResponse:string[], 133 | nowNum:number, 134 | maxNum:number 135 | )=>void 136 | ):void 137 | ``` 138 | 以下是解释。 139 | - `session`: 此时的会话对象。 140 | - `cb`: 更新消息框中的内容。 141 | - `text`: 要更新到消息框的内容,请使用`markdown`语法。 142 | - `suggestedResponse`: 要更新到参考候选的内容。 143 | - `maxNum`: 要更新到消息框右下角的最大对话次数。 144 | - `nowNum`: 要更新到消息框右下角的当前对话次数。 145 | ### 事件: switchIn 146 | 以下为其导出函数的typescript声明。 147 | ```ts 148 | function switchIn( 149 | session:session, 150 | cb:( 151 | text:string, 152 | suggestedResponse:string[], 153 | nowNum:number, 154 | maxNum:number 155 | )=>void 156 | ):void 157 | ``` 158 | 以下是解释。 159 | - `session`: 此时的会话对象。 160 | - `cb`: 更新消息框中的内容。 161 | - `text`: 要更新到消息框的内容,请使用`markdown`语法。 162 | - `suggestedResponse`: 要更新到参考候选的内容。 163 | - `maxNum`: 要更新到消息框右下角的最大对话次数。 164 | - `nowNum`: 要更新到消息框右下角的当前对话次数。 165 | ### 事件: exit 166 | 以下为其导出函数的typescript声明。 167 | ```ts 168 | function exit( 169 | session:session, 170 | cb:( 171 | text:string, 172 | suggestedResponse:string[], 173 | nowNum:number, 174 | maxNum:number 175 | )=>void 176 | ):void 177 | ``` 178 | 以下是解释。 179 | - `session`: 此时的会话对象。 180 | - `cb`: 更新消息框中的内容。 181 | - `text`: 要更新到消息框的内容,请使用`markdown`语法。 182 | - `suggestedResponse`: 要更新到参考候选的内容。 183 | - `maxNum`: 要更新到消息框右下角的最大对话次数。 184 | - `nowNum`: 要更新到消息框右下角的当前对话次数。 185 | ## 环境变量 186 | 在扩展程序中可以使用`process.env`来访问环境变量。 187 | 比如。 188 | ```js 189 | // 打印词典笔mtp服务器下Music文件夹的绝对位置 190 | console.log(process.env['mtpPath']); 191 | ``` 192 | ### mtpPath 193 | 如果你需要使用词典笔的`/userdisk/Music`文件夹,请使用此环境变量来代替该路径,因为扩展程序主机可能运行在`chroot容器`中。 194 | 例如。 195 | ```js 196 | import * as fs from "node:fs"; 197 | 198 | export init(session,cb){ 199 | // 读取词典笔看来的 /userdisk/Music/1.txt 200 | var buff = fs.readFileSync(`${process.env['mtpPath']}/1.txt`); 201 | cb(`Readed file \`1.txt\` from ydp. 202 | \`\`\` 203 | ${buff.toString()} 204 | \`\`\` 205 | `,[],0,1); 206 | 207 | } 208 | ``` 209 | ### ydpSysRootPath 210 | 如果你需要使用词典笔的`/`目录访问`system_a`或`system_b`中的内容,请使用此环境变量来代替`/`,因为扩展程序主机可能运行在`chroot容器`中。 211 | 例如。 212 | ```js 213 | import * as fs from "node:fs"; 214 | 215 | export init(session,cb){ 216 | // 读取词典笔看来的 /1.txt 217 | const ydpSysRootPath = process.env['ydpSysRootPath']; 218 | var buff = fs.readFileSync(`${ydpSysRootPath}/1.txt`); 219 | cb(`Readed file \`1.txt\` from ydp. 220 | \`\`\` 221 | ${buff.toString()} 222 | \`\`\` 223 | `,[],0,1); 224 | 225 | } 226 | ``` 227 | ### basePath 228 | 如果你需要使用与扩展程序同目录的文件,请使用此环境变量代替`./`,并参考上文`扩展程序工作目录`。 -------------------------------------------------------------------------------- /docs/faqs.md: -------------------------------------------------------------------------------- 1 | # FAQs 2 | ## A: nodejs本体硕大,ydp可用内存并不富裕,是否会影响ydp性能? 3 | Q: 影响不是很大。当使用扩展程序的时候并不会使用原生功能,反之在你使用原生功能时并不会使用扩展程序,在这种并不频繁地切换中,Linux会在你切换两种状态时自行调节以保证系统流畅(清理内存、页面换入swap、页面从swap换出)。 -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # 安装文档 2 | ## How to install 3 | ### Step0 4 | 安装`Penmods`。 5 | 确保你所使用的`Penmods`的版本>=`1.2.0`。 6 | ### Step1 7 | 从`release`处下载最新的发行版。 8 | 解压所得的所有内容复制到词典笔`/userdisk/`处。 9 | ### Step2 10 | 现在有两种安装模式: 11 | 1) 装入chroot容器中:使用高版本的nodejs(目前是v18.13.0,后续可升级),更多特性,支持所有扩展程序,但是容器可能占用一部分存储空间。 12 | 2) 直接安装:节省空间,使用固定版本的nodejs单文件版(v17.3.0),支持绝大多数扩展程序,但不能运行使用高版本nodejs特性的扩展程序。 13 | #### chroot容器 14 | 开始之前,请确保你的chroot容器安装在`/userdisk/chroot`。 15 | 执行如下命令。 16 | ``` 17 | cd /userdisk/ 18 | chmod +x ./install.chroot.sh 19 | ./install.chroot.sh 20 | ``` 21 | #### 直接安装 22 | 执行如下命令。 23 | ``` 24 | cd /userdisk/ 25 | chmod +x ./install.native.sh 26 | ./install.native.sh 27 | ``` 28 | ### Step3 29 | 现在来测试安装是否成功。 30 | 在shell中执行`youdaoExt`命令,等待20秒左右(因为启动脚本中有20秒延时防止随系统启动时抢占系统资源影响`penmods`注入),看到类似如下输出视为成功。 31 | ``` 32 | [version] youdaoExtBingServer version 1.0.0 . 33 | [copyright] this project is developed by Wxp, MIT License Copyright (c) 2023 Wxp 34 | [repo] you can find this project at "https://github.com/DSFdsfWxp/ydpExt". 35 | [platform] running on "arm64","linux". 36 | [nodejs] current nodejs version is "v18.13.0". 37 | [path] current working path is "/youdaoExt". 38 | [env] current ydpSysRootPath is "/proc/1/root". 39 | [env] current mtpPath is "/proc/1/root/userdisk/Music". 40 | [config] loading config... 41 | [config] loaded config. 42 | [fallback] current fallback bing signatue create url is "https://www.bing.com/turing/conversation/create". 43 | [fallback] current fallback bing chathub url is "wss://sydney.bing.com/sydney/ChatHub". 44 | [info] starting... 45 | [info] 46 | [info] signature create server listened at "http://127.0.0.2:9988" . 47 | [info] chathub server listened at "ws://127.0.0.2:9989" . 48 | [info] fallback signature create server listened at "http://127.0.0.2:8988" . 49 | [info] fallback chathub server listened at "ws://127.0.0.2:8989" . 50 | [info] log server listened at 127.0.0.1:12345 . 51 | [info] started. 52 | [info] 53 | ``` 54 | ### Step4 55 | 在penmods中配置。 56 | 先将你原来的bing的signature创建地址和chathub地址按照下面的格式写到文件`/userdisk/Music/bing-url.json`中(记得先删掉`bing-url.json`中的旧内容)。 57 | ``` 58 | { 59 | "signature":"signature创建地址", 60 | "chathub":"chathub地址" 61 | } 62 | ``` 63 | 然后现在介绍两种使用模式。 64 | #### 使用扩展程序 65 | 通过此模式使用本项目。 66 | penmods中的signature创建地址改成`http://127.0.0.2:9988`,chathub地址改成`ws://127.0.0.2:9989`。 67 | #### 使用bing 68 | 通过此模式使用newbing。 69 | penmods中的signature创建地址改成`http://127.0.0.2:8988`,chathub地址改成`ws://127.0.0.2:8989`。 70 | 日后如需更换bing的相关地址,在`bing-url.json`文件中修改即可。 71 | ### Step5 72 | 配置扩展程序。 73 | #### gpt 74 | 打开文件`/userdisk/youdaoExt/ext/gpt/config.json`,将`https://api.openai.com/v1/chat/completions`换成你自己搭建的对应代理地址,将`sk-ThisIsAnExampleKeyPleaseReplaceItWithYourKey`换成你自己的apiKey。 75 | #### textweb 76 | 打开文件`/userdisk/youdaoExt/ext/textweb/ext/www.baidu.com/cookie.txt`,填入你自己提取的百度的`cookie`(登没登百度账号无所谓)。(此处的`cookie`可以用入坑penmods的newbing时提到的`Cookie-Editor`提取得到(请先升级`Cookie-Editor`到最新版本),不过步骤略有不同,提取时先点`Export`,再点`Header String`,随后即可粘贴到指定地方。) 77 | ### Step6 78 | 重启词典笔。ydpExt会随系统自启。 79 | ## How to uninstall 80 | 注意: 卸载程序会移除以下文件(夹),如需要请先自行备份。 81 | 82 | - /userdisk/Music/gpt 83 | - /userdisk/Music/textweb 84 | - /userdisk/Music/bing-url.json 85 | 86 | 注意: 请一定在**指定的工作目录**执行脚本(因为脚本使用了相对位置),否则后果自负。 87 | ### 直装版 88 | 在`/userdisk`下运行`uninstall.native.sh`。 89 | ### 容器版 90 | 在`/userdisk`下运行`uninstall.chroot.sh`。 91 | ## How to update 92 | **0x0** 下载最新发行版的用于升级的压缩包,解压。 93 | **0x1** 复制`youdaoExt`至ydp覆盖同名文件夹。 -------------------------------------------------------------------------------- /docs/use.md: -------------------------------------------------------------------------------- 1 | # 使用文档 2 | ## 使用模式 3 | 开始之前请先将你原先在penmods中设置的newbing地址按以下格式写入文件`/userdisk/Music/bing-url.json`。(记得先删掉`bing-url.json`中的旧内容) 4 | ```json 5 | { 6 | "signature":"signature创建地址", 7 | "chathub":"chathub地址" 8 | } 9 | ``` 10 | 日后如需更换bing的相关地址,在`bing-url.json`文件中修改即可。 11 | 12 | 为兼容`penmods`原有的newbing功能,`ydpExt`提供两种使用模式。 13 | 14 | - 扩展程序模式: 该模式下可以使用扩展程序。 15 | - newbing模式: 该模式下可以像没装`ydpExt`之前那样使用newbing。 16 | 17 | 在两种使用模式间切换,只需在penmods中修改以下地址。 18 | 19 | |名称|扩展程序模式|newbing模式| 20 | |---|---|---| 21 | |signature创建地址|`http://127.0.0.2:9988`|`http://127.0.0.2:8988`| 22 | |chathub地址|`ws://127.0.0.2:9989`|`ws://127.0.0.2:8989`| 23 | 24 | ## 扩展程序管理 25 | - 安装或更新一个扩展程序只需要将 包含它本体的文件夹(文件夹内应有`index.js`文件) 放入`/userdisk/youdaoExt/ext`或`/userdisk/chroot/youdaoExt/ext`下即可。 26 | - 卸载一个扩展程序只需要将包含它本体的文件夹从`/userdisk/youdaoExt/ext`或`/userdisk/chroot/youdaoExt/ext`下删除即可。 27 | ## 当前扩展程序 28 | 扩展程序主机支持**多任务**(对扩展程序而言),其操作逻辑与手机类似。 29 | 你可以在`bing聊天界面`点击左边的设置图标查看`服务版本`,其内容即是当前处于前台的扩展程序的名称。 30 | ### 前台 31 | 扩展程序主机会将 你的输入(你在`bing聊天界面`输入的内容) 送至**处于前台**的扩展程序,同时会将 扩展程序的输出(扩展程序对你的输入做出的回复,就是最后要显示在消息框的内容) 送回`penmods`显示在消息框内。 32 | ### 后台 33 | **处于后台**的扩展程序并不会 挂起(暂停运行) 或关闭,而会保持继续运行。你的输入不会被送至处于后台的扩展程序,处于后台的扩展程序的输出不会被送回`penmods`,且它在处于后台的时间内产生的输出将永远不会被送回。 34 | ### 前后台切换及退出扩展程序 35 | 参照下文`超级命令`。 36 | ### 扩展程序选择页 37 | 在扩展程序选择页,你可以选择要启动的或要切换到前台的扩展程序。扩展程序选择页显示在`服务版本`处的名称是`youdaoExt`。在退出一个扩展程序或要切换扩展程序时你会来到此页。 38 | ## 超级命令 39 | 只有在有扩展程序处于前台时,你可以在`bing聊天界面`输入超级命令。 40 | 超级命令会被**扩展程序主机**接收,而其他输入则会被**扩展程序**接收。 41 | 超级命令是只以一个`#`开头的不含空格的字符串(满足正则表达式`/^(#)([a-zA-Z]+)$/g`)。 42 | 以下是超级命令列表。 43 | 44 | - `#exit`: 关闭当前处于前台的扩展程序。 45 | - `#switch`: 启动扩展程序或将后台扩展程序切到前台。 46 | - `#memClean`: 清理运行内存。(不推荐此种方式,即将弃用) 47 | 48 | 若要向扩展程序输入以`#`开头的内容,请重复首字符`#`,例如: 你想输入`###test`,则输入`####test`,最后扩展程序将收到`###test`。 49 | ## 扩展程序错误 50 | 当扩展程序中出现 未处理的异常(uncaughtException) 和 在Promise中未处理异常(unhandledRejection) 时,扩展程序会自动关闭。在扩展程序事件处理和响应函数中的错误可以立即看到错误提示;但其他的不会有任何立即提示,需要再次向扩展程序输入内容时才可看到提示。为帮助解决问题,你可以在 词典笔的shell(注意不是容器的shell,除非你的容器内安装了`telnet`) 上telnet到 日志服务器(地址`127.0.0.1`,端口`12345`) 后复现错误收集实时日志并反馈至其作者,涉及到的命令如下。 51 | ```bash 52 | telnet 127.0.0.1 12345 53 | ``` 54 | ## 自定义配置 55 | 打开位于文件夹`youdaoExt`下的`config.json`,参照下表按需要以`json`格式写入内容。 56 | 57 | |名称|解释|默认值| 58 | |---|---|---| 59 | |`logServerAddr`|日志服务器地址|`127.0.0.1`| 60 | |`logServerPort`|日志服务器端口|`12345`| 61 | |`serverAddr`|扩展程序主机的服务器地址|`127.0.0.2`| 62 | |`signatureServerPort`|使用 **扩展程序模式** 时的signature创建地址的端口|`9988`| 63 | |`chathubServerPort`|使用 **扩展程序模式** 时的chathub地址的端口|`9989`| 64 | |`fallbackSignatureServerPort`|使用 **newbing模式** 时的signature创建地址的端口|`8988`| 65 | |`fallbackChathubServerPort`|使用 **newbing模式** 时的chathub地址的端口|`8989`| 66 | 67 | 以下是一个例子。 68 | ```json 69 | { 70 | "logServerAddr": "0.0.0.0", 71 | "logServerPort": 3344 72 | } 73 | ``` 74 | 此例子会将日志服务器公开到局域网并修改其端口。最后日志服务器地址为词典笔的局域网ip,端口为`3344`。 75 | ## 内置扩展程序的使用 76 | ### gpt 77 | 目前只支持`API`版本的gpt,配置方法参考[此处](./install.md#gpt)。 78 | 79 | 具体使用方法同`penmods`的`bing`,不多赘述。 80 | 81 | 特有的命令如下。 82 | 83 | - `##save`: 导出当前对话到`/userdisk/Music/gpt`。 84 | - `##retry`: 重新发送你的最后一次输入。 85 | - `##new`: 开始新对话。 86 | 87 | 如需输入以`#`开头的消息,请参考一下转义示例。 88 | 89 | |想输入的|实际要输入的| 90 | |---|---| 91 | |`# Title`|`### Title`| 92 | |`## Title`|`#### Title`| 93 | 94 | ### terminal 95 | 注意: 96 | 97 | 1) 此终端不是一个`tty`终端。 98 | 2) 终端用户是`root`,请注意防护。 99 | 3) 假如你输入`YourInput`,此终端实现`prompt`的原理是向`bash`的`stdin`写入```YourInput\necho \`whoami\`@\`hostname\`:\`pwd\`#\n```,所以例如使用`apt install`时,请带上`-y`参数。 100 | 101 | ### textweb 102 | #### 配置 103 | 配置textweb,请参考[此处](./install.md#textweb) 104 | #### tabs 105 | 消息框右下角的数字含义: 当前Tab的id/最大的`Tab's id`。 106 | 一些特殊Tab(tabId<0): 107 | 108 | |id|名称|使用方法| 109 | |---|---|---| 110 | |`-1`|初始页,或者叫主页|直接输入url查看对应网站(注意,url需要带协议名称)。| 111 | |`-2`|Tab切换页|直接在`参考候选`选择Tab。| 112 | 113 | 在正常tab(tabId>=0)上,你只能 输入对应指令进行相关操作 或 输入以`>`开头的内容传入由类型为`handle`的textweb扩展程序处理的网站(比如内置的百度和萌娘百科搜索)。 114 | #### 自定义主页 115 | 主页的`参考候选`中的链接可自定义。 116 | 配置文件位于`/userdisk/Music/textweb/sites.json`(若文件不存在请自行创建),其格式如下: 117 | ```json 118 | { 119 | "sites": ["链接1", "链接2", "链接3"] 120 | } 121 | ``` 122 | #### 链接 123 | 在网页上的链接通常会被标为蓝色且有这种格式: `链接名称[链接id]`。 124 | 直接输入链接id会在新tab打开对应链接。 125 | #### 图片 126 | 在网页上的图片通常有这种格式: `图片本体[p数字]`。 127 | 直接输入方括号内内容会在独立图片查看器中打开对应图片。 128 | #### 命令 129 | 以下是命令列表: 130 | 131 | - `info`: 查看当前tab的标题和链接。 132 | - `show`: 重新展示当前tab。 133 | - `save`: 离线页面至`/userdisk/Music/textweb/offline`。 134 | - `refresh`: 重新加载当前tab。 135 | - `close`: 关闭当前tab。 136 | - `open 链接`: 在新tab中打开链接(你输入的`链接`)。 137 | - `switch`: 进入`Tab切换页`。 138 | 139 | #### cookie 140 | 为特定网站加载cookie(常用于对抗反爬虫机制)。 141 | cookie文件请放在`/userdisk/Music/textweb/cookie`下,参考以下格式: 142 | ```json 143 | { 144 | "cookie": "你提取的cookie", 145 | "banSite": [] 146 | } 147 | ``` 148 | 关于cookie文件的命名,参考以下例子: 149 | 当为网站`www.github.com`加载cookie时,会依次读取以下cookie文件并合成最终cookie: 150 | 151 | - `www.github.com.json` 152 | - `github.com.json` 153 | - `com.json` 154 | 155 | 现在你应该知道如何命名cookie文件了。 156 | 假如现在你不想让`docs.github.com`加载cookie文件`github.com.json`,则在`github.com.json`中的`banSite`添加元素`docs.github.com`,现在你应该知道`banSite`的作用了。 157 | #### 支持的网页参数 158 | 159 | |名称|支持的| 160 | |---|---| 161 | |协议|`http`,`https`| 162 | |`content-encoding`|`br`,`gzip`,`deflate`,不压缩| 163 | |网页编码格式|`utf-8`| 164 | |请求方法|`GET`| 165 | |http版本|`1.1`| 166 | |`connection`|`closed`,`keep-alive`| 167 | |重定向`statusCode`|`301`,`302`,`307`,`308`| 168 | 169 | #### 扩展程序 170 | 分为`config`、`hook`和`handle`三种。 -------------------------------------------------------------------------------- /ext/built-in/gpt/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "url":"https://api.openai.com/v1/chat/completions", 3 | "key":"sk-ThisIsAnExampleKeyPleaseReplaceItWithYourKey" 4 | } 5 | -------------------------------------------------------------------------------- /ext/built-in/gpt/gptType.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /ext/built-in/gpt/random.js: -------------------------------------------------------------------------------- 1 | export var map; 2 | (function (map) { 3 | map.number = "1234567890"; 4 | map.upperLetter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 5 | map.lowerLetter = "abcdefghijklmnopqrstuvwxyz"; 6 | map.letter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 7 | map.numberAndUpperLetter = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 8 | map.numberAndLowerLetter = "1234567890abcdefghijklmnopqrstuvwxyz"; 9 | map.hex = "1234567890abcdef"; 10 | map.numberAndLetter = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 11 | map.numberAndLetterAndSymbol = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+=/"; 12 | })(map || (map = {})); 13 | export function randomNumber(length) { 14 | var out = ''; 15 | while (true) { 16 | var t = Math.random().toString().split('.')[1]; 17 | out += t.slice(0, length - out.length); 18 | if (length <= t.length) 19 | break; 20 | } 21 | return parseInt(out); 22 | } 23 | export function randomString(length, map) { 24 | var out = ''; 25 | if (map.length == 0) 26 | throw 'map is an empty string!'; 27 | while (true) { 28 | out += map[Math.floor(map.length * Math.random())]; 29 | if (out.length == length) 30 | break; 31 | } 32 | return out; 33 | } 34 | export function randomUUID() { 35 | return `${randomString(8, map.hex)}-${randomString(4, map.hex)}-${randomString(4, map.hex)}-${randomString(4, map.hex)}-${randomString(12, map.hex)}`; 36 | } 37 | -------------------------------------------------------------------------------- /ext/built-in/textweb/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir -p ./build 3 | 4 | cd ./third/html-to-md 5 | npm run build:main 6 | 7 | cd ../.. 8 | 9 | cp -r ./src/* ./build/ 10 | cp -r ./third/html-to-md/dist/* ./build/node_modules/html-to-md/dist/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /ext/built-in/textweb/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd ./src 4 | npm install 5 | cd ../third/html-to-md 6 | npm install 7 | 8 | 9 | -------------------------------------------------------------------------------- /ext/built-in/textweb/src/ext/wiki.tweb/ext.json: -------------------------------------------------------------------------------- 1 | { 2 | "type":"handle", 3 | "index":"index.js", 4 | "acceptSite": ["moegirl.wiki.tweb"] 5 | } -------------------------------------------------------------------------------- /ext/built-in/textweb/src/ext/wiki.tweb/index.js: -------------------------------------------------------------------------------- 1 | 2 | import * as https from 'node:https'; 3 | 4 | const target = { 5 | 'moegirl':'zh.moegirl.org.cn' 6 | }; 7 | 8 | const ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'; 9 | 10 | var nowTargetHost = ''; 11 | var nowMatch = ''; 12 | 13 | export async function handle(context, input) { 14 | return new Promise((resolve,reject)=>{ 15 | try{ 16 | if (input == context.url) { 17 | var url = new URL(context.url); 18 | nowMatch = url.host.split('.')[0]; 19 | if (typeof target[nowMatch] =='string') nowTargetHost = target[nowMatch]; 20 | if (nowTargetHost=='') return { 21 | txt: `### Wiki for textweb\nError: not any target match "${nowMatch}".`, 22 | title: nowMatch + ' - Wiki for tweb', 23 | suggest: ['close'], 24 | links: [] 25 | } 26 | resolve({ 27 | txt: `### Wiki for textweb\nNow target is "${nowMatch}". \nType text to search.`, 28 | title: nowMatch + ' - Wiki for tweb', 29 | suggest: ['>Warma'], 30 | links: [] 31 | }); 32 | return; 33 | } 34 | if (nowTargetHost=='') resolve({ 35 | txt: `### Wiki for textweb\nError: not any target match "${nowMatch}".`, 36 | title: nowMatch + ' - Wiki for tweb', 37 | suggest: ['close'], 38 | links: [] 39 | }); 40 | 41 | var req = https.request({host:nowTargetHost,hostname:nowTargetHost,servername:nowTargetHost,method:'GET',port:443,path:`/api.php?action=opensearch&redirects=resolve&search=${encodeURIComponent(input)}`,headers:{'user-agent':ua,'accept':'application/json'}}); 42 | 43 | req.on("error",(e)=>{ 44 | console.log(e); 45 | reject(e); 46 | }); 47 | 48 | req.on('response',(res)=>{ 49 | if (res.statusCode!=200){ 50 | resolve({ 51 | txt: `### [Error]\n\`\`\`\ncode ${res.statusCode} msg "${res.statusMessage}"`, 52 | title: `${input} - ${nowMatch + ' - Wiki for tweb'}`, 53 | links: [], 54 | suggest: [`>${input}`] 55 | }); 56 | return; 57 | } 58 | var data = []; 59 | res.on('data',(d)=>{ 60 | data.push(Buffer.from(d)); 61 | }); 62 | res.on('end',()=>{ 63 | var txt = Buffer.concat(data).toString(); 64 | console.log(txt); 65 | var obj = JSON.parse(txt); 66 | var links = obj[3]; 67 | var titles = obj[1]; 68 | for (var i=0;i{ 74 | out.push(`#### ${titles.indexOf(c)}. ${c} \n`); 75 | }); 76 | resolve({ 77 | txt: out.join(' \n'), 78 | title: `${input} - ${nowMatch + ' - Wiki for tweb'}`, 79 | links: links, 80 | suggest: [] 81 | }); 82 | }); 83 | }); 84 | req.end(); 85 | }catch(e){ 86 | console.log(e); 87 | reject(e); 88 | } 89 | }); 90 | } -------------------------------------------------------------------------------- /ext/built-in/textweb/src/ext/www.baidu.com/cookie.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PenExtMods/ydpExt/077230117f295264ea14f203b09afc0a6958d175/ext/built-in/textweb/src/ext/www.baidu.com/cookie.txt -------------------------------------------------------------------------------- /ext/built-in/textweb/src/ext/www.baidu.com/ext.json: -------------------------------------------------------------------------------- 1 | { 2 | "type":"handle", 3 | "index":"index.js", 4 | "acceptSite":["www.baidu.com"] 5 | } 6 | -------------------------------------------------------------------------------- /ext/built-in/textweb/src/libEncode.js: -------------------------------------------------------------------------------- 1 | export function encode(txt) { 2 | return Buffer.from(txt).toString('hex'); 3 | } 4 | export function decode(txt) { 5 | return Buffer.from(txt, 'hex').toString(); 6 | } 7 | export function base64(str) { 8 | const binString = Array.from(new TextEncoder().encode(str), (x) => String.fromCodePoint(x)).join(""); 9 | return btoa(binString); 10 | } 11 | export function deBase64(str) { 12 | const binString = atob(str); 13 | return new TextDecoder().decode(Uint8Array.from(binString, (m) => m.codePointAt(0))); 14 | } 15 | 16 | export function uriArgEncode(t){ 17 | var o = Buffer.from(t).toString('hex').toUpperCase(); 18 | var out = ''; 19 | for (var i=0;i/dev/null 2>&1 22 | 23 | # E: Build LifeCycle 24 | 25 | branches: 26 | only: 27 | - master 28 | env: 29 | global: 30 | - GH_REF: github.com/stonehank/html-to-md 31 | - P_BRANCH: gh-pages 32 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.8.3 2 | 3 | - Change `Tag` and `SelfCloseTag` class property name `content` to `innerHTML` 4 | - fix [#60](https://github.com/stonehank/html-to-md/issues/60) 5 | - fix quote in tag attribute can not be detected correctly 6 | - fix `|` escapes in table 7 | - ADD: `tagListener` to custom some props of tag 8 | 9 | (tagName, props: [TagListenerProps](https://github.com/stonehank/html-to-md/blob/master/README.md#TagListenerProps)): [TagListenerReturnProps](https://github.com/stonehank/html-to-md/blob/master/README.md#TagListenerReturnProps) => TagListenerReturnProps` 10 | 11 | ## 0.8.0 12 | 13 | - add eslint and premitter 14 | - sourcecode use es module 15 | - convert to typescript 16 | 17 | ### 0.7.0 18 | 19 | - add `renderCustomTags` in options 20 | 21 | ### 0.6.1 22 | 23 | - add title in a tag 24 | - add input options in demo page 25 | 26 | ### 0.6.0 27 | 28 | - fix undefined language in pre tag 29 | 30 | ### 0.5.9 31 | 32 | - fix p tag add an extra gap when inside text node. 33 | - update get language function define in pre tag. 34 | 35 | ### 0.5.8 36 | 37 | - fix[#45](https://github.com/stonehank/html-to-md/issues/45) 38 | 39 | ### 0.5.7 40 | 41 | 1. fixed: keep format in coding block(#44) 42 | 2. reset config before start 43 | 44 | ### 0.5.4 - 0.5.6 45 | 46 | - fix [#43](https://github.com/stonehank/html-to-md/issues/43) 47 | 48 | ### 0.5.2 - 0.5.3 49 | 50 | - fix [#42](https://github.com/stonehank/html-to-md/issues/42) 51 | 52 | ### 0.5.1 53 | 54 | - Support attr:align in table 55 | - Add UMD to support browser [#41](https://github.com/stonehank/html-to-md/pull/41) 56 | 57 | ## 0.5.0 58 | 59 | #### Refactor 60 | 61 | - Less space 62 | - Change some default configs 63 | - Add typings for typescript [#34](https://github.com/stonehank/html-to-md/pull/34) 64 | 65 | ### 0.4.4 66 | 67 | - Fixed issue #33, use `\s` instead of `' '` in tags attribute detect. 68 | 69 | ### 0.4.3 70 | 71 | - Speed up parse string 72 | 73 | ### 0.4.2 74 | 75 | - Remove `window` 76 | 77 | ### 0.4.1 78 | 79 | - Remove `throw Error`, add some errors test 80 | - Add empty `table` detect, add test case 81 | 82 | ### 0.4.0 83 | 84 | - Remove the first `\n` in some tags 85 | - Escape some character in some tag, like `* abc` 86 | - Fix render issues when `
` in `
  • ` 87 | - Fix render issues when have `` ` ``(single or multiple) in `` or `
    `
     88 | - Ignore extra tags if already have `code` tag inside `pre`
     89 | 
     90 | ### 0.3.9
     91 | 
     92 | - Output some raw unvalid content, like `<` in the tag contents.
     93 | 
     94 | #### 0.3.8
     95 | 
     96 | - Fix when render as raw HTML, remove all the wrap.
     97 | 
     98 | #### 0.3.7
     99 | 
    100 | - Fixed some tags inside `th`, `td` will cause wrap.Consider `
    ABC
    ` 101 | - Add some tags should render as raw HTML inside a table.Consider `
    • 1
    ` 102 | 103 | #### 0.3.6 104 | 105 | - Add `aliasTags`. 106 | - Remove console in production. 107 | 108 | #### 0.3.5 109 | 110 | - Remove console. 111 | 112 | #### 0.3.4 113 | 114 | - Add 'dl,dd,dt' inside default skipTags. 115 | - Fixed some no used space. 116 | 117 | #### 0.3.3 118 | 119 | - Add 'html' and 'body' inside default skipTags. 120 | - Add `force` options, it can totally overwrite the customize options object. 121 | - Fix some redundant empty line. 122 | 123 | ##### 0.3.2 124 | 125 | - Fixed bugs that cause text after heading tags will not line feed. 126 | 127 | ##### 0.3.1 128 | 129 | - created `CHANGELOG.md`, support `english` readme 130 | - add options, can set the tags resolve strategy 131 | - add `div` to default value of `options:skipTags` 132 | - skipTags will check if need '\n' 133 | 134 | ##### 0.3.0 135 | 136 | - add support for tag`` 137 | - fixed the bug when ``language is `markdown` 138 | - fixed the bug when `

    ` nest in `

  • ` 139 | - fixed some nest tag render bug 140 | - merge tht common use code 141 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 hank 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/error-boundary/error-test.test.js: -------------------------------------------------------------------------------- 1 | import Tag from "../../src/Tag"; 2 | import SelfCloseTag from "../../src/SelfCloseTag"; 3 | import html2Md from '../../src/index' 4 | 5 | describe('error test', () => { 6 | 7 | it('not integrity test', () => { 8 | let str='\n' + 9 | '
    \n' + 10 | '
      \n' + 11 | '
    1. bq-nest-2
    2. \n' + 12 | '
    \n' + 13 | '
    \n' + 14 | '
      \n' + 15 | '
    1. bq-nest-3
    2. \n' + 16 | '
    \n' + 17 | '
    \n' + 18 | '
    \n' + 19 | '\n' + 20 | '
  • \n' + 21 | '\n' + 22 | '
  • ' 23 | 24 | expect(html2Md(str)).toBe( 25 | '> 1. bq-nest-2\n' + 26 | '>\n' + 27 | '>> 1. bq-nest-3') 28 | }) 29 | 30 | it('unvalid tag, string, not close, wrong tagname, self tag in Tag', () => { 31 | let str='String send inside Tag' 32 | let str2='String send inside Tag' 33 | let str3='' 34 | let str4='
    ' 35 | let str5='
    ' 36 | let tag=new Tag(str) 37 | let tag2=new Tag(str2, 'a') 38 | let tag3=new Tag(str3, 'img') 39 | let tag4=new Tag(str4, 'div') 40 | let tag5=new Tag(str5, 'div') 41 | expect(tag.innerHTML).toBe('') 42 | expect(tag2.innerHTML).toBe('') 43 | expect(tag3.innerHTML).toBe('') 44 | expect(tag4.innerHTML).toBe('') 45 | expect(tag5.innerHTML).toBe('') 46 | }) 47 | 48 | 49 | it('unvalid selfTag, string, wrong tagName in SelfCloseTag ', () => { 50 | let str='String send inside selfTag' 51 | let str2='' 52 | let selfTag=new SelfCloseTag(str) 53 | let selfTag2=new SelfCloseTag(str2,'input') 54 | expect(selfTag.attrs).toEqual({}) 55 | expect(selfTag2.attrs).toEqual({}) 56 | }) 57 | 58 | it('other tags inside tr', () => { 59 | let str="\n" + 60 | "\n" + 61 | "\n" + 62 | "\n" + 63 | "

    data-1-left

    \n" + 64 | "\n" + 65 | "\n" + 66 | "\n" + 67 | "\n" + 68 | "\n" + 69 | "\n" + 70 | "\n" + 71 | "\n" + 72 | "\n" + 73 | "\n" + 74 | "\n" + 75 | "\n" + 76 | "\n" + 77 | "\n" + 78 | "\n" + 79 | "\n" + 80 | "\n" + 81 | "
    data-1-center
    data-1-leftdata-1-center
    data-2-leftdata-2-center
    data-3-leftdata-3-center
    " 82 | expect(html2Md(str)).toBe('|data-1-center|\n' + 83 | '|---|\n' + 84 | '|data-1-left|data-1-center|\n' + 85 | '|data-2-left|data-2-center|\n' + 86 | '|data-3-left|data-3-center|') 87 | }) 88 | 89 | it('Not valid p in Ol',()=>{ 90 | let str='
      \n' + 91 | '
    1. one
    2. \n' + 92 | '

      two

      \n' + 93 | '
    3. three
    4. \n' + 94 | '
    ' 95 | expect(html2Md(str)).toBe('1. one\n' + 96 | '2. three') 97 | }) 98 | 99 | it('Not valid p in Ol2',()=>{ 100 | let str='
      \n' + 101 | '
    1. one
    2. \n' + 102 | 'two\n' + 103 | '
    3. three
    4. \n' + 104 | '
    ' 105 | expect(html2Md(str)).toBe('1. one\n' + 106 | '2. three') 107 | }) 108 | 109 | it('Not valid tag in Ul',()=>{ 110 | let str='
      \n' + 111 | '
    • one
    • \n' + 112 | 'two\n' + 113 | '
    • three
    • \n' + 114 | '
    ' 115 | expect(html2Md(str)).toBe('* one\n' + 116 | '* three') 117 | }) 118 | }) 119 | 120 | 121 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/module/module5.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | describe('test special', () => { 4 | 5 | it('h1 Text should be next line', () => { 6 | let str = `
      7 |
    • 8 |

      h1

      Should Be Next Line
    • 9 |
    ` 10 | expect(html2Md(str)).toBe( 11 | '* # h1\n' + 12 | ' Should Be Next Line') 13 | }) 14 | 15 | it('h3 Text should be next line', () => { 16 | let str = `
      17 |
    • 18 |

      h3

      Should Be Next Line
    • 19 |
    ` 20 | expect(html2Md(str)).toBe( 21 | '* ### h3\n' + 22 | ' Should Be Next Line') 23 | }) 24 | 25 | it('Unvalid tag with valid sub tags',()=>{ 26 | expect(html2Md('
    italyStrong')).toBe("italyStrong") 27 | }) 28 | 29 | it('Unvalid tag in table',()=>{ 30 | expect(html2Md('\n' + 31 | '\n' + 32 | '
    sdfdfdfdfdf
    ')).toBe( 33 | "||\n" + 34 | "|---|\n" + 35 | "|sdfdfdfdfdf{ 39 | expect(html2Md("sdfsdfsdfsdf")).toBe("**sdfsdfs**dfsdf") 40 | }) 41 | 42 | }) 43 | 44 | 45 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/module/module6.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | describe('some correction',()=>{ 3 | 4 | it('Default no ignore svg',()=>{ 5 | let str='\n' + 6 | '\n' + 7 | ' ' 8 | expect(html2Md(str)).toBe('') 9 | }) 10 | 11 | it('Need to escape 1',()=>{ 12 | let str='

    * 一个标题

    ' 13 | expect(html2Md(str)).toBe('**\\* 一个标题**') 14 | }) 15 | 16 | it('Need to escape 2',()=>{ 17 | let str='

    * 一个标题- 第二个标题

    ' 18 | expect(html2Md(str)).toBe('**\\* 一个标题*\\- 第二个标题***') 19 | }) 20 | it('Need to escape 3',()=>{ 21 | let str='

    **一个标题**-- 第二个标题

    ' 22 | expect(html2Md(str)).toBe('\\*\\*一个标题\\*\\**\\-- 第二个标题*') 23 | }) 24 | it('test starts with space',()=>{ 25 | expect(html2Md(" ")).toBe("[Sign In]()[Register]()") 26 | }) 27 | 28 | it('test no wrapper',()=>{ 29 | expect(html2Md(" bold

    paragraph2

    ")).toBe("**bold**\n" + 30 | "\n" + 31 | "paragraph2") 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/options.js: -------------------------------------------------------------------------------- 1 | const tagSpaceNum = 4 2 | const SYMBOL_I = '*' 3 | const SYMBOL_B = '**' 4 | 5 | export { tagSpaceNum, SYMBOL_I, SYMBOL_B } 6 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/__empty__.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | import config from '../../src/config' 3 | 4 | describe('清空指定的tag以及内部的标签',()=>{ 5 | 6 | beforeEach(()=>{ 7 | config.reset() 8 | }) 9 | 10 | it('消除tag',()=>{ 11 | expect(html2Md("<>abc",{emptyTags:['']})).toBe('abc') 12 | }) 13 | 14 | it('跳过空白标签及其内部',()=>{ 15 | expect(html2Md('<>abc',{emptyTags:['']})).toBe('abc') 16 | }) 17 | 18 | it('消除b和i',()=>{ 19 | expect(html2Md("abc",{emptyTags:['b','i']})).toBe('abc') 20 | }) 21 | 22 | it('消除b和i(2)',()=>{ 23 | expect(html2Md("abc",{emptyTags:['b']})).toBe('abc') 24 | // expect(html2Md("abc")).toBe('abc') 25 | }) 26 | 27 | 28 | it('强制修改config为当前提供的值,只消除i',()=>{ 29 | expect(html2Md("abc",{emptyTags:['i']},true)).toBe('**abc**') 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/__ignore__.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | 4 | describe('直接忽视整个tag',()=>{ 5 | 6 | 7 | it('忽视 所有 ',()=>{ 8 | expect(html2Md("<>abc",{ignoreTags:['']}, true)).toBe('') 9 | }) 10 | 11 | 12 | it('忽视 i 及其内部abc ',()=>{ 13 | expect(html2Md("abc",{ignoreTags:['i']})).toBe('') 14 | }) 15 | 16 | it('忽视内部所有tag',()=>{ 17 | expect(html2Md("abc",{ignoreTags:['b']})).toBe('') 18 | }) 19 | 20 | 21 | it('忽视自闭和标签',()=>{ 22 | expect(html2Md("
    delete",{ignoreTags:['br']})).toBe('~~delete~~') 23 | expect(html2Md("
    delete",{ignoreTags:['br']})).toBe('~~delete~~') 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/__nomatch__.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | describe('对于不匹配,消除attrs,标签不处理',()=>{ 4 | 5 | 6 | it('不匹配消除attrs',()=>{ 7 | expect(html2Md("abc", { renderCustomTags: true })).toBe('*abc*') 8 | }) 9 | 10 | it('自闭和标签,不匹配消除attrs',()=>{ 11 | expect(html2Md('', { renderCustomTags: true })).toBe('**') 12 | }) 13 | 14 | }) 15 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/__skip__.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | import config from '../../src/config' 3 | 4 | describe('跳过指定的tag标签,内部不影响',()=>{ 5 | 6 | beforeEach(()=>{ 7 | config.reset() 8 | }) 9 | 10 | it('跳过空白tag',()=>{ 11 | expect(html2Md("<>abc",{skipTags:['']})).toBe('abc') 12 | }) 13 | 14 | it('跳过空白tag,内部不变',()=>{ 15 | expect(html2Md("<>abc",{skipTags:['']})).toBe('***abc***') 16 | }) 17 | 18 | it('跳过b 和 i',()=>{ 19 | expect(html2Md("abc",{skipTags:['b','i']})).toBe('abc') 20 | }) 21 | 22 | it('跳过del 和 i',()=>{ 23 | expect(html2Md("abc",{skipTags:['del','i']})).toBe('**abc**') 24 | }) 25 | 26 | it('跳过 b',()=>{ 27 | expect(html2Md("abc",{skipTags:['b']})).toBe('*abc*') 28 | }) 29 | 30 | 31 | it('跳过 i',()=>{ 32 | expect(html2Md("abc",{skipTags:['i']})).toBe('**abc**') 33 | }) 34 | 35 | it('跳过 html 和 div',()=>{ 36 | expect(html2Md("
    abc
    ")).toBe("*abc*") 37 | }) 38 | 39 | it('只跳过 html',()=>{ 40 | expect(html2Md("
    abc
    ",{skipTags:['html']},true)).toBe( 41 | '
    *abc*
    ' 42 | ) 43 | }) 44 | 45 | it('跳过dt,dd,dl,保持space',()=> { 46 | expect(html2Md("
    \n\n\n\n" + 47 | "
    \n" + 48 | "title" + 49 | "
    \n\n\n" + 50 | "
    " + 51 | "content" + 52 | "
    \n\n\n" + 53 | "
    ")).toBe( 54 | "`title`\n" + 55 | "\n" + 56 | "`content`") 57 | }) 58 | 59 | it('Skip self tag',()=>{ 60 | expect(html2Md(`123234`,{skipTags:['img','b']},true)).toBe('123*234*') 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/a.test.js: -------------------------------------------------------------------------------- 1 | import A from '../../src/tags/a' 2 | 3 | describe('test tag',()=>{ 4 | it('has href',()=>{ 5 | let a=new A("pica") 6 | expect(a.exec()).toBe("[**pica**](https://nodeca.github.io/pica/demo/)") 7 | }) 8 | it('no href',()=>{ 9 | let a=new A("pica") 10 | expect(a.exec()).toBe("[**pica**]()") 11 | }) 12 | 13 | it('space and \n in tag',()=>{ 14 | let a=new A( 15 | ` 16 | click 17 | `) 18 | expect(a.exec()).toBe('[click](#)') 19 | }) 20 | 21 | it('space and \n in attributes',()=>{ 22 | let a=new A( 23 | `link from moz`) 25 | expect(a.exec()).toBe("[link from moz](/#you-should-see-this)") 26 | }) 27 | 28 | it('title in tag',()=>{ 29 | let a=new A(`link from moz`) 30 | expect(a.exec()).toBe(`[link from moz](/#you-should-see-this "some title")`) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/b.test.js: -------------------------------------------------------------------------------- 1 | // import B from '../../src/tags/b' 2 | import html2Md from '../../src/index' 3 | import {SYMBOL_I,SYMBOL_B} from '../options' 4 | 5 | describe("test tag",()=>{ 6 | it('no nest',()=>{ 7 | let b=html2Md("b") 8 | expect(b).toBe(SYMBOL_B+"b"+SYMBOL_B) 9 | }) 10 | 11 | it('can nest',()=>{ 12 | let b=html2Md("b and italic") 13 | expect(b).toBe("***b and italic***") 14 | }) 15 | 16 | it('换行需要省略',()=>{ 17 | let b=html2Md("\n" + 18 | "b and italic\n" + 19 | "") 20 | expect(b).toBe("***b and italic***") 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/blockquote.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | import {tagSpaceNum} from '../options' 3 | 4 | describe("test
    tag",()=>{ 5 | 6 | it('no nest',()=>{ 7 | let blockquote=html2Md("
    \n" + 8 | "

    This is quote

    \n" + 9 | "
    ") 10 | expect(blockquote).toBe( 11 | "> This is **quote**…") 12 | }) 13 | 14 | 15 | it('nest ul',()=>{ 16 | let blockquote=html2Md("
    \n" + 17 | "
      \n" + 18 | "
    • sdfs
    • \n" + 19 | "
    • sdfsdf
    • \n" + 20 | "
    • sdfsaf
    • \n" + 21 | "
    \n" + 22 | "
    ") 23 | expect(blockquote).toBe( 24 | "> * sdfs\n" + 25 | "> * sdfsdf\n" + 26 | "> * sdfsaf") 27 | }) 28 | 29 | it('nest blockquote',()=>{ 30 | let blockquote=html2Md("
    \n" + 31 | "

    Blockquotes can also be nested…

    \n" + 32 | "
    \n" + 33 | "

    …by using additional greater-than signs right next to each other…

    \n" + 34 | "
    \n" + 35 | "

    …or with spaces between arrows.

    \n" + 36 | "
    \n" + 37 | "
    \n" + 38 | "
    ") 39 | 40 | expect(blockquote).toBe( 41 | "> Blockquotes can also be nested…\n" + 42 | ">\n" + 43 | ">> …by using additional greater-than signs right next to each other…\n" + 44 | ">>\n" + 45 | ">>> …or with spaces between arrows.") 46 | }) 47 | 48 | it('some independent line tag in blockquote',()=>{ 49 | let blockquote=html2Md("
    \n" + 50 | "

    Blockquotes can also be nested…

    \n" + 51 | "
    \n" + 52 | "

    …by using additional greater-than signs right next to each other…

    \n" + 53 | "
    \n" + 54 | "

    …or with spaces between arrows.

    \n" + 55 | "
    \n" + 56 | "
    \n" + 57 | "
    ") 58 | 59 | expect(blockquote).toBe( 60 | "> Blockquotes can also be nested…\n" + 61 | ">\n" + 62 | ">> …by using additional greater-than signs right next to each other…\n" + 63 | ">>\n" + 64 | ">>> …or with spaces between arrows.") 65 | }) 66 | 67 | it('some independent line tag in blockquote 2',()=>{ 68 | let blockquote=html2Md("
    主题: React\n" + 69 | "
    \n" + 70 | " 难度: ddd\n" + 71 | "

    paragraph1

    \n" + 72 | " raw text1bold\n" + 73 | "

    paragraph2

    \n" + 74 | "
    ") 75 | 76 | expect(blockquote).toBe("> 主题: React \n" + 77 | "> 难度: ~~ddd~~\n" + 78 | ">\n" + 79 | "> paragraph1\n" + 80 | ">\n" + 81 | "> raw text1**bold**\n" + 82 | ">\n" + 83 | "> paragraph2") 84 | }) 85 | 86 | it('multiple br in blockquote',()=>{ 87 | let blockquote=html2Md("
    主题: React\n" + 88 | "


    \n" + 89 | " 难度: ddd\n" + 90 | "

    paragraph1

    \n" + 91 | " raw text1bold\n" + 92 | "

    paragraph2

    \n" + 93 | "
    ") 94 | 95 | expect(blockquote).toBe("> 主题: React \n" + 96 | "> \n" + 97 | "> \n" + 98 | "> 难度: ~~ddd~~\n" + 99 | ">\n" + 100 | "> paragraph1\n" + 101 | ">\n" + 102 | "> raw text1**bold**\n" + 103 | ">\n" + 104 | "> paragraph2") 105 | }) 106 | }) 107 | 108 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/code.test.js: -------------------------------------------------------------------------------- 1 | import Code from '../../src/tags/code' 2 | import html2Md from '../../src/index' 3 | 4 | describe('test tag',()=>{ 5 | it('has textNode',()=>{ 6 | let code=new Code("javascript") 7 | expect(code.exec()).toBe("`javascript`") 8 | }) 9 | 10 | it('tag inside should be render to tag',()=>{ 11 | let code=new Code("dfafbabelfish") 12 | expect(code.exec()).toBe("`dfaf[*babelfish*](https://github.com/nodeca/babelfish/)`") 13 | }) 14 | 15 | it('code match symbol',()=>{ 16 | let str='`123' 17 | expect(html2Md(str)).toBe('`` `123 ``') 18 | }) 19 | 20 | it('code match symbol2',()=>{ 21 | let str='`123``' 22 | expect(html2Md(str)).toBe('``` `123`` ```') 23 | }) 24 | 25 | it('slim space',()=>{ 26 | let str='0 123 ' 27 | expect(html2Md(str)).toBe('`0 123`') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/config.test.js: -------------------------------------------------------------------------------- 1 | import config from '../../src/config' 2 | 3 | describe('跳过指定的tag标签,内部不影响',()=>{ 4 | beforeEach(()=>{ 5 | config.clear() 6 | }) 7 | 8 | it('array,默认concat',()=>{ 9 | config.set({a:[1,2]}) 10 | config.set({a:[2,7]}) 11 | expect(config.get()).toEqual({a:[1,2,2,7]}) 12 | }) 13 | 14 | it('array使用强制参数,则覆盖',()=>{ 15 | config.set({a:[1,2]}) 16 | config.set({a:[2,7]},true) 17 | expect(config.get()).toEqual({a:[2,7]}) 18 | }) 19 | 20 | it('obj,默认assign',()=>{ 21 | config.set({a:{x:1,y:2}}) 22 | config.set({a:{y:3,z:4}}) 23 | expect(config.get()).toEqual({a:{x:1,y:3,z:4}}) 24 | }) 25 | 26 | it('引用值都只处理一层',()=>{ 27 | config.set({a:{x:[1,2],y:[3,4]}}) 28 | config.set({a:{y:[4,5],z:[6,7]}}) 29 | expect(config.get()).toEqual({a:{x:[1,2],y:[4,5],z:[6,7]}}) 30 | }) 31 | 32 | it('obj使用强制参数,则覆盖',()=>{ 33 | config.set({a:{x:1,y:2}}) 34 | config.set({a:{y:3,z:4}},true) 35 | expect(config.get()).toEqual({a:{y:3,z:4}}) 36 | }) 37 | 38 | it('Number直接覆盖',()=>{ 39 | config.set({a:1}) 40 | config.set({a:5}) 41 | expect(config.get()).toEqual({a:5}) 42 | }) 43 | 44 | it('String直接覆盖',()=>{ 45 | config.set({a:'1'}) 46 | config.set({a:'5'}) 47 | expect(config.get()).toEqual({a:'5'}) 48 | }) 49 | 50 | it('Boolean直接覆盖',()=>{ 51 | config.set({a:false}) 52 | config.set({a:true}) 53 | expect(config.get()).toEqual({a:true}) 54 | }) 55 | 56 | }) 57 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/customTag.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | describe('测试自定义标签',()=>{ 4 | 5 | 6 | it('渲染自定义标签 ',()=>{ 7 | expect(html2Md("1234BOLD", {renderCustomTags: true})).toBe("1234**BOLD**") 8 | }) 9 | 10 | 11 | it('渲染自定义标签为 SKIP ',()=>{ 12 | expect(html2Md("1234BOLD",{renderCustomTags: 'SKIP'})).toBe("1234**BOLD**") 13 | expect(html2Md("1234BOLD",{renderCustomTags: false})).toBe("1234**BOLD**") 14 | }) 15 | 16 | it('渲染自定义标签为 EMPTY',()=>{ 17 | expect(html2Md("1234BOLD",{renderCustomTags: 'EMPTY'})).toBe('1234BOLD') 18 | }) 19 | 20 | 21 | it('渲染自定义标签为 IGNORE',()=>{ 22 | expect(html2Md("1234BOLD",{renderCustomTags: 'IGNORE'})).toBe('') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/del.test.js: -------------------------------------------------------------------------------- 1 | import Del from '../../src/tags/del' 2 | 3 | 4 | describe('test tag',()=>{ 5 | it('no nest',()=>{ 6 | let del=new Del("javascript") 7 | expect(del.exec()).toBe("~~javascript~~") 8 | }) 9 | 10 | it('can nest',()=>{ 11 | let del=new Del("babelfish") 12 | expect(del.exec()).toBe("~~[*babelfish*](https://github.com/nodeca/babelfish/)~~") 13 | }) 14 | 15 | it('换行需省略',()=>{ 16 | let del=new Del("\n" + 17 | "babelfish\n" + 18 | "") 19 | expect(del.exec()).toBe("~~[*babelfish*](https://github.com/nodeca/babelfish/)~~") 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/em.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | describe('test tag',()=>{ 3 | it('no nest',()=>{ 4 | let em=html2Md("javascript") 5 | expect(em).toBe("*javascript*") 6 | }) 7 | 8 | it('can nest',()=>{ 9 | let em=html2Md("strong and italic") 10 | expect(em).toBe("***strong and italic***") 11 | }) 12 | 13 | it('换行需要省略',()=>{ 14 | let em=html2Md("\n" + 15 | "strong and italic\n" + 16 | "") 17 | expect(em).toBe("***strong and italic***") 18 | }) 19 | 20 | it('和strong重叠时需要空格',()=>{ 21 | let em=html2Md("和为目标值target") 22 | expect(em).toBe("**和为目标值** *`target`*") 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/empty.test.js: -------------------------------------------------------------------------------- 1 | import {__NoMatch__,__NoMatchSelfClose__} from '../../src/tags/__nomatch__' 2 | 3 | 4 | describe('test empty(not match) tag',()=>{ 5 | it('if not match, do nothing ',()=>{ 6 | let empty=new __NoMatch__("javascript","sub") 7 | expect(empty.exec()).toBe("javascript") 8 | }) 9 | 10 | it('have match tag',()=>{ 11 | let empty=new __NoMatch__("javascript","sup") 12 | expect(empty.exec()).toBe("*javascript*") 13 | }) 14 | 15 | it('no match self-close tag',()=>{ 16 | let empty=new __NoMatchSelfClose__("","embed") 17 | expect(empty.exec()).toBe("") 18 | }) 19 | 20 | 21 | }) 22 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/h1.test.js: -------------------------------------------------------------------------------- 1 | import H1 from '../../src/tags/h1' 2 | import __Heading__ from '../../src/tags/__Heading__' 3 | 4 | 5 | describe('test

    tag',()=>{ 6 | it('no nest',()=>{ 7 | let h1=new H1("

    javascript

    ") 8 | expect(h1.exec()).toBe("\n# javascript\n") 9 | }) 10 | 11 | it('default H1',()=>{ 12 | let h1=new __Heading__("

    javascript

    ") 13 | expect(h1.exec()).toBe("\n# javascript\n") 14 | }) 15 | 16 | it('can nest',()=>{ 17 | let h1=new H1("

    strong and italic

    ") 18 | expect(h1.exec()).toBe("\n# ***strong and italic***\n") 19 | }) 20 | 21 | it('can nest-2',()=>{ 22 | let h1=new H1("

    Subscript / Superscript

    ") 23 | expect(h1.exec()).toBe("\n" + 24 | "# [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)\n") 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/h2.test.js: -------------------------------------------------------------------------------- 1 | import H2 from '../../src/tags/h2' 2 | 3 | 4 | describe('test

    tag',()=>{ 5 | it('no nest',()=>{ 6 | let h2=new H2("

    javascript

    ") 7 | expect(h2.exec()).toBe("\n## javascript\n") 8 | }) 9 | 10 | it('can nest',()=>{ 11 | let h2=new H2("

    strong and italic

    ") 12 | expect(h2.exec()).toBe("\n## ***strong and italic***\n") 13 | }) 14 | 15 | it('can nest-2',()=>{ 16 | let h2=new H2("

    Subscript / Superscript

    ") 17 | expect(h2.exec()).toBe("\n" + 18 | "## [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)\n") 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/h3.test.js: -------------------------------------------------------------------------------- 1 | import H3 from '../../src/tags/h3' 2 | 3 | 4 | describe('test

    tag',()=>{ 5 | it('no nest',()=>{ 6 | let h3=new H3("

    javascript

    ") 7 | expect(h3.exec()).toBe("\n### javascript\n") 8 | }) 9 | 10 | it('can nest',()=>{ 11 | let h3=new H3("

    strong and italic

    ") 12 | expect(h3.exec()).toBe("\n### ***strong and italic***\n") 13 | }) 14 | 15 | it('can nest-2',()=>{ 16 | let h3=new H3("

    Subscript / Superscript

    ") 17 | expect(h3.exec()).toBe("\n" + 18 | "### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)\n") 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/h4.test.js: -------------------------------------------------------------------------------- 1 | import H4 from '../../src/tags/h4' 2 | 3 | 4 | describe('test

    tag',()=>{ 5 | it('no nest',()=>{ 6 | let h4=new H4("

    javascript

    ") 7 | expect(h4.exec()).toBe("\n#### javascript\n") 8 | }) 9 | 10 | it('can nest',()=>{ 11 | let h4=new H4("

    strong and italic

    ") 12 | expect(h4.exec()).toBe("\n#### ***strong and italic***\n") 13 | }) 14 | 15 | it('can nest-2',()=>{ 16 | let h4=new H4("

    Subscript / Superscript

    ") 17 | expect(h4.exec()).toBe("\n" + 18 | "#### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)\n") 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/h5.test.js: -------------------------------------------------------------------------------- 1 | import H5 from '../../src/tags/h5' 2 | 3 | 4 | describe('test
    tag',()=>{ 5 | it('no nest',()=>{ 6 | let h5=new H5("
    javascript
    ") 7 | expect(h5.exec()).toBe("\n##### javascript\n") 8 | }) 9 | 10 | it('can nest',()=>{ 11 | let h5=new H5("
    strong and italic
    ") 12 | expect(h5.exec()).toBe("\n##### ***strong and italic***\n") 13 | }) 14 | 15 | it('can nest-2',()=>{ 16 | let h5=new H5("
    Subscript / Superscript
    ") 17 | expect(h5.exec()).toBe("\n" + 18 | "##### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)\n") 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/h6.test.js: -------------------------------------------------------------------------------- 1 | import H6 from '../../src/tags/h6' 2 | 3 | describe('test
    tag',()=>{ 4 | it('no nest',()=>{ 5 | let h6=new H6("
    javascript
    ") 6 | expect(h6.exec()).toBe("\n###### javascript\n") 7 | }) 8 | 9 | it('can nest',()=>{ 10 | let h6=new H6("
    strong and italic
    ") 11 | expect(h6.exec()).toBe("\n###### ***strong and italic***\n") 12 | }) 13 | 14 | it('can nest-2',()=>{ 15 | let h6=new H6("
    Subscript / Superscript
    ") 16 | expect(h6.exec()).toBe("\n" + 17 | "###### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)\n") 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/hr.test.js: -------------------------------------------------------------------------------- 1 | import Hr from '../../src/tags/hr' 2 | 3 | describe('test
    tag',()=>{ 4 | it('self-close',()=>{ 5 | let hr=new Hr("
    ") 6 | expect(hr.exec()).toBe("\n---\n") 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/i.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | import {SYMBOL_I,SYMBOL_B} from '../options' 3 | describe('test tag',()=>{ 4 | it('no nest',()=>{ 5 | expect(html2Md("javascript")).toBe(SYMBOL_I+"javascript"+SYMBOL_I) 6 | }) 7 | 8 | it('can nest',()=>{ 9 | expect(html2Md("strong and italic")).toBe(SYMBOL_I+SYMBOL_B+"strong and italic"+SYMBOL_B+SYMBOL_I) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/img.test.js: -------------------------------------------------------------------------------- 1 | import Img from '../../src/tags/img' 2 | 3 | 4 | describe('test tag',()=>{ 5 | it('has alt',()=>{ 6 | let img=new Img("\"Minion\"") 7 | expect(img.exec()).toBe("![Minion](https://octodex.github.com/images/minion.png)") 8 | }) 9 | 10 | it('no alt',()=>{ 11 | let img=new Img("") 12 | expect(img.exec()).toBe("![](https://octodex.github.com/images/minion.png)") 13 | }) 14 | 15 | it('empty alt',()=>{ 16 | let img=new Img("\"\"") 17 | expect(img.exec()).toBe("![](https://octodex.github.com/images/minion.png)") 18 | }) 19 | 20 | it('= in attr value should keep',()=>{ 21 | let img=new Img(``) 22 | expect(img.exec()).toBe('![](https://www.zhihu.com/equation?tex=A%5Cmathbf%7Bu%7D+%3D+%5Clambda%5Cmathbf%7Bu%7D%5C%5C)') 23 | }) 24 | 25 | it(' " or \' in src value',()=>{ 26 | let img=new Img(``) 27 | expect(img.exec()).toBe('![](http://abc\'cde.png)') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/input.test.js: -------------------------------------------------------------------------------- 1 | import Input from '../../src/tags/input' 2 | import Li from '../../src/tags/li' 3 | 4 | 5 | describe('test tag',()=>{ 6 | it('render alone, should do nothing',()=>{ 7 | let input=new Input("") 8 | expect(input.exec()).toBe("") 9 | }) 10 | 11 | it('parent tag is li, should be parsed(checked)',()=>{ 12 | let li=new Li("
  • checked
  • ") 13 | expect(li.exec()).toBe("\n* [x] checked\n") 14 | }) 15 | 16 | it('parent tag is li, should be parsed(no checked)',()=>{ 17 | let li=new Li('
  • not checked
  • ') 18 | expect(li.exec()).toBe("\n* [ ] not checked\n") 19 | }) 20 | 21 | it('parent tag is li, but is not checkbox, do nothing)',()=>{ 22 | let li=new Li('
  • text
  • ') 23 | expect(li.exec()).toBe('\n' + 24 | '* text\n') 25 | }) 26 | 27 | // it('parent tag is li, but checkbox is not disabled, do nothing)',()=>{ 28 | // let li=new Li('
  • not disabled
  • ') 29 | // expect(li.exec()).toBe('\n' + 30 | // '* not disabled\n') 31 | // }) 32 | 33 | it('parent tag is li, but checkbox is not disabled, will auto convert to disable)',()=>{ 34 | let li=new Li('
  • not disabled
  • ') 35 | expect(li.exec()).toBe("\n" + 36 | "* [x] not disabled\n") 37 | }) 38 | 39 | 40 | }) 41 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/li.test.js: -------------------------------------------------------------------------------- 1 | import Li from '../../src/tags/li' 2 | 3 | 4 | describe('test
  • tag',()=>{ 5 | it('no nest',()=>{ 6 | let li=new Li("
  • javascript
  • ") 7 | expect(li.exec()).toBe("\n" + 8 | "* javascript\n") 9 | }) 10 | 11 | it('can nest',()=>{ 12 | let li=new Li("
  • strong
  • ") 13 | expect(li.exec()).toBe("\n* **strong**\n") 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/ol.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | const OL_SPACE=3 3 | const UL_SPACE=2 4 | 5 | describe("test
      tag",()=>{ 6 | 7 | it('order list',()=>{ 8 | let ol=html2Md('
        \n' + 9 | '
      1. one
      2. \n' + 10 | '
      3. two
      4. \n' + 11 | '
      5. three
      6. \n' + 12 | '
      ') 13 | expect(ol).toBe('1.'+' '.repeat(OL_SPACE-2)+'one\n' + 14 | '2.'+' '.repeat(OL_SPACE-2)+'two\n' + 15 | '3.'+' '.repeat(OL_SPACE-2)+'three') 16 | }) 17 | 18 | it('nest order list',()=>{ 19 | let ol=html2Md('
        \n' + 20 | '
      1. one
      2. \n' + 21 | '
      3. two\n' + 22 | '
          \n' + 23 | '
        1. one
        2. \n' + 24 | '
        3. two
        4. \n' + 25 | '
        5. three
        6. \n' + 26 | '
        \n' + 27 | '
      4. \n' + 28 | '
      5. three
      6. \n' + 29 | '
      ') 30 | expect(ol).toBe( 31 | '1.'+' '.repeat(OL_SPACE-2)+'one\n' + 32 | '2.'+ ' '.repeat(OL_SPACE-2)+'two\n' + 33 | ' '.repeat(OL_SPACE)+ '1.'+' '.repeat(OL_SPACE-2)+'one\n' + 34 | ' '.repeat(OL_SPACE)+ '2.'+' '.repeat(OL_SPACE-2)+'two\n' + 35 | ' '.repeat(OL_SPACE)+ '3.'+' '.repeat(OL_SPACE-2)+'three\n' + 36 | '3.'+ ' '.repeat(OL_SPACE-2)+ 'three') 37 | }) 38 | 39 | 40 | it('nest ul',()=>{ 41 | let ol=html2Md('
        \n' + 42 | '
      1. one
      2. \n' + 43 | '
      3. two\n' + 44 | '
          \n' + 45 | '
        • unorder-1
        • \n' + 46 | '
        • unorder-2
        • \n' + 47 | '
        • unorder-3
        • \n' + 48 | '
        \n' + 49 | '
      4. \n' + 50 | '
      5. three
      6. \n' + 51 | '
      ') 52 | expect(ol).toBe( 53 | '1.'+' '.repeat(OL_SPACE-2)+'one\n' + 54 | '2.'+' '.repeat(OL_SPACE-2)+'two\n' + 55 | ' '.repeat(OL_SPACE)+ '* '+' '.repeat(UL_SPACE-2)+'unorder-1\n' + 56 | ' '.repeat(OL_SPACE)+ '* '+' '.repeat(UL_SPACE-2)+'unorder-2\n' + 57 | ' '.repeat(OL_SPACE)+ '* '+' '.repeat(UL_SPACE-2)+'unorder-3\n' + 58 | '3.'+' '.repeat(OL_SPACE-2)+'three') 59 | }) 60 | 61 | it('complicate nest',()=>{ 62 | let ol=html2Md('
        \n' + 63 | '
      1. STRONG
      2. \n' + 64 | '
      3. ATag\n' + 65 | '
          \n' + 66 | '
        • unorder-1
        • \n' + 67 | '
        • unorder-2\n' + 68 | '
            \n' + 69 | '
          1. one
          2. \n' + 70 | '
          3. two\n' + 71 | '
            \n' + 72 | '
              \n' + 73 | '
            • bq-nest-1
            • \n' + 74 | '
            \n' + 75 | '
            \n' + 76 | '
              \n' + 77 | '
            • bq-nest-2
            • \n' + 78 | '
            \n' + 79 | '
            \n' + 80 | '
              \n' + 81 | '
            • bq-nest-3
            • \n' + 82 | '
            \n' + 83 | '
            \n' + 84 | '
            \n' + 85 | '
            \n' + 86 | '
          4. \n' + 87 | '
          \n' + 88 | '
        • \n' + 89 | '
        • unorder-3\n' + 90 | '
            \n' + 91 | '
          • code
            var a=5\n' +
             92 |       '
            \n' + 93 | '
          • \n' + 94 | '
          \n' + 95 | '
        • \n' + 96 | '
        \n' + 97 | '
      4. \n' + 98 | '
      5. three
      6. \n' + 99 | '
      ') 100 | expect(ol).toBe( 101 | '1.'+' '.repeat(OL_SPACE-2)+ '**STRONG**\n' + 102 | '2.'+' '.repeat(OL_SPACE-2)+ '[ATag](https://github.com/-it/markdown-it-sub)\n' + 103 | '\n'+ 104 | ' '.repeat(OL_SPACE)+'*'+' '.repeat(UL_SPACE-1)+ 'unorder-1\n' + 105 | ' '.repeat(OL_SPACE)+'*'+' '.repeat(UL_SPACE-1)+ 'unorder-2\n' + 106 | ' '.repeat(OL_SPACE+UL_SPACE)+'1.'+' '.repeat(OL_SPACE-2)+ 'one\n' + 107 | ' '.repeat(OL_SPACE+UL_SPACE)+'2.'+' '.repeat(OL_SPACE-2)+ 'two\n' + 108 | ' '.repeat(OL_SPACE*2+UL_SPACE)+'> *'+' '.repeat(UL_SPACE-1)+ 'bq-nest-1\n' + 109 | ' '.repeat(OL_SPACE*2+UL_SPACE)+'>\n' + 110 | ' '.repeat(OL_SPACE*2+UL_SPACE)+'>> *'+' '.repeat(UL_SPACE-1)+ 'bq-nest-2\n' + 111 | ' '.repeat(OL_SPACE*2+UL_SPACE)+'>>\n' + 112 | ' '.repeat(OL_SPACE*2+UL_SPACE)+'>>> *'+' '.repeat(UL_SPACE-1)+ 'bq-nest-3\n' + 113 | ' '.repeat(OL_SPACE)+'*'+' '.repeat(UL_SPACE-1)+ 'unorder-3\n' + 114 | ' '.repeat(OL_SPACE+UL_SPACE)+'*'+' '.repeat(UL_SPACE-1)+ 'code\n' + 115 | ' '.repeat(OL_SPACE+2*UL_SPACE)+'```js\n' + 116 | ' '.repeat(OL_SPACE+2*UL_SPACE)+'var a=5\n' + 117 | ' '.repeat(OL_SPACE+2*UL_SPACE)+'```\n' + 118 | '3.'+' '.repeat(OL_SPACE-2)+ 'three') 119 | }) 120 | 121 | 122 | it("li nest p",()=>{ 123 | let ol=html2Md("
        \n" + 124 | "
      1. \n" + 125 | "

        Lorem ipsum dolor sit amet

        \n" + 126 | "
      2. \n" + 127 | "
      3. \n" + 128 | "

        Consectetur adipiscing elit

        \n" + 129 | "
      4. \n" + 130 | "
      5. \n" + 131 | "

        Integer molestie lorem at massa

        \n" + 132 | "
      6. \n" + 133 | "
      7. \n" + 134 | "

        You can use sequential numbers…

        \n" + 135 | "
      8. \n" + 136 | "
      9. \n" + 137 | "

        …or keep all the numbers as 1.

        \n" + 138 | "
      10. \n" + 139 | "
      ") 140 | 141 | expect(ol).toBe( 142 | '1.'+' '.repeat(OL_SPACE-2)+ 'Lorem ipsum dolor sit amet\n' + 143 | '\n' + 144 | '2.'+' '.repeat(OL_SPACE-2)+ 'Consectetur adipiscing elit\n' + 145 | '\n' + 146 | '3.'+' '.repeat(OL_SPACE-2)+ 'Integer molestie lorem at massa\n' + 147 | '\n' + 148 | '4.'+' '.repeat(OL_SPACE-2)+ 'You can use sequential numbers…\n' + 149 | '\n' + 150 | '5.'+' '.repeat(OL_SPACE-2)+ '…or keep all the numbers as `1.`') 151 | }) 152 | }) 153 | 154 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/p.test.js: -------------------------------------------------------------------------------- 1 | import html2md from '../../src' 2 | import P from '../../src/tags/p' 3 | import config from '../../src/config' 4 | 5 | config.set({renderCustomTags:true}) 6 | 7 | describe('test

      tag',()=>{ 8 | it('textNode',()=>{ 9 | let p=new P("

      This is paragraph

      ") 10 | expect(p.exec()).toBe("\nThis is paragraph\n") 11 | }) 12 | it('nest',()=>{ 13 | let p=new P("

      bold

      ") 14 | expect(p.exec()).toBe("\n**bold**\n") 15 | }) 16 | 17 | it('nest2',()=>{ 18 | let p=new P("

      SDSSDF<>SSDF<>

      ") 19 | expect(p.exec()).toBe('\n' + 20 | '~~SDSSDF<>SSDF<>~~\n') 21 | }) 22 | 23 | it('p tag inside string, should have extra gap',()=>{ 24 | let p=new P("

      一款集成了模拟和拦截

      请求并拥有一

      定编程能力的谷歌浏览器插件...

      ") 25 | expect(p.exec()).toBe("\n" + 26 | "一款集成了模拟和拦截\n" + 27 | "\n" + 28 | "请求并拥有一\n" + 29 | "\n" + 30 | "定编程能力的谷歌浏览器插件...\n") 31 | }) 32 | 33 | it('p tag gaps 1',()=>{ 34 | let str=html2md("

      1234

       \n\n\n\n 

      5678

      ") 35 | expect(str).toBe("1234\n\n5678") 36 | }) 37 | 38 | it('p tag gaps 2',()=>{ 39 | let str=html2md("

      1234

        

      5678

      ") 40 | expect(str).toBe("1234\n\n5678") 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/s.test.js: -------------------------------------------------------------------------------- 1 | import S from '../../src/tags/s' 2 | 3 | 4 | describe('test tag',()=>{ 5 | it('no nest',()=>{ 6 | let s=new S("javascript") 7 | expect(s.exec()).toBe("~~javascript~~") 8 | }) 9 | 10 | it('can nest',()=>{ 11 | let s=new S("babelfish") 12 | expect(s.exec()).toBe("~~[*babelfish*](https://github.com/nodeca/babelfish/)~~") 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/space.test.js: -------------------------------------------------------------------------------- 1 | import P from '../../src/tags/p' 2 | import html2Md from '../../src/index' 3 | describe('Remove some space',()=>{ 4 | 5 | 6 | it('The space between tags should be remove',()=>{ 7 | let spaceHtml=new P("

      strong

      ") 8 | expect(spaceHtml.exec()).toBe("\n" + 9 | "**strong**\n") 10 | }) 11 | 12 | it('The space between tags should be remove 2',()=>{ 13 | let spaceHtml=html2Md("
      \n" + 14 | "
        \n" + 15 | "
      • PHP Manual
      • \n" + 16 | "
      • APCIterator
      • \n" + 17 | "
      • Constructs an APCIterator iterator object
      • \n" + 18 | "
      \n" + 19 | "
      \n" + 20 | "
      \n" + 21 | "
      \n" + 22 | "
      \n" + 23 | "

      APCIterator::__construct

      \n" + 24 | "
      ") 25 | expect(spaceHtml).toBe( 26 | "* [PHP Manual](index.html)\n" + 27 | "* [APCIterator](class.apciterator.html)\n" + 28 | "* Constructs an APCIterator iterator object\n" + 29 | "\n" + 30 | "# APCIterator::\\_\\_construct") 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/span.test.js: -------------------------------------------------------------------------------- 1 | import Span from '../../src/tags/span' 2 | import html2Md from '../../src/index' 3 | 4 | 5 | describe('test tag',()=>{ 6 | it('no nest',()=>{ 7 | let span=new Span("javascript") 8 | expect(span.exec()).toBe("javascript") 9 | }) 10 | 11 | 12 | it('code in span will also resolve',()=>{ 13 | let span=new Span("strong") 14 | expect(span.exec()).toBe("**strong**") 15 | }) 16 | 17 | // it('span will treat as p, but no change line',()=>{ 18 | // let spanResStr=html2Md("strongstrong") 19 | // expect(spanResStr).toBe("**strongstrong**") 20 | // }) 21 | 22 | 23 | }) 24 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/unit/strong.test.js: -------------------------------------------------------------------------------- 1 | import Strong from '../../src/tags/strong' 2 | import B from "../../src/tags/b"; 3 | import html2md from '../../src'; 4 | 5 | describe("test tag",()=>{ 6 | it('no nest',()=>{ 7 | let strong=new Strong("strong") 8 | expect(strong.exec()).toBe("**strong**") 9 | }) 10 | 11 | it('can nest',()=>{ 12 | let strong=new Strong("strong and italic") 13 | expect(strong.exec()).toBe("***strong and italic***") 14 | }) 15 | 16 | it('换行需要省略',()=>{ 17 | let strong=new Strong("\n" + 18 | "b and italic\n" + 19 | "") 20 | expect(strong.exec()).toBe("***b and italic***") 21 | }) 22 | it('和em重叠时需要空格',()=>{ 23 | let test=html2md("和为目标值target") 24 | expect(test).toBe("*和为目标值* **`target`**") 25 | }) 26 | 27 | it('遇到match符号不需要空格',()=>{ 28 | let test=html2md("**一个标题**-- 第二个标题") 29 | expect(test).toBe("\\*\\*一个标题\\*\\***\\-- 第二个标题**") 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/utils/alias-tags.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | describe('替换标签',()=>{ 4 | 5 | it('figure as div',()=>{ 6 | expect(html2Md('
      Figure show as div
      ')).toBe("Figure show as div") 7 | }) 8 | 9 | it('figcaption as p',()=>{ 10 | expect(html2Md( 11 | `
      12 | 13 |
      Fig.1 - Trulli, Puglia, Italy.
      14 |
      `)).toBe("![](someimg.jpg)\n" + 15 | "\n" + 16 | "Fig.1 - Trulli, Puglia, Italy.") 17 | }) 18 | 19 | it('No li in ul, but use alias-tag',()=>{ 20 | expect(html2Md( 21 | `
        22 | this b is alias as li 23 | this i is alias as li 24 |
      ` 25 | , {aliasTags: {b: 'li', i:'li'}})) 26 | .toBe( 27 | `* this b is alias as li 28 | * this i is alias as li` 29 | )}) 30 | 31 | it('No li in ol, but use alias-tag',()=>{ 32 | expect(html2Md( 33 | `
        34 | this b is alias as li 35 | this i is alias as li 36 |
      ` 37 | , {aliasTags: {b: 'li', i:'li'}})) 38 | .toBe( 39 | `1. this b is alias as li 40 | 2. this i is alias as li` 41 | )}) 42 | 43 | }) 44 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/utils/clearComment.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | describe('清空注释',()=>{ 4 | 5 | it('清除注释1',()=>{ 6 | expect(html2Md('strong')).toBe("**strong**") 7 | }) 8 | 9 | it('清除注释2',()=>{ 10 | expect(html2Md('' + 11 | 'strong')).toBe("**strong**") 18 | }) 19 | 20 | it('注释存在 \n \t都省略',()=>{ 21 | expect(html2Md(`strong 25 | `)).toBe('**strong**') 33 | }) 34 | 35 | }) 36 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/utils/generateGetNextValidTag.test.js: -------------------------------------------------------------------------------- 1 | import { generateGetNextValidTag } from '../../src/utils' 2 | 3 | 4 | describe('测试寻找标签',()=>{ 5 | 6 | it('Normal tags',()=>{ 7 | let findFunc=generateGetNextValidTag('
      abc
      ') 8 | expect(findFunc()).toStrictEqual(["div", "
      abc
      "]) 9 | }) 10 | 11 | it('Have sub tags',()=>{ 12 | expect(generateGetNextValidTag('
      Strong
      ')()).toStrictEqual(["div", "
      Strong
      "]) 13 | }) 14 | 15 | it('Self close tag',()=>{ 16 | expect(generateGetNextValidTag('')()).toStrictEqual(["img", ""]) 17 | }) 18 | 19 | it('Self close tag2',()=>{ 20 | expect(generateGetNextValidTag('')()).toStrictEqual(["img", ""]) 21 | }) 22 | 23 | it('No tags',()=>{ 24 | expect(generateGetNextValidTag('xabc content text only')()).toStrictEqual([null,"xabc content text only"]) 25 | }) 26 | 27 | it('Have < in content',()=>{ 28 | expect(generateGetNextValidTag(' in content',()=>{ 32 | expect(generateGetNextValidTag('ttent<')()).toStrictEqual([null, "ttent<"]) 33 | }) 34 | 35 | it('Unvalid tag',()=>{ 36 | expect(generateGetNextValidTag('
      abc')()).toStrictEqual([null, "abc"]) 37 | }) 38 | it('Unvalid tag with valid sub tags',()=>{ 39 | expect(generateGetNextValidTag('
      italyStrong')()).toStrictEqual([null,'italyStrong']) 40 | }) 41 | 42 | it('Unvalid tag with valid sub tags with SPACE',()=>{ 43 | expect(generateGetNextValidTag('
      italyStrong')()).toStrictEqual([null,'italyStrong']) 44 | }) 45 | 46 | it('Multi tags',()=>{ 47 | expect(generateGetNextValidTag(`dfafbabelfish`)()).toStrictEqual(["span", "dfaf"]) 48 | }) 49 | 50 | it('some test',()=>{ 51 | expect(generateGetNextValidTag("
      ssss")()).toStrictEqual(["div", "
      ssss"]) 52 | }) 53 | 54 | it('tags with -',()=>{ 55 | expect(generateGetNextValidTag("ssss")()).toStrictEqual(["ab-cd", "ssss"]) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/utils/getLanguage.test.js: -------------------------------------------------------------------------------- 1 | import { getLanguage } from '../../src/utils' 2 | 3 | describe('find the lang',()=>{ 4 | 5 | it('end with lang',()=>{ 6 | expect(getLanguage('
      let abc
      ')).toBe('jsx') 7 | }) 8 | it('end with space',()=>{ 9 | expect(getLanguage('
      let abc
      ')).toBe('jsx') 10 | }) 11 | it('end with other class',()=>{ 12 | expect(getLanguage('
      let abc
      ')).toBe('jsx') 13 | }) 14 | 15 | it('without highlight empty',()=>{ 16 | expect(getLanguage('
      let abc
      ')).toBe('') 17 | }) 18 | 19 | it('with highlight default',()=>{ 20 | expect(getLanguage('
      let abc
      ')).toBe('javascript') 21 | }) 22 | 23 | it('with no lang',()=>{ 24 | expect(getLanguage('
      let abc
      ')).toBe('') 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/__test__/utils/tagListener.test.js: -------------------------------------------------------------------------------- 1 | import html2Md from '../../src/index' 2 | 3 | describe('Tag listener',()=>{ 4 | 5 | it('tagListener language',()=>{ 6 | expect(html2Md('
      var a = 2
      ', {tagListener:(tagName, props)=>{ 7 | return { 8 | ...props, 9 | language: props.language === 'tsx' ? 'typescript' : 'javascript' 10 | } 11 | }})).toBe("```typescript\n" + 12 | "var a = 2\n" + 13 | "```") 14 | }) 15 | 16 | it('tagListener change match symbol',()=>{ 17 | expect(html2Md('Not bold, del instead', {tagListener:(tagName, props)=>{ 18 | return { 19 | ...props, 20 | match: props.match === '**' ? '~~' : props.match 21 | } 22 | }})).toBe("~~Not bold, del instead~~") 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-to-md", 3 | "version": "0.8.3", 4 | "description": "A JS library for convert HTML to markdown, gzip 10kb", 5 | "main": "dist/index.js", 6 | "keywords": [ 7 | "html2md", 8 | "html2markdown", 9 | "htmlToMarkdown", 10 | "parseHtml", 11 | "markdown", 12 | "html" 13 | ], 14 | "author": "stonehank ", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/stonehank/html-to-md.git" 18 | }, 19 | "homepage": "https://github.com/stonehank/html-to-md#README", 20 | "license": "MIT", 21 | "scripts": { 22 | "prepublishOnly": "node prepublish.js", 23 | "release": "npm run build && cross-env RELEASE_MODE=true npm publish", 24 | "test:part": "jest", 25 | "test": "jest --coverage && codecov", 26 | "build": "npm run test && npm run build:demo && npm run build:main", 27 | "build:main": "export NODE_OPTIONS=--openssl-legacy-provider && cross-env NODE_ENV=production webpack", 28 | "build:demo": "export NODE_OPTIONS=--openssl-legacy-provider && cross-env NODE_ENV=production webpack --config webpack.demo.config.js", 29 | "start": "cross-env NODE_ENV=development webpack-dev-server --open --config webpack.demo.config.js" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "7.4.5", 33 | "@babel/preset-env": "7.4.5", 34 | "@babel/preset-typescript": "^7.18.6", 35 | "@typescript-eslint/eslint-plugin": "^5.40.0", 36 | "babel-loader": "8.0.6", 37 | "clean-webpack-plugin": "3.0.0", 38 | "codecov": "^3.8.3", 39 | "cross-env": "7.0.3", 40 | "css-loader": "3.0.0", 41 | "eslint": "^8.25.0", 42 | "eslint-config-prettier": "^8.5.0", 43 | "eslint-config-standard": "^17.0.0", 44 | "eslint-config-standard-with-typescript": "^23.0.0", 45 | "eslint-plugin-import": "^2.26.0", 46 | "eslint-plugin-n": "^15.3.0", 47 | "eslint-plugin-prettier": "^4.2.1", 48 | "eslint-plugin-promise": "^6.1.0", 49 | "highlight.js": "9.15.8", 50 | "html-loader": "0.5.5", 51 | "html-webpack-plugin": "3.2.0", 52 | "jest": "24.8.0", 53 | "markdown-it": "8.4.2", 54 | "marked": "0.6.2", 55 | "postcss-flexbugs-fixes": "4.1.0", 56 | "postcss-loader": "3.0.0", 57 | "postcss-preset-env": "6.6.0", 58 | "prettier": "^2.7.1", 59 | "style-loader": "0.23.1", 60 | "terser-webpack-plugin": "1.3.0", 61 | "ts-loader": "^8.4.0", 62 | "typescript": "^4.8.4", 63 | "webpack": "4.33.0", 64 | "webpack-bundle-analyzer": "3.3.2", 65 | "webpack-cli": "3.3.11", 66 | "webpack-dev-server": "3.7.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/prepublish.js: -------------------------------------------------------------------------------- 1 | const RELEASE_MODE = !!process.env.RELEASE_MODE 2 | 3 | if (!RELEASE_MODE) { 4 | console.log('Run `npm run release` to publish the package') 5 | process.exit(1) // which terminates the publish process 6 | } 7 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/AFT.ts: -------------------------------------------------------------------------------- 1 | import { AFTSvgProcessOpt } from "./type"; 2 | 3 | function encode(txt:string) { 4 | return Buffer.from(txt).toString('hex'); 5 | } 6 | 7 | function base64(str:string) { 8 | const binString = Array.from(new TextEncoder().encode(str), (x) => String.fromCodePoint(x)).join(""); 9 | return btoa(binString); 10 | } 11 | function deBase64(str:string) { 12 | const binString = atob(str); 13 | return new TextDecoder().decode(Uint8Array.from(binString, (m:string) => { 14 | let t = m.codePointAt(0); 15 | if (typeof t == 'undefined') return 1114112; 16 | return t; 17 | })); 18 | } 19 | 20 | export function makePicUrl(url: string, processServerHost: string, processPicOpt: string,processSvgPicOpt:AFTSvgProcessOpt) { 21 | if (!processServerHost.startsWith('http://')) 22 | processServerHost = `http://${processServerHost}`; 23 | if (!processServerHost.endsWith('/')) 24 | processServerHost = `${processServerHost}/`; 25 | return `${processServerHost}${encode(url)}/${encode(processPicOpt)}/${encode(JSON.stringify(processSvgPicOpt))}`; 26 | } 27 | 28 | export function smartURL(url: string, nowUrl: string) { 29 | if (typeof url!='string') throw new TypeError('url must be a string'); 30 | if (typeof nowUrl!='string') throw new TypeError('nowUrl must be a string'); 31 | var u = new URL(nowUrl); 32 | if (url.trim().length==0) return ''; 33 | if (url.startsWith('https://')) return url; 34 | if (url.startsWith('http://')) return url; 35 | if (url.startsWith('data:')) return url; 36 | if (url.startsWith('#')) return url; 37 | if (url.startsWith('javascript:')) return '#'; 38 | if (url.startsWith('//')) return `${u.protocol}${url}`; 39 | if (url.startsWith('/')) return `${u.protocol}//${u.host}${url}`; 40 | if (u.pathname.endsWith('/')) return `${u.protocol}//${u.host}${u.pathname}${url}`; 41 | let t = u.pathname.split('/'); 42 | t.pop(); 43 | return `${u.protocol}//${u.host}${t.join('/')}/${url}`; 44 | } 45 | 46 | function svgDealer(src:string,opt:AFTSvgProcessOpt){ 47 | if (src.includes(' style="')){ 48 | src = src.replace(/( style=")/g,' style="color:white;'); 49 | }else{ 50 | src = src.replace('{ 59 | var o = input.split(''); 60 | while (/a-zA-Z/.test(o[o.length-1])){ 61 | o.pop(); 62 | if (o.length==0) break; 63 | } 64 | return o.join(''); 65 | }; 66 | var w = src.split(' width="')[1].split('"')[0]; 67 | var wn = parseFloat(f(w)); 68 | var h = src.split(' height="')[1].split('"')[0]; 69 | var hn = parseFloat(f(h)); 70 | if (w.includes('ex') && h.includes('ex')){ 71 | hn*=10; 72 | wn*=10; 73 | }else{ 74 | hn = (opt.maxWidth/wn) * hn; 75 | wn = opt.maxWidth; 76 | } 77 | if (wn>opt.maxWidth){ 78 | hn = (opt.maxWidth/wn) * hn; 79 | wn = opt.maxWidth; 80 | } 81 | if (hn>opt.maxHight){ 82 | wn = (opt.maxHight/hn); 83 | hn = opt.maxHight; 84 | } 85 | src = src.replace(` width="${w}"`,` width="${wn}px"`); 86 | src = src.replace(` height="${h}"`,` height="${hn}px"`); 87 | return src; 88 | } 89 | 90 | export function picProcess(data:string,nowUrl:string,picProcessHost:string,picProcessOpt:string,picSvgProcessOpt:AFTSvgProcessOpt){ 91 | if (data.startsWith('data:image/svg+xml;utf8,') || data.startsWith('#') || data.startsWith('javascript:')) return ''; 92 | if (data.startsWith('data:image/svg+xml;base64,')){ 93 | return `data:image/svg+xml;base64,${base64(svgDealer(deBase64(data.split(',')[1]),picSvgProcessOpt))}`; 94 | } 95 | if (data.startsWith('data:')){ 96 | return data; 97 | } 98 | return makePicUrl(smartURL(data,nowUrl),picProcessHost,picProcessOpt,picSvgProcessOpt); 99 | } 100 | 101 | export function wrapPic(type:'a'|'li',picNoWrap:boolean,str:string,data:string,layer:number,linkIndex:number){ 102 | if (layer>3) layer = 3; 103 | //str = str.replace(/[☈]/g,''); 104 | //console.log('in',str); 105 | if (type=='li' && picNoWrap) return str; 106 | var out:string[] = []; 107 | var o = ''; 108 | var linkIndexTxt = (linkIndex==-1) ? '' : `\\[${linkIndex}\\]`; 109 | str.split('\n').forEach(c=>{ 110 | if (c.replace(/[ \*☈]/g,'').trim().length==0) return; 111 | if (c.startsWith('![') && c.endsWith(')') && c.includes('](')){ 112 | let t = out.join('').trim(); 113 | if (t.length>0){ 114 | if (type=='a') o+= ` [${t}${linkIndexTxt}](${data}) `; 115 | if (type=='li'){ 116 | if (!t.startsWith('* ') && !t.startsWith('☈☈')){ 117 | if(o.length>0) o += `\n${'☈☈'.repeat(layer-1)}* `; 118 | if (o.length==0) o+= `* `; 119 | }else{ 120 | t = `\n${t}`; 121 | } 122 | o += t; 123 | } 124 | //console.log(t,o); 125 | } 126 | if (picNoWrap) o+=`\n${c}\n`; 127 | if (!picNoWrap) o+= `\n\n \n${c} \n\n\n`; 128 | out = []; 129 | return; 130 | } 131 | var d = c; 132 | if (c.trim().startsWith('☈☈') && !c.trim().includes('☈☈* ') && type=='li'){ 133 | d = ''; 134 | var tt = c.trim(); 135 | for(var i=0;i0 || out.length>0)?`\n`:''}${(d.trim().startsWith('☈☈') || d.trim().startsWith('* ')) ? '' : `${'☈☈'.repeat(layer-1)}* `}${d}` : c); 145 | }); 146 | if (out.length>0){ 147 | if (type=='a') o+=` [${out.join('').trim()}${linkIndexTxt}](${data}) `; 148 | //if (type=='li') o+=`${o.length>0 ? `\n${'*'.repeat(layer)}` : '* '}${out.join('').trim()}`; 149 | if (type=='li') o+= out.join(''); 150 | } 151 | //console.log('ret',o); 152 | return o; 153 | } -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/SelfCloseTag.ts: -------------------------------------------------------------------------------- 1 | import { AFTExtraData, SelfCloseTagOptions, SelfCloseTagProps, TagName } from './type' 2 | import { getTagAttributes } from './utils' 3 | import config from './config' 4 | 5 | class SelfCloseTag implements SelfCloseTagProps { 6 | constructor( 7 | str: string, 8 | tagName: TagName, 9 | { 10 | parentTag = '', 11 | leadingSpace = '', 12 | layer = 1, 13 | isFirstSubTag = false, 14 | inTable = false, 15 | match = null, 16 | prevTagName = '', 17 | nextTagName = '', 18 | extraData = undefined, 19 | picNoWrap = false, 20 | }: SelfCloseTagOptions = {} 21 | ) { 22 | this.tagName = tagName 23 | this.rawStr = str 24 | this.parentTag = parentTag 25 | this.isFirstSubTag = isFirstSubTag 26 | this.prevTagName = prevTagName 27 | this.nextTagName = nextTagName 28 | this.leadingSpace = leadingSpace 29 | this.layer = layer 30 | this.innerHTML = '' 31 | this.match = match 32 | this.inTable = inTable 33 | this.extraData = extraData; 34 | this.picNoWrap = picNoWrap; 35 | if (!this.__detectStr__(str, this.tagName)) { 36 | this.attrs = {} 37 | return 38 | } 39 | const { attr } = this.__fetchTagAttr__(str) 40 | this.attrs = attr 41 | } 42 | tagName: TagName 43 | parentTag: TagName 44 | prevTagName: TagName 45 | nextTagName: TagName 46 | rawStr: string 47 | match: string | null 48 | isFirstSubTag: boolean 49 | leadingSpace: string 50 | layer: number 51 | attrs: Record 52 | innerHTML: string 53 | inTable: boolean 54 | extraData: AFTExtraData | undefined 55 | picNoWrap: boolean 56 | 57 | /** 58 | * Detect is a valid tag string 59 | * @param str 60 | * @param tagName 61 | * @returns {boolean} 62 | */ 63 | __detectStr__(str: string, tagName: TagName) { 64 | if (str[0] !== '<') { 65 | console.error( 66 | `Not a valid tag, current tag name: ${this.tagName}, tag content: ${str}` 67 | ) 68 | return false 69 | } 70 | let name = '' 71 | let name_done = false 72 | for (let i = 1; i < str.length; i++) { 73 | if (str[i] === '>') break 74 | if (!name_done && /(\s|\/)/.test(str[i])) { 75 | name_done = true 76 | } 77 | if (!name_done) { 78 | name += str[i] 79 | } 80 | } 81 | if (name !== tagName) { 82 | console.warn( 83 | 'Tag is not match tagName, tagName in str is ' + 84 | name + 85 | ', which tagName passed from parent is ' + 86 | tagName 87 | ) 88 | return false 89 | } 90 | return true 91 | } 92 | 93 | /** 94 | * 95 | * @param str 96 | * @returns {{attr: {}}} 97 | */ 98 | __fetchTagAttr__(str: string) { 99 | let openTagAttrs = '' 100 | let i = 1 101 | for (; i < str.length; i++) { 102 | if (str[i] === '>') break 103 | openTagAttrs += str[i] 104 | } 105 | return { 106 | attr: getTagAttributes(openTagAttrs), 107 | } 108 | } 109 | 110 | // 在步骤开始前,处理 tagListener 111 | beforeParse() { 112 | const { tagListener } = config.get() 113 | if (tagListener) { 114 | const { attrs, match } = tagListener(this.tagName, { 115 | parentTag: this.parentTag, 116 | prevTagName: this.prevTagName, 117 | nextTagName: this.nextTagName, 118 | isFirstSubTag: this.isFirstSubTag, 119 | attrs: this.attrs, 120 | innerHTML: this.innerHTML, 121 | match: this.match, 122 | isSelfClosing: true, 123 | }) 124 | this.attrs = attrs 125 | this.match = match 126 | } 127 | return '' 128 | } 129 | 130 | // 在合并必要的空行前 131 | beforeMergeSpace(content: string) { 132 | return content 133 | } 134 | 135 | // 合并必要的空行后 136 | afterMergeSpace(str: string) { 137 | return str 138 | } 139 | 140 | // 最终返回前 141 | beforeReturn(content: string) { 142 | return content 143 | } 144 | 145 | exec(prevGap = '', endGap = '') { 146 | let content = this.beforeParse() 147 | content = this.beforeMergeSpace(content) 148 | 149 | // empty check 150 | if (content.trim().replace(/[\n\r]/g,'').length==0) return ''; 151 | 152 | content = prevGap + content + endGap 153 | content = this.afterMergeSpace(content) 154 | content = this.beforeReturn(content) 155 | return content 156 | } 157 | } 158 | 159 | export default SelfCloseTag 160 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/config.ts: -------------------------------------------------------------------------------- 1 | import { Html2MdOptions, TagListenerProps, TagName } from './type' 2 | 3 | interface Config { 4 | options: Html2MdOptions 5 | } 6 | 7 | class Config { 8 | constructor({ 9 | skipTags = [], 10 | emptyTags = [], 11 | ignoreTags = [], 12 | aliasTags = {}, 13 | renderCustomTags = true, 14 | tagListener = (tag: TagName, props: TagListenerProps) => props, 15 | } = {}) { 16 | this.options = { 17 | skipTags, 18 | emptyTags, 19 | ignoreTags, 20 | aliasTags, 21 | renderCustomTags, 22 | tagListener, 23 | } 24 | } 25 | 26 | get() { 27 | return this.options 28 | } 29 | 30 | clear() { 31 | this.options = {} 32 | } 33 | 34 | set(obj: Html2MdOptions | undefined, force: boolean) { 35 | if (!obj) return 36 | if (Object.prototype.toString.call(obj) === '[object Object]') { 37 | ;(Object.keys(obj) as (keyof typeof obj)[]).forEach((key) => { 38 | if (force) { 39 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 40 | // @ts-ignore 41 | this.options[key] = obj[key] 42 | } else { 43 | assign(this.options, obj, key) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | reset() { 50 | this.options = JSON.parse(JSON.stringify(defaultOptions)) 51 | this.options.tagListener = (tag: TagName, props: TagListenerProps) => props 52 | } 53 | } 54 | 55 | function assign( 56 | obj: Record, 57 | newObj: Record, 58 | key: string 59 | ) { 60 | if (!(key in obj)) { 61 | obj[key] = newObj[key] 62 | return 63 | } 64 | const isArray = Array.isArray(obj[key]) 65 | const isObj = Object.prototype.toString.call(obj[key]) === '[object Object]' 66 | isArray 67 | ? (obj[key] = obj[key].concat(newObj[key])) 68 | : isObj 69 | ? (obj[key] = Object.assign(obj[key], newObj[key])) 70 | : (obj[key] = newObj[key]) 71 | } 72 | 73 | const defaultOptions = { 74 | ignoreTags: [ 75 | '', 76 | 'style', 77 | 'head', 78 | '!doctype', 79 | 'form', 80 | 'svg', 81 | 'noscript', 82 | 'script', 83 | 'meta', 84 | ], 85 | skipTags: [ 86 | 'div', 87 | 'html', 88 | 'body', 89 | 'nav', 90 | 'section', 91 | 'footer', 92 | 'main', 93 | 'aside', 94 | 'article', 95 | 'header', 96 | ], 97 | emptyTags: [], 98 | aliasTags: { 99 | figure: 'p', 100 | dl: 'p', 101 | dd: 'p', 102 | dt: 'p', 103 | figcaption: 'p', 104 | }, 105 | renderCustomTags: true, 106 | } 107 | const config = new Config() 108 | config.reset() 109 | 110 | export default config 111 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getTagConstructor, 3 | generateGetNextValidTag, 4 | unescapeStr, 5 | clearComment, 6 | isIndependentTag, 7 | } from './utils' 8 | import config from './config' 9 | import { AFTExtraData, Html2MdOptions } from './type' 10 | import RawString from './tags/__rawString__' 11 | 12 | function html2md(str: string, options?: Html2MdOptions, force = false): string { 13 | var enableATF = false; 14 | var extraData:AFTExtraData = { 15 | links: [], 16 | pics: [], 17 | nowUrl: '', 18 | processPicOpt: '', 19 | processServerHost: '', 20 | processSvgPicOpt: { 21 | maxHight: 0, 22 | maxWidth: 0 23 | }, 24 | head: { 25 | level: 9, 26 | txt: '' 27 | } 28 | }; 29 | if (options?.enableATF==true && typeof options.extraData == 'object'){ 30 | enableATF = true; 31 | extraData = options.extraData; 32 | } 33 | config.reset() 34 | config.set(options, force) 35 | str = clearComment(str) 36 | str = str.trim() 37 | str = str.replace(/(\r\n)/g, '').replace(/ /g, ' ') 38 | const getNxtValidTag = generateGetNextValidTag(str) 39 | let res = '' 40 | let prevTagName = null 41 | let [nextTagName, nextTagStr] = getNxtValidTag() 42 | // 还存在下一个tag,递归寻找 43 | while (nextTagStr !== '') { 44 | if (nextTagName != null) { 45 | // 下一个tag是一个有效的并且不是纯文本 46 | const SubTagClass = getTagConstructor(nextTagName) 47 | const options = { 48 | parentTag: null, 49 | prevTagName, 50 | prevTagStr: res, 51 | extraData: (enableATF) ? extraData : undefined, 52 | picNoWrap: false, 53 | // leadingSpace:this.leadingSpace, 54 | // layer:this.layer, 55 | // keepSpace:this.keepSpace, 56 | } 57 | const subTag = new SubTagClass(nextTagStr, nextTagName, options) 58 | const subContent = subTag.exec() 59 | const prevIsIndependent = isIndependentTag(prevTagName) 60 | const curIsIndependent = isIndependentTag(nextTagName) 61 | if (curIsIndependent && !prevIsIndependent && !res.endsWith('\n')) { 62 | res += '\n' + subContent 63 | } else { 64 | res += subContent 65 | } 66 | } else { 67 | // 下一个tag是一个无效的或者是纯文本 68 | // res += nextTagStr 69 | res += new RawString(nextTagStr, nextTagName).exec() 70 | res = res.replace(/(?:\n\s*)$/, '\n') 71 | } 72 | prevTagName = nextTagName 73 | const nxt = getNxtValidTag() 74 | nextTagName = nxt[0] 75 | nextTagStr = nxt[1] 76 | } 77 | return beforeReturn(unescapeStr(res)) 78 | } 79 | 80 | function beforeReturn(str: string) { 81 | // console.log('beforeReturn', str) 82 | str = str.replace(/^\s+/, '') 83 | str = str.replace(/\s+$/, '') 84 | str = str.replace(/☈/g, ' ') 85 | return str 86 | } 87 | 88 | export default html2md 89 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/__Heading__.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { TagOptions } from '../type'; 3 | 4 | class __Heading__ extends Tag { 5 | constructor(str: string, tagName = 'h1',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '#' 8 | } 9 | 10 | beforeMergeSpace(content: string) { 11 | 12 | // AFT smart head 13 | if (typeof this.extraData == 'object' && this.match!=null){ 14 | if (this.extraData.head.level>this.match.length){ 15 | this.extraData.head.level = this.match.length; 16 | this.extraData.head.txt = content.split('\n')[0].replace(/[\\/\n\r\t]/g,''); 17 | } 18 | } 19 | 20 | return this.match + ' ' + content 21 | } 22 | 23 | exec(prevGap: string, endGap: string) { 24 | if (!prevGap) prevGap = '\n' 25 | if (!endGap) endGap = '\n' 26 | return super.exec(prevGap, endGap) 27 | } 28 | } 29 | 30 | export default __Heading__ 31 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/__empty__.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import SelfCloseTag from '../SelfCloseTag' 3 | import { ParseOptions, TagName, TagOptions } from '../type' 4 | import { getTagConstructor } from '../utils'; 5 | import __Ignore__ from './__ignore__'; 6 | /* 7 | * 8 | *
      abc
      9 | * ==> abc 10 | * 11 | * */ 12 | class __Empty__ extends Tag { 13 | constructor( 14 | str: string, 15 | tagName: TagName = '__empty__', 16 | options: TagOptions 17 | ) { 18 | super(str, tagName, options) 19 | } 20 | 21 | slim(content: string) { 22 | if (this.inTable) return super.slim(content); 23 | return content 24 | } 25 | 26 | parseValidSubTag( 27 | subTagStr: string, 28 | subTagName: TagName, 29 | options: ParseOptions 30 | ) { 31 | if (this.inTable && subTagName!=null){ 32 | var SubTagClass = getTagConstructor(subTagName); 33 | if (SubTagClass===__Ignore__){ 34 | return new SubTagClass(subTagStr,subTagName,options).exec(); 35 | } 36 | } 37 | return new __Empty__(subTagStr, subTagName, { 38 | ...options, 39 | }).exec() 40 | } 41 | 42 | parseOnlyString(subTagStr: string, subTagName: null, options: ParseOptions) { 43 | //fix inTable 44 | if (this.inTable) return subTagStr.trim(); 45 | 46 | return subTagStr 47 | } 48 | 49 | exec() { 50 | return super.exec('', '') 51 | } 52 | 53 | beforeReturn(content: string): string { 54 | if (this.inTable) return ` ${content.trim()} `; 55 | return content; 56 | } 57 | 58 | } 59 | 60 | class __EmptySelfClose__ extends SelfCloseTag { 61 | constructor(str: string, tagName = '__emptyselfclose__') { 62 | super(str, tagName) 63 | this.tagName = tagName 64 | } 65 | 66 | exec() { 67 | return super.exec('', '') 68 | } 69 | 70 | beforeReturn(content: string): string { 71 | if (this.inTable) return content.trim(); 72 | return content; 73 | } 74 | 75 | } 76 | 77 | export { __Empty__, __EmptySelfClose__ } 78 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/__ignore__.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | *
      abc
      4 | * ==> '' 5 | * 6 | * */ 7 | 8 | class __Ignore__ { 9 | exec() { 10 | return '' 11 | } 12 | } 13 | 14 | export default __Ignore__ 15 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/__nomatch__.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import SelfCloseTag from '../SelfCloseTag' 3 | import { TagOptions } from '../type' 4 | /* 5 | * 6 | * abc 7 | * ==> **abc** 8 | * 9 | * */ 10 | 11 | class __NoMatch__ extends Tag { 12 | constructor(str: string, tagName = '__nomatch__',options:TagOptions) { 13 | super(str, tagName,options) 14 | } 15 | 16 | beforeMergeSpace(content: string) { 17 | return `<${this.tagName}>${content}` 18 | } 19 | 20 | exec() { 21 | return super.exec('', '') 22 | } 23 | } 24 | 25 | class __NoMatchSelfClose__ extends SelfCloseTag { 26 | constructor(str: string, tagName = '__nomatchselfclose__') { 27 | super(str, tagName) 28 | } 29 | 30 | exec() { 31 | return `<${this.tagName} />` 32 | } 33 | } 34 | 35 | export { __NoMatch__, __NoMatchSelfClose__ } 36 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/__rawString__.ts: -------------------------------------------------------------------------------- 1 | import { ParseOptions, TagName } from '../type' 2 | import { extraEscape, unescapeStr } from '../utils' 3 | import isIndependentTag from '../utils/isIndependentTag' 4 | 5 | class __RawString__ { 6 | tagName: TagName 7 | nextTagName: TagName 8 | prevTagName: TagName 9 | parentTag: TagName 10 | keepSpace: boolean 11 | calcLeading: boolean 12 | inTable: boolean 13 | leadingSpace: string 14 | layer: number 15 | rawStr: string 16 | constructor( 17 | str: string, 18 | tagName: TagName = '__nomatch__', 19 | { 20 | keepSpace = false, 21 | prevTagName = '', 22 | nextTagName = '', 23 | parentTag = '', 24 | calcLeading = false, 25 | layer = 1, 26 | leadingSpace = '', 27 | inTable = false, 28 | }: ParseOptions = {} 29 | ) { 30 | this.tagName = tagName 31 | this.nextTagName = nextTagName 32 | this.prevTagName = prevTagName 33 | this.parentTag = parentTag 34 | this.keepSpace = keepSpace 35 | this.calcLeading = calcLeading 36 | this.leadingSpace = leadingSpace 37 | this.layer = layer 38 | this.rawStr = str 39 | this.inTable = inTable 40 | } 41 | 42 | slim(str: string) { 43 | if (this.keepSpace) return str 44 | 45 | let _str = str.replace(/\s+/g, ' ') 46 | 47 | if (isIndependentTag(this.prevTagName)) { 48 | _str = _str.trimLeft() 49 | } 50 | if (isIndependentTag(this.nextTagName)) { 51 | _str = _str.trimRight() 52 | } 53 | return _str 54 | } 55 | 56 | beforeReturn(content: string) { 57 | // fix no unescape for raw string 58 | content = unescapeStr(content); 59 | 60 | if (this.keepSpace) return content 61 | if (content.trim().length==0) return ''; 62 | if (this.calcLeading) { 63 | return this.leadingSpace + extraEscape(content) 64 | } 65 | let validStr = extraEscape(content) 66 | 67 | // fix inTable 68 | if (this.inTable) { 69 | validStr = ` ${validStr} `.replace(/[\n\r]/g,' ').replace(/(?<=[^\\])[\|]/g, '\\|').trim(); 70 | } 71 | 72 | if (this.inTable) return validStr.trim(); 73 | 74 | return validStr 75 | } 76 | 77 | exec() { 78 | let content = this.rawStr 79 | content = this.slim(content) 80 | content = this.beforeReturn(content) 81 | return content 82 | } 83 | } 84 | 85 | export default __RawString__ 86 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/__skip__.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import SelfCloseTag from '../SelfCloseTag' 3 | import { isIndependentTag, getRealTagName } from '../utils' 4 | import { TagOptions, SelfCloseTagOptions } from '../type' 5 | /* 6 | * 7 | *
      abc
      8 | * ==> **abc** 9 | * 10 | * */ 11 | 12 | class __Skip__ extends Tag { 13 | noNeedWrap: string[] 14 | constructor(str: string, tagName = '__skip__', options: TagOptions) { 15 | super(str, tagName, options) 16 | this.noNeedWrap = ['td', 'th'] 17 | } 18 | 19 | exec() { 20 | const need = 21 | isIndependentTag(getRealTagName(this.tagName)) && 22 | (!this.parentTag || !this.noNeedWrap.includes(this.parentTag)) 23 | const pre = need ? '\n' : '' 24 | const aft = need ? '\n' : '' 25 | return super.exec(pre, aft) 26 | } 27 | } 28 | 29 | class __SkipSelfClose__ extends SelfCloseTag { 30 | constructor( 31 | str: string, 32 | tagName = '__skipselfclose__', 33 | options: SelfCloseTagOptions 34 | ) { 35 | super(str, tagName, options) 36 | } 37 | 38 | exec() { 39 | return '' 40 | } 41 | } 42 | 43 | export { __Skip__, __SkipSelfClose__ } 44 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/a.ts: -------------------------------------------------------------------------------- 1 | import { smartURL, wrapPic } from '../AFT' 2 | import Tag from '../Tag' 3 | import { ParseOptions, TagName, TagOptions } from '../type' 4 | 5 | class A extends Tag { 6 | constructor(str: string, tagName = 'a', options: TagOptions) { 7 | super(str, tagName, options) 8 | } 9 | 10 | beforeMergeSpace(content: string) { 11 | var { href, title } = this.attrs 12 | 13 | href = href ? href : ''; 14 | // () escape 15 | const validHref = href ? ` ${href} `.replace(/(?<=[^\\+])[\(]/g, '\\(').replace(/(?<=[^\\+])[\)]/g, '\\)').replace(/(?<=[^\\+])[\`]/g, '\\\`').replace(/[\n\r]/g,'').trim() : '' 16 | //kill title 17 | /* 18 | if (title) { 19 | return `[${content}](${validHref} "${title}")` 20 | } 21 | */ 22 | 23 | //empty check 24 | if (content.trim().length==0 && validHref.trim().length==0) return ''; 25 | 26 | // AFT pic wrap 27 | if (typeof this.extraData=='undefined') return ` [${content}](${validHref}) `; 28 | 29 | href = smartURL(href,this.extraData.nowUrl); 30 | if (!href.includes('%')) href = encodeURI(href); 31 | 32 | var pos = this.extraData.links.indexOf(href); 33 | if (!this.extraData.links.includes(href) && !validHref.startsWith('#') && !validHref.startsWith('javascript:') && validHref.trim().length>0){ 34 | this.extraData.links.push(href); 35 | pos = this.extraData.links.length-1; 36 | //console.log(pos); 37 | } 38 | 39 | return wrapPic('a',this.picNoWrap,content,validHref,1,pos); 40 | 41 | } 42 | 43 | // AFT pic wrap 44 | parseValidSubTag(subTagStr: string, subTagName: string, options: ParseOptions): string { 45 | if (typeof this.extraData=='undefined') return super.parseValidSubTag(subTagStr,subTagName,options); 46 | var opt = Object.assign({},options); 47 | opt.picNoWrap = true; 48 | return super.parseValidSubTag(subTagStr,subTagName,opt); 49 | } 50 | 51 | parseOnlyString( 52 | subTagStr: string, 53 | subTagName: TagName, 54 | options: ParseOptions 55 | ) { 56 | if (this.parentTag === 'tbody' || this.parentTag === 'thead') { 57 | return subTagStr 58 | } 59 | return super.parseOnlyString(subTagStr, subTagName, options) 60 | } 61 | 62 | exec(prevGap = '', endGap = '') { 63 | return super.exec(prevGap, endGap) 64 | } 65 | } 66 | 67 | export default A 68 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/b.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import Strong from './strong' 3 | 4 | class B extends Strong { 5 | constructor(str: string, tagName = 'b', options: TagOptions) { 6 | super(str, tagName, options) 7 | } 8 | 9 | exec(prevGap: string, endGap: string) { 10 | return super.exec(prevGap, endGap) 11 | } 12 | } 13 | 14 | export default B 15 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/blockquote.ts: -------------------------------------------------------------------------------- 1 | import isIndependentTag from '../utils/isIndependentTag' 2 | import Tag from '../Tag' 3 | import { getTagConstructor } from '../utils' 4 | import { ParseOptions, TagOptions } from '../type' 5 | 6 | class Blockquote extends Tag { 7 | constructor(str: string, tagName = 'blockquote', options: TagOptions) { 8 | super(str, tagName, options) 9 | this.match = this.match || '>' 10 | this.fillPerLine = this.fillPerLine.bind(this) 11 | } 12 | 13 | beforeMergeSpace(content: string) { 14 | if (content.trim() === '') return '' 15 | const matchStr = this.match + ' ' + content 16 | if (this.calcLeading) { 17 | return this.leadingSpace + matchStr 18 | } 19 | return matchStr 20 | } 21 | 22 | afterMergeSpace(content: string) { 23 | let split = content.split('\n') 24 | // 去除连续 25 | for (let i = split.length - 1; i >= 0; i--) { 26 | if ( 27 | i < split.length - 1 && 28 | split[i].trim() === '>' && 29 | split[i + 1].trim() === '>' 30 | ) { 31 | split.splice(i, 1) 32 | } 33 | } 34 | split = split.map((n) => { 35 | if (n === '') return '' 36 | return this.fillPerLine(n) 37 | }) 38 | // console.log(content,split) 39 | return split.join('\n') 40 | } 41 | 42 | beforeReturn(content: string) { 43 | // 去除空行 44 | return content.replace('\n\n', '\n') 45 | } 46 | 47 | fillPerLine(lineStr: string) { 48 | let startWith = '>' 49 | if (this.calcLeading) { 50 | startWith = this.leadingSpace + '>' 51 | } 52 | if (!lineStr.startsWith(startWith)) { 53 | const matchStr = this.match + ' ' + lineStr 54 | if (this.calcLeading) { 55 | return this.leadingSpace + matchStr 56 | } 57 | return matchStr 58 | } 59 | return lineStr 60 | } 61 | 62 | parseValidSubTag( 63 | subTagStr: string, 64 | subTagName: string, 65 | options: ParseOptions 66 | ) { 67 | let subTag 68 | if (subTagName === 'blockquote') { 69 | const SubTagClass = getTagConstructor(subTagName) 70 | subTag = new SubTagClass(subTagStr, subTagName, { 71 | ...options, 72 | calcLeading: this.calcLeading, 73 | match: this.match + '>', 74 | noExtraLine: true, 75 | }) 76 | } else { 77 | const SubTagClass = getTagConstructor(subTagName) 78 | subTag = new SubTagClass(subTagStr, subTagName, { 79 | ...options, 80 | noExtraLine: true, 81 | }) 82 | } 83 | let str = subTag.exec() 84 | let leadingSpace = '' 85 | if (this.calcLeading) { 86 | leadingSpace = this.leadingSpace 87 | } 88 | const prevNeedNewLine = 89 | isIndependentTag(options.prevTagName) && options.prevTagName !== 'br' 90 | const nextNeedNewLine = 91 | isIndependentTag(options.nextTagName) && options.nextTagName !== 'br' 92 | const needNewLine = isIndependentTag(subTagName) && subTagName !== 'br' 93 | if (this.isFirstSubTag) { 94 | return str.trimLeft().replace(leadingSpace, '') 95 | } else { 96 | if (needNewLine) { 97 | str = leadingSpace + this.match + str 98 | if (!prevNeedNewLine) { 99 | str = '\n' + str 100 | } 101 | if ( 102 | !nextNeedNewLine && 103 | options.nextTagStr && 104 | options.nextTagStr.trim() 105 | ) { 106 | str += this.match + '\n' 107 | } 108 | } else { 109 | if (prevNeedNewLine) { 110 | return leadingSpace + this.match + '\n' + str 111 | } 112 | return str 113 | } 114 | } 115 | return str 116 | } 117 | 118 | exec(prevGap = '\n', endGap = '\n') { 119 | return super.exec(prevGap, endGap) 120 | } 121 | } 122 | 123 | export default Blockquote 124 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/br.ts: -------------------------------------------------------------------------------- 1 | import SelfCloseTag from '../SelfCloseTag' 2 | import { TagOptions } from '../type' 3 | 4 | class Br extends SelfCloseTag { 5 | constructor(str: string, tagName = 'b', options: TagOptions) { 6 | super(str, tagName, options) 7 | } 8 | 9 | exec(prevGap: string, endGap = '\n') { 10 | if (this.inTable) return ' '; 11 | return ' ' + endGap 12 | } 13 | } 14 | 15 | export default Br 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/code.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { ParseOptions, TagOptions } from '../type' 3 | import { getTagConstructor, unescapeStr } from '../utils' 4 | import __Ignore__ from './__ignore__' 5 | import { __NoMatch__ } from './__nomatch__' 6 | 7 | class Code extends Tag { 8 | constructor(str: string, tagName = 'code', options: TagOptions) { 9 | super(str, tagName, options) 10 | this.match = this.match == null ? '`' : this.match 11 | this.noWrap = this.match === '`' 12 | this.layer = 1 13 | } 14 | 15 | beforeMergeSpace(content: string) { 16 | let startMatch, endMatch 17 | // 不是在pre内部,并且存在冲突,是多个`组成 18 | if (this.match !== '' && this.match !== '`') { 19 | startMatch = this.match + ' ' 20 | endMatch = ' ' + this.match 21 | } else { 22 | startMatch = this.match 23 | endMatch = this.match 24 | } 25 | return startMatch + content + endMatch 26 | } 27 | 28 | // 在嵌套pre中,pre应该视为换行 29 | parseValidSubTag( 30 | subTagStr: string, 31 | subTagName: string, 32 | options: ParseOptions 33 | ) { 34 | if (subTagName === 'pre') { 35 | const SubTagClass = getTagConstructor(subTagName) 36 | const subTag = new SubTagClass(subTagStr, subTagName, { 37 | ...options, 38 | language: '', 39 | match: '', 40 | }) 41 | 42 | // fix inTable 43 | if (this.inTable && SubTagClass!=__Ignore__){ 44 | const subTag = new __NoMatch__(subTagStr, subTagName,options); 45 | return subTag.exec().replace(/[\n\r]/g,''); 46 | } 47 | 48 | return subTag.exec('', '\n') 49 | } else { 50 | const SubTagClass = getTagConstructor(subTagName) 51 | const subTag = new SubTagClass(subTagStr, subTagName, { 52 | ...options, 53 | keepSpace: this.keepSpace, 54 | noWrap: this.noWrap, 55 | }) 56 | return subTag.exec('', '') 57 | } 58 | } 59 | 60 | parseOnlyString(subTagStr: string) { 61 | if (this.match !== '' && !!subTagStr) { 62 | let count = 1 63 | if (subTagStr.startsWith('`') || subTagStr.endsWith('`')) { 64 | count = 2 65 | if (subTagStr.startsWith('``') || subTagStr.endsWith('``')) { 66 | count = 3 67 | } 68 | } 69 | this.match = '`'.repeat(count) 70 | } 71 | // 将<转换为<,等等 72 | return unescapeStr(subTagStr) 73 | } 74 | 75 | slim(content: string) { 76 | if (this.keepSpace) return content 77 | return content.trim() 78 | } 79 | 80 | exec(prevGap = '', endGap = '') { 81 | return super.exec(prevGap, endGap) 82 | } 83 | } 84 | 85 | export default Code 86 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/del.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | 3 | class Del extends Tag { 4 | constructor(str: string, tagName = 'del') { 5 | super(str, tagName) 6 | this.match = this.match || '~~' 7 | } 8 | 9 | beforeMergeSpace(content: string) { 10 | // fix '~~ a~~' '~~~~' '~~ ~~' 11 | if (content.trim().length==0) return ''; 12 | return ' '+this.match + content.trim() + this.match+' '; 13 | } 14 | 15 | exec(prevGap = '', endGap = '') { 16 | return super.exec(prevGap, endGap) 17 | } 18 | } 19 | 20 | export default Del 21 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/em.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { TagOptions } from '../type' 3 | 4 | class Em extends Tag { 5 | constructor(str: string, tagName = 'em', options: TagOptions) { 6 | super(str, tagName, options) 7 | this.match = this.match || '*' 8 | } 9 | 10 | beforeMergeSpace(content: string) { 11 | // fix '* a*' '**' '* *' 12 | if (content.trim().length==0) return ''; 13 | return ' '+this.match + content.trim() + this.match+' '; 14 | } 15 | 16 | exec(prevGap = '', endGap = '') { 17 | if (this.parentTag === 'strong' && this.nextTagStr) endGap = ' ' 18 | if ( 19 | this.match != null && 20 | this.prevTagStr && 21 | !this.prevTagStr.endsWith('\\' + this.match) && 22 | this.prevTagStr.endsWith(this.match) 23 | ) 24 | prevGap = ' ' 25 | return super.exec(prevGap, endGap) 26 | } 27 | } 28 | 29 | export default Em 30 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/h1.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import __Heading__ from './__Heading__' 3 | 4 | class H1 extends __Heading__ { 5 | constructor(str: string, tagName = 'h1',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '#' 8 | } 9 | 10 | exec(prevGap = '\n', endGap = '\n') { 11 | return super.exec(prevGap, endGap) 12 | } 13 | } 14 | 15 | export default H1 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/h2.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import __Heading__ from './__Heading__' 3 | 4 | class H2 extends __Heading__ { 5 | constructor(str: string, tagName = 'h2',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '##' 8 | } 9 | 10 | exec(prevGap = '\n', endGap = '\n') { 11 | return super.exec(prevGap, endGap) 12 | } 13 | } 14 | 15 | export default H2 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/h3.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import __Heading__ from './__Heading__' 3 | 4 | class H3 extends __Heading__ { 5 | constructor(str: string, tagName = 'h3',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '###' 8 | } 9 | 10 | exec(prevGap = '\n', endGap = '\n') { 11 | return super.exec(prevGap, endGap) 12 | } 13 | } 14 | 15 | export default H3 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/h4.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import __Heading__ from './__Heading__' 3 | 4 | class H4 extends __Heading__ { 5 | constructor(str: string, tagName = 'h4',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '####' 8 | } 9 | 10 | exec(prevGap = '\n', endGap = '\n') { 11 | return super.exec(prevGap, endGap) 12 | } 13 | } 14 | 15 | export default H4 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/h5.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import __Heading__ from './__Heading__' 3 | 4 | class H5 extends __Heading__ { 5 | constructor(str: string, tagName = 'h5',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '#####' 8 | } 9 | 10 | exec(prevGap = '\n', endGap = '\n') { 11 | return super.exec(prevGap, endGap) 12 | } 13 | } 14 | 15 | export default H5 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/h6.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import __Heading__ from './__Heading__' 3 | 4 | class H6 extends __Heading__ { 5 | constructor(str: string, tagName = 'h6',options:TagOptions) { 6 | super(str, tagName,options) 7 | this.match = '######' 8 | } 9 | 10 | exec(prevGap = '\n', endGap = '\n') { 11 | return super.exec(prevGap, endGap) 12 | } 13 | } 14 | 15 | export default H6 16 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/hr.ts: -------------------------------------------------------------------------------- 1 | import SelfCloseTag from '../SelfCloseTag' 2 | import { SelfCloseTagOptions } from '../type' 3 | 4 | class Hr extends SelfCloseTag { 5 | constructor(str: string, tagName = 'hr', options: SelfCloseTagOptions) { 6 | super(str, tagName, options) 7 | 8 | this.match = '---' 9 | } 10 | 11 | beforeMergeSpace() { 12 | const leadingSpace = this.leadingSpace 13 | return leadingSpace + this.match 14 | } 15 | 16 | beforeReturn(content: string) { 17 | content.replace(/^(?:\n\s*)/, '\n\n').replace(/(?:\n\s*)$/, '\n\n') 18 | return content 19 | } 20 | 21 | exec(prevGap = '\n', endGap = '\n') { 22 | if (this.inTable) return super.exec('', ''); 23 | return super.exec(prevGap, endGap) 24 | } 25 | } 26 | 27 | export default Hr 28 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/i.ts: -------------------------------------------------------------------------------- 1 | import { TagOptions } from '../type' 2 | import Em from './em' 3 | 4 | class I extends Em { 5 | constructor(str: string, tagName = 'i', options: TagOptions) { 6 | super(str, tagName, options) 7 | } 8 | 9 | exec(prevGap: string, endGap: string) { 10 | return super.exec(prevGap, endGap) 11 | } 12 | } 13 | 14 | export default I 15 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/img.ts: -------------------------------------------------------------------------------- 1 | import { picProcess } from '../AFT' 2 | import SelfCloseTag from '../SelfCloseTag' 3 | import { SelfCloseTagOptions } from '../type' 4 | 5 | class Img extends SelfCloseTag { 6 | constructor(str: string, tagName = 'img', options: SelfCloseTagOptions) { 7 | super(str, tagName, options) 8 | } 9 | 10 | beforeMergeSpace() { 11 | let { src, alt } = this.attrs 12 | //if (!alt) alt = '' 13 | if (!src) src = '' 14 | 15 | // kill alt 16 | alt = 'pic'; 17 | 18 | src = src.trim(); 19 | 20 | // empty check 21 | if (src.trim().length==0) return ''; 22 | //if (alt.trim().length==0) alt = 'pic'; 23 | 24 | // escape "()" and "`" for src 25 | //src = ` ${src} `; 26 | //let vaildSrc = ` ${src} `.replace(/(?<=[^\\+])[\(]/g, '\\(').replace(/(?<=[^\\+])[\)]/g, '\\)').replace(/(?<=[^\\+])[\`]/g, '\\\`').replace(/[\n\r]/g,'').trim(); 27 | //src = src.replace(/(?<=[^\\+])[\(]/g, '\\(').replace(/(?<=[^\\+])[\)]/g, '\\)').replace(/(?<=[^\\+])[\`]/g, '\\\`').replace(/[\n\r]/g,'').trim(); 28 | if (!src.includes('%')) src = encodeURI(src); 29 | 30 | // AFT pic wrap 31 | //console.log(alt,src,this.extraData); 32 | if (typeof this.extraData == 'object'){ 33 | src = picProcess(src,this.extraData.nowUrl,this.extraData.processServerHost,this.extraData.processPicOpt,this.extraData.processSvgPicOpt); 34 | if (src.trim().length==0) return ''; 35 | if (!this.extraData.pics.includes(src)) this.extraData.pics.push(src); 36 | var pos = this.extraData.pics.indexOf(src); 37 | if (this.inTable) return `![${alt}](${src})${(pos==-1)?'':`\\[p${pos}\\]`}`; 38 | if (this.picNoWrap) return `\\[ \n![${alt}](${src})\n${(pos==-1)?'':`\\[p${pos}\\]`}\\] `; 39 | return ` \n\n \n![${alt}](${src}) \n\n\n${(pos==-1)?'':`\\[p${pos}\\]`} `; 40 | } 41 | 42 | return `![${alt}](${src})` 43 | } 44 | 45 | exec(prevGap = '', endGap = '') { 46 | return super.exec(prevGap, endGap) 47 | } 48 | } 49 | export default Img 50 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/input.ts: -------------------------------------------------------------------------------- 1 | import SelfCloseTag from '../SelfCloseTag' 2 | import { SelfCloseTagOptions } from '../type' 3 | 4 | class Input extends SelfCloseTag { 5 | constructor(str: string, tagName = 'input', options: SelfCloseTagOptions) { 6 | super(str, tagName, options) 7 | } 8 | 9 | beforeMergeSpace() { 10 | const { type, checked } = this.attrs 11 | if (this.parentTag === 'li' && type === 'checkbox') { 12 | return checked != null ? '[x] ' : '[ ] ' 13 | } 14 | return '' 15 | } 16 | 17 | exec(prevGap = '', endGap = '') { 18 | return super.exec(prevGap, endGap) 19 | } 20 | } 21 | export default Input 22 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/li.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { getTagConstructor } from '../utils' 3 | import isIndependentTag from '../utils/isIndependentTag' 4 | import { DOUBLE, TRIPLE } from '../utils/CONSTANT' 5 | import { ParseOptions, TagName, TagOptions } from '../type' 6 | import { wrapPic } from '../AFT' 7 | 8 | /** 9 | * 内部含有p, 如果是第一个元素,需要最后额外加一个\n,否则开始额外加一个\n 10 | * 在li内部的元素需要layer,单内部元素的内部则不需要layer 11 | * 在li内部第一个元素,需要去除所有layer空格,但是原本如果有空行,需要保留空行 12 | * 在li内部的字符串,只有换行了,才需要layer 13 | */ 14 | class Li extends Tag { 15 | extraGap: string 16 | constructor(str: string, tagName = 'li', options: TagOptions) { 17 | super(str, tagName, options) 18 | // 在没有UL的情况下 19 | this.match = this.match || this.__calcThisMatch__(); 20 | this.extraGap = '' 21 | } 22 | 23 | beforeMergeSpace(content: string) { 24 | // empty check 25 | if (content.trim().length==0) return ''; 26 | 27 | if (typeof this.extraData=='undefined') return this.extraGap + this.leadingSpace + this.match + ' ' + content 28 | 29 | // fix "* 1. xxx" 30 | if (/(\*[0-9]+\.)/.test(`*${content.trim()}`)){ 31 | content = content.replace('.','\\.'); 32 | } 33 | //console.log(content); 34 | 35 | //AFT pic wrap 36 | return this.extraGap + this.leadingSpace + wrapPic('li',this.picNoWrap,this.match+' '+content,'',this.layer,-1); 37 | } 38 | 39 | __calcThisMatch__(){ 40 | if (this.layer>0){ 41 | return '*'; 42 | }else{ 43 | return ('\\'+'*'.repeat(-this.layer-3)); 44 | } 45 | } 46 | 47 | __calcNextLeading__() { 48 | if (this.layer<0 || this.layer+1>3) return ''; 49 | return this.match?.length === 1 50 | ? DOUBLE 51 | : this.match?.length === 2 52 | ? TRIPLE 53 | : this.match?.length === 3 54 | ? DOUBLE 55 | : TRIPLE + DOUBLE 56 | } 57 | 58 | __calcNextLayer__(){ 59 | if (this.layer>0){ 60 | if (this.layer+1>3) return -(this.layer+1); 61 | return this.layer+1; 62 | }else{ 63 | return this.layer-1; 64 | } 65 | } 66 | 67 | parseValidSubTag( 68 | subTagStr: string, 69 | subTagName: string, 70 | options: ParseOptions 71 | ) { 72 | // kill
    1. in
    2. parent match 73 | //if (subTagName=='li' || subTagName=='ul') this.match=''; 74 | 75 | const SubTagClass = getTagConstructor(subTagName) 76 | const nextLeading = this.__calcNextLeading__() 77 | const subTag = new SubTagClass(subTagStr, subTagName, { 78 | ...options, 79 | calcLeading: true, 80 | leadingSpace: this.leadingSpace + nextLeading, 81 | layer: this.__calcNextLayer__(), 82 | picNoWrap: (typeof this.extraData!='undefined') ? true : false, 83 | }) 84 | const str = subTag.exec() 85 | if (subTagName === 'p') { 86 | this.extraGap = '\n' 87 | } 88 | if (this.isFirstSubTag) { 89 | return str.trimLeft().replace(this.leadingSpace + nextLeading, '') 90 | } else { 91 | return str 92 | } 93 | } 94 | 95 | parseOnlyString( 96 | subTagStr: string, 97 | subTagName: TagName, 98 | options: ParseOptions 99 | ) { 100 | let calcLeading = false 101 | if (isIndependentTag(options.prevTagName)) { 102 | calcLeading = true 103 | } 104 | const nextLeading = this.__calcNextLeading__() 105 | const str = super.parseOnlyString(subTagStr, subTagName, { 106 | ...options, 107 | calcLeading, 108 | leadingSpace: this.leadingSpace + nextLeading, 109 | layer: this.layer + 1, 110 | }) 111 | if (this.isFirstSubTag) { 112 | return str.replace(this.leadingSpace + nextLeading, '') 113 | } else { 114 | return str 115 | } 116 | } 117 | 118 | beforeReturn(content: string) { 119 | return super.beforeReturn(content) 120 | } 121 | 122 | exec(prevGap = '\n', endGap = '\n') { 123 | return super.exec(prevGap, endGap) 124 | } 125 | } 126 | 127 | export default Li 128 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/ol.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import __Ignore__ from './__ignore__' 3 | import { getTagConstructor } from '../utils' 4 | import config from '../config' 5 | import { ParseOptions, TagName, TagOptions } from '../type' 6 | 7 | class Ol extends Tag { 8 | constructor(str: string, tagName = 'ol', options: TagOptions) { 9 | super(str, tagName, options) 10 | const attrStartNum = parseInt(this?.attrs?.start, 10) 11 | this.count = isNaN(attrStartNum) ? 1 : attrStartNum 12 | } 13 | 14 | __isValidSubTag__(subTagName: TagName): boolean { 15 | if (!subTagName) return false 16 | const { aliasTags } = config.get() 17 | const SubTagClass = getTagConstructor(subTagName) 18 | return ( 19 | subTagName === 'li' || 20 | aliasTags?.[subTagName] == 'li' || 21 | SubTagClass === __Ignore__ 22 | ) 23 | } 24 | 25 | getValidSubTagName(subTagName: TagName) { 26 | if (!subTagName) return null 27 | return this.__isValidSubTag__(subTagName) ? subTagName : null 28 | } 29 | 30 | parseValidSubTag( 31 | subTagStr: string, 32 | subTagName: string, 33 | options: ParseOptions 34 | ) { 35 | const SubTagClass = getTagConstructor(subTagName) 36 | if (this.__isValidSubTag__(subTagName)) { 37 | const match = this.count + '\\.' 38 | const subTag = new SubTagClass(subTagStr, subTagName, { 39 | ...options, 40 | calcLeading: true, 41 | leadingSpace: this.leadingSpace, 42 | layer: this.layer, 43 | match, 44 | }) 45 | this.count++ 46 | return subTag.exec('', '\n') 47 | } else { 48 | console.error( 49 | 'Should not have tags except
    3. inside ol, current tag is ' + 50 | subTagName + 51 | ', current tagStr is' + 52 | subTagStr 53 | ) 54 | return '' 55 | } 56 | } 57 | 58 | parseOnlyString() { 59 | return '' 60 | } 61 | 62 | exec(prevGap = '\n', endGap = '\n') { 63 | return super.exec(prevGap, endGap) 64 | } 65 | } 66 | 67 | export default Ol 68 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/p.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { ParseOptions } from '../type' 3 | 4 | class P extends Tag { 5 | constructor(str: string, tagName = 'p', options: ParseOptions) { 6 | super(str, tagName, options) 7 | } 8 | 9 | beforeMergeSpace(content: string) { 10 | if (this.calcLeading) { 11 | return this.leadingSpace + content 12 | } 13 | return content 14 | } 15 | 16 | exec(prevGap = '\n', endGap = '\n') { 17 | if ( 18 | !this.prevTagName && 19 | !!this.prevTagStr && 20 | !this.prevTagStr.endsWith('\n') 21 | ) { 22 | prevGap = '\n\n' 23 | } 24 | if ( 25 | !this.nextTagName && 26 | !!this.nextTagStr && 27 | !this.nextTagStr.startsWith('\n') 28 | ) { 29 | endGap = '\n\n' 30 | } 31 | return super.exec(prevGap, endGap) 32 | } 33 | } 34 | 35 | export default P 36 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/pre.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { __Empty__, __EmptySelfClose__ } from './__empty__' 3 | import { getTagConstructor, isSelfClosing, getLanguage } from '../utils' 4 | import { DOUBLE } from '../utils/CONSTANT' 5 | import { ParseOptions, TagOptions } from '../type' 6 | import { __NoMatch__ } from './__nomatch__' 7 | import __Ignore__ from './__ignore__' 8 | 9 | /** 10 | * 在pre内部所有元素,应该转换为 plain text,并入 ``` 内部, 保持格式 11 | */ 12 | 13 | class Pre extends Tag { 14 | isIndent: boolean 15 | constructor(str: string, tagName = 'pre', options: TagOptions) { 16 | super(str, tagName, options) 17 | this.indentSpace = DOUBLE + DOUBLE 18 | this.isIndent = this.innerHTML.includes('```') 19 | this.match = (this.isIndent || this.inTable) ? '' : '```' //fix inTable 20 | // this.match = (this.isIndent) ? '' : '```' 21 | this.language = this.language || getLanguage(str) 22 | this.keepSpace = true 23 | } 24 | 25 | beforeMergeSpace(content: string) { 26 | // 嵌套时 27 | const before = 28 | this.isIndent || this.parentTag === 'code' 29 | ? '' 30 | : this.match + this.language + '\n' 31 | let gap = '' 32 | if (!content.endsWith('\n')) gap = '\n' 33 | const after = 34 | this.isIndent || this.parentTag === 'code' ? '' : gap + this.match 35 | return before + content + after 36 | } 37 | 38 | fillPerLine(lineStr: string) { 39 | let leadingSpace = '' 40 | if (this.calcLeading) { 41 | leadingSpace = this.leadingSpace 42 | } 43 | if (this.isIndent) { 44 | return leadingSpace + this.indentSpace + lineStr 45 | } 46 | return leadingSpace + lineStr 47 | } 48 | 49 | afterMergeSpace(content: string) { 50 | let split = content.split('\n') 51 | split = split.map((n) => { 52 | if (n === '') return '' 53 | return this.fillPerLine(n) 54 | }) 55 | return split.join('\n') 56 | } 57 | 58 | parseValidSubTag( 59 | subTagStr: string, 60 | subTagName: string, 61 | options: ParseOptions 62 | ) { 63 | if (subTagName === 'code') { 64 | const SubTagClass = getTagConstructor(subTagName) 65 | const subTag = new SubTagClass(subTagStr, subTagName, { 66 | ...options, 67 | match: '', 68 | language: this.language, 69 | keepSpace: true, 70 | }) 71 | 72 | // fix inTable 73 | if (this.inTable && SubTagClass!=__Ignore__){ 74 | const subTag = new __NoMatch__(subTagStr, subTagName,options); 75 | return subTag.exec().replace(/[\n\r]/g,'').trim(); 76 | } 77 | 78 | return subTag.exec('', '') 79 | } else { 80 | let emptyTag 81 | if (isSelfClosing(subTagName)) { 82 | emptyTag = new __EmptySelfClose__(subTagStr, subTagName) 83 | } else { 84 | emptyTag = new __Empty__(subTagStr, subTagName, { 85 | ...options, 86 | keepSpace: true, 87 | }) 88 | } 89 | return emptyTag.exec() 90 | } 91 | } 92 | 93 | parseOnlyString(subTagStr: string) { 94 | return subTagStr 95 | } 96 | 97 | slim(content: string) { 98 | return content 99 | } 100 | 101 | exec(prevGap = '\n', endGap = '\n') { 102 | return super.exec(prevGap, endGap) 103 | } 104 | 105 | // fix inTable 106 | beforeReturn(content: string): string { 107 | if (this.inTable) return `
      ${content.trim()}
      `; 108 | return content; 109 | } 110 | 111 | } 112 | 113 | export default Pre 114 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/s.ts: -------------------------------------------------------------------------------- 1 | import Del from './del' 2 | 3 | class S extends Del { 4 | constructor(str: string, tagName = 's') { 5 | super(str, tagName) 6 | } 7 | 8 | exec(prevGap: string, endGap: string) { 9 | return super.exec(prevGap, endGap) 10 | } 11 | } 12 | 13 | export default S 14 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/span.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { TagOptions } from '../type' 3 | 4 | class Span extends Tag { 5 | constructor(str: string, tagName = 'span', options: TagOptions) { 6 | super(str, tagName, options) 7 | } 8 | 9 | exec(prevGap = '', endGap = '') { 10 | return super.exec(prevGap, endGap) 11 | } 12 | } 13 | 14 | export default Span 15 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/strong.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { TagOptions } from '../type' 3 | 4 | class Strong extends Tag { 5 | constructor(str: string, tagName = 'strong', options: TagOptions) { 6 | super(str, tagName, options) 7 | this.layer = 1 8 | this.match = this.match || '**' 9 | } 10 | 11 | beforeMergeSpace(content: string) { 12 | // fix '** a**' '****' '** **' 13 | if (content.trim().length==0) return ''; 14 | return ' '+this.match + content.trim() + this.match+' '; 15 | } 16 | 17 | exec(prevGap = '', endGap = '') { 18 | if ( 19 | this.match != null && 20 | this.prevTagStr && 21 | !this.prevTagStr.endsWith('\\' + this.match[0]) && 22 | this.prevTagStr.endsWith(this.match[0]) 23 | ) 24 | prevGap = ' ' 25 | return super.exec(prevGap, endGap) 26 | } 27 | } 28 | 29 | export default Strong 30 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/table.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import { ParseOptions, TagOptions } from '../type' 3 | import { getTagConstructor, getTableAlign, generateGetNextValidTag } from '../utils' 4 | 5 | /* 6 | function countTdNum(str: string) { 7 | let trStr = '' 8 | for (let i = 0; i < str.length; i++) { 9 | if (trStr.endsWith('')) { 10 | break 11 | } 12 | trStr += str[i] 13 | } 14 | return Math.max( 15 | trStr.split('').length - 1, 16 | trStr.split('').length - 1 17 | ) 18 | } 19 | */ 20 | // rewrite countTdNum to fix col count error 21 | function countTdNum(str: string) { 22 | var out = 0; 23 | var getNextTag = generateGetNextValidTag(str); 24 | const lagacyCount = (s:string)=>{ 25 | var count = 0; 26 | var getNextTag = generateGetNextValidTag(s); 27 | while (true){ 28 | let o = getNextTag(); 29 | if (o[0]==null && o[1]=='') break; 30 | if (o[1].trim().length==0) continue; 31 | if (o[0]!='tr') continue; 32 | let tag = new Tag(o[1],'tr',{}); 33 | let getNextTrInnerTag = generateGetNextValidTag(tag.innerHTML); 34 | while (true){ 35 | let o = getNextTrInnerTag(); 36 | if (o[0]==null && o[1]=='') break; 37 | if (o[1].trim().length==0) continue; 38 | if (o[0]!='th' && o[0]!='td') continue; 39 | count++; 40 | } 41 | } 42 | return count; 43 | }; 44 | while (true){ 45 | let o = getNextTag(); 46 | if (o[0]==null && o[1]=='') break; 47 | if (o[1].trim().length==0) continue; 48 | if (o[0]!='thead' && o[0]!='tbody') continue; 49 | let body = new Tag(o[1],o[0],{}); 50 | let bodyGetNextTag = generateGetNextValidTag(body.innerHTML); 51 | while (true){ 52 | let bodyo = bodyGetNextTag(); 53 | if (bodyo[0]==null && bodyo[1]=='') break; 54 | if (bodyo[1].trim().length==0) continue; 55 | if (bodyo[0]!='tr') continue; 56 | let res = lagacyCount(bodyo[1]); 57 | if (outthis.tableColumnCount) { 17 | console.warn('This is abnormal','\n',this.rawStr,'\n',content,'\n',this.tableColumnCount,count); 18 | count = this.tableColumnCount; 19 | } 20 | //console.log(this.rawStr,content,this.tableColumnCount,count); 21 | return '|' + content + '|'.repeat(this.tableColumnCount-count); 22 | 23 | //return '|' + content 24 | } 25 | 26 | parseValidSubTag( 27 | subTagStr: string, 28 | subTagName: string, 29 | options: ParseOptions 30 | ) { 31 | const { aliasTags } = config.get() 32 | const SubTagClass = getTagConstructor(subTagName) 33 | if ( 34 | subTagName !== 'td' && 35 | subTagName !== 'th' && 36 | aliasTags?.[subTagName] !== 'td' && 37 | aliasTags?.[subTagName] !== 'th' && 38 | SubTagClass !== __Ignore__ 39 | ) { 40 | console.error( 41 | `Should not have tags except or inside , current tag is ${subTagName} have been ignore.` 42 | ) 43 | return '' 44 | } else { 45 | const subTag = new SubTagClass(subTagStr, subTagName, options) 46 | return subTag.exec('', '') 47 | } 48 | } 49 | 50 | parseOnlyString() { 51 | return '' 52 | } 53 | 54 | exec(prevGap = '', endGap = '\n') { 55 | return super.exec(prevGap, endGap) 56 | } 57 | } 58 | export default Tr 59 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/tags/ul.ts: -------------------------------------------------------------------------------- 1 | import Tag from '../Tag' 2 | import __Ignore__ from './__ignore__' 3 | import { getTagConstructor } from '../utils' 4 | import config from '../config' 5 | import { ParseOptions, TagName, TagOptions } from '../type' 6 | const { aliasTags } = config.get() 7 | 8 | class Ul extends Tag { 9 | constructor(str: string, tagName = 'ul', options: TagOptions) { 10 | super(str, tagName, options) 11 | } 12 | 13 | __isValidSubTag__(subTagName: TagName): boolean { 14 | if (!subTagName) return false 15 | const SubTagClass = getTagConstructor(subTagName) 16 | return ( 17 | subTagName === 'li' || 18 | aliasTags?.[subTagName] == 'li' || 19 | SubTagClass === __Ignore__ 20 | ) 21 | } 22 | 23 | getValidSubTagName(subTagName: TagName) { 24 | if (!subTagName) return null 25 | return this.__isValidSubTag__(subTagName) ? subTagName : null 26 | } 27 | 28 | parseValidSubTag( 29 | subTagStr: string, 30 | subTagName: string, 31 | options: ParseOptions 32 | ): string { 33 | const SubTagClass = getTagConstructor(subTagName) 34 | if (this.__isValidSubTag__(subTagName)) { 35 | const subTag = new SubTagClass(subTagStr, subTagName, { 36 | ...options, 37 | calcLeading: true, 38 | leadingSpace: this.leadingSpace, 39 | layer: this.layer, 40 | match: '*', 41 | }) 42 | return subTag.exec('', '\n') 43 | } else { 44 | console.error( 45 | 'Should not have tags except
    4. inside ul, current tag is ' + 46 | subTagName + 47 | ', current tagStr is' + 48 | subTagStr 49 | ) 50 | return '' 51 | } 52 | } 53 | 54 | parseOnlyString() { 55 | return '' 56 | } 57 | 58 | exec(prevGap = '\n', endGap = '\n') { 59 | return super.exec(prevGap, endGap) 60 | } 61 | } 62 | 63 | export default Ul 64 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/type.ts: -------------------------------------------------------------------------------- 1 | export type OptionsKey = 2 | | 'skipTags' 3 | | 'emptyTags' 4 | | 'ignoreTags' 5 | | 'aliasTags' 6 | | 'renderCustomTags' 7 | 8 | export type TagName = string | null 9 | 10 | export type AFTSvgProcessOpt = { 11 | maxHight: number, 12 | maxWidth: number 13 | } 14 | 15 | export type AFTExtraData = { 16 | links: string[], 17 | pics: string[], 18 | nowUrl: string, 19 | processPicOpt: string, 20 | processSvgPicOpt: AFTSvgProcessOpt, 21 | processServerHost: string, 22 | head:{ 23 | level: number, 24 | txt: string 25 | } 26 | } 27 | 28 | export type Html2MdOptions = { 29 | skipTags?: string[] 30 | emptyTags?: string[] 31 | ignoreTags?: string[] 32 | aliasTags?: Record 33 | renderCustomTags?: boolean | 'SKIP' | 'EMPTY' | 'IGNORE', 34 | extraData?: AFTExtraData, 35 | enableATF?: boolean, 36 | tagListener?: ( 37 | tag: TagName, 38 | props: TagListenerProps 39 | ) => TagListenerReturnProps 40 | } 41 | 42 | export type TagOptions = { 43 | parentTag?: TagName 44 | prevTagName?: TagName 45 | nextTagName?: TagName 46 | keepSpace?: boolean 47 | prevTagStr?: string 48 | nextTagStr?: string 49 | isFirstSubTag?: boolean 50 | calcLeading?: boolean 51 | leadingSpace?: string 52 | layer?: number 53 | noWrap?: boolean 54 | match?: string | null 55 | indentSpace?: string 56 | language?: string 57 | count?: number 58 | tableColumnCount?: number 59 | noExtraLine?: boolean 60 | inTable?: boolean, 61 | picNoWrap?: boolean, 62 | extraData?: AFTExtraData 63 | } 64 | 65 | export type SelfCloseTagOptions = { 66 | parentTag?: TagName 67 | prevTagName?: TagName 68 | nextTagName?: TagName 69 | match?: string | null 70 | isFirstSubTag?: boolean 71 | leadingSpace?: string 72 | layer?: number 73 | inTable?: boolean, 74 | picNoWrap?: boolean, 75 | extraData?: AFTExtraData 76 | } 77 | 78 | export type ParseOptions = { 79 | parentTag?: TagName 80 | prevTagName?: TagName 81 | nextTagName?: TagName 82 | nextTagStr?: string 83 | prevTagStr?: string 84 | leadingSpace?: string 85 | layer?: number 86 | keepSpace?: boolean 87 | calcLeading?: boolean 88 | inTable?: boolean, 89 | picNoWrap?: boolean, 90 | extraData?: AFTExtraData 91 | } 92 | 93 | export interface TagProps { 94 | tagName: TagName 95 | parentTag: TagName 96 | prevTagName: TagName 97 | nextTagName: TagName 98 | rawStr: string 99 | prevTagStr: string 100 | nextTagStr: string 101 | isFirstSubTag: boolean 102 | calcLeading: boolean 103 | leadingSpace: string 104 | layer: number 105 | noWrap: boolean 106 | match: string | null 107 | indentSpace: string 108 | language: string 109 | count: number 110 | tableColumnCount: number 111 | noExtraLine: boolean 112 | keepSpace: boolean 113 | attrs: Record 114 | innerHTML: string 115 | inTable: boolean, 116 | picNoWrap: boolean, 117 | extraData?: AFTExtraData 118 | } 119 | 120 | export interface SelfCloseTagProps { 121 | tagName: TagName 122 | parentTag: TagName 123 | prevTagName: TagName 124 | nextTagName: TagName 125 | rawStr: string 126 | isFirstSubTag: boolean 127 | match: string | null 128 | leadingSpace: string 129 | layer: number 130 | attrs: Record 131 | innerHTML: string 132 | inTable: boolean, 133 | picNoWrap: boolean, 134 | extraData?: AFTExtraData 135 | } 136 | 137 | export type TagListenerProps = { 138 | parentTag: TagName 139 | prevTagName: TagName 140 | nextTagName: TagName 141 | isFirstSubTag: boolean 142 | attrs: Record 143 | innerHTML: string 144 | match: string | null 145 | isSelfClosing: boolean 146 | language?: string 147 | } 148 | 149 | export type TagListenerReturnProps = { 150 | attrs: Record 151 | match: string | null 152 | language?: string 153 | } 154 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/CONSTANT.ts: -------------------------------------------------------------------------------- 1 | const SINGLE = '☈' 2 | const DOUBLE = '☈☈' 3 | const TRIPLE = '☈☈☈' 4 | 5 | export { SINGLE, DOUBLE, TRIPLE } 6 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/clearComment.ts: -------------------------------------------------------------------------------- 1 | function clearComment(str: string): string { 2 | return str.replace(//g, '') 3 | } 4 | 5 | export default clearComment 6 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/escape.ts: -------------------------------------------------------------------------------- 1 | const unescapeMap: Record = {} 2 | const escapeMap: Record = { 3 | '&': '&', 4 | '<': '<', 5 | '>': '>', 6 | '"': '"', 7 | "'": ''', 8 | '`': '`', 9 | '“': '“', 10 | '”': '”', 11 | } 12 | 13 | for (const key in escapeMap) { 14 | unescapeMap[escapeMap[key]] = key 15 | } 16 | 17 | // const reUnescapedHtml = /[&<>"'`“”]/g 18 | // const reHasUnescapedHtml = RegExp(reUnescapedHtml.source) 19 | const reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#x60|ldquo|rdquo);/g 20 | const reHasEscapedHtml = RegExp(reEscapedHtml.source) 21 | const _extra_escapes: [RegExp, string][] = [ 22 | [/\\/g, '\\\\'], 23 | [/\*/g, '\\*'], 24 | [/^-/g, '\\-'], 25 | [/^\+ /g, '\\+ '], 26 | [/^(=+)/g, '\\$1'], 27 | [/^(#{1,6}) /g, '\\$1 '], 28 | [/`/g, '\\`'], 29 | [/^~~~/g, '\\~~~'], 30 | [/\[/g, '\\['], 31 | [/\]/g, '\\]'], 32 | [/>/g, '\\>'], 33 | [/ escapeMap[chr]) 40 | // : s 41 | // } 42 | 43 | function unescapeStr(s: string): string { 44 | s = 45 | s && reHasEscapedHtml.test(s) 46 | ? s.replace(reEscapedHtml, (entity) => unescapeMap[entity]) 47 | : s 48 | return s 49 | } 50 | 51 | function extraEscape(s: string): string { 52 | return _extra_escapes.reduce(function (accumulator, escape) { 53 | return accumulator.replace(escape[0], escape[1]) 54 | }, s) 55 | } 56 | 57 | export { extraEscape, unescapeStr } 58 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/generateGetNextValidTag.ts: -------------------------------------------------------------------------------- 1 | import isSelfClosing from './isSelfClosing' 2 | 3 | function getTagName(str: string, idx: number) { 4 | let name = '' 5 | while (idx < str.length && /[a-zA-Z0-9!-]/.test(str[idx])) { 6 | name += str[idx++] 7 | } 8 | 9 | return name.toLowerCase() 10 | } 11 | // 找到下一个有效tag, 可多次调用,返回[tagName, tagContent] 12 | // 例如: abcabc, 返回['b', 'abc'] 13 | function generateGetNextValidTag(str: string) { 14 | let startId = 0 15 | return (): [string | null, string] => { 16 | let res = '' 17 | let startTagName: string | null = null 18 | let count = 0 19 | let endTagName: string | null = null 20 | let canBeBreak = false 21 | if (startId >= str.length) { 22 | return [startTagName, res] 23 | } 24 | for (let i = startId; i < str.length; i++) { 25 | if (str[i] === '<' && str[i + 1] !== '/') { 26 | if (res !== '' && startTagName == null && !canBeBreak) { 27 | startId = i 28 | return [startTagName, res] 29 | } 30 | const tempName = getTagName(str, i + 1) 31 | if (startTagName == null) { 32 | startTagName = tempName 33 | } 34 | if (startTagName === tempName) count++ 35 | 36 | if (isSelfClosing(startTagName)) { 37 | count-- 38 | if (count === 0) canBeBreak = true 39 | if (count < 0) console.warn(`Tag ${startTagName} is abnormal`) 40 | } 41 | } else if (str[i] === '<' && str[i + 1] === '/') { 42 | if (startTagName == null) { 43 | console.warn( 44 | `Tag is not integrity, current tagStr is ${str.slice(startId)}` 45 | ) 46 | let id = i 47 | while (id < str.length && str[id] !== '>') { 48 | id++ 49 | } 50 | i = id 51 | continue 52 | } 53 | endTagName = getTagName(str, i + 2) 54 | if (startTagName === endTagName) { 55 | count-- 56 | } 57 | if (count <= 0) canBeBreak = true 58 | } 59 | res += str[i] 60 | if (str[i] === '>' && canBeBreak) { 61 | startId = i + 1 62 | return [startTagName, res] 63 | } 64 | if (i === str.length - 1) { 65 | if (startTagName !== endTagName) { 66 | /* 67 | if (endTagName != null && startTagName != null) { 68 | res = res 69 | .replace(`<${startTagName}>`, '') 70 | .replace(``, '') 71 | } 72 | startTagName = null 73 | */ 74 | 75 | // fix render error in not closing tag 76 | if (endTagName != null && startTagName != null) { 77 | res += ``; 78 | }else{ 79 | startTagName = null; 80 | } 81 | 82 | } 83 | } 84 | } 85 | 86 | startId = str.length 87 | return [startTagName, res] 88 | } 89 | } 90 | 91 | export default generateGetNextValidTag 92 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/getLanguage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * { 3 | 'js':'javascript', 4 | 'javascript':'javascript', 5 | 'java':'java', 6 | 'python':'python', 7 | 'py':'python', 8 | 'cpp':'cpp', 9 | 'c++':'cpp', 10 | 'css':'css', 11 | 'html':'html', 12 | 'markdown':'markdown', 13 | 'md':'markdown' 14 | 'c':'c' 15 | } 16 | * */ 17 | 18 | // const toStandard={ 19 | // 'js':'javascript', 20 | // 'py':'python', 21 | // 'c++':'cpp', 22 | // 'md':'markdown' 23 | // } 24 | 25 | const DEFAULT_LANG = 'javascript' 26 | 27 | // only exec in pre tag 28 | function getLanguage(str: string): string { 29 | const matchLang = str.match(/<.*?class=".*?language-([^\s"]*)?.*".*>/) 30 | if (matchLang) return matchLang[1] || '' 31 | const match = str.match( 32 | // 33 | ) 34 | return match ? DEFAULT_LANG : '' 35 | } 36 | 37 | export default getLanguage 38 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/getRealTagName.ts: -------------------------------------------------------------------------------- 1 | import config from '../config' 2 | 3 | function getRealTagName(tagName: string | null) { 4 | if (!tagName) return tagName 5 | const { aliasTags } = config.get() 6 | if (aliasTags?.[tagName] != null) { 7 | return aliasTags[tagName] 8 | } 9 | return tagName 10 | } 11 | 12 | export default getRealTagName 13 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/getTableAlign.ts: -------------------------------------------------------------------------------- 1 | type AlignKeys = 'center' | 'left' | 'right' | 'start' | 'end' 2 | 3 | function getTableAlign(str: string, tableColumnCount: number) { 4 | const alignObj = { 5 | _default_: '---|', 6 | center: ':---:|', 7 | left: ':---|', 8 | right: '---:|', 9 | start: ':---|', 10 | end: '---:|', 11 | } 12 | let res = Array(tableColumnCount).fill(alignObj._default_) 13 | const match = str.match(/<(td|th)(.*?)>/g) 14 | if (!match) return res 15 | res = match.slice(0, tableColumnCount) 16 | // console.log(res,match,str) 17 | res = res.map((s) => { 18 | const alignMatch: [unknown, AlignKeys] | null = s.match( 19 | /align\s*=\s*['"]\s*(center|left|right|start|end)/ 20 | ) 21 | const styleMatch: [unknown, AlignKeys] | null = s.match( 22 | /text-align\s*:\s*(center|left|right|start|end)/ 23 | ) 24 | // Style first 25 | if (!alignMatch && !styleMatch) { 26 | return alignObj._default_ 27 | } else if (alignMatch && !styleMatch) { 28 | return alignObj[alignMatch[1]] || alignObj._default_ 29 | } else if (styleMatch) { 30 | return alignObj[styleMatch[1]] || alignObj._default_ 31 | } 32 | }) 33 | 34 | return res 35 | } 36 | export default getTableAlign 37 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/getTagAttributes.ts: -------------------------------------------------------------------------------- 1 | function getTagAttributes(attrStr: string): Record { 2 | const attrsObj: Record = {} 3 | let inside = false 4 | let key = '' 5 | let value = '' 6 | let quote = null 7 | for (let i = 0; i <= attrStr.length; i++) { 8 | if (i === attrStr.length || /\s/.test(attrStr[i])) { 9 | if (i === attrStr.length || !inside) { 10 | let slimKey = key.trim() 11 | if (slimKey[slimKey.length - 1] === '/') { 12 | slimKey = slimKey.slice(0, slimKey.length - 1) 13 | } 14 | if (slimKey) { 15 | attrsObj[slimKey] = value.trim() 16 | } 17 | key = '' 18 | value = '' 19 | } 20 | } else if (/['"]/.test(attrStr[i]) && (!quote || attrStr[i] === quote)) { 21 | // todo add test case 22 | inside = !inside 23 | if (inside) quote = attrStr[i] 24 | continue 25 | // only pass not inside attr value (https://github.com/stonehank/html-to-md/issues/43) 26 | } else if (attrStr[i] === '=' && !inside) { 27 | continue 28 | } 29 | if (i === attrStr.length) break 30 | if (!inside) key += attrStr[i] 31 | else value += attrStr[i] 32 | } 33 | 34 | return attrsObj 35 | } 36 | 37 | export default getTagAttributes 38 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/getTagConstructor.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import config from '../config' 3 | import isSelfClosing from './isSelfClosing' 4 | import isValidHTMLTags from './isValidHTMLTags' 5 | 6 | function getTagConstructor(tagName: string): any { 7 | let clazz 8 | const { skipTags, emptyTags, ignoreTags, aliasTags, renderCustomTags } = 9 | config.get() 10 | const selfClose = isSelfClosing(tagName) 11 | if (skipTags?.includes(tagName)) { 12 | const skip = require('../tags/__skip__') 13 | return selfClose ? skip.__SkipSelfClose__ : skip.__Skip__ 14 | } 15 | if (emptyTags?.includes(tagName)) { 16 | const empty = require('../tags/__empty__') 17 | return selfClose ? empty.__EmptySelfClose__ : empty.__Empty__ 18 | } 19 | if (ignoreTags?.includes(tagName)) { 20 | return require('../tags/__ignore__').default 21 | } 22 | if (aliasTags?.[tagName] != null) { 23 | const newTagName = aliasTags[tagName] 24 | return getTagConstructor(newTagName) 25 | } 26 | 27 | const lowerTag = tagName.toLowerCase() 28 | if (renderCustomTags !== true && !isValidHTMLTags(lowerTag)) { 29 | if (renderCustomTags === false || renderCustomTags === 'SKIP') { 30 | const skip = require('../tags/__skip__') 31 | return selfClose ? skip.__SkipSelfClose__ : skip.__Skip__ 32 | } 33 | if (renderCustomTags === 'EMPTY') { 34 | const empty = require('../tags/__empty__') 35 | return selfClose ? empty.__EmptySelfClose__ : empty.__Empty__ 36 | } 37 | if (renderCustomTags === 'IGNORE') { 38 | return require('../tags/__ignore__').default 39 | } 40 | } 41 | 42 | try { 43 | clazz = require(`../tags/${tagName}`).default 44 | } catch (e) { 45 | if (selfClose) { 46 | clazz = require('../tags/__nomatch__').__NoMatchSelfClose__ 47 | } else { 48 | clazz = require('../tags/__nomatch__').__NoMatch__ 49 | } 50 | } 51 | 52 | return clazz 53 | } 54 | 55 | export default getTagConstructor 56 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { extraEscape, unescapeStr } from './escape' 2 | import generateGetNextValidTag from './generateGetNextValidTag' 3 | import getTagConstructor from './getTagConstructor' 4 | import isSelfClosing from './isSelfClosing' 5 | import getTagAttributes from './getTagAttributes' 6 | import getLanguage from './getLanguage' 7 | import clearComment from './clearComment' 8 | import getRealTagName from './getRealTagName' 9 | import isIndependentTag from './isIndependentTag' 10 | import getTableAlign from './getTableAlign' 11 | import shouldRenderRawInside from './shouldRenderRawInside' 12 | 13 | export { 14 | // escape, 15 | extraEscape, 16 | unescapeStr, 17 | getRealTagName, 18 | getTagConstructor, 19 | generateGetNextValidTag, 20 | isSelfClosing, 21 | getTagAttributes, 22 | getTableAlign, 23 | getLanguage, 24 | clearComment, 25 | isIndependentTag, 26 | shouldRenderRawInside, 27 | } 28 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/isIndependentTag.ts: -------------------------------------------------------------------------------- 1 | import { TagName } from '../type' 2 | import getRealTagName from './getRealTagName' 3 | 4 | const independentLineTags: Record = { 5 | html: true, 6 | body: true, 7 | p: true, 8 | div: true, 9 | pre: true, 10 | section: true, 11 | blockquote: true, 12 | aside: true, 13 | li: true, 14 | ul: true, 15 | ol: true, 16 | form: true, 17 | hr: true, 18 | h1: true, 19 | h2: true, 20 | h3: true, 21 | h4: true, 22 | h5: true, 23 | h6: true, 24 | dl: true, 25 | dd: true, 26 | dt: true, 27 | br: true, 28 | } 29 | 30 | function isIndependentTag(tagName?: TagName): boolean { 31 | if (!tagName) return false 32 | const realName = getRealTagName(tagName) 33 | if (!realName) return false 34 | return !!independentLineTags[realName] 35 | } 36 | 37 | export default isIndependentTag 38 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/isSelfClosing.ts: -------------------------------------------------------------------------------- 1 | const selfTags: Record = { 2 | img: true, 3 | hr: true, 4 | input: true, 5 | br: true, 6 | meta: true, 7 | link: true, 8 | '!doctype': true, 9 | base: true, 10 | col: true, 11 | area: true, 12 | param: true, 13 | object: true, 14 | embed: true, 15 | keygen: true, 16 | source: true, 17 | } 18 | 19 | function isSelfClosing(tag: string | null): boolean { 20 | if (tag == null) return false 21 | return !!selfTags[tag.toLowerCase()] 22 | } 23 | 24 | export default isSelfClosing 25 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/isValidHTMLTags.ts: -------------------------------------------------------------------------------- 1 | const tags = [ 2 | '!doctype', 3 | 'a', 4 | 'abbr', 5 | 'acronym', 6 | 'address', 7 | 'applet', 8 | 'area', 9 | 'article', 10 | 'aside', 11 | 'audio', 12 | 'b', 13 | 'base', 14 | 'basefont', 15 | 'bdi', 16 | 'bdo', 17 | 'bgsound', 18 | 'big', 19 | 'blink', 20 | 'blockquote', 21 | 'body', 22 | 'br', 23 | 'button', 24 | 'canvas', 25 | 'caption', 26 | 'center', 27 | 'circle', 28 | 'cite', 29 | 'clipPath', 30 | 'code', 31 | 'col', 32 | 'colgroup', 33 | 'command', 34 | 'content', 35 | 'data', 36 | 'datalist', 37 | 'dd', 38 | 'defs', 39 | 'del', 40 | 'details', 41 | 'dfn', 42 | 'dialog', 43 | 'dir', 44 | 'div', 45 | 'dl', 46 | 'dt', 47 | 'element', 48 | 'ellipse', 49 | 'em', 50 | 'embed', 51 | 'fieldset', 52 | 'figcaption', 53 | 'figure', 54 | 'font', 55 | 'footer', 56 | 'foreignObject', 57 | 'form', 58 | 'frame', 59 | 'frameset', 60 | 'g', 61 | 'h1', 62 | 'h2', 63 | 'h3', 64 | 'h4', 65 | 'h5', 66 | 'h6', 67 | 'head', 68 | 'header', 69 | 'hgroup', 70 | 'hr', 71 | 'html', 72 | 'i', 73 | 'iframe', 74 | 'image', 75 | 'img', 76 | 'input', 77 | 'ins', 78 | 'isindex', 79 | 'kbd', 80 | 'keygen', 81 | 'label', 82 | 'legend', 83 | 'li', 84 | 'line', 85 | 'linearGradient', 86 | 'link', 87 | 'listing', 88 | 'main', 89 | 'map', 90 | 'mark', 91 | 'marquee', 92 | 'mask', 93 | 'math', 94 | 'menu', 95 | 'menuitem', 96 | 'meta', 97 | 'meter', 98 | 'multicol', 99 | 'nav', 100 | 'nextid', 101 | 'nobr', 102 | 'noembed', 103 | 'noframes', 104 | 'noscript', 105 | 'object', 106 | 'ol', 107 | 'optgroup', 108 | 'option', 109 | 'output', 110 | 'p', 111 | 'param', 112 | 'path', 113 | 'pattern', 114 | 'picture', 115 | 'plaintext', 116 | 'polygon', 117 | 'polyline', 118 | 'pre', 119 | 'progress', 120 | 'q', 121 | 'radialGradient', 122 | 'rb', 123 | 'rbc', 124 | 'rect', 125 | 'rp', 126 | 'rt', 127 | 'rtc', 128 | 'ruby', 129 | 's', 130 | 'samp', 131 | 'script', 132 | 'section', 133 | 'select', 134 | 'shadow', 135 | 'slot', 136 | 'small', 137 | 'source', 138 | 'spacer', 139 | 'span', 140 | 'stop', 141 | 'strike', 142 | 'strong', 143 | 'style', 144 | 'sub', 145 | 'summary', 146 | 'sup', 147 | 'svg', 148 | 'table', 149 | 'tbody', 150 | 'td', 151 | 'template', 152 | 'text', 153 | 'textarea', 154 | 'tfoot', 155 | 'th', 156 | 'thead', 157 | 'time', 158 | 'title', 159 | 'tr', 160 | 'track', 161 | 'tspan', 162 | 'tt', 163 | 'u', 164 | 'ul', 165 | 'var', 166 | 'video', 167 | 'wbr', 168 | 'xmp', 169 | ] 170 | 171 | function isValidHTMLTags(tag: unknown) { 172 | if (typeof tag !== 'string') return false 173 | return tags.includes(tag.toLowerCase()) 174 | } 175 | 176 | export default isValidHTMLTags 177 | 178 | /* 179 | [ 180 | 'a', 'b', 'blockquote', 181 | 'br', 'code', 'del', 182 | 'em', 'h1', 'h2', 183 | 'h3', 'h4', 'h5', 184 | 'h6', 'hr', 'i', 185 | 'img', 'input', 'li', 186 | 'ol', 'p', 'pre', 187 | 's', 'span', 'strong', 188 | 'table', 'tbody', 'td', 189 | 'th', 'thead', 'tr', 190 | 'ul' 191 | ] 192 | */ 193 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/shouldRenderRawInside.ts: -------------------------------------------------------------------------------- 1 | export default ['th', 'td'] 2 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/src/utils/trim.ts: -------------------------------------------------------------------------------- 1 | const regType: Record = { 2 | all: /\s/, 3 | linebreak: /\n/, 4 | whitespace: /([ \t])/, 5 | } 6 | 7 | type TrimType = 'whitespace' | 'linebreak' | 'all' 8 | type OriType = 'left' | 'right' | 'all' 9 | type TrimOptions = { 10 | type?: TrimType 11 | ori?: OriType 12 | } 13 | 14 | function trim( 15 | str: string, 16 | { type = 'whitespace', ori = 'left' }: TrimOptions = {} 17 | ): string { 18 | if (!str) return '' 19 | const { leftSpace, rightSpace, content } = parse(str) 20 | const reg: RegExp = regType[type] 21 | if (ori === 'left') { 22 | return filter(leftSpace, reg) + content + rightSpace 23 | } else if (ori === 'right') { 24 | return leftSpace + content + filter(rightSpace, reg) 25 | } else if (ori === 'all') { 26 | return filter(leftSpace, reg) + content + filter(rightSpace, reg) 27 | } 28 | return str 29 | } 30 | 31 | function filter(str: string, reg: RegExp): string { 32 | let newStr = '' 33 | for (let i = 0; i < str.length; i++) { 34 | if (reg.test(str[i])) continue 35 | newStr += str[i] 36 | } 37 | return newStr 38 | } 39 | 40 | function parse(str: string): { 41 | leftSpace: string 42 | rightSpace: string 43 | content: string 44 | } { 45 | let leftSpace = '' 46 | let rightSpace = '' 47 | let leftEndIdx = -1 48 | let rightStartIdx = -1 49 | for (let i = 0; i < str.length; i++) { 50 | if (/\s/.test(str[i])) { 51 | leftSpace += str[i] 52 | } else { 53 | leftEndIdx = i 54 | break 55 | } 56 | } 57 | for (let i = str.length - 1; i >= 0; i--) { 58 | if (/\s/.test(str[i])) { 59 | rightSpace = str[i] + rightSpace 60 | } else { 61 | rightStartIdx = i 62 | break 63 | } 64 | } 65 | return { 66 | leftSpace, 67 | rightSpace, 68 | content: str.slice(leftEndIdx, rightStartIdx + 1), 69 | } 70 | } 71 | 72 | export default trim 73 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "rootDir": "./src" 9 | } 10 | } -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isWsl = require('is-wsl') 4 | const path = require('path') 5 | const TerserPlugin = require('terser-webpack-plugin') 6 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 7 | const BundleAnalyzerPlugin = 8 | require('webpack-bundle-analyzer').BundleAnalyzerPlugin 9 | 10 | const src = path.join(__dirname, 'src') 11 | 12 | /** @type {import('webpack').Configuration} */ 13 | module.exports = { 14 | mode: 'production', 15 | entry: path.join(src, 'index.ts'), 16 | output: { 17 | path: path.join(__dirname, 'dist'), 18 | filename: 'index.js', 19 | library: { 20 | root: 'html2md', 21 | amd: 'html2md', 22 | commonjs: 'html2md', 23 | }, 24 | libraryExport: 'default', 25 | globalObject: 'this', 26 | libraryTarget: 'umd', 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [ 31 | new TerserPlugin({ 32 | terserOptions: { 33 | parse: { 34 | ecma: 8, 35 | }, 36 | compress: { 37 | ecma: 5, 38 | warnings: false, 39 | comparisons: false, 40 | inline: 2, 41 | drop_console: false, 42 | pure_funcs:['console.log'] 43 | }, 44 | mangle: { 45 | safari10: true, 46 | }, 47 | output: { 48 | ecma: 5, 49 | comments: false, 50 | ascii_only: true, 51 | }, 52 | }, 53 | parallel: !isWsl, 54 | cache: true, 55 | }), 56 | ], 57 | }, 58 | 59 | resolve: { 60 | extensions: ['.ts', '.js', '.json'], 61 | }, 62 | module: { 63 | rules: [ 64 | { parser: { requireEnsure: false } }, 65 | { 66 | test: /\.tsx?$/, 67 | use: 'ts-loader', 68 | exclude: /node_modules/, 69 | }, 70 | { 71 | test: /\.js$/, 72 | include: path.resolve(__dirname, 'src'), 73 | loader: 'babel-loader', 74 | options: { 75 | cacheDirectory: true, 76 | cacheCompression: true, 77 | compact: true, 78 | }, 79 | }, 80 | ], 81 | }, 82 | plugins: [ 83 | new CleanWebpackPlugin(), 84 | new BundleAnalyzerPlugin({ analyzerMode: 'static' }), 85 | ], 86 | } 87 | -------------------------------------------------------------------------------- /ext/built-in/textweb/third/html-to-md/webpack.demo.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isWsl = require('is-wsl') 4 | const path = require('path') 5 | const TerserPlugin = require('terser-webpack-plugin') 6 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 7 | const HtmlWebPackPlugin = require('html-webpack-plugin') 8 | 9 | const src = path.join(__dirname, 'src') 10 | 11 | module.exports = (env) => { 12 | const isDev = process.env.NODE_ENV === 'development' 13 | return { 14 | mode: isDev ? 'development' : 'production', 15 | entry: path.join(src, 'index.ts'), 16 | output: { 17 | path: path.join(__dirname, 'demo'), 18 | filename: './index.[contenthash].js', 19 | library: 'html2md', 20 | libraryExport: 'default', 21 | globalObject: 'this', 22 | libraryTarget: 'umd', 23 | }, 24 | devtool: isDev ? 'cheap-module-source-map' : false, 25 | optimization: isDev 26 | ? {} 27 | : { 28 | minimize: true, 29 | minimizer: [ 30 | new TerserPlugin({ 31 | terserOptions: { 32 | parse: { 33 | ecma: 8, 34 | }, 35 | compress: { 36 | ecma: 5, 37 | warnings: false, 38 | comparisons: false, 39 | inline: 2, 40 | }, 41 | mangle: { 42 | safari10: true, 43 | }, 44 | output: { 45 | ecma: 5, 46 | comments: false, 47 | ascii_only: true, 48 | }, 49 | }, 50 | parallel: !isWsl, 51 | cache: true, 52 | }), 53 | ], 54 | }, 55 | 56 | resolve: { 57 | extensions: ['.ts', '.js', '.json'], 58 | }, 59 | module: { 60 | rules: [ 61 | { parser: { requireEnsure: false } }, 62 | { 63 | test: /\.tsx?$/, 64 | use: 'ts-loader', 65 | exclude: /node_modules/, 66 | }, 67 | { 68 | test: /\.(js)$/, 69 | include: path.resolve(__dirname, 'src'), 70 | loader: 'babel-loader', 71 | options: isDev 72 | ? { 73 | cacheDirectory: true, 74 | cacheCompression: true, 75 | compact: true, 76 | } 77 | : {}, 78 | }, 79 | { 80 | test: /\.css$/, 81 | use: [ 82 | 'style-loader', 83 | 'css-loader', 84 | { 85 | loader: 'postcss-loader', 86 | options: { 87 | ident: 'postcss', 88 | plugins: () => [ 89 | require('postcss-flexbugs-fixes'), 90 | require('postcss-preset-env')({ 91 | stage: 3, 92 | }), 93 | ], 94 | }, 95 | }, 96 | ], 97 | }, 98 | { 99 | test: /\.html$/, 100 | use: [ 101 | { 102 | loader: 'html-loader', 103 | }, 104 | ], 105 | }, 106 | ], 107 | }, 108 | plugins: [ 109 | new CleanWebpackPlugin(), 110 | new HtmlWebPackPlugin({ 111 | template: './index.html', 112 | filename: './index.html', 113 | }), 114 | ], 115 | devServer: isDev 116 | ? { 117 | clientLogLevel: 'none', 118 | overlay: true, 119 | } 120 | : {}, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ext/example/async/index.js: -------------------------------------------------------------------------------- 1 | export const applicationName = 'asyncSample'; 2 | 3 | const recommendRes = ['test','a','b','114514','2233','overflow']; 4 | 5 | const helpInfo = ` 6 | ### asyncSample 7 | This sample extension will show you how to use async in event handle in two different ways. 8 | 9 | - using \`hold()\` in event \`input\` 10 | - using \`Promise\` in event \`switchIn\` 11 | 12 | Let's start. 13 | `; 14 | 15 | const fadeTime = 40; 16 | 17 | async function fadeIn(txt,t,update,nowNum,maxNum,suggestedResponse){ 18 | var i = 0; 19 | return new Promise((resolve,reject)=>{ 20 | const f = ()=>{ 21 | update(txt.substring(0,i+1),nowNum,maxNum,[]); 22 | i++; 23 | if (i<=txt.length){ 24 | setTimeout(f,t); 25 | }else{ 26 | update(txt,nowNum,maxNum,suggestedResponse); 27 | resolve(); 28 | } 29 | }; 30 | f(); 31 | }); 32 | } 33 | 34 | async function fadeOut(txt,t,update,nowNum,maxNum,suggestedResponse){ 35 | var i = txt.length - 1; 36 | return new Promise((resolve,reject)=>{ 37 | const f = ()=>{ 38 | update(txt.substring(0,i+1),nowNum,maxNum,[]); 39 | i--; 40 | if (i>=0){ 41 | setTimeout(f,t); 42 | }else{ 43 | update('',nowNum,maxNum,suggestedResponse); 44 | resolve(); 45 | } 46 | }; 47 | f(); 48 | }); 49 | } 50 | 51 | export function init(session,cb){ 52 | cb(helpInfo,recommendRes,0,2048); 53 | } 54 | 55 | export async function input(session,msg,update,done,isAlive,hold){ 56 | hold(); 57 | await fadeIn(`this input event used \`hold()\`.`,fadeTime,update,0,2048,[]); 58 | setTimeout(async ()=>{ 59 | await fadeOut(`this input event used \`hold()\`.`,fadeTime,update,0,2048,[]); 60 | await fadeIn(helpInfo,fadeTime,update,0,2048,recommendRes); 61 | done(); 62 | },3000); 63 | } 64 | 65 | export async function switchIn(session,cb){ 66 | const f = (txt,nowNum,maxNum,suggestedResponse)=>{ 67 | cb(txt,suggestedResponse,nowNum,maxNum); 68 | }; 69 | return new Promise(async (resolve,reject)=>{ 70 | await fadeIn(`this input event used \`Promise\`.`,fadeTime,f,0,2048,[]); 71 | setTimeout(async ()=>{ 72 | await fadeOut(`this input event used \`Promise\`.`,fadeTime,f,0,2048,[]); 73 | await fadeIn(helpInfo,fadeTime,f,0,2048,recommendRes); 74 | resolve(); 75 | },3000); 76 | }); 77 | } -------------------------------------------------------------------------------- /ext/example/mockingBird/index.js: -------------------------------------------------------------------------------- 1 | export const applicationName = 'mockingBird'; 2 | 3 | var lastCid = ''; 4 | var nowNum = 0; 5 | const recommendRes = ['test','a','b','114514','2233','overflow']; 6 | 7 | export function init(session,cb){ 8 | lastCid = session.cid; 9 | cb(`### mockingBird\nHello, ${lastCid}.`,recommendRes,0,114514); 10 | } 11 | 12 | export function input(session,msg,update,done,isAlive,hold){ 13 | var out = ''; 14 | 15 | nowNum++; 16 | if (nowNum>114514) nowNum = 0; 17 | 18 | if (session.cid!=lastCid){ 19 | lastCid = session.cid; 20 | out += `You created a new conversation, ${lastCid}! \n`; 21 | } 22 | 23 | out += `${msg.text} \n`; 24 | 25 | update(out,nowNum,114514,recommendRes); 26 | done(); 27 | } 28 | 29 | export function switchIn(session,cb){ 30 | lastCid = session.cid; 31 | cb(`### mockingBird\nWelcome back, ${lastCid}.`,recommendRes,nowNum,114514); 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ydpext", 3 | "version": "1.0.0", 4 | "license":"MIT", 5 | "type": "module", 6 | "dependencies": { 7 | "ws": "^8.13.0" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.5.1", 11 | "@types/ws": "^8.5.5", 12 | "tsx": "^3.12.7", 13 | "typescript": "^5.1.6" 14 | }, 15 | "scripts": { 16 | "init": "./script/init.sh", 17 | "build:main": "./script/build.main.sh", 18 | "build:full": "./script/build.full.sh" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ydpExt 2 | 在Penmods正式推出扩展程序功能前,这是一个在ydp上使用自定义扩展程序的可行解决方案。 3 | 它的运行效果可以前往[位于Penmods社区的预发行贴](https://www.google.com/search?q=404)查看。 4 | 5 | **适配 penmods 1.3.x 的版本: v1.0.1** 6 | **适配 penmods 1.2.x 的版本: v1.0.0** 7 | 8 | **不再更新,Archived.** 9 | 10 | ## 上手本项目 11 | ### 初始化项目 12 | ```bash 13 | npm run init 14 | ``` 15 | ### 构建主程序及内置扩展 16 | ```bash 17 | npm run build:main 18 | ``` 19 | ### 构建发行包 20 | ```bash 21 | npm run build:full 22 | ``` 23 | ## 文档 24 | 如何安装?[点我](./docs/install.md#how-to-install) 25 | 如何卸载?[点我](./docs/install.md#how-to-uninstall) 26 | 如何更新?[点我](./docs/install.md#how-to-update) 27 | 如何使用?[点我](./docs/use.md) 28 | 不会用内置扩展程序?[点我](./docs/use.md#内置扩展程序的使用) 29 | 我有疑问。[点我](./docs/faqs.md) 30 | 我想开发扩展程序。[点我](./docs/develop.md) 31 | -------------------------------------------------------------------------------- /script/build.full.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # build.full.sh 4 | 5 | # MIT License Copyright (c) 2023 Wxp 6 | 7 | echo '[info] start to build ydpExt full part.' 8 | node ./script/build.js 9 | echo '[info] start to copy setup scripts.' 10 | mkdir ./.tmp 11 | mv ./build/* ./.tmp 12 | mkdir ./build/youdaoExt 13 | mv ./.tmp/* ./build/youdaoExt 14 | rm -r ./.tmp 15 | cp ./util/* ./build/ 16 | echo '[info] copy setup scripts operation done.' 17 | echo '[info] operation done.' -------------------------------------------------------------------------------- /script/build.js: -------------------------------------------------------------------------------- 1 | 2 | // build.js 3 | 4 | // MIT License Copyright (c) 2023 Wxp 5 | 6 | // here we build the built-in exts 7 | 8 | import * as fs from "node:fs"; 9 | import * as child from "node:child_process"; 10 | 11 | const rootPath = (()=>{var t = process.argv[1].split('/');t.pop();return t.join('/');})(); 12 | 13 | var builtInList = []; 14 | 15 | if (!fs.existsSync('./ext/built-in')){ 16 | console.warn('[warn] path "./ext/built-in" not existed.'); 17 | }else{ 18 | builtInList = fs.readdirSync('./ext/built-in'); 19 | } 20 | 21 | function main(){ 22 | console.log('[info] start to build main.'); 23 | console.log('[info] removing old building.'); 24 | if (fs.existsSync('./build')){ 25 | fs.rmSync('./build',{recursive:true}); 26 | } 27 | fs.mkdirSync('./build',{recursive:true}); 28 | console.log('[info] building ydpExt.'); 29 | try{ 30 | child.execSync('npx tsc'); 31 | }catch(e){ 32 | console.error(`[error] failed to build ydpExt.`); 33 | //console.log(Object.keys(e)); 34 | if (Buffer.isBuffer(e.stdout)){ 35 | console.log('[error] here are the stdout. '); 36 | console.log(e.stdout.toString()); 37 | } 38 | if (Buffer.isBuffer(e.stderr)){ 39 | console.log('[error] here are the stderr. '); 40 | console.log(e.stderr.toString()); 41 | } 42 | console.log('[error] here the raw error object. '); 43 | console.log(e); 44 | 45 | console.error('[info] build failed. '); 46 | process.exit(-1); 47 | } 48 | fs.mkdirSync('./build/node_modules',{recursive:true}); 49 | fs.cpSync(`./package.json`,`./build/package.json`,{force:true}); 50 | fs.cpSync(`./LICENSE`,`./build/LICENSE`,{force:true}); 51 | fs.cpSync(`./node_modules/ws`,`./build/node_modules/ws`,{recursive:true,force:true}); 52 | console.log('[info] start to build the built-in exts.'); 53 | fs.mkdirSync('./build/ext',{recursive:true}); 54 | for (let i=0;i{var t = process.argv[1].split('/');t.pop();return t.join('/');})(); 13 | 14 | var builtInList = []; 15 | 16 | if (!fs.existsSync('./ext/built-in')){ 17 | console.warn('[warn] path "./ext/built-in" not existed.'); 18 | }else{ 19 | builtInList = fs.readdirSync('./ext/built-in'); 20 | } 21 | 22 | function main(){ 23 | console.log('[info] start to init the built-in exts.'); 24 | for (let i=0;ivoid, 14 | switchIn: (session:bingServer.conversationSession,cb:(text:string,suggestedResponse:string[],nowNum:number,maxNum:number)=>void)=>void, 15 | input: (session:bingServer.conversationSession,userMessage:bing.incomeMessage.userMesssage,updateCb:(text:string,nowNum:number,maxNum:number,suggestedResponse:string[])=>void,doneCb:()=>void,alive:()=>Promise,hold:()=>void)=>void, 16 | exit: (session:bingServer.conversationSession,cb:(text:string,suggestedResponse:string[],nowNum:number,maxNum:number)=>void)=>void, 17 | init: (session:bingServer.conversationSession,cb:(text:string,suggestedResponse:string[],nowNum:number,maxNum:number)=>void)=>void 18 | } 19 | export class base{ 20 | handle: handle; 21 | applicationName: string; 22 | } 23 | export interface importedStruct{ 24 | constructor?: ()=>base, 25 | applicationName?: string, 26 | switchOut?: (session:bingServer.conversationSession)=>void, 27 | switchIn?: (session:bingServer.conversationSession,cb:(text:string,suggestedResponse:string[],nowNum:number,maxNum:number)=>void)=>void, 28 | input?: (session:bingServer.conversationSession,userMessage:bing.incomeMessage.userMesssage,updateCb:(text:string,nowNum:number,maxNum:number,suggestedResponse:string[])=>void,doneCb:()=>void,alive:()=>Promise,hold:()=>void)=>void, 29 | exit?: (session:bingServer.conversationSession,cb:(text:string,suggestedResponse:string[],nowNum:number,maxNum:number)=>void)=>void, 30 | init?: (session:bingServer.conversationSession,cb:(text:string,suggestedResponse:string[],nowNum:number,maxNum:number)=>void)=>void 31 | } 32 | export interface applicationControlBlock{ 33 | imported: importedStruct, 34 | base: base, 35 | inited: boolean, 36 | switchOut: boolean, 37 | name: string, 38 | nowNum: number, 39 | maxNum: number 40 | } 41 | export interface remoteApplicationControlBlock{ 42 | inited: boolean, 43 | switchOut: boolean, 44 | name: string, 45 | nowNum: number, 46 | maxNum: number, 47 | rawName?: string, 48 | worker?: worker.Worker 49 | } 50 | } 51 | export namespace extMain{ 52 | export interface workerPayload{ 53 | applicationName: string, 54 | fileType: string, 55 | rootPath: string 56 | } 57 | export namespace message{ 58 | export namespace income{ 59 | export interface handleCall{ 60 | type: 'handleCall', 61 | id: string, 62 | name: 'init' | 'exit' | 'input' | 'switchIn' | 'switchOut', 63 | session: bingServer.conversationSession, 64 | userMessage?: bing.incomeMessage.userMesssage 65 | } 66 | export interface updateApplictionControlBlock{ 67 | type: 'updateApplictionControlBlock', 68 | id: string 69 | } 70 | export interface handleAliveCb{ 71 | type: 'handleAliveCb', 72 | alive: boolean, 73 | id: string 74 | } 75 | } 76 | export namespace outcome{ 77 | export interface handleCb{ 78 | type: 'handleCb', 79 | id: string, 80 | text:string, 81 | suggestedResponse:string[], 82 | nowNum:number, 83 | maxNum:number 84 | } 85 | export interface handleUpdateCb{ 86 | type: 'handleUpdateCb', 87 | id: string, 88 | text:string, 89 | nowNum:number, 90 | maxNum:number, 91 | suggestedResponse:string[] 92 | } 93 | export interface handleAliveCall{ 94 | type: 'handleAliveCall', 95 | id: string 96 | } 97 | export interface handleDoneCb{ 98 | type: 'handleDoneCb', 99 | id: string 100 | } 101 | export interface workerError{ 102 | type: 'error' 103 | id: string, 104 | desc: string, 105 | detail: any 106 | } 107 | export interface updateApplictionControlBlock{ 108 | type: 'updateApplictionControlBlock', 109 | id: string, 110 | acb: application.remoteApplicationControlBlock 111 | } 112 | export interface handleCallDone{ 113 | type: 'handleCallDone', 114 | id: string, 115 | } 116 | } 117 | } 118 | 119 | } 120 | } -------------------------------------------------------------------------------- /src/fallbackServer.ts: -------------------------------------------------------------------------------- 1 | 2 | // ydpExt 3 | 4 | // MIT License Copyright (c) 2023 Wxp 5 | 6 | import * as http from "node:http"; 7 | import * as https from "node:https"; 8 | import * as net from "node:net"; 9 | import * as event from "node:events"; 10 | 11 | 12 | export class fallbackServer extends event.EventEmitter{ 13 | #wsServer: net.Server; 14 | #httpServer = http.createServer(); 15 | #listening = false; 16 | 17 | constructor(httpPort:number,wsPort:number,hostAddr:string,httpUrl:string,wsUrl:string){ 18 | super(); 19 | this.#wsServer = net.createServer(); 20 | https.globalAgent.options.keepAlive = true; 21 | 22 | this.#wsServer.on('listening',()=>{ 23 | if (this.#httpServer.listening){ 24 | this.#listening = true; 25 | this.emit('listening'); 26 | }else{ 27 | this.#httpServer.on('listening',()=>{ 28 | this.#listening = true; 29 | this.emit('listening'); 30 | }); 31 | } 32 | }); 33 | 34 | this.#httpServer.on("error",(e)=>{ 35 | console.warn(`[fallbackServer][http] err\n${e}`); 36 | }); 37 | this.#wsServer.on("error",(e)=>{ 38 | console.warn(`[fallbackServer][ws] err\n${e}`); 39 | }); 40 | this.#httpServer.on("request",(req,res)=>{ 41 | let url:URL; 42 | try{ 43 | url = new URL(httpUrl); 44 | }catch(e){ 45 | console.warn('[fallbackServer][http][err] not a vaild http url "',httpUrl,'". \n',e); 46 | res.writeHead(500); 47 | res.write('Please check you fallback url settings. '); 48 | res.end(); 49 | return; 50 | } 51 | if (url.protocol!='https:'){ 52 | console.warn('[fallbackServer][http][err] we only accept https target, but receive "'+url.protocol+'". '); 53 | res.writeHead(500); 54 | res.write('Please check you fallback url settings. '); 55 | res.end(); 56 | return; 57 | } 58 | let header = req.headers; 59 | header['host'] = url.host; 60 | var rreq = https.request({host:url.host,hostname:url.hostname,path:url.pathname+url.search,servername:url.host,headers:header,method:req.method}); 61 | req.pipe(rreq); 62 | rreq.on('response',(rres)=>{ 63 | // on("http",code,msg,header) 64 | this.emit('http',rres.statusCode,rres.statusMessage,rres.headers); 65 | res.writeHead(rres.statusCode,rres.statusMessage,rres.headers); 66 | rres.pipe(res); 67 | rres.on('end',()=>{ 68 | try{res.end();}catch(ee){} 69 | }); 70 | rres.on('error',(e)=>{ 71 | console.warn('[fallbackServer][http][err] rres raise error.','\n',e); 72 | res.writeHead(500); 73 | res.write('Request failed. '+String(e.name)+' '+String(e.message)+' '+String(e.cause)); 74 | res.end(); 75 | }); 76 | }); 77 | req.on('end',()=>{ 78 | try{rreq.end();}catch(ee){} 79 | }); 80 | rreq.on('error',(e)=>{ 81 | console.warn('[fallbackServer][http][err] rreq raise error.','\n',e); 82 | res.writeHead(500); 83 | res.write('Request failed. '+String(e.name)+' '+String(e.message)+' '+String(e.cause)); 84 | res.end(); 85 | }); 86 | req.on('error',(e)=>{ 87 | console.warn('[fallbackServer][http][err] req raise error.','\n',e); 88 | try{ 89 | res.writeHead(500); 90 | res.write('Request failed. '+String(e.name)+' '+String(e.message)+' '+String(e.cause)); 91 | res.end(); 92 | }catch(Ee){} 93 | }); 94 | res.on('error',(e)=>{ 95 | console.warn('[fallbackServer][http][err] res raise error.','\n',e); 96 | }); 97 | }); 98 | this.#wsServer.on('connection',(socket)=>{ 99 | socket.on('error',(e)=>{ 100 | console.warn('[fallbackServer][ws][err] socket raise error.','\n',e); 101 | try{socket.destroy();}catch(ee){} 102 | }); 103 | let url: URL; 104 | try{ 105 | url = new URL(wsUrl); 106 | }catch(e){ 107 | console.warn('[fallbackServer][ws][err] not a vaild ws url "',httpUrl,'". \n',e); 108 | socket.write('Please check you fallback url settings. '); 109 | socket.destroy(); 110 | return; 111 | } 112 | if (url.protocol!='ws:' && url.protocol!='wss:'){ 113 | console.warn('[fallbackServer][ws][err] we only accept ws and wss target, but receive "'+url.protocol+'". '); 114 | socket.write('Please check you fallback url settings. '); 115 | socket.destroy(); 116 | return; 117 | } 118 | let remote = net.connect({ 119 | host: url.host, 120 | port: url.port 121 | }); 122 | remote.on('error',(e)=>{ 123 | console.warn('[fallbackServer][ws][err] remote raise error.','\n',e); 124 | socket.write(`${e.name} ${e.message} ${e.cause}`); 125 | socket.destroy(); 126 | }); 127 | remote.on('connection', ()=>{ 128 | remote.pipe(socket); 129 | socket.pipe(remote); 130 | }); 131 | }); 132 | this.#httpServer.listen(httpPort,hostAddr); 133 | this.#wsServer.listen({ 134 | host: hostAddr, 135 | port: wsPort 136 | }); 137 | } 138 | 139 | isListening(){ 140 | return this.#listening; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/logServer.ts: -------------------------------------------------------------------------------- 1 | 2 | // ydpExt 3 | 4 | // MIT License Copyright (c) 2023 Wxp 5 | 6 | import * as net from "node:net"; 7 | 8 | var clientList:net.Socket[] = []; 9 | 10 | var noRawWrite = false; 11 | 12 | var listening = false; 13 | 14 | const server = net.createServer({},(socket)=>{ 15 | socket.on('end',()=>{ 16 | socket.removeAllListeners(); 17 | var pos = clientList.indexOf(socket); 18 | if (pos==-1) return; 19 | clientList = clientList.slice(0,pos).concat(clientList.slice(pos+1)); 20 | //console.log(clientList); 21 | console.log('[logServer] A logServer client disattached. ip:',socket.remoteAddress,'.'); 22 | }); 23 | socket.on('close',()=>{ 24 | socket.removeAllListeners(); 25 | var pos = clientList.indexOf(socket); 26 | if (pos==-1) return; 27 | clientList = clientList.slice(0,pos).concat(clientList.slice(pos+1)); 28 | //console.log(clientList); 29 | console.log('[logServer] A logServer client disattached. ip:',socket.remoteAddress,'.'); 30 | }); 31 | socket.on('error',(e)=>{ 32 | console.log('[logServer] error happened in socket','ip:',socket.remoteAddress,'\n',e,'\n'); 33 | }); 34 | if (!clientList.includes(socket)) clientList.push(socket); 35 | //socket.write(`======\nYou are not connected to youdaoExt's log server.\nYour Ip: ${socket.remoteAddress}\n======\n`); 36 | console.log('[logServer] A logServer client attached. ip:',socket.remoteAddress,'.'); 37 | //console.log(clientList); 38 | }); 39 | 40 | export function createServer(address:string,port:number,cb:()=>void){ 41 | server.on('listening',()=>{ 42 | listening = true; 43 | cb(); 44 | }); 45 | server.on('error',(e)=>{ 46 | console.log('[logServer] error happened\n',e,'\n'); 47 | }); 48 | server.listen(port,address); 49 | } 50 | 51 | export function log(txt:string|Uint8Array){ 52 | clientList.forEach(socket=>{ 53 | try{ 54 | socket.write(txt); 55 | }catch(e){ 56 | console.log('[logServer] error happened in log\n',e); 57 | } 58 | }); 59 | } 60 | 61 | export function disableRawWrite(){ 62 | noRawWrite = true; 63 | } 64 | 65 | export function bindToStream(s:any){ 66 | s['__logServerwrite'] = s.write; 67 | s.write = (...args):boolean=>{ 68 | var ret = true; 69 | try{ 70 | if (!noRawWrite) ret = s.__logServerwrite.apply(s,args); 71 | }catch(e){} 72 | if (args.length>0) log(args[0]); 73 | return ret; 74 | } 75 | } 76 | 77 | export function isListening(){ 78 | return listening; 79 | } -------------------------------------------------------------------------------- /src/random.ts: -------------------------------------------------------------------------------- 1 | 2 | // ydpExt 3 | 4 | // MIT License Copyright (c) 2023 Wxp 5 | 6 | export namespace map{ 7 | export const number = "1234567890"; 8 | export const upperLetter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 9 | export const lowerLetter = "abcdefghijklmnopqrstuvwxyz"; 10 | export const letter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 11 | export const numberAndUpperLetter = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 12 | export const numberAndLowerLetter = "1234567890abcdefghijklmnopqrstuvwxyz"; 13 | export const hex = "1234567890abcdef"; 14 | export const numberAndLetter = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 15 | export const numberAndLetterAndSymbol = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+=/"; 16 | } 17 | 18 | export function randomNumber(length:number){ 19 | var out = ''; 20 | while (true){ 21 | var t = Math.random().toString().split('.')[1]; 22 | out += t.slice(0,length-out.length); 23 | if (length<=t.length) break; 24 | } 25 | return parseInt(out); 26 | } 27 | 28 | export function randomString(length:number,map:string){ 29 | var out = ''; 30 | if (map.length==0) throw 'map is an empty string!'; 31 | while (true){ 32 | out += map[Math.floor(map.length*Math.random())]; 33 | if (out.length==length) break; 34 | } 35 | return out; 36 | } 37 | 38 | export function randomUUID(){ 39 | return `${randomString(8,map.hex)}-${randomString(4,map.hex)}-${randomString(4,map.hex)}-${randomString(4,map.hex)}-${randomString(12,map.hex)}`; 40 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "esModuleInterop": true, 10 | "module": "NodeNext", 11 | "moduleResolution": "NodeNext", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "baseUrl": "./", 15 | "outDir": "./build", 16 | "noEmit": false, 17 | "rootDir": "./src" 18 | }, 19 | "exclude": ["node_modules" ,"build" ,"ext"], 20 | "include": ["**/*.ts"] 21 | } -------------------------------------------------------------------------------- /util/bing-url.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /util/install.chroot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Before all: make sure your chroot container is installed in '/userdisk/chroot'." 4 | echo "" 5 | echo "Operation will start in 10s." 6 | 7 | sleep 10 8 | 9 | mv ./youdaoExt ./chroot/youdaoExt 10 | mv ./youdaoExt.sh.inchroot ./chroot/youdaoExt.sh 11 | chmod +x ./chroot/youdaoExt.sh 12 | 13 | mkdir ./chroot/ydp 14 | mkdir ./chroot/ydpsys 15 | 16 | mv ./youdaoExt.sh.chroot ./youdaoExt.sh 17 | chmod +x ./youdaoExt.sh 18 | ln -sr ./youdaoExt.sh /usr/bin/youdaoExt 19 | chmod +x /usr/bin/youdaoExt 20 | 21 | sed -i '1i sleep 5' /usr/bin/runDictPen 22 | sed -i '1i youdaoExt &' /usr/bin/runDictPen 23 | sed -i '1i #!/bin/sh' /usr/bin/runDictPen 24 | chmod +x /usr/bin/runDictPen 25 | 26 | mkdir /userdisk/Music/gpt 27 | mkdir -p /userdisk/Music/textweb/offline 28 | mkdir -p /userdisk/Music/textweb/cookie 29 | mv ./bing-url.json /userdisk/Music/bing-url.json 30 | 31 | rm ./node.native 32 | rm ./youdaoExt.sh.native 33 | 34 | mv ./node.chroot ./chroot/node.sh 35 | chmod +x ./chroot/node.sh 36 | chroot ./chroot /node.sh 37 | rm ./chroot/node.sh 38 | 39 | cp /usr/bin/memsize ./chroot/usr/bin/memsize 40 | 41 | rm ./install.native.sh 42 | rm ./unintall.native.sh 43 | 44 | echo "done." -------------------------------------------------------------------------------- /util/install.native.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mv ./youdaoExt.sh.native ./youdaoExt.sh 4 | chmod +x ./youdaoExt.sh 5 | rm ./youdaoExt.sh.chroot 6 | rm ./youdaoExt.sh.inchroot 7 | ln -sr ./youdaoExt.sh /usr/bin/youdaoExt 8 | chmod +x /usr/bin/youdaoExt 9 | 10 | mv ./node.native ./node 11 | chmod +x ./node 12 | ln -sr ./node /usr/bin/node 13 | chmod +x /usr/bin/node 14 | rm ./node.chroot 15 | 16 | mkdir /userdisk/Music/gpt 17 | mkdir -p /userdisk/Music/textweb/offline 18 | mkdir -p /userdisk/Music/textweb/cookie 19 | mv ./bing-url.json /userdisk/Music/bing-url.json 20 | 21 | sed -i '1i sleep 5' /usr/bin/runDictPen 22 | sed -i '1i youdaoExt &' /usr/bin/runDictPen 23 | sed -i '1i #!/bin/sh' /usr/bin/runDictPen 24 | chmod +x /usr/bin/runDictPen 25 | 26 | rm ./install.chroot.sh 27 | rm ./uninstall.chroot.sh 28 | 29 | echo "done." -------------------------------------------------------------------------------- /util/node.chroot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | apt install nodejs -------------------------------------------------------------------------------- /util/node.native: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PenExtMods/ydpExt/077230117f295264ea14f203b09afc0a6958d175/util/node.native -------------------------------------------------------------------------------- /util/uninstall.chroot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Before all: make sure your chroot container is installed in '/userdisk/chroot'." 4 | echo "" 5 | echo "This script will remove ydpExt and ALL THE DATA IT PRODUCED from you device." 6 | echo "" 7 | echo "Since it is in a chroot container, you have to remove 'nodejs' from your container by youself if you want." 8 | echo "" 9 | echo "Warning: the following files and dirs will be removed." 10 | echo " - /userdisk/Music/textweb" 11 | echo " - /userdisk/Music/gpt" 12 | echo " - /userdisk/Music/bing-url.json" 13 | echo "" 14 | echo "Operation will start in 10s." 15 | 16 | sleep 10 17 | 18 | rm /usr/bin/youdaoExt 19 | rm ./youdaoExt.sh 20 | 21 | rm /usr/bin/node 22 | rm ./node 23 | 24 | rm -r /userdisk/Music/textweb 25 | rm -r /userdisk/Music/gpt 26 | rm /userdisk/Music/bing-url.json 27 | 28 | rm -r ./chroot/youdaoExt 29 | rm ./chroot/youdaoExt.sh 30 | 31 | echo "done." -------------------------------------------------------------------------------- /util/uninstall.native.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "This script will remove ydpExt and ALL THE DATA IT PRODUCED from you device." 4 | echo "" 5 | echo "Warning: the following files and dirs will be removed." 6 | echo " - /userdisk/Music/textweb" 7 | echo " - /userdisk/Music/gpt" 8 | echo " - /userdisk/Music/bing-url.json" 9 | echo "" 10 | echo "Operation will start in 10s." 11 | 12 | sleep 10 13 | 14 | rm /usr/bin/youdaoExt 15 | rm ./youdaoExt.sh 16 | 17 | rm /usr/bin/node 18 | rm ./node 19 | 20 | rm -r /userdisk/Music/textweb 21 | rm -r /userdisk/Music/gpt 22 | rm /userdisk/Music/bing-url.json 23 | 24 | rm -r ./youdaoExt 25 | 26 | echo "done." -------------------------------------------------------------------------------- /util/youdaoExt.sh.chroot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | startfile=/tmp/.youdaoExtStarted 3 | if [ -f $startfile ];then 4 | exit 5 | fi 6 | touch $startfile 7 | 8 | sleep 20 9 | 10 | mkdir -p /tmp/ydext 11 | mount --bind /tmp/ydext /userdisk/chroot/tmp 12 | 13 | mount -t proc proc /userdisk/chroot/proc 14 | mount -t sysfs sys /userdisk/chroot/sys 15 | mount --bind /dev /userdisk/chroot/dev 16 | mkdir -p /userdisk/chroot/dev/pts 17 | mount -t devpts none /userdisk/chroot/dev/pts 18 | 19 | cp /usr/share/zoneinfo/Asia/Shanghai /userdisk/chroot/etc/localtime 20 | 21 | 22 | chroot /userdisk/chroot ./youdaoExt.sh & 23 | -------------------------------------------------------------------------------- /util/youdaoExt.sh.inchroot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export ydpSysRootPath=/proc/1/root 3 | export mtpPath=/proc/1/root/userdisk/Music 4 | node /youdaoExt/main.js 5 | -------------------------------------------------------------------------------- /util/youdaoExt.sh.native: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | startfile=/tmp/.youdaoExtStarted 3 | if [ -f $startfile ];then 4 | exit 5 | fi 6 | touch $startfile 7 | 8 | sleep 20 9 | 10 | export ydpSysRootPath=/ 11 | export mtpPath=/userdisk/Music 12 | 13 | node /userdisk/youdaoExt/main.js & --------------------------------------------------------------------------------