-
Notifications
You must be signed in to change notification settings - Fork 26
Story: 2437 User Profile Edit UI #2478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 7 commits
1809380
9f15777
306830f
9968df6
3eb3584
af580a0
d39b1b1
20ec3b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,339 @@ | ||||||||||||||
| import { Editor } from "@tiptap/core"; | ||||||||||||||
| import StarterKit from "@tiptap/starter-kit"; | ||||||||||||||
| import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; | ||||||||||||||
| import Underline from "@tiptap/extension-underline"; | ||||||||||||||
| import Link from "@tiptap/extension-link"; | ||||||||||||||
| import Table from "@tiptap/extension-table"; | ||||||||||||||
| import TableRow from "@tiptap/extension-table-row"; | ||||||||||||||
| import TableCell from "@tiptap/extension-table-cell"; | ||||||||||||||
| import TableHeader from "@tiptap/extension-table-header"; | ||||||||||||||
| import Image from "@tiptap/extension-image"; | ||||||||||||||
| import TaskList from "@tiptap/extension-task-list"; | ||||||||||||||
| import TaskItem from "@tiptap/extension-task-item"; | ||||||||||||||
| import { common, createLowlight } from "lowlight"; | ||||||||||||||
| import { toHtml } from "hast-util-to-html"; | ||||||||||||||
| import { marked } from "marked"; | ||||||||||||||
| import DOMPurify from "dompurify"; | ||||||||||||||
| import TurndownService from "turndown"; | ||||||||||||||
| import { gfm } from "turndown-plugin-gfm"; | ||||||||||||||
| import {handleKeyDown, handlePaste, createToolbarButton, ICONS, setupMermaidEditMode, debounce} from './wysiwyg-editor'; | ||||||||||||||
|
|
||||||||||||||
| const lowlight = createLowlight(common); | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unused lowlight initialization. The 🧹 Proposed fix-const lowlight = createLowlight(common);
-
const editorInstances = new Map();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| const editorInstances = new Map(); | ||||||||||||||
|
|
||||||||||||||
| const buildToolbar = (editor, toolbarEl) => { | ||||||||||||||
| const left = document.createElement("div"); | ||||||||||||||
| left.className = "wysiwyg-toolbar__left"; | ||||||||||||||
| const right = document.createElement("div"); | ||||||||||||||
| right.className = "wysiwyg-toolbar__right"; | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| left.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Bold", title: "Bold", html: "<strong>B</strong>", | ||||||||||||||
| onClick: () => editor.chain().focus().toggleBold().run(), | ||||||||||||||
| isActive: () => editor.isActive("bold"), | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
| left.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Italic", title: "Italic", html: "<em>I</em>", | ||||||||||||||
| onClick: () => editor.chain().focus().toggleItalic().run(), | ||||||||||||||
| isActive: () => editor.isActive("italic"), | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
| left.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Underline", title: "Underline", html: "<u>U</u>", | ||||||||||||||
| onClick: () => editor.chain().focus().toggleUnderline().run(), | ||||||||||||||
| isActive: () => editor.isActive("underline"), | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| left.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Bullet list", title: "Bullet list", html: ICONS.bulletList, | ||||||||||||||
| onClick: () => editor.chain().focus().toggleBulletList().run(), | ||||||||||||||
| isActive: () => editor.isActive("bulletList"), | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
| left.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Ordered list", title: "Ordered list", html: ICONS.orderedList, | ||||||||||||||
| onClick: () => editor.chain().focus().toggleOrderedList().run(), | ||||||||||||||
| isActive: () => editor.isActive("orderedList"), | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| left.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Link", title: "Insert link", html: ICONS.link, | ||||||||||||||
| onClick: async () => { | ||||||||||||||
| const result = await openModal("Insert Link", [ | ||||||||||||||
| { name: "url", label: "URL", type: "url", placeholder: "https://example.com" }, | ||||||||||||||
| ]); | ||||||||||||||
| if (!result || !result.url) return; | ||||||||||||||
| if (!isSafeUrl(result.url)) { | ||||||||||||||
| window.alert("Only http, https, and mailto URLs are allowed."); | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
| editor.chain().focus().setLink({ href: result.url }).run(); | ||||||||||||||
| }, | ||||||||||||||
| isActive: () => editor.isActive("link"), | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
Comment on lines
+75
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify that openModal and isSafeUrl are not exported from wysiwyg-editor.js
echo "=== Checking if openModal is exported ==="
rg -n "export.*openModal" frontend/wysiwyg-editor.js || echo "Not found as export"
echo ""
echo "=== Checking if isSafeUrl is exported ==="
rg -n "export.*isSafeUrl" frontend/wysiwyg-editor.js || echo "Not found as export"
echo ""
echo "=== Checking where openModal is defined ==="
rg -n "^(const|function|export const|export function)\s+openModal" frontend/wysiwyg-editor.js
echo ""
echo "=== Checking where isSafeUrl is defined ==="
rg -n "^(const|function|export const|export function)\s+isSafeUrl" frontend/wysiwyg-editor.jsRepository: boostorg/website-v2 Length of output: 350 Export/import
Export 🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| const handleDocClick = (e) => { | ||||||||||||||
| if (!tableWrapper.contains(e.target)) gridPopup.style.display = "none"; | ||||||||||||||
| }; | ||||||||||||||
|
Comment on lines
+89
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove dead code referencing undefined variables. This 🗑️ Proposed fix });
- const handleDocClick = (e) => {
- if (!tableWrapper.contains(e.target)) gridPopup.style.display = "none";
- };
-
const mdBtn = document.createElement("button");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| const mdBtn = document.createElement("button"); | ||||||||||||||
| mdBtn.type = "button"; | ||||||||||||||
| mdBtn.className = "wysiwyg-toolbar__btn wysiwyg-toolbar__btn--md"; | ||||||||||||||
| mdBtn.setAttribute("aria-label", "Markdown"); | ||||||||||||||
| mdBtn.setAttribute("title", "Toggle Markdown mode"); | ||||||||||||||
| mdBtn.innerHTML = ICONS.markdown; | ||||||||||||||
| left.appendChild(mdBtn); | ||||||||||||||
|
|
||||||||||||||
| const previewBtn = document.createElement("button"); | ||||||||||||||
| previewBtn.type = "button"; | ||||||||||||||
| previewBtn.className = "wysiwyg-toolbar__btn wysiwyg-toolbar__btn--preview-toggle"; | ||||||||||||||
| previewBtn.setAttribute("aria-label", "Preview"); | ||||||||||||||
| previewBtn.setAttribute("title", "Toggle preview"); | ||||||||||||||
| previewBtn.innerHTML = ICONS.preview; | ||||||||||||||
| previewBtn.style.display = "none"; | ||||||||||||||
|
|
||||||||||||||
| right.appendChild(previewBtn); | ||||||||||||||
| right.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Undo", title: "Undo", html: "↶", | ||||||||||||||
| onClick: () => editor.chain().focus().undo().run(), | ||||||||||||||
| isActive: () => false, | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
| right.appendChild( | ||||||||||||||
| createToolbarButton(editor, { | ||||||||||||||
| label: "Redo", title: "Redo", html: "↷", | ||||||||||||||
| onClick: () => editor.chain().focus().redo().run(), | ||||||||||||||
| isActive: () => false, | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| toolbarEl.appendChild(left); | ||||||||||||||
| toolbarEl.appendChild(right); | ||||||||||||||
|
|
||||||||||||||
| return { mdBtn, previewBtn, handleDocClick }; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export const initWysiwyg = (textareaId) => { | ||||||||||||||
| const prev = editorInstances.get(textareaId); | ||||||||||||||
| if (prev) { | ||||||||||||||
| prev.editor.destroy(); | ||||||||||||||
| prev.cleanup(); | ||||||||||||||
| editorInstances.delete(textareaId); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const textarea = document.getElementById(textareaId); | ||||||||||||||
| if (!textarea) return null; | ||||||||||||||
| const wrapper = textarea.closest('[data-wysiwyg="v3"]'); | ||||||||||||||
| if (!wrapper) return null; | ||||||||||||||
|
|
||||||||||||||
| const toolbarEl = wrapper.querySelector(".wysiwyg-editor__toolbar"); | ||||||||||||||
| const editorEl = wrapper.querySelector(".wysiwyg-editor__body"); | ||||||||||||||
| if (!toolbarEl || !editorEl) return null; | ||||||||||||||
|
|
||||||||||||||
| /* Ensure toolbar is empty and remove any previous table-context bar after re-init (e.g. Fill demo content) to avoid duplicate bars */ | ||||||||||||||
| toolbarEl.innerHTML = ""; | ||||||||||||||
| wrapper.querySelectorAll(".wysiwyg-table-context").forEach((el) => el.remove()); | ||||||||||||||
|
|
||||||||||||||
| const rawContent = textarea.value ? textarea.value.trim() : ""; | ||||||||||||||
| const isHtml = rawContent.startsWith("<") && rawContent.includes(">"); | ||||||||||||||
| let initialContent = rawContent; | ||||||||||||||
| if (initialContent && !isHtml) { | ||||||||||||||
| try { | ||||||||||||||
| initialContent = parseMarkdownSafe(initialContent); | ||||||||||||||
| } catch (_) { | ||||||||||||||
| initialContent = rawContent; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+157
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if these required functions are exported from wysiwyg-editor.js
echo "=== Checking exports ==="
for func in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
echo "Checking $func:"
rg -n "export.*(const|function)\s+$func" frontend/wysiwyg-editor.js || echo " ❌ Not exported"
done
echo ""
echo "=== Where they are defined in wysiwyg-editor.js ==="
rg -n "^(const|function)\s+(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)" frontend/wysiwyg-editor.jsRepository: boostorg/website-v2 Length of output: 514 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== user-profile-wysiwyg-editor.js imports/exports near top ==="
sed -n '1,220p' frontend/user-profile-wysiwyg-editor.js | nl -ba | sed -n '1,220p'
echo ""
echo "=== user-profile-wysiwyg-editor.js: locate references ==="
rg -n "\bparseMarkdownSafe\b|\bhighlightPreviewCodeBlocks\b|\brenderMermaidPreview\b|\bturndown\b" frontend/user-profile-wysiwyg-editor.js -n
echo ""
echo "=== user-profile-wysiwyg-editor.js: check if identifiers are defined locally ==="
rg -n "function\s+parseMarkdownSafe|const\s+parseMarkdownSafe|let\s+parseMarkdownSafe|var\s+parseMarkdownSafe" frontend/user-profile-wysiwyg-editor.js || true
rg -n "function\s+highlightPreviewCodeBlocks|const\s+highlightPreviewCodeBlocks|let\s+highlightPreviewCodeBlocks|var\s+highlightPreviewCodeBlocks" frontend/user-profile-wysiwyg-editor.js || true
rg -n "function\s+renderMermaidPreview|const\s+renderMermaidPreview|let\s+renderMermaidPreview|var\s+renderMermaidPreview" frontend/user-profile-wysiwyg-editor.js || true
rg -n "const\s+turndown|let\s+turndown|var\s+turndown" frontend/user-profile-wysiwyg-editor.js || true
echo ""
echo "=== wysiwyg-editor.js: check exports mechanism ==="
rg -n "export\s+(default\s+)?|module\.exports|exports\." frontend/wysiwyg-editor.js
echo ""
echo "=== wysiwyg-editor.js: show definitions for referenced identifiers ==="
rg -n "function\s+parseMarkdownSafe|const\s+turndown\s*=|const\s+highlightPreviewCodeBlocks\s*=|const\s+renderMermaidPreview\s*=" frontend/wysiwyg-editor.js -n
echo ""
echo "=== wysiwyg-editor.js: show surrounding code for the definitions ==="
for line in 35 53 655 669; do
echo "--- context around line $line ---"
sed -n "$((line-5)),$((line+20))p" frontend/wysiwyg-editor.js | nl -ba | sed -n '1,80p'
doneRepository: boostorg/website-v2 Length of output: 188 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== user-profile-wysiwyg-editor.js: show first ~240 lines with line numbers ==="
awk '{printf "%d:%s\n", NR, $0}' frontend/user-profile-wysiwyg-editor.js | sed -n '1,240p'
echo ""
echo "=== user-profile-wysiwyg-editor.js: locate references to the identifiers ==="
rg -n "\bparseMarkdownSafe\b|\bhighlightPreviewCodeBlocks\b|\brenderMermaidPreview\b|\bturndown\b" frontend/user-profile-wysiwyg-editor.js
echo ""
echo "=== user-profile-wysiwyg-editor.js: locate any imports for wysiwyg-editor.js ==="
rg -n "from\s+['\"].*wysiwyg-editor['\"]|require\(['\"].*wysiwyg-editor['\"]\)" frontend/user-profile-wysiwyg-editor.js || true
echo ""
echo "=== user-profile-wysiwyg-editor.js: check whether any of these identifiers are declared locally ==="
rg -n "(function|const|let|var)\s+parseMarkdownSafe\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+highlightPreviewCodeBlocks\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+renderMermaidPreview\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(const|let|var)\s+turndown\b" frontend/user-profile-wysiwyg-editor.js || true
echo ""
echo "=== wysiwyg-editor.js: check how these identifiers are declared and exported ==="
rg -n "function\s+parseMarkdownSafe|const\s+turndown\s*=|const\s+highlightPreviewCodeBlocks\s*=|const\s+renderMermaidPreview\s*=" frontend/wysiwyg-editor.js
echo ""
echo "=== wysiwyg-editor.js: check for exports of these identifiers ==="
rg -n "export\s*\{[^}]*\b(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)\b[^}]*\}|export\s+(const|function)\s+(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)\b|module\.exports|exports\." frontend/wysiwyg-editor.js || true
echo ""
echo "=== wysiwyg-editor.js: show context around each definition/export area ==="
for start in 1 20 40 620 640 650 660 670; do
:
done
for line in 35 53 655 669; do
echo "--- context around wysiwyg-editor.js:$line ---"
awk -v s="$((line-10))" -v e="$((line+25))" 'NR>=s && NR<=e {printf "%d:%s\n", NR, $0}' frontend/wysiwyg-editor.js
doneRepository: boostorg/website-v2 Length of output: 15757 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== user-profile-wysiwyg-editor.js: check for any local declarations (whole file) ==="
rg -n "(function|const|let|var)\s+parseMarkdownSafe\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+highlightPreviewCodeBlocks\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+renderMermaidPreview\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(const|let|var)\s+turndown\b" frontend/user-profile-wysiwyg-editor.js || true
echo ""
echo "=== user-profile-wysiwyg-editor.js: show context around reported usages (150-220) ==="
awk 'NR>=140 && NR<=220 {printf "%d:%s\n", NR, $0}' frontend/user-profile-wysiwyg-editor.js
echo ""
echo "=== user-profile-wysiwyg-editor.js: show context around reported usages (230-320) ==="
awk 'NR>=220 && NR<=340 {printf "%d:%s\n", NR, $0}' frontend/user-profile-wysiwyg-editor.js
echo ""
echo "=== wysiwyg-editor.js: show exports section (if any) ==="
rg -n "export\s*\{|export\s+default|module\.exports|exports\." frontend/wysiwyg-editor.js || trueRepository: boostorg/website-v2 Length of output: 8029 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Search for global assignments of these identifiers ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
echo "--- $sym ---"
rg -n "globalThis\.${sym}|window\.${sym}|global\.\${sym}|self\.${sym}" -S . || true
done
echo ""
echo "=== Search for re-exports from wysiwyg-editor.js ==="
rg -n "wysiwyg-editor['\"]" frontend -S || true
rg -n "export\s*\{[^}]*parseMarkdownSafe|export\s*\{[^}]*highlightPreviewCodeBlocks|export\s*\{[^}]*renderMermaidPreview|export\s*\{[^}]*turndown" frontend/wysiwyg-editor.js -S || true
echo ""
echo "=== Search for any import specifiers of parseMarkdownSafe etc from wysiwyg-editor ==="
rg -n "from\s+['\"].*wysiwyg-editor['\"]" frontend/user-profile-wysiwyg-editor.js -n
rg -n "parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown" frontend/user-profile-wysiwyg-editor.js -nRepository: boostorg/website-v2 Length of output: 2403 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Check for any ES-module exports of these symbols in wysiwyg-editor.js
echo "=== wysiwyg-editor.js: export statements referencing these symbols ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
echo "--- $sym ---"
rg -n "export\s+(const|function)\s+$sym\b" frontend/wysiwyg-editor.js || true
rg -n "export\s*\{[^}]*\b$sym\b[^}]*\}" frontend/wysiwyg-editor.js || true
rg -n "\bexport\s*\*\s*from\s*['\"][^'\"]*['\"]" frontend/wysiwyg-editor.js || true
done
echo ""
echo "=== wysiwyg-editor.js: all occurrences of 'export' (context) ==="
rg -n "\bexport\b" frontend/wysiwyg-editor.js
echo ""
# 2) Check for global/window assignments in frontend for these symbols
echo "=== frontend/: global assignments for these symbols ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
echo "--- $sym ---"
rg -n "globalThis\.${sym}\b|window\.${sym}\b|global\.${sym}\b|self\.${sym}\b" frontend -S || true
done
echo ""
echo "=== user-profile-wysiwyg-editor.js: any imports of these symbols from wysiwyg-editor ==="
rg -n "from\s+['\"]\./wysiwyg-editor['\"]" frontend/user-profile-wysiwyg-editor.js
rg -n "import\s*\{[^}]*\b(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)\b[^}]*\}\s*from\s+['\"]\./wysiwyg-editor['\"]" frontend/user-profile-wysiwyg-editor.js || true
echo ""
echo "=== user-profile-wysiwyg-editor.js: whether these symbols are declared locally ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
rg -n "(function|const|let|var)\s+$sym\b" frontend/user-profile-wysiwyg-editor.js || true
doneRepository: boostorg/website-v2 Length of output: 1010 Fix undefined identifiers in
Snippet at issue location (final state): initialContent = parseMarkdownSafe(initialContent);
} catch (_) {
initialContent = rawContent;
}Impacted identifiers/usages:
Solution: Export 🤖 Prompt for AI Agents |
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const editorRef = { current: null }; | ||||||||||||||
| const editor = new Editor({ | ||||||||||||||
| element: editorEl, | ||||||||||||||
| extensions: [ | ||||||||||||||
| StarterKit.configure({ codeBlock: false }), | ||||||||||||||
| Underline, | ||||||||||||||
| TaskList, | ||||||||||||||
| TaskItem.configure({ nested: true }), | ||||||||||||||
| ], | ||||||||||||||
| content: initialContent, | ||||||||||||||
| editorProps: { | ||||||||||||||
| attributes: { | ||||||||||||||
| class: "wysiwyg-editor__prose", | ||||||||||||||
| }, | ||||||||||||||
| handleKeyDown(view, event) { | ||||||||||||||
| if (event.key === "Tab") { | ||||||||||||||
| const { $from } = view.state.selection; | ||||||||||||||
| if ($from.parent.type.name === "codeBlock") { | ||||||||||||||
| event.preventDefault(); | ||||||||||||||
| editorRef.current?.chain().focus().insertContent("\t").run(); | ||||||||||||||
| return true; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| return false; | ||||||||||||||
| }, | ||||||||||||||
| handlePaste(_view, event) { | ||||||||||||||
| const pastedText = event.clipboardData?.getData("text/plain") || ""; | ||||||||||||||
| if (!pastedText.trim() || !editorRef.current) return false; | ||||||||||||||
| const trimmed = pastedText.trim(); | ||||||||||||||
| const looksLikeMarkdown = | ||||||||||||||
| (!trimmed.startsWith("<") && | ||||||||||||||
| (/^#|^\*\*|^\- |^\d+\. |^`|^\[|^>|^\||^\- \[ \]|^\- \[x\]/i.test(trimmed) || | ||||||||||||||
| /\n```|\n#{1,6}\s|\n\*\*|\n\- |\n\d+\. |\n\|---|\n\- \[ \]/.test(pastedText))); | ||||||||||||||
| if (looksLikeMarkdown) { | ||||||||||||||
| try { | ||||||||||||||
| event.preventDefault(); | ||||||||||||||
| const html = parseMarkdownSafe(pastedText); | ||||||||||||||
| editorRef.current.chain().focus().insertContent(html).run(); | ||||||||||||||
| return true; | ||||||||||||||
| } catch (_) { | ||||||||||||||
| return false; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| return false; | ||||||||||||||
| }, | ||||||||||||||
| }, | ||||||||||||||
| }); | ||||||||||||||
| editorRef.current = editor; | ||||||||||||||
|
|
||||||||||||||
| const state = { mode: "wysiwyg", markdownText: "", previewOn: false }; | ||||||||||||||
|
|
||||||||||||||
| const { mdBtn, previewBtn, handleDocClick } = buildToolbar(editor, toolbarEl); | ||||||||||||||
| setupMermaidEditMode(editor, editorEl); | ||||||||||||||
|
|
||||||||||||||
| const markdownPane = document.createElement("div"); | ||||||||||||||
| markdownPane.className = "wysiwyg-editor__markdown-pane"; | ||||||||||||||
| markdownPane.style.display = "none"; | ||||||||||||||
|
|
||||||||||||||
| const mdTextarea = document.createElement("textarea"); | ||||||||||||||
| mdTextarea.className = "wysiwyg-markdown__textarea"; | ||||||||||||||
| mdTextarea.setAttribute("aria-label", "Markdown source"); | ||||||||||||||
| mdTextarea.setAttribute("placeholder", "Write markdown here..."); | ||||||||||||||
|
|
||||||||||||||
| const mdPreview = document.createElement("div"); | ||||||||||||||
| mdPreview.className = "wysiwyg-markdown__preview wysiwyg-editor__prose"; | ||||||||||||||
|
|
||||||||||||||
| markdownPane.appendChild(mdTextarea); | ||||||||||||||
| markdownPane.appendChild(mdPreview); | ||||||||||||||
| editorEl.after(markdownPane); | ||||||||||||||
|
|
||||||||||||||
| const previewEl = document.createElement("div"); | ||||||||||||||
| previewEl.className = "wysiwyg-editor__preview wysiwyg-editor__prose"; | ||||||||||||||
| previewEl.style.display = "none"; | ||||||||||||||
| markdownPane.after(previewEl); | ||||||||||||||
|
|
||||||||||||||
| const updateMdPreview = () => { | ||||||||||||||
| mdPreview.innerHTML = parseMarkdownSafe(state.markdownText); | ||||||||||||||
| highlightPreviewCodeBlocks(mdPreview); | ||||||||||||||
| renderMermaidPreview(mdPreview); | ||||||||||||||
| }; | ||||||||||||||
| const debouncedMdPreview = debounce(updateMdPreview, 300); | ||||||||||||||
|
|
||||||||||||||
| mdTextarea.addEventListener("input", () => { | ||||||||||||||
| state.markdownText = mdTextarea.value; | ||||||||||||||
| debouncedMdPreview(); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| mdBtn.addEventListener("click", (e) => { | ||||||||||||||
| e.preventDefault(); | ||||||||||||||
| if (state.mode === "wysiwyg") { | ||||||||||||||
| state.markdownText = turndown.turndown(editor.getHTML()); | ||||||||||||||
| state.mode = "markdown"; | ||||||||||||||
| state.previewOn = false; | ||||||||||||||
| mdBtn.classList.add("wysiwyg-toolbar__btn--active"); | ||||||||||||||
| toolbarEl.classList.add("wysiwyg-editor__toolbar--markdown"); | ||||||||||||||
| editorEl.style.display = "none"; | ||||||||||||||
| markdownPane.style.display = ""; | ||||||||||||||
| previewEl.style.display = "none"; | ||||||||||||||
| previewBtn.style.display = ""; | ||||||||||||||
| previewBtn.classList.remove("wysiwyg-toolbar__btn--active"); | ||||||||||||||
| mdTextarea.value = state.markdownText; | ||||||||||||||
| updateMdPreview(); | ||||||||||||||
| mdTextarea.focus(); | ||||||||||||||
| } else { | ||||||||||||||
| editor.commands.setContent(parseMarkdownSafe(state.markdownText)); | ||||||||||||||
| state.mode = "wysiwyg"; | ||||||||||||||
| state.previewOn = false; | ||||||||||||||
| mdBtn.classList.remove("wysiwyg-toolbar__btn--active"); | ||||||||||||||
| toolbarEl.classList.remove("wysiwyg-editor__toolbar--markdown"); | ||||||||||||||
| editorEl.style.display = ""; | ||||||||||||||
| markdownPane.style.display = "none"; | ||||||||||||||
| previewEl.style.display = "none"; | ||||||||||||||
| previewBtn.style.display = "none"; | ||||||||||||||
| previewBtn.classList.remove("wysiwyg-toolbar__btn--active"); | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| previewBtn.addEventListener("click", (e) => { | ||||||||||||||
| e.preventDefault(); | ||||||||||||||
| state.previewOn = !state.previewOn; | ||||||||||||||
| previewBtn.classList.toggle("wysiwyg-toolbar__btn--active", state.previewOn); | ||||||||||||||
| if (state.previewOn) { | ||||||||||||||
| markdownPane.style.display = "none"; | ||||||||||||||
| previewEl.style.display = ""; | ||||||||||||||
| previewEl.innerHTML = parseMarkdownSafe(state.markdownText); | ||||||||||||||
| highlightPreviewCodeBlocks(previewEl); | ||||||||||||||
| renderMermaidPreview(previewEl); | ||||||||||||||
| } else { | ||||||||||||||
| markdownPane.style.display = ""; | ||||||||||||||
| previewEl.style.display = "none"; | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| textarea.style.position = "absolute"; | ||||||||||||||
| textarea.style.left = "-9999px"; | ||||||||||||||
| textarea.style.width = "1px"; | ||||||||||||||
| textarea.style.height = "1px"; | ||||||||||||||
| textarea.setAttribute("aria-hidden", "true"); | ||||||||||||||
| textarea.tabIndex = -1; | ||||||||||||||
|
|
||||||||||||||
| const form = wrapper.closest("form"); | ||||||||||||||
| const syncTextarea = () => { | ||||||||||||||
| if (state.mode === "markdown") { | ||||||||||||||
| textarea.value = state.markdownText; | ||||||||||||||
| } else { | ||||||||||||||
| textarea.value = turndown.turndown(editor.getHTML()); | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
| if (form) { | ||||||||||||||
| form.addEventListener("submit", syncTextarea, true); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| editorInstances.set(textareaId, { | ||||||||||||||
| editor, | ||||||||||||||
| cleanup: () => { | ||||||||||||||
| document.removeEventListener("click", handleDocClick); | ||||||||||||||
| if (form) form.removeEventListener("submit", syncTextarea, true); | ||||||||||||||
| }, | ||||||||||||||
| }); | ||||||||||||||
| return editor; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| const autoInit = () => { | ||||||||||||||
| if (typeof document === "undefined" || !document.querySelector) return; | ||||||||||||||
| document.querySelectorAll('[data-wysiwyg="v3"]').forEach((wrapper) => { | ||||||||||||||
| const ta = wrapper.querySelector("textarea[id]"); | ||||||||||||||
| if (ta && ta.id) initWysiwyg(ta.id); | ||||||||||||||
| }); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| if (typeof document !== "undefined") { | ||||||||||||||
| if (document.readyState === "loading") { | ||||||||||||||
| document.addEventListener("DOMContentLoaded", autoInit); | ||||||||||||||
| } else { | ||||||||||||||
| autoInit(); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heya! Is there any specific reason we need to duplicate the wysiwyg component here?
I also noticed there are a lot of imports not being used and a few function calls/refs that don't seem to exist in the scope of the script (e.g.
isSafeUrl,parseMarkdownSafeand others..).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this was the best solution I found for the requirements of limiting the features on the wysiwig. I'm not super happy with this solution, but it is the best one I discovered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing, no problem with that specifically. My comment is more related to the unused imports and the missing function references. Did you have the chance to take a look at those?