diff --git a/package-lock.json b/package-lock.json index 98e04f2..fa72148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "color": "^4.2.3", "markmap-lib": "^0.11.1", "markmap-view": "^0.2.1", + "pinyin-pro": "^3.29.3", "roamjs-components": "^0.88.4", "tesseract.js": "^2.1.4", "turndown": "^7.1.1" @@ -22,7 +23,8 @@ "@types/charset": "^1.0.2", "@types/color": "^3.0.3", "@types/mozilla-readability": "^0.2.0", - "@types/turndown": "^5.0.1" + "@types/turndown": "^5.0.1", + "ts-node": "^10.9.1" } }, "node_modules/@alloc/quick-lru": { @@ -1321,7 +1323,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1333,7 +1334,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1732,7 +1732,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1749,8 +1748,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "peer": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", @@ -2875,26 +2873,22 @@ "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "peer": true + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "peer": true + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "peer": true + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "peer": true + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "node_modules/@types/aria-query": { "version": "5.0.1", @@ -3308,7 +3302,6 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3330,7 +3323,6 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -4097,8 +4089,7 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "peer": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -4622,7 +4613,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "peer": true, "engines": { "node": ">=0.3.1" } @@ -12288,8 +12278,7 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "peer": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/marked": { "version": "4.0.16", @@ -12834,6 +12823,12 @@ "node": ">=0.10.0" } }, + "node_modules/pinyin-pro": { + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.3.tgz", + "integrity": "sha512-+UU9bx6vfDw8amOJGHm0TE0rdQl8VPylsDWviQ5OOQ3e+on1xRP4OqDbiDuMT5OISgvfl/Y6ez1BBRaIP80GLQ==", + "license": "MIT" + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -13976,7 +13971,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "peer": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -14018,8 +14013,7 @@ "node_modules/ts-node/node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "peer": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "node_modules/tslib": { "version": "2.2.0", @@ -14100,8 +14094,7 @@ "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "peer": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/v8-to-istanbul": { "version": "9.1.0", @@ -14367,7 +14360,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "peer": true, "engines": { "node": ">=6" } @@ -15659,7 +15651,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "peer": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -15668,7 +15659,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "peer": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -15860,8 +15850,7 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "peer": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", @@ -15872,8 +15861,7 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "peer": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { "version": "0.3.18", @@ -16905,26 +16893,22 @@ "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "peer": true + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "peer": true + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "peer": true + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "peer": true + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "@types/aria-query": { "version": "5.0.1", @@ -17337,8 +17321,7 @@ "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "peer": true + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" }, "acorn-globals": { "version": "7.0.1", @@ -17353,8 +17336,7 @@ "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "peer": true + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" }, "agent-base": { "version": "6.0.2", @@ -17933,8 +17915,7 @@ "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "peer": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "cross-spawn": { "version": "7.0.3", @@ -18394,8 +18375,7 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "peer": true + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, "dlv": { "version": "1.1.3", @@ -24206,8 +24186,7 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "peer": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "marked": { "version": "4.0.16", @@ -24606,6 +24585,11 @@ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "peer": true }, + "pinyin-pro": { + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.3.tgz", + "integrity": "sha512-+UU9bx6vfDw8amOJGHm0TE0rdQl8VPylsDWviQ5OOQ3e+on1xRP4OqDbiDuMT5OISgvfl/Y6ez1BBRaIP80GLQ==" + }, "pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -25424,7 +25408,6 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -25444,8 +25427,7 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "peer": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" } } }, @@ -25512,8 +25494,7 @@ "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "peer": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "v8-to-istanbul": { "version": "9.1.0", @@ -25710,8 +25691,7 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "peer": true + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 4038d03..0ca0098 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "scripts": { "prebuild:roam": "npm install", "build:roam": "samepage build --dry", - "start": "samepage dev" + "start": "samepage dev", + "test:deepnav": "node --test --require ts-node/register src/features/deepnavKeys.test.ts" }, "dependencies": { "@mozilla/readability": "^0.3.0", @@ -13,6 +14,7 @@ "color": "^4.2.3", "markmap-lib": "^0.11.1", "markmap-view": "^0.2.1", + "pinyin-pro": "^3.29.3", "roamjs-components": "^0.88.4", "tesseract.js": "^2.1.4", "turndown": "^7.1.1" @@ -21,7 +23,8 @@ "@types/charset": "^1.0.2", "@types/color": "^3.0.3", "@types/mozilla-readability": "^0.2.0", - "@types/turndown": "^5.0.1" + "@types/turndown": "^5.0.1", + "ts-node": "^10.9.1" }, "version": "1.8.2", "samepage": { diff --git a/src/features/deepnav.tsx b/src/features/deepnav.tsx index e1da61a..e7af54c 100644 --- a/src/features/deepnav.tsx +++ b/src/features/deepnav.tsx @@ -8,6 +8,12 @@ import createBlock from "roamjs-components/writes/createBlock"; import getCurrentUserUid from "roamjs-components/queries/getCurrentUserUid"; import React from "react"; import { addCommand } from "./workBench"; +import { + getItemAlternativeTexts, + getItemInitials, + hasMatchingKeyPrefix, + preprocessItemText, +} from "./deepnavKeys"; type Breadcrumbs = { hash: string; title: string; uid?: string }[]; @@ -17,10 +23,12 @@ type Item = { mustBeKeys?: string | null; text?: string; initials?: string; + alternativeTexts?: string[]; extraClasses?: string[]; }; let currentOptions: Record = {}; +let currentOptionAliases: Record = {}; let currentNavigatePrefixesUsed: Record = {}; let navigateKeysPressed = ""; @@ -273,38 +281,6 @@ const addBlocks = ( } }; -const preprocessItemText = (txt: string) => { - let result = ""; - for (let i = 0; i < txt.length; i++) { - const char = txt[i]; - const lowerChar = char.toLowerCase(); - if (lowercaseCharIsAlpha(lowerChar)) { - result += lowerChar; - } - } - return result; -}; - -const getItemInitials = (txt: string) => { - let result = ""; - for (let i = 0; i < txt.length; i++) { - const char = txt[i]; - const lowerChar = char.toLowerCase(); - if ( - lowercaseCharIsAlpha(lowerChar) && - (i === 0 || txt[i - 1] === " " || lowerChar !== char) - ) { - result += lowerChar; - } - } - return result; -}; - -const lowercaseCharIsAlpha = (char: string) => { - const code = char.charCodeAt(0); - return code > 96 && code < 123; // (a-z) -}; - const addLinks = (linkItems: Item[], container: Element) => { const links = container.querySelectorAll( [".rm-page-ref", "a"].join(", ") @@ -323,6 +299,7 @@ const addLinks = (linkItems: Item[], container: Element) => { mustBeKeys: null, text: preprocessItemText(text), initials: getItemInitials(text), + alternativeTexts: getItemAlternativeTexts(text), extraClasses: [LINK_HINT_CLASS], navigate, }); @@ -334,7 +311,16 @@ const addLinks = (linkItems: Item[], container: Element) => { }) ); } else if (link.classList.contains("rm-alias")) { - pushLink(link, async () => link.click()); + const pageUid = link.getAttribute("data-link-uid"); + pushLink( + link, + link.classList.contains("rm-alias--page") && pageUid + ? () => + window.roamAlphaAPI.ui.mainWindow.openPage({ + page: { uid: pageUid }, + }) + : async () => link.click() + ); } else if (link.hasAttribute("href") && link) { pushLink(link, async () => { const href = link.getAttribute("href"); @@ -344,21 +330,29 @@ const addLinks = (linkItems: Item[], container: Element) => { console.warn("Unexpected element", link); } } else if (link.classList.contains("rm-page-ref")) { - const uidAttr = parent?.getAttribute("data-link-uid"); - if (uidAttr && parent) { + const pageUid = parent?.getAttribute("data-link-uid"); + const tag = link.getAttribute("data-tag"); + const blockUid = link.getAttribute("data-link-uid"); + if (pageUid && parent) { pushLink(parent, () => - window.roamAlphaAPI.ui.mainWindow.openBlock({ - block: { uid: uidAttr }, + window.roamAlphaAPI.ui.mainWindow.openPage({ + page: { uid: pageUid }, }) ); - } else if (link.hasAttribute("data-tag")) { + } else if (tag) { pushLink(link, () => window.roamAlphaAPI.ui.mainWindow.openPage({ - page: { title: link.getAttribute("data-tag") ?? "" }, + page: { title: tag }, + }) + ); + } else if (blockUid) { + pushLink(link, () => + window.roamAlphaAPI.ui.mainWindow.openBlock({ + block: { uid: blockUid }, }) ); } else { - console.warn("Unxpected .rm-page-ref element", link); + console.warn("Unexpected .rm-page-ref element", link); } } } @@ -376,6 +370,7 @@ const endNavigate = () => { navigateKeysPressed = ""; clearBreadcrumbs(); currentOptions = {}; + currentOptionAliases = {}; removeOldTips(); if (document.body.classList.contains(NAVIGATE_CLASS)) { document.body.classList.remove(NAVIGATE_CLASS); @@ -385,11 +380,15 @@ const endNavigate = () => { const rerenderTips = () => { updateBreadcrumbs(); removeOldTips(); - return Object.entries(currentOptions) + const renderedVisibleTip = Object.entries(currentOptions) .map(([k, opt]) => { return renderTip(k, opt); }) .some((s) => !!s); + return ( + renderedVisibleTip || + hasMatchingKeyPrefix(Object.keys(currentOptionAliases), navigateKeysPressed) + ); }; const renderTip = (key: string, option: Item) => { @@ -443,6 +442,8 @@ const renderTip = (key: string, option: Item) => { // Assign keys to items based on their text. const assignKeysToItems = (items: Item[]) => { + const allItems = [...items]; + const assignedItems = new Set(); let item; let keys; let prefix; @@ -470,6 +471,7 @@ const assignKeysToItems = (items: Item[]) => { const noAlias = noAliasing(ks); if (noAlias) { currentOptions[ks] = x; + assignedItems.add(x); for (let i = 1; i <= ks.length; i++) { currentNavigatePrefixesUsed[ks.slice(0, i)] = true; } @@ -597,6 +599,18 @@ const assignKeysToItems = (items: Item[]) => { } } } + allItems.forEach((assignedItem) => { + if (!assignedItems.has(assignedItem)) return; + assignedItem.alternativeTexts?.forEach((alternativeText) => { + const alternativeKey = alternativeText.slice(0, MAX_NAVIGATE_PREFIX); + if (alternativeKey && noAliasing(alternativeKey)) { + currentOptionAliases[alternativeKey] = assignedItem; + for (let i = 1; i <= alternativeKey.length; i++) { + currentNavigatePrefixesUsed[alternativeKey.slice(0, i)] = true; + } + } + }); + }); // VARGAS-TODO items.forEach addResult(nextAvailableKey, item); }; @@ -681,6 +695,7 @@ const setupNavigate = () => { mustBeKeys: null, text: preprocessItemText(text), initials: getItemInitials(text), + alternativeTexts: getItemAlternativeTexts(text), navigate: () => window.roamAlphaAPI.ui.mainWindow.openPage({ page: { title: text }, @@ -803,6 +818,7 @@ export const navigate = () => { } currentOptions = {}; + currentOptionAliases = {}; currentNavigatePrefixesUsed = {}; navigateKeysPressed = ""; isNavigating = true; @@ -840,7 +856,9 @@ const handleNavigateKey = (ev: KeyboardEvent) => { const key = eventToKey(ev); if (key) { navigateKeysPressed += key; - const option = currentOptions[navigateKeysPressed]; + const option = + currentOptions[navigateKeysPressed] || + currentOptionAliases[navigateKeysPressed]; if (option) { option.navigate().then(endNavigate); } else if (!rerenderTips()) { diff --git a/src/features/deepnavKeys.test.ts b/src/features/deepnavKeys.test.ts new file mode 100644 index 0000000..106244f --- /dev/null +++ b/src/features/deepnavKeys.test.ts @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + getItemAlternativeTexts, + getItemInitials, + hasMatchingKeyPrefix, + preprocessItemText, +} from "./deepnavKeys"; + +test("keeps existing Latin prefix and initial behavior", () => { + assert.equal(preprocessItemText("Project Alpha"), "projectalpha"); + assert.equal(getItemInitials("Project Alpha"), "pa"); + assert.equal(getItemInitials("camelCase Page"), "ccp"); +}); + +test("creates full pinyin and abbreviation keys for Chinese page titles", () => { + assert.equal(preprocessItemText("中文页面"), "zhongwenyemian"); + assert.equal(getItemInitials("中文页面"), "zwym"); + assert.deepEqual(getItemAlternativeTexts("中文页面"), ["zhongwenyemian"]); +}); + +test("creates keys from mixed Chinese alias text", () => { + assert.equal(preprocessItemText("中文 Alias"), "zhongwenalias"); + assert.equal(getItemInitials("中文 Alias"), "zwa"); +}); + +test("does not add alternative keys for Latin-only text", () => { + assert.deepEqual(getItemAlternativeTexts("Project Alpha"), []); +}); + +test("keeps navigation active while a hidden full-pinyin key is partial", () => { + assert.equal(hasMatchingKeyPrefix(["zh"], "z"), true); + assert.equal(hasMatchingKeyPrefix(["zh"], "x"), false); +}); diff --git a/src/features/deepnavKeys.ts b/src/features/deepnavKeys.ts new file mode 100644 index 0000000..735e208 --- /dev/null +++ b/src/features/deepnavKeys.ts @@ -0,0 +1,46 @@ +import { pinyin } from "pinyin-pro"; + +const lowercaseCharIsAlpha = (char: string) => { + const code = char.charCodeAt(0); + return code > 96 && code < 123; +}; + +const normalizeLetters = (text: string) => + Array.from(text) + .map((char) => char.toLowerCase()) + .filter(lowercaseCharIsAlpha) + .join(""); + +const getPinyin = (text: string) => + pinyin(text, { toneType: "none", type: "array" }); + +const CONTAINS_HAN_CHARACTER = /\p{Script=Han}/u; + +export const preprocessItemText = (text: string) => + normalizeLetters(getPinyin(text).join("")); + +export const getItemAlternativeTexts = (text: string) => + CONTAINS_HAN_CHARACTER.test(text) ? [preprocessItemText(text)] : []; + +export const getItemInitials = (text: string) => { + const characters = Array.from(text); + const transliterations = getPinyin(text); + + return characters + .map((char, index) => { + const lowerChar = char.toLowerCase(); + if ( + lowercaseCharIsAlpha(lowerChar) && + (index === 0 || characters[index - 1] === " " || lowerChar !== char) + ) { + return lowerChar; + } + + const transliteration = normalizeLetters(transliterations[index] || ""); + return transliteration !== lowerChar ? transliteration.charAt(0) : ""; + }) + .join(""); +}; + +export const hasMatchingKeyPrefix = (keys: string[], prefix: string) => + keys.some((key) => key.startsWith(prefix));