├── .eslintrc.js ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── README.md ├── babel.config.js ├── build ├── rollup.config.base.mjs ├── rollup.config.browser.mjs ├── rollup.config.es.mjs └── rollup.config.umd.mjs ├── commitlint.config.js ├── dist ├── html │ └── popup.html └── js │ ├── background.min.js │ ├── background.min.js.map │ ├── content.min.js │ └── content.min.js.map ├── icon-128.png ├── icon-16.png ├── images ├── demo-google-excel.gif └── demo.gif ├── manifest.json ├── package-lock.json ├── package.json └── src ├── background.js ├── common └── util.js ├── content.js └── pangu.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: "eslint:recommended", 7 | overrides: [ 8 | { 9 | env: { 10 | node: true, 11 | }, 12 | files: [".eslintrc.{js,cjs}"], 13 | parserOptions: { 14 | sourceType: "script", 15 | }, 16 | }, 17 | ], 18 | parserOptions: { 19 | ecmaVersion: "latest", 20 | sourceType: "module", 21 | }, 22 | rules: {}, 23 | ignorePatterns: [ 24 | "node_modules/", 25 | "dist/", 26 | "src/pangu.js", 27 | "babel.config.js", 28 | "commitlint.config.js", 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .temp/ 3 | .cache/ 4 | .DS_Store -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js, json, md}": [ 3 | "prettier --write", 4 | "git add" 5 | ], 6 | "*.{js,ts}": [ 7 | "eslint --ext .js, ./src/*.js", 8 | "git add" 9 | ] 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logo Format your text anytime in the web editor. 2 | 3 | 通过谷歌扩展,快速将选中文本,格式化为符合 [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) 的文本。 4 | 5 | 已经支持的平台: 6 | 7 | + [谷歌文档 - Excel](https://docs.google.com/spreadsheets) 8 | + [石墨文档](https://shimo.im/) 9 | 10 | ## How to install 11 | 12 | 1. 打开 Chrome 扩展页面 `chrome://extensions/` 13 | 2. 下载本项目至本地 14 | 3. 点击左上角 `Load unpacked` 按钮,加载此项目 15 | 4. 如果扩展程序需要特定的权限或数据,您会看到提示。若要批准,请点击添加扩展程序 16 | 17 | 完成安装后,将会看到本扩展被添加到扩展列表。 18 | 19 | ## How to use 20 | 21 | 1. 选择你需要格式化的文本内容 22 | 2. 右键呼出菜单,选择 `Text formatting` 按钮 23 | - 如果当前选择内容处于可编辑输入框内,会完成自动文本替换 24 | - 如果当前选择内容处于不可编辑状态,可打开控制台查看格式化后的内容 25 | 26 | ![](./images//demo.gif) 27 | 28 | ## Update Logs 29 | 30 | 1. 【2023-10-27】,新增对谷歌文档 Excel 的支持 31 | 32 | ![](./images//demo-google-excel.gif) 33 | 34 | ## Next 35 | 36 | 写给自己用的扩展,计划逐步兼容语雀文档、有道云、飞书文档、掘金编辑器、Github issues 输入框,感兴趣可提前 Star。 -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [[require("@babel/preset-env"), { modules: false }]], 3 | }; 4 | -------------------------------------------------------------------------------- /build/rollup.config.base.mjs: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import cjs from '@rollup/plugin-commonjs'; 5 | import livereload from 'rollup-plugin-livereload'; 6 | 7 | const commonConfig = { 8 | plugins: [ 9 | resolve({ 10 | mainFields: ['module', 'jsnext', 'main', 'browser'], 11 | }), 12 | commonjs(), 13 | babel({ 14 | exclude: 'node_modules/**', 15 | }), 16 | cjs({ 17 | include: /node_modules/, 18 | }), 19 | livereload(), 20 | ], 21 | watch: { 22 | include: 'src/**', 23 | }, 24 | external: [] 25 | }; 26 | 27 | export default { 28 | content: { 29 | input: 'src/content.js', 30 | ...commonConfig 31 | }, 32 | background: { 33 | input: 'src/background.js', 34 | ...commonConfig 35 | } 36 | }; -------------------------------------------------------------------------------- /build/rollup.config.browser.mjs: -------------------------------------------------------------------------------- 1 | import base from './rollup.config.base.mjs'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | 4 | const commonConfig = { 5 | format: 'iife', 6 | sourcemap: true, 7 | } 8 | 9 | const configContent = Object.assign({}, base.content, { 10 | output: { 11 | exports: 'named', 12 | name: 'content', 13 | file: 'dist/js/content.min.js', 14 | globals: {}, 15 | ...commonConfig 16 | }, 17 | }); 18 | const configBackground = Object.assign({}, base.background, { 19 | output: { 20 | exports: 'named', 21 | name: 'background', 22 | file: 'dist/js/background.min.js', 23 | globals: { 24 | 'pangu': 'https://jspm.dev/pangu@4.0.7' 25 | }, 26 | external: ['pangu'], 27 | ...commonConfig 28 | }, 29 | }); 30 | 31 | configContent.plugins.push(terser()); 32 | configBackground.plugins.push(terser()); 33 | 34 | console.log('configContent', configContent); 35 | console.log('configBackground', configBackground); 36 | 37 | export default [configContent, configBackground]; -------------------------------------------------------------------------------- /build/rollup.config.es.mjs: -------------------------------------------------------------------------------- 1 | import base from './rollup.config.base.mjs'; 2 | 3 | const config = Object.assign({}, base, { 4 | output: { 5 | name: 'content', 6 | file: 'dist/js/content.esm.js', 7 | format: 'es', 8 | sourcemap: true, 9 | }, 10 | external: [ 11 | ...base.external, 12 | ], 13 | }) 14 | 15 | export default config; -------------------------------------------------------------------------------- /build/rollup.config.umd.mjs: -------------------------------------------------------------------------------- 1 | import base from './rollup.config.base.mjs'; 2 | 3 | const config = Object.assign({}, base, { 4 | output: { 5 | exports: 'named', 6 | name: 'content', 7 | file: 'dist/js/content.umd.js', 8 | format: 'umd', 9 | sourcemap: true, 10 | }, 11 | }) 12 | 13 | export default config; -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ["@commitlint/config-conventional"] }; 2 | -------------------------------------------------------------------------------- /dist/html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Text Formatting 6 | 7 | 8 | 9 | 10 |

Text Formatting

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/js/background.min.js: -------------------------------------------------------------------------------- 1 | !(function (e, t) { 2 | e && 3 | !e.getElementById("livereloadscript") && 4 | (((t = e.createElement("script")).async = 1), 5 | (t.src = 6 | "//" + 7 | (self.location.host || "localhost").split(":")[0] + 8 | ":35729/livereload.js?snipver=1"), 9 | (t.id = "livereloadscript"), 10 | e.getElementsByTagName("head")[0].appendChild(t)); 11 | })(self.document), 12 | (function () { 13 | "use strict"; 14 | chrome.contextMenus.create({ 15 | title: "Text Formatting", 16 | contexts: ["all"], 17 | onclick: function (e) { 18 | var t, 19 | s = e.selectionText, 20 | c = e.editable; 21 | console.log("selectedText", s), 22 | console.log("isEditable", c), 23 | s && 24 | ((t = { code: 1, isEditable: c, message: s }), 25 | chrome.tabs.query({ active: !0, currentWindow: !0 }, function (e) { 26 | chrome.tabs.sendMessage(e[0].id, t); 27 | })); 28 | }, 29 | }); 30 | })(); 31 | //# sourceMappingURL=background.min.js.map 32 | -------------------------------------------------------------------------------- /dist/js/background.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"background.min.js","sources":["../../src/background.js"],"sourcesContent":["// eslint-disable-next-line no-undef\nchrome.contextMenus.create({\n title: \"Text Formatting\",\n contexts: [\"all\"],\n onclick: (val) => {\n const selectedText = val.selectionText;\n const isEditable = val.editable;\n\n console.log(\"selectedText\", selectedText);\n console.log(\"isEditable\", isEditable);\n\n if (selectedText) {\n sendMsgToContentScript({\n code: 1,\n isEditable,\n message: selectedText,\n });\n }\n },\n});\n\nfunction sendMsgToContentScript(data) {\n // eslint-disable-next-line no-undef\n chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {\n // eslint-disable-next-line no-undef\n chrome.tabs.sendMessage(tabs[0].id, data);\n });\n}\n\n// function addSpaceBetweenChineseAndEnglish(text) {\n// // 使用正则表达式进行匹配和替换\n// var modifiedText = text.replace(/([\\u4e00-\\u9fa5])([a-zA-Z1-9])/g, '$1 $2');\n// modifiedText = modifiedText.replace(/([a-zA-Z1-9])([\\u4e00-\\u9fa5])/g, '$1 $2');\n\n// return modifiedText;\n// }\n"],"names":["chrome","contextMenus","create","title","contexts","onclick","val","data","selectedText","selectionText","isEditable","editable","console","log","code","message","tabs","query","active","currentWindow","sendMessage","id"],"mappings":"8SACAA,OAAOC,aAAaC,OAAO,CACzBC,MAAO,kBACPC,SAAU,CAAC,OACXC,QAAS,SAACC,GACR,IAgB4BC,EAhBtBC,EAAeF,EAAIG,cACnBC,EAAaJ,EAAIK,SAEvBC,QAAQC,IAAI,eAAgBL,GAC5BI,QAAQC,IAAI,aAAcH,GAEtBF,IAUwBD,EATH,CACrBO,KAAM,EACNJ,WAAAA,EACAK,QAASP,GAQfR,OAAOgB,KAAKC,MAAM,CAAEC,QAAAA,EAAcC,eAAe,IAAA,SAAkBH,GAEjEhB,OAAOgB,KAAKI,YAAYJ,EAAK,GAAGK,GAAId,EAPtC,IAAA"} -------------------------------------------------------------------------------- /dist/js/content.min.js: -------------------------------------------------------------------------------- 1 | !(function (e, t) { 2 | e && 3 | !e.getElementById("livereloadscript") && 4 | (((t = e.createElement("script")).async = 1), 5 | (t.src = 6 | "//" + 7 | (self.location.host || "localhost").split(":")[0] + 8 | ":35729/livereload.js?snipver=1"), 9 | (t.id = "livereloadscript"), 10 | e.getElementsByTagName("head")[0].appendChild(t)); 11 | })(self.document), 12 | (function () { 13 | "use strict"; 14 | console.info("【格式化插件】Text formatting init."); 15 | var e = (function (e) { 16 | switch (((e = e || location.href), !0)) { 17 | case /docs\.google\.com\/spreadsheets/i.test(e): 18 | return "GoogleExcel"; 19 | case /docs\.google\.com\/document/i.test(e): 20 | return "GoogleDocs"; 21 | case /shimo\.im\/docs/i.test(e): 22 | return "Shimo"; 23 | case /yuque\.com/i.test(e): 24 | return "Yueque"; 25 | default: 26 | return "Unknown"; 27 | } 28 | })(), 29 | t = window.getSelection(); 30 | console.info("【格式化插件】当前插件宿主:", e), 31 | chrome.runtime.onMessage.addListener(function (o) { 32 | if ((console.log("request", o), 1 === o.code)) { 33 | var n = pangu.spacing(o.message); 34 | if ( 35 | (console.log("【格式化插件】替换前文本:", o.message), 36 | console.log("【格式化插件】替换后文本:", n), 37 | n && o.isEditable) 38 | ) { 39 | if (1 == ("GoogleExcel" === e || "Shimo" === e)) 40 | return void (function (e) { 41 | if (window.getSelection && t.rangeCount > 0) { 42 | var o = t.getRangeAt(0), 43 | n = document.createTextNode(e); 44 | o.deleteContents(), 45 | o.insertNode(n), 46 | t.removeAllRanges(), 47 | t.addRange(o); 48 | } else 49 | document.selection && 50 | "Control" != document.selection.type && 51 | (document.selection.createRange().text = e); 52 | })(n); 53 | console.log("【格式化插件】通用框逻辑"), 54 | (function (e) { 55 | var t = document.activeElement; 56 | if ("INPUT" === t.tagName || "TEXTAREA" === t.tagName) { 57 | var o = t.selectionStart, 58 | n = t.selectionEnd, 59 | s = t.value.substring(0, o) + e + t.value.substring(n); 60 | t.value = s; 61 | var i = o + e.length; 62 | t.setSelectionRange(i, i); 63 | } 64 | })(n); 65 | } 66 | } 67 | }); 68 | })(); 69 | //# sourceMappingURL=content.min.js.map 70 | -------------------------------------------------------------------------------- /dist/js/content.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"content.min.js","sources":["../../src/content.js","../../src/common/util.js"],"sourcesContent":["import { businessJudgment } from \"./common/util\";\n\nconsole.info(\"【格式化插件】Text formatting init.\");\n\nconst business = businessJudgment();\nconst selection = window.getSelection();\n\nconsole.info(\"【格式化插件】当前插件宿主:\", business);\n\n// eslint-disable-next-line no-undef\nchrome.runtime.onMessage.addListener(function (request) {\n console.log(\"request\", request);\n\n // 接收到 background 传递的替换信息\n if (request.code === 1) {\n // eslint-disable-next-line no-undef\n var replacedText = pangu.spacing(request.message); // 要替换的文本\n console.log(\"【格式化插件】替换前文本:\", request.message);\n console.log(\"【格式化插件】替换后文本:\", replacedText);\n\n if (replacedText && request.isEditable) {\n switch (true) {\n case business === \"GoogleExcel\" || business === \"Shimo\":\n googleExcelreplaceSelectedText(replacedText);\n return;\n default:\n break;\n }\n console.log(\"【格式化插件】通用框逻辑\");\n replaceSelectedText(replacedText);\n }\n }\n});\n\n// eslint-disable-next-line no-unused-vars\nfunction getSelectedText() {\n // 获取当前活动的输入框\n var inputElement = document.activeElement;\n var selectedText = \"\";\n\n if (inputElement.tagName === \"INPUT\" || inputElement.tagName === \"TEXTAREA\") {\n selectedText = inputElement.value.substring(\n inputElement.selectionStart,\n inputElement.selectionEnd,\n );\n }\n\n return selectedText;\n}\n\n// 替换选中的文本片段\nfunction replaceSelectedText(replacement) {\n // 获取当前活动的输入框\n var inputElement = document.activeElement;\n\n if (inputElement.tagName === \"INPUT\" || inputElement.tagName === \"TEXTAREA\") {\n var start = inputElement.selectionStart;\n var end = inputElement.selectionEnd;\n\n var newValue =\n inputElement.value.substring(0, start) +\n replacement +\n inputElement.value.substring(end);\n\n inputElement.value = newValue;\n\n // 重新设置光标位置\n var newCursorPosition = start + replacement.length;\n inputElement.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n}\n\nfunction googleExcelreplaceSelectedText(newText) {\n if (window.getSelection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0);\n const newTextNode = document.createTextNode(newText);\n range.deleteContents();\n range.insertNode(newTextNode);\n selection.removeAllRanges();\n selection.addRange(range);\n } else if (document.selection && document.selection.type != \"Control\") {\n document.selection.createRange().text = newText;\n }\n}\n\n// console.info(\"Text formatting end.\");\n","export function businessJudgment(domain) {\n domain = domain || location.href;\n\n const excelRegex = /docs\\.google\\.com\\/spreadsheets/i;\n const docsRegex = /docs\\.google\\.com\\/document/i;\n const shimoRegex = /shimo\\.im\\/docs/i;\n const yuqueRegex = /yuque\\.com/i;\n\n switch (true) {\n case excelRegex.test(domain):\n return \"GoogleExcel\";\n case docsRegex.test(domain):\n return \"GoogleDocs\";\n case shimoRegex.test(domain):\n return \"Shimo\";\n case yuqueRegex.test(domain):\n return \"Yueque\";\n default:\n return \"Unknown\";\n }\n}\n"],"names":["console","info","business","domain","location","href","test","selection","window","getSelection","chrome","runtime","onMessage","addListener","request","log","code","replacedText","pangu","spacing","message","isEditable","newText","rangeCount","range","getRangeAt","newTextNode","document","createTextNode","deleteContents","insertNode","removeAllRanges","addRange","type","createRange","text","replacement","inputElement","activeElement","tagName","start","selectionStart","end","selectionEnd","newValue","value","substring","newCursorPosition","length","setSelectionRange"],"mappings":"8SAEAA,QAAQC,KAAK,gCAEb,IAAMC,ECJC,SAA0BC,GAQ/B,OAPAA,EAASA,GAAUC,SAASC,MAOpB,GACN,IANiB,mCAMDC,KAAKH,GACnB,MAAO,cACT,IAPgB,+BAODG,KAAKH,GAClB,MAAO,aACT,IARiB,mBAQDG,KAAKH,GACnB,MAAO,QACT,IATiB,cASDG,KAAKH,GACnB,MAAO,SACT,QACE,MAAO,UAEb,CApBO,GDKDI,EAAYC,OAAOC,eAEzBT,QAAQC,KAAK,iBAAkBC,GAG/BQ,OAAOC,QAAQC,UAAUC,aAAAA,SAAsBC,GAI7C,GAHAd,QAAQe,IAAI,UAAWD,GAGF,IAAjBA,EAAQE,KAAY,CAEtB,IAAIC,EAAeC,MAAMC,QAAQL,EAAQM,SAIzC,GAHApB,QAAQe,IAAI,gBAAiBD,EAAQM,SACrCpB,QAAQe,IAAI,gBAAiBE,GAEzBA,GAAgBH,EAAQO,WAAY,CACtC,GAAA,IACoB,gBAAbnB,GAA2C,UAAbA,GAEjC,YAgDV,SAAwCoB,GACtC,GAAId,OAAOC,cAAgBF,EAAUgB,WAAa,EAAG,CACnD,IAAMC,EAAQjB,EAAUkB,WAAW,GAC7BC,EAAcC,SAASC,eAAeN,GAC5CE,EAAMK,iBACNL,EAAMM,WAAWJ,GACjBnB,EAAUwB,kBACVxB,EAAUyB,SAASR,EACrB,MAAWG,SAASpB,WAAwC,WAA3BoB,SAASpB,UAAU0B,OAClDN,SAASpB,UAAU2B,cAAcC,KAAOb,EAE5C,CAXA,CAjDyCL,GAKnCjB,QAAQe,IAAI,gBAuBlB,SAA6BqB,GAE3B,IAAIC,EAAeV,SAASW,cAE5B,GAA6B,UAAzBD,EAAaE,SAAgD,aAAzBF,EAAaE,QAAwB,CAC3E,IAAIC,EAAQH,EAAaI,eACrBC,EAAML,EAAaM,aAEnBC,EACFP,EAAaQ,MAAMC,UAAU,EAAGN,GAChCJ,EACAC,EAAaQ,MAAMC,UAAUJ,GAE/BL,EAAaQ,MAAQD,EAGrB,IAAIG,EAAoBP,EAAQJ,EAAYY,OAC5CX,EAAaY,kBAAkBF,EAAmBA,EACpD,CACF,CAnBA,CAtB0B9B,EACtB,CACF,CACF"} -------------------------------------------------------------------------------- /icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chokcoco/chrome-extension-text-formatting/51adcd84513c5572c75b92f1710055b5645d745d/icon-128.png -------------------------------------------------------------------------------- /icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chokcoco/chrome-extension-text-formatting/51adcd84513c5572c75b92f1710055b5645d745d/icon-16.png -------------------------------------------------------------------------------- /images/demo-google-excel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chokcoco/chrome-extension-text-formatting/51adcd84513c5572c75b92f1710055b5645d745d/images/demo-google-excel.gif -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chokcoco/chrome-extension-text-formatting/51adcd84513c5572c75b92f1710055b5645d745d/images/demo.gif -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Text Formatting", 3 | "version": "1.0.0", 4 | "author": "Coco", 5 | "manifest_version": 2, 6 | "description": "Format your text anytime in the web editor.", 7 | "permissions": [ 8 | "contextMenus", 9 | "http://*/", 10 | "http://*/*", 11 | "https://*/", 12 | "https://*/*" 13 | ], 14 | "icons": { 15 | "16": "icon-16.png", 16 | "128": "icon-128.png" 17 | }, 18 | "browser_action": { 19 | "default_icon": "icon-16.png", 20 | "default_popup": "dist/html/popup.html" 21 | }, 22 | "background": { 23 | "scripts": ["dist/js/background.min.js"] 24 | }, 25 | "content_scripts": [ 26 | { 27 | "matches": ["http://*/", "https://*/", "http://*/*", "https://*/*"], 28 | "js": ["src/pangu.js", "dist/js/content.min.js"] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "text-formatting", 3 | "version": "1.0.0", 4 | "description": "Format your text anytime in the web editor.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "npm run build:browser && npm run build:es && npm run build:umd", 9 | "build:browser": "rollup -c build/rollup.config.browser.mjs -w", 10 | "build:es": "rollup --config build/rollup.config.es.mjs", 11 | "build:umd": "rollup --config build/rollup.config.umd.mjs", 12 | "lint": "eslint --ext .js, ./src/*.js", 13 | "prettier": "prettier --write *.{js,md,json} src/*.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/chokcoco/chrome-extension-text-formatting.git" 18 | }, 19 | "author": "Coco", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/chokcoco/chrome-extension-text-formatting/issues" 23 | }, 24 | "homepage": "https://github.com/chokcoco/chrome-extension-text-formatting#readme", 25 | "dependencies": { 26 | "rollup": "^4.1.4" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.23.2", 30 | "@babel/preset-env": "^7.23.2", 31 | "@commitlint/cli": "^17.6.5", 32 | "@commitlint/config-conventional": "^17.6.5", 33 | "@rollup/plugin-babel": "^6.0.4", 34 | "@rollup/plugin-commonjs": "^25.0.7", 35 | "@rollup/plugin-node-resolve": "^15.2.3", 36 | "eslint": "^8.52.0", 37 | "husky": "^8.0.3", 38 | "lint-staged": "^15.0.2", 39 | "prettier": "3.0.3", 40 | "rollup-plugin-livereload": "^2.0.5", 41 | "rollup-plugin-terser": "^7.0.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | chrome.contextMenus.create({ 3 | title: "Text Formatting", 4 | contexts: ["all"], 5 | onclick: (val) => { 6 | const selectedText = val.selectionText; 7 | const isEditable = val.editable; 8 | 9 | console.log("selectedText", selectedText); 10 | console.log("isEditable", isEditable); 11 | 12 | if (selectedText) { 13 | sendMsgToContentScript({ 14 | code: 1, 15 | isEditable, 16 | message: selectedText, 17 | }); 18 | } 19 | }, 20 | }); 21 | 22 | function sendMsgToContentScript(data) { 23 | // eslint-disable-next-line no-undef 24 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 25 | // eslint-disable-next-line no-undef 26 | chrome.tabs.sendMessage(tabs[0].id, data); 27 | }); 28 | } 29 | 30 | // function addSpaceBetweenChineseAndEnglish(text) { 31 | // // 使用正则表达式进行匹配和替换 32 | // var modifiedText = text.replace(/([\u4e00-\u9fa5])([a-zA-Z1-9])/g, '$1 $2'); 33 | // modifiedText = modifiedText.replace(/([a-zA-Z1-9])([\u4e00-\u9fa5])/g, '$1 $2'); 34 | 35 | // return modifiedText; 36 | // } 37 | -------------------------------------------------------------------------------- /src/common/util.js: -------------------------------------------------------------------------------- 1 | export function businessJudgment(domain) { 2 | domain = domain || location.href; 3 | 4 | const excelRegex = /docs\.google\.com\/spreadsheets/i; 5 | const docsRegex = /docs\.google\.com\/document/i; 6 | const shimoRegex = /shimo\.im\/docs/i; 7 | const yuqueRegex = /yuque\.com/i; 8 | 9 | switch (true) { 10 | case excelRegex.test(domain): 11 | return "GoogleExcel"; 12 | case docsRegex.test(domain): 13 | return "GoogleDocs"; 14 | case shimoRegex.test(domain): 15 | return "Shimo"; 16 | case yuqueRegex.test(domain): 17 | return "Yueque"; 18 | default: 19 | return "Unknown"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/content.js: -------------------------------------------------------------------------------- 1 | import { businessJudgment } from "./common/util"; 2 | 3 | console.info("【格式化插件】Text formatting init."); 4 | 5 | const business = businessJudgment(); 6 | const selection = window.getSelection(); 7 | 8 | console.info("【格式化插件】当前插件宿主:", business); 9 | 10 | // eslint-disable-next-line no-undef 11 | chrome.runtime.onMessage.addListener(function (request) { 12 | console.log("request", request); 13 | 14 | // 接收到 background 传递的替换信息 15 | if (request.code === 1) { 16 | // eslint-disable-next-line no-undef 17 | var replacedText = pangu.spacing(request.message); // 要替换的文本 18 | console.log("【格式化插件】替换前文本:", request.message); 19 | console.log("【格式化插件】替换后文本:", replacedText); 20 | 21 | if (replacedText && request.isEditable) { 22 | switch (true) { 23 | case business === "GoogleExcel" || business === "Shimo": 24 | googleExcelreplaceSelectedText(replacedText); 25 | return; 26 | default: 27 | break; 28 | } 29 | console.log("【格式化插件】通用框逻辑"); 30 | replaceSelectedText(replacedText); 31 | } 32 | } 33 | }); 34 | 35 | // eslint-disable-next-line no-unused-vars 36 | function getSelectedText() { 37 | // 获取当前活动的输入框 38 | var inputElement = document.activeElement; 39 | var selectedText = ""; 40 | 41 | if (inputElement.tagName === "INPUT" || inputElement.tagName === "TEXTAREA") { 42 | selectedText = inputElement.value.substring( 43 | inputElement.selectionStart, 44 | inputElement.selectionEnd, 45 | ); 46 | } 47 | 48 | return selectedText; 49 | } 50 | 51 | // 替换选中的文本片段 52 | function replaceSelectedText(replacement) { 53 | // 获取当前活动的输入框 54 | var inputElement = document.activeElement; 55 | 56 | if (inputElement.tagName === "INPUT" || inputElement.tagName === "TEXTAREA") { 57 | var start = inputElement.selectionStart; 58 | var end = inputElement.selectionEnd; 59 | 60 | var newValue = 61 | inputElement.value.substring(0, start) + 62 | replacement + 63 | inputElement.value.substring(end); 64 | 65 | inputElement.value = newValue; 66 | 67 | // 重新设置光标位置 68 | var newCursorPosition = start + replacement.length; 69 | inputElement.setSelectionRange(newCursorPosition, newCursorPosition); 70 | } 71 | } 72 | 73 | function googleExcelreplaceSelectedText(newText) { 74 | if (window.getSelection && selection.rangeCount > 0) { 75 | const range = selection.getRangeAt(0); 76 | const newTextNode = document.createTextNode(newText); 77 | range.deleteContents(); 78 | range.insertNode(newTextNode); 79 | selection.removeAllRanges(); 80 | selection.addRange(range); 81 | } else if (document.selection && document.selection.type != "Control") { 82 | document.selection.createRange().text = newText; 83 | } 84 | } 85 | 86 | // console.info("Text formatting end."); 87 | -------------------------------------------------------------------------------- /src/pangu.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pangu.js 3 | * -------- 4 | * @version: 4.0.7 5 | * @homepage: https://github.com/vinta/pangu.js 6 | * @license: MIT 7 | * @author: Vinta Chen (https://github.com/vinta) 8 | */ 9 | (function webpackUniversalModuleDefinition(root, factory) { 10 | if (typeof exports === "object" && typeof module === "object") 11 | module.exports = factory(); 12 | else if (typeof define === "function" && define.amd) 13 | define("pangu", [], factory); 14 | else if (typeof exports === "object") exports["pangu"] = factory(); 15 | else root["pangu"] = factory(); 16 | })(window, function () { 17 | return /******/ (function (modules) { 18 | // webpackBootstrap 19 | /******/ // The module cache 20 | /******/ var installedModules = {}; 21 | /******/ 22 | /******/ // The require function 23 | /******/ function __webpack_require__(moduleId) { 24 | /******/ 25 | /******/ // Check if module is in cache 26 | /******/ if (installedModules[moduleId]) { 27 | /******/ return installedModules[moduleId].exports; 28 | /******/ 29 | } 30 | /******/ // Create a new module (and put it into the cache) 31 | /******/ var module = (installedModules[moduleId] = { 32 | /******/ i: moduleId, 33 | /******/ l: false, 34 | /******/ exports: {}, 35 | /******/ 36 | }); 37 | /******/ 38 | /******/ // Execute the module function 39 | /******/ modules[moduleId].call( 40 | module.exports, 41 | module, 42 | module.exports, 43 | __webpack_require__, 44 | ); 45 | /******/ 46 | /******/ // Flag the module as loaded 47 | /******/ module.l = true; 48 | /******/ 49 | /******/ // Return the exports of the module 50 | /******/ return module.exports; 51 | /******/ 52 | } 53 | /******/ 54 | /******/ 55 | /******/ // expose the modules object (__webpack_modules__) 56 | /******/ __webpack_require__.m = modules; 57 | /******/ 58 | /******/ // expose the module cache 59 | /******/ __webpack_require__.c = installedModules; 60 | /******/ 61 | /******/ // define getter function for harmony exports 62 | /******/ __webpack_require__.d = function (exports, name, getter) { 63 | /******/ if (!__webpack_require__.o(exports, name)) { 64 | /******/ Object.defineProperty(exports, name, { 65 | enumerable: true, 66 | get: getter, 67 | }); 68 | /******/ 69 | } 70 | /******/ 71 | }; 72 | /******/ 73 | /******/ // define __esModule on exports 74 | /******/ __webpack_require__.r = function (exports) { 75 | /******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) { 76 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { 77 | value: "Module", 78 | }); 79 | /******/ 80 | } 81 | /******/ Object.defineProperty(exports, "__esModule", { value: true }); 82 | /******/ 83 | }; 84 | /******/ 85 | /******/ // create a fake namespace object 86 | /******/ // mode & 1: value is a module id, require it 87 | /******/ // mode & 2: merge all properties of value into the ns 88 | /******/ // mode & 4: return value when already ns object 89 | /******/ // mode & 8|1: behave like require 90 | /******/ __webpack_require__.t = function (value, mode) { 91 | /******/ if (mode & 1) value = __webpack_require__(value); 92 | /******/ if (mode & 8) return value; 93 | /******/ if ( 94 | mode & 4 && 95 | typeof value === "object" && 96 | value && 97 | value.__esModule 98 | ) 99 | return value; 100 | /******/ var ns = Object.create(null); 101 | /******/ __webpack_require__.r(ns); 102 | /******/ Object.defineProperty(ns, "default", { 103 | enumerable: true, 104 | value: value, 105 | }); 106 | /******/ if (mode & 2 && typeof value != "string") 107 | for (var key in value) 108 | __webpack_require__.d( 109 | ns, 110 | key, 111 | function (key) { 112 | return value[key]; 113 | }.bind(null, key), 114 | ); 115 | /******/ return ns; 116 | /******/ 117 | }; 118 | /******/ 119 | /******/ // getDefaultExport function for compatibility with non-harmony modules 120 | /******/ __webpack_require__.n = function (module) { 121 | /******/ var getter = 122 | module && module.__esModule 123 | ? /******/ function getDefault() { 124 | return module["default"]; 125 | } 126 | : /******/ function getModuleExports() { 127 | return module; 128 | }; 129 | /******/ __webpack_require__.d(getter, "a", getter); 130 | /******/ return getter; 131 | /******/ 132 | }; 133 | /******/ 134 | /******/ // Object.prototype.hasOwnProperty.call 135 | /******/ __webpack_require__.o = function (object, property) { 136 | return Object.prototype.hasOwnProperty.call(object, property); 137 | }; 138 | /******/ 139 | /******/ // __webpack_public_path__ 140 | /******/ __webpack_require__.p = ""; 141 | /******/ 142 | /******/ 143 | /******/ // Load entry module and return exports 144 | /******/ return __webpack_require__((__webpack_require__.s = 0)); 145 | /******/ 146 | })( 147 | /************************************************************************/ 148 | /******/ [ 149 | /* 0 */ 150 | /***/ function (module, exports, __webpack_require__) { 151 | var __WEBPACK_AMD_DEFINE_FACTORY__, 152 | __WEBPACK_AMD_DEFINE_ARRAY__, 153 | __WEBPACK_AMD_DEFINE_RESULT__; 154 | (function (global, factory) { 155 | if (true) { 156 | !((__WEBPACK_AMD_DEFINE_ARRAY__ = []), 157 | (__WEBPACK_AMD_DEFINE_FACTORY__ = factory), 158 | (__WEBPACK_AMD_DEFINE_RESULT__ = 159 | typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" 160 | ? __WEBPACK_AMD_DEFINE_FACTORY__.apply( 161 | exports, 162 | __WEBPACK_AMD_DEFINE_ARRAY__, 163 | ) 164 | : __WEBPACK_AMD_DEFINE_FACTORY__), 165 | __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && 166 | (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 167 | } else { 168 | var mod; 169 | } 170 | })(this, function () { 171 | "use strict"; 172 | 173 | function _typeof(obj) { 174 | if ( 175 | typeof Symbol === "function" && 176 | typeof Symbol.iterator === "symbol" 177 | ) { 178 | _typeof = function _typeof(obj) { 179 | return typeof obj; 180 | }; 181 | } else { 182 | _typeof = function _typeof(obj) { 183 | return obj && 184 | typeof Symbol === "function" && 185 | obj.constructor === Symbol && 186 | obj !== Symbol.prototype 187 | ? "symbol" 188 | : typeof obj; 189 | }; 190 | } 191 | return _typeof(obj); 192 | } 193 | 194 | function _classCallCheck(instance, Constructor) { 195 | if (!(instance instanceof Constructor)) { 196 | throw new TypeError("Cannot call a class as a function"); 197 | } 198 | } 199 | 200 | function _defineProperties(target, props) { 201 | for (var i = 0; i < props.length; i++) { 202 | var descriptor = props[i]; 203 | descriptor.enumerable = descriptor.enumerable || false; 204 | descriptor.configurable = true; 205 | if ("value" in descriptor) descriptor.writable = true; 206 | Object.defineProperty(target, descriptor.key, descriptor); 207 | } 208 | } 209 | 210 | function _createClass(Constructor, protoProps, staticProps) { 211 | if (protoProps) 212 | _defineProperties(Constructor.prototype, protoProps); 213 | if (staticProps) _defineProperties(Constructor, staticProps); 214 | return Constructor; 215 | } 216 | 217 | function _possibleConstructorReturn(self, call) { 218 | if ( 219 | call && 220 | (_typeof(call) === "object" || typeof call === "function") 221 | ) { 222 | return call; 223 | } 224 | return _assertThisInitialized(self); 225 | } 226 | 227 | function _assertThisInitialized(self) { 228 | if (self === void 0) { 229 | throw new ReferenceError( 230 | "this hasn't been initialised - super() hasn't been called", 231 | ); 232 | } 233 | return self; 234 | } 235 | 236 | function _getPrototypeOf(o) { 237 | _getPrototypeOf = Object.setPrototypeOf 238 | ? Object.getPrototypeOf 239 | : function _getPrototypeOf(o) { 240 | return o.__proto__ || Object.getPrototypeOf(o); 241 | }; 242 | return _getPrototypeOf(o); 243 | } 244 | 245 | function _inherits(subClass, superClass) { 246 | if (typeof superClass !== "function" && superClass !== null) { 247 | throw new TypeError( 248 | "Super expression must either be null or a function", 249 | ); 250 | } 251 | subClass.prototype = Object.create( 252 | superClass && superClass.prototype, 253 | { 254 | constructor: { 255 | value: subClass, 256 | writable: true, 257 | configurable: true, 258 | }, 259 | }, 260 | ); 261 | if (superClass) _setPrototypeOf(subClass, superClass); 262 | } 263 | 264 | function _setPrototypeOf(o, p) { 265 | _setPrototypeOf = 266 | Object.setPrototypeOf || 267 | function _setPrototypeOf(o, p) { 268 | o.__proto__ = p; 269 | return o; 270 | }; 271 | return _setPrototypeOf(o, p); 272 | } 273 | 274 | var _require = __webpack_require__(1), 275 | Pangu = _require.Pangu; 276 | 277 | function once(func) { 278 | var _this = this, 279 | _arguments = arguments; 280 | 281 | var executed = false; 282 | return function () { 283 | if (executed) { 284 | return; 285 | } 286 | 287 | var self = _this; 288 | executed = true; 289 | func.apply(self, _arguments); 290 | }; 291 | } 292 | 293 | function debounce(func, delay, mustRunDelay) { 294 | var _this2 = this, 295 | _arguments2 = arguments; 296 | 297 | var timer = null; 298 | var startTime = null; 299 | return function () { 300 | var self = _this2; 301 | var args = _arguments2; 302 | var currentTime = +new Date(); 303 | clearTimeout(timer); 304 | 305 | if (!startTime) { 306 | startTime = currentTime; 307 | } 308 | 309 | if (currentTime - startTime >= mustRunDelay) { 310 | func.apply(self, args); 311 | startTime = currentTime; 312 | } else { 313 | timer = setTimeout(function () { 314 | func.apply(self, args); 315 | }, delay); 316 | } 317 | }; 318 | } 319 | 320 | var BrowserPangu = (function (_Pangu) { 321 | _inherits(BrowserPangu, _Pangu); 322 | 323 | function BrowserPangu() { 324 | var _this3; 325 | 326 | _classCallCheck(this, BrowserPangu); 327 | 328 | _this3 = _possibleConstructorReturn( 329 | this, 330 | _getPrototypeOf(BrowserPangu).call(this), 331 | ); 332 | _this3.blockTags = /^(div|p|h1|h2|h3|h4|h5|h6)$/i; 333 | _this3.ignoredTags = /^(script|code|pre|textarea)$/i; 334 | _this3.presentationalTags = /^(b|code|del|em|i|s|strong|kbd)$/i; 335 | _this3.spaceLikeTags = /^(br|hr|i|img|pangu)$/i; 336 | _this3.spaceSensitiveTags = /^(a|del|pre|s|strike|u)$/i; 337 | _this3.isAutoSpacingPageExecuted = false; 338 | return _this3; 339 | } 340 | 341 | _createClass(BrowserPangu, [ 342 | { 343 | key: "isContentEditable", 344 | value: function isContentEditable(node) { 345 | return ( 346 | node.isContentEditable || 347 | (node.getAttribute && 348 | node.getAttribute("g_editable") === "true") 349 | ); 350 | }, 351 | }, 352 | { 353 | key: "isSpecificTag", 354 | value: function isSpecificTag(node, tagRegex) { 355 | return ( 356 | node && node.nodeName && node.nodeName.search(tagRegex) >= 0 357 | ); 358 | }, 359 | }, 360 | { 361 | key: "isInsideSpecificTag", 362 | value: function isInsideSpecificTag(node, tagRegex) { 363 | var checkCurrent = 364 | arguments.length > 2 && arguments[2] !== undefined 365 | ? arguments[2] 366 | : false; 367 | var currentNode = node; 368 | 369 | if (checkCurrent) { 370 | if (this.isSpecificTag(currentNode, tagRegex)) { 371 | return true; 372 | } 373 | } 374 | 375 | while (currentNode.parentNode) { 376 | currentNode = currentNode.parentNode; 377 | 378 | if (this.isSpecificTag(currentNode, tagRegex)) { 379 | return true; 380 | } 381 | } 382 | 383 | return false; 384 | }, 385 | }, 386 | { 387 | key: "canIgnoreNode", 388 | value: function canIgnoreNode(node) { 389 | var currentNode = node; 390 | 391 | if ( 392 | currentNode && 393 | (this.isSpecificTag(currentNode, this.ignoredTags) || 394 | this.isContentEditable(currentNode)) 395 | ) { 396 | return true; 397 | } 398 | 399 | while (currentNode.parentNode) { 400 | currentNode = currentNode.parentNode; 401 | 402 | if ( 403 | currentNode && 404 | (this.isSpecificTag(currentNode, this.ignoredTags) || 405 | this.isContentEditable(currentNode)) 406 | ) { 407 | return true; 408 | } 409 | } 410 | 411 | return false; 412 | }, 413 | }, 414 | { 415 | key: "isFirstTextChild", 416 | value: function isFirstTextChild(parentNode, targetNode) { 417 | var childNodes = parentNode.childNodes; 418 | 419 | for (var i = 0; i < childNodes.length; i++) { 420 | var childNode = childNodes[i]; 421 | 422 | if ( 423 | childNode.nodeType !== Node.COMMENT_NODE && 424 | childNode.textContent 425 | ) { 426 | return childNode === targetNode; 427 | } 428 | } 429 | 430 | return false; 431 | }, 432 | }, 433 | { 434 | key: "isLastTextChild", 435 | value: function isLastTextChild(parentNode, targetNode) { 436 | var childNodes = parentNode.childNodes; 437 | 438 | for (var i = childNodes.length - 1; i > -1; i--) { 439 | var childNode = childNodes[i]; 440 | 441 | if ( 442 | childNode.nodeType !== Node.COMMENT_NODE && 443 | childNode.textContent 444 | ) { 445 | return childNode === targetNode; 446 | } 447 | } 448 | 449 | return false; 450 | }, 451 | }, 452 | { 453 | key: "spacingNodeByXPath", 454 | value: function spacingNodeByXPath(xPathQuery, contextNode) { 455 | if ( 456 | !(contextNode instanceof Node) || 457 | contextNode instanceof DocumentFragment 458 | ) { 459 | return; 460 | } 461 | 462 | var textNodes = document.evaluate( 463 | xPathQuery, 464 | contextNode, 465 | null, 466 | XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, 467 | null, 468 | ); 469 | var currentTextNode; 470 | var nextTextNode; 471 | 472 | for (var i = textNodes.snapshotLength - 1; i > -1; --i) { 473 | currentTextNode = textNodes.snapshotItem(i); 474 | 475 | if ( 476 | this.isSpecificTag( 477 | currentTextNode.parentNode, 478 | this.presentationalTags, 479 | ) && 480 | !this.isInsideSpecificTag( 481 | currentTextNode.parentNode, 482 | this.ignoredTags, 483 | ) 484 | ) { 485 | var elementNode = currentTextNode.parentNode; 486 | 487 | if (elementNode.previousSibling) { 488 | var previousSibling = elementNode.previousSibling; 489 | 490 | if (previousSibling.nodeType === Node.TEXT_NODE) { 491 | var testText = 492 | previousSibling.data.substr(-1) + 493 | currentTextNode.data.toString().charAt(0); 494 | var testNewText = this.spacing(testText); 495 | 496 | if (testText !== testNewText) { 497 | previousSibling.data = "".concat( 498 | previousSibling.data, 499 | " ", 500 | ); 501 | } 502 | } 503 | } 504 | 505 | if (elementNode.nextSibling) { 506 | var nextSibling = elementNode.nextSibling; 507 | 508 | if (nextSibling.nodeType === Node.TEXT_NODE) { 509 | var _testText = 510 | currentTextNode.data.substr(-1) + 511 | nextSibling.data.toString().charAt(0); 512 | 513 | var _testNewText = this.spacing(_testText); 514 | 515 | if (_testText !== _testNewText) { 516 | nextSibling.data = " ".concat(nextSibling.data); 517 | } 518 | } 519 | } 520 | } 521 | 522 | if (this.canIgnoreNode(currentTextNode)) { 523 | nextTextNode = currentTextNode; 524 | continue; 525 | } 526 | 527 | var newText = this.spacing(currentTextNode.data); 528 | 529 | if (currentTextNode.data !== newText) { 530 | currentTextNode.data = newText; 531 | } 532 | 533 | if (nextTextNode) { 534 | if ( 535 | currentTextNode.nextSibling && 536 | currentTextNode.nextSibling.nodeName.search( 537 | this.spaceLikeTags, 538 | ) >= 0 539 | ) { 540 | nextTextNode = currentTextNode; 541 | continue; 542 | } 543 | 544 | var _testText2 = 545 | currentTextNode.data.toString().substr(-1) + 546 | nextTextNode.data.toString().substr(0, 1); 547 | 548 | var _testNewText2 = this.spacing(_testText2); 549 | 550 | if (_testNewText2 !== _testText2) { 551 | var nextNode = nextTextNode; 552 | 553 | while ( 554 | nextNode.parentNode && 555 | nextNode.nodeName.search(this.spaceSensitiveTags) === 556 | -1 && 557 | this.isFirstTextChild(nextNode.parentNode, nextNode) 558 | ) { 559 | nextNode = nextNode.parentNode; 560 | } 561 | 562 | var currentNode = currentTextNode; 563 | 564 | while ( 565 | currentNode.parentNode && 566 | currentNode.nodeName.search( 567 | this.spaceSensitiveTags, 568 | ) === -1 && 569 | this.isLastTextChild( 570 | currentNode.parentNode, 571 | currentNode, 572 | ) 573 | ) { 574 | currentNode = currentNode.parentNode; 575 | } 576 | 577 | if (currentNode.nextSibling) { 578 | if ( 579 | currentNode.nextSibling.nodeName.search( 580 | this.spaceLikeTags, 581 | ) >= 0 582 | ) { 583 | nextTextNode = currentTextNode; 584 | continue; 585 | } 586 | } 587 | 588 | if ( 589 | currentNode.nodeName.search(this.blockTags) === -1 590 | ) { 591 | if ( 592 | nextNode.nodeName.search( 593 | this.spaceSensitiveTags, 594 | ) === -1 595 | ) { 596 | if ( 597 | nextNode.nodeName.search(this.ignoredTags) === 598 | -1 && 599 | nextNode.nodeName.search(this.blockTags) === -1 600 | ) { 601 | if (nextTextNode.previousSibling) { 602 | if ( 603 | nextTextNode.previousSibling.nodeName.search( 604 | this.spaceLikeTags, 605 | ) === -1 606 | ) { 607 | nextTextNode.data = " ".concat( 608 | nextTextNode.data, 609 | ); 610 | } 611 | } else { 612 | if (!this.canIgnoreNode(nextTextNode)) { 613 | nextTextNode.data = " ".concat( 614 | nextTextNode.data, 615 | ); 616 | } 617 | } 618 | } 619 | } else if ( 620 | currentNode.nodeName.search( 621 | this.spaceSensitiveTags, 622 | ) === -1 623 | ) { 624 | currentTextNode.data = "".concat( 625 | currentTextNode.data, 626 | " ", 627 | ); 628 | } else { 629 | var panguSpace = document.createElement("pangu"); 630 | panguSpace.innerHTML = " "; 631 | 632 | if (nextNode.previousSibling) { 633 | if ( 634 | nextNode.previousSibling.nodeName.search( 635 | this.spaceLikeTags, 636 | ) === -1 637 | ) { 638 | nextNode.parentNode.insertBefore( 639 | panguSpace, 640 | nextNode, 641 | ); 642 | } 643 | } else { 644 | nextNode.parentNode.insertBefore( 645 | panguSpace, 646 | nextNode, 647 | ); 648 | } 649 | 650 | if (!panguSpace.previousElementSibling) { 651 | if (panguSpace.parentNode) { 652 | panguSpace.parentNode.removeChild(panguSpace); 653 | } 654 | } 655 | } 656 | } 657 | } 658 | } 659 | 660 | nextTextNode = currentTextNode; 661 | } 662 | }, 663 | }, 664 | { 665 | key: "spacingNode", 666 | value: function spacingNode(contextNode) { 667 | var xPathQuery = ".//*/text()[normalize-space(.)]"; 668 | 669 | if ( 670 | contextNode.children && 671 | contextNode.children.length === 0 672 | ) { 673 | xPathQuery = ".//text()[normalize-space(.)]"; 674 | } 675 | 676 | this.spacingNodeByXPath(xPathQuery, contextNode); 677 | }, 678 | }, 679 | { 680 | key: "spacingElementById", 681 | value: function spacingElementById(idName) { 682 | var xPathQuery = 'id("'.concat(idName, '")//text()'); 683 | this.spacingNodeByXPath(xPathQuery, document); 684 | }, 685 | }, 686 | { 687 | key: "spacingElementByClassName", 688 | value: function spacingElementByClassName(className) { 689 | var xPathQuery = 690 | '//*[contains(concat(" ", normalize-space(@class), " "), "'.concat( 691 | className, 692 | '")]//text()', 693 | ); 694 | this.spacingNodeByXPath(xPathQuery, document); 695 | }, 696 | }, 697 | { 698 | key: "spacingElementByTagName", 699 | value: function spacingElementByTagName(tagName) { 700 | var xPathQuery = "//".concat(tagName, "//text()"); 701 | this.spacingNodeByXPath(xPathQuery, document); 702 | }, 703 | }, 704 | { 705 | key: "spacingPageTitle", 706 | value: function spacingPageTitle() { 707 | var xPathQuery = "/html/head/title/text()"; 708 | this.spacingNodeByXPath(xPathQuery, document); 709 | }, 710 | }, 711 | { 712 | key: "spacingPageBody", 713 | value: function spacingPageBody() { 714 | var xPathQuery = "/html/body//*/text()[normalize-space(.)]"; 715 | ["script", "style", "textarea"].forEach(function (tag) { 716 | xPathQuery = "" 717 | .concat( 718 | xPathQuery, 719 | '[translate(name(..),"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")!="', 720 | ) 721 | .concat(tag, '"]'); 722 | }); 723 | this.spacingNodeByXPath(xPathQuery, document); 724 | }, 725 | }, 726 | { 727 | key: "spacingPage", 728 | value: function spacingPage() { 729 | this.spacingPageTitle(); 730 | this.spacingPageBody(); 731 | }, 732 | }, 733 | { 734 | key: "autoSpacingPage", 735 | value: function autoSpacingPage() { 736 | var pageDelay = 737 | arguments.length > 0 && arguments[0] !== undefined 738 | ? arguments[0] 739 | : 1000; 740 | var nodeDelay = 741 | arguments.length > 1 && arguments[1] !== undefined 742 | ? arguments[1] 743 | : 500; 744 | var nodeMaxWait = 745 | arguments.length > 2 && arguments[2] !== undefined 746 | ? arguments[2] 747 | : 2000; 748 | 749 | if (!(document.body instanceof Node)) { 750 | return; 751 | } 752 | 753 | if (this.isAutoSpacingPageExecuted) { 754 | return; 755 | } 756 | 757 | this.isAutoSpacingPageExecuted = true; 758 | var self = this; 759 | var onceSpacingPage = once(function () { 760 | self.spacingPage(); 761 | }); 762 | var videos = document.getElementsByTagName("video"); 763 | 764 | if (videos.length === 0) { 765 | setTimeout(function () { 766 | onceSpacingPage(); 767 | }, pageDelay); 768 | } else { 769 | for (var i = 0; i < videos.length; i++) { 770 | var video = videos[i]; 771 | 772 | if (video.readyState === 4) { 773 | setTimeout(function () { 774 | onceSpacingPage(); 775 | }, 3000); 776 | break; 777 | } 778 | 779 | video.addEventListener("loadeddata", function () { 780 | setTimeout(function () { 781 | onceSpacingPage(); 782 | }, 4000); 783 | }); 784 | } 785 | } 786 | 787 | var queue = []; 788 | var debouncedSpacingNodes = debounce( 789 | function () { 790 | while (queue.length) { 791 | var node = queue.shift(); 792 | 793 | if (node) { 794 | self.spacingNode(node); 795 | } 796 | } 797 | }, 798 | nodeDelay, 799 | { 800 | maxWait: nodeMaxWait, 801 | }, 802 | ); 803 | var mutationObserver = new MutationObserver(function ( 804 | mutations, 805 | observer, 806 | ) { 807 | mutations.forEach(function (mutation) { 808 | switch (mutation.type) { 809 | case "childList": 810 | mutation.addedNodes.forEach(function (node) { 811 | if (node.nodeType === Node.ELEMENT_NODE) { 812 | queue.push(node); 813 | } else if (node.nodeType === Node.TEXT_NODE) { 814 | queue.push(node.parentNode); 815 | } 816 | }); 817 | break; 818 | 819 | case "characterData": 820 | var node = mutation.target; 821 | 822 | if (node.nodeType === Node.TEXT_NODE) { 823 | queue.push(node.parentNode); 824 | } 825 | 826 | break; 827 | 828 | default: 829 | break; 830 | } 831 | }); 832 | debouncedSpacingNodes(); 833 | }); 834 | mutationObserver.observe(document.body, { 835 | characterData: true, 836 | childList: true, 837 | subtree: true, 838 | }); 839 | }, 840 | }, 841 | ]); 842 | 843 | return BrowserPangu; 844 | })(Pangu); 845 | 846 | var pangu = new BrowserPangu(); 847 | module.exports = pangu; 848 | module.exports.default = pangu; 849 | module.exports.Pangu = BrowserPangu; 850 | }); 851 | 852 | /***/ 853 | }, 854 | /* 1 */ 855 | /***/ function (module, exports, __webpack_require__) { 856 | var __WEBPACK_AMD_DEFINE_FACTORY__, 857 | __WEBPACK_AMD_DEFINE_ARRAY__, 858 | __WEBPACK_AMD_DEFINE_RESULT__; 859 | (function (global, factory) { 860 | if (true) { 861 | !((__WEBPACK_AMD_DEFINE_ARRAY__ = []), 862 | (__WEBPACK_AMD_DEFINE_FACTORY__ = factory), 863 | (__WEBPACK_AMD_DEFINE_RESULT__ = 864 | typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" 865 | ? __WEBPACK_AMD_DEFINE_FACTORY__.apply( 866 | exports, 867 | __WEBPACK_AMD_DEFINE_ARRAY__, 868 | ) 869 | : __WEBPACK_AMD_DEFINE_FACTORY__), 870 | __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && 871 | (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 872 | } else { 873 | var mod; 874 | } 875 | })(this, function () { 876 | "use strict"; 877 | 878 | function _typeof(obj) { 879 | if ( 880 | typeof Symbol === "function" && 881 | typeof Symbol.iterator === "symbol" 882 | ) { 883 | _typeof = function _typeof(obj) { 884 | return typeof obj; 885 | }; 886 | } else { 887 | _typeof = function _typeof(obj) { 888 | return obj && 889 | typeof Symbol === "function" && 890 | obj.constructor === Symbol && 891 | obj !== Symbol.prototype 892 | ? "symbol" 893 | : typeof obj; 894 | }; 895 | } 896 | return _typeof(obj); 897 | } 898 | 899 | function _classCallCheck(instance, Constructor) { 900 | if (!(instance instanceof Constructor)) { 901 | throw new TypeError("Cannot call a class as a function"); 902 | } 903 | } 904 | 905 | function _defineProperties(target, props) { 906 | for (var i = 0; i < props.length; i++) { 907 | var descriptor = props[i]; 908 | descriptor.enumerable = descriptor.enumerable || false; 909 | descriptor.configurable = true; 910 | if ("value" in descriptor) descriptor.writable = true; 911 | Object.defineProperty(target, descriptor.key, descriptor); 912 | } 913 | } 914 | 915 | function _createClass(Constructor, protoProps, staticProps) { 916 | if (protoProps) 917 | _defineProperties(Constructor.prototype, protoProps); 918 | if (staticProps) _defineProperties(Constructor, staticProps); 919 | return Constructor; 920 | } 921 | 922 | var CJK = 923 | "\u2E80-\u2EFF\u2F00-\u2FDF\u3040-\u309F\u30A0-\u30FA\u30FC-\u30FF\u3100-\u312F\u3200-\u32FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF"; 924 | var ANY_CJK = new RegExp("[".concat(CJK, "]")); 925 | var CONVERT_TO_FULLWIDTH_CJK_SYMBOLS_CJK = new RegExp( 926 | "([".concat(CJK, "])[ ]*([\\:]+|\\.)[ ]*([").concat(CJK, "])"), 927 | "g", 928 | ); 929 | var CONVERT_TO_FULLWIDTH_CJK_SYMBOLS = new RegExp( 930 | "([".concat(CJK, "])[ ]*([~\\!;,\\?]+)[ ]*"), 931 | "g", 932 | ); 933 | var DOTS_CJK = new RegExp( 934 | "([\\.]{2,}|\u2026)([".concat(CJK, "])"), 935 | "g", 936 | ); 937 | var FIX_CJK_COLON_ANS = new RegExp( 938 | "([".concat(CJK, "])\\:([A-Z0-9\\(\\)])"), 939 | "g", 940 | ); 941 | var CJK_QUOTE = new RegExp("([".concat(CJK, '])([`"\u05F4])'), "g"); 942 | var QUOTE_CJK = new RegExp('([`"\u05F4])(['.concat(CJK, "])"), "g"); 943 | var FIX_QUOTE_ANY_QUOTE = /([`"\u05f4]+)[ ]*(.+?)[ ]*([`"\u05f4]+)/g; 944 | var CJK_SINGLE_QUOTE_BUT_POSSESSIVE = new RegExp( 945 | "([".concat(CJK, "])('[^s])"), 946 | "g", 947 | ); 948 | var SINGLE_QUOTE_CJK = new RegExp("(')([".concat(CJK, "])"), "g"); 949 | var FIX_POSSESSIVE_SINGLE_QUOTE = new RegExp( 950 | "([A-Za-z0-9".concat(CJK, "])( )('s)"), 951 | "g", 952 | ); 953 | var HASH_ANS_CJK_HASH = new RegExp( 954 | "([" 955 | .concat(CJK, "])(#)([") 956 | .concat(CJK, "]+)(#)([") 957 | .concat(CJK, "])"), 958 | "g", 959 | ); 960 | var CJK_HASH = new RegExp("([".concat(CJK, "])(#([^ ]))"), "g"); 961 | var HASH_CJK = new RegExp("(([^ ])#)([".concat(CJK, "])"), "g"); 962 | var CJK_OPERATOR_ANS = new RegExp( 963 | "([".concat(CJK, "])([\\+\\-\\*\\/=&\\|<>])([A-Za-z0-9])"), 964 | "g", 965 | ); 966 | var ANS_OPERATOR_CJK = new RegExp( 967 | "([A-Za-z0-9])([\\+\\-\\*\\/=&\\|<>])([".concat(CJK, "])"), 968 | "g", 969 | ); 970 | var FIX_SLASH_AS = /([/]) ([a-z\-_\./]+)/g; 971 | var FIX_SLASH_AS_SLASH = /([/\.])([A-Za-z\-_\./]+) ([/])/g; 972 | var CJK_LEFT_BRACKET = new RegExp( 973 | "([".concat(CJK, "])([\\(\\[\\{<>\u201C])"), 974 | "g", 975 | ); 976 | var RIGHT_BRACKET_CJK = new RegExp( 977 | "([\\)\\]\\}<>\u201D])([".concat(CJK, "])"), 978 | "g", 979 | ); 980 | var FIX_LEFT_BRACKET_ANY_RIGHT_BRACKET = 981 | /([\(\[\{<\u201c]+)[ ]*(.+?)[ ]*([\)\]\}>\u201d]+)/; 982 | var ANS_CJK_LEFT_BRACKET_ANY_RIGHT_BRACKET = new RegExp( 983 | "([A-Za-z0-9" 984 | .concat(CJK, "])[ ]*([\u201C])([A-Za-z0-9") 985 | .concat(CJK, "\\-_ ]+)([\u201D])"), 986 | "g", 987 | ); 988 | var LEFT_BRACKET_ANY_RIGHT_BRACKET_ANS_CJK = new RegExp( 989 | "([\u201C])([A-Za-z0-9" 990 | .concat(CJK, "\\-_ ]+)([\u201D])[ ]*([A-Za-z0-9") 991 | .concat(CJK, "])"), 992 | "g", 993 | ); 994 | var AN_LEFT_BRACKET = /([A-Za-z0-9])([\(\[\{])/g; 995 | var RIGHT_BRACKET_AN = /([\)\]\}])([A-Za-z0-9])/g; 996 | var CJK_ANS = new RegExp( 997 | "([".concat( 998 | CJK, 999 | "])([A-Za-z\u0370-\u03FF0-9@\\$%\\^&\\*\\-\\+\\\\=\\|/\xA1-\xFF\u2150-\u218F\u2700\u2014\u27BF])", 1000 | ), 1001 | "g", 1002 | ); 1003 | var ANS_CJK = new RegExp( 1004 | "([A-Za-z\u0370-\u03FF0-9~\\$%\\^&\\*\\-\\+\\\\=\\|/!;:,\\.\\?\xA1-\xFF\u2150-\u218F\u2700\u2014\u27BF])([".concat( 1005 | CJK, 1006 | "])", 1007 | ), 1008 | "g", 1009 | ); 1010 | var S_A = /(%)([A-Za-z])/g; 1011 | var MIDDLE_DOT = /([ ]*)([\u00b7\u2022\u2027])([ ]*)/g; 1012 | 1013 | var Pangu = (function () { 1014 | function Pangu() { 1015 | _classCallCheck(this, Pangu); 1016 | 1017 | this.version = "4.0.7"; 1018 | } 1019 | 1020 | _createClass(Pangu, [ 1021 | { 1022 | key: "convertToFullwidth", 1023 | value: function convertToFullwidth(symbols) { 1024 | return symbols 1025 | .replace(/~/g, "~") 1026 | .replace(/!/g, "!") 1027 | .replace(/;/g, ";") 1028 | .replace(/:/g, ":") 1029 | .replace(/,/g, ",") 1030 | .replace(/\./g, "。") 1031 | .replace(/\?/g, "?"); 1032 | }, 1033 | }, 1034 | { 1035 | key: "spacing", 1036 | value: function spacing(text) { 1037 | if (typeof text !== "string") { 1038 | console.warn( 1039 | "spacing(text) only accepts string but got ".concat( 1040 | _typeof(text), 1041 | ), 1042 | ); 1043 | return text; 1044 | } 1045 | 1046 | if (text.length <= 1 || !ANY_CJK.test(text)) { 1047 | return text; 1048 | } 1049 | 1050 | var self = this; 1051 | var newText = text; 1052 | newText = newText.replace( 1053 | CONVERT_TO_FULLWIDTH_CJK_SYMBOLS_CJK, 1054 | function (match, leftCjk, symbols, rightCjk) { 1055 | var fullwidthSymbols = self.convertToFullwidth(symbols); 1056 | return "" 1057 | .concat(leftCjk) 1058 | .concat(fullwidthSymbols) 1059 | .concat(rightCjk); 1060 | }, 1061 | ); 1062 | newText = newText.replace( 1063 | CONVERT_TO_FULLWIDTH_CJK_SYMBOLS, 1064 | function (match, cjk, symbols) { 1065 | var fullwidthSymbols = self.convertToFullwidth(symbols); 1066 | return "".concat(cjk).concat(fullwidthSymbols); 1067 | }, 1068 | ); 1069 | newText = newText.replace(DOTS_CJK, "$1 $2"); 1070 | newText = newText.replace(FIX_CJK_COLON_ANS, "$1:$2"); 1071 | newText = newText.replace(CJK_QUOTE, "$1 $2"); 1072 | newText = newText.replace(QUOTE_CJK, "$1 $2"); 1073 | newText = newText.replace(FIX_QUOTE_ANY_QUOTE, "$1$2$3"); 1074 | newText = newText.replace( 1075 | CJK_SINGLE_QUOTE_BUT_POSSESSIVE, 1076 | "$1 $2", 1077 | ); 1078 | newText = newText.replace(SINGLE_QUOTE_CJK, "$1 $2"); 1079 | newText = newText.replace( 1080 | FIX_POSSESSIVE_SINGLE_QUOTE, 1081 | "$1's", 1082 | ); 1083 | newText = newText.replace(HASH_ANS_CJK_HASH, "$1 $2$3$4 $5"); 1084 | newText = newText.replace(CJK_HASH, "$1 $2"); 1085 | newText = newText.replace(HASH_CJK, "$1 $3"); 1086 | newText = newText.replace(CJK_OPERATOR_ANS, "$1 $2 $3"); 1087 | newText = newText.replace(ANS_OPERATOR_CJK, "$1 $2 $3"); 1088 | newText = newText.replace(FIX_SLASH_AS, "$1$2"); 1089 | newText = newText.replace(FIX_SLASH_AS_SLASH, "$1$2$3"); 1090 | newText = newText.replace(CJK_LEFT_BRACKET, "$1 $2"); 1091 | newText = newText.replace(RIGHT_BRACKET_CJK, "$1 $2"); 1092 | newText = newText.replace( 1093 | FIX_LEFT_BRACKET_ANY_RIGHT_BRACKET, 1094 | "$1$2$3", 1095 | ); 1096 | newText = newText.replace( 1097 | ANS_CJK_LEFT_BRACKET_ANY_RIGHT_BRACKET, 1098 | "$1 $2$3$4", 1099 | ); 1100 | newText = newText.replace( 1101 | LEFT_BRACKET_ANY_RIGHT_BRACKET_ANS_CJK, 1102 | "$1$2$3 $4", 1103 | ); 1104 | newText = newText.replace(AN_LEFT_BRACKET, "$1 $2"); 1105 | newText = newText.replace(RIGHT_BRACKET_AN, "$1 $2"); 1106 | newText = newText.replace(CJK_ANS, "$1 $2"); 1107 | newText = newText.replace(ANS_CJK, "$1 $2"); 1108 | newText = newText.replace(S_A, "$1 $2"); 1109 | newText = newText.replace(MIDDLE_DOT, "・"); 1110 | return newText; 1111 | }, 1112 | }, 1113 | { 1114 | key: "spacingText", 1115 | value: function spacingText(text) { 1116 | var callback = 1117 | arguments.length > 1 && arguments[1] !== undefined 1118 | ? arguments[1] 1119 | : function () {}; 1120 | var newText; 1121 | 1122 | try { 1123 | newText = this.spacing(text); 1124 | } catch (err) { 1125 | callback(err); 1126 | return; 1127 | } 1128 | 1129 | callback(null, newText); 1130 | }, 1131 | }, 1132 | { 1133 | key: "spacingTextSync", 1134 | value: function spacingTextSync(text) { 1135 | return this.spacing(text); 1136 | }, 1137 | }, 1138 | ]); 1139 | 1140 | return Pangu; 1141 | })(); 1142 | 1143 | var pangu = new Pangu(); 1144 | module.exports = pangu; 1145 | module.exports.default = pangu; 1146 | module.exports.Pangu = Pangu; 1147 | }); 1148 | 1149 | /***/ 1150 | }, 1151 | /******/ 1152 | ], 1153 | ); 1154 | }); 1155 | --------------------------------------------------------------------------------