mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Migrate to remark
This commit is contained in:
181
frontend/package-lock.json
generated
181
frontend/package-lock.json
generated
@@ -25,10 +25,15 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-arborist": "^3.4.0",
|
"react-arborist": "^3.4.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-markdown": "^9.0.1",
|
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"remark-math": "^6.0.0"
|
"rehype-prism": "^2.3.3",
|
||||||
|
"rehype-react": "^8.0.0",
|
||||||
|
"remark-math": "^6.0.0",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-rehype": "^11.1.1",
|
||||||
|
"unified": "^11.0.5",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.67",
|
"@types/react": "^18.2.67",
|
||||||
@@ -2011,12 +2016,14 @@
|
|||||||
"version": "15.7.13",
|
"version": "15.7.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.9",
|
"version": "18.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz",
|
||||||
"integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==",
|
"integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -2103,6 +2110,12 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
@@ -2367,6 +2380,22 @@
|
|||||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/css-selector-parser": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/mdevils"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://patreon.com/mdevils"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -2863,9 +2892,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hast-util-to-jsx-runtime": {
|
"node_modules/hast-util-to-jsx-runtime": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz",
|
||||||
"integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==",
|
"integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "^1.0.0",
|
"@types/estree": "^1.0.0",
|
||||||
@@ -3001,16 +3030,6 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/html-url-attributes": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/unified"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||||
@@ -3914,6 +3933,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -4274,32 +4305,6 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/react-markdown": {
|
|
||||||
"version": "9.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
|
|
||||||
"integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/hast": "^3.0.0",
|
|
||||||
"devlop": "^1.0.0",
|
|
||||||
"hast-util-to-jsx-runtime": "^2.0.0",
|
|
||||||
"html-url-attributes": "^3.0.0",
|
|
||||||
"mdast-util-to-hast": "^13.0.0",
|
|
||||||
"remark-parse": "^11.0.0",
|
|
||||||
"remark-rehype": "^11.0.0",
|
|
||||||
"unified": "^11.0.0",
|
|
||||||
"unist-util-visit": "^5.0.0",
|
|
||||||
"vfile": "^6.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/unified"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": ">=18",
|
|
||||||
"react": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-number-format": {
|
"node_modules/react-number-format": {
|
||||||
"version": "5.4.2",
|
"version": "5.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.2.tgz",
|
||||||
@@ -4603,6 +4608,83 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-parse": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"hast-util-from-html": "^2.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-prism": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-prism/-/rehype-prism-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-J9mhio/CwcJRDyIhsp5hgXmyGeQsFN+/1eNEKnBRxfdJAx2CqH41kV0dqn/k2OgMdjk21IoGFgar0MfVtGYTSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hastscript": "^8.0.0",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
|
"rehype-parse": "^9.0.1",
|
||||||
|
"unist-util-is": "^6.0.0",
|
||||||
|
"unist-util-select": "^5.1.0",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"unified": "^10 || ^11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-prism/node_modules/hast-util-parse-selector": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-prism/node_modules/hastscript": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"comma-separated-tokens": "^2.0.0",
|
||||||
|
"hast-util-parse-selector": "^4.0.0",
|
||||||
|
"property-information": "^6.0.0",
|
||||||
|
"space-separated-tokens": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-react": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-react/-/rehype-react-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-vzo0YxYbB2HE+36+9HWXVdxNoNDubx63r5LBzpxBGVWM8s9mdnMdbmuJBAX6TTyuGdZjZix6qU3GcSuKCIWivw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"hast-util-to-jsx-runtime": "^2.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-math": {
|
"node_modules/remark-math": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
|
||||||
@@ -5085,6 +5167,23 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/unist-util-select": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-select/-/unist-util-select-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-4A5mfokSHG/rNQ4g7gSbdEs+H586xyd24sdJqF1IWamqrLHvYb+DH48fzxowyOhOfK7YSqX+XlCojAyuuyyT2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"css-selector-parser": "^3.0.0",
|
||||||
|
"devlop": "^1.1.0",
|
||||||
|
"nth-check": "^2.0.0",
|
||||||
|
"zwitch": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unist-util-stringify-position": {
|
"node_modules/unist-util-stringify-position": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
||||||
|
|||||||
@@ -39,10 +39,15 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-arborist": "^3.4.0",
|
"react-arborist": "^3.4.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-markdown": "^9.0.1",
|
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"remark-math": "^6.0.0"
|
"rehype-prism": "^2.3.3",
|
||||||
|
"rehype-react": "^8.0.0",
|
||||||
|
"remark-math": "^6.0.0",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-rehype": "^11.1.1",
|
||||||
|
"unified": "^11.0.5",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.67",
|
"@types/react": "^18.2.67",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const ContentView = ({
|
|||||||
content,
|
content,
|
||||||
handleContentChange,
|
handleContentChange,
|
||||||
handleSave,
|
handleSave,
|
||||||
handleLinkClick,
|
handleFileSelect,
|
||||||
}) => {
|
}) => {
|
||||||
if (!selectedFile) {
|
if (!selectedFile) {
|
||||||
return (
|
return (
|
||||||
@@ -47,7 +47,7 @@ const ContentView = ({
|
|||||||
selectedFile={selectedFile}
|
selectedFile={selectedFile}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<MarkdownPreview content={content} handleLinkClick={handleLinkClick} />
|
<MarkdownPreview content={content} handleFileSelect={handleFileSelect} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import { useWorkspace } from '../contexts/WorkspaceContext';
|
|||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const { currentWorkspace, loading: workspaceLoading } = useWorkspace();
|
const { currentWorkspace, loading: workspaceLoading } = useWorkspace();
|
||||||
const { selectedFile, handleFileSelect, handleLinkClick } =
|
const { selectedFile, handleFileSelect } = useFileNavigation();
|
||||||
useFileNavigation();
|
|
||||||
const { files, loadFileList } = useFileList();
|
const { files, loadFileList } = useFileList();
|
||||||
|
|
||||||
if (workspaceLoading) {
|
if (workspaceLoading) {
|
||||||
@@ -49,7 +48,6 @@ const Layout = () => {
|
|||||||
<MainContent
|
<MainContent
|
||||||
selectedFile={selectedFile}
|
selectedFile={selectedFile}
|
||||||
handleFileSelect={handleFileSelect}
|
handleFileSelect={handleFileSelect}
|
||||||
handleLinkClick={handleLinkClick}
|
|
||||||
loadFileList={loadFileList}
|
loadFileList={loadFileList}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ import { useFileOperations } from '../hooks/useFileOperations';
|
|||||||
import { useGitOperations } from '../hooks/useGitOperations';
|
import { useGitOperations } from '../hooks/useGitOperations';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const MainContent = ({
|
const MainContent = ({ selectedFile, handleFileSelect, loadFileList }) => {
|
||||||
selectedFile,
|
|
||||||
handleFileSelect,
|
|
||||||
handleLinkClick,
|
|
||||||
loadFileList,
|
|
||||||
}) => {
|
|
||||||
const [activeTab, setActiveTab] = useState('source');
|
const [activeTab, setActiveTab] = useState('source');
|
||||||
const { settings } = useWorkspace();
|
const { settings } = useWorkspace();
|
||||||
const {
|
const {
|
||||||
@@ -113,7 +108,7 @@ const MainContent = ({
|
|||||||
content={content}
|
content={content}
|
||||||
handleContentChange={handleContentChange}
|
handleContentChange={handleContentChange}
|
||||||
handleSave={handleSaveFile}
|
handleSave={handleSaveFile}
|
||||||
handleLinkClick={handleLinkClick}
|
handleFileSelect={handleFileSelect}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<CreateFileModal onCreateFile={handleCreateFile} />
|
<CreateFileModal onCreateFile={handleCreateFile} />
|
||||||
|
|||||||
@@ -1,159 +1,117 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import { unified } from 'unified';
|
||||||
|
import remarkParse from 'remark-parse';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
|
import remarkRehype from 'remark-rehype';
|
||||||
import rehypeKatex from 'rehype-katex';
|
import rehypeKatex from 'rehype-katex';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import rehypeReact from 'rehype-react';
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import rehypePrism from 'rehype-prism';
|
||||||
|
import * as prod from 'react/jsx-runtime';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
import { lookupFileByName } from '../services/api';
|
import { remarkWikiLinks } from '../utils/remarkWikiLinks';
|
||||||
|
import { useWorkspace } from '../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const MarkdownPreview = ({ content, handleLinkClick }) => {
|
const MarkdownPreview = ({ content, handleFileSelect }) => {
|
||||||
const [processedContent, setProcessedContent] = useState(content);
|
const [processedContent, setProcessedContent] = useState(null);
|
||||||
const baseUrl = window.API_BASE_URL;
|
const baseUrl = window.API_BASE_URL;
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
useEffect(() => {
|
const handleLinkClick = (e, href) => {
|
||||||
const processContent = async (rawContent) => {
|
e.preventDefault();
|
||||||
const regex = /(!?)\[\[(.*?)\]\]/g;
|
|
||||||
let result = rawContent;
|
|
||||||
const matches = [...rawContent.matchAll(regex)];
|
|
||||||
|
|
||||||
for (const match of matches) {
|
if (href.startsWith(`${baseUrl}/internal/`)) {
|
||||||
const [fullMatch, isImage, innerContent] = match;
|
// For existing files, extract the path and directly select it
|
||||||
let fileName, displayText, heading;
|
const [filePath, heading] = decodeURIComponent(
|
||||||
|
href.replace(`${baseUrl}/internal/`, '')
|
||||||
|
).split('#');
|
||||||
|
handleFileSelect(filePath);
|
||||||
|
|
||||||
// Parse the inner content
|
// TODO: Handle heading navigation if needed
|
||||||
const pipeIndex = innerContent.indexOf('|');
|
if (heading) {
|
||||||
const hashIndex = innerContent.indexOf('#');
|
console.debug('Heading navigation not implemented:', heading);
|
||||||
|
|
||||||
if (pipeIndex !== -1) {
|
|
||||||
displayText = innerContent.slice(pipeIndex + 1).trim();
|
|
||||||
fileName = innerContent.slice(0, pipeIndex).trim();
|
|
||||||
} else {
|
|
||||||
displayText = innerContent;
|
|
||||||
fileName = innerContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hashIndex !== -1 && (pipeIndex === -1 || hashIndex < pipeIndex)) {
|
|
||||||
heading = fileName.slice(hashIndex + 1).trim();
|
|
||||||
fileName = fileName.slice(0, hashIndex).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const paths = await lookupFileByName(fileName);
|
|
||||||
if (paths && paths.length > 0) {
|
|
||||||
const filePath = paths[0];
|
|
||||||
if (isImage) {
|
|
||||||
result = result.replace(
|
|
||||||
fullMatch,
|
|
||||||
``
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Include heading in the URL if present
|
|
||||||
const url = heading
|
|
||||||
? `${baseUrl}/internal/${encodeURIComponent(
|
|
||||||
filePath
|
|
||||||
)}#${encodeURIComponent(heading)}`
|
|
||||||
: `${baseUrl}/internal/${encodeURIComponent(filePath)}`;
|
|
||||||
result = result.replace(fullMatch, `[${displayText}](${url})`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = result.replace(
|
|
||||||
fullMatch,
|
|
||||||
`[${displayText}](${baseUrl}/notfound/${encodeURIComponent(
|
|
||||||
fileName
|
|
||||||
)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error looking up file:', error);
|
|
||||||
result = result.replace(
|
|
||||||
fullMatch,
|
|
||||||
`[${displayText}](${baseUrl}/notfound/${encodeURIComponent(
|
|
||||||
fileName
|
|
||||||
)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if (href.startsWith(`${baseUrl}/notfound/`)) {
|
||||||
return result;
|
// For non-existent files, show a notification
|
||||||
};
|
const fileName = decodeURIComponent(
|
||||||
|
href.replace(`${baseUrl}/notfound/`, '')
|
||||||
processContent(content).then(setProcessedContent);
|
);
|
||||||
}, [content, baseUrl]);
|
notifications.show({
|
||||||
|
title: 'File Not Found',
|
||||||
const handleImageError = (event) => {
|
message: `The file "${fileName}" does not exist.`,
|
||||||
console.error('Failed to load image:', event.target.src);
|
color: 'red',
|
||||||
event.target.alt = 'Failed to load image';
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const processor = useMemo(
|
||||||
<div className="markdown-preview">
|
() =>
|
||||||
<ReactMarkdown
|
unified()
|
||||||
remarkPlugins={[remarkMath]}
|
.use(remarkParse)
|
||||||
rehypePlugins={[rehypeKatex]}
|
.use(remarkWikiLinks, currentWorkspace?.id)
|
||||||
components={{
|
.use(remarkMath)
|
||||||
code({ node, inline, className, children, ...props }) {
|
.use(remarkRehype)
|
||||||
const match = /language-(\w+)/.exec(className || '');
|
.use(rehypeKatex)
|
||||||
return !inline && match ? (
|
.use(rehypePrism)
|
||||||
<SyntaxHighlighter
|
.use(rehypeReact, {
|
||||||
style={vscDarkPlus}
|
production: true,
|
||||||
language={match[1]}
|
jsx: prod.jsx,
|
||||||
PreTag="div"
|
jsxs: prod.jsxs,
|
||||||
|
Fragment: prod.Fragment,
|
||||||
|
components: {
|
||||||
|
img: ({ src, alt, ...props }) => (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
onError={(event) => {
|
||||||
|
console.error('Failed to load image:', event.target.src);
|
||||||
|
event.target.alt = 'Failed to load image';
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
a: ({ href, children, ...props }) => (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
onClick={(e) => handleLinkClick(e, href)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{String(children).replace(/\n$/, '')}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
) : (
|
|
||||||
<code className={className} {...props}>
|
|
||||||
{children}
|
{children}
|
||||||
</code>
|
</a>
|
||||||
);
|
),
|
||||||
},
|
code: ({ children, className, ...props }) => {
|
||||||
img: ({ src, alt, ...props }) => (
|
const language = className
|
||||||
<img src={src} alt={alt} onError={handleImageError} {...props} />
|
? className.replace('language-', '')
|
||||||
),
|
: null;
|
||||||
a: ({ href, children }) => {
|
|
||||||
if (href.startsWith(`${baseUrl}/internal/`)) {
|
|
||||||
const [filePath, heading] = decodeURIComponent(
|
|
||||||
href.replace(`${baseUrl}/internal/`, '')
|
|
||||||
).split('#');
|
|
||||||
return (
|
return (
|
||||||
<a
|
<pre className={className}>
|
||||||
href="#"
|
<code {...props}>{children}</code>
|
||||||
onClick={(e) => {
|
</pre>
|
||||||
e.preventDefault();
|
|
||||||
handleLinkClick(filePath, heading);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
);
|
);
|
||||||
} else if (href.startsWith(`${baseUrl}/notfound/`)) {
|
},
|
||||||
const fileName = decodeURIComponent(
|
|
||||||
href.replace(`${baseUrl}/notfound/`, '')
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
style={{ color: 'red', textDecoration: 'underline' }}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleLinkClick(fileName);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Regular markdown link
|
|
||||||
return <a href={href}>{children}</a>;
|
|
||||||
},
|
},
|
||||||
}}
|
}),
|
||||||
>
|
[baseUrl, handleFileSelect, currentWorkspace?.id]
|
||||||
{processedContent}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const processContent = async () => {
|
||||||
|
if (!currentWorkspace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await processor.process(content);
|
||||||
|
setProcessedContent(result.result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing markdown:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
processContent();
|
||||||
|
}, [content, processor, currentWorkspace]);
|
||||||
|
|
||||||
|
return <div className="markdown-preview">{processedContent}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MarkdownPreview;
|
export default MarkdownPreview;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { notifications } from '@mantine/notifications';
|
|
||||||
import { lookupFileByName } from '../services/api';
|
|
||||||
import { DEFAULT_FILE } from '../utils/constants';
|
import { DEFAULT_FILE } from '../utils/constants';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../contexts/WorkspaceContext';
|
||||||
import { useLastOpenedFile } from './useLastOpenedFile';
|
import { useLastOpenedFile } from './useLastOpenedFile';
|
||||||
@@ -24,33 +22,6 @@ export const useFileNavigation = () => {
|
|||||||
[saveLastOpenedFile]
|
[saveLastOpenedFile]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLinkClick = useCallback(
|
|
||||||
async (filename) => {
|
|
||||||
if (!currentWorkspace) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const filePaths = await lookupFileByName(currentWorkspace.id, filename);
|
|
||||||
if (filePaths.length >= 1) {
|
|
||||||
handleFileSelect(filePaths[0]);
|
|
||||||
} else {
|
|
||||||
notifications.show({
|
|
||||||
title: 'File Not Found',
|
|
||||||
message: `File "${filename}" not found`,
|
|
||||||
color: 'red',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error looking up file:', error);
|
|
||||||
notifications.show({
|
|
||||||
title: 'Error',
|
|
||||||
message: 'Failed to lookup file.',
|
|
||||||
color: 'red',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentWorkspace, handleFileSelect]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load last opened file when workspace changes
|
// Load last opened file when workspace changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeFile = async () => {
|
const initializeFile = async () => {
|
||||||
@@ -65,5 +36,5 @@ export const useFileNavigation = () => {
|
|||||||
initializeFile();
|
initializeFile();
|
||||||
}, [currentWorkspace, loadLastOpenedFile, handleFileSelect]);
|
}, [currentWorkspace, loadLastOpenedFile, handleFileSelect]);
|
||||||
|
|
||||||
return { handleLinkClick, selectedFile, isNewFile, handleFileSelect };
|
return { selectedFile, isNewFile, handleFileSelect };
|
||||||
};
|
};
|
||||||
|
|||||||
165
frontend/src/utils/remarkWikiLinks.js
Normal file
165
frontend/src/utils/remarkWikiLinks.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
import { lookupFileByName, getFileUrl } from '../services/api';
|
||||||
|
|
||||||
|
function createNotFoundLink(fileName, displayText, baseUrl) {
|
||||||
|
return {
|
||||||
|
type: 'link',
|
||||||
|
url: `${baseUrl}/notfound/${encodeURIComponent(fileName)}`,
|
||||||
|
children: [{ type: 'text', value: displayText }],
|
||||||
|
data: {
|
||||||
|
hProperties: { style: { color: 'red', textDecoration: 'underline' } },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFileLink(filePath, displayText, heading, baseUrl) {
|
||||||
|
const url = heading
|
||||||
|
? `${baseUrl}/internal/${encodeURIComponent(filePath)}#${encodeURIComponent(
|
||||||
|
heading
|
||||||
|
)}`
|
||||||
|
: `${baseUrl}/internal/${encodeURIComponent(filePath)}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'link',
|
||||||
|
url,
|
||||||
|
children: [{ type: 'text', value: displayText }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createImageNode(workspaceId, filePath, displayText) {
|
||||||
|
return {
|
||||||
|
type: 'image',
|
||||||
|
url: getFileUrl(workspaceId, filePath),
|
||||||
|
alt: displayText,
|
||||||
|
title: displayText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMarkdownExtension(fileName) {
|
||||||
|
if (fileName.includes('.')) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
return `${fileName}.md`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remarkWikiLinks(workspaceId) {
|
||||||
|
return async function transformer(tree) {
|
||||||
|
if (!workspaceId) {
|
||||||
|
console.warn('No workspace ID provided to remarkWikiLinks plugin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = window.API_BASE_URL;
|
||||||
|
const replacements = new Map();
|
||||||
|
|
||||||
|
// Find all wiki links
|
||||||
|
visit(tree, 'text', function (node) {
|
||||||
|
const regex = /(!?)\[\[(.*?)\]\]/g;
|
||||||
|
let match;
|
||||||
|
const matches = [];
|
||||||
|
|
||||||
|
while ((match = regex.exec(node.value)) !== null) {
|
||||||
|
const [fullMatch, isImage, innerContent] = match;
|
||||||
|
let fileName, displayText, heading;
|
||||||
|
|
||||||
|
// Parse the inner content
|
||||||
|
const pipeIndex = innerContent.indexOf('|');
|
||||||
|
const hashIndex = innerContent.indexOf('#');
|
||||||
|
|
||||||
|
if (pipeIndex !== -1) {
|
||||||
|
displayText = innerContent.slice(pipeIndex + 1).trim();
|
||||||
|
fileName = innerContent.slice(0, pipeIndex).trim();
|
||||||
|
} else {
|
||||||
|
displayText = innerContent;
|
||||||
|
fileName = innerContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashIndex !== -1 && (pipeIndex === -1 || hashIndex < pipeIndex)) {
|
||||||
|
heading = fileName.slice(hashIndex + 1).trim();
|
||||||
|
fileName = fileName.slice(0, hashIndex).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.push({
|
||||||
|
fullMatch,
|
||||||
|
isImage,
|
||||||
|
fileName,
|
||||||
|
displayText,
|
||||||
|
heading,
|
||||||
|
index: match.index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.length > 0) {
|
||||||
|
replacements.set(node, matches);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process all matches
|
||||||
|
for (const [node, matches] of replacements) {
|
||||||
|
const children = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
// Add text before the match
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
children.push({
|
||||||
|
type: 'text',
|
||||||
|
value: node.value.slice(lastIndex, match.index),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add .md extension for non-image files if they don't have an extension
|
||||||
|
const lookupFileName = match.isImage
|
||||||
|
? match.fileName
|
||||||
|
: addMarkdownExtension(match.fileName);
|
||||||
|
|
||||||
|
const paths = await lookupFileByName(workspaceId, lookupFileName);
|
||||||
|
|
||||||
|
if (paths && paths.length > 0) {
|
||||||
|
const filePath = paths[0];
|
||||||
|
if (match.isImage) {
|
||||||
|
children.push(
|
||||||
|
createImageNode(workspaceId, filePath, match.displayText)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
children.push(
|
||||||
|
createFileLink(
|
||||||
|
filePath,
|
||||||
|
match.displayText,
|
||||||
|
match.heading,
|
||||||
|
baseUrl
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
children.push(
|
||||||
|
createNotFoundLink(match.fileName, match.displayText, baseUrl)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Handle both 404s and other errors by creating a "not found" link
|
||||||
|
console.debug('File lookup failed:', match.fileName, error);
|
||||||
|
children.push(
|
||||||
|
createNotFoundLink(match.fileName, match.displayText, baseUrl)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex = match.index + match.fullMatch.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining text
|
||||||
|
if (lastIndex < node.value.length) {
|
||||||
|
children.push({
|
||||||
|
type: 'text',
|
||||||
|
value: node.value.slice(lastIndex),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the node with new children
|
||||||
|
node.type = 'paragraph';
|
||||||
|
node.children = children;
|
||||||
|
delete node.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user