diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..81f3ec3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 +tab_width = 4 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e019f3c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ + +main.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..0807290 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,23 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "env": { "node": true }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], + "@typescript-eslint/ban-ts-comment": "off", + "no-prototype-builtins": "off", + "@typescript-eslint/no-empty-function": "off" + } + } \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a22b91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# vscode +.vscode + +# Intellij +*.iml +.idea + +# npm +node_modules + +# Don't include the compiled main.js file in the repo. +# They should be uploaded to GitHub releases instead. +main.js + +# Exclude sourcemaps +*.map + +# obsidian +data.json + +# Exclude macOS Finder (System Explorer) View States +.DS_Store + +assets \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b973752 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +tag-version-prefix="" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..db4b010 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 sunbooshi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..be2cafe --- /dev/null +++ b/esbuild.config.mjs @@ -0,0 +1,48 @@ +import esbuild from "esbuild"; +import process from "process"; +import builtins from "builtin-modules"; + +const banner = +`/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ +`; + +const prod = (process.argv[2] === "production"); + +const context = await esbuild.context({ + banner: { + js: banner, + }, + entryPoints: ["src/main.ts"], + bundle: true, + external: [ + "obsidian", + "electron", + "@codemirror/autocomplete", + "@codemirror/collab", + "@codemirror/commands", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr", + ...builtins], + format: "cjs", + target: "es2018", + logLevel: "info", + sourcemap: prod ? false : "inline", + treeShaking: true, + outfile: "main.js", +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} \ No newline at end of file diff --git a/images/20240630221748.jpg b/images/20240630221748.jpg new file mode 100644 index 0000000..061ee71 Binary files /dev/null and b/images/20240630221748.jpg differ diff --git a/images/20240702203745.jpg b/images/20240702203745.jpg new file mode 100644 index 0000000..2ed79ea Binary files /dev/null and b/images/20240702203745.jpg differ diff --git a/images/20240728183041.png b/images/20240728183041.png new file mode 100644 index 0000000..913acc4 Binary files /dev/null and b/images/20240728183041.png differ diff --git a/images/clipboard-paste.png b/images/clipboard-paste.png new file mode 100644 index 0000000..7e90022 Binary files /dev/null and b/images/clipboard-paste.png differ diff --git a/images/screenshot.png b/images/screenshot.png new file mode 100644 index 0000000..62d3582 Binary files /dev/null and b/images/screenshot.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..e05f008 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "note-to-mp", + "name": "NoteToMP", + "version": "1.3.0", + "minAppVersion": "1.4.5", + "description": "Send notes to WeChat MP drafts, or copy notes to WeChat MP editor, perfect preservation of note styles, support code highlighting, line numbers in code, and support local image uploads.", + "author": "Sun Booshi", + "authorUrl": "https://sunboshi.tech", + "isDesktopOnly": false +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cd928e1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2278 @@ +{ + "name": "note-to-mp", + "version": "1.3.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "note-to-mp", + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@zip.js/zip.js": "^2.7.43", + "highlight.js": "^11.9.0", + "html-to-image": "^1.11.11", + "marked": "^12.0.1", + "marked-highlight": "^2.1.3" + }, + "devDependencies": { + "@types/node": "^16.11.6", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "0.17.3", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "dev": true, + "peer": true + }, + "node_modules/@codemirror/view": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.0.tgz", + "integrity": "sha512-nSSmzONpqsNzshPOxiKhK203R6BvABepugAe34QfQDbNDslyjkqBuKgrK5ZBvqNXpfxz5iLrlGTmEfhbQyH46A==", + "dev": true, + "peer": true, + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", + "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", + "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", + "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", + "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", + "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", + "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", + "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", + "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", + "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", + "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", + "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", + "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", + "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", + "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", + "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", + "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", + "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", + "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", + "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", + "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", + "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", + "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true, + "peer": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.18.89", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.89.tgz", + "integrity": "sha512-QlrE8QI5z62nfnkiUZysUsAaxWaTMoGqFVcB3PvK1WxJ0c699bacErV4Fabe9Hki6ZnaHalgzihLbTl2d34XfQ==", + "dev": true + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz", + "integrity": "sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/type-utils": "5.29.0", + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz", + "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz", + "integrity": "sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz", + "integrity": "sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz", + "integrity": "sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz", + "integrity": "sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz", + "integrity": "sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz", + "integrity": "sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "peer": true + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.43", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.43.tgz", + "integrity": "sha512-kW7elA/Q1o5xusStfZeysCvheD1SvW3TWDfqTCmoWW4ALBSqKonZSTrQgdEGOUec2U/TLMSGq0SuSMTAxy4gFg==", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "peer": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "peer": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", + "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.3", + "@esbuild/android-arm64": "0.17.3", + "@esbuild/android-x64": "0.17.3", + "@esbuild/darwin-arm64": "0.17.3", + "@esbuild/darwin-x64": "0.17.3", + "@esbuild/freebsd-arm64": "0.17.3", + "@esbuild/freebsd-x64": "0.17.3", + "@esbuild/linux-arm": "0.17.3", + "@esbuild/linux-arm64": "0.17.3", + "@esbuild/linux-ia32": "0.17.3", + "@esbuild/linux-loong64": "0.17.3", + "@esbuild/linux-mips64el": "0.17.3", + "@esbuild/linux-ppc64": "0.17.3", + "@esbuild/linux-riscv64": "0.17.3", + "@esbuild/linux-s390x": "0.17.3", + "@esbuild/linux-x64": "0.17.3", + "@esbuild/netbsd-x64": "0.17.3", + "@esbuild/openbsd-x64": "0.17.3", + "@esbuild/sunos-x64": "0.17.3", + "@esbuild/win32-arm64": "0.17.3", + "@esbuild/win32-ia32": "0.17.3", + "@esbuild/win32-x64": "0.17.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "peer": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "peer": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-to-image": { + "version": "1.11.11", + "resolved": "https://registry.npmmirror.com/html-to-image/-/html-to-image-1.11.11.tgz", + "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==" + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/marked": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.1.tgz", + "integrity": "sha512-Y1/V2yafOcOdWQCX0XpAKXzDakPOpn6U0YLxTJs3cww6VxOzZV1BTOOYWLvH3gX38cq+iLwljHHTnMtlDfg01Q==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-highlight": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked-highlight/-/marked-highlight-2.1.3.tgz", + "integrity": "sha512-t35JWm2u8HanOJ+gSJBAYQ0Jgr3vy+gl7ORAXN8bSEQFHl5FYXH0A7YXVMrfhmKaSuBSy6LidXECn3U9Qv/dHA==", + "peerDependencies": { + "marked": ">=4 <14" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "peer": true + }, + "node_modules/obsidian": { + "version": "1.5.7-1", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.5.7-1.tgz", + "integrity": "sha512-T5ZRuQ1FnfXqEoakTTHVDYvzUEEoT8zSPnQCW31PVgYwG4D4tZCQfKHN2hTz1ifnCe8upvwa6mBTAP2WUA5Vng==", + "dev": true, + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "peer": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "dev": true, + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "peer": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "peer": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0cbe0e0 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "note-to-mp", + "version": "1.3.0", + "description": "This is a plugin for Obsidian (https://obsidian.md)", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "download": "node tools/download.mjs", + "version": "node version-bump.mjs && git add manifest.json versions.json" + }, + "keywords": [], + "author": "", + "license": "MIT", + "devDependencies": { + "@types/node": "^16.11.6", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "0.17.3", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + }, + "dependencies": { + "@zip.js/zip.js": "^2.7.43", + "highlight.js": "^11.9.0", + "html-to-image": "^1.11.11", + "marked": "^12.0.1", + "marked-highlight": "^2.1.3" + } +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..d678676 --- /dev/null +++ b/run.sh @@ -0,0 +1,20 @@ + +#!/bin/bash + +# 自动添加所有修改 +git add . + +# 如果没有提交信息,默认用时间戳 +msg="update at $(date '+%Y-%m-%d %H:%M:%S')" + +# 支持自定义提交信息:./run.sh "your message" +if [ $# -gt 0 ]; then + msg="$*" +fi + +# 提交 +git commit -m "$msg" + +# 推送到远程 main 分支 +git push origin main + diff --git a/src/article-render.ts b/src/article-render.ts new file mode 100644 index 0000000..b702fd2 --- /dev/null +++ b/src/article-render.ts @@ -0,0 +1,630 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { App, ItemView, Workspace, Notice, sanitizeHTMLToDom, apiVersion, TFile, MarkdownRenderer, FrontMatterCache } from 'obsidian'; +import { applyCSS } from './utils'; +import { UploadImageToWx } from './imagelib'; +import { NMPSettings } from './settings'; +import AssetsManager from './assets'; +import InlineCSS from './inline-css'; +import { wxGetToken, wxAddDraft, wxBatchGetMaterial, DraftArticle, DraftImageMediaId, DraftImages, wxAddDraftImages } from './weixin-api'; +import { MDRendererCallback } from './markdown/extension'; +import { MarkedParser } from './markdown/parser'; +import { LocalImageManager, LocalFile } from './markdown/local-file'; +import { CardDataManager } from './markdown/code'; +import { debounce } from './utils'; +import { PrepareImageLib, IsImageLibReady, WebpToJPG } from './imagelib'; +import { toPng } from 'html-to-image'; + + +const FRONT_MATTER_REGEX = /^(---)$.+?^(---)$.+?/ims; + +export class ArticleRender implements MDRendererCallback { + app: App; + itemView: ItemView; + workspace: Workspace; + styleEl: HTMLElement; + articleDiv: HTMLDivElement; + settings: NMPSettings; + assetsManager: AssetsManager; + articleHTML: string; + title: string; + _currentTheme: string; + _currentHighlight: string; + _currentAppId: string; + markedParser: MarkedParser; + cachedElements: Map = new Map(); + debouncedRenderMarkdown: (...args: any[]) => void; + + constructor(app: App, itemView: ItemView, styleEl: HTMLElement, articleDiv: HTMLDivElement) { + this.app = app; + this.itemView = itemView; + this.styleEl = styleEl; + this.articleDiv = articleDiv; + this.settings = NMPSettings.getInstance(); + this.assetsManager = AssetsManager.getInstance(); + this.articleHTML = ''; + this.title = ''; + this._currentTheme = 'default'; + this._currentHighlight = 'default'; + this.markedParser = new MarkedParser(app, this); + this.debouncedRenderMarkdown = debounce(this.renderMarkdown.bind(this), 1000); + } + + set currentTheme(value: string) { + this._currentTheme = value; + } + + get currentTheme() { + const { theme } = this.getMetadata(); + if (theme) { + return theme; + } + return this._currentTheme; + } + + set currentHighlight(value: string) { + this._currentHighlight = value; + } + + get currentHighlight() { + const { highlight } = this.getMetadata(); + if (highlight) { + return highlight; + } + return this._currentHighlight; + } + + isOldTheme() { + const theme = this.assetsManager.getTheme(this.currentTheme); + if (theme) { + return theme.css.indexOf('.note-to-mp') < 0; + } + return false; + } + + setArticle(article: string) { + this.articleDiv.empty(); + let className = 'note-to-mp'; + // 兼容旧版本样式 + if (this.isOldTheme()) { + className = this.currentTheme; + } + const html = `
${article}
`; + const doc = sanitizeHTMLToDom(html); + if (doc.firstChild) { + this.articleDiv.appendChild(doc.firstChild); + } + } + + setStyle(css: string) { + this.styleEl.empty(); + this.styleEl.appendChild(document.createTextNode(css)); + } + + reloadStyle() { + this.setStyle(this.getCSS()); + } + + getArticleSection() { + return this.articleDiv.querySelector('#article-section') as HTMLElement; + } + + getArticleContent() { + const content = this.articleDiv.innerHTML; + let html = applyCSS(content, this.getCSS()); + // 处理话题多余内容 + html = html.replace(/rel="noopener nofollow"/g, ''); + html = html.replace(/target="_blank"/g, ''); + html = html.replace(/data-leaf=""/g, 'leaf=""'); + return CardDataManager.getInstance().restoreCard(html); + } + + getArticleText() { + return this.articleDiv.innerText.trimStart(); + } + + errorContent(error: any) { + return '

渲染失败!


' + + '如需帮助请前往  https://github.com/sunbooshi/note-to-mp/issues  反馈

' + + '如果方便,请提供引发错误的完整Markdown内容。

' + + '
Obsidian版本:' + apiVersion + + '
错误信息:
' + + `${error}`; + } + + async renderMarkdown(af: TFile | null = null) { + try { + let md = ''; + if (af && af.extension.toLocaleLowerCase() === 'md') { + md = await this.app.vault.adapter.read(af.path); + this.title = af.basename; + } + else { + md = '没有可渲染的笔记或文件不支持渲染'; + } + if (md.startsWith('---')) { + md = md.replace(FRONT_MATTER_REGEX, ''); + } + + this.articleHTML = await this.markedParser.parse(md); + this.setStyle(this.getCSS()); + this.setArticle(this.articleHTML); + await this.processCachedElements(); + } + catch (e) { + console.error(e); + this.setArticle(this.errorContent(e)); + } + } + getCSS() { + try { + const theme = this.assetsManager.getTheme(this.currentTheme); + const highlight = this.assetsManager.getHighlight(this.currentHighlight); + const customCSS = this.settings.customCSSNote.length > 0 || this.settings.useCustomCss ? this.assetsManager.customCSS : ''; + const baseCSS = this.settings.baseCSS ? `.note-to-mp {${this.settings.baseCSS}}` : ''; + return `${InlineCSS}\n\n${highlight!.css}\n\n${theme!.css}\n\n${baseCSS}\n\n${customCSS}`; + } catch (error) { + console.error(error); + new Notice(`获取样式失败${this.currentTheme}|${this.currentHighlight},请检查主题是否正确安装。`); + } + return ''; + } + + updateStyle(styleName: string) { + this.currentTheme = styleName; + this.setStyle(this.getCSS()); + } + + updateHighLight(styleName: string) { + this.currentHighlight = styleName; + this.setStyle(this.getCSS()); + } + + getFrontmatterValue(frontmatter: FrontMatterCache, key: string) { + const value = frontmatter[key]; + + if (value instanceof Array) { + return value[0]; + } + + return value; + } + + getMetadata() { + let res: DraftArticle = { + title: '', + author: undefined, + digest: undefined, + content: '', + content_source_url: undefined, + cover: undefined, + thumb_media_id: '', + need_open_comment: undefined, + only_fans_can_comment: undefined, + pic_crop_235_1: undefined, + pic_crop_1_1: undefined, + appid: undefined, + theme: undefined, + highlight: undefined, + } + const file = this.app.workspace.getActiveFile(); + if (!file) return res; + const metadata = this.app.metadataCache.getFileCache(file); + if (metadata?.frontmatter) { + const keys = this.assetsManager.expertSettings.frontmatter; + const frontmatter = metadata.frontmatter; + res.title = this.getFrontmatterValue(frontmatter, keys.title); + res.author = this.getFrontmatterValue(frontmatter, keys.author); + res.digest = this.getFrontmatterValue(frontmatter, keys.digest); + res.content_source_url = this.getFrontmatterValue(frontmatter, keys.content_source_url); + res.cover = this.getFrontmatterValue(frontmatter, keys.cover); + res.thumb_media_id = this.getFrontmatterValue(frontmatter, keys.thumb_media_id); + res.need_open_comment = frontmatter[keys.need_open_comment] ? 1 : undefined; + res.only_fans_can_comment = frontmatter[keys.only_fans_can_comment] ? 1 : undefined; + res.appid = this.getFrontmatterValue(frontmatter, keys.appid); + if (res.appid && !res.appid.startsWith('wx')) { + res.appid = this.settings.wxInfo.find(wx => wx.name === res.appid)?.appid; + } + res.theme = this.getFrontmatterValue(frontmatter, keys.theme); + res.highlight = this.getFrontmatterValue(frontmatter, keys.highlight); + if (frontmatter[keys.crop]) { + res.pic_crop_235_1 = '0_0_1_0.5'; + res.pic_crop_1_1 = '0_0.525_0.404_1'; + } + } + return res; + } + + async uploadVaultCover(name: string, token: string) { + const LocalFileRegex = /^!\[\[(.*?)\]\]/; + const matches = name.match(LocalFileRegex); + let fileName = ''; + if (matches && matches.length > 1) { + fileName = matches[1]; + } + else { + fileName = name; + } + const vault = this.app.vault; + const file = this.assetsManager.searchFile(fileName) as TFile; + if (!file) { + throw new Error('找不到封面文件: ' + fileName); + } + const fileData = await vault.readBinary(file); + + return await this.uploadCover(new Blob([fileData]), file.name, token); + } + + async uploadCover(data: Blob, filename: string, token: string) { + if (filename.toLowerCase().endsWith('.webp')) { + await PrepareImageLib(); + if (IsImageLibReady()) { + data = new Blob([WebpToJPG(await data.arrayBuffer())]); + filename = filename.toLowerCase().replace('.webp', '.jpg'); + } + } + + const res = await UploadImageToWx(data, filename, token, 'image'); + if (res.media_id) { + return res.media_id; + } + console.error('upload cover fail: ' + res.errmsg); + throw new Error('上传封面失败: ' + res.errmsg); + } + + async getDefaultCover(token: string) { + const res = await wxBatchGetMaterial(token, 'image'); + if (res.item_count > 0) { + return res.item[0].media_id; + } + return ''; + } + + async getToken(appid: string) { + const secret = this.getSecret(appid); + const res = await wxGetToken(this.settings.authKey, appid, secret); + if (res.status != 200) { + const data = res.json; + throw new Error('获取token失败: ' + data.message); + } + const token = res.json.token; + if (token === '') { + throw new Error('获取token失败: ' + res.json.message); + } + return token; + } + + async uploadImages(appid: string) { + if (!this.settings.authKey) { + throw new Error('请先设置注册码(AuthKey)'); + } + + let metadata = this.getMetadata(); + if (metadata.appid) { + appid = metadata.appid; + } + + if (!appid || appid.length == 0) { + throw new Error('请先选择公众号'); + } + + // 获取token + const token = await this.getToken(appid); + if (token === '') { + return; + } + + await this.cachedElementsToImages(); + + const lm = LocalImageManager.getInstance(); + // 上传图片 + await lm.uploadLocalImage(token, this.app.vault); + // 上传图床图片 + await lm.uploadRemoteImage(this.articleDiv, token); + // 替换图片链接 + lm.replaceImages(this.articleDiv); + + await this.copyArticle(); + } + + async copyArticle() { + const content = this.getArticleContent(); + await navigator.clipboard.write([new ClipboardItem({ + 'text/html': new Blob([content], { type: 'text/html' }) + })]) + } + + getSecret(appid: string) { + for (const wx of this.settings.wxInfo) { + if (wx.appid === appid) { + return wx.secret.replace('SECRET', ''); + } + } + return ''; + } + + async postArticle(appid:string, localCover: File | null = null) { + if (!this.settings.authKey) { + throw new Error('请先设置注册码(AuthKey)'); + } + + let metadata = this.getMetadata(); + if (metadata.appid) { + appid = metadata.appid; + } + + if (!appid || appid.length == 0) { + throw new Error('请先选择公众号'); + } + // 获取token + const token = await this.getToken(appid); + if (token === '') { + throw new Error('获取token失败,请检查网络链接!'); + } + await this.cachedElementsToImages(); + const lm = LocalImageManager.getInstance(); + // 上传图片 + await lm.uploadLocalImage(token, this.app.vault); + // 上传图床图片 + await lm.uploadRemoteImage(this.articleDiv, token); + // 替换图片链接 + lm.replaceImages(this.articleDiv); + // 上传封面 + let mediaId = metadata.thumb_media_id; + if (!mediaId) { + if (metadata.cover) { + // 上传仓库里的图片 + if (metadata.cover.startsWith('http')) { + const res = await LocalImageManager.getInstance().uploadImageFromUrl(metadata.cover, token, 'image'); + if (res.media_id) { + mediaId = res.media_id; + } + else { + throw new Error('上传封面失败:' + res.errmsg); + } + } + else { + mediaId = await this.uploadVaultCover(metadata.cover, token); + } + } + else if (localCover) { + mediaId = await this.uploadCover(localCover, localCover.name, token); + } + else { + mediaId = await this.getDefaultCover(token); + } + } + + if (mediaId === '') { + throw new Error('请先上传图片或者设置默认封面'); + } + + metadata.title = metadata.title || this.title; + metadata.content = this.getArticleContent(); + metadata.thumb_media_id = mediaId; + + // 创建草稿 + const res = await wxAddDraft(token, metadata); + + if (res.status != 200) { + console.error(res.text); + throw new Error(`创建草稿失败, https状态码: ${res.status} 可能是文章包含异常内容,请尝试手动复制到公众号编辑器!`); + } + + const draft = res.json; + if (draft.media_id) { + return draft.media_id; + } + else { + console.error(JSON.stringify(draft)); + throw new Error('发布失败!' + draft.errmsg); + } + } + + async postImages(appid: string) { + if (!this.settings.authKey) { + throw new Error('请先设置注册码(AuthKey)'); + } + + let metadata = this.getMetadata(); + if (metadata.appid) { + appid = metadata.appid; + } + + if (!appid || appid.length == 0) { + throw new Error('请先选择公众号'); + } + + // 获取token + const token = await this.getToken(appid); + if (token === '') { + throw new Error('获取token失败,请检查网络链接!'); + } + + const imageList: DraftImageMediaId[] = []; + const lm = LocalImageManager.getInstance(); + // 上传图片 + await lm.uploadLocalImage(token, this.app.vault, 'image'); + // 上传图床图片 + await lm.uploadRemoteImage(this.articleDiv, token, 'image'); + + const images = lm.getImageInfos(this.articleDiv); + for (const image of images) { + if (!image.media_id) { + console.warn('miss media id:', image.resUrl); + continue; + } + imageList.push({ + image_media_id: image.media_id, + }); + } + + if (imageList.length === 0) { + throw new Error('没有图片需要发布!'); + } + + const content = this.getArticleText(); + + const imagesData: DraftImages = { + article_type: 'newspic', + title: metadata.title || this.title, + content: content, + need_open_commnet: metadata.need_open_comment || 0, + only_fans_can_comment: metadata.only_fans_can_comment || 0, + image_info: { + image_list: imageList, + } + } + // 创建草稿 + const res = await wxAddDraftImages(token, imagesData); + + if (res.status != 200) { + console.error(res.text); + throw new Error(`创建图片/文字失败, https状态码: ${res.status} ${res.text}!`); + } + + const draft = res.json; + if (draft.media_id) { + return draft.media_id; + } + else { + console.error(JSON.stringify(draft)); + throw new Error('发布失败!' + draft.errmsg); + } + } + + async exportHTML() { + await this.cachedElementsToImages(); + const lm = LocalImageManager.getInstance(); + const content = await lm.embleImages(this.articleDiv, this.app.vault); + const globalStyle = await this.assetsManager.getStyle(); + const html = applyCSS(content, this.getCSS() + globalStyle); + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = this.title + '.html'; + a.click(); + URL.revokeObjectURL(url); + a.remove(); + } + + async processCachedElements() { + const af = this.app.workspace.getActiveFile(); + if (!af) { + console.error('当前没有打开文件,无法处理缓存元素'); + return; + } + for (const [key, value] of this.cachedElements) { + const [category, id] = key.split(':'); + if (category === 'mermaid' || category === 'excalidraw') { + const container = this.articleDiv.querySelector('#' + id) as HTMLElement; + if (container) { + await MarkdownRenderer.render(this.app, value, container, af.path, this.itemView); + } + } + } + } + + async cachedElementsToImages() { + for (const [key, cached] of this.cachedElements) { + const [category, elementId] = key.split(':'); + const container = this.articleDiv.querySelector(`#${elementId}`) as HTMLElement; + if (!container) continue; + + if (category === 'mermaid') { + await this.replaceMermaidWithImage(container, elementId); + } else if (category === 'excalidraw') { + await this.replaceExcalidrawWithImage(container, elementId); + } + } + } + + private async replaceMermaidWithImage(container: HTMLElement, id: string) { + const mermaidContainer = container.querySelector('.mermaid') as HTMLElement; + if (!mermaidContainer || !mermaidContainer.children.length) return; + + const svg = mermaidContainer.querySelector('svg'); + if (!svg) return; + + try { + const pngDataUrl = await toPng(mermaidContainer.firstElementChild as HTMLElement, { pixelRatio: 2 }); + const img = document.createElement('img'); + img.id = `img-${id}`; + img.src = pngDataUrl; + img.style.width = `${svg.clientWidth}px`; + img.style.height = 'auto'; + + container.replaceChild(img, mermaidContainer); + } catch (error) { + console.warn(`Failed to render Mermaid diagram: ${id}`, error); + } + } + + private async replaceExcalidrawWithImage(container: HTMLElement, id: string) { + const innerDiv = container.querySelector('div') as HTMLElement; + if (!innerDiv) return; + + if (NMPSettings.getInstance().excalidrawToPNG) { + const originalImg = container.querySelector('img') as HTMLImageElement; + if (!originalImg) return; + + const style = originalImg.getAttribute('style') || ''; + try { + const pngDataUrl = await toPng(originalImg, { pixelRatio: 2 }); + + const img = document.createElement('img'); + img.id = `img-${id}`; + img.src = pngDataUrl; + img.setAttribute('style', style); + + container.replaceChild(img, container.firstChild!); + } catch (error) { + console.warn(`Failed to render Excalidraw image: ${id}`, error); + } + } else { + const svg = await LocalFile.renderExcalidraw(innerDiv.innerHTML); + this.updateElementByID(id, svg); + } + } + + updateElementByID(id: string, html: string): void { + const item = this.articleDiv.querySelector('#' + id) as HTMLElement; + if (!item) return; + const doc = sanitizeHTMLToDom(html); + item.empty(); + if (doc.childElementCount > 0) { + for (const child of doc.children) { + item.appendChild(child.cloneNode(true)); // 使用 cloneNode 复制节点以避免移动它 + } + } + else { + item.innerText = '渲染失败'; + } + } + + cacheElement(category: string, id: string, data: string): void { + const key = category + ':' + id; + this.cachedElements.set(key, data); + } +} \ No newline at end of file diff --git a/src/assets.ts b/src/assets.ts new file mode 100644 index 0000000..521a8f1 --- /dev/null +++ b/src/assets.ts @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { App, PluginManifest, Notice, requestUrl, FileSystemAdapter, TAbstractFile, TFile, TFolder } from "obsidian"; +import * as zip from "@zip.js/zip.js"; +import DefaultTheme from "./default-theme"; +import DefaultHighlight from "./default-highlight"; +import { NMPSettings } from "./settings"; +import { ExpertSettings, defaultExpertSettings, expertSettingsFromString } from "./expert-settings"; + + +export interface Theme { + name: string + className: string + desc: string + author: string + css: string +} + +export interface Highlight { + name: string + url: string + css: string +} + +export default class AssetsManager { + app: App; + defaultTheme: Theme = DefaultTheme; + manifest: PluginManifest; + themes: Theme[]; + highlights: Highlight[]; + assetsPath: string; + themesPath: string; + hilightPath: string; + customCSS: string = ''; + themeCfg: string; + hilightCfg: string; + customCSSPath: string; + iconsPath: string; + wasmPath: string; + expertSettings: ExpertSettings; + isLoaded: boolean = false; + + private static instance: AssetsManager; + + // 静态方法,用于获取实例 + public static getInstance(): AssetsManager { + if (!AssetsManager.instance) { + AssetsManager.instance = new AssetsManager(); + } + return AssetsManager.instance; + } + + public static setup(app: App, manifest: PluginManifest) { + AssetsManager.getInstance()._setup(app, manifest); + } + + private _setup(app: App, manifest: PluginManifest) { + this.app = app; + this.manifest = manifest; + this.assetsPath = this.app.vault.configDir + '/plugins/' + this.manifest.id + '/assets/'; + this.themesPath = this.assetsPath + 'themes/'; + this.hilightPath = this.assetsPath + 'highlights/'; + this.themeCfg = this.assetsPath + 'themes.json'; + this.hilightCfg = this.assetsPath + 'highlights.json'; + this.customCSSPath = this.assetsPath + 'custom.css'; + this.iconsPath = this.assetsPath + 'icons/'; + this.wasmPath = this.assetsPath + 'lib.wasm'; + } + + private constructor() { + + } + + async loadAssets() { + await this.loadThemes(); + await this.loadHighlights(); + await this.loadCustomCSS(); + await this.loadExpertSettings(); + this.isLoaded = true; + } + + async loadThemes() { + try { + if (!await this.app.vault.adapter.exists(this.themeCfg)) { + new Notice('主题资源未下载,请前往设置下载!'); + this.themes = [this.defaultTheme]; + return; + } + const data = await this.app.vault.adapter.read(this.themeCfg); + if (data) { + const themes = JSON.parse(data); + await this.loadCSS(themes); + this.themes = [this.defaultTheme, ... themes]; + } + } catch (error) { + console.error(error); + new Notice('themes.json解析失败!'); + } + } + + async loadCSS(themes: Theme[]) { + try { + for (const theme of themes) { + const cssFile = this.themesPath + theme.className + '.css'; + const cssContent = await this.app.vault.adapter.read(cssFile); + if (cssContent) { + theme.css = cssContent; + } + } + } catch (error) { + console.error(error); + new Notice('读取CSS失败!'); + } + } + + async loadCustomCSS() { + try { + const customCSSNote = NMPSettings.getInstance().customCSSNote; + if (customCSSNote != '') { + const file = this.searchFile(customCSSNote); + if (file) { + const cssContent = await this.app.vault.adapter.read(file.path); + if (cssContent) { + this.customCSS = cssContent.replace(/```css/gi, '').replace(/```/g, ''); + } + } + else { + new Notice(customCSSNote + '自定义CSS文件不存在!'); + } + return; + } + + if (!await this.app.vault.adapter.exists(this.customCSSPath)) { + return; + } + + const cssContent = await this.app.vault.adapter.read(this.customCSSPath); + if (cssContent) { + this.customCSS = cssContent; + } + } catch (error) { + console.error(error); + new Notice('读取CSS失败!'); + } + } + + async loadExpertSettings() { + try { + const note = NMPSettings.getInstance().expertSettingsNote; + if (note != '') { + const file = this.searchFile(note); + if (file) { + let content = await this.app.vault.adapter.read(file.path); + if (content) { + this.expertSettings = expertSettingsFromString(content); + } + else { + this.expertSettings = defaultExpertSettings; + new Notice(note + '专家设置文件内容为空!'); + } + } + else { + this.expertSettings = defaultExpertSettings; + new Notice(note + '专家设置不存在!'); + } + } + else { + this.expertSettings = defaultExpertSettings; + } + } catch (error) { + console.error(error); + new Notice('读取专家设置失败!'); + } + } + + async loadHighlights() { + try { + const defaultHighlight = {name: '默认', url: '', css: DefaultHighlight}; + this.highlights = [defaultHighlight]; + if (!await this.app.vault.adapter.exists(this.hilightCfg)) { + new Notice('高亮资源未下载,请前往设置下载!'); + return; + } + + const data = await this.app.vault.adapter.read(this.hilightCfg); + if (data) { + const items = JSON.parse(data); + for (const item of items) { + const cssFile = this.hilightPath + item.name + '.css'; + const cssContent = await this.app.vault.adapter.read(cssFile); + this.highlights.push({name: item.name, url: item.url, css: cssContent}); + } + } + } + catch (error) { + console.error(error); + new Notice('highlights.json解析失败!'); + } + } + + async loadIcon(name: string) { + const icon = this.iconsPath + name + '.svg'; + if (!await this.app.vault.adapter.exists(icon)) { + return ''; + } + const iconContent = await this.app.vault.adapter.read(icon); + if (iconContent) { + return iconContent; + } + return ''; + } + + async loadWasm() { + if (!await this.app.vault.adapter.exists(this.wasmPath)) { + return null; + } + const wasmContent = await this.app.vault.adapter.readBinary(this.wasmPath); + if (wasmContent) { + return wasmContent; + } + return null; + } + + getTheme(themeName: string) { + if (themeName === '') { + return this.themes[0]; + } + + for (const theme of this.themes) { + if (theme.name.toLowerCase() === themeName.toLowerCase() || theme.className.toLowerCase() === themeName.toLowerCase()) { + return theme; + } + } + } + + getHighlight(highlightName: string) { + if (highlightName === '') { + return this.highlights[0]; + } + + for (const highlight of this.highlights) { + if (highlight.name.toLowerCase() === highlightName.toLowerCase()) { + return highlight; + } + } + } + + getThemeURL() { + const version = this.manifest.version; + return `https://github.com/sunbooshi/note-to-mp/releases/download/${version}/assets.zip`; + } + + async getStyle() { + const file = this.app.vault.configDir + '/plugins/' + this.manifest.id + '/styles.css'; + if (!await this.app.vault.adapter.exists(file)) { + return ''; + } + const data = await this.app.vault.adapter.read(file); + if (data) { + return data; + } + return ''; + } + + async downloadThemes() { + try { + if (await this.app.vault.adapter.exists(this.themeCfg)) { + new Notice('主题资源已存在!') + return; + } + const res = await requestUrl(this.getThemeURL()); + const data = res.arrayBuffer; + await this.unzip(new Blob([data])); + await this.loadAssets(); + new Notice('主题下载完成!'); + } catch (error) { + console.error(error); + await this.removeThemes(); + new Notice('主题下载失败, 请检查网络!'); + } + } + + async unzip(data:Blob) { + const zipFileReader = new zip.BlobReader(data); + const zipReader = new zip.ZipReader(zipFileReader); + const entries = await zipReader.getEntries(); + + if (!await this.app.vault.adapter.exists(this.assetsPath)) { + await this.app.vault.adapter.mkdir(this.assetsPath); + } + + for (const entry of entries) { + if (entry.directory) { + const dirPath = this.assetsPath + entry.filename; + await this.app.vault.adapter.mkdir(dirPath); + } + else { + const filePath = this.assetsPath + entry.filename; + const blobWriter = new zip.Uint8ArrayWriter(); + if (entry.getData) { + const data = await entry.getData(blobWriter); + await this.app.vault.adapter.writeBinary(filePath, data.buffer as ArrayBuffer); + } + } + } + + await zipReader.close(); + } + + async removeThemes() { + try { + const adapter = this.app.vault.adapter; + if (await adapter.exists(this.themeCfg)) { + await adapter.remove(this.themeCfg); + } + if (await adapter.exists(this.hilightCfg)) { + await adapter.remove(this.hilightCfg); + } + if (await adapter.exists(this.themesPath)) { + await adapter.rmdir(this.themesPath, true); + } + if (await adapter.exists(this.hilightPath)) { + await adapter.rmdir(this.hilightPath, true); + } + await this.loadAssets(); + new Notice('清空完成!'); + } catch (error) { + console.error(error); + new Notice('清空主题失败!'); + } + } + + async openAssets() { + const path = require('path'); + const adapter = this.app.vault.adapter as FileSystemAdapter; + const vaultRoot = adapter.getBasePath(); + const assets = this.assetsPath; + if (!await adapter.exists(assets)) { + await adapter.mkdir(assets); + } + const dst = path.join(vaultRoot, assets); + const { shell } = require('electron'); + shell.openPath(dst); + } + + searchFile(nameOrPath: string): TAbstractFile | null { + const resolvedPath = this.resolvePath(nameOrPath); + const vault= this.app.vault; + const attachmentFolderPath = vault.config.attachmentFolderPath || ''; + let localPath = resolvedPath; + let file = null; + + // 先按路径查找 + file = vault.getFileByPath(resolvedPath); + if (file) { + return file; + } + + // 在根目录查找 + file = vault.getFileByPath(nameOrPath); + if (file) { + return file; + } + + // 从附件文件夹查找 + if (attachmentFolderPath != '') { + localPath = attachmentFolderPath + '/' + nameOrPath; + file = vault.getFileByPath(localPath) + if (file) { + return file; + } + + localPath = attachmentFolderPath + '/' + resolvedPath; + file = vault.getFileByPath(localPath) + if (file) { + return file; + } + } + + // 最后查找所有文件,这里只需要判断文件名 + const files = vault.getAllLoadedFiles(); + for (let f of files) { + if (f instanceof TFolder) continue + file = f as TFile; + if (file.basename === nameOrPath || file.name === nameOrPath) { + return f; + } + } + + return null; + } + + getResourcePath(path: string): {resUrl:string, filePath:string}|null { + const file = this.searchFile(path) as TFile; + if (file == null) { + return null; + } + const resUrl = this.app.vault.getResourcePath(file); + return {resUrl, filePath: file.path}; + } + + resolvePath(relativePath: string): string { + const basePath = this.getActiveFileDir(); + if (!relativePath.includes('/')) { + return relativePath; + } + const stack = basePath.split("/"); + const parts = relativePath.split("/"); + + stack.pop(); // Remove the current file name (or empty string) + + for (const part of parts) { + if (part === ".") continue; + if (part === "..") stack.pop(); + else stack.push(part); + } + return stack.join("/"); + } + + getActiveFileDir() { + const af = this.app.workspace.getActiveFile(); + if (af == null) { + return ''; + } + const parts = af.path.split('/'); + parts.pop(); + if (parts.length == 0) { + return ''; + } + return parts.join('/'); + } + + async readFileBinary(path: string) { + const vault= this.app.vault; + const file = this.searchFile(path) as TFile; + if (file == null) { + return null; + } + return await vault.readBinary(file); + } +} \ No newline at end of file diff --git a/src/default-highlight.ts b/src/default-highlight.ts new file mode 100644 index 0000000..9d1719b --- /dev/null +++ b/src/default-highlight.ts @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +export default ` +pre code.hljs { + display: block; +} +/* + +XCode style (c) Angel Garcia + +*/ +.hljs { + background: #fff; + color: black +} +/* Gray DOCTYPE selectors like WebKit */ +.xml .hljs-meta { + color: #c0c0c0 +} +.hljs-comment, +.hljs-quote { + color: #007400 +} +.hljs-tag, +.hljs-attribute, +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-name { + color: #aa0d91 +} +.hljs-variable, +.hljs-template-variable { + color: #3F6E74 +} +.hljs-code, +.hljs-string, +.hljs-meta .hljs-string { + color: #c41a16 +} +.hljs-regexp, +.hljs-link { + color: #0E0EFF +} +.hljs-title, +.hljs-symbol, +.hljs-bullet, +.hljs-number { + color: #1c00cf +} +.hljs-section, +.hljs-meta { + color: #643820 +} +.hljs-title.class_, +.hljs-class .hljs-title, +.hljs-type, +.hljs-built_in, +.hljs-params { + color: #5c2699 +} +.hljs-attr { + color: #836C28 +} +.hljs-subst { + color: #000 +} +.hljs-formula { + background-color: #eee; + font-style: italic +} +.hljs-addition { + background-color: #baeeba +} +.hljs-deletion { + background-color: #ffc8bd +} +.hljs-selector-id, +.hljs-selector-class { + color: #9b703f +} +.hljs-doctag, +.hljs-strong { + font-weight: bold +} +.hljs-emphasis { + font-style: italic +} +`; \ No newline at end of file diff --git a/src/default-theme.ts b/src/default-theme.ts new file mode 100644 index 0000000..2bed06d --- /dev/null +++ b/src/default-theme.ts @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +const css = ` +/* =========================================================== */ +/* Obsidian的默认样式 */ +/* =========================================================== */ +.note-to-mp { + padding: 0; + user-select: text; + -webkit-user-select: text; + color: #222222; + font-size: 16px; +} + +.note-to-mp:last-child { + margin-bottom: 0; +} + +.note-to-mp .fancybox-img { + border: none; +} + +.note-to-mp .fancybox-img:hover { + opacity: none; + border: none; +} + +/* +================================= +Heading +================================== +*/ +.note-to-mp h1 { + color: #222; + font-weight: 700; + font-size: 1.802em; + line-height: 1.2; + margin-block-start: 1em; + margin-block-end: 0; +} + +.note-to-mp h2 { + color: #222; + font-weight: 600; + font-size: 1.602em; + line-height: 1.2; + margin-block-start: 1em; + margin-block-end: 0; +} + +.note-to-mp h3 { + color: #222; + font-weight: 600; + font-size: 1.424em; + line-height: 1.3; + margin-block-start: 1em; + margin-block-end: 0; +} + +.note-to-mp h4 { + color: #222; + font-weight: 600; + font-size: 1.266em; + line-height: 1.4; + margin-block-start: 1em; + margin-block-end: 0; +} + +.note-to-mp h5 { + color: #222; + margin-block-start: 1em; + margin-block-end: 0; +} + +.note-to-mp h6 { + color: #222; + margin-block-start: 1em; + margin-block-end: 0; +} + +/* +================================= +Horizontal Rules +================================== + */ +.note-to-mp hr { + border-color: #e0e0e0; + margin-top: 3em; + margin-bottom: 3em; +} + +/* +================================= +Paragraphs +================================== + */ +.note-to-mp p { + line-height: 1.6em; + margin: 1em 0; +} + +/* +================================= +Emphasis +================================== + */ +.note-to-mp strong { + color: #222222; + font-weight: 600; +} + +.note-to-mp em { + color: inherit; + font-style: italic; +} + +.note-to-mp s { + color: inherit; +} + +/* +================================= + Blockquotes +================================== + */ +.note-to-mp blockquote { + font-size: 1rem; + display: block; + margin: 2em 0; + padding: 0em 0.8em 0em 0.8em; + position: relative; + color: inherit; + border-left: 0.15rem solid #7852ee; +} + +.note-to-mp blockquote blockquote { + margin: 0 0; +} + +.note-to-mp blockquote p { + margin: 0; +} + +.note-to-mp blockquote footer strong { + margin-right: 0.5em; +} + +/* +================================= +List +================================== +*/ +.note-to-mp ul { + margin: 0; + margin-top: 1.25em; + margin-bottom: 1.25em; + line-height: 1.6em; +} + +.note-to-mp ul>li::marker { + color: #ababab; + /* font-size: 1.5em; */ +} + +.note-to-mp li>p { + margin: 0; +} + +.note-to-mp ol { + margin: 0; + padding: 0; + margin-top: 1.25em; + margin-bottom: 0em; + list-style-type: decimal; + line-height: 1.6em; +} + +.note-to-mp ol>li { + position: relative; + padding-left: 0.1em; + margin-left: 2em; +} + +/* +================================= +Link +================================== +*/ +.note-to-mp a { + color: #7852ee; + text-decoration: none; + font-weight: 500; + text-decoration: none; + border-bottom: 1px solid #7852ee; + transition: border 0.3s ease-in-out; +} + +.note-to-mp a:hover { + color: #7952eebb; + border-bottom: 1px solid #7952eebb; +} + +/* +================================= +Table +================================== +*/ +.note-to-mp table { + width: 100%; + table-layout: auto; + text-align: left; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; + border-collapse: collapse; + border-color: inherit; + text-indent: 0; +} + +.note-to-mp table thead { + color: #000; + font-weight: 600; + border: #e0e0e0 1px solid; +} + +.note-to-mp table thead th { + vertical-align: bottom; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; + border: #e0e0e0 1px solid; +} + +.note-to-mp table thead th:first-child { + padding-left: 0.5em; +} + +.note-to-mp table thead th:last-child { + padding-right: 0.5em; +} + +.note-to-mp table tbody tr { + border-style: solid; + border: #e0e0e0 1px solid; +} + +.note-to-mp table tbody tr:last-child { + border-bottom-width: 0; +} + +.note-to-mp table tbody td { + vertical-align: top; + padding-top: 0.5714286em; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; + border: #e0e0e0 1px solid; +} + +.note-to-mp table tbody td:first-child { + padding-left: 0; +} + +.note-to-mp table tbody td:last-child { + padding-right: 0; +} + +/* +================================= +Images +================================== +*/ +.note-to-mp img { + margin: 2em auto; +} + +.note-to-mp .footnotes hr { + margin-top: 4em; + margin-bottom: 0.5em; +} + +/* +================================= +Code +================================== +*/ +.note-to-mp .code-section { + display: flex; + border: rgb(240, 240, 240) 1px solid; + line-height: 26px; + font-size: 14px; + margin: 1em 0; + padding: 0.875em; + box-sizing: border-box; +} + +.note-to-mp .code-section ul { + width: fit-content; + margin-block-start: 0; + margin-block-end: 0; + flex-shrink: 0; + height: 100%; + padding: 0; + line-height: 26px; + list-style-type: none; + backgroud: transparent !important; +} + +.note-to-mp .code-section ul>li { + text-align: right; +} + +.note-to-mp .code-section pre { + margin-block-start: 0; + margin-block-end: 0; + white-space: normal; + overflow: auto; + padding: 0 0 0 0.875em; +} + +.note-to-mp .code-section code { + display: flex; + text-wrap: nowrap; + font-family: Consolas,Courier,monospace; +} +` + +export default {name: '默认', className: 'obsidian-light', desc: '默认主题', author: 'SunBooshi', css:css}; \ No newline at end of file diff --git a/src/doc-modal.ts b/src/doc-modal.ts new file mode 100644 index 0000000..253ce7c --- /dev/null +++ b/src/doc-modal.ts @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { App, Modal, sanitizeHTMLToDom } from "obsidian"; + +export class DocModal extends Modal { + url: string = ''; + title: string = '提示'; + content: string = ''; + + constructor(app: App, title: string = "提示", content: string = "", url: string = "") { + super(app); + this.title = title; + this.content = content; + this.url = url; + } + + onOpen() { + let { contentEl, modalEl } = this; + modalEl.style.width = '640px'; + modalEl.style.height = '720px'; + contentEl.style.display = 'flex'; + contentEl.style.flexDirection = 'column'; + + const titleEl = contentEl.createEl('h2', { text: this.title }); + titleEl.style.marginTop = '0.5em'; + const content = contentEl.createEl('div'); + content.setAttr('style', 'margin-bottom:1em;-webkit-user-select: text; user-select: text;'); + content.appendChild(sanitizeHTMLToDom(this.content)); + + const iframe = contentEl.createEl('iframe', { + attr: { + src: this.url, + width: '100%', + allow: 'clipboard-read; clipboard-write', + }, + }); + + iframe.style.flex = '1'; + } + + onClose() { + + let { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/expert-settings.ts b/src/expert-settings.ts new file mode 100644 index 0000000..eec4549 --- /dev/null +++ b/src/expert-settings.ts @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { parseYaml } from "obsidian"; + +export interface ExpertSettings { + render?: { + h1?: string | number | object; + h2?: string | number | object; + h3?: string | number | object; + code?: number; + callout?: object | undefined; + }, + frontmatter: { + title: string; + author: string; + digest: string; + content_source_url: string; + cover: string; + thumb_media_id: string + need_open_comment: string; + only_fans_can_comment: string; + appid: string; + theme: string; + highlight: string; + crop: string; + } +} + +export const defaultExpertSettings: ExpertSettings = { + render: undefined, + frontmatter: { + title: '标题', + author: '作者', + digest: '摘要', + content_source_url: '原文地址', + cover: '封面', + thumb_media_id: '封面素材ID', + need_open_comment: '打开评论', + only_fans_can_comment: '仅粉丝可评论', + appid: '公众号', + theme: '样式', + highlight: '代码高亮', + crop: '封面裁剪', + } +}; + +export function expertSettingsFromString(content: string): ExpertSettings { + content = content.replace(/```yaml/gi, '').replace(/```/g, ''); + let parsed = parseYaml(content) as Partial; + if (!parsed || typeof parsed !== 'object') { + parsed = {}; + } + return { + render: parsed.render, + frontmatter: { + ...defaultExpertSettings.frontmatter, + ...(parsed.frontmatter || {}) + } + }; +} diff --git a/src/gallery/index.ts b/src/gallery/index.ts new file mode 100644 index 0000000..c781eb3 --- /dev/null +++ b/src/gallery/index.ts @@ -0,0 +1,47 @@ +// [note-to-mp 重构] Gallery 模块 +import { App } from 'obsidian'; + +export interface GalleryTransformResult { + content: string; + replaced: boolean; +} + +// 单行 self-closing 形式: {{}}{{}} +const GALLERY_INLINE_RE = /{{}}\s*{{}}/g; +// 块级形式 +const GALLERY_BLOCK_RE = /{{}}([\s\S]*?){{<\/gallery>}}/g; +const FIGURE_RE = /{{]*>}}/g; + +export function transformGalleryShortcodes(raw: string): GalleryTransformResult { + let replaced = false; + // 处理块级 + raw = raw.replace(GALLERY_BLOCK_RE, (_m, inner) => { + const imgs: string[] = []; + let fm: RegExpExecArray | null; + while ((fm = FIGURE_RE.exec(inner)) !== null) { + const src = fm[1]; + const base = src.split(/[?#]/)[0].split('/').pop(); + if (base) imgs.push(`![[${base}]]`); + } + if (imgs.length === 0) return _m; // 保留原文本 + replaced = true; + return imgs.join('\n') + '\n'; + }); + + // 处理单行自闭合形式 + raw = raw.replace(GALLERY_INLINE_RE, (_m, dir, figcaption) => { + replaced = true; + const comment = figcaption ? `\n` : ''; + // 暂不实际列目录;由后续 selectGalleryImages 扩展 + return comment + ``; + }); + + return { content: raw, replaced }; +} + +// 占位:真实实现可遍历 vault 目录 +export async function selectGalleryImages(app: App, dir: string, options?: { limit?: number }): Promise { + // TODO: 遍历 app.vault.getAbstractFileByPath(dir) + // 返回文件名数组(不含路径) + return []; +} diff --git a/src/image/index.ts b/src/image/index.ts new file mode 100644 index 0000000..aedf3c9 --- /dev/null +++ b/src/image/index.ts @@ -0,0 +1,64 @@ +// [note-to-mp 重构] 图片处理模块 +// 负责统一解析 wikilink 与 markdown 图片,并提供集中管理 + +export interface LocalImage { + original: string; // 原始匹配串(包括语法标记) + basename: string; // 文件基本名(不含路径) + alt?: string; // alt 描述(若来自 markdown 语法) + sourceType: 'wikilink' | 'markdown'; + index: number; // 在原文中的出现顺序 +} + +export const LocalFileRegex = /^(?:!\[\[(.*?)\]\]|!\[[^\]]*\]\(([^\n\r\)]+)\))/; + +export class LocalImageManager { + private images: LocalImage[] = []; + private byBasename: Map = new Map(); + + add(image: LocalImage) { + this.images.push(image); + const list = this.byBasename.get(image.basename) || []; + list.push(image); + this.byBasename.set(image.basename, list); + } + + all(): LocalImage[] { return this.images.slice(); } + + first(): LocalImage | undefined { return this.images[0]; } + + findByBasename(name: string): LocalImage | undefined { + const list = this.byBasename.get(name); + return list && list[0]; + } + + clear() { this.images = []; this.byBasename.clear(); } +} + +export function parseImagesFromMarkdown(markdown: string): LocalImage[] { + // 扫描整篇,统一抽取,不做替换 + const result: LocalImage[] = []; + const wikilinkRe = /!\[\[(.+?)\]\]/g; // 非贪婪 + const mdImgRe = /!\[([^\]]*)\]\(([^\n\r\)]+)\)/g; + let index = 0; + + let m: RegExpExecArray | null; + while ((m = wikilinkRe.exec(markdown)) !== null) { + const full = m[0]; + const inner = m[1].trim(); + const basename = inner.split('/').pop() || inner; + result.push({ original: full, basename, sourceType: 'wikilink', index: index++ }); + } + while ((m = mdImgRe.exec(markdown)) !== null) { + const full = m[0]; + const alt = m[1].trim(); + const link = m[2].trim(); + const basename = link.split(/[?#]/)[0].split('/').pop() || link; + result.push({ original: full, basename, alt, sourceType: 'markdown', index: index++ }); + } + + // 按出现顺序(两个正则独立扫描会破坏顺序,重新排序 by 原始位置) + result.sort((a, b) => markdown.indexOf(a.original) - markdown.indexOf(b.original)); + // 重排 index + result.forEach((r, i) => r.index = i); + return result; +} diff --git a/src/imagelib.ts b/src/imagelib.ts new file mode 100644 index 0000000..892432a --- /dev/null +++ b/src/imagelib.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { getBlobArrayBuffer } from "obsidian"; +import { wxUploadImage } from "./weixin-api"; +import { NMPSettings } from "./settings"; +import { IsWasmReady, LoadWasm } from "./wasm/wasm"; +import AssetsManager from "./assets"; + +declare function GoWebpToJPG(data: Uint8Array): Uint8Array; +declare function GoWebpToPNG(data: Uint8Array): Uint8Array; +declare function GoAddWatermark(img: Uint8Array, watermark: Uint8Array): Uint8Array; + +export function IsImageLibReady() { + return IsWasmReady(); +} + +export async function PrepareImageLib() { + await LoadWasm(); +} + +export function WebpToJPG(data: ArrayBuffer): ArrayBuffer { + return GoWebpToJPG(new Uint8Array(data)); +} + +export function WebpToPNG(data: ArrayBuffer): ArrayBuffer { + return GoWebpToPNG(new Uint8Array(data)); +} + +export function AddWatermark(img: ArrayBuffer, watermark: ArrayBuffer): ArrayBuffer { + return GoAddWatermark(new Uint8Array(img), new Uint8Array(watermark)); +} + +export async function UploadImageToWx(data: Blob, filename: string, token: string, type?: string) { + if (!IsImageLibReady()) { + await PrepareImageLib(); + } + + const watermark = NMPSettings.getInstance().watermark; + if (watermark != null && watermark != '') { + const watermarkData = await AssetsManager.getInstance().readFileBinary(watermark); + if (watermarkData == null) { + throw new Error('水印图片不存在: ' + watermark); + } + const watermarkImg = AddWatermark(await data.arrayBuffer(), watermarkData); + data = new Blob([watermarkImg], { type: data.type }); + } + return await wxUploadImage(data, filename, token, type); +} \ No newline at end of file diff --git a/src/inline-css.ts b/src/inline-css.ts new file mode 100644 index 0000000..21a7541 --- /dev/null +++ b/src/inline-css.ts @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// 需要渲染进inline style的css样式 +export default ` +/* --------------------------------------- */ +/* callout */ +/* --------------------------------------- */ +section .note-callout { + border: none; + padding: 1em 1em 1em 1.5em; + display: flex; + flex-direction: column; + margin: 1em 0; + border-radius: 4px; +} + +section .note-callout-title-wrap { + display: flex; + flex-direction: row; + align-items: center; + font-size: 1em; + font-weight: 600; +} + +.note-callout-icon { + display: inline-block; + width: 18px; + height: 18px; +} + +.note-callout-icon svg { + width: 100%; + height: 100%; +} + +section .note-callout-title { + margin-left: 0.25em; +} + +section .note-callout-content { + color: rgb(34,34,34); +} + +/* note info todo */ +section .note-callout-note { + color: rgb(8, 109, 221); + background-color: rgba(8, 109, 221, 0.1); +} +/* abstract tip hint */ +section .note-callout-abstract { + color: rgb(0, 191, 188); + background-color: rgba(0, 191, 188, 0.1); +} +section .note-callout-success { + color: rgb(8, 185, 78); + background-color: rgba(8, 185, 78, 0.1); +} +/* question help, faq, warning, caution, attention */ +section .note-callout-question { + color: rgb(236, 117, 0); + background-color: rgba(236, 117, 0, 0.1); +} +/* failure, fail, missing, danger, error, bug */ +section .note-callout-failure { + color: rgb(233, 49, 71); + background-color: rgba(233, 49, 71, 0.1); +} +section .note-callout-example { + color: rgb(120, 82, 238); + background-color: rgba(120, 82, 238, 0.1); +} +section .note-callout-quote { + color: rgb(158, 158, 158); + background-color: rgba(158, 158, 158, 0.1); +} +/* custom icon callout */ +section .note-callout-custom { + color: rgb(8, 109, 221); + background-color: rgba(8, 109, 221, 0.1); +} + +/* --------------------------------------- */ +/* math */ +/* --------------------------------------- */ +.block-math-svg { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; + margin:20px 0px; + max-width: 300% !important; +} + +.block-math-section { + text-align: center; + overflow: auto; +} + +/* --------------------------------------- */ +/* 高亮 */ +/* --------------------------------------- */ +.note-highlight { + background-color: rgba(255,208,0, 0.4); +} + +/* --------------------------------------- */ +/* 列表需要强制设置样式*/ +/* --------------------------------------- */ +ul { + list-style-type: disc; +} + +.note-svg-icon { + min-width: 24px; + height: 24px; + display: inline-block; +} + +.note-svg-icon svg { + width: 100%; + height: 100%; +} + +.note-embed-excalidraw-left { + display: flex; + flex-direction: row; + width: 100%; +} + +.note-embed-excalidraw-center { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; +} + +.note-embed-excalidraw-right { + display: flex; + flex-direction: row; + justify-content: flex-end; + width: 100%; +} + +.note-embed-excalidraw { + display: inline-block; +} + +.note-embed-excalidraw p { + line-height: 0 !important; + margin: 0 !important; +} + +/* +.note-embed-excalidraw svg { + width: 100%; + height: 100%; +} +*/ + +.note-embed-svg-left { + display: flex; + flex-direction: row; + justify-content: flex-start; + width: 100%; +} + +.note-embed-svg-center { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; +} + +.note-embed-svg-right { + display: flex; + flex-direction: row; + justify-content: flex-end; + width: 100%; +} + +.note-embed-svg svg { + width: 100%; + height: 100%; +} + +`; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5a38bf5 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Plugin, WorkspaceLeaf, App, PluginManifest, Menu, Notice, TAbstractFile, TFile, TFolder } from 'obsidian'; +import { NotePreview, VIEW_TYPE_NOTE_PREVIEW } from './note-preview'; +import { NMPSettings } from './settings'; +import { NoteToMpSettingTab } from './setting-tab'; +import AssetsManager from './assets'; +import { setVersion, uevent } from './utils'; +import { WidgetsModal } from './widgets-modal'; + + +export default class NoteToMpPlugin extends Plugin { + settings: NMPSettings; + assetsManager: AssetsManager; + constructor(app: App, manifest: PluginManifest) { + super(app, manifest); + AssetsManager.setup(app, manifest); + this.assetsManager = AssetsManager.getInstance(); + } + + async loadResource() { + await this.loadSettings(); + await this.assetsManager.loadAssets(); + } + + async onload() { + console.log('Loading NoteToMP'); + setVersion(this.manifest.version); + uevent('load'); + this.app.workspace.onLayoutReady(()=>{ + this.loadResource(); + }) + + this.registerView( + VIEW_TYPE_NOTE_PREVIEW, + (leaf) => new NotePreview(leaf, this) + ); + + const ribbonIconEl = this.addRibbonIcon('clipboard-paste', '复制到公众号', (evt: MouseEvent) => { + this.activateView(); + }); + ribbonIconEl.addClass('note-to-mp-plugin-ribbon-class'); + + this.addCommand({ + id: 'note-to-mp-preview', + name: '复制到公众号', + callback: () => { + this.activateView(); + } + }); + + this.addSettingTab(new NoteToMpSettingTab(this.app, this)); + + this.addCommand({ + id: 'note-to-mp-widget', + name: '插入样式小部件', + callback: () => { + new WidgetsModal(this.app).open(); + } + }); + + this.addCommand({ + id: 'note-to-mp-pub', + name: '发布公众号文章', + callback: async () => { + await this.activateView(); + this.getNotePreview()?.postArticle(); + } + }); + + // 监听右键菜单 + this.registerEvent( + this.app.workspace.on('file-menu', (menu, file) => { + menu.addItem((item) => { + item + .setTitle('发布到公众号') + .setIcon('lucide-send') + .onClick(async () => { + if (file instanceof TFile) { + if (file.extension.toLowerCase() !== 'md') { + new Notice('只能发布 Markdown 文件'); + return; + } + await this.activateView(); + await this.getNotePreview()?.renderMarkdown(file); + await this.getNotePreview()?.postArticle(); + } else if (file instanceof TFolder) { + await this.activateView(); + await this.getNotePreview()?.batchPost(file); + } + }); + }); + }) + ); + } + + onunload() { + + } + + async loadSettings() { + NMPSettings.loadSettings(await this.loadData()); + } + + async saveSettings() { + await this.saveData(NMPSettings.allSettings()); + } + + async activateView() { + const { workspace } = this.app; + + let leaf: WorkspaceLeaf | null = null; + const leaves = workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW); + + if (leaves.length > 0) { + leaf = leaves[0]; + } else { + leaf = workspace.getRightLeaf(false); + await leaf?.setViewState({ type: VIEW_TYPE_NOTE_PREVIEW, active: false }); + } + + if (leaf) workspace.revealLeaf(leaf); + } + + getNotePreview(): NotePreview | null { + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW); + if (leaves.length > 0) { + const leaf = leaves[0]; + return leaf.view as NotePreview; + } + return null; + } +} diff --git a/src/markdown/blockquote.ts b/src/markdown/blockquote.ts new file mode 100644 index 0000000..3964857 --- /dev/null +++ b/src/markdown/blockquote.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension, MDRendererCallback } from "./extension"; +import { NMPSettings } from "src/settings"; +import { App, Vault } from "obsidian"; +import AssetsManager from "../assets"; +import { CalloutRenderer } from "./callouts"; +import { WidgetBox } from "./widget-box"; + +export class Blockquote extends Extension { + callout: CalloutRenderer; + box: WidgetBox; + + constructor(app: App, settings: NMPSettings, assetsManager: AssetsManager, callback: MDRendererCallback) { + super(app, settings, assetsManager, callback); + this.callout = new CalloutRenderer(app, settings, assetsManager, callback); + if (settings.isAuthKeyVaild()) { + this.box = new WidgetBox(app, settings, assetsManager, callback); + } + } + + async prepare() { + if (!this.marked) { + console.error("marked is not ready"); + return; + } + if (this.callout) this.callout.marked = this.marked; + if (this.box) this.box.marked = this.marked; + return; + } + + async renderer(token: Tokens.Blockquote) { + if (this.callout.matched(token.text)) { + return await this.callout.renderer(token); + } + + if (this.box && this.box.matched(token.text)) { + return await this.box.renderer(token); + } + + const body = this.marked.parser(token.tokens); + return `
${body}
`; + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type !== 'blockquote') { + return; + } + token.html = await this.renderer(token as Tokens.Blockquote); + }, + extensions: [{ + name: 'blockquote', + level: 'block', + renderer: (token: Tokens.Generic) => { + return token.html; + }, + }] + } + } +} \ No newline at end of file diff --git a/src/markdown/callouts.ts b/src/markdown/callouts.ts new file mode 100644 index 0000000..74fd994 --- /dev/null +++ b/src/markdown/callouts.ts @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension} from "marked"; +import { Extension } from "./extension"; +import AssetsManager from "src/assets"; +import { wxWidget } from "src/weixin-api"; + +const icon_note = `` +const icon_abstract = `` +const icon_info = `` +const icon_todo = `` +const icon_tip = `` +const icon_success = `` +const icon_question = `` +const icon_warning = `` +const icon_failure = `` +const icon_danger = `` +const icon_bug = `` +const icon_example = `` +const icon_quote = `` +/* +note, +abstract, summary, tldr +info +todo +tip +hint, important +success, check, done +question, help, faq +warning, caution, attention +failure, fail, missing +danger, error +bug +example +quote, cite +*/ + +type CalloutInfo = {icon: string, style: string} + +const CalloutTypes = new Map(Object.entries({ + note: { + icon: icon_note, + style: 'note-callout-note', + }, + abstract: { + icon: icon_abstract, + style: 'note-callout-abstract', + }, + summary: { + icon: icon_abstract, + style: 'note-callout-abstract', + }, + tldr: { + icon: icon_abstract, + style: 'note-callout-abstract', + }, + info: { + icon: icon_info, + style: 'note-callout-note', + }, + todo: { + icon: icon_todo, + style: 'note-callout-note', + }, + tip: { + icon: icon_tip, + style: 'note-callout-abstract', + }, + hint: { + icon: icon_tip, + style: 'note-callout-abstract', + }, + important: { + icon: icon_tip, + style: 'note-callout-abstract', + }, + success: { + icon: icon_success, + style: 'note-callout-success', + }, + check: { + icon: icon_success, + style: 'note-callout-success', + }, + done: { + icon: icon_success, + style: 'note-callout-success', + }, + question: { + icon: icon_question, + style: 'note-callout-question', + }, + help: { + icon: icon_question, + style: 'note-callout-question', + }, + faq: { + icon: icon_question, + style: 'note-callout-question', + }, + warning: { + icon: icon_warning, + style: 'note-callout-question', + }, + caution: { + icon: icon_warning, + style: 'note-callout-question', + }, + attention: { + icon: icon_warning, + style: 'note-callout-question', + }, + failure: { + icon: icon_failure, + style: 'note-callout-failure', + }, + fail: { + icon: icon_failure, + style: 'note-callout-failure', + }, + missing: { + icon: icon_failure, + style: 'note-callout-failure', + }, + danger: { + icon: icon_danger, + style: 'note-callout-failure', + }, + error: { + icon: icon_danger, + style: 'note-callout-failure', + }, + bug: { + icon: icon_bug, + style: 'note-callout-failure', + }, + example: { + icon: icon_example, + style: 'note-callout-example', + }, + quote: { + icon: icon_quote, + style: 'note-callout-quote', + }, + cite: { + icon: icon_quote, + style: 'note-callout-quote', + } +})); + +function GetCallout(type: string) { + return CalloutTypes.get(type); +}; + +function matchCallouts(text:string) { + const regex = /\[\!(.*?)\]/g; + let m; + if( m = regex.exec(text)) { + return m[1]; + } + return ""; +} + +function GetCalloutTitle(callout:string, text:string) { + let title = callout.charAt(0).toUpperCase() + callout.slice(1).toLowerCase(); + let start = text.indexOf(']') + 1; + if (text.indexOf(']-') > 0 || text.indexOf(']+') > 0) { + start = start + 1; + } + let end = text.indexOf('\n'); + if (end === -1) end = text.length; + if (start >= end) return title; + const customTitle = text.slice(start, end).trim(); + if (customTitle !== '') { + title = customTitle; + } + return title; +} + +export class CalloutRenderer extends Extension { + matched(text: string) { + return matchCallouts(text) != ''; + } + + async renderer(token: Tokens.Blockquote) { + let callout = matchCallouts(token.text); + if (callout == '') { + const body = this.marked.parser(token.tokens); + return `
${body}
`;; + } + + const title = GetCalloutTitle(callout, token.text); + const index = token.text.indexOf('\n'); + let body = ''; + if (index > 0) { + token.text = token.text.slice(index+1) + body = await this.marked.parse(token.text); + } + + const setting = AssetsManager.getInstance().expertSettings.render?.callout as { [key: string]: any }; + if (setting && callout.toLocaleLowerCase() in setting) { + const authkey = this.settings.authKey; + const widget = setting[callout.toLocaleLowerCase()]; + if (typeof widget === 'number') { + return await wxWidget(authkey, JSON.stringify({ + id: `${widget}`, + title, + content: body, + })); + } + if (typeof widget === 'object') { + const {id, style} = widget; + return await wxWidget(authkey, JSON.stringify({ + id: `${id}`, + title, + style: style || {}, + content: body, + })); + } + } + + let info = GetCallout(callout.toLowerCase()); + if (info == null) { + const svg = await this.assetsManager.loadIcon(callout); + if (svg) { + info = {icon: svg, style: 'note-callout-custom'} + } + else { + info = GetCallout('note'); + } + } + + + return `
${info?.icon}${title}
${body}
`; + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type !== 'blockquote') { + return; + } + token.html = await this.renderer(token as Tokens.Blockquote); + }, + extensions:[{ + name: 'blockquote', + level: 'block', + renderer: (token: Tokens.Generic)=> { + return token.html; + }, + }] + } + } +} \ No newline at end of file diff --git a/src/markdown/code.ts b/src/markdown/code.ts new file mode 100644 index 0000000..d03d4de --- /dev/null +++ b/src/markdown/code.ts @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Notice } from "obsidian"; +import { MarkedExtension, Tokens } from "marked"; +import hljs from "highlight.js"; +import { MathRendererQueue } from "./math"; +import { Extension } from "./extension"; +import { UploadImageToWx } from "../imagelib"; +import AssetsManager from "src/assets"; +import { wxWidget } from "src/weixin-api"; + +export class CardDataManager { + private cardData: Map; + private static instance: CardDataManager; + + private constructor() { + this.cardData = new Map(); + } + + // 静态方法,用于获取实例 + public static getInstance(): CardDataManager { + if (!CardDataManager.instance) { + CardDataManager.instance = new CardDataManager(); + } + return CardDataManager.instance; + } + + public setCardData(id: string, cardData: string) { + this.cardData.set(id, cardData); + } + + public cleanup() { + this.cardData.clear(); + } + + public restoreCard(html: string) { + for (const [key, value] of this.cardData.entries()) { + const exp = `]*\\sdata-id="${key}"[^>]*>(.*?)<\\/section>`; + const regex = new RegExp(exp, 'gs'); + if (!regex.test(html)) { + console.warn('没有公众号信息:', key); + continue; + } + html = html.replace(regex, value); + } + return html; + } +} + +const MermaidSectionClassName = 'note-mermaid'; +const MermaidImgClassName = 'note-mermaid-img'; + +export class CodeRenderer extends Extension { + showLineNumber: boolean; + mermaidIndex: number; + + async prepare() { + this.mermaidIndex = 0; + } + + static srcToBlob(src: string) { + const base64 = src.split(',')[1]; + const byteCharacters = atob(base64); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + return new Blob([byteArray], { type: 'image/png' }); + } + + static async uploadMermaidImages(root: HTMLElement, token: string) { + const imgs = root.querySelectorAll('.' + MermaidImgClassName); + for (let img of imgs) { + const src = img.getAttribute('src'); + if (!src) continue; + if (src.startsWith('http')) continue; + const blob = CodeRenderer.srcToBlob(img.getAttribute('src')!); + const name = img.id + '.png'; + const res = await UploadImageToWx(blob, name, token); + if (res.errcode != 0) { + const msg = `上传图片失败: ${res.errcode} ${res.errmsg}`; + new Notice(msg); + console.error(msg); + continue; + } + const url = res.url; + img.setAttribute('src', url); + } + } + + replaceSpaces(text: string) { + let result = ''; + let inTag = false; + for (let char of text) { + if (char === '<') { + inTag = true; + result += char; + continue; + } else if (char === '>') { + inTag = false; + result += char; + continue; + } + if (inTag) { + result += char; + } else { + if (char === ' ') { + result += ' '; + } else if (char === '\t') { + result += '    '; + } else { + result += char; + } + } + } + return result; + } + + async codeRenderer(code: string, infostring: string | undefined) { + const lang = (infostring || '').match(/^\S*/)?.[0]; + code = code.replace(/\n$/, ''); + + try { + if (lang && hljs.getLanguage(lang)) { + code = hljs.highlight(code, { language: lang }).value; + } + else { + code = hljs.highlightAuto(code).value; + } + } catch (err) { + console.error(err); + } + + code = this.replaceSpaces(code); + const lines = code.split('\n'); + let body = ''; + let liItems = ''; + for (let line in lines) { + let text = lines[line]; + if (text.length === 0) { + text = '
' + } + body = body + '' + text + ''; + liItems = liItems + `
  • ${parseInt(line)+1}
  • `; + } + + let codeSection = '
    '; + if (this.settings.lineNumber) { + codeSection = codeSection + '
      ' + + liItems + + '
    '; + } + + let html = ''; + if (lang) { + html = codeSection + '
    '
    +			+ body
    +			+ '
    '; + } + else { + html = codeSection + '
    '
    +				+ body
    +				+ '
    '; + } + + if (!this.settings.isAuthKeyVaild()) { + return html; + } + + const settings = AssetsManager.getInstance().expertSettings; + const id = settings.render?.code; + if (id && typeof id === 'number') { + const params = JSON.stringify({ + id: `${id}`, + content: html, + }); + html = await wxWidget(this.settings.authKey, params); + } + return html; + } + + static getMathType(lang: string | null) { + if (!lang) return null; + let l = lang.toLowerCase(); + l = l.trim(); + if (l === 'am' || l === 'asciimath') return 'asciimath'; + if (l === 'latex' || l === 'tex') return 'latex'; + return null; + } + + parseCard(htmlString: string) { + const id = /data-id="([^"]+)"/; + const headimgRegex = /data-headimg="([^"]+)"/; + const nicknameRegex = /data-nickname="([^"]+)"/; + const signatureRegex = /data-signature="([^"]+)"/; + + const idMatch = htmlString.match(id); + const headimgMatch = htmlString.match(headimgRegex); + const nicknameMatch = htmlString.match(nicknameRegex); + const signatureMatch = htmlString.match(signatureRegex); + + return { + id: idMatch ? idMatch[1] : '', + headimg: headimgMatch ? headimgMatch[1] : '', + nickname: nicknameMatch ? nicknameMatch[1] : '公众号名称', + signature: signatureMatch ? signatureMatch[1] : '公众号介绍' + }; + } + + renderCard(token: Tokens.Code) { + const { id, headimg, nickname, signature } = this.parseCard(token.text); + if (id === '') { + return '公众号卡片数据错误,没有id'; + } + CardDataManager.getInstance().setCardData(id, token.text); + return `
    ${nickname}
    ${signature}
    公众号
    `; + } + + renderMermaid(token: Tokens.Code) { + try { + const meraidIndex = this.mermaidIndex; + const containerId = `mermaid-${meraidIndex}`; + this.callback.cacheElement('mermaid', containerId, token.raw); + this.mermaidIndex += 1; + return `
    `; + } catch (error) { + console.error(error.message); + return 'mermaid渲染失败'; + } + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type !== 'code') return; + if (this.settings.isAuthKeyVaild()) { + const type = CodeRenderer.getMathType(token.lang ?? ''); + if (type) { + token.html = await MathRendererQueue.getInstance().render(token, false, type); + return; + } + if (token.lang && token.lang.trim().toLocaleLowerCase() == 'mermaid') { + token.html = this.renderMermaid(token as Tokens.Code); + return; + } + } + if (token.lang && token.lang.trim().toLocaleLowerCase() == 'mpcard') { + token.html = this.renderCard(token as Tokens.Code); + return; + } + token.html = await this.codeRenderer(token.text, token.lang); + }, + extensions: [{ + name: 'code', + level: 'block', + renderer: (token: Tokens.Generic) => { + return token.html; + }, + }] + } + } +} + diff --git a/src/markdown/commnet.ts b/src/markdown/commnet.ts new file mode 100644 index 0000000..a0a6e63 --- /dev/null +++ b/src/markdown/commnet.ts @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +const commentRegex = /^%%([\s\S]*?)%%/; + +export class Comment extends Extension { + markedExtension(): MarkedExtension { + return { + extensions: [{ + name: 'CommentInline', + level: 'inline', + start(src: string) { + let index; + let indexSrc = src; + + while (indexSrc) { + index = indexSrc.indexOf('%%'); + if (index === -1) return; + return index; + } + }, + tokenizer(src: string) { + const match = src.match(commentRegex); + if (match) { + return { + type: 'CommentInline', + raw: match[0], + text: match[1], + }; + } + }, + renderer(token: Tokens.Generic) { + return ''; + } + }, + { + name: 'CommentBlock', + level: 'block', + tokenizer(src: string) { + const match = src.match(commentRegex); + if (match) { + return { + type: 'CommentBlock', + raw: match[0], + text: match[1], + }; + } + }, + renderer(token: Tokens.Generic) { + return ''; + } + }, + ] + } + } +} \ No newline at end of file diff --git a/src/markdown/embed-block-mark.ts b/src/markdown/embed-block-mark.ts new file mode 100644 index 0000000..dfd8932 --- /dev/null +++ b/src/markdown/embed-block-mark.ts @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +const BlockMarkRegex = /^\^[0-9A-Za-z-]+$/; + +export class EmbedBlockMark extends Extension { + allLinks:string[] = []; + async prepare() { + this.allLinks = []; + } + + markedExtension(): MarkedExtension { + return { + extensions: [{ + name: 'EmbedBlockMark', + level: 'inline', + start(src: string) { + let index = src.indexOf('^'); + if (index === -1) { + return; + } + return index; + }, + tokenizer(src: string) { + const match = src.match(BlockMarkRegex); + if (match) { + return { + type: 'EmbedBlockMark', + raw: match[0], + text: match[0] + }; + } + }, + renderer: (token: Tokens.Generic) => { + return ` { + return '


    '.repeat(token.raw.length - 1); + }, + }] + } + } +} diff --git a/src/markdown/extension.ts b/src/markdown/extension.ts new file mode 100644 index 0000000..6e46126 --- /dev/null +++ b/src/markdown/extension.ts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { NMPSettings } from "src/settings"; +import { Marked, MarkedExtension } from "marked"; +import { App, Vault } from "obsidian"; +import AssetsManager from "../assets"; + +export interface MDRendererCallback { + settings: NMPSettings; + updateElementByID(id:string, html:string):void; // 改为异步渲染后已废弃 + cacheElement(category: string, id: string, data: string): void; +} + +export abstract class Extension { + app: App; + vault: Vault; + assetsManager: AssetsManager + settings: NMPSettings; + callback: MDRendererCallback; + marked: Marked; + + constructor(app: App, settings: NMPSettings, assetsManager: AssetsManager, callback: MDRendererCallback) { + this.app = app; + this.vault = app.vault; + this.settings = settings; + this.assetsManager = assetsManager; + this.callback = callback; + } + + async prepare() { return; } + async postprocess(html:string) { return html; } + async beforePublish() { } + async cleanup() { return; } + abstract markedExtension(): MarkedExtension +} \ No newline at end of file diff --git a/src/markdown/footnote.ts b/src/markdown/footnote.ts new file mode 100644 index 0000000..ee6d5ec --- /dev/null +++ b/src/markdown/footnote.ts @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +const refRule = /^\[\^([^\]]+)\]/; // 匹配 [^label] +const defRule = /^ *\[\^([^\]]+)\]:/; // 匹配 [^label]: + +export class FootnoteRenderer extends Extension { + allDefs: any[] = []; + defCounter = 0; + async prepare() { + this.allDefs = []; + this.defCounter = 0; + } + + async postprocess(html: string) { + if (this.allDefs.length == 0) { + return html; + } + + let body = ''; + for (const def of this.allDefs) { + const {label, content} = def; + const html = await this.marked.parse(content); + const id = `fn-${label}`; + body += `
  • ${html}
  • `; + } + return html + `

      ${body}
    `; + } + + markedExtension(): MarkedExtension { + return { + extensions: [ + { + name: 'FootnoteRef', + level: 'inline', + start(src) { + const index = src.indexOf('[^'); + return index > 0 ? index : -1; + }, + tokenizer: (src) => { + const match = src.match(refRule); + if (match) { + return { + type: 'FootnoteRef', + raw: match[0], + text: match[1], + }; + } + }, + renderer: (token: Tokens.Generic) => { + this.defCounter += 1; + const id = `fnref-${this.defCounter}`; + return `${this.defCounter}`; + } + }, + { + name: 'FootnoteDef', + level: 'block', + tokenizer: (src) => { + const match = src.match(defRule); + if (match) { + const label = match[1].trim(); + const end = src.indexOf('\n'); + const raw = end === -1 ? src: src.substring(0, end + 1); + const content = raw.substring(match[0].length); + this.allDefs.push({label, content}); + + return { + type: 'FootnoteDef', + raw: raw, + text: content, + }; + } + }, + renderer: (token: Tokens.Generic) => { + return ''; + } + } + ] + } + } +} \ No newline at end of file diff --git a/src/markdown/heading.ts b/src/markdown/heading.ts new file mode 100644 index 0000000..1ea3fba --- /dev/null +++ b/src/markdown/heading.ts @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; +import AssetsManager from "src/assets"; +import { ExpertSettings } from "src/expert-settings"; +import { wxWidget } from "src/weixin-api"; + +export class HeadingRenderer extends Extension { + index = [0, 0, 0, 0]; + expertSettings: ExpertSettings; + headingSettings: any[] + + async prepare() { + this.index = [0, 0, 0, 0]; + this.expertSettings = AssetsManager.getInstance().expertSettings; + this.headingSettings = [undefined, undefined, undefined, undefined]; + if (!this.expertSettings.render) { + return; + } + if (this.expertSettings.render.h1) { + this.headingSettings[1] = this.expertSettings.render.h1; + } + if (this.expertSettings.render.h2) { + this.headingSettings[2] = this.expertSettings.render.h2; + } + if (this.expertSettings.render.h3) { + this.headingSettings[3] = this.expertSettings.render.h3; + } + } + + async renderWithTemplate(token: Tokens.Heading, template: string) { + const content = await this.marked.parseInline(token.text); + return template.replace('{content}', content); + } + + async renderWithWidgetId(token: Tokens.Heading, widgetId: number) { + const authkey = this.settings.authKey; + const content = await this.marked.parseInline(token.text); + const params = JSON.stringify({ + id: `${widgetId}`, + title: content, + }); + return await wxWidget(authkey, params); + } + + async renderWithWidget(token: Tokens.Heading, widgetId: number, counter: boolean|undefined, len: number|undefined, style: object|undefined = undefined) { + const authkey = this.settings.authKey; + let title = token.text; + if (counter === undefined) { + counter = false; + } + if (len === undefined) { + len = 1; + } + if (style === undefined) { + style = new Map(); + } + if (counter) { + title = `${this.index[token.depth]}`; + if (title.length < len) { + title = title.padStart(len, '0'); + } + } + const content = await this.marked.parseInline(token.text); + const params = JSON.stringify({ + id: `${widgetId}`, + title, + style, + content: '

    ' + content + '

    ', + }); + return await wxWidget(authkey, params); + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type !== 'heading') { + return; + } + + const setting = this.headingSettings[token.depth]; + this.index[token.depth] += 1; + if (setting) { + if (typeof setting === 'string') { + token.html = await this.renderWithTemplate(token as Tokens.Heading, setting); + } + else if (typeof setting === 'number') { + token.html = await this.renderWithWidgetId(token as Tokens.Heading, setting); + } + else { + const { id, counter, len, style } = setting; + token.html = await this.renderWithWidget(token as Tokens.Heading, id, counter, len, style); + } + return; + } + + const body = await this.marked.parseInline(token.text); + token.html = `${body}`; + }, + extensions: [{ + name: 'heading', + level: 'block', + renderer: (token: Tokens.Generic) => { + return token.html; + }, + }] + } + } +} \ No newline at end of file diff --git a/src/markdown/icons.ts b/src/markdown/icons.ts new file mode 100644 index 0000000..7a66913 --- /dev/null +++ b/src/markdown/icons.ts @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +const iconsRegex = /^\[:(.*?):\]/; + +export class SVGIcon extends Extension { + isNumeric(str: string): boolean { + return !isNaN(Number(str)) && str.trim() !== ''; + } + + getSize(size: string) { + const items = size.split('x'); + let width, height; + if (items.length == 2) { + width = items[0]; + height = items[1]; + } + else { + width = items[0]; + height = items[0]; + } + width = this.isNumeric(width) ? width+'px' : width; + height = this.isNumeric(height) ? height+'px' : height; + return {width, height}; + } + + renderStyle(items: string[]) { + let size = ''; + let color = ''; + if (items.length == 3) { + size = items[1]; + color = items[2]; + } + else if (items.length == 2) { + if (items[1].startsWith('#')) { + color = items[1]; + } + else { + size = items[1]; + } + } + let style = ''; + if (size.length > 0) { + const {width, height} = this.getSize(size); + style += `width:${width};height:${height};`; + } + if (color.length > 0) { + style += `color:${color};`; + } + return style.length > 0 ? `style="${style}"` : ''; + } + + async render(text: string) { + const items = text.split('|'); + const name = items[0]; + const svg = await this.assetsManager.loadIcon(name); + const body = svg==='' ? '未找到图标' + name : svg; + const style = this.renderStyle(items); + return `${body}` + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type !== 'SVGIcon') { + return; + } + token.html = await this.render(token.text); + }, + extensions: [{ + name: 'SVGIcon', + level: 'inline', + start(src: string) { + let index; + let indexSrc = src; + + while (indexSrc) { + index = indexSrc.indexOf('[:'); + if (index === -1) return; + return index; + } + }, + tokenizer(src: string) { + const match = src.match(iconsRegex); + if (match) { + return { + type: 'SVGIcon', + raw: match[0], + text: match[1], + }; + } + }, + renderer(token: Tokens.Generic) { + return token.html; + } + }] + } + } +} \ No newline at end of file diff --git a/src/markdown/link.ts b/src/markdown/link.ts new file mode 100644 index 0000000..47ec24f --- /dev/null +++ b/src/markdown/link.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +export class LinkRenderer extends Extension { + allLinks:string[] = []; + async prepare() { + this.allLinks = []; + } + + async postprocess(html: string) { + if (this.settings.linkStyle !== 'footnote' + || this.allLinks.length == 0) { + return html; + } + + const links = this.allLinks.map((href, i) => { + return `
  • ${href} ↩
  • `; + }); + return `${html}
      ${links.join('')}
    `; + } + + markedExtension(): MarkedExtension { + return { + extensions: [{ + name: 'link', + level: 'inline', + renderer: (token: Tokens.Link) => { + if (token.href.startsWith('mailto:')) { + return token.text; + } + if (token.text.indexOf(token.href) === 0 + || (token.href.indexOf('https://mp.weixin.qq.com/mp') === 0) + || (token.href.indexOf('https://mp.weixin.qq.com/s') === 0)) { + return `${token.text}`; + } + this.allLinks.push(token.href); + if (this.settings.linkStyle == 'footnote') { + return `${token.text}[${this.allLinks.length}]`; + } + else { + return `${token.text}[${token.href}]`; + } + } + }] + } + } +} \ No newline at end of file diff --git a/src/markdown/local-file.ts b/src/markdown/local-file.ts new file mode 100644 index 0000000..ff8a44b --- /dev/null +++ b/src/markdown/local-file.ts @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Token, Tokens, MarkedExtension } from "marked"; +import { Notice, TAbstractFile, TFile, Vault, MarkdownView, requestUrl, Platform } from "obsidian"; +import { Extension } from "./extension"; +import { NMPSettings } from "../settings"; +import { IsImageLibReady, PrepareImageLib, WebpToJPG, UploadImageToWx } from "../imagelib"; + +declare module 'obsidian' { + interface Vault { + config: { + attachmentFolderPath: string; + newLinkFormat: string; + useMarkdownLinks: boolean; + }; + } +} + +const LocalFileRegex = /^!\[\[(.*?)\]\]/; + +interface ImageInfo { + resUrl: string; + filePath: string; + url: string | null; + media_id: string | null; +} + +export class LocalImageManager { + private images: Map; + private static instance: LocalImageManager; + + private constructor() { + this.images = new Map(); + } + + // 静态方法,用于获取实例 + public static getInstance(): LocalImageManager { + if (!LocalImageManager.instance) { + LocalImageManager.instance = new LocalImageManager(); + } + return LocalImageManager.instance; + } + + public setImage(path: string, info: ImageInfo): void { + if (!this.images.has(path)) { + this.images.set(path, info); + } + } + + isWebp(file: TFile | string): boolean { + if (file instanceof TFile) { + return file.extension.toLowerCase() === 'webp'; + } + const name = file.toLowerCase(); + return name.endsWith('.webp'); + } + + async uploadLocalImage(token: string, vault: Vault, type: string = '') { + const keys = this.images.keys(); + await PrepareImageLib(); + const result = []; + for (let key of keys) { + const value = this.images.get(key); + if (value == null) continue; + if (value.url != null) continue; + const file = vault.getFileByPath(value.filePath); + if (file == null) continue; + let fileData = await vault.readBinary(file); + let name = file.name; + if (this.isWebp(file)) { + if (IsImageLibReady()) { + fileData = WebpToJPG(fileData); + name = name.toLowerCase().replace('.webp', '.jpg'); + } + else { + console.error('wasm not ready for webp'); + } + } + + const res = await UploadImageToWx(new Blob([fileData]), name, token, type); + if (res.errcode != 0) { + const msg = `上传图片失败: ${res.errcode} ${res.errmsg}`; + new Notice(msg); + console.error(msg); + } + + value.url = res.url; + value.media_id = res.media_id; + result.push(res); + } + return result; + } + + checkImageExt(filename: string ): boolean { + const name = filename.toLowerCase(); + + if (name.endsWith('.jpg') + || name.endsWith('.jpeg') + || name.endsWith('.png') + || name.endsWith('.gif') + || name.endsWith('.bmp') + || name.endsWith('.tiff') + || name.endsWith('.svg') + || name.endsWith('.webp')) { + return true; + } + return false; + } + + getImageNameFromUrl(url: string, type: string): string { + try { + // 创建URL对象 + const urlObj = new URL(url); + // 获取pathname部分 + const pathname = urlObj.pathname; + // 获取最后一个/后的内容作为文件名 + let filename = pathname.split('/').pop() || ''; + filename = decodeURIComponent(filename); + if (!this.checkImageExt(filename)) { + filename = filename + this.getImageExt(type); + } + return filename; + } catch (e) { + // 如果URL解析失败,尝试简单的字符串处理 + const queryIndex = url.indexOf('?'); + if (queryIndex !== -1) { + url = url.substring(0, queryIndex); + } + return url.split('/').pop() || ''; + } + } + + getImageExtFromBlob(blob: Blob): string { + // MIME类型到文件扩展名的映射 + const mimeToExt: { [key: string]: string } = { + 'image/jpeg': '.jpg', + 'image/jpg': '.jpg', + 'image/png': '.png', + 'image/gif': '.gif', + 'image/bmp': '.bmp', + 'image/webp': '.webp', + 'image/svg+xml': '.svg', + 'image/tiff': '.tiff' + }; + + // 获取MIME类型 + const mimeType = blob.type.toLowerCase(); + + // 返回对应的扩展名,如果找不到则返回空字符串 + return mimeToExt[mimeType] || ''; + } + + base64ToBlob(src: string) { + const items = src.split(','); + if (items.length != 2) { + throw new Error('base64格式错误'); + } + const mineType = items[0].replace('data:', ''); + const base64 = items[1]; + + const byteCharacters = atob(base64); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + return {blob: new Blob([byteArray], { type: mineType }), ext: this.getImageExt(mineType)}; + } + + async uploadImageFromUrl(url: string, token: string, type: string = '') { + try { + const rep = await requestUrl(url); + await PrepareImageLib(); + let data = rep.arrayBuffer; + let blob = new Blob([data]); + + let filename = this.getImageNameFromUrl(url, rep.headers['content-type']); + if (filename == '' || filename == null) { + filename = 'remote_img' + this.getImageExtFromBlob(blob); + } + + if (this.isWebp(filename)) { + if (IsImageLibReady()) { + data = WebpToJPG(data); + blob = new Blob([data]); + filename = filename.toLowerCase().replace('.webp', '.jpg'); + } + else { + console.error('wasm not ready for webp'); + } + } + + return await UploadImageToWx(blob, filename, token, type); + } + catch (e) { + console.error(e); + throw new Error('上传图片失败:' + e.message + '|' + url); + } + } + + getImageExt(type: string): string { + const mimeToExt: { [key: string]: string } = { + 'image/jpeg': '.jpg', + 'image/jpg': '.jpg', + 'image/png': '.png', + 'image/gif': '.gif', + 'image/bmp': '.bmp', + 'image/webp': '.webp', + 'image/svg+xml': '.svg', + 'image/tiff': '.tiff' + }; + return mimeToExt[type] || '.jpg'; + } + + getMimeType(ext: string): string { + const extToMime: { [key: string]: string } = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.bmp': 'image/bmp', + '.webp': 'image/webp', + '.svg': 'image/svg+xml', + '.tiff': 'image/tiff' + }; + return extToMime[ext.toLowerCase()] || 'image/jpeg'; + } + + getImageInfos(root: HTMLElement) { + const images = root.getElementsByTagName('img'); + const result = []; + for (let i = 0; i < images.length; i++) { + const img = images[i]; + const res = this.images.get(img.src); + if (res) { + result.push(res); + } + } + return result; + } + + async uploadRemoteImage(root: HTMLElement, token: string, type: string = '') { + const images = root.getElementsByTagName('img'); + const result = []; + for (let i = 0; i < images.length; i++) { + const img = images[i]; + if (img.src.includes('mmbiz.qpic.cn')) continue; + // 移动端本地图片不通过src上传 + if (img.src.startsWith('http://localhost/') && Platform.isMobileApp) { + continue; + } + + if (img.src.startsWith('http')) { + const res = await this.uploadImageFromUrl(img.src, token, type); + if (res.errcode != 0) { + const msg = `上传图片失败: ${img.src} ${res.errcode} ${res.errmsg}`; + new Notice(msg); + console.error(msg); + } + const info = { + resUrl: img.src, + filePath: "", + url: res.url, + media_id: res.media_id, + }; + this.images.set(img.src, info); + result.push(res); + } + else if (img.src.startsWith('data:image/')) { + const {blob, ext} = this.base64ToBlob(img.src); + if (!img.id) { + img.id = `local-img-${i}`; + } + const name = img.id + ext; + const res = await UploadImageToWx(blob, name, token); + if (res.errcode != 0) { + const msg = `上传图片失败: ${res.errcode} ${res.errmsg}`; + new Notice(msg); + console.error(msg); + continue; + } + const info = { + resUrl: '#' + img.id, + filePath: "", + url: res.url, + media_id: res.media_id, + }; + this.images.set('#' + img.id, info); + result.push(res); + } + } + return result; + } + + replaceImages(root: HTMLElement) { + const images = root.getElementsByTagName('img'); + for (let i = 0; i < images.length; i++) { + const img = images[i]; + let value = this.images.get(img.src); + if (value == null) { + if (!img.id) { + console.error('miss image id, ' + img.src); + continue; + } + value = this.images.get('#' + img.id); + } + if (value == null) continue; + if (value.url == null) continue; + img.setAttribute('src', value.url); + } + } + + arrayBufferToBase64(buffer: ArrayBuffer): string { + let binary = ''; + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); + } + + async localImagesToBase64(vault: Vault) { + const keys = this.images.keys(); + const result = new Map(); + for (let key of keys) { + const value = this.images.get(key); + if (value == null) continue; + const file = vault.getFileByPath(value.filePath); + if (file == null) continue; + let fileData = await vault.readBinary(file); + const base64 = this.arrayBufferToBase64(fileData); + const mimeType = this.getMimeType(file.extension); + const data = `data:${mimeType};base64,${base64}`; + result.set(value.resUrl, data); + } + return result; + } + + async downloadRemoteImage(url: string) { + try { + const rep = await requestUrl(url); + let data = rep.arrayBuffer; + let blob = new Blob([data]); + + let ext = this.getImageExtFromBlob(blob); + if (ext == '' || ext == null) { + const filename = this.getImageNameFromUrl(url, rep.headers['content-type']); + ext = '.' + filename.split('.').pop() || 'jpg'; + } + + const base64 = this.arrayBufferToBase64(data); + const mimeType = this.getMimeType(ext); + return `data:${mimeType};base64,${base64}`; + } + catch (e) { + console.error(e); + return ''; + } + } + + async remoteImagesToBase64(root: HTMLElement) { + const images = root.getElementsByTagName('img'); + const result = new Map(); + for (let i = 0; i < images.length; i++) { + const img = images[i]; + if (!img.src.startsWith('http')) continue; + const base64 = await this.downloadRemoteImage(img.src); + if (base64 == '') continue; + result.set(img.src, base64); + } + return result; + } + + async embleImages(root: HTMLElement, vault: Vault) { + const localImages = await this.localImagesToBase64(vault); + const remoteImages = await this.remoteImagesToBase64(root); + const result = root.cloneNode(true) as HTMLElement; + const images = result.getElementsByTagName('img'); + for (let i = 0; i < images.length; i++) { + const img = images[i]; + if (img.src.startsWith('http')) { + const base64 = remoteImages.get(img.src); + if (base64 != null) { + img.setAttribute('src', base64); + } + } + else { + const base64 = localImages.get(img.src); + if (base64 != null) { + img.setAttribute('src', base64); + } + } + } + return result.innerHTML; + } + + async cleanup() { + this.images.clear(); + } +} + + +export class LocalFile extends Extension{ + index: number = 0; + public static fileCache: Map = new Map(); + + generateId() { + this.index += 1; + return `fid-${this.index}`; + } + + getImagePath(path: string) { + const res = this.assetsManager.getResourcePath(path); + if (res == null) { + console.error('找不到文件:' + path); + return ''; + } + const info = { + resUrl: res.resUrl, + filePath: res.filePath, + media_id: null, + url: null + }; + LocalImageManager.getInstance().setImage(res.resUrl, info); + return res.resUrl; + } + + isImage(file: string) { + file = file.toLowerCase(); + return file.endsWith('.png') + || file.endsWith('.jpg') + || file.endsWith('.jpeg') + || file.endsWith('.gif') + || file.endsWith('.bmp') + || file.endsWith('.webp'); + } + + parseImageLink(link: string) { + if (link.includes('|')) { + const parts = link.split('|'); + const path = parts[0]; + if (!this.isImage(path)) return null; + + let width = null; + let height = null; + if (parts.length == 2) { + const size = parts[1].toLowerCase().split('x'); + width = parseInt(size[0]); + if (size.length == 2 && size[1] != '') { + height = parseInt(size[1]); + } + } + return { path, width, height }; + } + if (this.isImage(link)) { + return { path: link, width: null, height: null }; + } + return null; + } + + getHeaderLevel(line: string) { + const match = line.trimStart().match(/^#{1,6}/); + if (match) { + return match[0].length; + } + return 0; + } + + async getFileContent(file: TAbstractFile, header: string | null, block: string | null) { + const content = await this.app.vault.adapter.read(file.path); + if (header == null && block == null) { + return content; + } + + let result = ''; + const lines = content.split('\n'); + if (header) { + let level = 0; + let append = false; + for (let line of lines) { + if (append) { + if (level == this.getHeaderLevel(line)) { + break; + } + result += line + '\n'; + continue; + } + if (!line.trim().startsWith('#')) continue; + const items = line.trim().split(' '); + if (items.length != 2) continue; + if (header.trim() != items[1].trim()) continue; + if (this.getHeaderLevel(line)) { + result += line + '\n'; + level = this.getHeaderLevel(line); + append = true; + } + } + } + + function isStructuredBlock(line: string) { + const trimmed = line.trim(); + return trimmed.startsWith('-') || trimmed.startsWith('>') || trimmed.startsWith('|') || trimmed.match(/^\d+\./); + } + + if (block) { + let stopAtEmpty = false; + let totalLen = 0; + let structured = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.indexOf(block) >= 0) { + result = line.replace(block, '').trim(); + + // 标记和结构化内容位于同一行的时候只返回当前的条目 + if (isStructuredBlock(line)) { + break; + } + + // 向上查找内容 + for (let j = i - 1; j >= 0; j--) { + const l = lines[j]; + + if (l.startsWith('#')) { + break; + } + + if (l.trim() == '') { + if (stopAtEmpty) break; + if (j < i - 1 && totalLen > 0) break; + stopAtEmpty = true; + result = l + '\n' + result; + continue; + } + else { + stopAtEmpty = true; + } + + if (structured && !isStructuredBlock(l)) { + break; + } + + if (totalLen === 0 && isStructuredBlock(l)) { + structured = true; + } + + totalLen += result.length; + result = l + '\n' + result; + } + break; + } + } + } + + return result; + } + + parseFileLink(link: string) { + const info = link.split('|')[0]; + const items = info.split('#'); + let path = items[0]; + let header = null; + let block = null; + if (items.length == 2) { + if (items[1].startsWith('^')) { + block = items[1]; + } else { + header = items[1]; + } + } + return { path, head: header, block }; + } + + async renderFile(link: string, id: string) { + let { path, head: header, block} = this.parseFileLink(link); + let file = null; + if (path === '') { + file = this.app.workspace.getActiveFile(); + } + else { + if (!path.endsWith('.md')) { + path = path + '.md'; + } + file = this.assetsManager.searchFile(path); + } + + if (file == null) { + const msg = '找不到文件:' + path; + console.error(msg) + return msg; + } + + let content = await this.getFileContent(file, header, block); + if (content.startsWith('---')) { + content = content.replace(/^(---)$.+?^(---)$.+?/ims, ''); + } + const body = await this.marked.parse(content); + return body; + } + + static async readBlob(src: string) { + return await fetch(src).then(response => response.blob()) + } + + static async getExcalidrawUrl(data: string) { + const url = 'https://obplugin.sunboshi.tech/math/excalidraw'; + const req = await requestUrl({ + url, + method: 'POST', + contentType: 'application/json', + headers: { + authkey: NMPSettings.getInstance().authKey + }, + body: JSON.stringify({ data }) + }); + + if (req.status != 200) { + console.error(req.status); + return null; + } + return req.json.url; + } + + parseLinkStyle(link: string) { + let filename = ''; + let style = 'style="width:100%;height:100%"'; + let postion = 'left'; + const postions = ['left', 'center', 'right']; + if (link.includes('|')) { + const items = link.split('|'); + filename = items[0]; + let size = ''; + if (items.length == 2) { + if (postions.includes(items[1])) { + postion = items[1]; + } + else { + size = items[1]; + } + } + else if (items.length == 3) { + size = items[1]; + if (postions.includes(items[1])) { + size = items[2]; + postion = items[1]; + } + else { + size = items[1]; + postion = items[2]; + } + } + if (size != '') { + const sizes = size.split('x'); + if (sizes.length == 2) { + style = `style="width:${sizes[0]}px;height:${sizes[1]}px;"` + } + else { + style = `style="width:${sizes[0]}px;"` + } + } + } + else { + filename = link; + } + return { filename, style, postion }; + } + + parseExcalidrawLink(link: string) { + let classname = 'note-embed-excalidraw-left'; + const postions = new Map([ + ['left', 'note-embed-excalidraw-left'], + ['center', 'note-embed-excalidraw-center'], + ['right', 'note-embed-excalidraw-right'] + ]) + + let {filename, style, postion} = this.parseLinkStyle(link); + classname = postions.get(postion) || classname; + + if(filename.endsWith('excalidraw') || filename.endsWith('excalidraw.md')) { + return { filename, style, classname }; + } + + return null; + } + + static async renderExcalidraw(html: string) { + try { + const src = await this.getExcalidrawUrl(html); + let svg = ''; + if (src === '') { + svg = '渲染失败'; + console.log('Failed to get Excalidraw URL'); + } + else { + const blob = await this.readBlob(src); + if (blob.type === 'image/svg+xml') { + svg = await blob.text(); + } + else { + svg = '暂不支持' + blob.type; + } + } + return svg; + } catch (error) { + console.error(error.message); + return '渲染失败:' + error.message; + } + } + + parseSVGLink(link: string) { + let classname = 'note-embed-svg-left'; + const postions = new Map([ + ['left', 'note-embed-svg-left'], + ['center', 'note-embed-svg-center'], + ['right', 'note-embed-svg-right'] + ]) + + let {filename, style, postion} = this.parseLinkStyle(link); + classname = postions.get(postion) || classname; + + return { filename, style, classname }; + } + + async renderSVGFile(filename: string, id: string) { + const file = this.assetsManager.searchFile(filename); + + if (file == null) { + const msg = '找不到文件:' + file; + console.error(msg) + return msg; + } + + const content = await this.getFileContent(file, null, null); + LocalFile.fileCache.set(filename, content); + return content; + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type !== 'LocalImage') { + return; + } + // 渲染本地图片 + let item = this.parseImageLink(token.href); + if (item) { + const src = this.getImagePath(item.path); + const width = item.width ? `width="${item.width}"` : ''; + const height = item.height? `height="${item.height}"` : ''; + token.html = `${token.text}`; + return; + } + + const info = this.parseExcalidrawLink(token.href); + if (info) { + if (!NMPSettings.getInstance().isAuthKeyVaild()) { + token.html = "请设置注册码"; + return; + } + const id = this.generateId(); + this.callback.cacheElement('excalidraw', id, token.raw); + token.html = `` + return; + } + + if (token.href.endsWith('.svg') || token.href.includes('.svg|')) { + const info = this.parseSVGLink(token.href); + const id = this.generateId(); + let svg = '渲染中'; + if (LocalFile.fileCache.has(info.filename)) { + svg = LocalFile.fileCache.get(info.filename) || '渲染失败'; + } + else { + svg = await this.renderSVGFile(info.filename, id) || '渲染失败'; + } + token.html = `${svg}` + return; + } + + const id = this.generateId(); + const content = await this.renderFile(token.href, id); + const tag = this.callback.settings.embedStyle === 'quote' ? 'blockquote' : 'section'; + token.html = `<${tag} class="note-embed-file" id="${id}">${content}` + }, + + extensions:[{ + name: 'LocalImage', + level: 'block', + start: (src: string) => { + const index = src.indexOf('![['); + if (index === -1) return; + return index; + }, + tokenizer: (src: string) => { + const matches = src.match(LocalFileRegex); + if (matches == null) return; + const token: Token = { + type: 'LocalImage', + raw: matches[0], + href: matches[1], + text: matches[1] + }; + return token; + }, + renderer: (token: Tokens.Generic) => { + return token.html; + } + }]}; + } +} \ No newline at end of file diff --git a/src/markdown/math.ts b/src/markdown/math.ts new file mode 100644 index 0000000..a7741b3 --- /dev/null +++ b/src/markdown/math.ts @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { MarkedExtension, Token, Tokens } from "marked"; +import { requestUrl } from "obsidian"; +import { Extension } from "./extension"; +import { NMPSettings } from "src/settings"; + +const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1/; +const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/; + +const svgCache = new Map(); + +export function cleanMathCache() { + svgCache.clear(); +} + +export class MathRendererQueue { + private host = 'https://obplugin.sunboshi.tech'; + private static instance: MathRendererQueue; + private mathIndex: number = 0; + + // 静态方法,用于获取实例 + public static getInstance(): MathRendererQueue { + if (!MathRendererQueue.instance) { + MathRendererQueue.instance = new MathRendererQueue(); + } + return MathRendererQueue.instance; + } + + private constructor() { + } + + async getMathSVG(expression: string, inline: boolean, type: string) { + try { + let success = false; + let path = ''; + if (type === 'asciimath') { + path = '/math/am'; + } + else { + path = '/math/tex'; + } + + const url = `${this.host}${path}`; + const res = await requestUrl({ + url, + method: 'POST', + contentType: 'application/json', + headers: { + authkey: NMPSettings.getInstance().authKey + }, + body: JSON.stringify({ + expression, + inline + }) + }) + let svg = '' + if (res.status === 200) { + svg = res.text; + success = true; + } + else { + console.error('render error: ' + res.json.msg) + svg = '渲染失败: ' + res.json.msg; + } + return { svg, success }; + } + catch (err) { + console.log(err.msg); + const svg = '渲染失败: ' + err.message; + return { svg, success: false }; + } + } + + generateId() { + this.mathIndex += 1; + return `math-id-${this.mathIndex}`; + } + + async render(token: Tokens.Generic, inline: boolean, type: string) { + if (!NMPSettings.getInstance().isAuthKeyVaild()) { + return '注册码无效或已过期'; + } + + const id = this.generateId(); + let svg = '渲染中'; + const expression = token.text; + if (svgCache.has(token.text)) { + svg = svgCache.get(expression) as string; + } + else { + const res = await this.getMathSVG(expression, inline, type) + if (res.success) { + svgCache.set(expression, res.svg); + } + svg = res.svg; + } + + const className = inline ? 'inline-math-svg' : 'block-math-svg'; + const body = inline ? svg : `
    ${svg}
    `; + return `${body}`; + } +} + + +export class MathRenderer extends Extension { + async renderer(token: Tokens.Generic, inline: boolean, type: string = '') { + if (type === '') { + type = this.settings.math; + } + return await MathRendererQueue.getInstance().render(token, inline, type); + } + + markedExtension(): MarkedExtension { + return { + async: true, + walkTokens: async (token: Tokens.Generic) => { + if (token.type === 'InlineMath' || token.type === 'BlockMath') { + token.html = await this.renderer(token, token.type === 'InlineMath', token.displayMode ? 'latex' : 'asciimath'); + } + }, + extensions: [ + this.inlineMath(), + this.blockMath() + ] + } + } + + inlineMath() { + return { + name: 'InlineMath', + level: 'inline', + start(src: string) { + let index; + let indexSrc = src; + + while (indexSrc) { + index = indexSrc.indexOf('$'); + if (index === -1) { + return; + } + + const possibleKatex = indexSrc.substring(index); + + if (possibleKatex.match(inlineRule)) { + return index; + } + + indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, ''); + } + }, + tokenizer(src: string, tokens: Token[]) { + const match = src.match(inlineRule); + if (match) { + return { + type: 'InlineMath', + raw: match[0], + text: match[2].trim(), + displayMode: match[1].length === 2 + }; + } + }, + renderer: (token: Tokens.Generic) => { + return token.html; + } + } + } + blockMath() { + return { + name: 'BlockMath', + level: 'block', + tokenizer(src: string) { + const match = src.match(blockRule); + if (match) { + return { + type: 'BlockMath', + raw: match[0], + text: match[2].trim(), + displayMode: match[1].length === 2 + }; + } + }, + renderer: (token: Tokens.Generic) => { + return token.html; + } + }; + } +} diff --git a/src/markdown/parser.ts b/src/markdown/parser.ts new file mode 100644 index 0000000..ac4f153 --- /dev/null +++ b/src/markdown/parser.ts @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Marked } from "marked"; +import { NMPSettings } from "src/settings"; +import { App, Vault } from "obsidian"; +import AssetsManager from "../assets"; +import { Extension, MDRendererCallback } from "./extension"; +import { Blockquote} from "./blockquote"; +import { CodeRenderer } from "./code"; +import { EmbedBlockMark } from "./embed-block-mark"; +import { SVGIcon } from "./icons"; +import { LinkRenderer } from "./link"; +import { LocalFile, LocalImageManager } from "./local-file"; +import { MathRenderer } from "./math"; +import { TextHighlight } from "./text-highlight"; +import { Comment } from "./commnet"; +import { Topic } from "./topic"; +import { HeadingRenderer } from "./heading"; +import { FootnoteRenderer } from "./footnote"; +import { EmptyLineRenderer } from "./empty-line"; +import { cleanUrl } from "../utils"; + + +const markedOptiones = { + gfm: true, + breaks: true, +}; + +const customRenderer = { + hr(): string { + return '
    '; + }, + list(body: string, ordered: boolean, start: number | ''): string { + const type = ordered ? 'ol' : 'ul'; + const startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; + return '<' + type + startatt + ' class="list-paddingleft-1">' + body + ''; + }, + listitem(text: string, task: boolean, checked: boolean): string { + return `
  • ${text}
  • `; + }, + image(href: string, title: string | null, text: string): string { + const cleanHref = cleanUrl(href); + if (cleanHref === null) { + return text; + } + href = cleanHref; + + if (!href.startsWith('http')) { + const res = AssetsManager.getInstance().getResourcePath(decodeURI(href)); + if (res) { + href = res.resUrl; + const info = { + resUrl: res.resUrl, + filePath: res.filePath, + media_id: null, + url: null + }; + LocalImageManager.getInstance().setImage(res.resUrl, info); + } + } + let out = ''; + if (NMPSettings.getInstance().useFigcaption) { + out = `
    ${text} 0) { + out += `>
    ${text}
    `; + } + else { + out += '>' + } + } + else { + out = `${text} await ext.prepare()); + } + + async postprocess(html: string) { + let result = html; + for (let ext of this.extensions) { + result = await ext.postprocess(result); + } + return result; + } + + async parse(content: string) { + if (!this.marked) await this.buildMarked(); + await this.prepare(); + let html = await this.marked.parse(content); + html = await this.postprocess(html); + return html; + } +} diff --git a/src/markdown/text-highlight.ts b/src/markdown/text-highlight.ts new file mode 100644 index 0000000..e6f16cb --- /dev/null +++ b/src/markdown/text-highlight.ts @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Token, Tokens, Lexer, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +const highlightRegex = /^==(.*?)==/; + +export class TextHighlight extends Extension { + markedExtension(): MarkedExtension { + return { + extensions: [{ + name: 'InlineHighlight', + level: 'inline', + start(src: string) { + let index; + let indexSrc = src; + + while (indexSrc) { + index = indexSrc.indexOf('=='); + if (index === -1) return; + return index; + } + }, + tokenizer(src: string, tokens: Token[]) { + const match = src.match(highlightRegex); + if (match) { + return { + type: 'InlineHighlight', + raw: match[0], + text: match[1], + }; + } + }, + renderer(token: Tokens.Generic) { + const lexer = new Lexer(); + const tokens = lexer.lex(token.text); + // TODO: 优化一下 + let body = this.parser.parse(tokens) + body = body.replace('

    ', '') + body = body.replace('

    ', '') + return `${body}`; + } + }] + }; + } +} \ No newline at end of file diff --git a/src/markdown/topic.ts b/src/markdown/topic.ts new file mode 100644 index 0000000..82a7cf9 --- /dev/null +++ b/src/markdown/topic.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; + +const topicRegex = /^#([^\s#]+)/; + +export class Topic extends Extension { + markedExtension(): MarkedExtension { + return { + extensions: [{ + name: 'Topic', + level: 'inline', + start(src: string) { + let index; + let indexSrc = src; + + while (indexSrc) { + index = indexSrc.indexOf('#'); + if (index === -1) return; + return index; + } + }, + tokenizer(src: string) { + const match = src.match(topicRegex); + if (match) { + return { + type: 'Topic', + raw: match[0], + text: match[1], + }; + } + }, + renderer(token: Tokens.Generic) { + return `${'#' + token.text.trim()}`; + } + }, + ] + } + } +} \ No newline at end of file diff --git a/src/markdown/widget-box.ts b/src/markdown/widget-box.ts new file mode 100644 index 0000000..f91ee5a --- /dev/null +++ b/src/markdown/widget-box.ts @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { Tokens, MarkedExtension } from "marked"; +import { Extension } from "./extension"; +import { NMPSettings } from "src/settings"; +import { uevent } from "src/utils"; +import { wxWidget } from "src/weixin-api"; + +const widgetCache = new Map(); + +export function cleanWidgetCache() { + widgetCache.clear(); +} + +export class WidgetBox extends Extension { + mapToString(map: Map): string { + if (map.size === 0) return ""; + + return Array.from(map.entries()) + .map(([key, value]) => `${key}=${value}`) + .join("&"); // 用 "&" 连接键值对,可换成其他分隔符 + } + + calcKey(id: string, title: string, style: Map, content: string) { + const styleStr = this.mapToString(style); + const key = `${id}-${title}-${styleStr}-${content}`; + return key; + } + + cacheWidget(id: string, title: string, style: Map, content: string, result: string) { + const key = this.calcKey(id, title, style, content); + widgetCache.set(key, result); + } + + getWidget(id: string, title: string, style: Map, content: string) { + const key = this.calcKey(id, title, style, content); + if (!widgetCache.has(key)) { + return null; + } + return widgetCache.get(key); + } + + getBoxTitle(text: string) { + let start = text.indexOf(']') + 1; + let end = text.indexOf('\n'); + if (end === -1) end = text.length; + if (start >= end) return ''; + return text.slice(start, end).trim(); + } + + getBoxId(text: string) { + const regex = /\[#(.*?)\]/g; + let m; + if( m = regex.exec(text)) { + return m[1]; + } + return ""; + } + matched(text: string) { + return this.getBoxId(text) != ""; + } + + parseStyle(text: string) { + const style = text.split(':').map((s) => s.trim()); + if (style.length != 2) return null; + const key = style[0]; + const value = style[1]; + return {key, value}; + } + + parseBox(text: string) { + const lines = text.split('\n'); + let style = new Map(); + let content = []; + let isStyle = false; + for (let line of lines) { + if (line === '===') { + isStyle = !isStyle; + continue; + } + if (isStyle) { + const s = this.parseStyle(line); + if (s) style.set(s.key, s.value); + } else { + content.push(line); + } + } + const contentStr = content.join('\n'); + return { style, contentStr }; + } + + async reqContent(id: string, title: string, style: Map, content: string) { + const params = JSON.stringify({ + id, + title, + style: Object.fromEntries(style), + content + }); + return wxWidget(NMPSettings.getInstance().authKey, params) + } + + processColor(style: Map) { + const keys = style.keys(); + for (let key of keys) { + if (key.includes('color')) { + const value = style.get(key); + if (!value) continue; + if (value.startsWith('rgb') || value.startsWith('#')) { + continue; + } + style.set(key, '#' + value); + } + } + } + + async renderer(token: Tokens.Blockquote) { + let boxId = this.getBoxId(token.text); + if (boxId == '') { + const body = this.marked.parser(token.tokens); + return `
    ${body}
    `;; + } + + const title = this.getBoxTitle(token.text); + let style = new Map(); + let content = ''; + const index = token.text.indexOf('\n'); + if (index > 0) { + const pared = this.parseBox(token.text.slice(index + 1)) + style = pared.style; + content = await this.marked.parse(pared.contentStr); + } + + this.processColor(style); + + const cached = this.getWidget(boxId, title, style, content); + if (cached) { + uevent('render-widgets-cached'); + return cached; + } + else { + const reqContent = await this.reqContent(boxId, title, style, content); + this.cacheWidget(boxId, title, style, content, reqContent); + uevent('render-widgets'); + return reqContent; + } + } + + markedExtension(): MarkedExtension { + return { + extensions: [{ + name: 'blockquote', + level: 'block', + renderer: (token: Tokens.Generic) => { + return token.html; + }, + }] + } + } +} \ No newline at end of file diff --git a/src/meta/index.ts b/src/meta/index.ts new file mode 100644 index 0000000..5d12baf --- /dev/null +++ b/src/meta/index.ts @@ -0,0 +1,76 @@ +// [note-to-mp 重构] 元数据与封面模块 +import { LocalImage } from '../image'; + +export interface WeChatMetaRaw { + title?: string; + author?: string; + coverLink?: string; // frontmatter 或行内指定的图片 basename 形式 + rawImage?: string; // 原 frontmatter 中的 image 字段原始值(可包含路径) + hasFrontmatter: boolean; +} + +export interface FinalMeta { + title: string; + author?: string; + coverImage?: LocalImage; // 解析到的封面图片对象 + coverLink?: string; // 决策后的封面 basename +} + +const FRONTMATTER_RE = /^---[\s\S]*?\n---/; + +export function extractWeChatMeta(raw: string): { meta: WeChatMetaRaw; body: string } { + const fmMatch = raw.match(FRONTMATTER_RE); + if (!fmMatch) { + return { meta: { hasFrontmatter: false }, body: raw }; + } + const block = fmMatch[0]; + const lines = block.split(/\r?\n/).slice(1, -1); // 去除首尾 --- + let title: string | undefined; + let author: string | undefined; + let image: string | undefined; + + for (const line of lines) { + const m = line.match(/^([a-zA-Z0-9_-]+)\s*:\s*(.*)$/); + if (!m) continue; + const key = m[1].toLowerCase(); + let val = m[2].trim(); + // 去除包裹引号 + if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { + val = val.slice(1, -1); + } + if (key === 'title') title = val; + else if (key === 'author') author = val; + else if (key === 'image' || key === 'cover') image = val; + } + + let coverLink: string | undefined; + if (image) { + const basename = image.split(/[?#]/)[0].split('/').pop() || image; + coverLink = basename; + } + + const body = raw.slice(block.length).replace(/^\s+/, ''); + return { meta: { title, author, coverLink, rawImage: image, hasFrontmatter: true }, body }; +} + +export function getMetadata(images: LocalImage[], rawMeta: WeChatMetaRaw): FinalMeta { + // 标题回退策略:若无 frontmatter title,尝试第一行一级标题 + let title = rawMeta.title; + if (!title) { + // 简单取第一行 markdown 一级/二级标题 + // 实际调用方可传入 body 再做改进;这里保持接口简单 + title = '未命名文章'; + } + + let coverLink = rawMeta.coverLink; + let coverImage: LocalImage | undefined; + if (coverLink) { + coverImage = images.find(img => img.basename === coverLink); + } + if (!coverImage) { + coverImage = images[0]; + coverLink = coverImage?.basename; + } + + return { title, author: rawMeta.author, coverImage, coverLink }; +} diff --git a/src/note-preview.ts b/src/note-preview.ts new file mode 100644 index 0000000..1029d68 --- /dev/null +++ b/src/note-preview.ts @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { EventRef, ItemView, Workspace, WorkspaceLeaf, Notice, Platform, TFile, TFolder, TAbstractFile, Plugin } from 'obsidian'; +import { uevent, debounce, waitForLayoutReady } from './utils'; +// [note-to-mp 重构] 引入新渲染管线 +import { RenderService, RenderedArticle } from './render'; +import { NMPSettings } from './settings'; +import AssetsManager from './assets'; +import { MarkedParser } from './markdown/parser'; +import { LocalImageManager, LocalFile } from './markdown/local-file'; +import { CardDataManager } from './markdown/code'; +import { ArticleRender } from './article-render'; + + +export const VIEW_TYPE_NOTE_PREVIEW = 'note-preview'; + +export class NotePreview extends ItemView { + workspace: Workspace; + plugin: Plugin; + mainDiv: HTMLDivElement; + toolbar: HTMLDivElement; + renderDiv: HTMLDivElement; + articleDiv: HTMLDivElement; + styleEl: HTMLElement; + coverEl: HTMLInputElement; + useDefaultCover: HTMLInputElement; + useLocalCover: HTMLInputElement; + msgView: HTMLDivElement; + wechatSelect: HTMLSelectElement; + themeSelect: HTMLSelectElement; + highlightSelect: HTMLSelectElement; + listeners?: EventRef[]; + container: Element; + settings: NMPSettings; + assetsManager: AssetsManager; + articleHTML: string; + title: string; + currentFile?: TFile; + currentTheme: string; + currentHighlight: string; + currentAppId: string; + markedParser: MarkedParser; + cachedElements: Map = new Map(); + _articleRender: ArticleRender | null = null; + isCancelUpload: boolean = false; + isBatchRuning: boolean = false; + // [note-to-mp 重构] 新渲染服务实例与最近一次渲染结果 + newRenderService: RenderService | null = null; + lastArticle?: RenderedArticle; + + + constructor(leaf: WorkspaceLeaf, plugin: Plugin) { + super(leaf); + this.workspace = this.app.workspace; + this.plugin = plugin; + this.settings = NMPSettings.getInstance(); + this.assetsManager = AssetsManager.getInstance(); + this.currentTheme = this.settings.defaultStyle; + this.currentHighlight = this.settings.defaultHighlight; + } + + getViewType() { + return VIEW_TYPE_NOTE_PREVIEW; + } + + getIcon() { + return 'clipboard-paste'; + } + + getDisplayText() { + return '笔记预览'; + } + + get render() { + if (!this._articleRender) { + this._articleRender = new ArticleRender(this.app, this, this.styleEl, this.articleDiv); + this._articleRender.currentTheme = this.currentTheme; + this._articleRender.currentHighlight = this.currentHighlight; + } + return this._articleRender; + } + + async onOpen() { + this.viewLoading(); + this.setup(); + uevent('open'); + } + + async setup() { + await waitForLayoutReady(this.app); + + if (!this.settings.isLoaded) { + const data = await this.plugin.loadData(); + NMPSettings.loadSettings(data); + } + if (!this.assetsManager.isLoaded) { + await this.assetsManager.loadAssets(); + } + + this.buildUI(); + // [note-to-mp 重构] 初始化新渲染服务 + this.newRenderService = new RenderService(this.app); + this.listeners = [ + this.workspace.on('file-open', () => { + this.update(); + }), + this.app.vault.on("modify", (file) => { + if (this.currentFile?.path == file.path) { + this.renderMarkdown(); + } + } ) + ]; + + this.renderMarkdown(); + } + + async onClose() { + this.listeners?.forEach(listener => this.workspace.offref(listener)); + LocalFile.fileCache.clear(); + uevent('close'); + } + + onAppIdChanged() { + // 清理上传过的图片 + this.cleanArticleData(); + } + + async update() { + if (this.isBatchRuning) { + return; + } + this.cleanArticleData(); + this.renderMarkdown(); + } + + cleanArticleData() { + LocalImageManager.getInstance().cleanup(); + CardDataManager.getInstance().cleanup(); + } + + buildMsgView(parent: HTMLDivElement) { + this.msgView = parent.createDiv({ cls: 'msg-view' }); + const title = this.msgView.createDiv({ cls: 'msg-title' }); + title.id = 'msg-title'; + title.innerText = '加载中...'; + const okBtn = this.msgView.createEl('button', { cls: 'msg-ok-btn' }, async (button) => { + + }); + okBtn.id = 'msg-ok-btn'; + okBtn.innerText = '确定'; + okBtn.onclick = async () => { + this.msgView.setAttr('style', 'display: none;'); + } + const cancelBtn = this.msgView.createEl('button', { cls: 'msg-ok-btn' }, async (button) => { + }); + cancelBtn.id = 'msg-cancel-btn'; + cancelBtn.innerText = '取消'; + cancelBtn.onclick = async () => { + this.isCancelUpload = true; + this.msgView.setAttr('style', 'display: none;'); + } + } + + showLoading(msg: string, cancelable: boolean = false) { + const title = this.msgView.querySelector('#msg-title') as HTMLElement; + title!.innerText = msg; + const btn = this.msgView.querySelector('#msg-ok-btn') as HTMLElement; + btn.setAttr('style', 'display: none;'); + this.msgView.setAttr('style', 'display: flex;'); + const cancelBtn = this.msgView.querySelector('#msg-cancel-btn') as HTMLElement; + cancelBtn.setAttr('style', cancelable ? 'display: block;': 'display: none;'); + this.msgView.setAttr('style', 'display: flex;'); + } + + showMsg(msg: string) { + const title = this.msgView.querySelector('#msg-title') as HTMLElement; + title!.innerText = msg; + const btn = this.msgView.querySelector('#msg-ok-btn') as HTMLElement; + btn.setAttr('style', 'display: block;'); + this.msgView.setAttr('style', 'display: flex;'); + const cancelBtn = this.msgView.querySelector('#msg-cancel-btn') as HTMLElement; + cancelBtn.setAttr('style', 'display: none;'); + this.msgView.setAttr('style', 'display: flex;'); + } + + buildToolbar(parent: HTMLDivElement) { + this.toolbar = parent.createDiv({ cls: 'preview-toolbar' }); + let lineDiv; + + // 公众号 + if (this.settings.wxInfo.length > 1 || Platform.isDesktop) { + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line' }); + lineDiv.createDiv({ cls: 'style-label' }).innerText = '公众号:'; + const wxSelect = lineDiv.createEl('select', { cls: 'style-select' }) + wxSelect.setAttr('style', 'width: 200px'); + wxSelect.onchange = async () => { + this.currentAppId = wxSelect.value; + this.onAppIdChanged(); + } + const defautlOp =wxSelect.createEl('option'); + defautlOp.value = ''; + defautlOp.text = '请在设置里配置公众号'; + for (let i = 0; i < this.settings.wxInfo.length; i++) { + const op = wxSelect.createEl('option'); + const wx = this.settings.wxInfo[i]; + op.value = wx.appid; + op.text = wx.name; + if (i== 0) { + op.selected = true + this.currentAppId = wx.appid; + } + } + this.wechatSelect = wxSelect; + + if (Platform.isDesktop) { + const openBtn = lineDiv.createEl('button', { cls: 'refresh-button' }, async (button) => { + button.setText('去公众号后台'); + }) + + openBtn.onclick = async () => { + const { shell } = require('electron'); + shell.openExternal('https://mp.weixin.qq.com') + uevent('open-mp'); + } + } + } + else if (this.settings.wxInfo.length > 0) { + this.currentAppId = this.settings.wxInfo[0].appid; + } + + // 复制,刷新,带图片复制,发草稿箱 + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line' }); + const refreshBtn = lineDiv.createEl('button', { cls: 'refresh-button' }, async (button) => { + button.setText('刷新'); + }) + + refreshBtn.onclick = async () => { + await this.assetsManager.loadCustomCSS(); + await this.assetsManager.loadExpertSettings(); + this.render.reloadStyle(); + await this.renderMarkdown(); + uevent('refresh'); + } + if (Platform.isDesktop) { + const copyBtn = lineDiv.createEl('button', { cls: 'copy-button' }, async (button) => { + button.setText('复制'); + }) + + copyBtn.onclick = async() => { + try { + await this.render.copyArticle(); + new Notice('复制成功,请到公众号编辑器粘贴。'); + uevent('copy'); + } catch (error) { + console.error(error); + new Notice('复制失败: ' + error); + } + } + } + + const uploadImgBtn = lineDiv.createEl('button', { cls: 'copy-button' }, async (button) => { + button.setText('上传图片'); + }) + + uploadImgBtn.onclick = async() => { + await this.uploadImages(); + uevent('upload'); + } + + const postBtn = lineDiv.createEl('button', { cls: 'copy-button' }, async (button) => { + button.setText('发草稿'); + }) + + postBtn.onclick = async() => { + await this.postArticle(); + uevent('pub'); + } + + const imagesBtn = lineDiv.createEl('button', { cls: 'copy-button' }, async (button) => { + button.setText('图片/文字'); + }) + + imagesBtn.onclick = async() => { + await this.postImages(); + uevent('pub-images'); + } + + if (Platform.isDesktop && this.settings.isAuthKeyVaild()) { + const htmlBtn = lineDiv.createEl('button', { cls: 'copy-button' }, async (button) => { + button.setText('导出HTML'); + }) + + htmlBtn.onclick = async() => { + await this.exportHTML(); + uevent('export-html'); + } + } + + + // 封面 + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line' }); + + const coverTitle = lineDiv.createDiv({ cls: 'style-label' }); + coverTitle.innerText = '封面:'; + + this.useDefaultCover = lineDiv.createEl('input', { cls: 'input-style' }); + this.useDefaultCover.setAttr('type', 'radio'); + this.useDefaultCover.setAttr('name', 'cover'); + this.useDefaultCover.setAttr('value', 'default'); + this.useDefaultCover.setAttr('checked', true); + this.useDefaultCover.id = 'default-cover'; + this.useDefaultCover.onchange = async () => { + if (this.useDefaultCover.checked) { + this.coverEl.setAttr('style', 'visibility:hidden;width:0px;'); + } + else { + this.coverEl.setAttr('style', 'visibility:visible;width:180px;'); + } + } + const defaultLable = lineDiv.createEl('label'); + defaultLable.innerText = '默认'; + defaultLable.setAttr('for', 'default-cover'); + + this.useLocalCover = lineDiv.createEl('input', { cls: 'input-style' }); + this.useLocalCover.setAttr('type', 'radio'); + this.useLocalCover.setAttr('name', 'cover'); + this.useLocalCover.setAttr('value', 'local'); + this.useLocalCover.id = 'local-cover'; + this.useLocalCover.setAttr('style', 'margin-left:20px;'); + this.useLocalCover.onchange = async () => { + if (this.useLocalCover.checked) { + this.coverEl.setAttr('style', 'visibility:visible;width:180px;'); + } + else { + this.coverEl.setAttr('style', 'visibility:hidden;width:0px;'); + } + } + + const localLabel = lineDiv.createEl('label'); + localLabel.setAttr('for', 'local-cover'); + localLabel.innerText = '上传'; + + this.coverEl = lineDiv.createEl('input', { cls: 'upload-input' }); + this.coverEl.setAttr('type', 'file'); + this.coverEl.setAttr('placeholder', '封面图片'); + this.coverEl.setAttr('accept', '.png, .jpg, .jpeg'); + this.coverEl.setAttr('name', 'cover'); + this.coverEl.id = 'cover-input'; + + // 样式 + if (this.settings.showStyleUI) { + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line' }); + const cssStyle = lineDiv.createDiv({ cls: 'style-label' }); + cssStyle.innerText = '样式:'; + + const selectBtn = lineDiv.createEl('select', { cls: 'style-select' }, async (sel) => { + + }) + + selectBtn.onchange = async () => { + this.currentTheme = selectBtn.value; + this.render.updateStyle(selectBtn.value); + } + + for (let s of this.assetsManager.themes) { + const op = selectBtn.createEl('option'); + op.value = s.className; + op.text = s.name; + op.selected = s.className == this.settings.defaultStyle; + } + + this.themeSelect = selectBtn; + + const highlightStyle = lineDiv.createDiv({ cls: 'style-label' }); + highlightStyle.innerText = '代码高亮:'; + + const highlightStyleBtn = lineDiv.createEl('select', { cls: 'style-select' }, async (sel) => { + + }) + + highlightStyleBtn.onchange = async () => { + this.currentHighlight = highlightStyleBtn.value; + this.render.updateHighLight(highlightStyleBtn.value); + } + + for (let s of this.assetsManager.highlights) { + const op = highlightStyleBtn.createEl('option'); + op.value = s.name; + op.text = s.name; + op.selected = s.name == this.settings.defaultHighlight; + } + + this.highlightSelect = highlightStyleBtn; + } + + this.buildMsgView(this.toolbar); + } + + async buildUI() { + this.container = this.containerEl.children[1]; + this.container.empty(); + + this.mainDiv = this.container.createDiv({ cls: 'note-preview' }); + + this.buildToolbar(this.mainDiv); + + this.renderDiv = this.mainDiv.createDiv({cls: 'render-div'}); + this.renderDiv.id = 'render-div'; + this.renderDiv.setAttribute('style', '-webkit-user-select: text; user-select: text;'); + this.styleEl = this.renderDiv.createEl('style'); + this.styleEl.setAttr('title', 'note-to-mp-style'); + this.articleDiv = this.renderDiv.createEl('div'); + } + + async viewLoading() { + const container = this.containerEl.children[1] + container.empty(); + const loading = container.createDiv({cls: 'loading-wrapper'}) + loading.createDiv({cls: 'loading-spinner'}) + } + + async renderMarkdown(af: TFile | null = null) { + if (!af) { + af = this.app.workspace.getActiveFile(); + } + if (!af || af.extension.toLocaleLowerCase() !== 'md') { + return; + } + this.currentFile = af; + // [note-to-mp 重构] 使用新渲染服务进行渲染 + if (this.newRenderService) { + try { + const article = await this.newRenderService.renderFile(af); + this.lastArticle = article; + if (this.articleDiv) { + this.articleDiv.empty(); + const wrap = this.articleDiv.createDiv(); + wrap.innerHTML = article.html; + } + // 元数据适配(当前新 meta 不含 appid/theme/highlight,保持现有选择状态) + if (this.wechatSelect) { + this.wechatSelect.value = this.currentAppId || ''; + } + if (this.themeSelect) { + this.themeSelect.value = this.currentTheme; + } + if (this.highlightSelect) { + this.highlightSelect.value = this.currentHighlight; + } + } catch (e) { + console.error('[note-to-mp 重构] 渲染失败', e); + new Notice('渲染失败: ' + e.message); + } + } else { + // 兜底:仍使用旧渲染 + await this.render.renderMarkdown(af); + } + } + + async uploadImages() { + this.showLoading('图片上传中...'); + try { + await this.render.uploadImages(this.currentAppId); + this.showMsg('图片上传成功,并且文章内容已复制,请到公众号编辑器粘贴。'); + } catch (error) { + this.showMsg('图片上传失败: ' + error.message); + } + } + + async postArticle() { + let localCover = null; + if (this.useLocalCover.checked) { + const fileInput = this.coverEl; + if (!fileInput.files || fileInput.files.length === 0) { + this.showMsg('请选择封面文件'); + return; + } + localCover = fileInput.files[0]; + if (!localCover) { + this.showMsg('请选择封面文件'); + return; + } + } + this.showLoading('发布中...'); + try { + await this.render.postArticle(this.currentAppId, localCover); + this.showMsg('发布成功'); + } + catch (error) { + this.showMsg('发布失败: ' + error.message); + } + } + + async postImages() { + this.showLoading('发布图片中...'); + try { + await this.render.postImages(this.currentAppId); + this.showMsg('图片发布成功'); + } catch (error) { + this.showMsg('图片发布失败: ' + error.message); + } + } + + async exportHTML() { + this.showLoading('导出HTML中...'); + try { + await this.render.exportHTML(); + this.showMsg('HTML导出成功'); + } catch (error) { + this.showMsg('HTML导出失败: ' + error.message); + } + } + + async batchPost(folder: TFolder) { + const files = folder.children.filter((child: TAbstractFile) => child.path.toLocaleLowerCase().endsWith('.md')); + if (!files) { + new Notice('没有可渲染的笔记或文件不支持渲染'); + return; + } + + this.isCancelUpload = false; + this.isBatchRuning = true; + + try { + for (let file of files) { + this.showLoading(`即将发布: ${file.name}`, true); + await sleep(5000); + if (this.isCancelUpload) { + break; + } + this.cleanArticleData(); + await this.renderMarkdown(file as TFile); + await this.postArticle(); + } + + if (!this.isCancelUpload) { + this.showMsg(`批量发布完成:成功发布 ${files.length} 篇笔记`); + } + } + catch (e) { + console.error(e); + new Notice('批量发布失败: ' + e.message); + } + finally { + this.isBatchRuning = false; + this.isCancelUpload = false; + } + } +} \ No newline at end of file diff --git a/src/postcss/at-rule.d.ts b/src/postcss/at-rule.d.ts new file mode 100644 index 0000000..9fc375f --- /dev/null +++ b/src/postcss/at-rule.d.ts @@ -0,0 +1,140 @@ +import Container, { + ContainerProps, + ContainerWithChildren +} from './container.js' + +declare namespace AtRule { + export interface AtRuleRaws extends Record { + /** + * The space symbols after the last child of the node to the end of the node. + */ + after?: string + + /** + * The space between the at-rule name and its parameters. + */ + afterName?: string + + /** + * The space symbols before the node. It also stores `*` + * and `_` symbols before the declaration (IE hack). + */ + before?: string + + /** + * The symbols between the last parameter and `{` for rules. + */ + between?: string + + /** + * The rule’s selector with comments. + */ + params?: { + raw: string + value: string + } + + /** + * Contains `true` if the last child has an (optional) semicolon. + */ + semicolon?: boolean + } + + export interface AtRuleProps extends ContainerProps { + /** Name of the at-rule. */ + name: string + /** Parameters following the name of the at-rule. */ + params?: number | string + /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ + raws?: AtRuleRaws + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { AtRule_ as default } +} + +/** + * Represents an at-rule. + * + * ```js + * Once (root, { AtRule }) { + * let media = new AtRule({ name: 'media', params: 'print' }) + * media.append(…) + * root.append(media) + * } + * ``` + * + * If it’s followed in the CSS by a `{}` block, this node will have + * a nodes property representing its children. + * + * ```js + * const root = postcss.parse('@charset "UTF-8"; @media print {}') + * + * const charset = root.first + * charset.type //=> 'atrule' + * charset.nodes //=> undefined + * + * const media = root.last + * media.nodes //=> [] + * ``` + */ +declare class AtRule_ extends Container { + /** + * The at-rule’s name immediately follows the `@`. + * + * ```js + * const root = postcss.parse('@media print {}') + * const media = root.first + * media.name //=> 'media' + * ``` + */ + get name(): string + set name(value: string) + + /** + * An array containing the layer’s children. + * + * ```js + * const root = postcss.parse('@layer example { a { color: black } }') + * const layer = root.first + * layer.nodes.length //=> 1 + * layer.nodes[0].selector //=> 'a' + * ``` + * + * Can be `undefinded` if the at-rule has no body. + * + * ```js + * const root = postcss.parse('@layer a, b, c;') + * const layer = root.first + * layer.nodes //=> undefined + * ``` + */ + nodes: Container['nodes'] + /** + * The at-rule’s parameters, the values that follow the at-rule’s name + * but precede any `{}` block. + * + * ```js + * const root = postcss.parse('@media print, screen {}') + * const media = root.first + * media.params //=> 'print, screen' + * ``` + */ + get params(): string + set params(value: string) + parent: ContainerWithChildren | undefined + + raws: AtRule.AtRuleRaws + + type: 'atrule' + + constructor(defaults?: AtRule.AtRuleProps) + assign(overrides: AtRule.AtRuleProps | object): this + clone(overrides?: Partial): AtRule + cloneAfter(overrides?: Partial): AtRule + cloneBefore(overrides?: Partial): AtRule +} + +declare class AtRule extends AtRule_ {} + +export = AtRule diff --git a/src/postcss/comment.d.ts b/src/postcss/comment.d.ts new file mode 100644 index 0000000..8b0a7a2 --- /dev/null +++ b/src/postcss/comment.d.ts @@ -0,0 +1,68 @@ +import Container from './container.js' +import Node, { NodeProps } from './node.js' + +declare namespace Comment { + export interface CommentRaws extends Record { + /** + * The space symbols before the node. + */ + before?: string + + /** + * The space symbols between `/*` and the comment’s text. + */ + left?: string + + /** + * The space symbols between the comment’s text. + */ + right?: string + } + + export interface CommentProps extends NodeProps { + /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ + raws?: CommentRaws + /** Content of the comment. */ + text: string + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Comment_ as default } +} + +/** + * It represents a class that handles + * [CSS comments](https://developer.mozilla.org/en-US/docs/Web/CSS/Comments) + * + * ```js + * Once (root, { Comment }) { + * const note = new Comment({ text: 'Note: …' }) + * root.append(note) + * } + * ``` + * + * Remember that CSS comments inside selectors, at-rule parameters, + * or declaration values will be stored in the `raws` properties + * explained above. + */ +declare class Comment_ extends Node { + parent: Container | undefined + raws: Comment.CommentRaws + /** + * The comment's text. + */ + get text(): string + set text(value: string) + + type: 'comment' + + constructor(defaults?: Comment.CommentProps) + assign(overrides: Comment.CommentProps | object): this + clone(overrides?: Partial): Comment + cloneAfter(overrides?: Partial): Comment + cloneBefore(overrides?: Partial): Comment +} + +declare class Comment extends Comment_ {} + +export = Comment diff --git a/src/postcss/container.d.ts b/src/postcss/container.d.ts new file mode 100644 index 0000000..d16b85d --- /dev/null +++ b/src/postcss/container.d.ts @@ -0,0 +1,490 @@ +import AtRule from './at-rule.js' +import Comment from './comment.js' +import Declaration from './declaration.js' +import Node, { ChildNode, ChildProps, NodeProps } from './node.js' +import Rule from './rule.js' + +declare namespace Container { + export class ContainerWithChildren< + Child extends Node = ChildNode + > extends Container_ { + nodes: Child[] + } + + export interface ValueOptions { + /** + * String that’s used to narrow down values and speed up the regexp search. + */ + fast?: string + + /** + * An array of property names. + */ + props?: string[] + } + + export interface ContainerProps extends NodeProps { + nodes?: (ChildNode | ChildProps)[] + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Container_ as default } +} + +/** + * The `Root`, `AtRule`, and `Rule` container nodes + * inherit some common methods to help work with their children. + * + * Note that all containers can store any content. If you write a rule inside + * a rule, PostCSS will parse it. + */ +declare abstract class Container_ extends Node { + /** + * An array containing the container’s children. + * + * ```js + * const root = postcss.parse('a { color: black }') + * root.nodes.length //=> 1 + * root.nodes[0].selector //=> 'a' + * root.nodes[0].nodes[0].prop //=> 'color' + * ``` + */ + nodes: Child[] | undefined + + /** + * Inserts new nodes to the end of the container. + * + * ```js + * const decl1 = new Declaration({ prop: 'color', value: 'black' }) + * const decl2 = new Declaration({ prop: 'background-color', value: 'white' }) + * rule.append(decl1, decl2) + * + * root.append({ name: 'charset', params: '"UTF-8"' }) // at-rule + * root.append({ selector: 'a' }) // rule + * rule.append({ prop: 'color', value: 'black' }) // declaration + * rule.append({ text: 'Comment' }) // comment + * + * root.append('a {}') + * root.first.append('color: black; z-index: 1') + * ``` + * + * @param nodes New nodes. + * @return This node for methods chain. + */ + append( + ...nodes: ( + | ChildProps + | ChildProps[] + | Node + | Node[] + | string + | string[] + | undefined + )[] + ): this + + assign(overrides: Container.ContainerProps | object): this + clone(overrides?: Partial): Container + cloneAfter(overrides?: Partial): Container + cloneBefore(overrides?: Partial): Container + + /** + * Iterates through the container’s immediate children, + * calling `callback` for each child. + * + * Returning `false` in the callback will break iteration. + * + * This method only iterates through the container’s immediate children. + * If you need to recursively iterate through all the container’s descendant + * nodes, use `Container#walk`. + * + * Unlike the for `{}`-cycle or `Array#forEach` this iterator is safe + * if you are mutating the array of child nodes during iteration. + * PostCSS will adjust the current index to match the mutations. + * + * ```js + * const root = postcss.parse('a { color: black; z-index: 1 }') + * const rule = root.first + * + * for (const decl of rule.nodes) { + * decl.cloneBefore({ prop: '-webkit-' + decl.prop }) + * // Cycle will be infinite, because cloneBefore moves the current node + * // to the next index + * } + * + * rule.each(decl => { + * decl.cloneBefore({ prop: '-webkit-' + decl.prop }) + * // Will be executed only for color and z-index + * }) + * ``` + * + * @param callback Iterator receives each node and index. + * @return Returns `false` if iteration was broke. + */ + each( + callback: (node: Child, index: number) => false | void + ): false | undefined + + /** + * Returns `true` if callback returns `true` + * for all of the container’s children. + * + * ```js + * const noPrefixes = rule.every(i => i.prop[0] !== '-') + * ``` + * + * @param condition Iterator returns true or false. + * @return Is every child pass condition. + */ + every( + condition: (node: Child, index: number, nodes: Child[]) => boolean + ): boolean + /** + * Returns a `child`’s index within the `Container#nodes` array. + * + * ```js + * rule.index( rule.nodes[2] ) //=> 2 + * ``` + * + * @param child Child of the current container. + * @return Child index. + */ + index(child: Child | number): number + + /** + * Insert new node after old node within the container. + * + * @param oldNode Child or child’s index. + * @param newNode New node. + * @return This node for methods chain. + */ + insertAfter( + oldNode: Child | number, + newNode: + | Child + | Child[] + | ChildProps + | ChildProps[] + | string + | string[] + | undefined + ): this + /** + * Insert new node before old node within the container. + * + * ```js + * rule.insertBefore(decl, decl.clone({ prop: '-webkit-' + decl.prop })) + * ``` + * + * @param oldNode Child or child’s index. + * @param newNode New node. + * @return This node for methods chain. + */ + insertBefore( + oldNode: Child | number, + newNode: + | Child + | Child[] + | ChildProps + | ChildProps[] + | string + | string[] + | undefined + ): this + + /** + * Traverses the container’s descendant nodes, calling callback + * for each comment node. + * + * Like `Container#each`, this method is safe + * to use if you are mutating arrays during iteration. + * + * ```js + * root.walkComments(comment => { + * comment.remove() + * }) + * ``` + * + * @param callback Iterator receives each node and index. + * @return Returns `false` if iteration was broke. + */ + + /** + * Inserts new nodes to the start of the container. + * + * ```js + * const decl1 = new Declaration({ prop: 'color', value: 'black' }) + * const decl2 = new Declaration({ prop: 'background-color', value: 'white' }) + * rule.prepend(decl1, decl2) + * + * root.append({ name: 'charset', params: '"UTF-8"' }) // at-rule + * root.append({ selector: 'a' }) // rule + * rule.append({ prop: 'color', value: 'black' }) // declaration + * rule.append({ text: 'Comment' }) // comment + * + * root.append('a {}') + * root.first.append('color: black; z-index: 1') + * ``` + * + * @param nodes New nodes. + * @return This node for methods chain. + */ + prepend( + ...nodes: ( + | ChildProps + | ChildProps[] + | Node + | Node[] + | string + | string[] + | undefined + )[] + ): this + /** + * Add child to the end of the node. + * + * ```js + * rule.push(new Declaration({ prop: 'color', value: 'black' })) + * ``` + * + * @param child New node. + * @return This node for methods chain. + */ + push(child: Child): this + + /** + * Removes all children from the container + * and cleans their parent properties. + * + * ```js + * rule.removeAll() + * rule.nodes.length //=> 0 + * ``` + * + * @return This node for methods chain. + */ + removeAll(): this + + /** + * Removes node from the container and cleans the parent properties + * from the node and its children. + * + * ```js + * rule.nodes.length //=> 5 + * rule.removeChild(decl) + * rule.nodes.length //=> 4 + * decl.parent //=> undefined + * ``` + * + * @param child Child or child’s index. + * @return This node for methods chain. + */ + removeChild(child: Child | number): this + + replaceValues( + pattern: RegExp | string, + replaced: { (substring: string, ...args: any[]): string } | string + ): this + + /** + * Passes all declaration values within the container that match pattern + * through callback, replacing those values with the returned result + * of callback. + * + * This method is useful if you are using a custom unit or function + * and need to iterate through all values. + * + * ```js + * root.replaceValues(/\d+rem/, { fast: 'rem' }, string => { + * return 15 * parseInt(string) + 'px' + * }) + * ``` + * + * @param pattern Replace pattern. + * @param {object} opts Options to speed up the search. + * @param callback String to replace pattern or callback + * that returns a new value. The callback + * will receive the same arguments + * as those passed to a function parameter + * of `String#replace`. + * @return This node for methods chain. + */ + replaceValues( + pattern: RegExp | string, + options: Container.ValueOptions, + replaced: { (substring: string, ...args: any[]): string } | string + ): this + + /** + * Returns `true` if callback returns `true` for (at least) one + * of the container’s children. + * + * ```js + * const hasPrefix = rule.some(i => i.prop[0] === '-') + * ``` + * + * @param condition Iterator returns true or false. + * @return Is some child pass condition. + */ + some( + condition: (node: Child, index: number, nodes: Child[]) => boolean + ): boolean + + /** + * Traverses the container’s descendant nodes, calling callback + * for each node. + * + * Like container.each(), this method is safe to use + * if you are mutating arrays during iteration. + * + * If you only need to iterate through the container’s immediate children, + * use `Container#each`. + * + * ```js + * root.walk(node => { + * // Traverses all descendant nodes. + * }) + * ``` + * + * @param callback Iterator receives each node and index. + * @return Returns `false` if iteration was broke. + */ + walk( + callback: (node: ChildNode, index: number) => false | void + ): false | undefined + + /** + * Traverses the container’s descendant nodes, calling callback + * for each at-rule node. + * + * If you pass a filter, iteration will only happen over at-rules + * that have matching names. + * + * Like `Container#each`, this method is safe + * to use if you are mutating arrays during iteration. + * + * ```js + * root.walkAtRules(rule => { + * if (isOld(rule.name)) rule.remove() + * }) + * + * let first = false + * root.walkAtRules('charset', rule => { + * if (!first) { + * first = true + * } else { + * rule.remove() + * } + * }) + * ``` + * + * @param name String or regular expression to filter at-rules by name. + * @param callback Iterator receives each node and index. + * @return Returns `false` if iteration was broke. + */ + walkAtRules( + nameFilter: RegExp | string, + callback: (atRule: AtRule, index: number) => false | void + ): false | undefined + + walkAtRules( + callback: (atRule: AtRule, index: number) => false | void + ): false | undefined + walkComments( + callback: (comment: Comment, indexed: number) => false | void + ): false | undefined + + walkComments( + callback: (comment: Comment, indexed: number) => false | void + ): false | undefined + + /** + * Traverses the container’s descendant nodes, calling callback + * for each declaration node. + * + * If you pass a filter, iteration will only happen over declarations + * with matching properties. + * + * ```js + * root.walkDecls(decl => { + * checkPropertySupport(decl.prop) + * }) + * + * root.walkDecls('border-radius', decl => { + * decl.remove() + * }) + * + * root.walkDecls(/^background/, decl => { + * decl.value = takeFirstColorFromGradient(decl.value) + * }) + * ``` + * + * Like `Container#each`, this method is safe + * to use if you are mutating arrays during iteration. + * + * @param prop String or regular expression to filter declarations + * by property name. + * @param callback Iterator receives each node and index. + * @return Returns `false` if iteration was broke. + */ + walkDecls( + propFilter: RegExp | string, + callback: (decl: Declaration, index: number) => false | void + ): false | undefined + + walkDecls( + callback: (decl: Declaration, index: number) => false | void + ): false | undefined + + /** + * Traverses the container’s descendant nodes, calling callback + * for each rule node. + * + * If you pass a filter, iteration will only happen over rules + * with matching selectors. + * + * Like `Container#each`, this method is safe + * to use if you are mutating arrays during iteration. + * + * ```js + * const selectors = [] + * root.walkRules(rule => { + * selectors.push(rule.selector) + * }) + * console.log(`Your CSS uses ${ selectors.length } selectors`) + * ``` + * + * @param selector String or regular expression to filter rules by selector. + * @param callback Iterator receives each node and index. + * @return Returns `false` if iteration was broke. + */ + walkRules( + selectorFilter: RegExp | string, + callback: (rule: Rule, index: number) => false | void + ): false | undefined + walkRules( + callback: (rule: Rule, index: number) => false | void + ): false | undefined + /** + * The container’s first child. + * + * ```js + * rule.first === rules.nodes[0] + * ``` + */ + get first(): Child | undefined + /** + * The container’s last child. + * + * ```js + * rule.last === rule.nodes[rule.nodes.length - 1] + * ``` + */ + get last(): Child | undefined +} + +declare class Container< + Child extends Node = ChildNode +> extends Container_ {} + +export = Container diff --git a/src/postcss/css-syntax-error.d.ts b/src/postcss/css-syntax-error.d.ts new file mode 100644 index 0000000..e540d84 --- /dev/null +++ b/src/postcss/css-syntax-error.d.ts @@ -0,0 +1,248 @@ +import { FilePosition } from './input.js' + +declare namespace CssSyntaxError { + /** + * A position that is part of a range. + */ + export interface RangePosition { + /** + * The column number in the input. + */ + column: number + + /** + * The line number in the input. + */ + line: number + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { CssSyntaxError_ as default } +} + +/** + * The CSS parser throws this error for broken CSS. + * + * Custom parsers can throw this error for broken custom syntax using + * the `Node#error` method. + * + * PostCSS will use the input source map to detect the original error location. + * If you wrote a Sass file, compiled it to CSS and then parsed it with PostCSS, + * PostCSS will show the original position in the Sass file. + * + * If you need the position in the PostCSS input + * (e.g., to debug the previous compiler), use `error.input.file`. + * + * ```js + * // Raising error from plugin + * throw node.error('Unknown variable', { plugin: 'postcss-vars' }) + * ``` + * + * ```js + * // Catching and checking syntax error + * try { + * postcss.parse('a{') + * } catch (error) { + * if (error.name === 'CssSyntaxError') { + * error //=> CssSyntaxError + * } + * } + * ``` + */ +declare class CssSyntaxError_ extends Error { + /** + * Source column of the error. + * + * ```js + * error.column //=> 1 + * error.input.column //=> 4 + * ``` + * + * PostCSS will use the input source map to detect the original location. + * If you need the position in the PostCSS input, use `error.input.column`. + */ + column?: number + + /** + * Source column of the error's end, exclusive. Provided if the error pertains + * to a range. + * + * ```js + * error.endColumn //=> 1 + * error.input.endColumn //=> 4 + * ``` + * + * PostCSS will use the input source map to detect the original location. + * If you need the position in the PostCSS input, use `error.input.endColumn`. + */ + endColumn?: number + + /** + * Source line of the error's end, exclusive. Provided if the error pertains + * to a range. + * + * ```js + * error.endLine //=> 3 + * error.input.endLine //=> 4 + * ``` + * + * PostCSS will use the input source map to detect the original location. + * If you need the position in the PostCSS input, use `error.input.endLine`. + */ + endLine?: number + + /** + * Absolute path to the broken file. + * + * ```js + * error.file //=> 'a.sass' + * error.input.file //=> 'a.css' + * ``` + * + * PostCSS will use the input source map to detect the original location. + * If you need the position in the PostCSS input, use `error.input.file`. + */ + file?: string + + /** + * Input object with PostCSS internal information + * about input file. If input has source map + * from previous tool, PostCSS will use origin + * (for example, Sass) source. You can use this + * object to get PostCSS input source. + * + * ```js + * error.input.file //=> 'a.css' + * error.file //=> 'a.sass' + * ``` + */ + input?: FilePosition + + /** + * Source line of the error. + * + * ```js + * error.line //=> 2 + * error.input.line //=> 4 + * ``` + * + * PostCSS will use the input source map to detect the original location. + * If you need the position in the PostCSS input, use `error.input.line`. + */ + line?: number + + /** + * Full error text in the GNU error format + * with plugin, file, line and column. + * + * ```js + * error.message //=> 'a.css:1:1: Unclosed block' + * ``` + */ + message: string + + /** + * Always equal to `'CssSyntaxError'`. You should always check error type + * by `error.name === 'CssSyntaxError'` + * instead of `error instanceof CssSyntaxError`, + * because npm could have several PostCSS versions. + * + * ```js + * if (error.name === 'CssSyntaxError') { + * error //=> CssSyntaxError + * } + * ``` + */ + name: 'CssSyntaxError' + + /** + * Plugin name, if error came from plugin. + * + * ```js + * error.plugin //=> 'postcss-vars' + * ``` + */ + plugin?: string + + /** + * Error message. + * + * ```js + * error.message //=> 'Unclosed block' + * ``` + */ + reason: string + + /** + * Source code of the broken file. + * + * ```js + * error.source //=> 'a { b {} }' + * error.input.source //=> 'a b { }' + * ``` + */ + source?: string + + stack: string + + /** + * Instantiates a CSS syntax error. Can be instantiated for a single position + * or for a range. + * @param message Error message. + * @param lineOrStartPos If for a single position, the line number, or if for + * a range, the inclusive start position of the error. + * @param columnOrEndPos If for a single position, the column number, or if for + * a range, the exclusive end position of the error. + * @param source Source code of the broken file. + * @param file Absolute path to the broken file. + * @param plugin PostCSS plugin name, if error came from plugin. + */ + constructor( + message: string, + lineOrStartPos?: CssSyntaxError.RangePosition | number, + columnOrEndPos?: CssSyntaxError.RangePosition | number, + source?: string, + file?: string, + plugin?: string + ) + + /** + * Returns a few lines of CSS source that caused the error. + * + * If the CSS has an input source map without `sourceContent`, + * this method will return an empty string. + * + * ```js + * error.showSourceCode() //=> " 4 | } + * // 5 | a { + * // > 6 | bad + * // | ^ + * // 7 | } + * // 8 | b {" + * ``` + * + * @param color Whether arrow will be colored red by terminal + * color codes. By default, PostCSS will detect + * color support by `process.stdout.isTTY` + * and `process.env.NODE_DISABLE_COLORS`. + * @return Few lines of CSS source that caused the error. + */ + showSourceCode(color?: boolean): string + + /** + * Returns error position, message and source code of the broken part. + * + * ```js + * error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block + * // > 1 | a { + * // | ^" + * ``` + * + * @return Error position, message and source code. + */ + toString(): string +} + +declare class CssSyntaxError extends CssSyntaxError_ {} + +export = CssSyntaxError diff --git a/src/postcss/declaration.d.ts b/src/postcss/declaration.d.ts new file mode 100644 index 0000000..1c6821f --- /dev/null +++ b/src/postcss/declaration.d.ts @@ -0,0 +1,152 @@ +import { ContainerWithChildren } from './container.js' +import Node from './node.js' + +declare namespace Declaration { + export interface DeclarationRaws extends Record { + /** + * The space symbols before the node. It also stores `*` + * and `_` symbols before the declaration (IE hack). + */ + before?: string + + /** + * The symbols between the property and value for declarations. + */ + between?: string + + /** + * The content of the important statement, if it is not just `!important`. + */ + important?: string + + /** + * Declaration value with comments. + */ + value?: { + raw: string + value: string + } + } + + export interface DeclarationProps { + /** Whether the declaration has an `!important` annotation. */ + important?: boolean + /** Name of the declaration. */ + prop: string + /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ + raws?: DeclarationRaws + /** Value of the declaration. */ + value: string + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Declaration_ as default } +} + +/** + * It represents a class that handles + * [CSS declarations](https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax#css_declarations) + * + * ```js + * Once (root, { Declaration }) { + * const color = new Declaration({ prop: 'color', value: 'black' }) + * root.append(color) + * } + * ``` + * + * ```js + * const root = postcss.parse('a { color: black }') + * const decl = root.first?.first + * + * decl.type //=> 'decl' + * decl.toString() //=> ' color: black' + * ``` + */ +declare class Declaration_ extends Node { + /** + * It represents a specificity of the declaration. + * + * If true, the CSS declaration will have an + * [important](https://developer.mozilla.org/en-US/docs/Web/CSS/important) + * specifier. + * + * ```js + * const root = postcss.parse('a { color: black !important; color: red }') + * + * root.first.first.important //=> true + * root.first.last.important //=> undefined + * ``` + */ + get important(): boolean + set important(value: boolean) + + parent: ContainerWithChildren | undefined + + /** + * The property name for a CSS declaration. + * + * ```js + * const root = postcss.parse('a { color: black }') + * const decl = root.first.first + * + * decl.prop //=> 'color' + * ``` + */ + get prop(): string + set prop(value: string) + + raws: Declaration.DeclarationRaws + + type: 'decl' + + /** + * The property value for a CSS declaration. + * + * Any CSS comments inside the value string will be filtered out. + * CSS comments present in the source value will be available in + * the `raws` property. + * + * Assigning new `value` would ignore the comments in `raws` + * property while compiling node to string. + * + * ```js + * const root = postcss.parse('a { color: black }') + * const decl = root.first.first + * + * decl.value //=> 'black' + * ``` + */ + get value(): string + set value(value: string) + + /** + * It represents a getter that returns `true` if a declaration starts with + * `--` or `$`, which are used to declare variables in CSS and SASS/SCSS. + * + * ```js + * const root = postcss.parse(':root { --one: 1 }') + * const one = root.first.first + * + * one.variable //=> true + * ``` + * + * ```js + * const root = postcss.parse('$one: 1') + * const one = root.first + * + * one.variable //=> true + * ``` + */ + get variable(): boolean + set varaible(value: string) + + constructor(defaults?: Declaration.DeclarationProps) + assign(overrides: Declaration.DeclarationProps | object): this + clone(overrides?: Partial): Declaration + cloneAfter(overrides?: Partial): Declaration + cloneBefore(overrides?: Partial): Declaration +} + +declare class Declaration extends Declaration_ {} + +export = Declaration diff --git a/src/postcss/document.d.ts b/src/postcss/document.d.ts new file mode 100644 index 0000000..a368f16 --- /dev/null +++ b/src/postcss/document.d.ts @@ -0,0 +1,69 @@ +import Container, { ContainerProps } from './container.js' +import { ProcessOptions } from './postcss.js' +import Result from './result.js' +import Root from './root.js' + +declare namespace Document { + export interface DocumentProps extends ContainerProps { + nodes?: Root[] + + /** + * Information to generate byte-to-byte equal node string as it was + * in the origin input. + * + * Every parser saves its own properties. + */ + raws?: Record + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Document_ as default } +} + +/** + * Represents a file and contains all its parsed nodes. + * + * **Experimental:** some aspects of this node could change within minor + * or patch version releases. + * + * ```js + * const document = htmlParser( + * '' + * ) + * document.type //=> 'document' + * document.nodes.length //=> 2 + * ``` + */ +declare class Document_ extends Container { + nodes: Root[] + parent: undefined + type: 'document' + + constructor(defaults?: Document.DocumentProps) + + assign(overrides: Document.DocumentProps | object): this + clone(overrides?: Partial): Document + cloneAfter(overrides?: Partial): Document + cloneBefore(overrides?: Partial): Document + + /** + * Returns a `Result` instance representing the document’s CSS roots. + * + * ```js + * const root1 = postcss.parse(css1, { from: 'a.css' }) + * const root2 = postcss.parse(css2, { from: 'b.css' }) + * const document = postcss.document() + * document.append(root1) + * document.append(root2) + * const result = document.toResult({ to: 'all.css', map: true }) + * ``` + * + * @param opts Options. + * @return Result with current document’s CSS. + */ + toResult(options?: ProcessOptions): Result +} + +declare class Document extends Document_ {} + +export = Document diff --git a/src/postcss/fromJSON.d.ts b/src/postcss/fromJSON.d.ts new file mode 100644 index 0000000..e1deedb --- /dev/null +++ b/src/postcss/fromJSON.d.ts @@ -0,0 +1,9 @@ +import { JSONHydrator } from './postcss.js' + +interface FromJSON extends JSONHydrator { + default: FromJSON +} + +declare const fromJSON: FromJSON + +export = fromJSON diff --git a/src/postcss/input.d.ts b/src/postcss/input.d.ts new file mode 100644 index 0000000..c718bd1 --- /dev/null +++ b/src/postcss/input.d.ts @@ -0,0 +1,194 @@ +import { CssSyntaxError, ProcessOptions } from './postcss.js' +import PreviousMap from './previous-map.js' + +declare namespace Input { + export interface FilePosition { + /** + * Column of inclusive start position in source file. + */ + column: number + + /** + * Column of exclusive end position in source file. + */ + endColumn?: number + + /** + * Line of exclusive end position in source file. + */ + endLine?: number + + /** + * Absolute path to the source file. + */ + file?: string + + /** + * Line of inclusive start position in source file. + */ + line: number + + /** + * Source code. + */ + source?: string + + /** + * URL for the source file. + */ + url: string + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Input_ as default } +} + +/** + * Represents the source CSS. + * + * ```js + * const root = postcss.parse(css, { from: file }) + * const input = root.source.input + * ``` + */ +declare class Input_ { + /** + * Input CSS source. + * + * ```js + * const input = postcss.parse('a{}', { from: file }).input + * input.css //=> "a{}" + * ``` + */ + css: string + + /** + * The absolute path to the CSS source file defined + * with the `from` option. + * + * ```js + * const root = postcss.parse(css, { from: 'a.css' }) + * root.source.input.file //=> '/home/ai/a.css' + * ``` + */ + file?: string + + /** + * The flag to indicate whether or not the source code has Unicode BOM. + */ + hasBOM: boolean + + /** + * The unique ID of the CSS source. It will be created if `from` option + * is not provided (because PostCSS does not know the file path). + * + * ```js + * const root = postcss.parse(css) + * root.source.input.file //=> undefined + * root.source.input.id //=> "" + * ``` + */ + id?: string + + /** + * The input source map passed from a compilation step before PostCSS + * (for example, from Sass compiler). + * + * ```js + * root.source.input.map.consumer().sources //=> ['a.sass'] + * ``` + */ + map: PreviousMap + + /** + * @param css Input CSS source. + * @param opts Process options. + */ + constructor(css: string, opts?: ProcessOptions) + + error( + message: string, + start: + | { + column: number + line: number + } + | { + offset: number + }, + end: + | { + column: number + line: number + } + | { + offset: number + }, + opts?: { plugin?: CssSyntaxError['plugin'] } + ): CssSyntaxError + + /** + * Returns `CssSyntaxError` with information about the error and its position. + */ + error( + message: string, + line: number, + column: number, + opts?: { plugin?: CssSyntaxError['plugin'] } + ): CssSyntaxError + + error( + message: string, + offset: number, + opts?: { plugin?: CssSyntaxError['plugin'] } + ): CssSyntaxError + + /** + * Converts source offset to line and column. + * + * @param offset Source offset. + */ + fromOffset(offset: number): { col: number; line: number } | null + /** + * Reads the input source map and returns a symbol position + * in the input source (e.g., in a Sass file that was compiled + * to CSS before being passed to PostCSS). Optionally takes an + * end position, exclusive. + * + * ```js + * root.source.input.origin(1, 1) //=> { file: 'a.css', line: 3, column: 1 } + * root.source.input.origin(1, 1, 1, 4) + * //=> { file: 'a.css', line: 3, column: 1, endLine: 3, endColumn: 4 } + * ``` + * + * @param line Line for inclusive start position in input CSS. + * @param column Column for inclusive start position in input CSS. + * @param endLine Line for exclusive end position in input CSS. + * @param endColumn Column for exclusive end position in input CSS. + * + * @return Position in input source. + */ + origin( + line: number, + column: number, + endLine?: number, + endColumn?: number + ): false | Input.FilePosition + /** + * The CSS source identifier. Contains `Input#file` if the user + * set the `from` option, or `Input#id` if they did not. + * + * ```js + * const root = postcss.parse(css, { from: 'a.css' }) + * root.source.input.from //=> "/home/ai/a.css" + * + * const root = postcss.parse(css) + * root.source.input.from //=> "" + * ``` + */ + get from(): string +} + +declare class Input extends Input_ {} + +export = Input diff --git a/src/postcss/lazy-result.d.ts b/src/postcss/lazy-result.d.ts new file mode 100644 index 0000000..dd291aa --- /dev/null +++ b/src/postcss/lazy-result.d.ts @@ -0,0 +1,190 @@ +import Document from './document.js' +import { SourceMap } from './postcss.js' +import Processor from './processor.js' +import Result, { Message, ResultOptions } from './result.js' +import Root from './root.js' +import Warning from './warning.js' + +declare namespace LazyResult { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { LazyResult_ as default } +} + +/** + * A Promise proxy for the result of PostCSS transformations. + * + * A `LazyResult` instance is returned by `Processor#process`. + * + * ```js + * const lazy = postcss([autoprefixer]).process(css) + * ``` + */ +declare class LazyResult_ + implements PromiseLike> +{ + /** + * Processes input CSS through synchronous and asynchronous plugins + * and calls onRejected for each error thrown in any plugin. + * + * It implements standard Promise API. + * + * ```js + * postcss([autoprefixer]).process(css).then(result => { + * console.log(result.css) + * }).catch(error => { + * console.error(error) + * }) + * ``` + */ + catch: Promise>['catch'] + + /** + * Processes input CSS through synchronous and asynchronous plugins + * and calls onFinally on any error or when all plugins will finish work. + * + * It implements standard Promise API. + * + * ```js + * postcss([autoprefixer]).process(css).finally(() => { + * console.log('processing ended') + * }) + * ``` + */ + finally: Promise>['finally'] + + /** + * Processes input CSS through synchronous and asynchronous plugins + * and calls `onFulfilled` with a Result instance. If a plugin throws + * an error, the `onRejected` callback will be executed. + * + * It implements standard Promise API. + * + * ```js + * postcss([autoprefixer]).process(css, { from: cssPath }).then(result => { + * console.log(result.css) + * }) + * ``` + */ + then: Promise>['then'] + + /** + * @param processor Processor used for this transformation. + * @param css CSS to parse and transform. + * @param opts Options from the `Processor#process` or `Root#toResult`. + */ + constructor(processor: Processor, css: string, opts: ResultOptions) + + /** + * Run plugin in async way and return `Result`. + * + * @return Result with output content. + */ + async(): Promise> + + /** + * Run plugin in sync way and return `Result`. + * + * @return Result with output content. + */ + sync(): Result + + /** + * Alias for the `LazyResult#css` property. + * + * ```js + * lazy + '' === lazy.css + * ``` + * + * @return Output CSS. + */ + toString(): string + + /** + * Processes input CSS through synchronous plugins + * and calls `Result#warnings`. + * + * @return Warnings from plugins. + */ + warnings(): Warning[] + + /** + * An alias for the `css` property. Use it with syntaxes + * that generate non-CSS output. + * + * This property will only work with synchronous plugins. + * If the processor contains any asynchronous plugins + * it will throw an error. + * + * PostCSS runners should always use `LazyResult#then`. + */ + get content(): string + + /** + * Processes input CSS through synchronous plugins, converts `Root` + * to a CSS string and returns `Result#css`. + * + * This property will only work with synchronous plugins. + * If the processor contains any asynchronous plugins + * it will throw an error. + * + * PostCSS runners should always use `LazyResult#then`. + */ + get css(): string + + /** + * Processes input CSS through synchronous plugins + * and returns `Result#map`. + * + * This property will only work with synchronous plugins. + * If the processor contains any asynchronous plugins + * it will throw an error. + * + * PostCSS runners should always use `LazyResult#then`. + */ + get map(): SourceMap + + /** + * Processes input CSS through synchronous plugins + * and returns `Result#messages`. + * + * This property will only work with synchronous plugins. If the processor + * contains any asynchronous plugins it will throw an error. + * + * PostCSS runners should always use `LazyResult#then`. + */ + get messages(): Message[] + + /** + * Options from the `Processor#process` call. + */ + get opts(): ResultOptions + + /** + * Returns a `Processor` instance, which will be used + * for CSS transformations. + */ + get processor(): Processor + + /** + * Processes input CSS through synchronous plugins + * and returns `Result#root`. + * + * This property will only work with synchronous plugins. If the processor + * contains any asynchronous plugins it will throw an error. + * + * PostCSS runners should always use `LazyResult#then`. + */ + get root(): RootNode + + /** + * Returns the default string description of an object. + * Required to implement the Promise interface. + */ + get [Symbol.toStringTag](): string +} + +declare class LazyResult< + RootNode = Document | Root +> extends LazyResult_ {} + +export = LazyResult diff --git a/src/postcss/list.d.ts b/src/postcss/list.d.ts new file mode 100644 index 0000000..1a74d74 --- /dev/null +++ b/src/postcss/list.d.ts @@ -0,0 +1,57 @@ +declare namespace list { + type List = { + /** + * Safely splits comma-separated values (such as those for `transition-*` + * and `background` properties). + * + * ```js + * Once (root, { list }) { + * list.comma('black, linear-gradient(white, black)') + * //=> ['black', 'linear-gradient(white, black)'] + * } + * ``` + * + * @param str Comma-separated values. + * @return Split values. + */ + comma(str: string): string[] + + default: List + + /** + * Safely splits space-separated values (such as those for `background`, + * `border-radius`, and other shorthand properties). + * + * ```js + * Once (root, { list }) { + * list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)'] + * } + * ``` + * + * @param str Space-separated values. + * @return Split values. + */ + space(str: string): string[] + + /** + * Safely splits values. + * + * ```js + * Once (root, { list }) { + * list.split('1px calc(10% + 1px)', [' ', '\n', '\t']) //=> ['1px', 'calc(10% + 1px)'] + * } + * ``` + * + * @param string separated values. + * @param separators array of separators. + * @param last boolean indicator. + * @return Split values. + */ + split(string: string, separators: string[], last: boolean): string[] + } +} + +// eslint-disable-next-line @typescript-eslint/no-redeclare +declare const list: list.List + +export = list diff --git a/src/postcss/no-work-result.d.ts b/src/postcss/no-work-result.d.ts new file mode 100644 index 0000000..8039076 --- /dev/null +++ b/src/postcss/no-work-result.d.ts @@ -0,0 +1,46 @@ +import LazyResult from './lazy-result.js' +import { SourceMap } from './postcss.js' +import Processor from './processor.js' +import Result, { Message, ResultOptions } from './result.js' +import Root from './root.js' +import Warning from './warning.js' + +declare namespace NoWorkResult { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { NoWorkResult_ as default } +} + +/** + * A Promise proxy for the result of PostCSS transformations. + * This lazy result instance doesn't parse css unless `NoWorkResult#root` or `Result#root` + * are accessed. See the example below for details. + * A `NoWork` instance is returned by `Processor#process` ONLY when no plugins defined. + * + * ```js + * const noWorkResult = postcss().process(css) // No plugins are defined. + * // CSS is not parsed + * let root = noWorkResult.root // now css is parsed because we accessed the root + * ``` + */ +declare class NoWorkResult_ implements LazyResult { + catch: Promise>['catch'] + finally: Promise>['finally'] + then: Promise>['then'] + constructor(processor: Processor, css: string, opts: ResultOptions) + async(): Promise> + sync(): Result + toString(): string + warnings(): Warning[] + get content(): string + get css(): string + get map(): SourceMap + get messages(): Message[] + get opts(): ResultOptions + get processor(): Processor + get root(): Root + get [Symbol.toStringTag](): string +} + +declare class NoWorkResult extends NoWorkResult_ {} + +export = NoWorkResult diff --git a/src/postcss/node.d.ts b/src/postcss/node.d.ts new file mode 100644 index 0000000..5971656 --- /dev/null +++ b/src/postcss/node.d.ts @@ -0,0 +1,536 @@ +import AtRule = require('./at-rule.js') + +import { AtRuleProps } from './at-rule.js' +import Comment, { CommentProps } from './comment.js' +import Container from './container.js' +import CssSyntaxError from './css-syntax-error.js' +import Declaration, { DeclarationProps } from './declaration.js' +import Document from './document.js' +import Input from './input.js' +import { Stringifier, Syntax } from './postcss.js' +import Result from './result.js' +import Root from './root.js' +import Rule, { RuleProps } from './rule.js' +import Warning, { WarningOptions } from './warning.js' + +declare namespace Node { + export type ChildNode = AtRule.default | Comment | Declaration | Rule + + export type AnyNode = + | AtRule.default + | Comment + | Declaration + | Document + | Root + | Rule + + export type ChildProps = + | AtRuleProps + | CommentProps + | DeclarationProps + | RuleProps + + export interface Position { + /** + * Source line in file. In contrast to `offset` it starts from 1. + */ + column: number + + /** + * Source column in file. + */ + line: number + + /** + * Source offset in file. It starts from 0. + */ + offset: number + } + + export interface Range { + /** + * End position, exclusive. + */ + end: Position + + /** + * Start position, inclusive. + */ + start: Position + } + + /** + * Source represents an interface for the {@link Node.source} property. + */ + export interface Source { + /** + * The inclusive ending position for the source + * code of a node. + */ + end?: Position + + /** + * The source file from where a node has originated. + */ + input: Input + + /** + * The inclusive starting position for the source + * code of a node. + */ + start?: Position + } + + /** + * Interface represents an interface for an object received + * as parameter by Node class constructor. + */ + export interface NodeProps { + source?: Source + } + + export interface NodeErrorOptions { + /** + * An ending index inside a node's string that should be highlighted as + * source of error. + */ + endIndex?: number + /** + * An index inside a node's string that should be highlighted as source + * of error. + */ + index?: number + /** + * Plugin name that created this error. PostCSS will set it automatically. + */ + plugin?: string + /** + * A word inside a node's string, that should be highlighted as source + * of error. + */ + word?: string + } + + // eslint-disable-next-line @typescript-eslint/no-shadow + class Node extends Node_ {} + export { Node as default } +} + +/** + * It represents an abstract class that handles common + * methods for other CSS abstract syntax tree nodes. + * + * Any node that represents CSS selector or value should + * not extend the `Node` class. + */ +declare abstract class Node_ { + /** + * It represents parent of the current node. + * + * ```js + * root.nodes[0].parent === root //=> true + * ``` + */ + parent: Container | Document | undefined + + /** + * It represents unnecessary whitespace and characters present + * in the css source code. + * + * Information to generate byte-to-byte equal node string as it was + * in the origin input. + * + * The properties of the raws object are decided by parser, + * the default parser uses the following properties: + * + * * `before`: the space symbols before the node. It also stores `*` + * and `_` symbols before the declaration (IE hack). + * * `after`: the space symbols after the last child of the node + * to the end of the node. + * * `between`: the symbols between the property and value + * for declarations, selector and `{` for rules, or last parameter + * and `{` for at-rules. + * * `semicolon`: contains true if the last child has + * an (optional) semicolon. + * * `afterName`: the space between the at-rule name and its parameters. + * * `left`: the space symbols between `/*` and the comment’s text. + * * `right`: the space symbols between the comment’s text + * and */. + * - `important`: the content of the important statement, + * if it is not just `!important`. + * + * PostCSS filters out the comments inside selectors, declaration values + * and at-rule parameters but it stores the origin content in raws. + * + * ```js + * const root = postcss.parse('a {\n color:black\n}') + * root.first.first.raws //=> { before: '\n ', between: ':' } + * ``` + */ + raws: any + + /** + * It represents information related to origin of a node and is required + * for generating source maps. + * + * The nodes that are created manually using the public APIs + * provided by PostCSS will have `source` undefined and + * will be absent in the source map. + * + * For this reason, the plugin developer should consider + * duplicating nodes as the duplicate node will have the + * same source as the original node by default or assign + * source to a node created manually. + * + * ```js + * decl.source.input.from //=> '/home/ai/source.css' + * decl.source.start //=> { line: 10, column: 2 } + * decl.source.end //=> { line: 10, column: 12 } + * ``` + * + * ```js + * // Incorrect method, source not specified! + * const prefixed = postcss.decl({ + * prop: '-moz-' + decl.prop, + * value: decl.value + * }) + * + * // Correct method, source is inherited when duplicating. + * const prefixed = decl.clone({ + * prop: '-moz-' + decl.prop + * }) + * ``` + * + * ```js + * if (atrule.name === 'add-link') { + * const rule = postcss.rule({ + * selector: 'a', + * source: atrule.source + * }) + * + * atrule.parent.insertBefore(atrule, rule) + * } + * ``` + */ + source?: Node.Source + + /** + * It represents type of a node in + * an abstract syntax tree. + * + * A type of node helps in identification of a node + * and perform operation based on it's type. + * + * ```js + * const declaration = new Declaration({ + * prop: 'color', + * value: 'black' + * }) + * + * declaration.type //=> 'decl' + * ``` + */ + type: string + + constructor(defaults?: object) + + /** + * Insert new node after current node to current node’s parent. + * + * Just alias for `node.parent.insertAfter(node, add)`. + * + * ```js + * decl.after('color: black') + * ``` + * + * @param newNode New node. + * @return This node for methods chain. + */ + after(newNode: Node | Node.ChildProps | Node[] | string | undefined): this + + /** + * It assigns properties to an existing node instance. + * + * ```js + * decl.assign({ prop: 'word-wrap', value: 'break-word' }) + * ``` + * + * @param overrides New properties to override the node. + * + * @return `this` for method chaining. + */ + assign(overrides: object): this + + /** + * Insert new node before current node to current node’s parent. + * + * Just alias for `node.parent.insertBefore(node, add)`. + * + * ```js + * decl.before('content: ""') + * ``` + * + * @param newNode New node. + * @return This node for methods chain. + */ + before(newNode: Node | Node.ChildProps | Node[] | string | undefined): this + + /** + * Clear the code style properties for the node and its children. + * + * ```js + * node.raws.before //=> ' ' + * node.cleanRaws() + * node.raws.before //=> undefined + * ``` + * + * @param keepBetween Keep the `raws.between` symbols. + */ + cleanRaws(keepBetween?: boolean): void + + /** + * It creates clone of an existing node, which includes all the properties + * and their values, that includes `raws` but not `type`. + * + * ```js + * decl.raws.before //=> "\n " + * const cloned = decl.clone({ prop: '-moz-' + decl.prop }) + * cloned.raws.before //=> "\n " + * cloned.toString() //=> -moz-transform: scale(0) + * ``` + * + * @param overrides New properties to override in the clone. + * + * @return Duplicate of the node instance. + */ + clone(overrides?: object): Node + + /** + * Shortcut to clone the node and insert the resulting cloned node + * after the current node. + * + * @param overrides New properties to override in the clone. + * @return New node. + */ + cloneAfter(overrides?: object): Node + + /** + * Shortcut to clone the node and insert the resulting cloned node + * before the current node. + * + * ```js + * decl.cloneBefore({ prop: '-moz-' + decl.prop }) + * ``` + * + * @param overrides Mew properties to override in the clone. + * + * @return New node + */ + cloneBefore(overrides?: object): Node + + /** + * It creates an instance of the class `CssSyntaxError` and parameters passed + * to this method are assigned to the error instance. + * + * The error instance will have description for the + * error, original position of the node in the + * source, showing line and column number. + * + * If any previous map is present, it would be used + * to get original position of the source. + * + * The Previous Map here is referred to the source map + * generated by previous compilation, example: Less, + * Stylus and Sass. + * + * This method returns the error instance instead of + * throwing it. + * + * ```js + * if (!variables[name]) { + * throw decl.error(`Unknown variable ${name}`, { word: name }) + * // CssSyntaxError: postcss-vars:a.sass:4:3: Unknown variable $black + * // color: $black + * // a + * // ^ + * // background: white + * } + * ``` + * + * @param message Description for the error instance. + * @param options Options for the error instance. + * + * @return Error instance is returned. + */ + error(message: string, options?: Node.NodeErrorOptions): CssSyntaxError + + /** + * Returns the next child of the node’s parent. + * Returns `undefined` if the current node is the last child. + * + * ```js + * if (comment.text === 'delete next') { + * const next = comment.next() + * if (next) { + * next.remove() + * } + * } + * ``` + * + * @return Next node. + */ + next(): Node.ChildNode | undefined + + /** + * Get the position for a word or an index inside the node. + * + * @param opts Options. + * @return Position. + */ + positionBy(opts?: Pick): Node.Position + + /** + * Convert string index to line/column. + * + * @param index The symbol number in the node’s string. + * @return Symbol position in file. + */ + positionInside(index: number): Node.Position + + /** + * Returns the previous child of the node’s parent. + * Returns `undefined` if the current node is the first child. + * + * ```js + * const annotation = decl.prev() + * if (annotation.type === 'comment') { + * readAnnotation(annotation.text) + * } + * ``` + * + * @return Previous node. + */ + prev(): Node.ChildNode | undefined + + /** + * Get the range for a word or start and end index inside the node. + * The start index is inclusive; the end index is exclusive. + * + * @param opts Options. + * @return Range. + */ + rangeBy( + opts?: Pick + ): Node.Range + + /** + * Returns a `raws` value. If the node is missing + * the code style property (because the node was manually built or cloned), + * PostCSS will try to autodetect the code style property by looking + * at other nodes in the tree. + * + * ```js + * const root = postcss.parse('a { background: white }') + * root.nodes[0].append({ prop: 'color', value: 'black' }) + * root.nodes[0].nodes[1].raws.before //=> undefined + * root.nodes[0].nodes[1].raw('before') //=> ' ' + * ``` + * + * @param prop Name of code style property. + * @param defaultType Name of default value, it can be missed + * if the value is the same as prop. + * @return {string} Code style value. + */ + raw(prop: string, defaultType?: string): string + + /** + * It removes the node from its parent and deletes its parent property. + * + * ```js + * if (decl.prop.match(/^-webkit-/)) { + * decl.remove() + * } + * ``` + * + * @return `this` for method chaining. + */ + remove(): this + + /** + * Inserts node(s) before the current node and removes the current node. + * + * ```js + * AtRule: { + * mixin: atrule => { + * atrule.replaceWith(mixinRules[atrule.params]) + * } + * } + * ``` + * + * @param nodes Mode(s) to replace current one. + * @return Current node to methods chain. + */ + replaceWith( + ...nodes: ( + | Node.ChildNode + | Node.ChildNode[] + | Node.ChildProps + | Node.ChildProps[] + )[] + ): this + + /** + * Finds the Root instance of the node’s tree. + * + * ```js + * root.nodes[0].nodes[0].root() === root + * ``` + * + * @return Root parent. + */ + root(): Root + + /** + * Fix circular links on `JSON.stringify()`. + * + * @return Cleaned object. + */ + toJSON(): object + + /** + * It compiles the node to browser readable cascading style sheets string + * depending on it's type. + * + * ```js + * new Rule({ selector: 'a' }).toString() //=> "a {}" + * ``` + * + * @param stringifier A syntax to use in string generation. + * @return CSS string of this node. + */ + toString(stringifier?: Stringifier | Syntax): string + + /** + * It is a wrapper for {@link Result#warn}, providing convenient + * way of generating warnings. + * + * ```js + * Declaration: { + * bad: (decl, { result }) => { + * decl.warn(result, 'Deprecated property: bad') + * } + * } + * ``` + * + * @param result The `Result` instance that will receive the warning. + * @param message Description for the warning. + * @param options Options for the warning. + * + * @return `Warning` instance is returned + */ + warn(result: Result, message: string, options?: WarningOptions): Warning +} + +declare class Node extends Node_ {} + +export = Node diff --git a/src/postcss/parse.d.ts b/src/postcss/parse.d.ts new file mode 100644 index 0000000..4c943a4 --- /dev/null +++ b/src/postcss/parse.d.ts @@ -0,0 +1,9 @@ +import { Parser } from './postcss.js' + +interface Parse extends Parser { + default: Parse +} + +declare const parse: Parse + +export = parse diff --git a/src/postcss/postcss.d.ts b/src/postcss/postcss.d.ts new file mode 100644 index 0000000..72da1c3 --- /dev/null +++ b/src/postcss/postcss.d.ts @@ -0,0 +1,436 @@ +import AtRule, { AtRuleProps } from './at-rule.js' +import Comment, { CommentProps } from './comment.js' +import Container, { ContainerProps } from './container.js' +import CssSyntaxError from './css-syntax-error.js' +import Declaration, { DeclarationProps } from './declaration.js' +import Document, { DocumentProps } from './document.js' +import Input, { FilePosition } from './input.js' +import LazyResult from './lazy-result.js' +import list from './list.js' +import Node, { + AnyNode, + ChildNode, + ChildProps, + NodeErrorOptions, + NodeProps, + Position, + Source +} from './node.js' +import Processor from './processor.js' +import Result, { Message } from './result.js' +import Root, { RootProps } from './root.js' +import Rule, { RuleProps } from './rule.js' +import Warning, { WarningOptions } from './warning.js' + +type DocumentProcessor = ( + document: Document, + helper: postcss.Helpers +) => Promise | void +type RootProcessor = (root: Root, helper: postcss.Helpers) => Promise | void +type DeclarationProcessor = ( + decl: Declaration, + helper: postcss.Helpers +) => Promise | void +type RuleProcessor = (rule: Rule, helper: postcss.Helpers) => Promise | void +type AtRuleProcessor = (atRule: AtRule, helper: postcss.Helpers) => Promise | void +type CommentProcessor = ( + comment: Comment, + helper: postcss.Helpers +) => Promise | void + +interface Processors { + /** + * Will be called on all`AtRule` nodes. + * + * Will be called again on node or children changes. + */ + AtRule?: { [name: string]: AtRuleProcessor } | AtRuleProcessor + + /** + * Will be called on all `AtRule` nodes, when all children will be processed. + * + * Will be called again on node or children changes. + */ + AtRuleExit?: { [name: string]: AtRuleProcessor } | AtRuleProcessor + + /** + * Will be called on all `Comment` nodes. + * + * Will be called again on node or children changes. + */ + Comment?: CommentProcessor + + /** + * Will be called on all `Comment` nodes after listeners + * for `Comment` event. + * + * Will be called again on node or children changes. + */ + CommentExit?: CommentProcessor + + /** + * Will be called on all `Declaration` nodes after listeners + * for `Declaration` event. + * + * Will be called again on node or children changes. + */ + Declaration?: { [prop: string]: DeclarationProcessor } | DeclarationProcessor + + /** + * Will be called on all `Declaration` nodes. + * + * Will be called again on node or children changes. + */ + DeclarationExit?: + | { [prop: string]: DeclarationProcessor } + | DeclarationProcessor + + /** + * Will be called on `Document` node. + * + * Will be called again on children changes. + */ + Document?: DocumentProcessor + + /** + * Will be called on `Document` node, when all children will be processed. + * + * Will be called again on children changes. + */ + DocumentExit?: DocumentProcessor + + /** + * Will be called on `Root` node once. + */ + Once?: RootProcessor + + /** + * Will be called on `Root` node once, when all children will be processed. + */ + OnceExit?: RootProcessor + + /** + * Will be called on `Root` node. + * + * Will be called again on children changes. + */ + Root?: RootProcessor + + /** + * Will be called on `Root` node, when all children will be processed. + * + * Will be called again on children changes. + */ + RootExit?: RootProcessor + + /** + * Will be called on all `Rule` nodes. + * + * Will be called again on node or children changes. + */ + Rule?: RuleProcessor + + /** + * Will be called on all `Rule` nodes, when all children will be processed. + * + * Will be called again on node or children changes. + */ + RuleExit?: RuleProcessor +} + +declare namespace postcss { + export { + AnyNode, + AtRule, + AtRuleProps, + ChildNode, + ChildProps, + Comment, + CommentProps, + Container, + ContainerProps, + CssSyntaxError, + Declaration, + DeclarationProps, + Document, + DocumentProps, + FilePosition, + Input, + LazyResult, + list, + Message, + Node, + NodeErrorOptions, + NodeProps, + Position, + Processor, + Result, + Root, + RootProps, + Rule, + RuleProps, + Source, + Warning, + WarningOptions + } + + + export type Helpers = { postcss: Postcss; result: Result } & Postcss + + export interface Plugin extends Processors { + postcssPlugin: string + prepare?: (result: Result) => Processors + } + + export interface PluginCreator { + (opts?: PluginOptions): Plugin | Processor + postcss: true + } + + export interface Transformer extends TransformCallback { + postcssPlugin: string + postcssVersion: string + } + + export interface TransformCallback { + (root: Root, result: Result): Promise | void + } + + export interface OldPlugin extends Transformer { + (opts?: T): Transformer + postcss: Transformer + } + + export type AcceptedPlugin = + | { + postcss: Processor | TransformCallback + } + | OldPlugin + | Plugin + | PluginCreator + | Processor + | TransformCallback + + export interface Parser { + ( + css: { toString(): string } | string, + opts?: Pick + ): RootNode + } + + export interface Builder { + (part: string, node?: AnyNode, type?: 'end' | 'start'): void + } + + export interface Stringifier { + (node: AnyNode, builder: Builder): void + } + + export interface JSONHydrator { + (data: object): Node + (data: object[]): Node[] + } + + export interface Syntax { + /** + * Function to generate AST by string. + */ + parse?: Parser + + /** + * Class to generate string by AST. + */ + stringify?: Stringifier + } + + export interface SourceMapOptions { + /** + * Use absolute path in generated source map. + */ + absolute?: boolean + + /** + * Indicates that PostCSS should add annotation comments to the CSS. + * By default, PostCSS will always add a comment with a path + * to the source map. PostCSS will not add annotations to CSS files + * that do not contain any comments. + * + * By default, PostCSS presumes that you want to save the source map as + * `opts.to + '.map'` and will use this path in the annotation comment. + * A different path can be set by providing a string value for annotation. + * + * If you have set `inline: true`, annotation cannot be disabled. + */ + annotation?: ((file: string, root: Root) => string) | boolean | string + + /** + * Override `from` in map’s sources. + */ + from?: string + + /** + * Indicates that the source map should be embedded in the output CSS + * as a Base64-encoded comment. By default, it is `true`. + * But if all previous maps are external, not inline, PostCSS will not embed + * the map even if you do not set this option. + * + * If you have an inline source map, the result.map property will be empty, + * as the source map will be contained within the text of `result.css`. + */ + inline?: boolean + + /** + * Source map content from a previous processing step (e.g., Sass). + * + * PostCSS will try to read the previous source map + * automatically (based on comments within the source CSS), but you can use + * this option to identify it manually. + * + * If desired, you can omit the previous map with prev: `false`. + */ + prev?: ((file: string) => string) | boolean | object | string + + /** + * Indicates that PostCSS should set the origin content (e.g., Sass source) + * of the source map. By default, it is true. But if all previous maps do not + * contain sources content, PostCSS will also leave it out even if you + * do not set this option. + */ + sourcesContent?: boolean + } + + export interface ProcessOptions { + /** + * The path of the CSS source file. You should always set `from`, + * because it is used in source map generation and syntax error messages. + */ + from?: string | undefined + + /** + * Source map options + */ + map?: boolean | SourceMapOptions + + /** + * Function to generate AST by string. + */ + parser?: Parser | Syntax + + /** + * Class to generate string by AST. + */ + stringifier?: Stringifier | Syntax + + /** + * Object with parse and stringify. + */ + syntax?: Syntax + + /** + * The path where you'll put the output CSS file. You should always set `to` + * to generate correct source maps. + */ + to?: string + } + + export type Postcss = typeof postcss + + /** + * Default function to convert a node tree into a CSS string. + */ + export let stringify: Stringifier + + /** + * Parses source css and returns a new `Root` or `Document` node, + * which contains the source CSS nodes. + * + * ```js + * // Simple CSS concatenation with source map support + * const root1 = postcss.parse(css1, { from: file1 }) + * const root2 = postcss.parse(css2, { from: file2 }) + * root1.append(root2).toResult().css + * ``` + */ + export let parse: Parser + + /** + * Rehydrate a JSON AST (from `Node#toJSON`) back into the AST classes. + * + * ```js + * const json = root.toJSON() + * // save to file, send by network, etc + * const root2 = postcss.fromJSON(json) + * ``` + */ + export let fromJSON: JSONHydrator + + /** + * Creates a new `Comment` node. + * + * @param defaults Properties for the new node. + * @return New comment node + */ + export function comment(defaults?: CommentProps): Comment + + /** + * Creates a new `AtRule` node. + * + * @param defaults Properties for the new node. + * @return New at-rule node. + */ + export function atRule(defaults?: AtRuleProps): AtRule + + /** + * Creates a new `Declaration` node. + * + * @param defaults Properties for the new node. + * @return New declaration node. + */ + export function decl(defaults?: DeclarationProps): Declaration + + /** + * Creates a new `Rule` node. + * + * @param default Properties for the new node. + * @return New rule node. + */ + export function rule(defaults?: RuleProps): Rule + + /** + * Creates a new `Root` node. + * + * @param defaults Properties for the new node. + * @return New root node. + */ + export function root(defaults?: RootProps): Root + + /** + * Creates a new `Document` node. + * + * @param defaults Properties for the new node. + * @return New document node. + */ + export function document(defaults?: DocumentProps): Document + + export { postcss as default } +} + +/** + * Create a new `Processor` instance that will apply `plugins` + * as CSS processors. + * + * ```js + * let postcss = require('postcss') + * + * postcss(plugins).process(css, { from, to }).then(result => { + * console.log(result.css) + * }) + * ``` + * + * @param plugins PostCSS plugins. + * @return Processor to process multiple CSS. + */ +declare function postcss(plugins?: postcss.AcceptedPlugin[]): Processor +declare function postcss(...plugins: postcss.AcceptedPlugin[]): Processor + +export = postcss diff --git a/src/postcss/postcss.js b/src/postcss/postcss.js new file mode 100644 index 0000000..8733a9a --- /dev/null +++ b/src/postcss/postcss.js @@ -0,0 +1,6520 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.postcss = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('=') + if (validLen === -1) validLen = len + + var placeHoldersLen = validLen === len + ? 0 + : 4 - (validLen % 4) + + return [validLen, placeHoldersLen] +} + +// base64 is 4/3 + up to two characters of the original data +function byteLength (b64) { + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function _byteLength (b64, validLen, placeHoldersLen) { + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function toByteArray (b64) { + var tmp + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) + + var curByte = 0 + + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 + ? validLen - 4 + : validLen + + var i + for (i = 0; i < len; i += 4) { + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 2) { + tmp = + (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 1) { + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + + lookup[num >> 12 & 0x3F] + + lookup[num >> 6 & 0x3F] + + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = + ((uint8[i] << 16) & 0xFF0000) + + ((uint8[i + 1] << 8) & 0xFF00) + + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + parts.push( + lookup[tmp >> 2] + + lookup[(tmp << 4) & 0x3F] + + '==' + ) + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1] + parts.push( + lookup[tmp >> 10] + + lookup[(tmp >> 4) & 0x3F] + + lookup[(tmp << 2) & 0x3F] + + '=' + ) + } + + return parts.join('') +} + +},{}],2:[function(require,module,exports){ + +},{}],3:[function(require,module,exports){ +(function (Buffer){(function (){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } } + return arr.foo() === 42 + } catch (e) { + return false + } +} + +Object.defineProperty(Buffer.prototype, 'parent', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.buffer + } +}) + +Object.defineProperty(Buffer.prototype, 'offset', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.byteOffset + } +}) + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('The value "' + length + '" is invalid for option "size"') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new TypeError( + 'The "string" argument must be of type string. Received type number' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species != null && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + if (ArrayBuffer.isView(value)) { + return fromArrayLike(value) + } + + if (value == null) { + throw TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) + } + + if (isInstance(value, ArrayBuffer) || + (value && isInstance(value.buffer, ArrayBuffer))) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'number') { + throw new TypeError( + 'The "value" argument must not be of type number. Received type number' + ) + } + + var valueOf = value.valueOf && value.valueOf() + if (valueOf != null && valueOf !== value) { + return Buffer.from(valueOf, encodingOrOffset, length) + } + + var b = fromObject(value) + if (b) return b + + if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && + typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from( + value[Symbol.toPrimitive]('string'), encodingOrOffset, length + ) + } + + throw new TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number') + } else if (size < 0) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj.length !== undefined) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true && + b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false +} + +Buffer.compare = function compare (a, b) { + if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength) + if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength) + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError( + 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array' + ) + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (isInstance(buf, Uint8Array)) { + buf = Buffer.from(buf) + } + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { + return string.byteLength + } + if (typeof string !== 'string') { + throw new TypeError( + 'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + + 'Received type ' + typeof string + ) + } + + var len = string.length + var mustMatch = (arguments.length > 2 && arguments[2] === true) + if (!mustMatch && len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) { + return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8 + } + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.toLocaleString = Buffer.prototype.toString + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim() + if (this.length > max) str += ' ... ' + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength) + } + if (!Buffer.isBuffer(target)) { + throw new TypeError( + 'The "target" argument must be one of type Buffer or Uint8Array. ' + + 'Received type ' + (typeof target) + ) + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + var strLen = string.length + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end) + } else if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (var i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, end), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if ((encoding === 'utf8' && code < 128) || + encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code + } + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : Buffer.from(val, encoding) + var len = bytes.length + if (len === 0) { + throw new TypeError('The value "' + val + + '" is invalid for argument "value"') + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0] + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass +// the `instanceof` check but they should be treated as of that type. +// See: https://github.com/feross/buffer/issues/166 +function isInstance (obj, type) { + return obj instanceof type || + (obj != null && obj.constructor != null && obj.constructor.name != null && + obj.constructor.name === type.name) +} +function numberIsNaN (obj) { + // For IE11 support + return obj !== obj // eslint-disable-line no-self-compare +} + +}).call(this)}).call(this,require("buffer").Buffer) +},{"base64-js":1,"buffer":3,"ieee754":4}],4:[function(require,module,exports){ +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = ((value * c) - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],5:[function(require,module,exports){ +let urlAlphabet = + 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' +let customAlphabet = (alphabet, defaultSize = 21) => { + return (size = defaultSize) => { + let id = '' + let i = size + while (i--) { + id += alphabet[(Math.random() * alphabet.length) | 0] + } + return id + } +} +let nanoid = (size = 21) => { + let id = '' + let i = size + while (i--) { + id += urlAlphabet[(Math.random() * 64) | 0] + } + return id +} +module.exports = { nanoid, customAlphabet } + +},{}],6:[function(require,module,exports){ +var x=String; +var create=function() {return {isColorSupported:false,reset:x,bold:x,dim:x,italic:x,underline:x,inverse:x,hidden:x,strikethrough:x,black:x,red:x,green:x,yellow:x,blue:x,magenta:x,cyan:x,white:x,gray:x,bgBlack:x,bgRed:x,bgGreen:x,bgYellow:x,bgBlue:x,bgMagenta:x,bgCyan:x,bgWhite:x}}; +module.exports=create(); +module.exports.createColors = create; + +},{}],7:[function(require,module,exports){ +'use strict' + +let Container = require('./container') + +class AtRule extends Container { + constructor(defaults) { + super(defaults) + this.type = 'atrule' + } + + append(...children) { + if (!this.proxyOf.nodes) this.nodes = [] + return super.append(...children) + } + + prepend(...children) { + if (!this.proxyOf.nodes) this.nodes = [] + return super.prepend(...children) + } +} + +module.exports = AtRule +AtRule.default = AtRule + +Container.registerAtRule(AtRule) + +},{"./container":9}],8:[function(require,module,exports){ +'use strict' + +let Node = require('./node') + +class Comment extends Node { + constructor(defaults) { + super(defaults) + this.type = 'comment' + } +} + +module.exports = Comment +Comment.default = Comment + +},{"./node":19}],9:[function(require,module,exports){ +'use strict' + +let { isClean, my } = require('./symbols') +let Declaration = require('./declaration') +let Comment = require('./comment') +let Node = require('./node') + +let parse, Rule, AtRule, Root + +function cleanSource(nodes) { + return nodes.map(i => { + if (i.nodes) i.nodes = cleanSource(i.nodes) + delete i.source + return i + }) +} + +function markTreeDirty(node) { + node[isClean] = false + if (node.proxyOf.nodes) { + for (let i of node.proxyOf.nodes) { + markTreeDirty(i) + } + } +} + +class Container extends Node { + append(...children) { + for (let child of children) { + let nodes = this.normalize(child, this.last) + for (let node of nodes) this.proxyOf.nodes.push(node) + } + + this.markDirty() + + return this + } + + cleanRaws(keepBetween) { + super.cleanRaws(keepBetween) + if (this.nodes) { + for (let node of this.nodes) node.cleanRaws(keepBetween) + } + } + + each(callback) { + if (!this.proxyOf.nodes) return undefined + let iterator = this.getIterator() + + let index, result + while (this.indexes[iterator] < this.proxyOf.nodes.length) { + index = this.indexes[iterator] + result = callback(this.proxyOf.nodes[index], index) + if (result === false) break + + this.indexes[iterator] += 1 + } + + delete this.indexes[iterator] + return result + } + + every(condition) { + return this.nodes.every(condition) + } + + getIterator() { + if (!this.lastEach) this.lastEach = 0 + if (!this.indexes) this.indexes = {} + + this.lastEach += 1 + let iterator = this.lastEach + this.indexes[iterator] = 0 + + return iterator + } + + getProxyProcessor() { + return { + get(node, prop) { + if (prop === 'proxyOf') { + return node + } else if (!node[prop]) { + return node[prop] + } else if ( + prop === 'each' || + (typeof prop === 'string' && prop.startsWith('walk')) + ) { + return (...args) => { + return node[prop]( + ...args.map(i => { + if (typeof i === 'function') { + return (child, index) => i(child.toProxy(), index) + } else { + return i + } + }) + ) + } + } else if (prop === 'every' || prop === 'some') { + return cb => { + return node[prop]((child, ...other) => + cb(child.toProxy(), ...other) + ) + } + } else if (prop === 'root') { + return () => node.root().toProxy() + } else if (prop === 'nodes') { + return node.nodes.map(i => i.toProxy()) + } else if (prop === 'first' || prop === 'last') { + return node[prop].toProxy() + } else { + return node[prop] + } + }, + + set(node, prop, value) { + if (node[prop] === value) return true + node[prop] = value + if (prop === 'name' || prop === 'params' || prop === 'selector') { + node.markDirty() + } + return true + } + } + } + + index(child) { + if (typeof child === 'number') return child + if (child.proxyOf) child = child.proxyOf + return this.proxyOf.nodes.indexOf(child) + } + + insertAfter(exist, add) { + let existIndex = this.index(exist) + let nodes = this.normalize(add, this.proxyOf.nodes[existIndex]).reverse() + existIndex = this.index(exist) + for (let node of nodes) this.proxyOf.nodes.splice(existIndex + 1, 0, node) + + let index + for (let id in this.indexes) { + index = this.indexes[id] + if (existIndex < index) { + this.indexes[id] = index + nodes.length + } + } + + this.markDirty() + + return this + } + + insertBefore(exist, add) { + let existIndex = this.index(exist) + let type = existIndex === 0 ? 'prepend' : false + let nodes = this.normalize( + add, + this.proxyOf.nodes[existIndex], + type + ).reverse() + existIndex = this.index(exist) + for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node) + + let index + for (let id in this.indexes) { + index = this.indexes[id] + if (existIndex <= index) { + this.indexes[id] = index + nodes.length + } + } + + this.markDirty() + + return this + } + + normalize(nodes, sample) { + if (typeof nodes === 'string') { + nodes = cleanSource(parse(nodes).nodes) + } else if (typeof nodes === 'undefined') { + nodes = [] + } else if (Array.isArray(nodes)) { + nodes = nodes.slice(0) + for (let i of nodes) { + if (i.parent) i.parent.removeChild(i, 'ignore') + } + } else if (nodes.type === 'root' && this.type !== 'document') { + nodes = nodes.nodes.slice(0) + for (let i of nodes) { + if (i.parent) i.parent.removeChild(i, 'ignore') + } + } else if (nodes.type) { + nodes = [nodes] + } else if (nodes.prop) { + if (typeof nodes.value === 'undefined') { + throw new Error('Value field is missed in node creation') + } else if (typeof nodes.value !== 'string') { + nodes.value = String(nodes.value) + } + nodes = [new Declaration(nodes)] + } else if (nodes.selector) { + nodes = [new Rule(nodes)] + } else if (nodes.name) { + nodes = [new AtRule(nodes)] + } else if (nodes.text) { + nodes = [new Comment(nodes)] + } else { + throw new Error('Unknown node type in node creation') + } + + let processed = nodes.map(i => { + /* c8 ignore next */ + if (!i[my]) Container.rebuild(i) + i = i.proxyOf + if (i.parent) i.parent.removeChild(i) + if (i[isClean]) markTreeDirty(i) + if (typeof i.raws.before === 'undefined') { + if (sample && typeof sample.raws.before !== 'undefined') { + i.raws.before = sample.raws.before.replace(/\S/g, '') + } + } + i.parent = this.proxyOf + return i + }) + + return processed + } + + prepend(...children) { + children = children.reverse() + for (let child of children) { + let nodes = this.normalize(child, this.first, 'prepend').reverse() + for (let node of nodes) this.proxyOf.nodes.unshift(node) + for (let id in this.indexes) { + this.indexes[id] = this.indexes[id] + nodes.length + } + } + + this.markDirty() + + return this + } + + push(child) { + child.parent = this + this.proxyOf.nodes.push(child) + return this + } + + removeAll() { + for (let node of this.proxyOf.nodes) node.parent = undefined + this.proxyOf.nodes = [] + + this.markDirty() + + return this + } + + removeChild(child) { + child = this.index(child) + this.proxyOf.nodes[child].parent = undefined + this.proxyOf.nodes.splice(child, 1) + + let index + for (let id in this.indexes) { + index = this.indexes[id] + if (index >= child) { + this.indexes[id] = index - 1 + } + } + + this.markDirty() + + return this + } + + replaceValues(pattern, opts, callback) { + if (!callback) { + callback = opts + opts = {} + } + + this.walkDecls(decl => { + if (opts.props && !opts.props.includes(decl.prop)) return + if (opts.fast && !decl.value.includes(opts.fast)) return + + decl.value = decl.value.replace(pattern, callback) + }) + + this.markDirty() + + return this + } + + some(condition) { + return this.nodes.some(condition) + } + + walk(callback) { + return this.each((child, i) => { + let result + try { + result = callback(child, i) + } catch (e) { + throw child.addToError(e) + } + if (result !== false && child.walk) { + result = child.walk(callback) + } + + return result + }) + } + + walkAtRules(name, callback) { + if (!callback) { + callback = name + return this.walk((child, i) => { + if (child.type === 'atrule') { + return callback(child, i) + } + }) + } + if (name instanceof RegExp) { + return this.walk((child, i) => { + if (child.type === 'atrule' && name.test(child.name)) { + return callback(child, i) + } + }) + } + return this.walk((child, i) => { + if (child.type === 'atrule' && child.name === name) { + return callback(child, i) + } + }) + } + + walkComments(callback) { + return this.walk((child, i) => { + if (child.type === 'comment') { + return callback(child, i) + } + }) + } + + walkDecls(prop, callback) { + if (!callback) { + callback = prop + return this.walk((child, i) => { + if (child.type === 'decl') { + return callback(child, i) + } + }) + } + if (prop instanceof RegExp) { + return this.walk((child, i) => { + if (child.type === 'decl' && prop.test(child.prop)) { + return callback(child, i) + } + }) + } + return this.walk((child, i) => { + if (child.type === 'decl' && child.prop === prop) { + return callback(child, i) + } + }) + } + + walkRules(selector, callback) { + if (!callback) { + callback = selector + + return this.walk((child, i) => { + if (child.type === 'rule') { + return callback(child, i) + } + }) + } + if (selector instanceof RegExp) { + return this.walk((child, i) => { + if (child.type === 'rule' && selector.test(child.selector)) { + return callback(child, i) + } + }) + } + return this.walk((child, i) => { + if (child.type === 'rule' && child.selector === selector) { + return callback(child, i) + } + }) + } + + get first() { + if (!this.proxyOf.nodes) return undefined + return this.proxyOf.nodes[0] + } + + get last() { + if (!this.proxyOf.nodes) return undefined + return this.proxyOf.nodes[this.proxyOf.nodes.length - 1] + } +} + +Container.registerParse = dependant => { + parse = dependant +} + +Container.registerRule = dependant => { + Rule = dependant +} + +Container.registerAtRule = dependant => { + AtRule = dependant +} + +Container.registerRoot = dependant => { + Root = dependant +} + +module.exports = Container +Container.default = Container + +/* c8 ignore start */ +Container.rebuild = node => { + if (node.type === 'atrule') { + Object.setPrototypeOf(node, AtRule.prototype) + } else if (node.type === 'rule') { + Object.setPrototypeOf(node, Rule.prototype) + } else if (node.type === 'decl') { + Object.setPrototypeOf(node, Declaration.prototype) + } else if (node.type === 'comment') { + Object.setPrototypeOf(node, Comment.prototype) + } else if (node.type === 'root') { + Object.setPrototypeOf(node, Root.prototype) + } + + node[my] = true + + if (node.nodes) { + node.nodes.forEach(child => { + Container.rebuild(child) + }) + } +} +/* c8 ignore stop */ + +},{"./comment":8,"./declaration":11,"./node":19,"./symbols":29}],10:[function(require,module,exports){ +'use strict' + +let pico = require('picocolors') + +let terminalHighlight = require('./terminal-highlight') + +class CssSyntaxError extends Error { + constructor(message, line, column, source, file, plugin) { + super(message) + this.name = 'CssSyntaxError' + this.reason = message + + if (file) { + this.file = file + } + if (source) { + this.source = source + } + if (plugin) { + this.plugin = plugin + } + if (typeof line !== 'undefined' && typeof column !== 'undefined') { + if (typeof line === 'number') { + this.line = line + this.column = column + } else { + this.line = line.line + this.column = line.column + this.endLine = column.line + this.endColumn = column.column + } + } + + this.setMessage() + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, CssSyntaxError) + } + } + + setMessage() { + this.message = this.plugin ? this.plugin + ': ' : '' + this.message += this.file ? this.file : '' + if (typeof this.line !== 'undefined') { + this.message += ':' + this.line + ':' + this.column + } + this.message += ': ' + this.reason + } + + showSourceCode(color) { + if (!this.source) return '' + + let css = this.source + if (color == null) color = pico.isColorSupported + if (terminalHighlight) { + if (color) css = terminalHighlight(css) + } + + let lines = css.split(/\r?\n/) + let start = Math.max(this.line - 3, 0) + let end = Math.min(this.line + 2, lines.length) + + let maxWidth = String(end).length + + let mark, aside + if (color) { + let { bold, gray, red } = pico.createColors(true) + mark = text => bold(red(text)) + aside = text => gray(text) + } else { + mark = aside = str => str + } + + return lines + .slice(start, end) + .map((line, index) => { + let number = start + 1 + index + let gutter = ' ' + (' ' + number).slice(-maxWidth) + ' | ' + if (number === this.line) { + let spacing = + aside(gutter.replace(/\d/g, ' ')) + + line.slice(0, this.column - 1).replace(/[^\t]/g, ' ') + return mark('>') + aside(gutter) + line + '\n ' + spacing + mark('^') + } + return ' ' + aside(gutter) + line + }) + .join('\n') + } + + toString() { + let code = this.showSourceCode() + if (code) { + code = '\n\n' + code + '\n' + } + return this.name + ': ' + this.message + code + } +} + +module.exports = CssSyntaxError +CssSyntaxError.default = CssSyntaxError + +},{"./terminal-highlight":2,"picocolors":6}],11:[function(require,module,exports){ +'use strict' + +let Node = require('./node') + +class Declaration extends Node { + constructor(defaults) { + if ( + defaults && + typeof defaults.value !== 'undefined' && + typeof defaults.value !== 'string' + ) { + defaults = { ...defaults, value: String(defaults.value) } + } + super(defaults) + this.type = 'decl' + } + + get variable() { + return this.prop.startsWith('--') || this.prop[0] === '$' + } +} + +module.exports = Declaration +Declaration.default = Declaration + +},{"./node":19}],12:[function(require,module,exports){ +'use strict' + +let Container = require('./container') + +let LazyResult, Processor + +class Document extends Container { + constructor(defaults) { + // type needs to be passed to super, otherwise child roots won't be normalized correctly + super({ type: 'document', ...defaults }) + + if (!this.nodes) { + this.nodes = [] + } + } + + toResult(opts = {}) { + let lazy = new LazyResult(new Processor(), this, opts) + + return lazy.stringify() + } +} + +Document.registerLazyResult = dependant => { + LazyResult = dependant +} + +Document.registerProcessor = dependant => { + Processor = dependant +} + +module.exports = Document +Document.default = Document + +},{"./container":9}],13:[function(require,module,exports){ +'use strict' + +let Declaration = require('./declaration') +let PreviousMap = require('./previous-map') +let Comment = require('./comment') +let AtRule = require('./at-rule') +let Input = require('./input') +let Root = require('./root') +let Rule = require('./rule') + +function fromJSON(json, inputs) { + if (Array.isArray(json)) return json.map(n => fromJSON(n)) + + let { inputs: ownInputs, ...defaults } = json + if (ownInputs) { + inputs = [] + for (let input of ownInputs) { + let inputHydrated = { ...input, __proto__: Input.prototype } + if (inputHydrated.map) { + inputHydrated.map = { + ...inputHydrated.map, + __proto__: PreviousMap.prototype + } + } + inputs.push(inputHydrated) + } + } + if (defaults.nodes) { + defaults.nodes = json.nodes.map(n => fromJSON(n, inputs)) + } + if (defaults.source) { + let { inputId, ...source } = defaults.source + defaults.source = source + if (inputId != null) { + defaults.source.input = inputs[inputId] + } + } + if (defaults.type === 'root') { + return new Root(defaults) + } else if (defaults.type === 'decl') { + return new Declaration(defaults) + } else if (defaults.type === 'rule') { + return new Rule(defaults) + } else if (defaults.type === 'comment') { + return new Comment(defaults) + } else if (defaults.type === 'atrule') { + return new AtRule(defaults) + } else { + throw new Error('Unknown node type: ' + json.type) + } +} + +module.exports = fromJSON +fromJSON.default = fromJSON + +},{"./at-rule":7,"./comment":8,"./declaration":11,"./input":14,"./previous-map":22,"./root":25,"./rule":26}],14:[function(require,module,exports){ +'use strict' + +let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js') +let { fileURLToPath, pathToFileURL } = require('url') +let { isAbsolute, resolve } = require('path') +let { nanoid } = require('nanoid/non-secure') + +let terminalHighlight = require('./terminal-highlight') +let CssSyntaxError = require('./css-syntax-error') +let PreviousMap = require('./previous-map') + +let fromOffsetCache = Symbol('fromOffsetCache') + +let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator) +let pathAvailable = Boolean(resolve && isAbsolute) + +class Input { + constructor(css, opts = {}) { + if ( + css === null || + typeof css === 'undefined' || + (typeof css === 'object' && !css.toString) + ) { + throw new Error(`PostCSS received ${css} instead of CSS string`) + } + + this.css = css.toString() + + if (this.css[0] === '\uFEFF' || this.css[0] === '\uFFFE') { + this.hasBOM = true + this.css = this.css.slice(1) + } else { + this.hasBOM = false + } + + if (opts.from) { + if ( + !pathAvailable || + /^\w+:\/\//.test(opts.from) || + isAbsolute(opts.from) + ) { + this.file = opts.from + } else { + this.file = resolve(opts.from) + } + } + + if (pathAvailable && sourceMapAvailable) { + let map = new PreviousMap(this.css, opts) + if (map.text) { + this.map = map + let file = map.consumer().file + if (!this.file && file) this.file = this.mapResolve(file) + } + } + + if (!this.file) { + this.id = '' + } + if (this.map) this.map.file = this.from + } + + error(message, line, column, opts = {}) { + let result, endLine, endColumn + + if (line && typeof line === 'object') { + let start = line + let end = column + if (typeof start.offset === 'number') { + let pos = this.fromOffset(start.offset) + line = pos.line + column = pos.col + } else { + line = start.line + column = start.column + } + if (typeof end.offset === 'number') { + let pos = this.fromOffset(end.offset) + endLine = pos.line + endColumn = pos.col + } else { + endLine = end.line + endColumn = end.column + } + } else if (!column) { + let pos = this.fromOffset(line) + line = pos.line + column = pos.col + } + + let origin = this.origin(line, column, endLine, endColumn) + if (origin) { + result = new CssSyntaxError( + message, + origin.endLine === undefined + ? origin.line + : { column: origin.column, line: origin.line }, + origin.endLine === undefined + ? origin.column + : { column: origin.endColumn, line: origin.endLine }, + origin.source, + origin.file, + opts.plugin + ) + } else { + result = new CssSyntaxError( + message, + endLine === undefined ? line : { column, line }, + endLine === undefined ? column : { column: endColumn, line: endLine }, + this.css, + this.file, + opts.plugin + ) + } + + result.input = { column, endColumn, endLine, line, source: this.css } + if (this.file) { + if (pathToFileURL) { + result.input.url = pathToFileURL(this.file).toString() + } + result.input.file = this.file + } + + return result + } + + fromOffset(offset) { + let lastLine, lineToIndex + if (!this[fromOffsetCache]) { + let lines = this.css.split('\n') + lineToIndex = new Array(lines.length) + let prevIndex = 0 + + for (let i = 0, l = lines.length; i < l; i++) { + lineToIndex[i] = prevIndex + prevIndex += lines[i].length + 1 + } + + this[fromOffsetCache] = lineToIndex + } else { + lineToIndex = this[fromOffsetCache] + } + lastLine = lineToIndex[lineToIndex.length - 1] + + let min = 0 + if (offset >= lastLine) { + min = lineToIndex.length - 1 + } else { + let max = lineToIndex.length - 2 + let mid + while (min < max) { + mid = min + ((max - min) >> 1) + if (offset < lineToIndex[mid]) { + max = mid - 1 + } else if (offset >= lineToIndex[mid + 1]) { + min = mid + 1 + } else { + min = mid + break + } + } + } + return { + col: offset - lineToIndex[min] + 1, + line: min + 1 + } + } + + mapResolve(file) { + if (/^\w+:\/\//.test(file)) { + return file + } + return resolve(this.map.consumer().sourceRoot || this.map.root || '.', file) + } + + origin(line, column, endLine, endColumn) { + if (!this.map) return false + let consumer = this.map.consumer() + + let from = consumer.originalPositionFor({ column, line }) + if (!from.source) return false + + let to + if (typeof endLine === 'number') { + to = consumer.originalPositionFor({ column: endColumn, line: endLine }) + } + + let fromUrl + + if (isAbsolute(from.source)) { + fromUrl = pathToFileURL(from.source) + } else { + fromUrl = new URL( + from.source, + this.map.consumer().sourceRoot || pathToFileURL(this.map.mapFile) + ) + } + + let result = { + column: from.column, + endColumn: to && to.column, + endLine: to && to.line, + line: from.line, + url: fromUrl.toString() + } + + if (fromUrl.protocol === 'file:') { + if (fileURLToPath) { + result.file = fileURLToPath(fromUrl) + } else { + /* c8 ignore next 2 */ + throw new Error(`file: protocol is not available in this PostCSS build`) + } + } + + let source = consumer.sourceContentFor(from.source) + if (source) result.source = source + + return result + } + + toJSON() { + let json = {} + for (let name of ['hasBOM', 'css', 'file', 'id']) { + if (this[name] != null) { + json[name] = this[name] + } + } + if (this.map) { + json.map = { ...this.map } + if (json.map.consumerCache) { + json.map.consumerCache = undefined + } + } + return json + } + + get from() { + return this.file || this.id + } +} + +module.exports = Input +Input.default = Input + +if (terminalHighlight && terminalHighlight.registerInput) { + terminalHighlight.registerInput(Input) +} + +},{"./css-syntax-error":10,"./previous-map":22,"./terminal-highlight":2,"nanoid/non-secure":5,"path":2,"source-map-js":2,"url":2}],15:[function(require,module,exports){ +(function (process){(function (){ +'use strict' + +let { isClean, my } = require('./symbols') +let MapGenerator = require('./map-generator') +let stringify = require('./stringify') +let Container = require('./container') +let Document = require('./document') +let warnOnce = require('./warn-once') +let Result = require('./result') +let parse = require('./parse') +let Root = require('./root') + +const TYPE_TO_CLASS_NAME = { + atrule: 'AtRule', + comment: 'Comment', + decl: 'Declaration', + document: 'Document', + root: 'Root', + rule: 'Rule' +} + +const PLUGIN_PROPS = { + AtRule: true, + AtRuleExit: true, + Comment: true, + CommentExit: true, + Declaration: true, + DeclarationExit: true, + Document: true, + DocumentExit: true, + Once: true, + OnceExit: true, + postcssPlugin: true, + prepare: true, + Root: true, + RootExit: true, + Rule: true, + RuleExit: true +} + +const NOT_VISITORS = { + Once: true, + postcssPlugin: true, + prepare: true +} + +const CHILDREN = 0 + +function isPromise(obj) { + return typeof obj === 'object' && typeof obj.then === 'function' +} + +function getEvents(node) { + let key = false + let type = TYPE_TO_CLASS_NAME[node.type] + if (node.type === 'decl') { + key = node.prop.toLowerCase() + } else if (node.type === 'atrule') { + key = node.name.toLowerCase() + } + + if (key && node.append) { + return [ + type, + type + '-' + key, + CHILDREN, + type + 'Exit', + type + 'Exit-' + key + ] + } else if (key) { + return [type, type + '-' + key, type + 'Exit', type + 'Exit-' + key] + } else if (node.append) { + return [type, CHILDREN, type + 'Exit'] + } else { + return [type, type + 'Exit'] + } +} + +function toStack(node) { + let events + if (node.type === 'document') { + events = ['Document', CHILDREN, 'DocumentExit'] + } else if (node.type === 'root') { + events = ['Root', CHILDREN, 'RootExit'] + } else { + events = getEvents(node) + } + + return { + eventIndex: 0, + events, + iterator: 0, + node, + visitorIndex: 0, + visitors: [] + } +} + +function cleanMarks(node) { + node[isClean] = false + if (node.nodes) node.nodes.forEach(i => cleanMarks(i)) + return node +} + +let postcss = {} + +class LazyResult { + constructor(processor, css, opts) { + this.stringified = false + this.processed = false + + let root + if ( + typeof css === 'object' && + css !== null && + (css.type === 'root' || css.type === 'document') + ) { + root = cleanMarks(css) + } else if (css instanceof LazyResult || css instanceof Result) { + root = cleanMarks(css.root) + if (css.map) { + if (typeof opts.map === 'undefined') opts.map = {} + if (!opts.map.inline) opts.map.inline = false + opts.map.prev = css.map + } + } else { + let parser = parse + if (opts.syntax) parser = opts.syntax.parse + if (opts.parser) parser = opts.parser + if (parser.parse) parser = parser.parse + + try { + root = parser(css, opts) + } catch (error) { + this.processed = true + this.error = error + } + + if (root && !root[my]) { + /* c8 ignore next 2 */ + Container.rebuild(root) + } + } + + this.result = new Result(processor, root, opts) + this.helpers = { ...postcss, postcss, result: this.result } + this.plugins = this.processor.plugins.map(plugin => { + if (typeof plugin === 'object' && plugin.prepare) { + return { ...plugin, ...plugin.prepare(this.result) } + } else { + return plugin + } + }) + } + + async() { + if (this.error) return Promise.reject(this.error) + if (this.processed) return Promise.resolve(this.result) + if (!this.processing) { + this.processing = this.runAsync() + } + return this.processing + } + + catch(onRejected) { + return this.async().catch(onRejected) + } + + finally(onFinally) { + return this.async().then(onFinally, onFinally) + } + + getAsyncError() { + throw new Error('Use process(css).then(cb) to work with async plugins') + } + + handleError(error, node) { + let plugin = this.result.lastPlugin + try { + if (node) node.addToError(error) + this.error = error + if (error.name === 'CssSyntaxError' && !error.plugin) { + error.plugin = plugin.postcssPlugin + error.setMessage() + } else if (plugin.postcssVersion) { + if (process.env.NODE_ENV !== 'production') { + let pluginName = plugin.postcssPlugin + let pluginVer = plugin.postcssVersion + let runtimeVer = this.result.processor.version + let a = pluginVer.split('.') + let b = runtimeVer.split('.') + + if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) { + // eslint-disable-next-line no-console + console.error( + 'Unknown error from PostCSS plugin. Your current PostCSS ' + + 'version is ' + + runtimeVer + + ', but ' + + pluginName + + ' uses ' + + pluginVer + + '. Perhaps this is the source of the error below.' + ) + } + } + } + } catch (err) { + /* c8 ignore next 3 */ + // eslint-disable-next-line no-console + if (console && console.error) console.error(err) + } + return error + } + + prepareVisitors() { + this.listeners = {} + let add = (plugin, type, cb) => { + if (!this.listeners[type]) this.listeners[type] = [] + this.listeners[type].push([plugin, cb]) + } + for (let plugin of this.plugins) { + if (typeof plugin === 'object') { + for (let event in plugin) { + if (!PLUGIN_PROPS[event] && /^[A-Z]/.test(event)) { + throw new Error( + `Unknown event ${event} in ${plugin.postcssPlugin}. ` + + `Try to update PostCSS (${this.processor.version} now).` + ) + } + if (!NOT_VISITORS[event]) { + if (typeof plugin[event] === 'object') { + for (let filter in plugin[event]) { + if (filter === '*') { + add(plugin, event, plugin[event][filter]) + } else { + add( + plugin, + event + '-' + filter.toLowerCase(), + plugin[event][filter] + ) + } + } + } else if (typeof plugin[event] === 'function') { + add(plugin, event, plugin[event]) + } + } + } + } + } + this.hasListener = Object.keys(this.listeners).length > 0 + } + + async runAsync() { + this.plugin = 0 + for (let i = 0; i < this.plugins.length; i++) { + let plugin = this.plugins[i] + let promise = this.runOnRoot(plugin) + if (isPromise(promise)) { + try { + await promise + } catch (error) { + throw this.handleError(error) + } + } + } + + this.prepareVisitors() + if (this.hasListener) { + let root = this.result.root + while (!root[isClean]) { + root[isClean] = true + let stack = [toStack(root)] + while (stack.length > 0) { + let promise = this.visitTick(stack) + if (isPromise(promise)) { + try { + await promise + } catch (e) { + let node = stack[stack.length - 1].node + throw this.handleError(e, node) + } + } + } + } + + if (this.listeners.OnceExit) { + for (let [plugin, visitor] of this.listeners.OnceExit) { + this.result.lastPlugin = plugin + try { + if (root.type === 'document') { + let roots = root.nodes.map(subRoot => + visitor(subRoot, this.helpers) + ) + + await Promise.all(roots) + } else { + await visitor(root, this.helpers) + } + } catch (e) { + throw this.handleError(e) + } + } + } + } + + this.processed = true + return this.stringify() + } + + runOnRoot(plugin) { + this.result.lastPlugin = plugin + try { + if (typeof plugin === 'object' && plugin.Once) { + if (this.result.root.type === 'document') { + let roots = this.result.root.nodes.map(root => + plugin.Once(root, this.helpers) + ) + + if (isPromise(roots[0])) { + return Promise.all(roots) + } + + return roots + } + + return plugin.Once(this.result.root, this.helpers) + } else if (typeof plugin === 'function') { + return plugin(this.result.root, this.result) + } + } catch (error) { + throw this.handleError(error) + } + } + + stringify() { + if (this.error) throw this.error + if (this.stringified) return this.result + this.stringified = true + + this.sync() + + let opts = this.result.opts + let str = stringify + if (opts.syntax) str = opts.syntax.stringify + if (opts.stringifier) str = opts.stringifier + if (str.stringify) str = str.stringify + + let map = new MapGenerator(str, this.result.root, this.result.opts) + let data = map.generate() + this.result.css = data[0] + this.result.map = data[1] + + return this.result + } + + sync() { + if (this.error) throw this.error + if (this.processed) return this.result + this.processed = true + + if (this.processing) { + throw this.getAsyncError() + } + + for (let plugin of this.plugins) { + let promise = this.runOnRoot(plugin) + if (isPromise(promise)) { + throw this.getAsyncError() + } + } + + this.prepareVisitors() + if (this.hasListener) { + let root = this.result.root + while (!root[isClean]) { + root[isClean] = true + this.walkSync(root) + } + if (this.listeners.OnceExit) { + if (root.type === 'document') { + for (let subRoot of root.nodes) { + this.visitSync(this.listeners.OnceExit, subRoot) + } + } else { + this.visitSync(this.listeners.OnceExit, root) + } + } + } + + return this.result + } + + then(onFulfilled, onRejected) { + if (process.env.NODE_ENV !== 'production') { + if (!('from' in this.opts)) { + warnOnce( + 'Without `from` option PostCSS could generate wrong source map ' + + 'and will not find Browserslist config. Set it to CSS file path ' + + 'or to `undefined` to prevent this warning.' + ) + } + } + return this.async().then(onFulfilled, onRejected) + } + + toString() { + return this.css + } + + visitSync(visitors, node) { + for (let [plugin, visitor] of visitors) { + this.result.lastPlugin = plugin + let promise + try { + promise = visitor(node, this.helpers) + } catch (e) { + throw this.handleError(e, node.proxyOf) + } + if (node.type !== 'root' && node.type !== 'document' && !node.parent) { + return true + } + if (isPromise(promise)) { + throw this.getAsyncError() + } + } + } + + visitTick(stack) { + let visit = stack[stack.length - 1] + let { node, visitors } = visit + + if (node.type !== 'root' && node.type !== 'document' && !node.parent) { + stack.pop() + return + } + + if (visitors.length > 0 && visit.visitorIndex < visitors.length) { + let [plugin, visitor] = visitors[visit.visitorIndex] + visit.visitorIndex += 1 + if (visit.visitorIndex === visitors.length) { + visit.visitors = [] + visit.visitorIndex = 0 + } + this.result.lastPlugin = plugin + try { + return visitor(node.toProxy(), this.helpers) + } catch (e) { + throw this.handleError(e, node) + } + } + + if (visit.iterator !== 0) { + let iterator = visit.iterator + let child + while ((child = node.nodes[node.indexes[iterator]])) { + node.indexes[iterator] += 1 + if (!child[isClean]) { + child[isClean] = true + stack.push(toStack(child)) + return + } + } + visit.iterator = 0 + delete node.indexes[iterator] + } + + let events = visit.events + while (visit.eventIndex < events.length) { + let event = events[visit.eventIndex] + visit.eventIndex += 1 + if (event === CHILDREN) { + if (node.nodes && node.nodes.length) { + node[isClean] = true + visit.iterator = node.getIterator() + } + return + } else if (this.listeners[event]) { + visit.visitors = this.listeners[event] + return + } + } + stack.pop() + } + + walkSync(node) { + node[isClean] = true + let events = getEvents(node) + for (let event of events) { + if (event === CHILDREN) { + if (node.nodes) { + node.each(child => { + if (!child[isClean]) this.walkSync(child) + }) + } + } else { + let visitors = this.listeners[event] + if (visitors) { + if (this.visitSync(visitors, node.toProxy())) return + } + } + } + } + + warnings() { + return this.sync().warnings() + } + + get content() { + return this.stringify().content + } + + get css() { + return this.stringify().css + } + + get map() { + return this.stringify().map + } + + get messages() { + return this.sync().messages + } + + get opts() { + return this.result.opts + } + + get processor() { + return this.result.processor + } + + get root() { + return this.sync().root + } + + get [Symbol.toStringTag]() { + return 'LazyResult' + } +} + +LazyResult.registerPostcss = dependant => { + postcss = dependant +} + +module.exports = LazyResult +LazyResult.default = LazyResult + +Root.registerLazyResult(LazyResult) +Document.registerLazyResult(LazyResult) + +}).call(this)}).call(this,require('_process')) +},{"./container":9,"./document":12,"./map-generator":17,"./parse":20,"./result":24,"./root":25,"./stringify":28,"./symbols":29,"./warn-once":31,"_process":33}],16:[function(require,module,exports){ +'use strict' + +let list = { + comma(string) { + return list.split(string, [','], true) + }, + + space(string) { + let spaces = [' ', '\n', '\t'] + return list.split(string, spaces) + }, + + split(string, separators, last) { + let array = [] + let current = '' + let split = false + + let func = 0 + let inQuote = false + let prevQuote = '' + let escape = false + + for (let letter of string) { + if (escape) { + escape = false + } else if (letter === '\\') { + escape = true + } else if (inQuote) { + if (letter === prevQuote) { + inQuote = false + } + } else if (letter === '"' || letter === "'") { + inQuote = true + prevQuote = letter + } else if (letter === '(') { + func += 1 + } else if (letter === ')') { + if (func > 0) func -= 1 + } else if (func === 0) { + if (separators.includes(letter)) split = true + } + + if (split) { + if (current !== '') array.push(current.trim()) + current = '' + split = false + } else { + current += letter + } + } + + if (last || current !== '') array.push(current.trim()) + return array + } +} + +module.exports = list +list.default = list + +},{}],17:[function(require,module,exports){ +(function (Buffer){(function (){ +'use strict' + +let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js') +let { dirname, relative, resolve, sep } = require('path') +let { pathToFileURL } = require('url') + +let Input = require('./input') + +let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator) +let pathAvailable = Boolean(dirname && resolve && relative && sep) + +class MapGenerator { + constructor(stringify, root, opts, cssString) { + this.stringify = stringify + this.mapOpts = opts.map || {} + this.root = root + this.opts = opts + this.css = cssString + this.originalCSS = cssString + this.usesFileUrls = !this.mapOpts.from && this.mapOpts.absolute + + this.memoizedFileURLs = new Map() + this.memoizedPaths = new Map() + this.memoizedURLs = new Map() + } + + addAnnotation() { + let content + + if (this.isInline()) { + content = + 'data:application/json;base64,' + this.toBase64(this.map.toString()) + } else if (typeof this.mapOpts.annotation === 'string') { + content = this.mapOpts.annotation + } else if (typeof this.mapOpts.annotation === 'function') { + content = this.mapOpts.annotation(this.opts.to, this.root) + } else { + content = this.outputFile() + '.map' + } + let eol = '\n' + if (this.css.includes('\r\n')) eol = '\r\n' + + this.css += eol + '/*# sourceMappingURL=' + content + ' */' + } + + applyPrevMaps() { + for (let prev of this.previous()) { + let from = this.toUrl(this.path(prev.file)) + let root = prev.root || dirname(prev.file) + let map + + if (this.mapOpts.sourcesContent === false) { + map = new SourceMapConsumer(prev.text) + if (map.sourcesContent) { + map.sourcesContent = null + } + } else { + map = prev.consumer() + } + + this.map.applySourceMap(map, from, this.toUrl(this.path(root))) + } + } + + clearAnnotation() { + if (this.mapOpts.annotation === false) return + + if (this.root) { + let node + for (let i = this.root.nodes.length - 1; i >= 0; i--) { + node = this.root.nodes[i] + if (node.type !== 'comment') continue + if (node.text.indexOf('# sourceMappingURL=') === 0) { + this.root.removeChild(i) + } + } + } else if (this.css) { + this.css = this.css.replace(/\n*?\/\*#[\S\s]*?\*\/$/gm, '') + } + } + + generate() { + this.clearAnnotation() + if (pathAvailable && sourceMapAvailable && this.isMap()) { + return this.generateMap() + } else { + let result = '' + this.stringify(this.root, i => { + result += i + }) + return [result] + } + } + + generateMap() { + if (this.root) { + this.generateString() + } else if (this.previous().length === 1) { + let prev = this.previous()[0].consumer() + prev.file = this.outputFile() + this.map = SourceMapGenerator.fromSourceMap(prev, { + ignoreInvalidMapping: true + }) + } else { + this.map = new SourceMapGenerator({ + file: this.outputFile(), + ignoreInvalidMapping: true + }) + this.map.addMapping({ + generated: { column: 0, line: 1 }, + original: { column: 0, line: 1 }, + source: this.opts.from + ? this.toUrl(this.path(this.opts.from)) + : '' + }) + } + + if (this.isSourcesContent()) this.setSourcesContent() + if (this.root && this.previous().length > 0) this.applyPrevMaps() + if (this.isAnnotation()) this.addAnnotation() + + if (this.isInline()) { + return [this.css] + } else { + return [this.css, this.map] + } + } + + generateString() { + this.css = '' + this.map = new SourceMapGenerator({ + file: this.outputFile(), + ignoreInvalidMapping: true + }) + + let line = 1 + let column = 1 + + let noSource = '' + let mapping = { + generated: { column: 0, line: 0 }, + original: { column: 0, line: 0 }, + source: '' + } + + let lines, last + this.stringify(this.root, (str, node, type) => { + this.css += str + + if (node && type !== 'end') { + mapping.generated.line = line + mapping.generated.column = column - 1 + if (node.source && node.source.start) { + mapping.source = this.sourcePath(node) + mapping.original.line = node.source.start.line + mapping.original.column = node.source.start.column - 1 + this.map.addMapping(mapping) + } else { + mapping.source = noSource + mapping.original.line = 1 + mapping.original.column = 0 + this.map.addMapping(mapping) + } + } + + lines = str.match(/\n/g) + if (lines) { + line += lines.length + last = str.lastIndexOf('\n') + column = str.length - last + } else { + column += str.length + } + + if (node && type !== 'start') { + let p = node.parent || { raws: {} } + let childless = + node.type === 'decl' || (node.type === 'atrule' && !node.nodes) + if (!childless || node !== p.last || p.raws.semicolon) { + if (node.source && node.source.end) { + mapping.source = this.sourcePath(node) + mapping.original.line = node.source.end.line + mapping.original.column = node.source.end.column - 1 + mapping.generated.line = line + mapping.generated.column = column - 2 + this.map.addMapping(mapping) + } else { + mapping.source = noSource + mapping.original.line = 1 + mapping.original.column = 0 + mapping.generated.line = line + mapping.generated.column = column - 1 + this.map.addMapping(mapping) + } + } + } + }) + } + + isAnnotation() { + if (this.isInline()) { + return true + } + if (typeof this.mapOpts.annotation !== 'undefined') { + return this.mapOpts.annotation + } + if (this.previous().length) { + return this.previous().some(i => i.annotation) + } + return true + } + + isInline() { + if (typeof this.mapOpts.inline !== 'undefined') { + return this.mapOpts.inline + } + + let annotation = this.mapOpts.annotation + if (typeof annotation !== 'undefined' && annotation !== true) { + return false + } + + if (this.previous().length) { + return this.previous().some(i => i.inline) + } + return true + } + + isMap() { + if (typeof this.opts.map !== 'undefined') { + return !!this.opts.map + } + return this.previous().length > 0 + } + + isSourcesContent() { + if (typeof this.mapOpts.sourcesContent !== 'undefined') { + return this.mapOpts.sourcesContent + } + if (this.previous().length) { + return this.previous().some(i => i.withContent()) + } + return true + } + + outputFile() { + if (this.opts.to) { + return this.path(this.opts.to) + } else if (this.opts.from) { + return this.path(this.opts.from) + } else { + return 'to.css' + } + } + + path(file) { + if (this.mapOpts.absolute) return file + if (file.charCodeAt(0) === 60 /* `<` */) return file + if (/^\w+:\/\//.test(file)) return file + let cached = this.memoizedPaths.get(file) + if (cached) return cached + + let from = this.opts.to ? dirname(this.opts.to) : '.' + + if (typeof this.mapOpts.annotation === 'string') { + from = dirname(resolve(from, this.mapOpts.annotation)) + } + + let path = relative(from, file) + this.memoizedPaths.set(file, path) + + return path + } + + previous() { + if (!this.previousMaps) { + this.previousMaps = [] + if (this.root) { + this.root.walk(node => { + if (node.source && node.source.input.map) { + let map = node.source.input.map + if (!this.previousMaps.includes(map)) { + this.previousMaps.push(map) + } + } + }) + } else { + let input = new Input(this.originalCSS, this.opts) + if (input.map) this.previousMaps.push(input.map) + } + } + + return this.previousMaps + } + + setSourcesContent() { + let already = {} + if (this.root) { + this.root.walk(node => { + if (node.source) { + let from = node.source.input.from + if (from && !already[from]) { + already[from] = true + let fromUrl = this.usesFileUrls + ? this.toFileUrl(from) + : this.toUrl(this.path(from)) + this.map.setSourceContent(fromUrl, node.source.input.css) + } + } + }) + } else if (this.css) { + let from = this.opts.from + ? this.toUrl(this.path(this.opts.from)) + : '' + this.map.setSourceContent(from, this.css) + } + } + + sourcePath(node) { + if (this.mapOpts.from) { + return this.toUrl(this.mapOpts.from) + } else if (this.usesFileUrls) { + return this.toFileUrl(node.source.input.from) + } else { + return this.toUrl(this.path(node.source.input.from)) + } + } + + toBase64(str) { + if (Buffer) { + return Buffer.from(str).toString('base64') + } else { + return window.btoa(unescape(encodeURIComponent(str))) + } + } + + toFileUrl(path) { + let cached = this.memoizedFileURLs.get(path) + if (cached) return cached + + if (pathToFileURL) { + let fileURL = pathToFileURL(path).toString() + this.memoizedFileURLs.set(path, fileURL) + + return fileURL + } else { + throw new Error( + '`map.absolute` option is not available in this PostCSS build' + ) + } + } + + toUrl(path) { + let cached = this.memoizedURLs.get(path) + if (cached) return cached + + if (sep === '\\') { + path = path.replace(/\\/g, '/') + } + + let url = encodeURI(path).replace(/[#?]/g, encodeURIComponent) + this.memoizedURLs.set(path, url) + + return url + } +} + +module.exports = MapGenerator + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./input":14,"buffer":3,"path":2,"source-map-js":2,"url":2}],18:[function(require,module,exports){ +(function (process){(function (){ +'use strict' + +let MapGenerator = require('./map-generator') +let stringify = require('./stringify') +let warnOnce = require('./warn-once') +let parse = require('./parse') +const Result = require('./result') + +class NoWorkResult { + constructor(processor, css, opts) { + css = css.toString() + this.stringified = false + + this._processor = processor + this._css = css + this._opts = opts + this._map = undefined + let root + + let str = stringify + this.result = new Result(this._processor, root, this._opts) + this.result.css = css + + let self = this + Object.defineProperty(this.result, 'root', { + get() { + return self.root + } + }) + + let map = new MapGenerator(str, root, this._opts, css) + if (map.isMap()) { + let [generatedCSS, generatedMap] = map.generate() + if (generatedCSS) { + this.result.css = generatedCSS + } + if (generatedMap) { + this.result.map = generatedMap + } + } else { + map.clearAnnotation() + this.result.css = map.css + } + } + + async() { + if (this.error) return Promise.reject(this.error) + return Promise.resolve(this.result) + } + + catch(onRejected) { + return this.async().catch(onRejected) + } + + finally(onFinally) { + return this.async().then(onFinally, onFinally) + } + + sync() { + if (this.error) throw this.error + return this.result + } + + then(onFulfilled, onRejected) { + if (process.env.NODE_ENV !== 'production') { + if (!('from' in this._opts)) { + warnOnce( + 'Without `from` option PostCSS could generate wrong source map ' + + 'and will not find Browserslist config. Set it to CSS file path ' + + 'or to `undefined` to prevent this warning.' + ) + } + } + + return this.async().then(onFulfilled, onRejected) + } + + toString() { + return this._css + } + + warnings() { + return [] + } + + get content() { + return this.result.css + } + + get css() { + return this.result.css + } + + get map() { + return this.result.map + } + + get messages() { + return [] + } + + get opts() { + return this.result.opts + } + + get processor() { + return this.result.processor + } + + get root() { + if (this._root) { + return this._root + } + + let root + let parser = parse + + try { + root = parser(this._css, this._opts) + } catch (error) { + this.error = error + } + + if (this.error) { + throw this.error + } else { + this._root = root + return root + } + } + + get [Symbol.toStringTag]() { + return 'NoWorkResult' + } +} + +module.exports = NoWorkResult +NoWorkResult.default = NoWorkResult + +}).call(this)}).call(this,require('_process')) +},{"./map-generator":17,"./parse":20,"./result":24,"./stringify":28,"./warn-once":31,"_process":33}],19:[function(require,module,exports){ +'use strict' + +let { isClean, my } = require('./symbols') +let CssSyntaxError = require('./css-syntax-error') +let Stringifier = require('./stringifier') +let stringify = require('./stringify') + +function cloneNode(obj, parent) { + let cloned = new obj.constructor() + + for (let i in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, i)) { + /* c8 ignore next 2 */ + continue + } + if (i === 'proxyCache') continue + let value = obj[i] + let type = typeof value + + if (i === 'parent' && type === 'object') { + if (parent) cloned[i] = parent + } else if (i === 'source') { + cloned[i] = value + } else if (Array.isArray(value)) { + cloned[i] = value.map(j => cloneNode(j, cloned)) + } else { + if (type === 'object' && value !== null) value = cloneNode(value) + cloned[i] = value + } + } + + return cloned +} + +class Node { + constructor(defaults = {}) { + this.raws = {} + this[isClean] = false + this[my] = true + + for (let name in defaults) { + if (name === 'nodes') { + this.nodes = [] + for (let node of defaults[name]) { + if (typeof node.clone === 'function') { + this.append(node.clone()) + } else { + this.append(node) + } + } + } else { + this[name] = defaults[name] + } + } + } + + addToError(error) { + error.postcssNode = this + if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) { + let s = this.source + error.stack = error.stack.replace( + /\n\s{4}at /, + `$&${s.input.from}:${s.start.line}:${s.start.column}$&` + ) + } + return error + } + + after(add) { + this.parent.insertAfter(this, add) + return this + } + + assign(overrides = {}) { + for (let name in overrides) { + this[name] = overrides[name] + } + return this + } + + before(add) { + this.parent.insertBefore(this, add) + return this + } + + cleanRaws(keepBetween) { + delete this.raws.before + delete this.raws.after + if (!keepBetween) delete this.raws.between + } + + clone(overrides = {}) { + let cloned = cloneNode(this) + for (let name in overrides) { + cloned[name] = overrides[name] + } + return cloned + } + + cloneAfter(overrides = {}) { + let cloned = this.clone(overrides) + this.parent.insertAfter(this, cloned) + return cloned + } + + cloneBefore(overrides = {}) { + let cloned = this.clone(overrides) + this.parent.insertBefore(this, cloned) + return cloned + } + + error(message, opts = {}) { + if (this.source) { + let { end, start } = this.rangeBy(opts) + return this.source.input.error( + message, + { column: start.column, line: start.line }, + { column: end.column, line: end.line }, + opts + ) + } + return new CssSyntaxError(message) + } + + getProxyProcessor() { + return { + get(node, prop) { + if (prop === 'proxyOf') { + return node + } else if (prop === 'root') { + return () => node.root().toProxy() + } else { + return node[prop] + } + }, + + set(node, prop, value) { + if (node[prop] === value) return true + node[prop] = value + if ( + prop === 'prop' || + prop === 'value' || + prop === 'name' || + prop === 'params' || + prop === 'important' || + /* c8 ignore next */ + prop === 'text' + ) { + node.markDirty() + } + return true + } + } + } + + markDirty() { + if (this[isClean]) { + this[isClean] = false + let next = this + while ((next = next.parent)) { + next[isClean] = false + } + } + } + + next() { + if (!this.parent) return undefined + let index = this.parent.index(this) + return this.parent.nodes[index + 1] + } + + positionBy(opts, stringRepresentation) { + let pos = this.source.start + if (opts.index) { + pos = this.positionInside(opts.index, stringRepresentation) + } else if (opts.word) { + stringRepresentation = this.toString() + let index = stringRepresentation.indexOf(opts.word) + if (index !== -1) pos = this.positionInside(index, stringRepresentation) + } + return pos + } + + positionInside(index, stringRepresentation) { + let string = stringRepresentation || this.toString() + let column = this.source.start.column + let line = this.source.start.line + + for (let i = 0; i < index; i++) { + if (string[i] === '\n') { + column = 1 + line += 1 + } else { + column += 1 + } + } + + return { column, line } + } + + prev() { + if (!this.parent) return undefined + let index = this.parent.index(this) + return this.parent.nodes[index - 1] + } + + rangeBy(opts) { + let start = { + column: this.source.start.column, + line: this.source.start.line + } + let end = this.source.end + ? { + column: this.source.end.column + 1, + line: this.source.end.line + } + : { + column: start.column + 1, + line: start.line + } + + if (opts.word) { + let stringRepresentation = this.toString() + let index = stringRepresentation.indexOf(opts.word) + if (index !== -1) { + start = this.positionInside(index, stringRepresentation) + end = this.positionInside(index + opts.word.length, stringRepresentation) + } + } else { + if (opts.start) { + start = { + column: opts.start.column, + line: opts.start.line + } + } else if (opts.index) { + start = this.positionInside(opts.index) + } + + if (opts.end) { + end = { + column: opts.end.column, + line: opts.end.line + } + } else if (typeof opts.endIndex === 'number') { + end = this.positionInside(opts.endIndex) + } else if (opts.index) { + end = this.positionInside(opts.index + 1) + } + } + + if ( + end.line < start.line || + (end.line === start.line && end.column <= start.column) + ) { + end = { column: start.column + 1, line: start.line } + } + + return { end, start } + } + + raw(prop, defaultType) { + let str = new Stringifier() + return str.raw(this, prop, defaultType) + } + + remove() { + if (this.parent) { + this.parent.removeChild(this) + } + this.parent = undefined + return this + } + + replaceWith(...nodes) { + if (this.parent) { + let bookmark = this + let foundSelf = false + for (let node of nodes) { + if (node === this) { + foundSelf = true + } else if (foundSelf) { + this.parent.insertAfter(bookmark, node) + bookmark = node + } else { + this.parent.insertBefore(bookmark, node) + } + } + + if (!foundSelf) { + this.remove() + } + } + + return this + } + + root() { + let result = this + while (result.parent && result.parent.type !== 'document') { + result = result.parent + } + return result + } + + toJSON(_, inputs) { + let fixed = {} + let emitInputs = inputs == null + inputs = inputs || new Map() + let inputsNextIndex = 0 + + for (let name in this) { + if (!Object.prototype.hasOwnProperty.call(this, name)) { + /* c8 ignore next 2 */ + continue + } + if (name === 'parent' || name === 'proxyCache') continue + let value = this[name] + + if (Array.isArray(value)) { + fixed[name] = value.map(i => { + if (typeof i === 'object' && i.toJSON) { + return i.toJSON(null, inputs) + } else { + return i + } + }) + } else if (typeof value === 'object' && value.toJSON) { + fixed[name] = value.toJSON(null, inputs) + } else if (name === 'source') { + let inputId = inputs.get(value.input) + if (inputId == null) { + inputId = inputsNextIndex + inputs.set(value.input, inputsNextIndex) + inputsNextIndex++ + } + fixed[name] = { + end: value.end, + inputId, + start: value.start + } + } else { + fixed[name] = value + } + } + + if (emitInputs) { + fixed.inputs = [...inputs.keys()].map(input => input.toJSON()) + } + + return fixed + } + + toProxy() { + if (!this.proxyCache) { + this.proxyCache = new Proxy(this, this.getProxyProcessor()) + } + return this.proxyCache + } + + toString(stringifier = stringify) { + if (stringifier.stringify) stringifier = stringifier.stringify + let result = '' + stringifier(this, i => { + result += i + }) + return result + } + + warn(result, text, opts) { + let data = { node: this } + for (let i in opts) data[i] = opts[i] + return result.warn(text, data) + } + + get proxyOf() { + return this + } +} + +module.exports = Node +Node.default = Node + +},{"./css-syntax-error":10,"./stringifier":27,"./stringify":28,"./symbols":29}],20:[function(require,module,exports){ +(function (process){(function (){ +'use strict' + +let Container = require('./container') +let Parser = require('./parser') +let Input = require('./input') + +function parse(css, opts) { + let input = new Input(css, opts) + let parser = new Parser(input) + try { + parser.parse() + } catch (e) { + if (process.env.NODE_ENV !== 'production') { + if (e.name === 'CssSyntaxError' && opts && opts.from) { + if (/\.scss$/i.test(opts.from)) { + e.message += + '\nYou tried to parse SCSS with ' + + 'the standard CSS parser; ' + + 'try again with the postcss-scss parser' + } else if (/\.sass/i.test(opts.from)) { + e.message += + '\nYou tried to parse Sass with ' + + 'the standard CSS parser; ' + + 'try again with the postcss-sass parser' + } else if (/\.less$/i.test(opts.from)) { + e.message += + '\nYou tried to parse Less with ' + + 'the standard CSS parser; ' + + 'try again with the postcss-less parser' + } + } + } + throw e + } + + return parser.root +} + +module.exports = parse +parse.default = parse + +Container.registerParse(parse) + +}).call(this)}).call(this,require('_process')) +},{"./container":9,"./input":14,"./parser":21,"_process":33}],21:[function(require,module,exports){ +'use strict' + +let Declaration = require('./declaration') +let tokenizer = require('./tokenize') +let Comment = require('./comment') +let AtRule = require('./at-rule') +let Root = require('./root') +let Rule = require('./rule') + +const SAFE_COMMENT_NEIGHBOR = { + empty: true, + space: true +} + +function findLastWithPosition(tokens) { + for (let i = tokens.length - 1; i >= 0; i--) { + let token = tokens[i] + let pos = token[3] || token[2] + if (pos) return pos + } +} + +class Parser { + constructor(input) { + this.input = input + + this.root = new Root() + this.current = this.root + this.spaces = '' + this.semicolon = false + + this.createTokenizer() + this.root.source = { input, start: { column: 1, line: 1, offset: 0 } } + } + + atrule(token) { + let node = new AtRule() + node.name = token[1].slice(1) + if (node.name === '') { + this.unnamedAtrule(node, token) + } + this.init(node, token[2]) + + let type + let prev + let shift + let last = false + let open = false + let params = [] + let brackets = [] + + while (!this.tokenizer.endOfFile()) { + token = this.tokenizer.nextToken() + type = token[0] + + if (type === '(' || type === '[') { + brackets.push(type === '(' ? ')' : ']') + } else if (type === '{' && brackets.length > 0) { + brackets.push('}') + } else if (type === brackets[brackets.length - 1]) { + brackets.pop() + } + + if (brackets.length === 0) { + if (type === ';') { + node.source.end = this.getPosition(token[2]) + node.source.end.offset++ + this.semicolon = true + break + } else if (type === '{') { + open = true + break + } else if (type === '}') { + if (params.length > 0) { + shift = params.length - 1 + prev = params[shift] + while (prev && prev[0] === 'space') { + prev = params[--shift] + } + if (prev) { + node.source.end = this.getPosition(prev[3] || prev[2]) + node.source.end.offset++ + } + } + this.end(token) + break + } else { + params.push(token) + } + } else { + params.push(token) + } + + if (this.tokenizer.endOfFile()) { + last = true + break + } + } + + node.raws.between = this.spacesAndCommentsFromEnd(params) + if (params.length) { + node.raws.afterName = this.spacesAndCommentsFromStart(params) + this.raw(node, 'params', params) + if (last) { + token = params[params.length - 1] + node.source.end = this.getPosition(token[3] || token[2]) + node.source.end.offset++ + this.spaces = node.raws.between + node.raws.between = '' + } + } else { + node.raws.afterName = '' + node.params = '' + } + + if (open) { + node.nodes = [] + this.current = node + } + } + + checkMissedSemicolon(tokens) { + let colon = this.colon(tokens) + if (colon === false) return + + let founded = 0 + let token + for (let j = colon - 1; j >= 0; j--) { + token = tokens[j] + if (token[0] !== 'space') { + founded += 1 + if (founded === 2) break + } + } + // If the token is a word, e.g. `!important`, `red` or any other valid property's value. + // Then we need to return the colon after that word token. [3] is the "end" colon of that word. + // And because we need it after that one we do +1 to get the next one. + throw this.input.error( + 'Missed semicolon', + token[0] === 'word' ? token[3] + 1 : token[2] + ) + } + + colon(tokens) { + let brackets = 0 + let token, type, prev + for (let [i, element] of tokens.entries()) { + token = element + type = token[0] + + if (type === '(') { + brackets += 1 + } + if (type === ')') { + brackets -= 1 + } + if (brackets === 0 && type === ':') { + if (!prev) { + this.doubleColon(token) + } else if (prev[0] === 'word' && prev[1] === 'progid') { + continue + } else { + return i + } + } + + prev = token + } + return false + } + + comment(token) { + let node = new Comment() + this.init(node, token[2]) + node.source.end = this.getPosition(token[3] || token[2]) + node.source.end.offset++ + + let text = token[1].slice(2, -2) + if (/^\s*$/.test(text)) { + node.text = '' + node.raws.left = text + node.raws.right = '' + } else { + let match = text.match(/^(\s*)([^]*\S)(\s*)$/) + node.text = match[2] + node.raws.left = match[1] + node.raws.right = match[3] + } + } + + createTokenizer() { + this.tokenizer = tokenizer(this.input) + } + + decl(tokens, customProperty) { + let node = new Declaration() + this.init(node, tokens[0][2]) + + let last = tokens[tokens.length - 1] + if (last[0] === ';') { + this.semicolon = true + tokens.pop() + } + + node.source.end = this.getPosition( + last[3] || last[2] || findLastWithPosition(tokens) + ) + node.source.end.offset++ + + while (tokens[0][0] !== 'word') { + if (tokens.length === 1) this.unknownWord(tokens) + node.raws.before += tokens.shift()[1] + } + node.source.start = this.getPosition(tokens[0][2]) + + node.prop = '' + while (tokens.length) { + let type = tokens[0][0] + if (type === ':' || type === 'space' || type === 'comment') { + break + } + node.prop += tokens.shift()[1] + } + + node.raws.between = '' + + let token + while (tokens.length) { + token = tokens.shift() + + if (token[0] === ':') { + node.raws.between += token[1] + break + } else { + if (token[0] === 'word' && /\w/.test(token[1])) { + this.unknownWord([token]) + } + node.raws.between += token[1] + } + } + + if (node.prop[0] === '_' || node.prop[0] === '*') { + node.raws.before += node.prop[0] + node.prop = node.prop.slice(1) + } + + let firstSpaces = [] + let next + while (tokens.length) { + next = tokens[0][0] + if (next !== 'space' && next !== 'comment') break + firstSpaces.push(tokens.shift()) + } + + this.precheckMissedSemicolon(tokens) + + for (let i = tokens.length - 1; i >= 0; i--) { + token = tokens[i] + if (token[1].toLowerCase() === '!important') { + node.important = true + let string = this.stringFrom(tokens, i) + string = this.spacesFromEnd(tokens) + string + if (string !== ' !important') node.raws.important = string + break + } else if (token[1].toLowerCase() === 'important') { + let cache = tokens.slice(0) + let str = '' + for (let j = i; j > 0; j--) { + let type = cache[j][0] + if (str.trim().indexOf('!') === 0 && type !== 'space') { + break + } + str = cache.pop()[1] + str + } + if (str.trim().indexOf('!') === 0) { + node.important = true + node.raws.important = str + tokens = cache + } + } + + if (token[0] !== 'space' && token[0] !== 'comment') { + break + } + } + + let hasWord = tokens.some(i => i[0] !== 'space' && i[0] !== 'comment') + + if (hasWord) { + node.raws.between += firstSpaces.map(i => i[1]).join('') + firstSpaces = [] + } + this.raw(node, 'value', firstSpaces.concat(tokens), customProperty) + + if (node.value.includes(':') && !customProperty) { + this.checkMissedSemicolon(tokens) + } + } + + doubleColon(token) { + throw this.input.error( + 'Double colon', + { offset: token[2] }, + { offset: token[2] + token[1].length } + ) + } + + emptyRule(token) { + let node = new Rule() + this.init(node, token[2]) + node.selector = '' + node.raws.between = '' + this.current = node + } + + end(token) { + if (this.current.nodes && this.current.nodes.length) { + this.current.raws.semicolon = this.semicolon + } + this.semicolon = false + + this.current.raws.after = (this.current.raws.after || '') + this.spaces + this.spaces = '' + + if (this.current.parent) { + this.current.source.end = this.getPosition(token[2]) + this.current.source.end.offset++ + this.current = this.current.parent + } else { + this.unexpectedClose(token) + } + } + + endFile() { + if (this.current.parent) this.unclosedBlock() + if (this.current.nodes && this.current.nodes.length) { + this.current.raws.semicolon = this.semicolon + } + this.current.raws.after = (this.current.raws.after || '') + this.spaces + this.root.source.end = this.getPosition(this.tokenizer.position()) + } + + freeSemicolon(token) { + this.spaces += token[1] + if (this.current.nodes) { + let prev = this.current.nodes[this.current.nodes.length - 1] + if (prev && prev.type === 'rule' && !prev.raws.ownSemicolon) { + prev.raws.ownSemicolon = this.spaces + this.spaces = '' + } + } + } + + // Helpers + + getPosition(offset) { + let pos = this.input.fromOffset(offset) + return { + column: pos.col, + line: pos.line, + offset + } + } + + init(node, offset) { + this.current.push(node) + node.source = { + input: this.input, + start: this.getPosition(offset) + } + node.raws.before = this.spaces + this.spaces = '' + if (node.type !== 'comment') this.semicolon = false + } + + other(start) { + let end = false + let type = null + let colon = false + let bracket = null + let brackets = [] + let customProperty = start[1].startsWith('--') + + let tokens = [] + let token = start + while (token) { + type = token[0] + tokens.push(token) + + if (type === '(' || type === '[') { + if (!bracket) bracket = token + brackets.push(type === '(' ? ')' : ']') + } else if (customProperty && colon && type === '{') { + if (!bracket) bracket = token + brackets.push('}') + } else if (brackets.length === 0) { + if (type === ';') { + if (colon) { + this.decl(tokens, customProperty) + return + } else { + break + } + } else if (type === '{') { + this.rule(tokens) + return + } else if (type === '}') { + this.tokenizer.back(tokens.pop()) + end = true + break + } else if (type === ':') { + colon = true + } + } else if (type === brackets[brackets.length - 1]) { + brackets.pop() + if (brackets.length === 0) bracket = null + } + + token = this.tokenizer.nextToken() + } + + if (this.tokenizer.endOfFile()) end = true + if (brackets.length > 0) this.unclosedBracket(bracket) + + if (end && colon) { + if (!customProperty) { + while (tokens.length) { + token = tokens[tokens.length - 1][0] + if (token !== 'space' && token !== 'comment') break + this.tokenizer.back(tokens.pop()) + } + } + this.decl(tokens, customProperty) + } else { + this.unknownWord(tokens) + } + } + + parse() { + let token + while (!this.tokenizer.endOfFile()) { + token = this.tokenizer.nextToken() + + switch (token[0]) { + case 'space': + this.spaces += token[1] + break + + case ';': + this.freeSemicolon(token) + break + + case '}': + this.end(token) + break + + case 'comment': + this.comment(token) + break + + case 'at-word': + this.atrule(token) + break + + case '{': + this.emptyRule(token) + break + + default: + this.other(token) + break + } + } + this.endFile() + } + + precheckMissedSemicolon(/* tokens */) { + // Hook for Safe Parser + } + + raw(node, prop, tokens, customProperty) { + let token, type + let length = tokens.length + let value = '' + let clean = true + let next, prev + + for (let i = 0; i < length; i += 1) { + token = tokens[i] + type = token[0] + if (type === 'space' && i === length - 1 && !customProperty) { + clean = false + } else if (type === 'comment') { + prev = tokens[i - 1] ? tokens[i - 1][0] : 'empty' + next = tokens[i + 1] ? tokens[i + 1][0] : 'empty' + if (!SAFE_COMMENT_NEIGHBOR[prev] && !SAFE_COMMENT_NEIGHBOR[next]) { + if (value.slice(-1) === ',') { + clean = false + } else { + value += token[1] + } + } else { + clean = false + } + } else { + value += token[1] + } + } + if (!clean) { + let raw = tokens.reduce((all, i) => all + i[1], '') + node.raws[prop] = { raw, value } + } + node[prop] = value + } + + rule(tokens) { + tokens.pop() + + let node = new Rule() + this.init(node, tokens[0][2]) + + node.raws.between = this.spacesAndCommentsFromEnd(tokens) + this.raw(node, 'selector', tokens) + this.current = node + } + + spacesAndCommentsFromEnd(tokens) { + let lastTokenType + let spaces = '' + while (tokens.length) { + lastTokenType = tokens[tokens.length - 1][0] + if (lastTokenType !== 'space' && lastTokenType !== 'comment') break + spaces = tokens.pop()[1] + spaces + } + return spaces + } + + // Errors + + spacesAndCommentsFromStart(tokens) { + let next + let spaces = '' + while (tokens.length) { + next = tokens[0][0] + if (next !== 'space' && next !== 'comment') break + spaces += tokens.shift()[1] + } + return spaces + } + + spacesFromEnd(tokens) { + let lastTokenType + let spaces = '' + while (tokens.length) { + lastTokenType = tokens[tokens.length - 1][0] + if (lastTokenType !== 'space') break + spaces = tokens.pop()[1] + spaces + } + return spaces + } + + stringFrom(tokens, from) { + let result = '' + for (let i = from; i < tokens.length; i++) { + result += tokens[i][1] + } + tokens.splice(from, tokens.length - from) + return result + } + + unclosedBlock() { + let pos = this.current.source.start + throw this.input.error('Unclosed block', pos.line, pos.column) + } + + unclosedBracket(bracket) { + throw this.input.error( + 'Unclosed bracket', + { offset: bracket[2] }, + { offset: bracket[2] + 1 } + ) + } + + unexpectedClose(token) { + throw this.input.error( + 'Unexpected }', + { offset: token[2] }, + { offset: token[2] + 1 } + ) + } + + unknownWord(tokens) { + throw this.input.error( + 'Unknown word', + { offset: tokens[0][2] }, + { offset: tokens[0][2] + tokens[0][1].length } + ) + } + + unnamedAtrule(node, token) { + throw this.input.error( + 'At-rule without name', + { offset: token[2] }, + { offset: token[2] + token[1].length } + ) + } +} + +module.exports = Parser + +},{"./at-rule":7,"./comment":8,"./declaration":11,"./root":25,"./rule":26,"./tokenize":30}],22:[function(require,module,exports){ +(function (Buffer){(function (){ +'use strict' + +let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js') +let { existsSync, readFileSync } = require('fs') +let { dirname, join } = require('path') + +function fromBase64(str) { + if (Buffer) { + return Buffer.from(str, 'base64').toString() + } else { + /* c8 ignore next 2 */ + return window.atob(str) + } +} + +class PreviousMap { + constructor(css, opts) { + if (opts.map === false) return + this.loadAnnotation(css) + this.inline = this.startWith(this.annotation, 'data:') + + let prev = opts.map ? opts.map.prev : undefined + let text = this.loadMap(opts.from, prev) + if (!this.mapFile && opts.from) { + this.mapFile = opts.from + } + if (this.mapFile) this.root = dirname(this.mapFile) + if (text) this.text = text + } + + consumer() { + if (!this.consumerCache) { + this.consumerCache = new SourceMapConsumer(this.text) + } + return this.consumerCache + } + + decodeInline(text) { + let baseCharsetUri = /^data:application\/json;charset=utf-?8;base64,/ + let baseUri = /^data:application\/json;base64,/ + let charsetUri = /^data:application\/json;charset=utf-?8,/ + let uri = /^data:application\/json,/ + + if (charsetUri.test(text) || uri.test(text)) { + return decodeURIComponent(text.substr(RegExp.lastMatch.length)) + } + + if (baseCharsetUri.test(text) || baseUri.test(text)) { + return fromBase64(text.substr(RegExp.lastMatch.length)) + } + + let encoding = text.match(/data:application\/json;([^,]+),/)[1] + throw new Error('Unsupported source map encoding ' + encoding) + } + + getAnnotationURL(sourceMapString) { + return sourceMapString.replace(/^\/\*\s*# sourceMappingURL=/, '').trim() + } + + isMap(map) { + if (typeof map !== 'object') return false + return ( + typeof map.mappings === 'string' || + typeof map._mappings === 'string' || + Array.isArray(map.sections) + ) + } + + loadAnnotation(css) { + let comments = css.match(/\/\*\s*# sourceMappingURL=/gm) + if (!comments) return + + // sourceMappingURLs from comments, strings, etc. + let start = css.lastIndexOf(comments.pop()) + let end = css.indexOf('*/', start) + + if (start > -1 && end > -1) { + // Locate the last sourceMappingURL to avoid pickin + this.annotation = this.getAnnotationURL(css.substring(start, end)) + } + } + + loadFile(path) { + this.root = dirname(path) + if (existsSync(path)) { + this.mapFile = path + return readFileSync(path, 'utf-8').toString().trim() + } + } + + loadMap(file, prev) { + if (prev === false) return false + + if (prev) { + if (typeof prev === 'string') { + return prev + } else if (typeof prev === 'function') { + let prevPath = prev(file) + if (prevPath) { + let map = this.loadFile(prevPath) + if (!map) { + throw new Error( + 'Unable to load previous source map: ' + prevPath.toString() + ) + } + return map + } + } else if (prev instanceof SourceMapConsumer) { + return SourceMapGenerator.fromSourceMap(prev).toString() + } else if (prev instanceof SourceMapGenerator) { + return prev.toString() + } else if (this.isMap(prev)) { + return JSON.stringify(prev) + } else { + throw new Error( + 'Unsupported previous source map format: ' + prev.toString() + ) + } + } else if (this.inline) { + return this.decodeInline(this.annotation) + } else if (this.annotation) { + let map = this.annotation + if (file) map = join(dirname(file), map) + return this.loadFile(map) + } + } + + startWith(string, start) { + if (!string) return false + return string.substr(0, start.length) === start + } + + withContent() { + return !!( + this.consumer().sourcesContent && + this.consumer().sourcesContent.length > 0 + ) + } +} + +module.exports = PreviousMap +PreviousMap.default = PreviousMap + +}).call(this)}).call(this,require("buffer").Buffer) +},{"buffer":3,"fs":2,"path":2,"source-map-js":2}],23:[function(require,module,exports){ +(function (process){(function (){ +'use strict' + +let NoWorkResult = require('./no-work-result') +let LazyResult = require('./lazy-result') +let Document = require('./document') +let Root = require('./root') + +class Processor { + constructor(plugins = []) { + this.version = '8.4.40' + this.plugins = this.normalize(plugins) + } + + normalize(plugins) { + let normalized = [] + for (let i of plugins) { + if (i.postcss === true) { + i = i() + } else if (i.postcss) { + i = i.postcss + } + + if (typeof i === 'object' && Array.isArray(i.plugins)) { + normalized = normalized.concat(i.plugins) + } else if (typeof i === 'object' && i.postcssPlugin) { + normalized.push(i) + } else if (typeof i === 'function') { + normalized.push(i) + } else if (typeof i === 'object' && (i.parse || i.stringify)) { + if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'PostCSS syntaxes cannot be used as plugins. Instead, please use ' + + 'one of the syntax/parser/stringifier options as outlined ' + + 'in your PostCSS runner documentation.' + ) + } + } else { + throw new Error(i + ' is not a PostCSS plugin') + } + } + return normalized + } + + process(css, opts = {}) { + if ( + !this.plugins.length && + !opts.parser && + !opts.stringifier && + !opts.syntax + ) { + return new NoWorkResult(this, css, opts) + } else { + return new LazyResult(this, css, opts) + } + } + + use(plugin) { + this.plugins = this.plugins.concat(this.normalize([plugin])) + return this + } +} + +module.exports = Processor +Processor.default = Processor + +Root.registerProcessor(Processor) +Document.registerProcessor(Processor) + +}).call(this)}).call(this,require('_process')) +},{"./document":12,"./lazy-result":15,"./no-work-result":18,"./root":25,"_process":33}],24:[function(require,module,exports){ +'use strict' + +let Warning = require('./warning') + +class Result { + constructor(processor, root, opts) { + this.processor = processor + this.messages = [] + this.root = root + this.opts = opts + this.css = undefined + this.map = undefined + } + + toString() { + return this.css + } + + warn(text, opts = {}) { + if (!opts.plugin) { + if (this.lastPlugin && this.lastPlugin.postcssPlugin) { + opts.plugin = this.lastPlugin.postcssPlugin + } + } + + let warning = new Warning(text, opts) + this.messages.push(warning) + + return warning + } + + warnings() { + return this.messages.filter(i => i.type === 'warning') + } + + get content() { + return this.css + } +} + +module.exports = Result +Result.default = Result + +},{"./warning":32}],25:[function(require,module,exports){ +'use strict' + +let Container = require('./container') + +let LazyResult, Processor + +class Root extends Container { + constructor(defaults) { + super(defaults) + this.type = 'root' + if (!this.nodes) this.nodes = [] + } + + normalize(child, sample, type) { + let nodes = super.normalize(child) + + if (sample) { + if (type === 'prepend') { + if (this.nodes.length > 1) { + sample.raws.before = this.nodes[1].raws.before + } else { + delete sample.raws.before + } + } else if (this.first !== sample) { + for (let node of nodes) { + node.raws.before = sample.raws.before + } + } + } + + return nodes + } + + removeChild(child, ignore) { + let index = this.index(child) + + if (!ignore && index === 0 && this.nodes.length > 1) { + this.nodes[1].raws.before = this.nodes[index].raws.before + } + + return super.removeChild(child) + } + + toResult(opts = {}) { + let lazy = new LazyResult(new Processor(), this, opts) + return lazy.stringify() + } +} + +Root.registerLazyResult = dependant => { + LazyResult = dependant +} + +Root.registerProcessor = dependant => { + Processor = dependant +} + +module.exports = Root +Root.default = Root + +Container.registerRoot(Root) + +},{"./container":9}],26:[function(require,module,exports){ +'use strict' + +let Container = require('./container') +let list = require('./list') + +class Rule extends Container { + constructor(defaults) { + super(defaults) + this.type = 'rule' + if (!this.nodes) this.nodes = [] + } + + get selectors() { + return list.comma(this.selector) + } + + set selectors(values) { + let match = this.selector ? this.selector.match(/,\s*/) : null + let sep = match ? match[0] : ',' + this.raw('between', 'beforeOpen') + this.selector = values.join(sep) + } +} + +module.exports = Rule +Rule.default = Rule + +Container.registerRule(Rule) + +},{"./container":9,"./list":16}],27:[function(require,module,exports){ +'use strict' + +const DEFAULT_RAW = { + after: '\n', + beforeClose: '\n', + beforeComment: '\n', + beforeDecl: '\n', + beforeOpen: ' ', + beforeRule: '\n', + colon: ': ', + commentLeft: ' ', + commentRight: ' ', + emptyBody: '', + indent: ' ', + semicolon: false +} + +function capitalize(str) { + return str[0].toUpperCase() + str.slice(1) +} + +class Stringifier { + constructor(builder) { + this.builder = builder + } + + atrule(node, semicolon) { + let name = '@' + node.name + let params = node.params ? this.rawValue(node, 'params') : '' + + if (typeof node.raws.afterName !== 'undefined') { + name += node.raws.afterName + } else if (params) { + name += ' ' + } + + if (node.nodes) { + this.block(node, name + params) + } else { + let end = (node.raws.between || '') + (semicolon ? ';' : '') + this.builder(name + params + end, node) + } + } + + beforeAfter(node, detect) { + let value + if (node.type === 'decl') { + value = this.raw(node, null, 'beforeDecl') + } else if (node.type === 'comment') { + value = this.raw(node, null, 'beforeComment') + } else if (detect === 'before') { + value = this.raw(node, null, 'beforeRule') + } else { + value = this.raw(node, null, 'beforeClose') + } + + let buf = node.parent + let depth = 0 + while (buf && buf.type !== 'root') { + depth += 1 + buf = buf.parent + } + + if (value.includes('\n')) { + let indent = this.raw(node, null, 'indent') + if (indent.length) { + for (let step = 0; step < depth; step++) value += indent + } + } + + return value + } + + block(node, start) { + let between = this.raw(node, 'between', 'beforeOpen') + this.builder(start + between + '{', node, 'start') + + let after + if (node.nodes && node.nodes.length) { + this.body(node) + after = this.raw(node, 'after') + } else { + after = this.raw(node, 'after', 'emptyBody') + } + + if (after) this.builder(after) + this.builder('}', node, 'end') + } + + body(node) { + let last = node.nodes.length - 1 + while (last > 0) { + if (node.nodes[last].type !== 'comment') break + last -= 1 + } + + let semicolon = this.raw(node, 'semicolon') + for (let i = 0; i < node.nodes.length; i++) { + let child = node.nodes[i] + let before = this.raw(child, 'before') + if (before) this.builder(before) + this.stringify(child, last !== i || semicolon) + } + } + + comment(node) { + let left = this.raw(node, 'left', 'commentLeft') + let right = this.raw(node, 'right', 'commentRight') + this.builder('/*' + left + node.text + right + '*/', node) + } + + decl(node, semicolon) { + let between = this.raw(node, 'between', 'colon') + let string = node.prop + between + this.rawValue(node, 'value') + + if (node.important) { + string += node.raws.important || ' !important' + } + + if (semicolon) string += ';' + this.builder(string, node) + } + + document(node) { + this.body(node) + } + + raw(node, own, detect) { + let value + if (!detect) detect = own + + // Already had + if (own) { + value = node.raws[own] + if (typeof value !== 'undefined') return value + } + + let parent = node.parent + + if (detect === 'before') { + // Hack for first rule in CSS + if (!parent || (parent.type === 'root' && parent.first === node)) { + return '' + } + + // `root` nodes in `document` should use only their own raws + if (parent && parent.type === 'document') { + return '' + } + } + + // Floating child without parent + if (!parent) return DEFAULT_RAW[detect] + + // Detect style by other nodes + let root = node.root() + if (!root.rawCache) root.rawCache = {} + if (typeof root.rawCache[detect] !== 'undefined') { + return root.rawCache[detect] + } + + if (detect === 'before' || detect === 'after') { + return this.beforeAfter(node, detect) + } else { + let method = 'raw' + capitalize(detect) + if (this[method]) { + value = this[method](root, node) + } else { + root.walk(i => { + value = i.raws[own] + if (typeof value !== 'undefined') return false + }) + } + } + + if (typeof value === 'undefined') value = DEFAULT_RAW[detect] + + root.rawCache[detect] = value + return value + } + + rawBeforeClose(root) { + let value + root.walk(i => { + if (i.nodes && i.nodes.length > 0) { + if (typeof i.raws.after !== 'undefined') { + value = i.raws.after + if (value.includes('\n')) { + value = value.replace(/[^\n]+$/, '') + } + return false + } + } + }) + if (value) value = value.replace(/\S/g, '') + return value + } + + rawBeforeComment(root, node) { + let value + root.walkComments(i => { + if (typeof i.raws.before !== 'undefined') { + value = i.raws.before + if (value.includes('\n')) { + value = value.replace(/[^\n]+$/, '') + } + return false + } + }) + if (typeof value === 'undefined') { + value = this.raw(node, null, 'beforeDecl') + } else if (value) { + value = value.replace(/\S/g, '') + } + return value + } + + rawBeforeDecl(root, node) { + let value + root.walkDecls(i => { + if (typeof i.raws.before !== 'undefined') { + value = i.raws.before + if (value.includes('\n')) { + value = value.replace(/[^\n]+$/, '') + } + return false + } + }) + if (typeof value === 'undefined') { + value = this.raw(node, null, 'beforeRule') + } else if (value) { + value = value.replace(/\S/g, '') + } + return value + } + + rawBeforeOpen(root) { + let value + root.walk(i => { + if (i.type !== 'decl') { + value = i.raws.between + if (typeof value !== 'undefined') return false + } + }) + return value + } + + rawBeforeRule(root) { + let value + root.walk(i => { + if (i.nodes && (i.parent !== root || root.first !== i)) { + if (typeof i.raws.before !== 'undefined') { + value = i.raws.before + if (value.includes('\n')) { + value = value.replace(/[^\n]+$/, '') + } + return false + } + } + }) + if (value) value = value.replace(/\S/g, '') + return value + } + + rawColon(root) { + let value + root.walkDecls(i => { + if (typeof i.raws.between !== 'undefined') { + value = i.raws.between.replace(/[^\s:]/g, '') + return false + } + }) + return value + } + + rawEmptyBody(root) { + let value + root.walk(i => { + if (i.nodes && i.nodes.length === 0) { + value = i.raws.after + if (typeof value !== 'undefined') return false + } + }) + return value + } + + rawIndent(root) { + if (root.raws.indent) return root.raws.indent + let value + root.walk(i => { + let p = i.parent + if (p && p !== root && p.parent && p.parent === root) { + if (typeof i.raws.before !== 'undefined') { + let parts = i.raws.before.split('\n') + value = parts[parts.length - 1] + value = value.replace(/\S/g, '') + return false + } + } + }) + return value + } + + rawSemicolon(root) { + let value + root.walk(i => { + if (i.nodes && i.nodes.length && i.last.type === 'decl') { + value = i.raws.semicolon + if (typeof value !== 'undefined') return false + } + }) + return value + } + + rawValue(node, prop) { + let value = node[prop] + let raw = node.raws[prop] + if (raw && raw.value === value) { + return raw.raw + } + + return value + } + + root(node) { + this.body(node) + if (node.raws.after) this.builder(node.raws.after) + } + + rule(node) { + this.block(node, this.rawValue(node, 'selector')) + if (node.raws.ownSemicolon) { + this.builder(node.raws.ownSemicolon, node, 'end') + } + } + + stringify(node, semicolon) { + /* c8 ignore start */ + if (!this[node.type]) { + throw new Error( + 'Unknown AST node type ' + + node.type + + '. ' + + 'Maybe you need to change PostCSS stringifier.' + ) + } + /* c8 ignore stop */ + this[node.type](node, semicolon) + } +} + +module.exports = Stringifier +Stringifier.default = Stringifier + +},{}],28:[function(require,module,exports){ +'use strict' + +let Stringifier = require('./stringifier') + +function stringify(node, builder) { + let str = new Stringifier(builder) + str.stringify(node) +} + +module.exports = stringify +stringify.default = stringify + +},{"./stringifier":27}],29:[function(require,module,exports){ +'use strict' + +module.exports.isClean = Symbol('isClean') + +module.exports.my = Symbol('my') + +},{}],30:[function(require,module,exports){ +'use strict' + +const SINGLE_QUOTE = "'".charCodeAt(0) +const DOUBLE_QUOTE = '"'.charCodeAt(0) +const BACKSLASH = '\\'.charCodeAt(0) +const SLASH = '/'.charCodeAt(0) +const NEWLINE = '\n'.charCodeAt(0) +const SPACE = ' '.charCodeAt(0) +const FEED = '\f'.charCodeAt(0) +const TAB = '\t'.charCodeAt(0) +const CR = '\r'.charCodeAt(0) +const OPEN_SQUARE = '['.charCodeAt(0) +const CLOSE_SQUARE = ']'.charCodeAt(0) +const OPEN_PARENTHESES = '('.charCodeAt(0) +const CLOSE_PARENTHESES = ')'.charCodeAt(0) +const OPEN_CURLY = '{'.charCodeAt(0) +const CLOSE_CURLY = '}'.charCodeAt(0) +const SEMICOLON = ';'.charCodeAt(0) +const ASTERISK = '*'.charCodeAt(0) +const COLON = ':'.charCodeAt(0) +const AT = '@'.charCodeAt(0) + +const RE_AT_END = /[\t\n\f\r "#'()/;[\\\]{}]/g +const RE_WORD_END = /[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g +const RE_BAD_BRACKET = /.[\r\n"'(/\\]/ +const RE_HEX_ESCAPE = /[\da-f]/i + +module.exports = function tokenizer(input, options = {}) { + let css = input.css.valueOf() + let ignore = options.ignoreErrors + + let code, next, quote, content, escape + let escaped, escapePos, prev, n, currentToken + + let length = css.length + let pos = 0 + let buffer = [] + let returned = [] + + function position() { + return pos + } + + function unclosed(what) { + throw input.error('Unclosed ' + what, pos) + } + + function endOfFile() { + return returned.length === 0 && pos >= length + } + + function nextToken(opts) { + if (returned.length) return returned.pop() + if (pos >= length) return + + let ignoreUnclosed = opts ? opts.ignoreUnclosed : false + + code = css.charCodeAt(pos) + + switch (code) { + case NEWLINE: + case SPACE: + case TAB: + case CR: + case FEED: { + next = pos + do { + next += 1 + code = css.charCodeAt(next) + } while ( + code === SPACE || + code === NEWLINE || + code === TAB || + code === CR || + code === FEED + ) + + currentToken = ['space', css.slice(pos, next)] + pos = next - 1 + break + } + + case OPEN_SQUARE: + case CLOSE_SQUARE: + case OPEN_CURLY: + case CLOSE_CURLY: + case COLON: + case SEMICOLON: + case CLOSE_PARENTHESES: { + let controlChar = String.fromCharCode(code) + currentToken = [controlChar, controlChar, pos] + break + } + + case OPEN_PARENTHESES: { + prev = buffer.length ? buffer.pop()[1] : '' + n = css.charCodeAt(pos + 1) + if ( + prev === 'url' && + n !== SINGLE_QUOTE && + n !== DOUBLE_QUOTE && + n !== SPACE && + n !== NEWLINE && + n !== TAB && + n !== FEED && + n !== CR + ) { + next = pos + do { + escaped = false + next = css.indexOf(')', next + 1) + if (next === -1) { + if (ignore || ignoreUnclosed) { + next = pos + break + } else { + unclosed('bracket') + } + } + escapePos = next + while (css.charCodeAt(escapePos - 1) === BACKSLASH) { + escapePos -= 1 + escaped = !escaped + } + } while (escaped) + + currentToken = ['brackets', css.slice(pos, next + 1), pos, next] + + pos = next + } else { + next = css.indexOf(')', pos + 1) + content = css.slice(pos, next + 1) + + if (next === -1 || RE_BAD_BRACKET.test(content)) { + currentToken = ['(', '(', pos] + } else { + currentToken = ['brackets', content, pos, next] + pos = next + } + } + + break + } + + case SINGLE_QUOTE: + case DOUBLE_QUOTE: { + quote = code === SINGLE_QUOTE ? "'" : '"' + next = pos + do { + escaped = false + next = css.indexOf(quote, next + 1) + if (next === -1) { + if (ignore || ignoreUnclosed) { + next = pos + 1 + break + } else { + unclosed('string') + } + } + escapePos = next + while (css.charCodeAt(escapePos - 1) === BACKSLASH) { + escapePos -= 1 + escaped = !escaped + } + } while (escaped) + + currentToken = ['string', css.slice(pos, next + 1), pos, next] + pos = next + break + } + + case AT: { + RE_AT_END.lastIndex = pos + 1 + RE_AT_END.test(css) + if (RE_AT_END.lastIndex === 0) { + next = css.length - 1 + } else { + next = RE_AT_END.lastIndex - 2 + } + + currentToken = ['at-word', css.slice(pos, next + 1), pos, next] + + pos = next + break + } + + case BACKSLASH: { + next = pos + escape = true + while (css.charCodeAt(next + 1) === BACKSLASH) { + next += 1 + escape = !escape + } + code = css.charCodeAt(next + 1) + if ( + escape && + code !== SLASH && + code !== SPACE && + code !== NEWLINE && + code !== TAB && + code !== CR && + code !== FEED + ) { + next += 1 + if (RE_HEX_ESCAPE.test(css.charAt(next))) { + while (RE_HEX_ESCAPE.test(css.charAt(next + 1))) { + next += 1 + } + if (css.charCodeAt(next + 1) === SPACE) { + next += 1 + } + } + } + + currentToken = ['word', css.slice(pos, next + 1), pos, next] + + pos = next + break + } + + default: { + if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) { + next = css.indexOf('*/', pos + 2) + 1 + if (next === 0) { + if (ignore || ignoreUnclosed) { + next = css.length + } else { + unclosed('comment') + } + } + + currentToken = ['comment', css.slice(pos, next + 1), pos, next] + pos = next + } else { + RE_WORD_END.lastIndex = pos + 1 + RE_WORD_END.test(css) + if (RE_WORD_END.lastIndex === 0) { + next = css.length - 1 + } else { + next = RE_WORD_END.lastIndex - 2 + } + + currentToken = ['word', css.slice(pos, next + 1), pos, next] + buffer.push(currentToken) + pos = next + } + + break + } + } + + pos++ + return currentToken + } + + function back(token) { + returned.push(token) + } + + return { + back, + endOfFile, + nextToken, + position + } +} + +},{}],31:[function(require,module,exports){ +/* eslint-disable no-console */ +'use strict' + +let printed = {} + +module.exports = function warnOnce(message) { + if (printed[message]) return + printed[message] = true + + if (typeof console !== 'undefined' && console.warn) { + console.warn(message) + } +} + +},{}],32:[function(require,module,exports){ +'use strict' + +class Warning { + constructor(text, opts = {}) { + this.type = 'warning' + this.text = text + + if (opts.node && opts.node.source) { + let range = opts.node.rangeBy(opts) + this.line = range.start.line + this.column = range.start.column + this.endLine = range.end.line + this.endColumn = range.end.column + } + + for (let opt in opts) this[opt] = opts[opt] + } + + toString() { + if (this.node) { + return this.node.error(this.text, { + index: this.index, + plugin: this.plugin, + word: this.word + }).message + } + + if (this.plugin) { + return this.plugin + ': ' + this.text + } + + return this.text + } +} + +module.exports = Warning +Warning.default = Warning + +},{}],33:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],"postcss":[function(require,module,exports){ +(function (process){(function (){ +'use strict' + +let CssSyntaxError = require('./css-syntax-error') +let Declaration = require('./declaration') +let LazyResult = require('./lazy-result') +let Container = require('./container') +let Processor = require('./processor') +let stringify = require('./stringify') +let fromJSON = require('./fromJSON') +let Document = require('./document') +let Warning = require('./warning') +let Comment = require('./comment') +let AtRule = require('./at-rule') +let Result = require('./result.js') +let Input = require('./input') +let parse = require('./parse') +let list = require('./list') +let Rule = require('./rule') +let Root = require('./root') +let Node = require('./node') + +function postcss(...plugins) { + if (plugins.length === 1 && Array.isArray(plugins[0])) { + plugins = plugins[0] + } + return new Processor(plugins) +} + +postcss.plugin = function plugin(name, initializer) { + let warningPrinted = false + function creator(...args) { + // eslint-disable-next-line no-console + if (console && console.warn && !warningPrinted) { + warningPrinted = true + // eslint-disable-next-line no-console + console.warn( + name + + ': postcss.plugin was deprecated. Migration guide:\n' + + 'https://evilmartians.com/chronicles/postcss-8-plugin-migration' + ) + if (process.env.LANG && process.env.LANG.startsWith('cn')) { + /* c8 ignore next 7 */ + // eslint-disable-next-line no-console + console.warn( + name + + ': 里面 postcss.plugin 被弃用. 迁移指南:\n' + + 'https://www.w3ctech.com/topic/2226' + ) + } + } + let transformer = initializer(...args) + transformer.postcssPlugin = name + transformer.postcssVersion = new Processor().version + return transformer + } + + let cache + Object.defineProperty(creator, 'postcss', { + get() { + if (!cache) cache = creator() + return cache + } + }) + + creator.process = function (css, processOpts, pluginOpts) { + return postcss([creator(pluginOpts)]).process(css, processOpts) + } + + return creator +} + +postcss.stringify = stringify +postcss.parse = parse +postcss.fromJSON = fromJSON +postcss.list = list + +postcss.comment = defaults => new Comment(defaults) +postcss.atRule = defaults => new AtRule(defaults) +postcss.decl = defaults => new Declaration(defaults) +postcss.rule = defaults => new Rule(defaults) +postcss.root = defaults => new Root(defaults) +postcss.document = defaults => new Document(defaults) + +postcss.CssSyntaxError = CssSyntaxError +postcss.Declaration = Declaration +postcss.Container = Container +postcss.Processor = Processor +postcss.Document = Document +postcss.Comment = Comment +postcss.Warning = Warning +postcss.AtRule = AtRule +postcss.Result = Result +postcss.Input = Input +postcss.Rule = Rule +postcss.Root = Root +postcss.Node = Node + +LazyResult.registerPostcss(postcss) + +module.exports = postcss +postcss.default = postcss + +}).call(this)}).call(this,require('_process')) +},{"./at-rule":7,"./comment":8,"./container":9,"./css-syntax-error":10,"./declaration":11,"./document":12,"./fromJSON":13,"./input":14,"./lazy-result":15,"./list":16,"./node":19,"./parse":20,"./processor":23,"./result.js":24,"./root":25,"./rule":26,"./stringify":28,"./warning":32,"_process":33}]},{},[])("postcss") +}); diff --git a/src/postcss/previous-map.d.ts b/src/postcss/previous-map.d.ts new file mode 100644 index 0000000..23edeb5 --- /dev/null +++ b/src/postcss/previous-map.d.ts @@ -0,0 +1,81 @@ +import { SourceMapConsumer } from 'source-map-js' + +import { ProcessOptions } from './postcss.js' + +declare namespace PreviousMap { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { PreviousMap_ as default } +} + +/** + * Source map information from input CSS. + * For example, source map after Sass compiler. + * + * This class will automatically find source map in input CSS or in file system + * near input file (according `from` option). + * + * ```js + * const root = parse(css, { from: 'a.sass.css' }) + * root.input.map //=> PreviousMap + * ``` + */ +declare class PreviousMap_ { + /** + * `sourceMappingURL` content. + */ + annotation?: string + + /** + * The CSS source identifier. Contains `Input#file` if the user + * set the `from` option, or `Input#id` if they did not. + */ + file?: string + + /** + * Was source map inlined by data-uri to input CSS. + */ + inline: boolean + + /** + * Path to source map file. + */ + mapFile?: string + + /** + * The directory with source map file, if source map is in separated file. + */ + root?: string + + /** + * Source map file content. + */ + text?: string + + /** + * @param css Input CSS source. + * @param opts Process options. + */ + constructor(css: string, opts?: ProcessOptions) + + /** + * Create a instance of `SourceMapGenerator` class + * from the `source-map` library to work with source map information. + * + * It is lazy method, so it will create object only on first call + * and then it will use cache. + * + * @return Object with source map information. + */ + consumer(): SourceMapConsumer + + /** + * Does source map contains `sourcesContent` with input source text. + * + * @return Is `sourcesContent` present. + */ + withContent(): boolean +} + +declare class PreviousMap extends PreviousMap_ {} + +export = PreviousMap diff --git a/src/postcss/processor.d.ts b/src/postcss/processor.d.ts new file mode 100644 index 0000000..50c9a07 --- /dev/null +++ b/src/postcss/processor.d.ts @@ -0,0 +1,115 @@ +import Document from './document.js' +import LazyResult from './lazy-result.js' +import NoWorkResult from './no-work-result.js' +import { + AcceptedPlugin, + Plugin, + ProcessOptions, + TransformCallback, + Transformer +} from './postcss.js' +import Result from './result.js' +import Root from './root.js' + +declare namespace Processor { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Processor_ as default } +} + +/** + * Contains plugins to process CSS. Create one `Processor` instance, + * initialize its plugins, and then use that instance on numerous CSS files. + * + * ```js + * const processor = postcss([autoprefixer, postcssNested]) + * processor.process(css1).then(result => console.log(result.css)) + * processor.process(css2).then(result => console.log(result.css)) + * ``` + */ +declare class Processor_ { + /** + * Plugins added to this processor. + * + * ```js + * const processor = postcss([autoprefixer, postcssNested]) + * processor.plugins.length //=> 2 + * ``` + */ + plugins: (Plugin | TransformCallback | Transformer)[] + + /** + * Current PostCSS version. + * + * ```js + * if (result.processor.version.split('.')[0] !== '6') { + * throw new Error('This plugin works only with PostCSS 6') + * } + * ``` + */ + version: string + + /** + * @param plugins PostCSS plugins + */ + constructor(plugins?: AcceptedPlugin[]) + + /** + * Parses source CSS and returns a `LazyResult` Promise proxy. + * Because some plugins can be asynchronous it doesn’t make + * any transformations. Transformations will be applied + * in the `LazyResult` methods. + * + * ```js + * processor.process(css, { from: 'a.css', to: 'a.out.css' }) + * .then(result => { + * console.log(result.css) + * }) + * ``` + * + * @param css String with input CSS or any object with a `toString()` method, + * like a Buffer. Optionally, send a `Result` instance + * and the processor will take the `Root` from it. + * @param opts Options. + * @return Promise proxy. + */ + process( + css: { toString(): string } | LazyResult | Result | Root | string + ): LazyResult | NoWorkResult + process( + css: { toString(): string } | LazyResult | Result | Root | string, + options: ProcessOptions + ): LazyResult + + /** + * Adds a plugin to be used as a CSS processor. + * + * PostCSS plugin can be in 4 formats: + * * A plugin in `Plugin` format. + * * A plugin creator function with `pluginCreator.postcss = true`. + * PostCSS will call this function without argument to get plugin. + * * A function. PostCSS will pass the function a {@link Root} + * as the first argument and current `Result` instance + * as the second. + * * Another `Processor` instance. PostCSS will copy plugins + * from that instance into this one. + * + * Plugins can also be added by passing them as arguments when creating + * a `postcss` instance (see [`postcss(plugins)`]). + * + * Asynchronous plugins should return a `Promise` instance. + * + * ```js + * const processor = postcss() + * .use(autoprefixer) + * .use(postcssNested) + * ``` + * + * @param plugin PostCSS plugin or `Processor` with plugins. + * @return Current processor to make methods chain. + */ + use(plugin: AcceptedPlugin): this +} + +declare class Processor extends Processor_ {} + +export = Processor diff --git a/src/postcss/result.d.ts b/src/postcss/result.d.ts new file mode 100644 index 0000000..c3dcbda --- /dev/null +++ b/src/postcss/result.d.ts @@ -0,0 +1,206 @@ +import { + Document, + Node, + Plugin, + ProcessOptions, + Root, + SourceMap, + TransformCallback, + Warning, + WarningOptions +} from './postcss.js' +import Processor from './processor.js' + +declare namespace Result { + export interface Message { + [others: string]: any + + /** + * Source PostCSS plugin name. + */ + plugin?: string + + /** + * Message type. + */ + type: string + } + + export interface ResultOptions extends ProcessOptions { + /** + * The CSS node that was the source of the warning. + */ + node?: Node + + /** + * Name of plugin that created this warning. `Result#warn` will fill it + * automatically with `Plugin#postcssPlugin` value. + */ + plugin?: string + } + + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Result_ as default } +} + +/** + * Provides the result of the PostCSS transformations. + * + * A Result instance is returned by `LazyResult#then` + * or `Root#toResult` methods. + * + * ```js + * postcss([autoprefixer]).process(css).then(result => { + * console.log(result.css) + * }) + * ``` + * + * ```js + * const result2 = postcss.parse(css).toResult() + * ``` + */ +declare class Result_ { + /** + * A CSS string representing of `Result#root`. + * + * ```js + * postcss.parse('a{}').toResult().css //=> "a{}" + * ``` + */ + css: string + + /** + * Last runned PostCSS plugin. + */ + lastPlugin: Plugin | TransformCallback + + /** + * An instance of `SourceMapGenerator` class from the `source-map` library, + * representing changes to the `Result#root` instance. + * + * ```js + * result.map.toJSON() //=> { version: 3, file: 'a.css', … } + * ``` + * + * ```js + * if (result.map) { + * fs.writeFileSync(result.opts.to + '.map', result.map.toString()) + * } + * ``` + */ + map: SourceMap + + /** + * Contains messages from plugins (e.g., warnings or custom messages). + * Each message should have type and plugin properties. + * + * ```js + * AtRule: { + * import: (atRule, { result }) { + * const importedFile = parseImport(atRule) + * result.messages.push({ + * type: 'dependency', + * plugin: 'postcss-import', + * file: importedFile, + * parent: result.opts.from + * }) + * } + * } + * ``` + */ + messages: Result.Message[] + + /** + * Options from the `Processor#process` or `Root#toResult` call + * that produced this Result instance.] + * + * ```js + * root.toResult(opts).opts === opts + * ``` + */ + opts: Result.ResultOptions + + /** + * The Processor instance used for this transformation. + * + * ```js + * for (const plugin of result.processor.plugins) { + * if (plugin.postcssPlugin === 'postcss-bad') { + * throw 'postcss-good is incompatible with postcss-bad' + * } + * }) + * ``` + */ + processor: Processor + + /** + * Root node after all transformations. + * + * ```js + * root.toResult().root === root + * ``` + */ + root: RootNode + + /** + * @param processor Processor used for this transformation. + * @param root Root node after all transformations. + * @param opts Options from the `Processor#process` or `Root#toResult`. + */ + constructor(processor: Processor, root: RootNode, opts: Result.ResultOptions) + + /** + * Returns for `Result#css` content. + * + * ```js + * result + '' === result.css + * ``` + * + * @return String representing of `Result#root`. + */ + toString(): string + + /** + * Creates an instance of `Warning` and adds it to `Result#messages`. + * + * ```js + * if (decl.important) { + * result.warn('Avoid !important', { node: decl, word: '!important' }) + * } + * ``` + * + * @param text Warning message. + * @param opts Warning options. + * @return Created warning. + */ + warn(message: string, options?: WarningOptions): Warning + + /** + * Returns warnings from plugins. Filters `Warning` instances + * from `Result#messages`. + * + * ```js + * result.warnings().forEach(warn => { + * console.warn(warn.toString()) + * }) + * ``` + * + * @return Warnings from plugins. + */ + warnings(): Warning[] + + /** + * An alias for the `Result#css` property. + * Use it with syntaxes that generate non-CSS output. + * + * ```js + * result.css === result.content + * ``` + */ + get content(): string +} + +declare class Result extends Result_ {} + +export = Result diff --git a/src/postcss/root.d.ts b/src/postcss/root.d.ts new file mode 100644 index 0000000..9046aac --- /dev/null +++ b/src/postcss/root.d.ts @@ -0,0 +1,87 @@ +import Container, { ContainerProps } from './container.js' +import Document from './document.js' +import { ProcessOptions } from './postcss.js' +import Result from './result.js' + +declare namespace Root { + export interface RootRaws extends Record { + /** + * The space symbols after the last child to the end of file. + */ + after?: string + + /** + * Non-CSS code after `Root`, when `Root` is inside `Document`. + * + * **Experimental:** some aspects of this node could change within minor + * or patch version releases. + */ + codeAfter?: string + + /** + * Non-CSS code before `Root`, when `Root` is inside `Document`. + * + * **Experimental:** some aspects of this node could change within minor + * or patch version releases. + */ + codeBefore?: string + + /** + * Is the last child has an (optional) semicolon. + */ + semicolon?: boolean + } + + export interface RootProps extends ContainerProps { + /** + * Information used to generate byte-to-byte equal node string + * as it was in the origin input. + * */ + raws?: RootRaws + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Root_ as default } +} + +/** + * Represents a CSS file and contains all its parsed nodes. + * + * ```js + * const root = postcss.parse('a{color:black} b{z-index:2}') + * root.type //=> 'root' + * root.nodes.length //=> 2 + * ``` + */ +declare class Root_ extends Container { + nodes: NonNullable + parent: Document | undefined + raws: Root.RootRaws + type: 'root' + + constructor(defaults?: Root.RootProps) + + assign(overrides: object | Root.RootProps): this + clone(overrides?: Partial): Root + cloneAfter(overrides?: Partial): Root + cloneBefore(overrides?: Partial): Root + + /** + * Returns a `Result` instance representing the root’s CSS. + * + * ```js + * const root1 = postcss.parse(css1, { from: 'a.css' }) + * const root2 = postcss.parse(css2, { from: 'b.css' }) + * root1.append(root2) + * const result = root1.toResult({ to: 'all.css', map: true }) + * ``` + * + * @param opts Options. + * @return Result with current root’s CSS. + */ + toResult(options?: ProcessOptions): Result +} + +declare class Root extends Root_ {} + +export = Root diff --git a/src/postcss/rule.d.ts b/src/postcss/rule.d.ts new file mode 100644 index 0000000..568dcc1 --- /dev/null +++ b/src/postcss/rule.d.ts @@ -0,0 +1,119 @@ +import Container, { + ContainerProps, + ContainerWithChildren +} from './container.js' + +declare namespace Rule { + export interface RuleRaws extends Record { + /** + * The space symbols after the last child of the node to the end of the node. + */ + after?: string + + /** + * The space symbols before the node. It also stores `*` + * and `_` symbols before the declaration (IE hack). + */ + before?: string + + /** + * The symbols between the selector and `{` for rules. + */ + between?: string + + /** + * Contains `true` if there is semicolon after rule. + */ + ownSemicolon?: string + + /** + * The rule’s selector with comments. + */ + selector?: { + raw: string + value: string + } + + /** + * Contains `true` if the last child has an (optional) semicolon. + */ + semicolon?: boolean + } + + export interface RuleProps extends ContainerProps { + /** Information used to generate byte-to-byte equal node string as it was in the origin input. */ + raws?: RuleRaws + /** Selector or selectors of the rule. */ + selector?: string + /** Selectors of the rule represented as an array of strings. */ + selectors?: string[] + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Rule_ as default } +} + +/** + * Represents a CSS rule: a selector followed by a declaration block. + * + * ```js + * Once (root, { Rule }) { + * let a = new Rule({ selector: 'a' }) + * a.append(…) + * root.append(a) + * } + * ``` + * + * ```js + * const root = postcss.parse('a{}') + * const rule = root.first + * rule.type //=> 'rule' + * rule.toString() //=> 'a{}' + * ``` + */ +declare class Rule_ extends Container { + nodes: NonNullable + parent: ContainerWithChildren | undefined + raws: Rule.RuleRaws + /** + * The rule’s full selector represented as a string. + * + * ```js + * const root = postcss.parse('a, b { }') + * const rule = root.first + * rule.selector //=> 'a, b' + * ``` + */ + get selector(): string + set selector(value: string); + + /** + * An array containing the rule’s individual selectors. + * Groups of selectors are split at commas. + * + * ```js + * const root = postcss.parse('a, b { }') + * const rule = root.first + * + * rule.selector //=> 'a, b' + * rule.selectors //=> ['a', 'b'] + * + * rule.selectors = ['a', 'strong'] + * rule.selector //=> 'a, strong' + * ``` + */ + get selectors(): string[] + set selectors(values: string[]); + + type: 'rule' + + constructor(defaults?: Rule.RuleProps) + assign(overrides: object | Rule.RuleProps): this + clone(overrides?: Partial): Rule + cloneAfter(overrides?: Partial): Rule + cloneBefore(overrides?: Partial): Rule +} + +declare class Rule extends Rule_ {} + +export = Rule diff --git a/src/postcss/stringifier.d.ts b/src/postcss/stringifier.d.ts new file mode 100644 index 0000000..f707a6a --- /dev/null +++ b/src/postcss/stringifier.d.ts @@ -0,0 +1,46 @@ +import { + AnyNode, + AtRule, + Builder, + Comment, + Container, + Declaration, + Document, + Root, + Rule +} from './postcss.js' + +declare namespace Stringifier { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Stringifier_ as default } +} + +declare class Stringifier_ { + builder: Builder + constructor(builder: Builder) + atrule(node: AtRule, semicolon?: boolean): void + beforeAfter(node: AnyNode, detect: 'after' | 'before'): string + block(node: AnyNode, start: string): void + body(node: Container): void + comment(node: Comment): void + decl(node: Declaration, semicolon?: boolean): void + document(node: Document): void + raw(node: AnyNode, own: null | string, detect?: string): string + rawBeforeClose(root: Root): string | undefined + rawBeforeComment(root: Root, node: Comment): string | undefined + rawBeforeDecl(root: Root, node: Declaration): string | undefined + rawBeforeOpen(root: Root): string | undefined + rawBeforeRule(root: Root): string | undefined + rawColon(root: Root): string | undefined + rawEmptyBody(root: Root): string | undefined + rawIndent(root: Root): string | undefined + rawSemicolon(root: Root): boolean | undefined + rawValue(node: AnyNode, prop: string): string + root(node: Root): void + rule(node: Rule): void + stringify(node: AnyNode, semicolon?: boolean): void +} + +declare class Stringifier extends Stringifier_ {} + +export = Stringifier diff --git a/src/postcss/stringify.d.ts b/src/postcss/stringify.d.ts new file mode 100644 index 0000000..06ad0b4 --- /dev/null +++ b/src/postcss/stringify.d.ts @@ -0,0 +1,9 @@ +import { Stringifier } from './postcss.js' + +interface Stringify extends Stringifier { + default: Stringify +} + +declare const stringify: Stringify + +export = stringify diff --git a/src/postcss/warning.d.ts b/src/postcss/warning.d.ts new file mode 100644 index 0000000..b25bba8 --- /dev/null +++ b/src/postcss/warning.d.ts @@ -0,0 +1,147 @@ +import { RangePosition } from './css-syntax-error.js' +import Node from './node.js' + +declare namespace Warning { + export interface WarningOptions { + /** + * End position, exclusive, in CSS node string that caused the warning. + */ + end?: RangePosition + + /** + * End index, exclusive, in CSS node string that caused the warning. + */ + endIndex?: number + + /** + * Start index, inclusive, in CSS node string that caused the warning. + */ + index?: number + + /** + * CSS node that caused the warning. + */ + node?: Node + + /** + * Name of the plugin that created this warning. `Result#warn` fills + * this property automatically. + */ + plugin?: string + + /** + * Start position, inclusive, in CSS node string that caused the warning. + */ + start?: RangePosition + + /** + * Word in CSS source that caused the warning. + */ + word?: string + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + export { Warning_ as default } +} + +/** + * Represents a plugin’s warning. It can be created using `Node#warn`. + * + * ```js + * if (decl.important) { + * decl.warn(result, 'Avoid !important', { word: '!important' }) + * } + * ``` + */ +declare class Warning_ { + /** + * Column for inclusive start position in the input file with this warning’s source. + * + * ```js + * warning.column //=> 6 + * ``` + */ + column: number + + /** + * Column for exclusive end position in the input file with this warning’s source. + * + * ```js + * warning.endColumn //=> 4 + * ``` + */ + endColumn?: number + + /** + * Line for exclusive end position in the input file with this warning’s source. + * + * ```js + * warning.endLine //=> 6 + * ``` + */ + endLine?: number + + /** + * Line for inclusive start position in the input file with this warning’s source. + * + * ```js + * warning.line //=> 5 + * ``` + */ + line: number + + /** + * Contains the CSS node that caused the warning. + * + * ```js + * warning.node.toString() //=> 'color: white !important' + * ``` + */ + node: Node + + /** + * The name of the plugin that created this warning. + * When you call `Node#warn` it will fill this property automatically. + * + * ```js + * warning.plugin //=> 'postcss-important' + * ``` + */ + plugin: string + + /** + * The warning message. + * + * ```js + * warning.text //=> 'Try to avoid !important' + * ``` + */ + text: string + + /** + * Type to filter warnings from `Result#messages`. + * Always equal to `"warning"`. + */ + type: 'warning' + + /** + * @param text Warning message. + * @param opts Warning options. + */ + constructor(text: string, opts?: Warning.WarningOptions) + + /** + * Returns a warning position and message. + * + * ```js + * warning.toString() //=> 'postcss-lint:a.css:10:14: Avoid !important' + * ``` + * + * @return Warning position and message. + */ + toString(): string +} + +declare class Warning extends Warning_ {} + +export = Warning diff --git a/src/preprocess/index.ts b/src/preprocess/index.ts new file mode 100644 index 0000000..28163de --- /dev/null +++ b/src/preprocess/index.ts @@ -0,0 +1,36 @@ +// [note-to-mp 重构] 内容预处理模块 + +// 行级颜色块语法:||r text / ||g text / ||b text / ||y text / || text +const LINE_COLOR_RE = /^\|\|(r|g|b|y)?\s+(.*)$/; +const FIG_RE = /\[fig([^\n]*?)\/_?]/g; // 简单题注 + +function wrapColorLine(code: string | undefined, text: string): string { + const colorMap: Record = { + r: '#ffe5e5', + g: '#e5ffe9', + b: '#e5f1ff', + y: '#fff7d6', + '': '#f2f2f2' + }; + const c = (code && colorMap[code]) || colorMap['']; + return `

    ${text}

    `; +} + +export function preprocessContent(markdown: string): string { + const lines = markdown.split(/\r?\n/); + const out: string[] = []; + for (const line of lines) { + const m = line.match(LINE_COLOR_RE); + if (m) { + out.push(wrapColorLine(m[1], m[2])); + } else { + out.push(line); + } + } + let joined = out.join('\n'); + joined = joined.replace(FIG_RE, (_m, g1) => { + const text = g1.trim(); + return `${text}`; + }); + return joined; +} diff --git a/src/refactor-plan.md b/src/refactor-plan.md new file mode 100644 index 0000000..ab6f617 --- /dev/null +++ b/src/refactor-plan.md @@ -0,0 +1,69 @@ +# note-to-mp 重构规划 (模块与接口草案) + +> 标记格式: // [note-to-mp 重构] + +## 目标概要 +- 模块化:图片处理、元数据、Gallery、内容预处理、渲染管线分离。 +- 清晰接口:对外暴露统一渲染与数据提取 API。 +- 可测试:核心逻辑函数纯函数化,最小化对 Obsidian 运行时依赖。 + +## 模块划分 +1. 图片处理模块 (image/) + - 统一识别 wikilink 与 markdown 图片语法 + - LocalImage 结构: { original: string; basename: string; alt?: string; sourceType: 'wikilink'|'markdown'; index: number; } + - LocalImageManager: 收集、查询、封面候选、上传占位接口 + - 正则常量: LocalFileRegex + +2. 元数据与封面 (meta/) + - extractWeChatMeta(raw: string): WeChatMetaRaw + - getWeChatArticleMeta(): 返回最近一次渲染缓存的 meta + - getMetadata(images: LocalImage[], metaRaw: WeChatMetaRaw): FinalMeta + - 回退策略: frontmatter cover > metaRaw.coverLink > images[0] + +3. Gallery 支持 (gallery/) + - transformGalleryShortcodes(content: string): { content: string; extracted?: GalleryInfo } + - selectGalleryImages(dir: string, options): Promise + - 语法: 单行 self-closing 与 块级形式 + +4. 内容预处理 (preprocess/) + - preprocessContent(markdown: string): string + - 行级语法: ||r / ||g / ||b / ||y / || (默认灰) + - figure 语法: [fig text/] + +5. 渲染管线 (render/) + - renderMarkdown(file: TFile): Promise + - 内部阶段: + Raw -> extractWeChatMeta -> strip frontmatter -> transformGalleryShortcodes -> preprocessContent -> markdown parse (自定义 tokenizer) -> HTML + 样式注入 -> metadata 汇总 + +6. 上传/微信接口 (weixin/) + - 包装现有 weixin-api.ts 函数 + 错误封装 + +## 数据结构 +```ts +interface LocalImage { original: string; basename: string; alt?: string; sourceType: 'wikilink'|'markdown'; index: number; } +interface WeChatMetaRaw { title?: string; author?: string; coverLink?: string; rawImage?: string; hasFrontmatter: boolean; } +interface FinalMeta { title: string; author?: string; coverImage?: LocalImage; coverLink?: string; } +interface RenderedArticle { html: string; css?: string; meta: FinalMeta; images: LocalImage[]; raw: string; } +``` + +## 关键正则 +- frontmatter: ^---[\s\S]*?\n--- +- wikilink image: !\[\[(.+?)\]\] +- markdown image: !\[[^\]]*\]\(([^\n\r\)]+)\) +- gallery block: /{{}}([\s\S]*?){{<\/gallery>}}/g +- gallery figure: /{{]*>}}/g + +## 风险点 +- 正则误判 frontmatter +- 图片在预处理阶段被破坏索引 +- 多次渲染缓存污染 + +## 缓解 +- 提取后不修改原文本副本 +- 维护渲染上下文对象 (RenderContext) + +## 后续实现顺序 +图片处理 -> 元数据 -> Gallery -> 预处理 -> 渲染组装 -> 接口对接现有 NotePreview + +--- +(实现过程中该文档可增补) diff --git a/src/render/index.ts b/src/render/index.ts new file mode 100644 index 0000000..62f60a9 --- /dev/null +++ b/src/render/index.ts @@ -0,0 +1,50 @@ +// [note-to-mp 重构] 渲染管线模块 +import { App, TFile, MarkdownRenderer } from 'obsidian'; +import { parseImagesFromMarkdown, LocalImage, LocalImageManager } from '../image'; +import { extractWeChatMeta, getMetadata, FinalMeta } from '../meta'; +import { transformGalleryShortcodes } from '../gallery'; +import { preprocessContent } from '../preprocess'; + +export interface RenderedArticle { + html: string; + meta: FinalMeta; + images: LocalImage[]; + raw: string; +} + +export class RenderService { + private app: App; + private imageManager = new LocalImageManager(); + + constructor(app: App) { + this.app = app; + } + + async renderFile(file: TFile): Promise { + const raw = await this.app.vault.read(file); + return this.renderRaw(raw, file.path); + } + + async renderRaw(raw: string, path?: string): Promise { + this.imageManager.clear(); + // 1. frontmatter + 基础元数据 + const { meta: rawMeta, body } = extractWeChatMeta(raw); + // 2. gallery 转换 + const galleryRes = transformGalleryShortcodes(body); + // 3. 预处理行级语法 + const preprocessed = preprocessContent(galleryRes.content); + // 4. 图片解析 + const images = parseImagesFromMarkdown(preprocessed); + images.forEach(i => this.imageManager.add(i)); + // 5. 获取最终 meta(封面回退) + const finalMeta = getMetadata(images, rawMeta); + // 6. markdown -> HTML (使用 Obsidian 内部渲染管线) + const el = document.createElement('div'); + // NOTE: 这里简化,实际应考虑自定义 tokenizer;后续可补充 + await MarkdownRenderer.renderMarkdown(preprocessed, el, path || '', this.app as any); + // 7. 注入简单样式 (可外置) + const style = ``; + + return { html: style + el.innerHTML, meta: finalMeta, images, raw }; + } +} diff --git a/src/setting-tab.ts b/src/setting-tab.ts new file mode 100644 index 0000000..4a3ff01 --- /dev/null +++ b/src/setting-tab.ts @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { App, TextAreaComponent, PluginSettingTab, Setting, Notice, sanitizeHTMLToDom } from 'obsidian'; +import NoteToMpPlugin from './main'; +import { wxGetToken,wxEncrypt } from './weixin-api'; +import { cleanMathCache } from './markdown/math'; +import { NMPSettings } from './settings'; +import { DocModal } from './doc-modal'; + +export class NoteToMpSettingTab extends PluginSettingTab { + plugin: NoteToMpPlugin; + wxInfo: string; + wxTextArea: TextAreaComponent|null; + settings: NMPSettings; + + constructor(app: App, plugin: NoteToMpPlugin) { + super(app, plugin); + this.plugin = plugin; + this.settings = NMPSettings.getInstance(); + this.wxInfo = this.parseWXInfo(); + } + + displayWXInfo(txt:string) { + this.wxTextArea?.setValue(txt); + } + + parseWXInfo() { + const wxInfo = this.settings.wxInfo; + if (wxInfo.length == 0) { + return ''; + } + + let res = ''; + for (let wx of wxInfo) { + res += `${wx.name}|${wx.appid}|********\n`; + } + return res; + } + + async testWXInfo() { + const authKey = this.settings.authKey; + if (authKey.length == 0) { + new Notice('请先设置authKey'); + return; + } + const wxInfo = this.settings.wxInfo; + if (wxInfo.length == 0) { + new Notice('请先设置公众号信息'); + return; + } + try { + const docUrl = 'https://mp.weixin.qq.com/s/rk5CTPGr5ftly8PtYgSjCQ'; + for (let wx of wxInfo) { + const res = await wxGetToken(authKey, wx.appid, wx.secret.replace('SECRET', '')); + if (res.status != 200) { + const data = res.json; + const { code, message } = data; + let content = message; + if (code === 50002) { + content = '用户受限,可能是您的公众号被冻结或注销,请联系微信客服处理'; + } + else if (code === 40125) { + content = 'AppSecret错误,请检查或者重置,详细操作步骤请参考下方文档'; + } + else if (code === 40164) { + content = 'IP地址不在白名单中,请将如下地址添加到白名单:
    59.110.112.211
    154.8.198.218
    详细步骤请参考下方文档'; + } + const modal = new DocModal(this.app, `${wx.name} 测试失败`, content, docUrl); + modal.open(); + break + } + + const data = res.json; + if (data.token.length == 0) { + new Notice(`${wx.name}|${wx.appid} 测试失败`); + break + } + new Notice(`${wx.name} 测试通过`); + } + } catch (error) { + new Notice(`测试失败:${error}`); + } + } + + async encrypt() { + if (this.wxInfo.length == 0) { + new Notice('请输入内容'); + return false; + } + + if (this.settings.wxInfo.length > 0) { + new Notice('已经保存过了,请先清除!'); + return false; + } + + const wechat = []; + const lines = this.wxInfo.split('\n'); + for (let line of lines) { + line = line.trim(); + if (line.length == 0) { + continue; + } + const items = line.split('|'); + if (items.length != 3) { + new Notice('格式错误,请检查'); + return false; + } + const name = items[0]; + const appid = items[1].trim(); + const secret = items[2].trim(); + wechat.push({name, appid, secret}); + } + + if (wechat.length == 0) { + return false; + } + + try { + const res = await wxEncrypt(this.settings.authKey, wechat); + if (res.status != 200) { + const data = res.json; + new Notice(`${data.message}`); + return false; + } + + const data = res.json; + for (let wx of wechat) { + wx.secret = data[wx.appid]; + } + + this.settings.wxInfo = wechat; + await this.plugin.saveSettings(); + this.wxInfo = this.parseWXInfo(); + this.displayWXInfo(this.wxInfo); + new Notice('保存成功'); + return true; + + } catch (error) { + new Notice(`保存失败:${error}`); + console.error(error); + } + + return false; + } + + async clear() { + this.settings.wxInfo = []; + await this.plugin.saveSettings(); + this.wxInfo = ''; + this.displayWXInfo('') + } + + display() { + const {containerEl} = this; + + containerEl.empty(); + + this.wxInfo = this.parseWXInfo(); + + const helpEl = containerEl.createEl('div'); + helpEl.style.cssText = 'display: flex;flex-direction: row;align-items: center;'; + helpEl.createEl('h2', {text: '帮助文档'}).style.cssText = 'margin-right: 10px;'; + helpEl.createEl('a', {text: 'https://sunboshi.tech/doc', attr: {href: 'https://sunboshi.tech/doc'}}); + + containerEl.createEl('h2', {text: '插件设置'}); + + new Setting(containerEl) + .setName('默认样式') + .addDropdown(dropdown => { + const styles = this.plugin.assetsManager.themes; + for (let s of styles) { + dropdown.addOption(s.className, s.name); + } + dropdown.setValue(this.settings.defaultStyle); + dropdown.onChange(async (value) => { + this.settings.defaultStyle = value; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('代码高亮') + .addDropdown(dropdown => { + const styles = this.plugin.assetsManager.highlights; + for (let s of styles) { + dropdown.addOption(s.name, s.name); + } + dropdown.setValue(this.settings.defaultHighlight); + dropdown.onChange(async (value) => { + this.settings.defaultHighlight = value; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('在工具栏展示样式选择') + .setDesc('建议在移动端关闭,可以增大文章预览区域') + .addToggle(toggle => { + toggle.setValue(this.settings.showStyleUI); + toggle.onChange(async (value) => { + this.settings.showStyleUI = value; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('链接展示样式') + .addDropdown(dropdown => { + dropdown.addOption('inline', '内嵌'); + dropdown.addOption('footnote', '脚注'); + dropdown.setValue(this.settings.linkStyle); + dropdown.onChange(async (value) => { + this.settings.linkStyle = value; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('文件嵌入展示样式') + .addDropdown(dropdown => { + dropdown.addOption('quote', '引用'); + dropdown.addOption('content', '正文'); + dropdown.setValue(this.settings.embedStyle); + dropdown.onChange(async (value) => { + this.settings.embedStyle = value; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('数学公式语法') + .addDropdown(dropdown => { + dropdown.addOption('latex', 'latex'); + dropdown.addOption('asciimath', 'asciimath'); + dropdown.setValue(this.settings.math); + dropdown.onChange(async (value) => { + this.settings.math = value; + cleanMathCache(); + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('显示代码行号') + .addToggle(toggle => { + toggle.setValue(this.settings.lineNumber); + toggle.onChange(async (value) => { + this.settings.lineNumber = value; + await this.plugin.saveSettings(); + }); + }) + + new Setting(containerEl) + .setName('启用空行渲染') + .addToggle(toggle => { + toggle.setValue(this.settings.enableEmptyLine); + toggle.onChange(async (value) => { + this.settings.enableEmptyLine = value; + await this.plugin.saveSettings(); + }); + }) + + new Setting(containerEl) + .setName('渲染图片标题') + .addToggle(toggle => { + toggle.setValue(this.settings.useFigcaption); + toggle.onChange(async (value) => { + this.settings.useFigcaption = value; + await this.plugin.saveSettings(); + }); + }) + + new Setting(containerEl) + .setName('Excalidraw 渲染为 PNG 图片') + .addToggle(toggle => { + toggle.setValue(this.settings.excalidrawToPNG); + toggle.onChange(async (value) => { + this.settings.excalidrawToPNG = value; + await this.plugin.saveSettings(); + }); + }) + + new Setting(containerEl) + .setName('水印图片') + .addText(text => { + text.setPlaceholder('请输入图片名称') + .setValue(this.settings.watermark) + .onChange(async (value) => { + this.settings.watermark = value.trim(); + await this.plugin.saveSettings(); + }) + .inputEl.setAttr('style', 'width: 320px;') + }) + + new Setting(containerEl) + .setName('获取更多主题') + .addButton(button => { + button.setButtonText('下载'); + button.onClick(async () => { + button.setButtonText('下载中...'); + await this.plugin.assetsManager.downloadThemes(); + button.setButtonText('下载完成'); + }); + }) + .addButton(button => { + button.setIcon('folder-open'); + button.onClick(async () => { + await this.plugin.assetsManager.openAssets(); + }); + }); + + new Setting(containerEl) + .setName('清空主题') + .addButton(button => { + button.setButtonText('清空'); + button.onClick(async () => { + await this.plugin.assetsManager.removeThemes(); + this.settings.resetStyelAndHighlight(); + await this.plugin.saveSettings(); + }); + }) + new Setting(containerEl) + .setName('全局CSS属性') + .setDesc('只能填写CSS属性,不能写选择器') + .addTextArea(text => { + this.wxTextArea = text; + text.setPlaceholder('请输入CSS属性,如:background: #fff;padding: 10px;') + .setValue(this.settings.baseCSS) + .onChange(async (value) => { + this.settings.baseCSS = value; + await this.plugin.saveSettings(); + }) + .inputEl.setAttr('style', 'width: 520px; height: 60px;'); + }) + const customCSSDoc = '使用指南:https://sunboshi.tech/customcss'; + new Setting(containerEl) + .setName('自定义CSS笔记') + .setDesc(sanitizeHTMLToDom(customCSSDoc)) + .addText(text => { + text.setPlaceholder('请输入自定义CSS笔记标题') + .setValue(this.settings.customCSSNote) + .onChange(async (value) => { + this.settings.customCSSNote = value.trim(); + await this.plugin.saveSettings(); + await this.plugin.assetsManager.loadCustomCSS(); + }) + .inputEl.setAttr('style', 'width: 320px;') + }); + + const expertDoc = '使用指南:https://sunboshi.tech/expert'; + new Setting(containerEl) + .setName('专家设置笔记') + .setDesc(sanitizeHTMLToDom(expertDoc)) + .addText(text => { + text.setPlaceholder('请输入专家设置笔记标题') + .setValue(this.settings.expertSettingsNote) + .onChange(async (value) => { + this.settings.expertSettingsNote = value.trim(); + await this.plugin.saveSettings(); + await this.plugin.assetsManager.loadExpertSettings(); + }) + .inputEl.setAttr('style', 'width: 320px;') + }); + + let descHtml = '详情说明:https://sunboshi.tech/subscribe'; + if (this.settings.isVip) { + descHtml = '👑永久会员
    ' + descHtml; + } + else if (this.settings.expireat) { + const timestr = this.settings.expireat.toLocaleString(); + descHtml = `有效期至:${timestr}
    ${descHtml}` + } + new Setting(containerEl) + .setName('注册码(AuthKey)') + .setDesc(sanitizeHTMLToDom(descHtml)) + .addText(text => { + text.setPlaceholder('请输入注册码') + .setValue(this.settings.authKey) + .onChange(async (value) => { + this.settings.authKey = value.trim(); + this.settings.getExpiredDate(); + await this.plugin.saveSettings(); + }) + .inputEl.setAttr('style', 'width: 320px;') + }).descEl.setAttr('style', '-webkit-user-select: text; user-select: text;') + + + let isClear = this.settings.wxInfo.length > 0; + let isRealClear = false; + const buttonText = isClear ? '清空公众号信息' : '保存公众号信息'; + new Setting(containerEl) + .setName('公众号信息') + .addTextArea(text => { + this.wxTextArea = text; + text.setPlaceholder('请输入公众号信息\n格式:公众号名称|公众号AppID|公众号AppSecret\n多个公众号请换行输入\n输入完成后点击加密按钮') + .setValue(this.wxInfo) + .onChange(value => { + this.wxInfo = value; + }) + .inputEl.setAttr('style', 'width: 520px; height: 120px;'); + }) + + new Setting(containerEl).addButton(button => { + button.setButtonText(buttonText); + button.onClick(async () => { + if (isClear) { + isRealClear = true; + isClear = false; + button.setButtonText('确认清空?'); + } + else if (isRealClear) { + isRealClear = false; + isClear = false; + this.clear(); + button.setButtonText('保存公众号信息'); + } + else { + button.setButtonText('保存中...'); + if (await this.encrypt()) { + isClear = true; + isRealClear = false; + button.setButtonText('清空公众号信息'); + } + else { + button.setButtonText('保存公众号信息'); + } + } + }); + }) + .addButton(button => { + button.setButtonText('测试公众号'); + button.onClick(async () => { + button.setButtonText('测试中...'); + await this.testWXInfo(); + button.setButtonText('测试公众号'); + }) + }) + } +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..b8f2c23 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { wxKeyInfo } from './weixin-api'; + +export class NMPSettings { + defaultStyle: string; + defaultHighlight: string; + showStyleUI: boolean; + linkStyle: string; + embedStyle: string; + lineNumber: boolean; + authKey: string; + useCustomCss: boolean; + customCSSNote: string; + expertSettingsNote: string; + wxInfo: {name:string, appid:string, secret:string}[]; + math: string; + expireat: Date | null = null; + isVip: boolean = false; + baseCSS: string; + watermark: string; + useFigcaption: boolean; + excalidrawToPNG: boolean; + isLoaded: boolean = false; + enableEmptyLine: boolean = false; + + private static instance: NMPSettings; + + // 静态方法,用于获取实例 + public static getInstance(): NMPSettings { + if (!NMPSettings.instance) { + NMPSettings.instance = new NMPSettings(); + } + return NMPSettings.instance; + } + + private constructor() { + this.defaultStyle = 'obsidian-light'; + this.defaultHighlight = '默认'; + this.showStyleUI = true; + this.linkStyle = 'inline'; + this.embedStyle = 'content'; + this.lineNumber = true; + this.useCustomCss = false; + this.authKey = ''; + this.wxInfo = []; + this.math = 'latex'; + this.baseCSS = ''; + this.watermark = ''; + this.useFigcaption = false; + this.customCSSNote = ''; + this.excalidrawToPNG = false; + this.expertSettingsNote = ''; + this.enableEmptyLine = false; + } + + resetStyelAndHighlight() { + this.defaultStyle = 'obsidian-light'; + this.defaultHighlight = '默认'; + } + + public static loadSettings(data: any) { + if (!data) { + return + } + const { + defaultStyle, + linkStyle, + embedStyle, + showStyleUI, + lineNumber, + defaultHighlight, + authKey, + wxInfo, + math, + useCustomCss, + baseCSS, + watermark, + useFigcaption, + customCSSNote, + excalidrawToPNG, + expertSettingsNote, + ignoreEmptyLine, + } = data; + + const settings = NMPSettings.getInstance(); + if (defaultStyle) { + settings.defaultStyle = defaultStyle; + } + if (defaultHighlight) { + settings.defaultHighlight = defaultHighlight; + } + if (showStyleUI !== undefined) { + settings.showStyleUI = showStyleUI; + } + if (linkStyle) { + settings.linkStyle = linkStyle; + } + if (embedStyle) { + settings.embedStyle = embedStyle; + } + if (lineNumber !== undefined) { + settings.lineNumber = lineNumber; + } + if (authKey) { + settings.authKey = authKey; + } + if (wxInfo) { + settings.wxInfo = wxInfo; + } + if (math) { + settings.math = math; + } + if (useCustomCss !== undefined) { + settings.useCustomCss = useCustomCss; + } + if (baseCSS) { + settings.baseCSS = baseCSS; + } + if (watermark) { + settings.watermark = watermark; + } + if (useFigcaption !== undefined) { + settings.useFigcaption = useFigcaption; + } + if (customCSSNote) { + settings.customCSSNote = customCSSNote; + } + if (excalidrawToPNG !== undefined) { + settings.excalidrawToPNG = excalidrawToPNG; + } + if (expertSettingsNote) { + settings.expertSettingsNote = expertSettingsNote; + } + if (ignoreEmptyLine !== undefined) { + settings.enableEmptyLine = ignoreEmptyLine; + } + settings.getExpiredDate(); + settings.isLoaded = true; + } + + public static allSettings() { + const settings = NMPSettings.getInstance(); + return { + 'defaultStyle': settings.defaultStyle, + 'defaultHighlight': settings.defaultHighlight, + 'showStyleUI': settings.showStyleUI, + 'linkStyle': settings.linkStyle, + 'embedStyle': settings.embedStyle, + 'lineNumber': settings.lineNumber, + 'authKey': settings.authKey, + 'wxInfo': settings.wxInfo, + 'math': settings.math, + 'useCustomCss': settings.useCustomCss, + 'baseCSS': settings.baseCSS, + 'watermark': settings.watermark, + 'useFigcaption': settings.useFigcaption, + 'customCSSNote': settings.customCSSNote, + 'excalidrawToPNG': settings.excalidrawToPNG, + 'expertSettingsNote': settings.expertSettingsNote, + 'ignoreEmptyLine': settings.enableEmptyLine, + } + } + + getExpiredDate() { + if (this.authKey.length == 0) return; + wxKeyInfo(this.authKey).then((res) => { + if (res.status == 200) { + if (res.json.vip) { + this.isVip = true; + } + this.expireat = new Date(res.json.expireat); + } + }) + } + + isAuthKeyVaild() { + if (this.authKey.length == 0) return false; + if (this.isVip) return true; + if (this.expireat == null) return false; + return this.expireat > new Date(); + } +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..9937c4f --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { App, sanitizeHTMLToDom, requestUrl, Platform } from "obsidian"; +import * as postcss from "./postcss/postcss"; + +let PluginVersion = "0.0.0"; +let PlugPlatform = "obsidian"; + +export function setVersion(version: string) { + PluginVersion = version; + if (Platform.isWin) { + PlugPlatform = "win"; + } + else if (Platform.isMacOS) { + PlugPlatform = "mac"; + } + else if (Platform.isLinux) { + PlugPlatform = "linux"; + } + else if (Platform.isIosApp) { + PlugPlatform = "ios"; + } + else if (Platform.isAndroidApp) { + PlugPlatform = "android"; + } +} + +function getStyleSheet() { + for (var i = 0; i < document.styleSheets.length; i++) { + var sheet = document.styleSheets[i]; + if (sheet.title == 'note-to-mp-style') { + return sheet; + } + } +} + +function applyStyles(element: HTMLElement, styles: CSSStyleDeclaration, computedStyle: CSSStyleDeclaration) { + for (let i = 0; i < styles.length; i++) { + const propertyName = styles[i]; + let propertyValue = computedStyle.getPropertyValue(propertyName); + if (propertyName == 'width' && styles.getPropertyValue(propertyName) == 'fit-content') { + propertyValue = 'fit-content'; + } + if (propertyName.indexOf('margin') >= 0 && styles.getPropertyValue(propertyName).indexOf('auto') >= 0) { + propertyValue = styles.getPropertyValue(propertyName); + } + element.style.setProperty(propertyName, propertyValue); + } +} + +function parseAndApplyStyles(element: HTMLElement, sheet:CSSStyleSheet) { + try { + const computedStyle = getComputedStyle(element); + for (let i = 0; i < sheet.cssRules.length; i++) { + const rule = sheet.cssRules[i]; + if (rule instanceof CSSStyleRule && element.matches(rule.selectorText)) { + applyStyles(element, rule.style, computedStyle); + } + } + } catch (e) { + console.warn("Unable to access stylesheet: " + sheet.href, e); + } +} + +function traverse(root: HTMLElement, sheet:CSSStyleSheet) { + let element = root.firstElementChild; + while (element) { + if (element.tagName === 'svg') { + // pass + } + else { + traverse(element as HTMLElement, sheet); + } + element = element.nextElementSibling; + } + parseAndApplyStyles(root, sheet); +} + +export async function CSSProcess(content: HTMLElement) { + // 获取样式表 + const style = getStyleSheet(); + if (style) { + traverse(content, style); + } +} + +export function parseCSS(css: string) { + return postcss.parse(css); +} + +export function ruleToStyle(rule: postcss.Rule) { + let style = ''; + rule.walkDecls(decl => { + style += decl.prop + ':' + decl.value + ';'; + }) + + return style; +} + +function processPseudoSelector(selector: string) { + if (selector.includes('::before') || selector.includes('::after')) { + selector = selector.replace(/::before/g, '').replace(/::after/g, ''); + } + return selector; +} + +function getPseudoType(selector: string) { + if (selector.includes('::before')) { + return 'before'; + } + else if (selector.includes('::after')) { + return 'after'; + } + return undefined; +} + +function applyStyle(root: HTMLElement, cssRoot: postcss.Root) { + if (root.tagName.toLowerCase() === 'a' && root.classList.contains('wx_topic_link')) { + return; + } + + const cssText = root.style.cssText; + cssRoot.walkRules(rule => { + const selector = processPseudoSelector(rule.selector); + try { + if (root.matches(selector)) { + let item = root; + + const pseudoType = getPseudoType(rule.selector); + if (pseudoType) { + let content = ''; + rule.walkDecls('content', decl => { + content = decl.value || ''; + }) + item = createSpan(); + item.textContent = content.replace(/(^")|("$)/g, ''); + + if (pseudoType === 'before') { + root.prepend(item); + } + else if (pseudoType === 'after') { + root.appendChild(item); + } + } + + rule.walkDecls(decl => { + // 如果已经设置了,则不覆盖 + const setted = cssText.includes(decl.prop); + if (!setted || decl.important) { + item.style.setProperty(decl.prop, decl.value); + } + }) + } + } + catch (err) { + if (err.message && err.message.includes('is not a valid selector')) { + return; + } + else { + throw err; + } + } + }); + + if (root.tagName === 'svg') { + return; + } + + let element = root.firstElementChild; + while (element) { + applyStyle(element as HTMLElement, cssRoot); + element = element.nextElementSibling; + } +} + +export function applyCSS(html: string, css: string) { + const doc = sanitizeHTMLToDom(html); + const root = doc.firstChild as HTMLElement; + const cssRoot = postcss.parse(css); + applyStyle(root, cssRoot); + return root.outerHTML; +} + +export function uevent(name: string) { + const url = `https://u.sunboshi.tech/event?name=${name}&platform=${PlugPlatform}&v=${PluginVersion}`; + requestUrl(url).then().catch(error => { + console.error("Failed to send event: " + url, error); + }); +} + +/** + * 创建一个防抖函数 + * @param func 要执行的函数 + * @param wait 等待时间(毫秒) + * @returns 防抖处理后的函数 + */ +export function debounce any>(func: T, wait: number): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null; + + return function(this: any, ...args: Parameters) { + const context = this; + + const later = () => { + timeout = null; + func.apply(context, args); + }; + + if (timeout !== null) { + clearTimeout(timeout); + } + timeout = setTimeout(later, wait); + }; +} + +export function cleanUrl(href: string) { + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch (e) { + return null; + } + return href; +} + +export async function waitForLayoutReady(app: App): Promise { + if (app.workspace.layoutReady) { + return; + } + return new Promise((resolve) => { + app.workspace.onLayoutReady(() => resolve()); + }); +} diff --git a/src/wasm/wasm.ts b/src/wasm/wasm.ts new file mode 100644 index 0000000..5e18ef4 --- /dev/null +++ b/src/wasm/wasm.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import AssetsManager from "../assets"; +require('./wasm_exec.js'); + +declare class Go { + argv: string[]; + env: { [envKey: string]: string }; + exit: (code: number) => void; + importObject: WebAssembly.Imports; + exited: boolean; + mem: DataView; + run(instance: WebAssembly.Instance): Promise; +} + +let WasmLoaded = false; + +export function IsWasmReady() { + return WasmLoaded; +} + +export async function LoadWasm() { + if (WasmLoaded) { + return; + } + const assets = AssetsManager.getInstance(); + const wasmContent = await assets.loadWasm(); + + if (!wasmContent) { + console.error('WASM content not found'); + // throw new Error('WASM content not found'); + return; + } + const go = new Go(); + const ret = await WebAssembly.instantiate(wasmContent, go.importObject); + go.run(ret.instance); + WasmLoaded = true; +} diff --git a/src/wasm/wasm_exec.js b/src/wasm/wasm_exec.js new file mode 100644 index 0000000..bc6f210 --- /dev/null +++ b/src/wasm/wasm_exec.js @@ -0,0 +1,561 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +"use strict"; + +(() => { + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!globalThis.fs) { + let outputBuf = ""; + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substring(0, nl)); + outputBuf = outputBuf.substring(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, + }; + } + + if (!globalThis.process) { + globalThis.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, + pid: -1, + ppid: -1, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, + } + } + + if (!globalThis.crypto) { + throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + } + + if (!globalThis.performance) { + throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + } + + if (!globalThis.TextEncoder) { + throw new Error("globalThis.TextEncoder is not available, polyfill required"); + } + + if (!globalThis.TextDecoder) { + throw new Error("globalThis.TextDecoder is not available, polyfill required"); + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + globalThis.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const setInt32 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; + + if (typeof v === "number" && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; + } + this.mem.setFloat64(addr, v, true); + return; + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 0; + switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + _gotest: { + add: (a, b) => a + b, + }, + gojs: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + sp >>>= 0; + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + sp >>>= 0; + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + sp >>>= 0; + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + sp >>>= 0; + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, + getInt64(sp + 8), + )); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + sp >>>= 0; + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + sp >>>= 0; + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + sp >>>= 0; + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + sp >>>= 0; + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + sp >>>= 0; + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + sp >>>= 0; + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + globalThis, + this, + ]; + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [globalThis, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } +})(); diff --git a/src/weixin-api.ts b/src/weixin-api.ts new file mode 100644 index 0000000..bef95eb --- /dev/null +++ b/src/weixin-api.ts @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { requestUrl, RequestUrlParam, getBlobArrayBuffer } from "obsidian"; + +const PluginHost = 'https://obplugin.sunboshi.tech'; + +// 获取token +export async function wxGetToken(authkey:string, appid:string, secret:string) { + const url = PluginHost + '/v1/wx/token'; + const body = { + authkey, + appid, + secret + } + const res = await requestUrl({ + url, + method: 'POST', + throw: false, + contentType: 'application/json', + body: JSON.stringify(body) + }); + return res; +} + +export async function wxEncrypt(authkey:string, wechat:any[]) { + const url = PluginHost + '/v1/wx/encrypt'; + const body = JSON.stringify({ + authkey, + wechat + }); + const res = await requestUrl({ + url: url, + method: 'POST', + throw: false, + contentType: 'application/json', + body: body + }); + return res +} + +export async function wxKeyInfo(authkey:string) { + const url = PluginHost + '/v1/wx/info/' + authkey; + const res = await requestUrl({ + url: url, + method: 'GET', + throw: false, + contentType: 'application/json', + }); + return res +} + +export async function wxWidget(authkey: string, params: string) { + const host = 'https://obplugin.sunboshi.tech'; + const path = '/math/widget'; + const url = `${host}${path}`; + try { + const res = await requestUrl({ + url, + throw: false, + method: 'POST', + contentType: 'application/json', + headers: { + authkey + }, + body: params + }) + if (res.status === 200) { + return res.json.content; + } + return res.json.msg; + } catch (error) { + console.log(error); + return error.message; + } +} + +// 上传图片 +export async function wxUploadImage(data: Blob, filename: string, token: string, type?: string) { + let url = ''; + if (type == null || type === '') { + url = 'https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=' + token; + } else { + url = `https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${token}&type=${type}` + } + + const N = 16 // The length of our random boundry string + const randomBoundryString = "djmangoBoundry" + Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N) + + // Construct the form data payload as a string + const pre_string = `------${randomBoundryString}\r\nContent-Disposition: form-data; name="media"; filename="${filename}"\r\nContent-Type: "application/octet-stream"\r\n\r\n`; + const post_string = `\r\n------${randomBoundryString}--` + + // Convert the form data payload to a blob by concatenating the pre_string, the file data, and the post_string, and then return the blob as an array buffer + const pre_string_encoded = new TextEncoder().encode(pre_string); + // const data = file; + const post_string_encoded = new TextEncoder().encode(post_string); + const concatenated = await new Blob([pre_string_encoded, await getBlobArrayBuffer(data), post_string_encoded]).arrayBuffer() + + // Now that we have the form data payload as an array buffer, we can pass it to requestURL + // We also need to set the content type to multipart/form-data and pass in the boundry string + const options: RequestUrlParam = { + method: 'POST', + url: url, + contentType: `multipart/form-data; boundary=----${randomBoundryString}`, + body: concatenated + }; + + const res = await requestUrl(options); + const resData = await res.json; + return { + url: resData.url || '', + media_id: resData.media_id || '', + errcode: resData.errcode || 0, + errmsg: resData.errmsg || '', + } +} + +// 新建草稿 +export interface DraftArticle { + title: string; + author?: string; + digest?: string; + cover?: string; + content: string; + content_source_url?: string; + thumb_media_id: string; + need_open_comment?: number; + only_fans_can_comment?: number; + pic_crop_235_1?: string; + pic_crop_1_1?: string; + appid?: string; + theme?: string; + highlight?: string; +} + +export async function wxAddDraft(token: string, data: DraftArticle) { + const url = 'https://api.weixin.qq.com/cgi-bin/draft/add?access_token=' + token; + const body = {articles:[{ + title: data.title, + content: data.content, + digest: data.digest, + thumb_media_id: data.thumb_media_id, + ... data.pic_crop_235_1 && {pic_crop_235_1: data.pic_crop_235_1}, + ... data.pic_crop_1_1 && {pic_crop_1_1: data.pic_crop_1_1}, + ... data.content_source_url && {content_source_url: data.content_source_url}, + ... data.need_open_comment !== undefined && {need_open_comment: data.need_open_comment}, + ... data.only_fans_can_comment !== undefined && {only_fans_can_comment: data.only_fans_can_comment}, + ... data.author && {author: data.author}, + }]}; + + const res = await requestUrl({ + method: 'POST', + url: url, + throw: false, + body: JSON.stringify(body) + }); + + return res; +} + +export interface DraftImageMediaId { + image_media_id: string; +} + +export interface DraftImageInfo { + image_list: DraftImageMediaId[]; +} + +export interface DraftImages { + article_type: string; + title: string; + content: string; + need_open_commnet: number; + only_fans_can_comment: number; + image_info: DraftImageInfo; +} + +export async function wxAddDraftImages(token: string, data: DraftImages) { + const url = 'https://api.weixin.qq.com/cgi-bin/draft/add?access_token=' + token; + const body = {articles:[data]}; + + const res = await requestUrl({ + method: 'POST', + url: url, + throw: false, + body: JSON.stringify(body) + }); + + return res; +} + +export async function wxBatchGetMaterial(token: string, type: string, offset: number = 0, count: number = 10) { + const url = 'https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=' + token; + const body = { + type, + offset, + count + }; + + const res = await requestUrl({ + method: 'POST', + url: url, + throw: false, + body: JSON.stringify(body) + }); + + return await res.json; +} \ No newline at end of file diff --git a/src/widgets-modal.ts b/src/widgets-modal.ts new file mode 100644 index 0000000..f94b09a --- /dev/null +++ b/src/widgets-modal.ts @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import { App, Modal, MarkdownView } from "obsidian"; +import { uevent } from "./utils"; + +export class WidgetsModal extends Modal { + listener: any = null; + url: string = 'https://widgets.sunboshi.tech'; + constructor(app: App) { + super(app); + } + + insertMarkdown(markdown: string) { + const editor = this.app.workspace.getActiveViewOfType(MarkdownView)?.editor; + if (!editor) return; + editor.replaceSelection(markdown); + editor.exec("goRight"); + uevent('insert-widgets'); + } + + onOpen() { + let { contentEl, modalEl } = this; + modalEl.style.width = '640px'; + modalEl.style.height = '500px'; + const iframe = contentEl.createEl('iframe', { + attr: { + src: this.url, + width: '100%', + height: '100%', + allow: 'clipboard-read; clipboard-write', + }, + }); + + iframe.style.border = 'none'; + + this.listener = this.handleMessage.bind(this); + window.addEventListener('message', this.listener); + uevent('open-widgets'); + } + + handleMessage(event: MessageEvent) { + if (event.origin === this.url) { + const { type, data } = event.data; + if (type === 'cmd') { + this.insertMarkdown(data); + } + } + } + + onClose() { + if (this.listener) { + window.removeEventListener('message', this.listener); + } + let { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..a68f221 --- /dev/null +++ b/styles.css @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* =========================================================== */ +/* UI 样式 */ +/* =========================================================== */ +.note-preview { + min-height: 100%; + width: 100%; + height: 100%; + background-color: #fff; + display: flex; + flex-direction: column; +} + +.render-div { + flex: 1; + overflow-y: auto; + padding: 10px; +} + +.preview-toolbar { + position: relative; + min-height: 100px; + border-bottom: #e4e4e4 1px solid; + background-color: var(--background-primary); +} + +.toolbar-line { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + margin: 10px 10px; +} + +.copy-button { + margin-right: 10px; +} + +.refresh-button { + margin-right: 10px; +} + +.upload-input { + margin-left: 10px; + visibility: hidden; + width: 0px; +} + +.style-label { + margin-right: 10px; +} + +.style-select { + margin-right: 10px; + width: 120px; +} + +.msg-view { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--background-primary); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 18px; + z-index: 9999; + display: none; +} + +.msg-title { + margin-bottom: 20px; + max-width: 90%; +} + +.note-mpcard-wrapper { + margin: 20px 20px; + background-color: rgb(250, 250, 250); + padding: 10px 20px; + border-radius: 10px; +} +.note-mpcard-content { + display: flex; +} +.note-mpcard-headimg { + border: none !important; + border-radius: 27px !important; + box-shadow: none !important; + width: 54px !important; + height: 54px !important; + margin: 0 !important; +} +.note-mpcard-info { + margin-left: 10px; +} +.note-mpcard-nickname { + font-size: 17px; + font-weight: 500; + color: rgba(0, 0, 0, 0.9); +} + +.note-mpcard-signature { + font-size: 14px; + color: rgba(0, 0, 0, 0.55); +} +.note-mpcard-foot { + margin-top: 20px; + padding-top: 10px; + border-top: 1px solid #ececec; + font-size: 14px; + color: rgba(0, 0, 0, 0.3); +} + +.loading-wrapper { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.loading-spinner { + width: 50px; /* 可调整大小 */ + height: 50px; + border: 4px solid #fcd6ff; /* 底色,浅灰 */ + border-top: 4px solid #bb0cdf; /* 主色,蓝色顶部产生旋转感 */ + border-radius: 50%; /* 圆形 */ + animation: spin 1s linear infinite; /* 旋转动画 */ +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/tools/download.mjs b/tools/download.mjs new file mode 100644 index 0000000..bd5e57f --- /dev/null +++ b/tools/download.mjs @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024-2025 Sun Booshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import https from 'node:https'; +import { exec } from 'node:child_process'; + +// 仓库信息 +const owner = 'sunbooshi'; +const repo = 'mweb-themes'; +const assetName = 'assets.zip'; // 要下载的文件名 + +// GitHub API 获取最新 Release 信息 +const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`; + +https.get(apiUrl, { headers: { 'User-Agent': 'Node.js' } }, (apiRes) => { + let data = ''; + + // 接收 API 响应数据 + apiRes.on('data', (chunk) => { + data += chunk; + }); + + apiRes.on('end', () => { + try { + const releaseInfo = JSON.parse(data); + + // 查找 assets.zip 文件 + const asset = releaseInfo.assets.find((a) => a.name === assetName); + + if (!asset) { + console.error(`未找到 ${assetName} 文件`); + return; + } + + const downloadUrl = asset.browser_download_url; + console.log(`找到 ${assetName},下载链接: ${downloadUrl}`); + + // 使用系统 wget 命令下载 + exec(`wget "${downloadUrl}" -O "${assetName}"`, (error, stdout, stderr) => { + if (error) { + console.error(`下载失败: ${error}`); + return; + } + console.log(`${assetName} 下载完成!`); + }); + } catch (err) { + console.error('解析 API 响应失败:', err); + } + }); +}).on('error', (err) => { + console.error('请求 GitHub API 失败:', err); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bbad05a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES2018", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ES5", + "ES6", + "ES7" + ] + }, + "include": [ + "**/*.ts" + ] +} diff --git a/version-bump.mjs b/version-bump.mjs new file mode 100644 index 0000000..d409fa0 --- /dev/null +++ b/version-bump.mjs @@ -0,0 +1,14 @@ +import { readFileSync, writeFileSync } from "fs"; + +const targetVersion = process.env.npm_package_version; + +// read minAppVersion from manifest.json and bump version to target version +let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const { minAppVersion } = manifest; +manifest.version = targetVersion; +writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); + +// update versions.json with target version and minAppVersion from manifest.json +let versions = JSON.parse(readFileSync("versions.json", "utf8")); +versions[targetVersion] = minAppVersion; +writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..24211c8 --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "1.3.0": "1.4.5" +}