Ver código fonte

enh: rich text input

Timothy Jaeryang Baek 5 meses atrás
pai
commit
aca06f92e8
4 arquivos alterados com 138 adições e 1 exclusões
  1. 58 0
      package-lock.json
  2. 1 0
      package.json
  3. 52 0
      src/app.css
  4. 27 1
      src/lib/components/common/RichTextInput.svelte

+ 58 - 0
package-lock.json

@@ -17,6 +17,7 @@
 				"@pyscript/core": "^0.4.32",
 				"@sveltejs/adapter-node": "^2.0.0",
 				"@tiptap/core": "^2.10.0",
+				"@tiptap/extension-code-block-lowlight": "^2.10.0",
 				"@tiptap/extension-highlight": "^2.10.0",
 				"@tiptap/extension-placeholder": "^2.10.0",
 				"@tiptap/extension-typography": "^2.10.0",
@@ -2425,6 +2426,23 @@
 				"@tiptap/pm": "^2.7.0"
 			}
 		},
+		"node_modules/@tiptap/extension-code-block-lowlight": {
+			"version": "2.10.0",
+			"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.10.0.tgz",
+			"integrity": "sha512-dAv03XIHT5h+sdFmJzvx2FfpfFOOK9SBKHflRUdqTa8eA+0VZNAcPRjvJWVEWqts1fKZDJj774mO28NlhFzk9Q==",
+			"license": "MIT",
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/ueberdosis"
+			},
+			"peerDependencies": {
+				"@tiptap/core": "^2.7.0",
+				"@tiptap/extension-code-block": "^2.7.0",
+				"@tiptap/pm": "^2.7.0",
+				"highlight.js": "^11",
+				"lowlight": "^2 || ^3"
+			}
+		},
 		"node_modules/@tiptap/extension-document": {
 			"version": "2.10.0",
 			"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.10.0.tgz",
@@ -2793,6 +2811,16 @@
 			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
 			"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
 		},
+		"node_modules/@types/hast": {
+			"version": "3.0.4",
+			"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+			"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+			"license": "MIT",
+			"peer": true,
+			"dependencies": {
+				"@types/unist": "*"
+			}
+		},
 		"node_modules/@types/json-schema": {
 			"version": "7.0.15",
 			"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -5162,6 +5190,20 @@
 			"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
 			"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="
 		},
+		"node_modules/devlop": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+			"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+			"license": "MIT",
+			"peer": true,
+			"dependencies": {
+				"dequal": "^2.0.0"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/wooorm"
+			}
+		},
 		"node_modules/didyoumean": {
 			"version": "1.2.2",
 			"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -7390,6 +7432,22 @@
 				"get-func-name": "^2.0.1"
 			}
 		},
+		"node_modules/lowlight": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.1.0.tgz",
+			"integrity": "sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ==",
+			"license": "MIT",
+			"peer": true,
+			"dependencies": {
+				"@types/hast": "^3.0.0",
+				"devlop": "^1.0.0",
+				"highlight.js": "~11.9.0"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/wooorm"
+			}
+		},
 		"node_modules/magic-string": {
 			"version": "0.30.11",
 			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",

+ 1 - 0
package.json

@@ -58,6 +58,7 @@
 		"@pyscript/core": "^0.4.32",
 		"@sveltejs/adapter-node": "^2.0.0",
 		"@tiptap/core": "^2.10.0",
+		"@tiptap/extension-code-block-lowlight": "^2.10.0",
 		"@tiptap/extension-highlight": "^2.10.0",
 		"@tiptap/extension-placeholder": "^2.10.0",
 		"@tiptap/extension-typography": "^2.10.0",

+ 52 - 0
src/app.css

@@ -230,3 +230,55 @@ input[type='number'] {
 
 	@apply dark:bg-gray-800 bg-gray-100;
 }
+
+/* Code styling */
+.hljs-comment,
+.hljs-quote {
+	color: #616161;
+}
+
+.hljs-variable,
+.hljs-template-variable,
+.hljs-attribute,
+.hljs-tag,
+.hljs-regexp,
+.hljs-link,
+.hljs-name,
+.hljs-selector-id,
+.hljs-selector-class {
+	color: #f98181;
+}
+
+.hljs-number,
+.hljs-meta,
+.hljs-built_in,
+.hljs-builtin-name,
+.hljs-literal,
+.hljs-type,
+.hljs-params {
+	color: #fbbc88;
+}
+
+.hljs-string,
+.hljs-symbol,
+.hljs-bullet {
+	color: #b9f18d;
+}
+
+.hljs-title,
+.hljs-section {
+	color: #faf594;
+}
+
+.hljs-keyword,
+.hljs-selector-tag {
+	color: #70cff8;
+}
+
+.hljs-emphasis {
+	font-style: italic;
+}
+
+.hljs-strong {
+	font-weight: 700;
+}

+ 27 - 1
src/lib/components/common/RichTextInput.svelte

@@ -11,13 +11,19 @@
 
 	import { Editor } from '@tiptap/core';
 
+	import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
 	import Placeholder from '@tiptap/extension-placeholder';
 	import Highlight from '@tiptap/extension-highlight';
 	import Typography from '@tiptap/extension-typography';
 	import StarterKit from '@tiptap/starter-kit';
 
+	import { all, createLowlight } from 'lowlight';
+
 	import { PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
 
+	// create a lowlight instance with all languages loaded
+	const lowlight = createLowlight(all);
+
 	export let className = 'input-prose';
 	export let placeholder = 'Type here...';
 	export let value = '';
@@ -109,7 +115,15 @@
 	onMount(() => {
 		editor = new Editor({
 			element: element,
-			extensions: [StarterKit, Highlight, Typography, Placeholder.configure({ placeholder })],
+			extensions: [
+				StarterKit,
+				CodeBlockLowlight.configure({
+					lowlight
+				}),
+				Highlight,
+				Typography,
+				Placeholder.configure({ placeholder })
+			],
 			content: marked.parse(value),
 			autofocus: true,
 			onTransaction: () => {
@@ -144,10 +158,22 @@
 						}
 
 						if (messageInput) {
+							if (event.key === 'Enter') {
+								// Check if the current selection is inside a code block
+								const { state } = view;
+								const { $head } = state.selection;
+								const isInCodeBlock = $head.parent.type.name === 'codeBlock';
+
+								if (isInCodeBlock) {
+									return false; // Prevent Enter action inside a code block
+								}
+							}
+
 							// Handle shift + Enter for a line break
 							if (shiftEnter) {
 								if (event.key === 'Enter' && event.shiftKey) {
 									editor.commands.setHardBreak(); // Insert a hard break
+									view.dispatch(view.state.tr.scrollIntoView()); // Move viewport to the cursor
 									event.preventDefault();
 									return true;
 								}