From aca06f92e8178d7610dd1c3485e5ad1259a2453a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 20 Nov 2024 22:56:26 -0800 Subject: [PATCH] enh: rich text input --- package-lock.json | 58 +++++++++++++++++++ package.json | 1 + src/app.css | 52 +++++++++++++++++ .../components/common/RichTextInput.svelte | 28 ++++++++- 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index f3feac815..4a0f1bb18 100644 --- a/package-lock.json +++ b/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", diff --git a/package.json b/package.json index cdc0ef578..ea934d4e7 100644 --- a/package.json +++ b/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", diff --git a/src/app.css b/src/app.css index cb211318d..3974974bc 100644 --- a/src/app.css +++ b/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; +} diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index 81543665c..fdc98349a 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/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; }