getting the hang of the editContext api
This commit is contained in:
parent
213a1f7ae7
commit
4fb7405466
11 changed files with 587 additions and 134 deletions
|
@ -4,29 +4,14 @@ import devtools from 'solid-devtools/vite';
|
|||
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: '@', replacement: 'F:\\Github\\calque\\node_modules\\' },
|
||||
],
|
||||
},
|
||||
html: {
|
||||
cspNonce: 'KAAS_IS_AWESOME',
|
||||
},
|
||||
// css: {
|
||||
// postcss: {
|
||||
// },
|
||||
// },
|
||||
plugins: [
|
||||
devtools({
|
||||
autoname: true,
|
||||
}),
|
||||
solidSvg(),
|
||||
{
|
||||
name: 'temp',
|
||||
configResolved(config) {
|
||||
console.log(config.resolve.alias);
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
solid: {
|
||||
|
|
26
bun.lock
26
bun.lock
|
@ -6,17 +6,19 @@
|
|||
"dependencies": {
|
||||
"@solid-primitives/clipboard": "^1.6.0",
|
||||
"@solid-primitives/destructure": "^0.2.0",
|
||||
"@solid-primitives/event-listener": "^2.4.0",
|
||||
"@solid-primitives/i18n": "^2.2.0",
|
||||
"@solid-primitives/scheduled": "^1.5.0",
|
||||
"@solid-primitives/selection": "^0.1.0",
|
||||
"@solid-primitives/selection": "^0.1.1",
|
||||
"@solid-primitives/storage": "^4.3.1",
|
||||
"@solid-primitives/timer": "^1.4.0",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"@solidjs/start": "^1.1.1",
|
||||
"dexie": "^4.0.11",
|
||||
"flag-icons": "^7.3.2",
|
||||
"iterator-helpers-polyfill": "^3.0.1",
|
||||
"rehype-dom-parse": "^5.0.2",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-remark": "^10.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
|
@ -33,7 +35,7 @@
|
|||
"vinxi": "^0.5.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "^17.0.3",
|
||||
"@happy-dom/global-registrator": "^17.1.1",
|
||||
"@sinonjs/fake-timers": "^14.0.0",
|
||||
"@solidjs/testing-library": "^0.8.10",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
|
@ -167,7 +169,7 @@
|
|||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.20.2", "", { "os": "win32", "cpu": "x64" }, "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ=="],
|
||||
|
||||
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@17.0.3", "", { "dependencies": { "happy-dom": "^17.0.3" } }, "sha512-isCCWywZq8XPE3A5y7pRyFIsAgij+3eVXgQNCbexGRP00/+nctmf4SfQxC3vV3MmEaOXaNj7IiiSC0BtSHQZgg=="],
|
||||
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@17.1.1", "", { "dependencies": { "happy-dom": "^17.1.1" } }, "sha512-if4TVRU4SnQwpOC9pN/a892nXPpctqER/rYIS9E/YsqwpaPANhMjFM7/+Ibd748gP6OuDJbspQ0axFrCscI1og=="],
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||
|
||||
|
@ -347,7 +349,7 @@
|
|||
|
||||
"@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-RVw24IRNh1FQ4DCMb3OahB70tXIwc5vH8nhR4nNPsXwUPQeuOkLsDI5BlxaPk0vyZgqw9lDpufgI3HnPwplgDw=="],
|
||||
|
||||
"@solid-primitives/selection": ["@solid-primitives/selection@0.1.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-LtqSZVVascQpBeIHS2RZ8UNvLjCa/aDEqrd9WQtqhz14vkeHAwBP33THdWDYn55b6UaOfWQlVYni3r4NfHXK0w=="],
|
||||
"@solid-primitives/selection": ["@solid-primitives/selection@0.1.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-BPkTf8sW/t7GmcDj1A0Fa0xsyCjxh4D0qUAlcbxDkyAH4pG0bfuJ4wCKX+2qgmG/bkBQHiK/UexJtFhlQ1MS4Q=="],
|
||||
|
||||
"@solid-primitives/static-store": ["@solid-primitives/static-store@0.0.8", "", { "dependencies": { "@solid-primitives/utils": "^6.2.3" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ZecE4BqY0oBk0YG00nzaAWO5Mjcny8Fc06CdbXadH9T9lzq/9GefqcSe/5AtdXqjvY/DtJ5C6CkcjPZO0o/eqg=="],
|
||||
|
||||
|
@ -363,15 +365,15 @@
|
|||
|
||||
"@solidjs/router": ["@solidjs/router@0.15.3", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw=="],
|
||||
|
||||
"@solidjs/start": ["@solidjs/start@1.1.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.99.5", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.3" } }, "sha512-7MNhNVt8uF7tdvLkvJhj4357vg3Ha+yqJP8XhQ6IbSZbsyk/xMkYmfc1h6w4GWiWZ5tn1DvS1uqGXjLFbKRy6g=="],
|
||||
"@solidjs/start": ["@solidjs/start@1.1.1", "", { "dependencies": { "@tanstack/server-functions-plugin": "^1.103.1", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.3" } }, "sha512-vJuXJlhHvP/hSdKQ+iuvBU2bw0S+IKQYOyldnRoCvrX7Nmu1p3npnACSlhNNkN06IqSX3MVde8D8Lr9xXEMjRQ=="],
|
||||
|
||||
"@solidjs/testing-library": ["@solidjs/testing-library@0.8.10", "", { "dependencies": { "@testing-library/dom": "^10.4.0" }, "peerDependencies": { "@solidjs/router": ">=0.9.0", "solid-js": ">=1.0.0" }, "optionalPeers": ["@solidjs/router"] }, "sha512-qdeuIerwyq7oQTIrrKvV0aL9aFeuwTd86VYD3afdq5HYEwoox1OBTJy4y8A3TFZr8oAR0nujYgCzY/8wgHGfeQ=="],
|
||||
|
||||
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.102.2", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-utils": "^1.102.2", "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "babel-dead-code-elimination": "^1.0.9", "dedent": "^1.5.3", "tiny-invariant": "^1.3.3" } }, "sha512-wM4ovyuYx0rBvcaR9ay+CtuPK8AEqFv6rF4LbPqHx7EufzBi7aJt70RqPdHt3bP3dcEuJbcRGFxthZ+WUt/Q5Q=="],
|
||||
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.106.0", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-utils": "^1.102.2", "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "babel-dead-code-elimination": "^1.0.9", "dedent": "^1.5.3", "tiny-invariant": "^1.3.3" } }, "sha512-eAK+8tGl+ZZimROdnuHoAc1MVKvmQWh7WTQbh531607NQAuD/7TgbTAiSBfTJYW8qeGovrSp/qq+dnhjqTy3xQ=="],
|
||||
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.102.2", "", { "dependencies": { "@babel/generator": "^7.26.8", "@babel/parser": "^7.26.8", "ansis": "^3.11.0", "diff": "^7.0.0" } }, "sha512-Uwl2nbrxhCzviaHHBLNPhSC/OMpZLdOTxTJndUSsXTzWUP4IoQcVmngaIsxi9iriE3ArC1VXuanUAkfGmimNOQ=="],
|
||||
|
||||
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.102.2", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/directive-functions-plugin": "1.102.2", "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "babel-dead-code-elimination": "^1.0.9", "dedent": "^1.5.3", "tiny-invariant": "^1.3.3" } }, "sha512-l7nPcYjUaiO4ogjfE5hAPsuByFsn/5LqtdsC7f0I1VVgMxHp08OVVRH2rrgdR24ehRr3RkrUQoFviVyBSxcfCA=="],
|
||||
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.106.0", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/directive-functions-plugin": "1.106.0", "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "babel-dead-code-elimination": "^1.0.9", "dedent": "^1.5.3", "tiny-invariant": "^1.3.3" } }, "sha512-uFqiICV0b9t7jluQLroUt9/2PayEKu4hWTS5nezWsW8kNaK0Pw4avdLK+8mYjYuJLBhuQrQZ/mSEMqdJYgYv0Q=="],
|
||||
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.0", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" } }, "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ=="],
|
||||
|
||||
|
@ -795,7 +797,7 @@
|
|||
|
||||
"h3": ["h3@1.13.0", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": ">=0.2.0 <0.4.0", "defu": "^6.1.4", "destr": "^2.0.3", "iron-webcrypto": "^1.2.1", "ohash": "^1.1.4", "radix3": "^1.1.2", "ufo": "^1.5.4", "uncrypto": "^0.1.3", "unenv": "^1.10.0" } }, "sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg=="],
|
||||
|
||||
"happy-dom": ["happy-dom@17.0.3", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-1vWCwpeguN02wQF8kGeaj69FDX19bXKQXmyUKcE+O0WLY0uhS0RPTLCJR8Omy8hrjMHwV3dUJ24JUrK07aOA9Q=="],
|
||||
"happy-dom": ["happy-dom@17.1.1", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-OSTkBlmD/6Do7gCd7nZB5iFq1bF9VQg/iFmjHmxvVX2S1UiOpo6sT+aFNnu3XUsB8hCZb9+GZ0G1g1TaMiAggw=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
|
@ -803,6 +805,8 @@
|
|||
|
||||
"hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="],
|
||||
|
||||
"hast-util-from-dom": ["hast-util-from-dom@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="],
|
||||
|
||||
"hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="],
|
||||
|
||||
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
|
||||
|
@ -1183,6 +1187,8 @@
|
|||
|
||||
"regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
|
||||
|
||||
"rehype-dom-parse": ["rehype-dom-parse@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-dom": "^5.0.0", "unified": "^11.0.0" } }, "sha512-8CqP11KaqvtWsMqVEC2yM3cZWZsDNqqpr8nPvogjraLuh45stabgcpXadCAxu1n6JaUNJ/Xr3GIqXP7okbNqLg=="],
|
||||
|
||||
"rehype-minify-whitespace": ["rehype-minify-whitespace@6.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0" } }, "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw=="],
|
||||
|
||||
"rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="],
|
||||
|
@ -1523,8 +1529,6 @@
|
|||
|
||||
"@solid-primitives/resize-observer/@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.0", "", { "dependencies": { "@solid-primitives/utils": "^6.3.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-6Coau0Kv/dF83UQpbBzc+gnJafOQAPe2jCbB4jmTK5UocsR5cWmFBVRm3kin+nZFVaO4WkuELw0cKANWgTVh8Q=="],
|
||||
|
||||
"@solidjs/start/vite-plugin-solid": ["vite-plugin-solid@2.11.1", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-X9vbbK6AOOA6yxSsNl1VTuUq5y4BG9AR6Z5F/J1ZC2VO7ll8DlSCbOL+RcZXlRbxn0ptE6OI5832nGQhq4yXKQ=="],
|
||||
|
||||
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
|
||||
|
||||
"@testing-library/dom/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
|
|
@ -8,17 +8,19 @@
|
|||
"dependencies": {
|
||||
"@solid-primitives/clipboard": "^1.6.0",
|
||||
"@solid-primitives/destructure": "^0.2.0",
|
||||
"@solid-primitives/event-listener": "^2.4.0",
|
||||
"@solid-primitives/i18n": "^2.2.0",
|
||||
"@solid-primitives/scheduled": "^1.5.0",
|
||||
"@solid-primitives/selection": "^0.1.0",
|
||||
"@solid-primitives/selection": "^0.1.1",
|
||||
"@solid-primitives/storage": "^4.3.1",
|
||||
"@solid-primitives/timer": "^1.4.0",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"@solidjs/start": "^1.1.1",
|
||||
"dexie": "^4.0.11",
|
||||
"flag-icons": "^7.3.2",
|
||||
"iterator-helpers-polyfill": "^3.0.1",
|
||||
"rehype-dom-parse": "^5.0.2",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-remark": "^10.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
|
@ -35,7 +37,7 @@
|
|||
"vinxi": "^0.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "^17.0.3",
|
||||
"@happy-dom/global-registrator": "^17.1.1",
|
||||
"@sinonjs/fake-timers": "^14.0.0",
|
||||
"@solidjs/testing-library": "^0.8.10",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Component, createEffect, createMemo, createSignal, For, onMount, untrack } from 'solid-js';
|
||||
import { debounce } from '@solid-primitives/scheduled';
|
||||
import { Component, createEffect, createMemo, createSignal, For, on, untrack } from 'solid-js';
|
||||
import { createSelection, getTextNodes } from '@solid-primitives/selection';
|
||||
import { createSource } from '~/features/source';
|
||||
import { isServer } from 'solid-js/web';
|
||||
import { createEditContext } from '~/features/edit-context';
|
||||
import { createSource } from '~/features/source';
|
||||
import css from './textarea.module.css';
|
||||
|
||||
interface TextareaProps {
|
||||
|
@ -16,75 +16,25 @@ interface TextareaProps {
|
|||
}
|
||||
|
||||
export function Textarea(props: TextareaProps) {
|
||||
const [selection, setSelection] = createSelection();
|
||||
const [editorRef, setEditorRef] = createSignal<HTMLElement>();
|
||||
|
||||
const source = createSource(props.value);
|
||||
const source = createSource(() => props.value);
|
||||
const [text] = createEditContext(editorRef, () => source.out);
|
||||
|
||||
createEffect(() => {
|
||||
props.oninput?.(source.in);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
source.in = props.value;
|
||||
});
|
||||
createEffect(on(() => [editorRef(), source.spellingErrors] as const, ([ref, errors]) => {
|
||||
createHighlights(ref, 'spelling-error', errors);
|
||||
}));
|
||||
|
||||
const mutate = debounce(() => {
|
||||
const [, start, end] = selection();
|
||||
const ref = editorRef();
|
||||
createEffect(on(() => [editorRef(), source.grammarErrors] as const, ([ref, errors]) => {
|
||||
createHighlights(ref, 'grammar-error', errors);
|
||||
}));
|
||||
|
||||
if (ref) {
|
||||
source.out = ref.innerHTML;
|
||||
|
||||
ref.style.height = `1px`;
|
||||
ref.style.height = `${2 + ref.scrollHeight}px`;
|
||||
|
||||
setSelection([ref, start, end]);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
onMount(() => {
|
||||
const ref = editorRef()!;
|
||||
|
||||
console.log(EditContext);
|
||||
|
||||
const context = new EditContext({
|
||||
text: source.out,
|
||||
});
|
||||
|
||||
const sub = (event) => context.addEventListener(event, (e: Event) => console.log(event, e));
|
||||
|
||||
sub('textupdate');
|
||||
sub('textformatupdate');
|
||||
sub('characterboundupdate');
|
||||
|
||||
console.log(context);
|
||||
|
||||
ref.editContext = context;
|
||||
|
||||
const resize = () => context.updateControlBounds(ref.getBoundingClientRect());
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
// new MutationObserver(mutate).observe(ref, {
|
||||
// subtree: true,
|
||||
// childList: true,
|
||||
// characterData: true,
|
||||
// });
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
createHighlights(editorRef()!, 'spelling-error', source.spellingErrors);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
createHighlights(editorRef()!, 'grammar-error', source.grammarErrors);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
createHighlights(editorRef()!, 'search-results', source.queryResults);
|
||||
});
|
||||
createEffect(on(() => [editorRef(), source.queryResults] as const, ([ref, errors]) => {
|
||||
createHighlights(ref, 'search-results', errors);
|
||||
}));
|
||||
|
||||
return <>
|
||||
<Suggestions />
|
||||
|
@ -94,7 +44,7 @@ export function Textarea(props: TextareaProps) {
|
|||
class={`${css.textarea} ${props.class}`}
|
||||
dir="auto"
|
||||
lang={props.lang}
|
||||
innerHTML={source.out}
|
||||
innerHTML={text()}
|
||||
data-placeholder={props.placeholder ?? ''}
|
||||
on:keydown={e => e.stopPropagation()}
|
||||
on:pointerdown={e => e.stopPropagation()}
|
||||
|
@ -176,20 +126,7 @@ const findMarkerNode = (node: Node | null) => {
|
|||
return node;
|
||||
};
|
||||
|
||||
const spellChecker = checker(/\w+/gi);
|
||||
const grammarChecker = checker(/\w+\s+\w+/gi);
|
||||
|
||||
function checker(regex: RegExp) {
|
||||
return (subject: string, lang: string): [number, number][] => {
|
||||
// return [];
|
||||
|
||||
const threshold = .75//.99;
|
||||
|
||||
return Array.from<RegExpExecArray>(subject.matchAll(regex)).filter(() => Math.random() >= threshold).map(({ 0: match, index }) => {
|
||||
return [index, index + match.length] as const;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const createHighlights = (node: Node, type: string, ranges: [number, number][]) => {
|
||||
queueMicrotask(() => {
|
||||
|
|
324
src/features/edit-context/context.ts
Normal file
324
src/features/edit-context/context.ts
Normal file
|
@ -0,0 +1,324 @@
|
|||
import { createEventListenerMap, DocumentEventListener, WindowEventListener } from "@solid-primitives/event-listener";
|
||||
import { Accessor, createEffect, createMemo, onMount } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { isServer } from "solid-js/web";
|
||||
import { createSelection, getTextNodes } from "@solid-primitives/selection";
|
||||
import { visit } from "unist-util-visit";
|
||||
import type { Root, Text } from 'hast';
|
||||
import { unified } from "unified";
|
||||
import rehypeParse from "rehype-parse";
|
||||
|
||||
type EditContext = [Accessor<string>];
|
||||
|
||||
export function createEditContext(ref: Accessor<HTMLElement | undefined>, value: Accessor<string>): EditContext {
|
||||
if (isServer) {
|
||||
return [createMemo(() => value())];
|
||||
}
|
||||
|
||||
if (!("EditContext" in window)) {
|
||||
throw new Error('`EditContext` is not implemented');
|
||||
}
|
||||
|
||||
const context = new EditContext({
|
||||
text: value(),
|
||||
});
|
||||
|
||||
const [store, setStore] = createStore({
|
||||
text: value(),
|
||||
isComposing: false,
|
||||
|
||||
// Bounds
|
||||
characterBounds: new Array<DOMRect>(),
|
||||
controlBounds: new DOMRect(),
|
||||
selectionBounds: new DOMRect(),
|
||||
});
|
||||
|
||||
const ast = createMemo(() => unified().use(rehypeParse).parse(store.text));
|
||||
const indices = createMemo(() => {
|
||||
const root = ref();
|
||||
|
||||
if (!root) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nodes = getTextNodes(root);
|
||||
const indices: { node: Node, text: { start: number, end: number }, html: { start: number, end: number } }[] = [];
|
||||
|
||||
let index = 0;
|
||||
visit(ast(), n => n.type === 'text', (node) => {
|
||||
const { position, value } = node as Text;
|
||||
const end = index + value.length;
|
||||
|
||||
if (position) {
|
||||
indices.push({ node: nodes.shift()!, text: { start: index, end }, html: { start: position.start.offset!, end: position.end.offset! } });
|
||||
}
|
||||
|
||||
index = end;
|
||||
});
|
||||
|
||||
return indices;
|
||||
});
|
||||
const [selection, setSelection] = createSelection();
|
||||
|
||||
createEffect(() => {
|
||||
console.log(indices());
|
||||
});
|
||||
|
||||
createEventListenerMap<any>(context, {
|
||||
textupdate(e: TextUpdateEvent) {
|
||||
const { updateRangeStart: start, updateRangeEnd: end } = e;
|
||||
|
||||
setStore('text', `${store.text.slice(0, start)}${e.text}${store.text.slice(end)}`);
|
||||
|
||||
updateSelection(toRange(ref()!, start, end));
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('hmmm', e, start, end);
|
||||
context.updateSelection(start, end);
|
||||
|
||||
|
||||
setSelection([ref()!, start, end]);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
compositionstart() {
|
||||
setStore('isComposing', true);
|
||||
},
|
||||
|
||||
compositionend() {
|
||||
setStore('isComposing', false);
|
||||
},
|
||||
|
||||
characterboundsupdate(e: CharacterBoundsUpdateEvent) {
|
||||
context.updateCharacterBounds(e.rangeStart, []);
|
||||
},
|
||||
|
||||
textformatupdate(e: TextFormatUpdateEvent) {
|
||||
const formats = e.getTextFormats();
|
||||
|
||||
for (const format of formats) {
|
||||
console.log(format);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function updateControlBounds() {
|
||||
context.updateControlBounds(ref()!.getBoundingClientRect());
|
||||
}
|
||||
|
||||
function updateSelection(range: Range) {
|
||||
const [start, end] = toIndices(ref()!, range);
|
||||
|
||||
let index = 0;
|
||||
let mappedStart = -1;
|
||||
let mappedEnd = -1;
|
||||
|
||||
visit(ast(), n => n.type === 'text', (node) => {
|
||||
const { position, value } = node as Text;
|
||||
|
||||
if (position) {
|
||||
if (index <= start && (index + value.length) >= start) {
|
||||
mappedStart = position.start.offset! + range.startOffset;
|
||||
}
|
||||
|
||||
if (index <= end && (index + value.length) >= end) {
|
||||
mappedEnd = position.start.offset! + range.endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
index += value.length;
|
||||
});
|
||||
|
||||
context.updateSelection(mappedStart, mappedEnd);
|
||||
context.updateSelectionBounds(range.getBoundingClientRect());
|
||||
|
||||
setSelection([ref()!, start, end]);
|
||||
}
|
||||
|
||||
WindowEventListener({
|
||||
onresize() {
|
||||
updateControlBounds()
|
||||
},
|
||||
});
|
||||
|
||||
createEventListenerMap(() => ref()!, {
|
||||
keydown(e: KeyboardEvent) {
|
||||
// keyCode === 229 is a special code that indicates an IME event.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event#keydown_events_with_ime
|
||||
if (e.keyCode === 229) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = context.selectionStart;
|
||||
const end = context.selectionEnd;
|
||||
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
|
||||
context.updateText(start, end, '\t');
|
||||
// updateSelection(start + 1, start + 1);
|
||||
} else if (e.key === 'Enter') {
|
||||
context.updateText(start, end, '\n');
|
||||
|
||||
// updateSelection(start + 1, start + 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
DocumentEventListener({
|
||||
onSelectionchange(e) {
|
||||
const selection = document.getSelection()!;
|
||||
|
||||
if (selection.rangeCount < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.activeElement !== ref()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSelection(selection.getRangeAt(0)!);
|
||||
},
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
updateControlBounds();
|
||||
});
|
||||
|
||||
createEffect((last?: HTMLElement) => {
|
||||
if (last !== undefined) {
|
||||
last.editContext = undefined;
|
||||
}
|
||||
|
||||
const el = ref();
|
||||
|
||||
if (el === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.editContext = context;
|
||||
|
||||
return el;
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
context.updateText(0, 0, value());
|
||||
});
|
||||
|
||||
return [createMemo(() => store.text)];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElement {
|
||||
editContext?: EditContext;
|
||||
}
|
||||
|
||||
interface TextFormat {
|
||||
readonly rangeStart: number;
|
||||
readonly rangeEnd: number;
|
||||
readonly underlineStyle: 'none' | 'solid' | 'double' | 'dotted' | 'sadhed' | 'wavy';
|
||||
readonly underlineThickness: 'none' | 'thin' | 'thick';
|
||||
}
|
||||
|
||||
interface CharacterBoundsUpdateEvent extends Event {
|
||||
readonly rangeStart: number;
|
||||
readonly rangeEnd: number;
|
||||
}
|
||||
|
||||
interface TextFormatUpdateEvent extends Event {
|
||||
getTextFormats(): TextFormat[];
|
||||
}
|
||||
|
||||
interface TextUpdateEvent extends Event {
|
||||
readonly updateRangeStart: number;
|
||||
readonly updateRangeEnd: number;
|
||||
readonly text: string;
|
||||
readonly selectionStart: number;
|
||||
readonly selectionEnd: number;
|
||||
}
|
||||
|
||||
interface EditContextEventMap {
|
||||
characterboundsupdate: CharacterBoundsUpdateEvent;
|
||||
compositionstart: Event;
|
||||
compositionend: Event;
|
||||
textformatupdate: TextFormatUpdateEvent;
|
||||
textupdate: TextUpdateEvent;
|
||||
}
|
||||
|
||||
interface EditContext extends EventTarget {
|
||||
readonly text: string;
|
||||
readonly selectionStart: number;
|
||||
readonly selectionEnd: number;
|
||||
readonly characterBoundsRangeStart: number;
|
||||
|
||||
oncharacterboundsupdate?: (event: CharacterBoundsUpdateEvent) => any;
|
||||
oncompositionstart?: (event: Event) => any;
|
||||
oncompositionend?: (event: Event) => any;
|
||||
ontextformatupdate?: (event: TextFormatUpdateEvent) => any;
|
||||
ontextupdate?: (event: TextUpdateEvent) => any;
|
||||
|
||||
attachedElements(): [HTMLElement];
|
||||
characterBounds(): DOMRect[];
|
||||
updateText(rangeStart: number, rangeEnd: number, text: string): void;
|
||||
updateSelection(start: number, end: number): void;
|
||||
updateControlBounds(controlBounds: DOMRect): void;
|
||||
updateSelectionBounds(selectionBounds: DOMRect): void;
|
||||
updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void;
|
||||
|
||||
addEventListener<K extends keyof EditContextEventMap>(type: K, listener: (this: EditContext, ev: EditContextEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
}
|
||||
|
||||
interface EditContextConstructor {
|
||||
new(): EditContext;
|
||||
new(options: Partial<{ text: string, selectionStart: number, selectionEnd: number }>): EditContext;
|
||||
readonly prototype: EditContext;
|
||||
}
|
||||
|
||||
var EditContext: EditContextConstructor;
|
||||
}
|
||||
|
||||
const offsetOf = (node: Node, nodes: Node[]) => nodes.slice(0, nodes.indexOf(node)).reduce((t, n) => t + n.textContent!.length, 0);
|
||||
|
||||
const toRange = (root: Node, start: number, end: number): Range => {
|
||||
let index = 0;
|
||||
let startNode = null;
|
||||
let endNode = null;
|
||||
|
||||
for (const node of getTextNodes(root)) {
|
||||
const length = node.textContent!.length;
|
||||
|
||||
if (index <= start && (index + length) >= start) {
|
||||
startNode = [node, Math.abs(end - index)] as const;
|
||||
}
|
||||
|
||||
if (index <= end && (index + length) >= end) {
|
||||
endNode = [node, Math.abs(end - index)] as const;
|
||||
}
|
||||
|
||||
if (startNode !== null && endNode !== null) {
|
||||
break;
|
||||
}
|
||||
|
||||
index += length;
|
||||
}
|
||||
|
||||
const range = new Range();
|
||||
|
||||
if (startNode !== null) {
|
||||
range.setStart(...startNode);
|
||||
}
|
||||
|
||||
if (endNode !== null) {
|
||||
range.setEnd(...endNode);
|
||||
}
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
const toIndices = (node: Node, range: Range): [number, number] => {
|
||||
const nodes = getTextNodes(node);
|
||||
const start = offsetOf(range.startContainer, nodes) + range.startOffset;
|
||||
const end = offsetOf(range.endContainer, nodes) + range.endOffset;
|
||||
|
||||
return [start, end];
|
||||
};
|
3
src/features/edit-context/index.ts
Normal file
3
src/features/edit-context/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
export { createEditContext } from './context';
|
194
src/features/edit-context/tokenizer.ts
Normal file
194
src/features/edit-context/tokenizer.ts
Normal file
|
@ -0,0 +1,194 @@
|
|||
const WHITESPACE = [" ", "\n", "\t"];
|
||||
|
||||
function getOpenTagName(htmlString: string, pos: number) {
|
||||
let tagName = "";
|
||||
let char = htmlString.charAt(pos);
|
||||
|
||||
while (char !== ">" && char !== " " && char !== "/" && char !== "") {
|
||||
tagName += char;
|
||||
char = htmlString.charAt(++pos);
|
||||
}
|
||||
|
||||
return tagName;
|
||||
}
|
||||
|
||||
function getCloseTagName(htmlString: string, pos: number) {
|
||||
let tagName = "";
|
||||
let char = htmlString.charAt(pos);
|
||||
|
||||
while (char !== ">" && char !== "") {
|
||||
tagName += char;
|
||||
char = htmlString.charAt(++pos);
|
||||
}
|
||||
|
||||
return tagName;
|
||||
}
|
||||
|
||||
function getWhiteSpace(htmlString: string, pos: number) {
|
||||
let whitespace = "";
|
||||
let char = htmlString.charAt(pos);
|
||||
|
||||
while (WHITESPACE.includes(char) && char !== "") {
|
||||
whitespace += char;
|
||||
char = htmlString.charAt(++pos);
|
||||
}
|
||||
|
||||
return whitespace;
|
||||
}
|
||||
|
||||
function getAttributeName(htmlString: string, pos: number) {
|
||||
let attributeName = "";
|
||||
let char = htmlString.charAt(pos);
|
||||
|
||||
while (char !== "=" && char !== " " && char !== ">" && char !== "") {
|
||||
attributeName += char;
|
||||
char = htmlString.charAt(++pos);
|
||||
}
|
||||
|
||||
return attributeName;
|
||||
}
|
||||
|
||||
function getAttributeValue(htmlString: string, pos: number, quote: string) {
|
||||
let attributeValue = "";
|
||||
let char = htmlString.charAt(pos);
|
||||
|
||||
const isAtEnd = (c) => {
|
||||
if (quote) {
|
||||
return c === quote || c === "";
|
||||
}
|
||||
return c === " " || c === ">" || c === "/" || c === "";
|
||||
};
|
||||
|
||||
while (!isAtEnd(char)) {
|
||||
attributeValue += char;
|
||||
char = htmlString.charAt(++pos);
|
||||
}
|
||||
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
function getText(htmlString: string, pos: number) {
|
||||
let text = "";
|
||||
let char = htmlString.charAt(pos);
|
||||
|
||||
while (char !== "<" && char !== "") {
|
||||
text += char;
|
||||
char = htmlString.charAt(++pos);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
export function tokenizeHTML(htmlString: string) {
|
||||
let pos = 0;
|
||||
let isInTag = false;
|
||||
let isInAttribute = false;
|
||||
let isAfterAttributeEqual = false;
|
||||
|
||||
const tokens = [];
|
||||
|
||||
while (pos < htmlString.length) {
|
||||
const char = htmlString.charAt(pos);
|
||||
const nextChar = htmlString.charAt(pos + 1);
|
||||
|
||||
if (char === "<" && nextChar !== "/" && !isInTag && !isInAttribute) {
|
||||
isInTag = true;
|
||||
tokens.push({ type: "openTagStart", value: "<", pos });
|
||||
pos++;
|
||||
const tagName = getOpenTagName(htmlString, pos);
|
||||
tokens.push({ type: "tagName", value: tagName, pos });
|
||||
pos += tagName.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WHITESPACE.includes(char) && isInTag) {
|
||||
const whitespace = getWhiteSpace(htmlString, pos);
|
||||
tokens.push({ type: "whitespace", value: whitespace, pos });
|
||||
pos += whitespace.length;
|
||||
isInAttribute = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === ">" && isInTag && !isInAttribute) {
|
||||
isInTag = false;
|
||||
tokens.push({ type: "openTagEnd", value: ">", pos });
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isInTag && !isInAttribute && char === "/" && nextChar === ">") {
|
||||
isInTag = false;
|
||||
tokens.push({ type: "selfClose", value: "/>", pos });
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isInTag && !isInAttribute) {
|
||||
isInAttribute = true;
|
||||
const attributeName = getAttributeName(htmlString, pos);
|
||||
tokens.push({ type: "attributeName", value: attributeName, pos });
|
||||
pos += attributeName.length;
|
||||
|
||||
if (htmlString.charAt(pos) !== "=" && htmlString.charAt(pos) !== "'" && htmlString.charAt(pos) !== '"') {
|
||||
isInAttribute = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "=" && isInAttribute && isInTag) {
|
||||
isAfterAttributeEqual = true;
|
||||
tokens.push({ type: "equal", value: "=", pos });
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isAfterAttributeEqual && isInAttribute && isInTag) {
|
||||
const hasQuote = char === "'" || char === '"';
|
||||
const quote = hasQuote ? char : "";
|
||||
|
||||
if (hasQuote) {
|
||||
tokens.push({ type: "quoteStart", value: quote, pos });
|
||||
pos++;
|
||||
}
|
||||
|
||||
const attributeValue = getAttributeValue(htmlString, pos, quote);
|
||||
tokens.push({ type: "attributeValue", value: attributeValue, pos });
|
||||
pos += attributeValue.length;
|
||||
|
||||
if (hasQuote && htmlString.charAt(pos) === quote) {
|
||||
tokens.push({ type: "quoteEnd", value: quote, pos });
|
||||
pos++;
|
||||
}
|
||||
|
||||
isInAttribute = false;
|
||||
isAfterAttributeEqual = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isInTag && char === "<" && nextChar === "/") {
|
||||
tokens.push({ type: "closeTagStart", value: "</", pos });
|
||||
pos += 2;
|
||||
|
||||
const tagName = getCloseTagName(htmlString, pos);
|
||||
tokens.push({ type: "tagName", value: tagName, pos });
|
||||
pos += tagName.length;
|
||||
|
||||
if (htmlString.charAt(pos) === ">") {
|
||||
tokens.push({ type: "closeTagEnd", value: ">", pos });
|
||||
pos++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isInTag) {
|
||||
const text = getText(htmlString, pos);
|
||||
tokens.push({ type: "text", value: text, pos });
|
||||
pos += text.length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
import { Accessor, Component, createEffect, createMemo, createSignal, For, JSX, Show, untrack } from "solid-js";
|
||||
import { decode, Mutation } from "~/utilities";
|
||||
import { Mutation } from "~/utilities";
|
||||
import { Column, GridApi as GridCompApi, Grid as GridComp } from "~/components/grid";
|
||||
import { createDataSet, DataSetNode, DataSetRowNode } from "~/features/dataset";
|
||||
import { SelectionItem } from "../selectable";
|
||||
import { useI18n } from "../i18n";
|
||||
import { debounce } from "@solid-primitives/scheduled";
|
||||
import css from "./grid.module.css"
|
||||
import { Textarea } from "~/components/textarea";
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Accessor, children, Component, createContext, createEffect, createMemo, createResource, createSignal, For, InitializedResource, JSX, onCleanup, ParentComponent, Setter, Show, useContext } from "solid-js";
|
||||
import { Accessor, Component, createContext, createMemo, createResource, createSignal, For, JSX, onCleanup, ParentComponent, Setter, Show, useContext } from "solid-js";
|
||||
import { AiFillFile, AiFillFolder, AiFillFolderOpen } from "solid-icons/ai";
|
||||
import { SelectionProvider, selectable } from "~/features/selectable";
|
||||
import { debounce } from "@solid-primitives/scheduled";
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { createEffect, onMount } from "solid-js";
|
||||
import { Accessor, createEffect } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { unified } from 'unified'
|
||||
import { Text, Root } from 'hast'
|
||||
import { visit } from "unist-util-visit";
|
||||
import { decode } from "~/utilities";
|
||||
import remarkParse from 'remark-parse'
|
||||
import remarkRehype from 'remark-rehype'
|
||||
import remarkStringify from 'remark-stringify'
|
||||
import rehypeParse from 'rehype-parse'
|
||||
import rehypeDomParse from 'rehype-dom-parse'
|
||||
import rehypeRemark from 'rehype-remark'
|
||||
import rehypeStringify from 'rehype-stringify'
|
||||
import type { Text, Root } from 'hast'
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
interface SourceStore {
|
||||
in: string;
|
||||
|
@ -34,29 +36,12 @@ export interface Source {
|
|||
|
||||
// TODO :: make this configurable, right now we can only do markdown <--> html.
|
||||
const inToOutProcessor = unified().use(remarkParse).use(remarkRehype).use(rehypeStringify);
|
||||
const outToInProcessor = unified().use(rehypeParse).use(rehypeRemark).use(remarkStringify, { bullet: '-' });
|
||||
const outToInProcessor = unified().use(isServer ? rehypeParse : rehypeDomParse).use(rehypeRemark).use(remarkStringify, { bullet: '-' });
|
||||
|
||||
export function createSource(initalValue: string): Source {
|
||||
const ast = inToOutProcessor.runSync(inToOutProcessor.parse(initalValue));
|
||||
const out = String(inToOutProcessor.stringify(ast));
|
||||
const plain = String(unified().use(plainTextStringify).stringify(ast));
|
||||
export function createSource(value: Accessor<string>): Source {
|
||||
const [store, setStore] = createStore<SourceStore>({ in: '', out: '', plain: '', query: '', metadata: { spellingErrors: [], grammarErrors: [], queryResults: [] } });
|
||||
|
||||
const [store, setStore] = createStore<SourceStore>({ in: initalValue, out, plain, query: '', metadata: { spellingErrors: [], grammarErrors: [], queryResults: [] } });
|
||||
|
||||
createEffect(() => {
|
||||
const value = store.plain;
|
||||
|
||||
setStore('metadata', {
|
||||
spellingErrors: spellChecker(value, ''),
|
||||
grammarErrors: grammarChecker(value, ''),
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
setStore('metadata', 'queryResults', findMatches(store.plain, store.query).toArray());
|
||||
});
|
||||
|
||||
return {
|
||||
const src: Source = {
|
||||
get in() {
|
||||
return store.in;
|
||||
},
|
||||
|
@ -102,6 +87,26 @@ export function createSource(initalValue: string): Source {
|
|||
return store.metadata.queryResults;
|
||||
},
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
src.in = value();
|
||||
});
|
||||
src.in = value();
|
||||
|
||||
createEffect(() => {
|
||||
const value = store.plain;
|
||||
|
||||
setStore('metadata', {
|
||||
spellingErrors: spellChecker(value, ''),
|
||||
grammarErrors: grammarChecker(value, ''),
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
setStore('metadata', 'queryResults', findMatches(store.plain, store.query).toArray());
|
||||
});
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
function plainTextStringify() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Sidebar } from '~/components/sidebar';
|
||||
import { CellEditor, Column, DataSetGroupNode, DataSetNode, DataSetRowNode, Grid, GridApi } from '~/components/grid';
|
||||
import { people, Person } from './experimental.data';
|
||||
import { Component, createEffect, createMemo, createSignal, For, Match, Switch } from 'solid-js';
|
||||
import { Component, createEffect, createMemo, createSignal, For } from 'solid-js';
|
||||
import { MutarionKind, Mutation } from '~/utilities';
|
||||
import { Table } from '~/components/table';
|
||||
import { createDataSet } from '~/features/dataset';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue