feat(plugin): ShikiCodeblocks (#267)
Co-authored-by: ArjixWasTaken <53124886+ArjixWasTaken@users.noreply.github.com> Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
parent
4760af7f0e
commit
41dddc9eee
32 changed files with 1480 additions and 105 deletions
|
@ -40,6 +40,8 @@
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||||
"@typescript-eslint/parser": "^5.44.0",
|
"@typescript-eslint/parser": "^5.44.0",
|
||||||
|
"@vap/core": "0.0.12",
|
||||||
|
"@vap/shiki": "0.10.3",
|
||||||
"console-menu": "^0.1.0",
|
"console-menu": "^0.1.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
|
@ -50,6 +52,7 @@
|
||||||
"eslint-plugin-path-alias": "^1.0.0",
|
"eslint-plugin-path-alias": "^1.0.0",
|
||||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
|
"highlight.js": "10.6.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"puppeteer-core": "^19.3.0",
|
"puppeteer-core": "^19.3.0",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
|
|
182
pnpm-lock.yaml
182
pnpm-lock.yaml
|
@ -13,6 +13,8 @@ specifiers:
|
||||||
'@types/yazl': ^2.4.2
|
'@types/yazl': ^2.4.2
|
||||||
'@typescript-eslint/eslint-plugin': ^5.44.0
|
'@typescript-eslint/eslint-plugin': ^5.44.0
|
||||||
'@typescript-eslint/parser': ^5.44.0
|
'@typescript-eslint/parser': ^5.44.0
|
||||||
|
'@vap/core': 0.0.12
|
||||||
|
'@vap/shiki': 0.10.3
|
||||||
console-menu: ^0.1.0
|
console-menu: ^0.1.0
|
||||||
diff: ^5.1.0
|
diff: ^5.1.0
|
||||||
discord-types: ^1.3.26
|
discord-types: ^1.3.26
|
||||||
|
@ -24,6 +26,7 @@ specifiers:
|
||||||
eslint-plugin-simple-import-sort: ^8.0.0
|
eslint-plugin-simple-import-sort: ^8.0.0
|
||||||
eslint-plugin-unused-imports: ^2.0.0
|
eslint-plugin-unused-imports: ^2.0.0
|
||||||
fflate: ^0.7.4
|
fflate: ^0.7.4
|
||||||
|
highlight.js: 10.6.0
|
||||||
moment: ^2.29.4
|
moment: ^2.29.4
|
||||||
puppeteer-core: ^19.3.0
|
puppeteer-core: ^19.3.0
|
||||||
standalone-electron-types: ^1.0.0
|
standalone-electron-types: ^1.0.0
|
||||||
|
@ -39,8 +42,10 @@ devDependencies:
|
||||||
'@types/react': 18.0.25
|
'@types/react': 18.0.25
|
||||||
'@types/react-dom': 18.0.9
|
'@types/react-dom': 18.0.9
|
||||||
'@types/yazl': 2.4.2
|
'@types/yazl': 2.4.2
|
||||||
'@typescript-eslint/eslint-plugin': 5.44.0_fnsv2sbzcckq65bwfk7a5xwslu
|
'@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au
|
||||||
'@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/parser': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
|
'@vap/core': 0.0.12
|
||||||
|
'@vap/shiki': 0.10.3
|
||||||
console-menu: 0.1.0
|
console-menu: 0.1.0
|
||||||
diff: 5.1.0
|
diff: 5.1.0
|
||||||
discord-types: 1.3.26
|
discord-types: 1.3.26
|
||||||
|
@ -50,7 +55,8 @@ devDependencies:
|
||||||
eslint-plugin-header: 3.1.1_eslint@8.28.0
|
eslint-plugin-header: 3.1.1_eslint@8.28.0
|
||||||
eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0
|
eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0
|
||||||
eslint-plugin-simple-import-sort: 8.0.0_eslint@8.28.0
|
eslint-plugin-simple-import-sort: 8.0.0_eslint@8.28.0
|
||||||
eslint-plugin-unused-imports: 2.0.0_aucl44mjeutxyzmt4nvo2cczya
|
eslint-plugin-unused-imports: 2.0.0_5am2datodjm2qi4eijrjrnoz54
|
||||||
|
highlight.js: 10.6.0
|
||||||
moment: 2.29.4
|
moment: 2.29.4
|
||||||
puppeteer-core: 19.3.0
|
puppeteer-core: 19.3.0
|
||||||
standalone-electron-types: 1.0.0
|
standalone-electron-types: 1.0.0
|
||||||
|
@ -83,9 +89,9 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
espree: 9.4.1
|
espree: 9.4.0
|
||||||
globals: 13.18.0
|
globals: 13.17.0
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
import-fresh: 3.3.0
|
import-fresh: 3.3.0
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
|
@ -161,7 +167,7 @@ packages:
|
||||||
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.5
|
||||||
csstype: 3.1.1
|
csstype: 3.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/react/18.0.25:
|
/@types/react/18.0.25:
|
||||||
|
@ -169,7 +175,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.5
|
||||||
'@types/scheduler': 0.16.2
|
'@types/scheduler': 0.16.2
|
||||||
csstype: 3.1.1
|
csstype: 3.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/scheduler/0.16.2:
|
/@types/scheduler/0.16.2:
|
||||||
|
@ -194,8 +200,8 @@ packages:
|
||||||
'@types/node': 18.11.9
|
'@types/node': 18.11.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/eslint-plugin/5.44.0_fnsv2sbzcckq65bwfk7a5xwslu:
|
/@typescript-eslint/eslint-plugin/5.45.0_czs5uoqkd3podpy6vgtsxfc7au:
|
||||||
resolution: {integrity: sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==}
|
resolution: {integrity: sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^5.0.0
|
'@typescript-eslint/parser': ^5.0.0
|
||||||
|
@ -205,24 +211,24 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/parser': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
'@typescript-eslint/scope-manager': 5.44.0
|
'@typescript-eslint/scope-manager': 5.45.0
|
||||||
'@typescript-eslint/type-utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/type-utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
'@typescript-eslint/utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
natural-compare-lite: 1.4.0
|
natural-compare-lite: 1.4.0
|
||||||
regexpp: 3.2.0
|
regexpp: 3.2.0
|
||||||
semver: 7.3.8
|
semver: 7.3.7
|
||||||
tsutils: 3.21.0_typescript@4.9.3
|
tsutils: 3.21.0_typescript@4.9.3
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/parser/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
/@typescript-eslint/parser/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
||||||
resolution: {integrity: sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==}
|
resolution: {integrity: sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
@ -231,9 +237,9 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 5.44.0
|
'@typescript-eslint/scope-manager': 5.45.0
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3
|
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
|
@ -241,16 +247,16 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/scope-manager/5.44.0:
|
/@typescript-eslint/scope-manager/5.45.0:
|
||||||
resolution: {integrity: sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==}
|
resolution: {integrity: sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/visitor-keys': 5.44.0
|
'@typescript-eslint/visitor-keys': 5.45.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/type-utils/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
/@typescript-eslint/type-utils/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
||||||
resolution: {integrity: sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==}
|
resolution: {integrity: sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '*'
|
eslint: '*'
|
||||||
|
@ -259,8 +265,8 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3
|
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
|
||||||
'@typescript-eslint/utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
tsutils: 3.21.0_typescript@4.9.3
|
tsutils: 3.21.0_typescript@4.9.3
|
||||||
|
@ -269,13 +275,13 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/types/5.44.0:
|
/@typescript-eslint/types/5.45.0:
|
||||||
resolution: {integrity: sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==}
|
resolution: {integrity: sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/typescript-estree/5.44.0_typescript@4.9.3:
|
/@typescript-eslint/typescript-estree/5.45.0_typescript@4.9.3:
|
||||||
resolution: {integrity: sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==}
|
resolution: {integrity: sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
@ -283,56 +289,70 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/visitor-keys': 5.44.0
|
'@typescript-eslint/visitor-keys': 5.45.0
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.3.8
|
semver: 7.3.7
|
||||||
tsutils: 3.21.0_typescript@4.9.3
|
tsutils: 3.21.0_typescript@4.9.3
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/utils/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
/@typescript-eslint/utils/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
||||||
resolution: {integrity: sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==}
|
resolution: {integrity: sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/json-schema': 7.0.11
|
'@types/json-schema': 7.0.11
|
||||||
'@types/semver': 7.3.13
|
'@types/semver': 7.3.13
|
||||||
'@typescript-eslint/scope-manager': 5.44.0
|
'@typescript-eslint/scope-manager': 5.45.0
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3
|
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
eslint-scope: 5.1.1
|
eslint-scope: 5.1.1
|
||||||
eslint-utils: 3.0.0_eslint@8.28.0
|
eslint-utils: 3.0.0_eslint@8.28.0
|
||||||
semver: 7.3.8
|
semver: 7.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/visitor-keys/5.44.0:
|
/@typescript-eslint/visitor-keys/5.45.0:
|
||||||
resolution: {integrity: sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==}
|
resolution: {integrity: sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/acorn-jsx/5.3.2_acorn@8.8.1:
|
/@vap/core/0.0.12:
|
||||||
|
resolution: {integrity: sha512-3csHpkE1zUSRTZwl7xIf2uXg1cD4IhhtUm0F6K/dWydc95R5Nj+krB4OTNATuqkewIv/ViCbwjPfkafAgvZQSg==}
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@vap/shiki/0.10.3:
|
||||||
|
resolution: {integrity: sha512-tZPHZxDKEBlorQ2BaprytGfkbo5yKBvdxdAF144p94HCTpjO3ScJk/f319wi7GtV1NE4DV8HBQo/0XpldixWQA==}
|
||||||
|
dependencies:
|
||||||
|
jsonc-parser: 3.2.0
|
||||||
|
vscode-oniguruma: 1.7.0
|
||||||
|
vscode-textmate: 5.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/acorn-jsx/5.3.2_acorn@8.8.0:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.8.1
|
acorn: 8.8.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/acorn/8.8.1:
|
/acorn/8.8.0:
|
||||||
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
|
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -522,7 +542,7 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/concat-map/0.0.1:
|
/concat-map/0.0.1:
|
||||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/console-menu/0.1.0:
|
/console-menu/0.1.0:
|
||||||
|
@ -553,8 +573,8 @@ packages:
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/csstype/3.1.1:
|
/csstype/3.1.0:
|
||||||
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/debug/2.6.9:
|
/debug/2.6.9:
|
||||||
|
@ -897,7 +917,7 @@ packages:
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-unused-imports/2.0.0_aucl44mjeutxyzmt4nvo2cczya:
|
/eslint-plugin-unused-imports/2.0.0_5am2datodjm2qi4eijrjrnoz54:
|
||||||
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
|
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -907,7 +927,7 @@ packages:
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 5.44.0_fnsv2sbzcckq65bwfk7a5xwslu
|
'@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
eslint-rule-composer: 0.3.0
|
eslint-rule-composer: 0.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -971,21 +991,21 @@ packages:
|
||||||
eslint-scope: 7.1.1
|
eslint-scope: 7.1.1
|
||||||
eslint-utils: 3.0.0_eslint@8.28.0
|
eslint-utils: 3.0.0_eslint@8.28.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
espree: 9.4.1
|
espree: 9.4.0
|
||||||
esquery: 1.4.0
|
esquery: 1.4.0
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
file-entry-cache: 6.0.1
|
file-entry-cache: 6.0.1
|
||||||
find-up: 5.0.0
|
find-up: 5.0.0
|
||||||
glob-parent: 6.0.2
|
glob-parent: 6.0.2
|
||||||
globals: 13.18.0
|
globals: 13.17.0
|
||||||
grapheme-splitter: 1.0.4
|
grapheme-splitter: 1.0.4
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
import-fresh: 3.3.0
|
import-fresh: 3.3.0
|
||||||
imurmurhash: 0.1.4
|
imurmurhash: 0.1.4
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
is-path-inside: 3.0.3
|
is-path-inside: 3.0.3
|
||||||
js-sdsl: 4.2.0
|
js-sdsl: 4.1.5
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
json-stable-stringify-without-jsonify: 1.0.1
|
json-stable-stringify-without-jsonify: 1.0.1
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
@ -1001,12 +1021,12 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/espree/9.4.1:
|
/espree/9.4.0:
|
||||||
resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==}
|
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.8.1
|
acorn: 8.8.0
|
||||||
acorn-jsx: 5.3.2_acorn@8.8.1
|
acorn-jsx: 5.3.2_acorn@8.8.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -1039,6 +1059,10 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/eventemitter3/4.0.7:
|
||||||
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/extend-shallow/2.0.1:
|
/extend-shallow/2.0.1:
|
||||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -1198,8 +1222,8 @@ packages:
|
||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/globals/13.18.0:
|
/globals/13.17.0:
|
||||||
resolution: {integrity: sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==}
|
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 0.20.2
|
type-fest: 0.20.2
|
||||||
|
@ -1212,7 +1236,7 @@ packages:
|
||||||
array-union: 2.1.0
|
array-union: 2.1.0
|
||||||
dir-glob: 3.0.1
|
dir-glob: 3.0.1
|
||||||
fast-glob: 3.2.12
|
fast-glob: 3.2.12
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
merge2: 1.4.1
|
merge2: 1.4.1
|
||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1257,6 +1281,10 @@ packages:
|
||||||
kind-of: 4.0.0
|
kind-of: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/highlight.js/10.6.0:
|
||||||
|
resolution: {integrity: sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/https-proxy-agent/5.0.1:
|
/https-proxy-agent/5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
@ -1271,8 +1299,8 @@ packages:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ignore/5.2.1:
|
/ignore/5.2.0:
|
||||||
resolution: {integrity: sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==}
|
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -1423,8 +1451,8 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/js-sdsl/4.2.0:
|
/js-sdsl/4.1.5:
|
||||||
resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
|
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/js-yaml/4.1.0:
|
/js-yaml/4.1.0:
|
||||||
|
@ -1442,6 +1470,10 @@ packages:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jsonc-parser/3.2.0:
|
||||||
|
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/keypress/0.2.1:
|
/keypress/0.2.1:
|
||||||
resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==}
|
resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1797,8 +1829,8 @@ packages:
|
||||||
ret: 0.1.15
|
ret: 0.1.15
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/semver/7.3.8:
|
/semver/7.3.7:
|
||||||
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
|
resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2053,6 +2085,14 @@ packages:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vscode-oniguruma/1.7.0:
|
||||||
|
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/vscode-textmate/5.2.0:
|
||||||
|
resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/webidl-conversions/3.0.1:
|
/webidl-conversions/3.0.1:
|
||||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -141,14 +141,19 @@ export const Settings = makeProxy(settings);
|
||||||
* Settings hook for React components. Returns a smart settings
|
* Settings hook for React components. Returns a smart settings
|
||||||
* object that automagically triggers a rerender if any properties
|
* object that automagically triggers a rerender if any properties
|
||||||
* are altered
|
* are altered
|
||||||
|
* @param paths An optional list of paths to whitelist for rerenders
|
||||||
* @returns Settings
|
* @returns Settings
|
||||||
*/
|
*/
|
||||||
export function useSettings() {
|
export function useSettings(paths?: string[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
|
const onUpdate: SubscriptionCallback = paths
|
||||||
|
? (value, path) => paths.includes(path) && forceUpdate()
|
||||||
|
: forceUpdate;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
subscriptions.add(forceUpdate);
|
subscriptions.add(onUpdate);
|
||||||
return () => void subscriptions.delete(forceUpdate);
|
return () => void subscriptions.delete(onUpdate);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return Settings;
|
return Settings;
|
||||||
|
|
|
@ -179,7 +179,7 @@ function Newer(props: CommonProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Updater() {
|
function Updater() {
|
||||||
const [repo, err, repoPending] = useAwaiter(getRepo, "Loading...");
|
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (err)
|
if (err)
|
||||||
|
|
|
@ -27,7 +27,9 @@ import { Button, Card, Forms, Margins, React, Switch } from "@webpack/common";
|
||||||
const st = (style: string) => `vcSettings${style}`;
|
const st = (style: string) => `vcSettings${style}`;
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), {
|
||||||
|
fallbackValue: "Loading..."
|
||||||
|
});
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const [donateImage] = React.useState(
|
const [donateImage] = React.useState(
|
||||||
|
|
|
@ -109,16 +109,36 @@ if (!process.argv.includes("--vanilla")) {
|
||||||
|
|
||||||
|
|
||||||
// Remove CSP
|
// Remove CSP
|
||||||
|
type PolicyResult = Record<string, string[]>;
|
||||||
|
|
||||||
|
const parsePolicy = (policy: string): PolicyResult => {
|
||||||
|
const result: PolicyResult = {};
|
||||||
|
policy.split(";").forEach(directive => {
|
||||||
|
const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
|
||||||
|
if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
|
||||||
|
result[directiveKey] = directiveValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||||
|
Object.entries(policy)
|
||||||
|
.filter(([, values]) => values?.length)
|
||||||
|
.map(directive => directive.flat().join(" "))
|
||||||
|
.join("; ");
|
||||||
|
|
||||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
function patchCsp(headers: Record<string, string[]>, header: string) {
|
||||||
if (header in headers) {
|
if (header in headers) {
|
||||||
let patchedHeader = headers[header][0];
|
const csp = parsePolicy(headers[header][0]);
|
||||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src"]) {
|
|
||||||
patchedHeader = patchedHeader.replace(new RegExp(`${directive}.+?;`), `${directive} * blob: data: 'unsafe-inline';`);
|
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||||
|
csp[directive] = ["*", "blob:", "data:", "'unsafe-inline'"];
|
||||||
}
|
}
|
||||||
// TODO: Restrict this to only imported packages with fixed version.
|
// TODO: Restrict this to only imported packages with fixed version.
|
||||||
// Perhaps auto generate with esbuild
|
// Perhaps auto generate with esbuild
|
||||||
patchedHeader = patchedHeader.replace(/script-src.+?(?=;)/, "$& 'unsafe-eval' https://unpkg.com https://cdnjs.cloudflare.com");
|
csp["script-src"] ??= [];
|
||||||
headers[header] = [patchedHeader];
|
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||||
|
headers[header] = [stringifyPolicy(csp)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,11 +39,10 @@ export default function PronounsChatComponentWrapper({ message }: { message: Mes
|
||||||
}
|
}
|
||||||
|
|
||||||
function PronounsChatComponent({ message }: { message: Message; }) {
|
function PronounsChatComponent({ message }: { message: Message; }) {
|
||||||
const [result, , isPending] = useAwaiter(
|
const [result, , isPending] = useAwaiter(() => fetchPronouns(message.author.id), {
|
||||||
() => fetchPronouns(message.author.id),
|
fallbackValue: null,
|
||||||
null,
|
onError: e => console.error("Fetching pronouns failed: ", e)
|
||||||
e => console.error("Fetching pronouns failed: ", e)
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns
|
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns
|
||||||
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
||||||
|
|
|
@ -45,11 +45,10 @@ function ProfilePronouns(
|
||||||
leProps: UserProfilePronounsProps;
|
leProps: UserProfilePronounsProps;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const [result, , isPending] = useAwaiter(
|
const [result, , isPending] = useAwaiter(() => fetchPronouns(userId), {
|
||||||
() => fetchPronouns(userId),
|
fallbackValue: null,
|
||||||
null,
|
onError: e => console.error("Fetching pronouns failed: ", e),
|
||||||
e => console.error("Fetching pronouns failed: ", e)
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
|
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
|
||||||
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { classes, useAwaiter } from "@utils/misc";
|
import { classes, useAwaiter } from "@utils/misc";
|
||||||
import { findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Forms, Text, UserStore } from "@webpack/common";
|
import { Forms, React, Text, UserStore } from "@webpack/common";
|
||||||
import type { KeyboardEvent } from "react";
|
import type { KeyboardEvent } from "react";
|
||||||
|
|
||||||
import { addReview, getReviews } from "../Utils/ReviewDBAPI";
|
import { addReview, getReviews } from "../Utils/ReviewDBAPI";
|
||||||
|
@ -27,7 +27,13 @@ import ReviewComponent from "./ReviewComponent";
|
||||||
const Classes = findLazy(m => typeof m.textarea === "string");
|
const Classes = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
export default function ReviewsView({ userId }: { userId: string; }) {
|
export default function ReviewsView({ userId }: { userId: string; }) {
|
||||||
const [reviews, _, isLoading, refetch] = useAwaiter(() => getReviews(userId), []);
|
const [refetchCount, setRefetchCount] = React.useState(0);
|
||||||
|
const [reviews, _, isLoading] = useAwaiter(() => getReviews(userId), {
|
||||||
|
fallbackValue: [],
|
||||||
|
deps: [refetchCount],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dirtyRefetch = () => setRefetchCount(refetchCount + 1);
|
||||||
|
|
||||||
if (isLoading) return null;
|
if (isLoading) return null;
|
||||||
|
|
||||||
|
@ -40,7 +46,7 @@ export default function ReviewsView({ userId }: { userId: string; }) {
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res === 0 || res === 1) {
|
if (res === 0 || res === 1) {
|
||||||
(target as HTMLInputElement).value = ""; // clear the input
|
(target as HTMLInputElement).value = ""; // clear the input
|
||||||
refetch();
|
dirtyRefetch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -64,7 +70,7 @@ export default function ReviewsView({ userId }: { userId: string; }) {
|
||||||
<ReviewComponent
|
<ReviewComponent
|
||||||
key={review.id}
|
key={review.id}
|
||||||
review={review}
|
review={review}
|
||||||
refetch={refetch}
|
refetch={dirtyRefetch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{reviews?.length === 0 && (
|
{reviews?.length === 0 && (
|
||||||
|
|
74
src/plugins/shikiCodeblocks/api/languages.ts
Normal file
74
src/plugins/shikiCodeblocks/api/languages.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ILanguageRegistration } from "@vap/shiki";
|
||||||
|
|
||||||
|
export const VPC_REPO = "Vap0r1ze/vapcord";
|
||||||
|
export const VPC_REPO_COMMIT = "88a7032a59cca40da170926651b08201ea3b965a";
|
||||||
|
export const vpcRepoAssets = `https://raw.githubusercontent.com/${VPC_REPO}/${VPC_REPO_COMMIT}/assets/shiki-codeblocks`;
|
||||||
|
export const vpcRepoGrammar = (fileName: string) => `${vpcRepoAssets}/${fileName}`;
|
||||||
|
export const vpcRepoLanguages = `${vpcRepoAssets}/languages.json`;
|
||||||
|
|
||||||
|
export interface Language {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
devicon?: string;
|
||||||
|
grammarUrl: string,
|
||||||
|
grammar?: ILanguageRegistration["grammar"];
|
||||||
|
scopeName: string;
|
||||||
|
aliases?: string[];
|
||||||
|
custom?: boolean;
|
||||||
|
}
|
||||||
|
export interface LanguageJson {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
fileName: string;
|
||||||
|
devicon?: string;
|
||||||
|
scopeName: string;
|
||||||
|
aliases?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const languages: Record<string, Language> = {};
|
||||||
|
|
||||||
|
export const loadLanguages = async () => {
|
||||||
|
const langsJson: LanguageJson[] = await fetch(vpcRepoLanguages).then(res => res.json());
|
||||||
|
const loadedLanguages = Object.fromEntries(
|
||||||
|
langsJson.map(lang => [lang.id, {
|
||||||
|
...lang,
|
||||||
|
grammarUrl: vpcRepoGrammar(lang.fileName),
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
Object.assign(languages, loadedLanguages);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGrammar = (lang: Language): Promise<NonNullable<ILanguageRegistration["grammar"]>> => {
|
||||||
|
if (lang.grammar) return Promise.resolve(lang.grammar);
|
||||||
|
return fetch(lang.grammarUrl).then(res => res.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
const aliasCache = new Map<string, Language>();
|
||||||
|
export function resolveLang(idOrAlias: string) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(languages, idOrAlias)) return languages[idOrAlias];
|
||||||
|
|
||||||
|
const lang = Object.values(languages).find(lang => lang.aliases?.includes(idOrAlias));
|
||||||
|
|
||||||
|
if (!lang) return null;
|
||||||
|
|
||||||
|
aliasCache.set(idOrAlias, lang);
|
||||||
|
return lang;
|
||||||
|
}
|
119
src/plugins/shikiCodeblocks/api/shiki.ts
Normal file
119
src/plugins/shikiCodeblocks/api/shiki.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import { shikiOnigasmSrc, shikiWorkerSrc } from "@utils/dependencies";
|
||||||
|
import { WorkerClient } from "@vap/core/ipc";
|
||||||
|
import type { IShikiTheme, IThemedToken } from "@vap/shiki";
|
||||||
|
|
||||||
|
import { dispatchTheme } from "../hooks/useTheme";
|
||||||
|
import type { ShikiSpec } from "../types";
|
||||||
|
import { getGrammar, languages, loadLanguages, resolveLang } from "./languages";
|
||||||
|
import { themes } from "./themes";
|
||||||
|
|
||||||
|
const themeUrls = Object.values(themes);
|
||||||
|
|
||||||
|
let resolveClient: (client: WorkerClient<ShikiSpec>) => void;
|
||||||
|
|
||||||
|
export const shiki = {
|
||||||
|
client: null as WorkerClient<ShikiSpec> | null,
|
||||||
|
currentTheme: null as IShikiTheme | null,
|
||||||
|
currentThemeUrl: null as string | null,
|
||||||
|
timeoutMs: 10000,
|
||||||
|
languages,
|
||||||
|
themes,
|
||||||
|
loadedThemes: new Set<string>(),
|
||||||
|
loadedLangs: new Set<string>(),
|
||||||
|
clientPromise: new Promise<WorkerClient<ShikiSpec>>(resolve => resolveClient = resolve),
|
||||||
|
|
||||||
|
init: async (initThemeUrl: string | undefined) => {
|
||||||
|
/** https://stackoverflow.com/q/58098143 */
|
||||||
|
const workerBlob = await fetch(shikiWorkerSrc).then(res => res.blob());
|
||||||
|
|
||||||
|
const client = shiki.client = new WorkerClient<ShikiSpec>(
|
||||||
|
"shiki-client",
|
||||||
|
"shiki-host",
|
||||||
|
workerBlob,
|
||||||
|
{ name: "ShikiWorker" },
|
||||||
|
);
|
||||||
|
await client.init();
|
||||||
|
|
||||||
|
const themeUrl = initThemeUrl || themeUrls[0];
|
||||||
|
|
||||||
|
await loadLanguages();
|
||||||
|
await client.run("setOnigasm", { wasm: shikiOnigasmSrc });
|
||||||
|
await client.run("setHighlighter", { theme: themeUrl, langs: [] });
|
||||||
|
shiki.loadedThemes.add(themeUrl);
|
||||||
|
await shiki._setTheme(themeUrl);
|
||||||
|
resolveClient(client);
|
||||||
|
},
|
||||||
|
_setTheme: async (themeUrl: string) => {
|
||||||
|
shiki.currentThemeUrl = themeUrl;
|
||||||
|
const { themeData } = await shiki.client!.run("getTheme", { theme: themeUrl });
|
||||||
|
shiki.currentTheme = JSON.parse(themeData);
|
||||||
|
dispatchTheme({ id: themeUrl, theme: shiki.currentTheme });
|
||||||
|
},
|
||||||
|
loadTheme: async (themeUrl: string) => {
|
||||||
|
const client = await shiki.clientPromise;
|
||||||
|
if (shiki.loadedThemes.has(themeUrl)) return;
|
||||||
|
|
||||||
|
await client.run("loadTheme", { theme: themeUrl });
|
||||||
|
|
||||||
|
shiki.loadedThemes.add(themeUrl);
|
||||||
|
},
|
||||||
|
setTheme: async (themeUrl: string) => {
|
||||||
|
await shiki.clientPromise;
|
||||||
|
themeUrl ||= themeUrls[0];
|
||||||
|
if (!shiki.loadedThemes.has(themeUrl)) await shiki.loadTheme(themeUrl);
|
||||||
|
|
||||||
|
await shiki._setTheme(themeUrl);
|
||||||
|
},
|
||||||
|
loadLang: async (langId: string) => {
|
||||||
|
const client = await shiki.clientPromise;
|
||||||
|
const lang = resolveLang(langId);
|
||||||
|
|
||||||
|
if (!lang || shiki.loadedLangs.has(lang.id)) return;
|
||||||
|
|
||||||
|
await client.run("loadLanguage", {
|
||||||
|
lang: {
|
||||||
|
...lang,
|
||||||
|
grammar: lang.grammar ?? await getGrammar(lang),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
shiki.loadedLangs.add(lang.id);
|
||||||
|
},
|
||||||
|
tokenizeCode: async (code: string, langId: string): Promise<IThemedToken[][]> => {
|
||||||
|
const client = await shiki.clientPromise;
|
||||||
|
const lang = resolveLang(langId);
|
||||||
|
if (!lang) return [];
|
||||||
|
|
||||||
|
if (!shiki.loadedLangs.has(lang.id)) await shiki.loadLang(lang.id);
|
||||||
|
|
||||||
|
return await client.run("codeToThemedTokens", {
|
||||||
|
code,
|
||||||
|
lang: langId,
|
||||||
|
theme: shiki.currentThemeUrl ?? themeUrls[0],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
shiki.currentTheme = null;
|
||||||
|
shiki.currentThemeUrl = null;
|
||||||
|
dispatchTheme({ id: null, theme: null });
|
||||||
|
shiki.client?.destroy();
|
||||||
|
}
|
||||||
|
};
|
67
src/plugins/shikiCodeblocks/api/themes.ts
Normal file
67
src/plugins/shikiCodeblocks/api/themes.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IShikiTheme } from "@vap/shiki";
|
||||||
|
|
||||||
|
export const SHIKI_REPO = "shikijs/shiki";
|
||||||
|
export const SHIKI_REPO_COMMIT = "0b28ad8ccfbf2615f2d9d38ea8255416b8ac3043";
|
||||||
|
export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/shiki/themes/${name}.json`;
|
||||||
|
|
||||||
|
export const themes = {
|
||||||
|
// Default
|
||||||
|
DarkPlus: shikiRepoTheme("dark-plus"),
|
||||||
|
|
||||||
|
// Dev Choices
|
||||||
|
MaterialCandy: "https://raw.githubusercontent.com/millsp/material-candy/master/material-candy.json",
|
||||||
|
|
||||||
|
// More from Shiki repo
|
||||||
|
DraculaSoft: shikiRepoTheme("dracula-soft"),
|
||||||
|
Dracula: shikiRepoTheme("dracula"),
|
||||||
|
GithubDarkDimmed: shikiRepoTheme("github-dark-dimmed"),
|
||||||
|
GithubDark: shikiRepoTheme("github-dark"),
|
||||||
|
GithubLight: shikiRepoTheme("github-light"),
|
||||||
|
LightPlus: shikiRepoTheme("light-plus"),
|
||||||
|
MaterialDarker: shikiRepoTheme("material-darker"),
|
||||||
|
MaterialDefault: shikiRepoTheme("material-default"),
|
||||||
|
MaterialLighter: shikiRepoTheme("material-lighter"),
|
||||||
|
MaterialOcean: shikiRepoTheme("material-ocean"),
|
||||||
|
MaterialPalenight: shikiRepoTheme("material-palenight"),
|
||||||
|
MinDark: shikiRepoTheme("min-dark"),
|
||||||
|
MinLight: shikiRepoTheme("min-light"),
|
||||||
|
Monokai: shikiRepoTheme("monokai"),
|
||||||
|
Nord: shikiRepoTheme("nord"),
|
||||||
|
OneDarkPro: shikiRepoTheme("one-dark-pro"),
|
||||||
|
Poimandres: shikiRepoTheme("poimandres"),
|
||||||
|
RosePineDawn: shikiRepoTheme("rose-pine-dawn"),
|
||||||
|
RosePineMoon: shikiRepoTheme("rose-pine-moon"),
|
||||||
|
RosePine: shikiRepoTheme("rose-pine"),
|
||||||
|
SlackDark: shikiRepoTheme("slack-dark"),
|
||||||
|
SlackOchin: shikiRepoTheme("slack-ochin"),
|
||||||
|
SolarizedDark: shikiRepoTheme("solarized-dark"),
|
||||||
|
SolarizedLight: shikiRepoTheme("solarized-light"),
|
||||||
|
VitesseDark: shikiRepoTheme("vitesse-dark"),
|
||||||
|
VitesseLight: shikiRepoTheme("vitesse-light"),
|
||||||
|
CssVariables: shikiRepoTheme("css-variables"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const themeCache = new Map<string, IShikiTheme>();
|
||||||
|
|
||||||
|
export const getTheme = (url: string): Promise<IShikiTheme> => {
|
||||||
|
if (themeCache.has(url)) return Promise.resolve(themeCache.get(url)!);
|
||||||
|
return fetch(url).then(res => res.json());
|
||||||
|
};
|
46
src/plugins/shikiCodeblocks/components/ButtonRow.tsx
Normal file
46
src/plugins/shikiCodeblocks/components/ButtonRow.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Clipboard } from "@webpack/common";
|
||||||
|
|
||||||
|
import { cl } from "../utils/misc";
|
||||||
|
import { CopyButton } from "./CopyButton";
|
||||||
|
|
||||||
|
export interface ButtonRowProps {
|
||||||
|
theme: import("./Highlighter").ThemeBase;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonRow({ content, theme }: ButtonRowProps) {
|
||||||
|
const buttons: JSX.Element[] = [];
|
||||||
|
|
||||||
|
if (Clipboard.SUPPORTS_COPY) {
|
||||||
|
buttons.push(
|
||||||
|
<CopyButton
|
||||||
|
content={content}
|
||||||
|
className={cl("btn")}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.accentBgColor,
|
||||||
|
color: theme.accentFgColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cl("btns")}>{buttons}</div>;
|
||||||
|
}
|
92
src/plugins/shikiCodeblocks/components/Code.tsx
Normal file
92
src/plugins/shikiCodeblocks/components/Code.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IThemedToken } from "@vap/shiki";
|
||||||
|
|
||||||
|
import { cl } from "../utils/misc";
|
||||||
|
import { ThemeBase } from "./Highlighter";
|
||||||
|
|
||||||
|
export interface CodeProps {
|
||||||
|
theme: ThemeBase;
|
||||||
|
useHljs: boolean;
|
||||||
|
lang?: string;
|
||||||
|
content: string;
|
||||||
|
tokens: IThemedToken[][] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Code = ({
|
||||||
|
theme,
|
||||||
|
useHljs,
|
||||||
|
lang,
|
||||||
|
content,
|
||||||
|
tokens,
|
||||||
|
}: CodeProps) => {
|
||||||
|
let lines!: JSX.Element[];
|
||||||
|
|
||||||
|
if (useHljs) {
|
||||||
|
try {
|
||||||
|
const { value: hljsHtml } = hljs.highlight(lang!, content, true);
|
||||||
|
lines = hljsHtml
|
||||||
|
.split("\n")
|
||||||
|
.map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />);
|
||||||
|
} catch {
|
||||||
|
lines = content.split("\n").map(line => <span>{line}</span>);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const renderTokens =
|
||||||
|
tokens ??
|
||||||
|
content
|
||||||
|
.split("\n")
|
||||||
|
.map(line => [{ color: theme.plainColor, content: line } as IThemedToken]);
|
||||||
|
|
||||||
|
lines = renderTokens.map(line => {
|
||||||
|
// [Cynthia] this makes it so when you highlight the codeblock
|
||||||
|
// empty lines are also selected and copied when you Ctrl+C.
|
||||||
|
if (line.length === 0) {
|
||||||
|
return <span>{"\n"}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{line.map(({ content, color, fontStyle }, i) => (
|
||||||
|
<span
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
color,
|
||||||
|
fontStyle: (fontStyle ?? 0) & 1 ? "italic" : undefined,
|
||||||
|
fontWeight: (fontStyle ?? 0) & 2 ? "bold" : undefined,
|
||||||
|
textDecoration: (fontStyle ?? 0) & 4 ? "underline" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeTableRows = lines.map((line, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td style={{ color: theme.plainColor }}>{i + 1}</td>
|
||||||
|
<td>{line}</td>
|
||||||
|
</tr>
|
||||||
|
));
|
||||||
|
|
||||||
|
return <table className={cl("table")}>{...codeTableRows}</table>;
|
||||||
|
};
|
41
src/plugins/shikiCodeblocks/components/CopyButton.tsx
Normal file
41
src/plugins/shikiCodeblocks/components/CopyButton.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCopyCooldown } from "../hooks/useCopyCooldown";
|
||||||
|
|
||||||
|
export interface CopyButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyButton({ content, ...props }: CopyButtonProps) {
|
||||||
|
const [copyCooldown, copy] = useCopyCooldown(1000);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
style={{
|
||||||
|
...props.style,
|
||||||
|
cursor: copyCooldown ? "default" : undefined,
|
||||||
|
}}
|
||||||
|
onClick={() => copy(content)}
|
||||||
|
>
|
||||||
|
{copyCooldown ? "Copied!" : "Copy"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
42
src/plugins/shikiCodeblocks/components/Header.tsx
Normal file
42
src/plugins/shikiCodeblocks/components/Header.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Language } from "../api/languages";
|
||||||
|
import { DeviconSetting } from "../types";
|
||||||
|
import { cl } from "../utils/misc";
|
||||||
|
|
||||||
|
export interface HeaderProps {
|
||||||
|
langName?: string;
|
||||||
|
useDevIcon: DeviconSetting;
|
||||||
|
shikiLang: Language | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Header({ langName, useDevIcon, shikiLang }: HeaderProps) {
|
||||||
|
if (!langName) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cl("lang")}>
|
||||||
|
{useDevIcon !== DeviconSetting.Disabled && shikiLang?.devicon && (
|
||||||
|
<i
|
||||||
|
className={`devicon-${shikiLang.devicon}${useDevIcon === DeviconSetting.Color ? " colored" : ""}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{langName}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
123
src/plugins/shikiCodeblocks/components/Highlighter.tsx
Normal file
123
src/plugins/shikiCodeblocks/components/Highlighter.tsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { useAwaiter } from "@utils/misc";
|
||||||
|
import { useIntersection } from "@utils/react";
|
||||||
|
import { hljs, React } from "@webpack/common";
|
||||||
|
|
||||||
|
import { resolveLang } from "../api/languages";
|
||||||
|
import { shiki } from "../api/shiki";
|
||||||
|
import { useShikiSettings } from "../hooks/useShikiSettings";
|
||||||
|
import { useTheme } from "../hooks/useTheme";
|
||||||
|
import { hex2Rgb } from "../utils/color";
|
||||||
|
import { cl, shouldUseHljs } from "../utils/misc";
|
||||||
|
import { ButtonRow } from "./ButtonRow";
|
||||||
|
import { Code } from "./Code";
|
||||||
|
import { Header } from "./Header";
|
||||||
|
|
||||||
|
export interface ThemeBase {
|
||||||
|
plainColor: string;
|
||||||
|
accentBgColor: string;
|
||||||
|
accentFgColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighlighterProps {
|
||||||
|
lang?: string;
|
||||||
|
content: string;
|
||||||
|
isPreview: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createHighlighter = (props: HighlighterProps) => (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Highlighter {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
export const Highlighter = ({
|
||||||
|
lang,
|
||||||
|
content,
|
||||||
|
isPreview,
|
||||||
|
}: HighlighterProps) => {
|
||||||
|
const { tryHljs, useDevIcon, bgOpacity } = useShikiSettings(["tryHljs", "useDevIcon", "bgOpacity"]);
|
||||||
|
const { id: currentThemeId, theme: currentTheme } = useTheme();
|
||||||
|
|
||||||
|
const shikiLang = lang ? resolveLang(lang) : null;
|
||||||
|
const useHljs = shouldUseHljs({ lang, tryHljs });
|
||||||
|
|
||||||
|
const [preRef, isIntersecting] = useIntersection(true);
|
||||||
|
|
||||||
|
const [tokens] = useAwaiter(async () => {
|
||||||
|
if (!shikiLang || useHljs || !isIntersecting) return null;
|
||||||
|
return await shiki.tokenizeCode(content, lang!);
|
||||||
|
}, {
|
||||||
|
fallbackValue: null,
|
||||||
|
deps: [lang, content, currentThemeId, isIntersecting],
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeBase: ThemeBase = {
|
||||||
|
plainColor: currentTheme?.fg || "var(--text-normal)",
|
||||||
|
accentBgColor:
|
||||||
|
currentTheme?.colors?.["statusBar.background"] || (useHljs ? "#7289da" : "#007BC8"),
|
||||||
|
accentFgColor: currentTheme?.colors?.["statusBar.foreground"] || "#FFF",
|
||||||
|
backgroundColor:
|
||||||
|
currentTheme?.colors?.["editor.background"] || "var(--background-secondary)",
|
||||||
|
};
|
||||||
|
|
||||||
|
let langName;
|
||||||
|
if (lang) langName = useHljs ? hljs?.getLanguage?.(lang)?.name : shikiLang?.name;
|
||||||
|
|
||||||
|
const preClasses = [cl("root")];
|
||||||
|
if (!langName) preClasses.push(cl("plain"));
|
||||||
|
if (isPreview) preClasses.push(cl("preview"));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<pre
|
||||||
|
ref={preRef}
|
||||||
|
className={preClasses.join(" ")}
|
||||||
|
style={{
|
||||||
|
backgroundColor: useHljs
|
||||||
|
? themeBase.backgroundColor
|
||||||
|
: `rgba(${hex2Rgb(themeBase.backgroundColor)
|
||||||
|
.concat(bgOpacity / 100)
|
||||||
|
.join(", ")})`,
|
||||||
|
color: themeBase.plainColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<code>
|
||||||
|
<Header
|
||||||
|
langName={langName}
|
||||||
|
useDevIcon={useDevIcon}
|
||||||
|
shikiLang={shikiLang}
|
||||||
|
/>
|
||||||
|
<Code
|
||||||
|
theme={themeBase}
|
||||||
|
useHljs={useHljs}
|
||||||
|
lang={lang}
|
||||||
|
content={content}
|
||||||
|
tokens={tokens}
|
||||||
|
/>
|
||||||
|
{!isPreview && <ButtonRow
|
||||||
|
content={content}
|
||||||
|
theme={themeBase}
|
||||||
|
/>}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
34
src/plugins/shikiCodeblocks/hooks/useCopyCooldown.ts
Normal file
34
src/plugins/shikiCodeblocks/hooks/useCopyCooldown.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Clipboard, React } from "@webpack/common";
|
||||||
|
|
||||||
|
export function useCopyCooldown(cooldown: number) {
|
||||||
|
const [copyCooldown, setCopyCooldown] = React.useState(false);
|
||||||
|
|
||||||
|
function copy(text: string) {
|
||||||
|
Clipboard.copy(text);
|
||||||
|
setCopyCooldown(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopyCooldown(false);
|
||||||
|
}, cooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [copyCooldown, copy] as const;
|
||||||
|
}
|
25
src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts
Normal file
25
src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useSettings } from "@api/settings";
|
||||||
|
|
||||||
|
import { ShikiSettings } from "../types";
|
||||||
|
|
||||||
|
export function useShikiSettings(settings: (keyof ShikiSettings)[]) {
|
||||||
|
return useSettings(settings.map(setting => `plugins.ShikiCodeblocks.${setting}`)).plugins.ShikiCodeblocks as ShikiSettings;
|
||||||
|
}
|
49
src/plugins/shikiCodeblocks/hooks/useTheme.ts
Normal file
49
src/plugins/shikiCodeblocks/hooks/useTheme.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
|
type Shiki = typeof import("../api/shiki").shiki;
|
||||||
|
interface ThemeState {
|
||||||
|
id: Shiki["currentThemeUrl"],
|
||||||
|
theme: Shiki["currentTheme"],
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTheme: ThemeState = {
|
||||||
|
id: null,
|
||||||
|
theme: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeSetters = new Set<React.Dispatch<React.SetStateAction<ThemeState>>>();
|
||||||
|
|
||||||
|
export const useTheme = (): ThemeState => {
|
||||||
|
const [, setTheme] = React.useState<ThemeState>(currentTheme);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
themeSetters.add(setTheme);
|
||||||
|
return () => void themeSetters.delete(setTheme);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return currentTheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function dispatchTheme(state: ThemeState) {
|
||||||
|
if (currentTheme.id === state.id) return;
|
||||||
|
Object.assign(currentTheme, state);
|
||||||
|
themeSetters.forEach(setTheme => setTheme(state));
|
||||||
|
}
|
154
src/plugins/shikiCodeblocks/index.ts
Normal file
154
src/plugins/shikiCodeblocks/index.ts
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { parseUrl } from "@utils/misc";
|
||||||
|
import { wordsFromPascal, wordsToTitle } from "@utils/text";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
import cssText from "~fileContent/style.css";
|
||||||
|
|
||||||
|
import { Settings } from "../../Vencord";
|
||||||
|
import { shiki } from "./api/shiki";
|
||||||
|
import { themes } from "./api/themes";
|
||||||
|
import { createHighlighter } from "./components/Highlighter";
|
||||||
|
import { DeviconSetting, HljsSetting, ShikiSettings, StyleSheets } from "./types";
|
||||||
|
import { clearStyles, removeStyle, setStyle } from "./utils/createStyle";
|
||||||
|
|
||||||
|
const themeNames = Object.keys(themes);
|
||||||
|
const devIconCss = "@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css');";
|
||||||
|
|
||||||
|
const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings;
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ShikiCodeblocks",
|
||||||
|
description: "Brings vscode-style codeblocks into Discord, powered by Shiki",
|
||||||
|
authors: [Devs.Vap],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "codeBlock:{react:function",
|
||||||
|
replacement: {
|
||||||
|
match: /codeBlock:\{react:function\((.),(.),(.)\)\{/,
|
||||||
|
replace: "$&return Vencord.Plugins.plugins.ShikiCodeblocks.renderHighlighter($1,$2,$3);",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
start: async () => {
|
||||||
|
setStyle(cssText, StyleSheets.Main);
|
||||||
|
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
|
||||||
|
setStyle(devIconCss, StyleSheets.DevIcons);
|
||||||
|
|
||||||
|
await shiki.init(getSettings().customTheme || getSettings().theme);
|
||||||
|
},
|
||||||
|
stop: () => {
|
||||||
|
shiki.destroy();
|
||||||
|
clearStyles();
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
theme: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Default themes",
|
||||||
|
options: themeNames.map(themeName => ({
|
||||||
|
label: wordsToTitle(wordsFromPascal(themeName)),
|
||||||
|
value: themes[themeName],
|
||||||
|
default: themes[themeName] === themes.DarkPlus,
|
||||||
|
})),
|
||||||
|
disabled: () => !!getSettings().customTheme,
|
||||||
|
onChange: shiki.setTheme,
|
||||||
|
},
|
||||||
|
customTheme: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "A link to a custom vscode theme",
|
||||||
|
placeholder: themes.MaterialCandy,
|
||||||
|
isValid: value => {
|
||||||
|
if (!value) return true;
|
||||||
|
const url = parseUrl(value);
|
||||||
|
if (!url) return "Must be a valid URL";
|
||||||
|
|
||||||
|
if (!url.pathname.endsWith(".json")) return "Must be a json file";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onChange: value => shiki.setTheme(value || getSettings().theme),
|
||||||
|
},
|
||||||
|
tryHljs: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Use the more lightweight default Discord highlighter and theme.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Never",
|
||||||
|
value: HljsSetting.Never,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Prefer Shiki instead of Highlight.js",
|
||||||
|
value: HljsSetting.Secondary,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Prefer Highlight.js instead of Shiki",
|
||||||
|
value: HljsSetting.Primary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Always",
|
||||||
|
value: HljsSetting.Always,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
useDevIcon: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "How to show language icons on codeblocks",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Disabled",
|
||||||
|
value: DeviconSetting.Disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Colorless",
|
||||||
|
value: DeviconSetting.Greyscale,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Colored",
|
||||||
|
value: DeviconSetting.Color,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onChange: (newValue: DeviconSetting) => {
|
||||||
|
if (newValue === DeviconSetting.Disabled) removeStyle(StyleSheets.DevIcons);
|
||||||
|
else setStyle(devIconCss, StyleSheets.DevIcons);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bgOpacity: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Background opacity",
|
||||||
|
markers: [0, 20, 40, 60, 80, 100],
|
||||||
|
default: 100,
|
||||||
|
stickToMarkers: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// exports
|
||||||
|
shiki,
|
||||||
|
createHighlighter,
|
||||||
|
renderHighlighter: ({ lang, content }: { lang: string; content: string; }) => {
|
||||||
|
return createHighlighter({
|
||||||
|
lang,
|
||||||
|
content,
|
||||||
|
isPreview: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
100
src/plugins/shikiCodeblocks/style.css
Normal file
100
src/plugins/shikiCodeblocks/style.css
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
.shiki-root {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root code {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.125rem;
|
||||||
|
text-indent: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root [class^='devicon-'],
|
||||||
|
.shiki-root [class*=' devicon-'] {
|
||||||
|
margin-right: 8px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-plain code {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btns {
|
||||||
|
font-size: 1em;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root:hover .shiki-btns {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btn {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btn~.shiki-btn {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btn:last-child {
|
||||||
|
border-radius: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-spinner-container {
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
justify-content: center;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-preview {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-lang {
|
||||||
|
padding: 0 5px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: capitalize;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-table tr {
|
||||||
|
height: 19px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root td:first-child {
|
||||||
|
border-right: 1px solid transparent;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 8px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root td:last-child {
|
||||||
|
padding-left: 8px;
|
||||||
|
word-break: break-word;
|
||||||
|
width: 100%;
|
||||||
|
}
|
78
src/plugins/shikiCodeblocks/types.ts
Normal file
78
src/plugins/shikiCodeblocks/types.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ILanguageRegistration,
|
||||||
|
IShikiTheme,
|
||||||
|
IThemedToken,
|
||||||
|
IThemeRegistration,
|
||||||
|
} from "@vap/shiki";
|
||||||
|
|
||||||
|
import type { Settings } from "../../Vencord";
|
||||||
|
|
||||||
|
/** This must be atleast a subset of the `@vap/shiki-worker` spec */
|
||||||
|
export type ShikiSpec = {
|
||||||
|
setOnigasm: ({ wasm }: { wasm: string; }) => Promise<void>;
|
||||||
|
setHighlighter: ({ theme, langs }: {
|
||||||
|
theme: IThemeRegistration | void;
|
||||||
|
langs: ILanguageRegistration[];
|
||||||
|
}) => Promise<void>;
|
||||||
|
loadTheme: ({ theme }: {
|
||||||
|
theme: string | IShikiTheme;
|
||||||
|
}) => Promise<void>;
|
||||||
|
getTheme: ({ theme }: { theme: string; }) => Promise<{ themeData: string; }>;
|
||||||
|
loadLanguage: ({ lang }: { lang: ILanguageRegistration; }) => Promise<void>;
|
||||||
|
codeToThemedTokens: ({
|
||||||
|
code,
|
||||||
|
lang,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
code: string;
|
||||||
|
lang?: string;
|
||||||
|
theme?: string;
|
||||||
|
}) => Promise<IThemedToken[][]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum StyleSheets {
|
||||||
|
Main = "MAIN",
|
||||||
|
DevIcons = "DEVICONS",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HljsSetting {
|
||||||
|
Never = "NEVER",
|
||||||
|
Secondary = "SECONDARY",
|
||||||
|
Primary = "PRIMARY",
|
||||||
|
Always = "ALWAYS",
|
||||||
|
}
|
||||||
|
export enum DeviconSetting {
|
||||||
|
Disabled = "DISABLED",
|
||||||
|
Greyscale = "GREYSCALE",
|
||||||
|
Color = "COLOR"
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonSettings = {
|
||||||
|
[K in keyof Settings["plugins"][string]as K extends `${infer V}` ? K : never]: Settings["plugins"][string][K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ShikiSettings extends CommonSettings {
|
||||||
|
theme: string;
|
||||||
|
customTheme: string;
|
||||||
|
tryHljs: HljsSetting;
|
||||||
|
useDevIcon: DeviconSetting;
|
||||||
|
bgOpacity: number;
|
||||||
|
}
|
32
src/plugins/shikiCodeblocks/utils/color.ts
Normal file
32
src/plugins/shikiCodeblocks/utils/color.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function hex2Rgb(hex: string) {
|
||||||
|
hex = hex.slice(1);
|
||||||
|
if (hex.length < 6)
|
||||||
|
hex = hex
|
||||||
|
.split("")
|
||||||
|
.map(c => c + c)
|
||||||
|
.join("");
|
||||||
|
if (hex.length === 6) hex += "ff";
|
||||||
|
if (hex.length > 6) hex = hex.slice(0, 6);
|
||||||
|
return hex
|
||||||
|
.split(/(..)/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(c => parseInt(c, 16));
|
||||||
|
}
|
36
src/plugins/shikiCodeblocks/utils/createStyle.ts
Normal file
36
src/plugins/shikiCodeblocks/utils/createStyle.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const styles = new Map<string, HTMLStyleElement>();
|
||||||
|
|
||||||
|
export function setStyle(css: string, id: string) {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.innerText = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
styles.set(id, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeStyle(id: string) {
|
||||||
|
styles.get(id)?.remove();
|
||||||
|
return styles.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearStyles = () => {
|
||||||
|
styles.forEach(style => style.remove());
|
||||||
|
styles.clear();
|
||||||
|
};
|
50
src/plugins/shikiCodeblocks/utils/misc.ts
Normal file
50
src/plugins/shikiCodeblocks/utils/misc.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { hljs } from "@webpack/common";
|
||||||
|
|
||||||
|
import { resolveLang } from "../api/languages";
|
||||||
|
import { HighlighterProps } from "../components/Highlighter";
|
||||||
|
import { HljsSetting, ShikiSettings } from "../types";
|
||||||
|
|
||||||
|
export const cl = (className: string) => `shiki-${className}`;
|
||||||
|
|
||||||
|
export const shouldUseHljs = ({
|
||||||
|
lang,
|
||||||
|
tryHljs,
|
||||||
|
}: {
|
||||||
|
lang: HighlighterProps["lang"],
|
||||||
|
tryHljs: ShikiSettings["tryHljs"],
|
||||||
|
}) => {
|
||||||
|
const hljsLang = lang ? hljs?.getLanguage?.(lang) : null;
|
||||||
|
const shikiLang = lang ? resolveLang(lang) : null;
|
||||||
|
const langName = shikiLang?.name;
|
||||||
|
|
||||||
|
switch (tryHljs) {
|
||||||
|
case HljsSetting.Always:
|
||||||
|
return true;
|
||||||
|
case HljsSetting.Primary:
|
||||||
|
return !!hljsLang || lang === "";
|
||||||
|
case HljsSetting.Secondary:
|
||||||
|
return !langName && !!hljsLang;
|
||||||
|
case HljsSetting.Never:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
|
@ -157,8 +157,12 @@ export const Devs = Object.freeze({
|
||||||
name: "Luny",
|
name: "Luny",
|
||||||
id: 821472922140803112n
|
id: 821472922140803112n
|
||||||
},
|
},
|
||||||
|
Vap: {
|
||||||
|
name: "Vap0r1ze",
|
||||||
|
id: 454072114492866560n
|
||||||
|
},
|
||||||
KingFish: {
|
KingFish: {
|
||||||
name: "King Fish",
|
name: "King Fish",
|
||||||
id: 499400512559382538n
|
id: 499400512559382538n
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -74,3 +74,7 @@ export interface ApngFrameData {
|
||||||
frames: ApngFrame[];
|
frames: ApngFrame[];
|
||||||
playTime: number;
|
playTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist";
|
||||||
|
export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`;
|
||||||
|
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm";
|
||||||
|
|
|
@ -28,7 +28,12 @@ export function makeLazy<T>(factory: () => T): () => T {
|
||||||
return () => cache ?? (cache = factory());
|
return () => cache ?? (cache = factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
type AwaiterRes<T> = [T, any, boolean, () => void];
|
type AwaiterRes<T> = [T, any, boolean];
|
||||||
|
interface AwaiterOpts<T> {
|
||||||
|
fallbackValue: T,
|
||||||
|
deps?: unknown[],
|
||||||
|
onError?(e: any): void,
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Await a promise
|
* Await a promise
|
||||||
* @param factory Factory
|
* @param factory Factory
|
||||||
|
@ -36,26 +41,31 @@ type AwaiterRes<T> = [T, any, boolean, () => void];
|
||||||
* @returns [value, error, isPending]
|
* @returns [value, error, isPending]
|
||||||
*/
|
*/
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>): AwaiterRes<T | null>;
|
export function useAwaiter<T>(factory: () => Promise<T>): AwaiterRes<T | null>;
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T): AwaiterRes<T>;
|
export function useAwaiter<T>(factory: () => Promise<T>, providedOpts: AwaiterOpts<T>): AwaiterRes<T>;
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: null, onError: (e: unknown) => unknown): AwaiterRes<T>;
|
export function useAwaiter<T>(factory: () => Promise<T>, providedOpts?: AwaiterOpts<T | null>): AwaiterRes<T | null> {
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T | null = null, onError?: (e: unknown) => unknown): AwaiterRes<T | null> {
|
const opts: Required<AwaiterOpts<T | null>> = Object.assign({
|
||||||
|
fallbackValue: null,
|
||||||
|
deps: [],
|
||||||
|
onError: null,
|
||||||
|
}, providedOpts);
|
||||||
const [state, setState] = React.useState({
|
const [state, setState] = React.useState({
|
||||||
value: fallbackValue,
|
value: opts.fallbackValue,
|
||||||
error: null,
|
error: null,
|
||||||
pending: true
|
pending: true
|
||||||
});
|
});
|
||||||
const [signal, setSignal] = React.useState(0);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let isAlive = true;
|
let isAlive = true;
|
||||||
|
if (!state.pending) setState({ ...state, pending: true });
|
||||||
|
|
||||||
factory()
|
factory()
|
||||||
.then(value => isAlive && setState({ value, error: null, pending: false }))
|
.then(value => isAlive && setState({ value, error: null, pending: false }))
|
||||||
.catch(error => isAlive && (setState({ value: null, error, pending: false }), onError?.(error)));
|
.catch(error => isAlive && (setState({ value: null, error, pending: false }), opts.onError?.(error)));
|
||||||
|
|
||||||
return () => void (isAlive = false);
|
return () => void (isAlive = false);
|
||||||
}, [signal]);
|
}, opts.deps);
|
||||||
|
|
||||||
return [state.value, state.error, state.pending, () => setSignal(signal + 1)];
|
return [state.value, state.error, state.pending];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,3 +207,24 @@ export function copyWithToast(text: string, toastMessage = "Copied to clipboard!
|
||||||
export function isObject(obj: unknown): obj is object {
|
export function isObject(obj: unknown): obj is object {
|
||||||
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns null if value is not a URL, otherwise return URL object.
|
||||||
|
* Avoids having to wrap url checks in a try/catch
|
||||||
|
*/
|
||||||
|
export function parseUrl(urlString: string): URL | null {
|
||||||
|
try {
|
||||||
|
return new URL(urlString);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an element is on screen
|
||||||
|
*/
|
||||||
|
export const checkIntersecting = (el: Element) => {
|
||||||
|
const elementBox = el.getBoundingClientRect();
|
||||||
|
const documentHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
|
||||||
|
return !(elementBox.bottom < 0 || elementBox.top - documentHeight >= 0);
|
||||||
|
};
|
||||||
|
|
62
src/utils/react.ts
Normal file
62
src/utils/react.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
|
import { checkIntersecting } from "./misc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an element is on screen
|
||||||
|
* @param intersectOnly If `true`, will only update the state when the element comes into view
|
||||||
|
* @returns [refCallback, isIntersecting]
|
||||||
|
*/
|
||||||
|
export const useIntersection = (intersectOnly = false): [
|
||||||
|
refCallback: React.RefCallback<Element>,
|
||||||
|
isIntersecting: boolean,
|
||||||
|
] => {
|
||||||
|
const observerRef = React.useRef<IntersectionObserver | null>(null);
|
||||||
|
const [isIntersecting, setIntersecting] = React.useState(false);
|
||||||
|
|
||||||
|
const refCallback = (element: Element | null) => {
|
||||||
|
observerRef.current?.disconnect();
|
||||||
|
observerRef.current = null;
|
||||||
|
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
if (checkIntersecting(element)) {
|
||||||
|
setIntersecting(true);
|
||||||
|
if (intersectOnly) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observerRef.current = new IntersectionObserver(entries => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.target !== element) continue;
|
||||||
|
if (entry.isIntersecting && intersectOnly) {
|
||||||
|
setIntersecting(true);
|
||||||
|
observerRef.current?.disconnect();
|
||||||
|
observerRef.current = null;
|
||||||
|
} else {
|
||||||
|
setIntersecting(entry.isIntersecting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observerRef.current.observe(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [refCallback, isIntersecting];
|
||||||
|
};
|
36
src/utils/text.ts
Normal file
36
src/utils/text.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Utils for readable text transformations eg: `toTitle(fromKebab())`
|
||||||
|
|
||||||
|
// Case style to words
|
||||||
|
export const wordsFromCamel = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
||||||
|
export const wordsFromSnake = (text: string) => text.toLowerCase().split("_");
|
||||||
|
export const wordsFromKebab = (text: string) => text.toLowerCase().split("-");
|
||||||
|
export const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
||||||
|
export const wordsFromTitle = (text: string) => text.toLowerCase().split(" ");
|
||||||
|
|
||||||
|
// Words to case style
|
||||||
|
export const wordsToCamel = (words: string[]) =>
|
||||||
|
words.map((w, i) => (i ? w[0].toUpperCase() + w.slice(1) : w)).join("");
|
||||||
|
export const wordsToSnake = (words: string[]) => words.join("_").toUpperCase();
|
||||||
|
export const wordsToKebab = (words: string[]) => words.join("-").toLowerCase();
|
||||||
|
export const wordsToPascal = (words: string[]) =>
|
||||||
|
words.map(w => w[0].toUpperCase() + w.slice(1)).join("");
|
||||||
|
export const wordsToTitle = (words: string[]) =>
|
||||||
|
words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
@ -37,6 +37,8 @@ export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPorta
|
||||||
export const RestAPI = findByPropsLazy("getAPIBaseURL", "get");
|
export const RestAPI = findByPropsLazy("getAPIBaseURL", "get");
|
||||||
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
|
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
|
||||||
|
|
||||||
|
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight");
|
||||||
|
|
||||||
export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & {
|
export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & {
|
||||||
getMessages(chanId: string): any;
|
getMessages(chanId: string): any;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue