From e89d2c87f07ffcf698c25c0c563b70f2844d62f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Blomberg?= Date: Mon, 13 Apr 2026 15:14:46 +0200 Subject: [PATCH] feat: add syntax highlighting and enhance document editing features - Import GitHub dark theme for highlight.js in main.ts - Pass slug prop to VisualEditor in DocumentView.vue for version and normal editing modes - Update Vite configuration to increase maximum cache size and add media proxy - Add new dependencies for Tiptap extensions and highlight.js in package.json and package-lock.json --- ARCHITECTURE.md | 2 +- frontend/package-lock.json | 153 +++++ frontend/package.json | 8 + frontend/src/bridge/asciidoc-bridge.ts | 83 ++- frontend/src/bridge/asciidoc-extensions.ts | 50 +- .../src/components/editor/ImageEditDialog.vue | 98 +++ .../components/editor/ImagePickerDialog.vue | 211 +++++++ .../src/components/editor/VisualEditor.vue | 217 ++++++- frontend/src/main.ts | 1 + frontend/src/views/DocumentView.vue | 3 +- frontend/vite.config.ts | 2 + package-lock.json | 558 +++++++++++++++++- package.json | 9 +- 13 files changed, 1380 insertions(+), 15 deletions(-) create mode 100644 frontend/src/components/editor/ImageEditDialog.vue create mode 100644 frontend/src/components/editor/ImagePickerDialog.vue diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ef758be..643c226 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -40,7 +40,7 @@ Webbläsare | Frontend | Vue 3 (Composition API), Vite, TypeScript, Tailwind CSS | | State | Pinia | | Routing | Vue Router 4 | -| Editor | TipTap 2 (visuell), CodeMirror 6 (AsciiDoc-källa) | +| Editor | TipTap 2 (visuell, med custom image extension), CodeMirror 6 (AsciiDoc-källa) | | AsciiDoc | Asciidoctor.js (rendering i webbläsaren) | | PWA | vite-plugin-pwa (Service Worker, manifest) | | Backend | Go 1.22, HTTP-standardbibliotek | diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e2255f6..44f5ba4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@tiptap/extension-bullet-list": "^2.27.2", "@tiptap/extension-code": "^2.27.2", "@tiptap/extension-code-block": "^2.27.2", + "@tiptap/extension-code-block-lowlight": "^2.27.2", "@tiptap/extension-document": "^2.27.2", "@tiptap/extension-heading": "^2.27.2", "@tiptap/extension-horizontal-rule": "^2.27.2", @@ -24,6 +25,10 @@ "@tiptap/extension-ordered-list": "^2.27.2", "@tiptap/extension-paragraph": "^2.27.2", "@tiptap/extension-strike": "^2.27.2", + "@tiptap/extension-table": "^2.27.2", + "@tiptap/extension-table-cell": "^2.27.2", + "@tiptap/extension-table-header": "^2.27.2", + "@tiptap/extension-table-row": "^2.27.2", "@tiptap/extension-text": "^2.27.2", "@tiptap/html": "^2.27.2", "@tiptap/pm": "^2.4.0", @@ -33,6 +38,8 @@ "codemirror": "^6.0.1", "graphql": "^16.9.0", "graphql-request": "^6.1.0", + "highlight.js": "^11.11.1", + "lowlight": "^3.3.0", "pinia": "^2.1.7", "vue": "^3.4.0", "vue-router": "^4.3.0" @@ -40,6 +47,7 @@ "devDependencies": { "@codemirror/lang-markdown": "^6.2.5", "@codemirror/theme-one-dark": "^6.1.2", + "@tiptap/extension-image": "^2.27.2", "@types/node": "^20.14.0", "@vitejs/plugin-vue": "^5.0.5", "autoprefixer": "^10.4.19", @@ -2978,6 +2986,23 @@ "@tiptap/pm": "^2.7.0" } }, + "node_modules/@tiptap/extension-code-block-lowlight": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.27.2.tgz", + "integrity": "sha512-v6NKStBbQ/XCc1NnCi3ObsL1DsxadSIBtUQNA/B+urkPgn5LEy72HAGlf0xwjRaNkAGSaTASLKmc84L5q5zlGQ==", + "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.27.2", "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.27.2.tgz", @@ -3090,6 +3115,20 @@ "@tiptap/pm": "^2.7.0" } }, + "node_modules/@tiptap/extension-image": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.27.2.tgz", + "integrity": "sha512-5zL/BY41FIt72azVrCrv3n+2YJ/JyO8wxCcA4Dk1eXIobcgVyIdo4rG39gCqIOiqziAsqnqoj12QHTBtHsJ6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, "node_modules/@tiptap/extension-italic": { "version": "2.27.2", "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.27.2.tgz", @@ -3172,6 +3211,59 @@ "@tiptap/core": "^2.7.0" } }, + "node_modules/@tiptap/extension-table": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.27.2.tgz", + "integrity": "sha512-pDbhOpT5phZkcsyPjGBQlXv0+0hmdrvqHJ+dJjkGcCtlfy2pHiEIhmIItOFagc7wXy8G9iUFZ9Jie4zvDf+brg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-table-cell": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.27.2.tgz", + "integrity": "sha512-9Lk46MjZMFzVZfOj9Kd7VgC6Odt6vmEhlCYVumErShUY7EkFqCw3b2IYoUtQkntfOEx/Afnhff/okNQwPsJeUA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-table-header": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.27.2.tgz", + "integrity": "sha512-ZEb6lbG0NbbodWLV0b4BS/QrDIPlUbCcuOsUxzqVvlMUY1Vg6Fj6fKwLaBcsIUDHi8sxZDBEgYEDw3BR/zcO6A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-table-row": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.27.2.tgz", + "integrity": "sha512-Nw9+tA56Y5HtLVP01NGCZSUuTQhJPtfK9OfmDgGgcxynn2cRVdEtj+9FNZqRhQ1iRVaAI+Rd4xRvX9qYePMOxw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, "node_modules/@tiptap/extension-text": { "version": "2.27.2", "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.27.2.tgz", @@ -3304,6 +3396,15 @@ "dev": true, "license": "MIT" }, + "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", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -3350,6 +3451,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -4371,6 +4478,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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", + "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", @@ -5157,6 +5286,15 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/idb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", @@ -5859,6 +5997,21 @@ "dev": true, "license": "MIT" }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index e78da73..0c66e14 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "@tiptap/extension-bullet-list": "^2.27.2", "@tiptap/extension-code": "^2.27.2", "@tiptap/extension-code-block": "^2.27.2", + "@tiptap/extension-code-block-lowlight": "^2.27.2", "@tiptap/extension-document": "^2.27.2", "@tiptap/extension-heading": "^2.27.2", "@tiptap/extension-horizontal-rule": "^2.27.2", @@ -25,6 +26,10 @@ "@tiptap/extension-ordered-list": "^2.27.2", "@tiptap/extension-paragraph": "^2.27.2", "@tiptap/extension-strike": "^2.27.2", + "@tiptap/extension-table": "^2.27.2", + "@tiptap/extension-table-cell": "^2.27.2", + "@tiptap/extension-table-header": "^2.27.2", + "@tiptap/extension-table-row": "^2.27.2", "@tiptap/extension-text": "^2.27.2", "@tiptap/html": "^2.27.2", "@tiptap/pm": "^2.4.0", @@ -34,6 +39,8 @@ "codemirror": "^6.0.1", "graphql": "^16.9.0", "graphql-request": "^6.1.0", + "highlight.js": "^11.11.1", + "lowlight": "^3.3.0", "pinia": "^2.1.7", "vue": "^3.4.0", "vue-router": "^4.3.0" @@ -41,6 +48,7 @@ "devDependencies": { "@codemirror/lang-markdown": "^6.2.5", "@codemirror/theme-one-dark": "^6.1.2", + "@tiptap/extension-image": "^2.27.2", "@types/node": "^20.14.0", "@vitejs/plugin-vue": "^5.0.5", "autoprefixer": "^10.4.19", diff --git a/frontend/src/bridge/asciidoc-bridge.ts b/frontend/src/bridge/asciidoc-bridge.ts index a5d3ca5..f129a2a 100644 --- a/frontend/src/bridge/asciidoc-bridge.ts +++ b/frontend/src/bridge/asciidoc-bridge.ts @@ -3,13 +3,17 @@ import { generateJSON } from '@tiptap/html' import type { JSONContent } from '@tiptap/core' import StarterKit from '@tiptap/starter-kit' import Link from '@tiptap/extension-link' -import { Admonition, CustomCodeBlock } from './asciidoc-extensions' +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 { Admonition, CustomCodeBlock, AsciidocImage } from './asciidoc-extensions' const asciidoctor = Asciidoctor() // ── AsciiDoc → TipTap ──────────────────────────────────────────────────────── -export function toTipTap(adoc: string): JSONContent { +export function toTipTap(adoc: string, slug?: string): JSONContent { if (!adoc || !adoc.trim()) { return { type: 'doc', content: [{ type: 'paragraph' }] } } @@ -29,11 +33,47 @@ export function toTipTap(adoc: string): JSONContent { } ) + // Rewrite image tags to point to backend /media/ path + // Asciidoctor outputs: alt + // We add data-original-src so we can convert back properly, and rewrite src to /media/... + if (slug) { + const parts = slug.split('/') + parts.pop() // remove filename + const dir = parts.length > 0 ? parts.join('/') + '/' : '' + + htmlContent = htmlContent.replace( + /]*)src="([^"]+)"([^>]*)>/g, + (_match, before, src, after) => { + // Only rewrite relative paths (not http/https) + if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('data:')) { + return `` + } + const backendSrc = `/media/${dir}${src}` + return `` + } + ) + } else { + // Om slug saknas, lägg i alla fall till data-original-src så den bevaras + htmlContent = htmlContent.replace( + /]*)src="([^"]+)"([^>]*)>/g, + (_match, before, src, after) => { + return `` + } + ) + } + const extensions = [ StarterKit.configure({ codeBlock: false }), CustomCodeBlock, Link, - Admonition + Admonition, + AsciidocImage, + Table.configure({ + resizable: true, + }), + TableRow, + TableHeader, + TableCell, ] return generateJSON(htmlContent, extensions) @@ -86,6 +126,12 @@ class TipTapToAsciidoc { case 'admonition': output += this.convertAdmonition(node) break + case 'image': + output += this.convertImage(node) + break + case 'table': + output += this.convertTable(node) + break case 'horizontalRule': output += "'''\n\n" break @@ -137,6 +183,37 @@ class TipTapToAsciidoc { return `[${type.toUpperCase()}]\n====\n${text}\n====\n\n` } + convertImage(node: JSONContent): string { + const src = node.attrs?.['data-original-src'] || node.attrs?.src || '' + const alt = node.attrs?.alt || '' + const width = node.attrs?.width ? `, width=${node.attrs.width}` : '' + const height = node.attrs?.height ? `, height=${node.attrs.height}` : '' + const title = node.attrs?.title ? `title="${node.attrs.title}", ` : '' + + if (title || width || height || alt) { + return `image::${src}[${title}${alt}${width}${height}]\n\n` + } + return `image::${src}[]\n\n` + } + + convertTable(node: JSONContent): string { + if (!node.content || node.content.length === 0) return '' + + let tableOut = '|===\n' + node.content.forEach((row) => { + if (row.type !== 'tableRow' || !row.content) return + + row.content.forEach((cell) => { + const cellText = (cell.content ? this.processNodes(cell.content).trim() : ''); + tableOut += `| ${cellText} ` + }) + tableOut += '\n\n' + }) + + tableOut += '|===\n\n' + return tableOut + } + renderTextNodes(nodes: JSONContent[]): string { if (!nodes || nodes.length === 0) return '' let textOut = '' diff --git a/frontend/src/bridge/asciidoc-extensions.ts b/frontend/src/bridge/asciidoc-extensions.ts index 5473187..bfb343c 100644 --- a/frontend/src/bridge/asciidoc-extensions.ts +++ b/frontend/src/bridge/asciidoc-extensions.ts @@ -1,5 +1,40 @@ import { Node } from '@tiptap/core' -import CodeBlock from '@tiptap/extension-code-block' +import { CodeBlockLowlight, CodeBlockLowlightOptions } from '@tiptap/extension-code-block-lowlight' +import { createLowlight, all } from 'lowlight' +import Image from '@tiptap/extension-image' + +const lowlight = createLowlight(all) + +export const AsciidocImage = Image.extend({ + addAttributes() { + return { + ...this.parent?.(), + src: { default: null }, + alt: { default: null }, + title: { default: null }, + width: { + default: null, + parseHTML: element => element.getAttribute('width'), + renderHTML: attributes => { + if (!attributes.width) return {} + return { width: attributes.width } + } + }, + height: { + default: null, + parseHTML: element => element.getAttribute('height'), + renderHTML: attributes => { + if (!attributes.height) return {} + return { height: attributes.height } + } + }, + 'data-original-src': { + default: null, + parseHTML: element => element.getAttribute('data-original-src'), + } + } + } +}) export const Admonition = Node.create({ name: 'admonition', @@ -26,7 +61,18 @@ export const Admonition = Node.create({ }, }) -export const CustomCodeBlock = CodeBlock.extend({ +export const CustomCodeBlock = CodeBlockLowlight.extend({ + addOptions() { + return { + ...(this.parent?.() || {}), + lowlight, + defaultLanguage: null, + languageClassPrefix: 'language-', + HTMLAttributes: { + class: 'code-block text-sm bg-gray-800 text-gray-100 p-3 rounded', + }, + } as any + }, addAttributes() { return { language: { diff --git a/frontend/src/components/editor/ImageEditDialog.vue b/frontend/src/components/editor/ImageEditDialog.vue new file mode 100644 index 0000000..30e926b --- /dev/null +++ b/frontend/src/components/editor/ImageEditDialog.vue @@ -0,0 +1,98 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/editor/ImagePickerDialog.vue b/frontend/src/components/editor/ImagePickerDialog.vue new file mode 100644 index 0000000..9036c02 --- /dev/null +++ b/frontend/src/components/editor/ImagePickerDialog.vue @@ -0,0 +1,211 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/editor/VisualEditor.vue b/frontend/src/components/editor/VisualEditor.vue index c20f82c..da72f77 100644 --- a/frontend/src/components/editor/VisualEditor.vue +++ b/frontend/src/components/editor/VisualEditor.vue @@ -3,21 +3,49 @@ import { ref, watch, nextTick, onBeforeUnmount } from 'vue' import { useEditor, EditorContent } from '@tiptap/vue-3' import StarterKit from '@tiptap/starter-kit' 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 { toTipTap, fromTipTap } from '@/bridge/asciidoc-bridge' -import { Admonition, CustomCodeBlock } from '@/bridge/asciidoc-extensions' +import { Admonition, CustomCodeBlock, AsciidocImage } from '@/bridge/asciidoc-extensions' +import ImagePickerDialog from './ImagePickerDialog.vue' +import ImageEditDialog from './ImageEditDialog.vue' const model = defineModel({ required: true }) +const props = defineProps<{ slug?: string }>() const isEditing = ref(false) +const showImagePicker = ref(false) +const showImageEditor = ref(false) + +const contextMenu = ref({ show: false, x: 0, y: 0, type: '' as 'table' | 'image' | '' }) +const activeImageData = ref<{ src: string; width: string; height: string; alt: string; title: string; 'data-original-src': string } | null>(null) const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false }), CustomCodeBlock, Link, - Admonition + Admonition, + AsciidocImage, + Table.configure({ + resizable: true, + HTMLAttributes: { + class: 'border-collapse table-auto w-full text-sm', + }, + }), + TableRow.configure({ + HTMLAttributes: { class: 'border-b dark:border-slate-700' } + }), + TableHeader.configure({ + HTMLAttributes: { class: 'border p-2 bg-gray-100 dark:bg-slate-800 font-bold' } + }), + TableCell.configure({ + HTMLAttributes: { class: 'border p-2' } + }), ], - content: toTipTap(model.value), + content: toTipTap(model.value, props.slug), editable: false, onUpdate({ editor }) { model.value = fromTipTap(editor.getJSON()) @@ -32,14 +60,124 @@ watch(isEditing, (editing) => { // Sync external changes (e.g. switching from SourceEditor) into TipTap. watch(model, (adoc) => { if (!editor.value) return - const json = toTipTap(adoc) + const json = toTipTap(adoc, props.slug) const current = JSON.stringify(editor.value.getJSON()) if (JSON.stringify(json) !== current) { editor.value.commands.setContent(json, false) } }) -onBeforeUnmount(() => editor.value?.destroy()) +function onContextMenu(e: MouseEvent) { + if (!isEditing.value || !editor.value) return + + const target = e.target as HTMLElement + + if (target instanceof HTMLImageElement) { + // Om man högerklickar på en bild vill vi välja hela noden + const pos = editor.value.view.posAtDOM(target, 0) + if (pos >= 0) { + editor.value.commands.setNodeSelection(pos) + contextMenu.value = { show: true, x: e.clientX, y: e.clientY, type: 'image' } + e.preventDefault() + return + } + } + + const view = editor.value.view + const coords = view.posAtCoords({ left: e.clientX, top: e.clientY }) + if (!coords) { + contextMenu.value.show = false + return + } + + // För text/celler sätt markören inuti + editor.value.commands.setTextSelection(coords.pos) + + if (editor.value.isActive('table')) { + contextMenu.value = { show: true, x: e.clientX, y: e.clientY, type: 'table' } + e.preventDefault() + } else { + contextMenu.value.show = false + } +} + +function handleContextAction(action: string) { + if (!editor.value) return + const cmd = editor.value.chain().focus() + + switch (action) { + case 'editImage': + openImageEditor() + break + case 'addRowBefore': cmd.addRowBefore().run(); break + case 'addRowAfter': cmd.addRowAfter().run(); break + case 'deleteRow': cmd.deleteRow().run(); break + case 'addColumnBefore': cmd.addColumnBefore().run(); break + case 'addColumnAfter': cmd.addColumnAfter().run(); break + case 'deleteColumn': cmd.deleteColumn().run(); break + case 'deleteTable': cmd.deleteTable().run(); break + } + + contextMenu.value.show = false +} + +function openImageEditor() { + if (!editor.value) return + const attrs = editor.value.getAttributes('image') + activeImageData.value = { + src: attrs.src || '', + width: attrs.width || '', + height: attrs.height || '', + alt: attrs.alt || '', + title: attrs.title || '', + 'data-original-src': attrs['data-original-src'] || '' + } + showImageEditor.value = true +} + +function handleImageUpdate(updatedData: { src: string; width: string; height: string; alt: string; title: string, 'data-original-src': string }) { + if (!editor.value) return + (editor.value.commands as any).updateAttributes('image', updatedData) + showImageEditor.value = false +} + +function insertAdmonition(e: Event) { + const select = e.target as HTMLSelectElement + const type = select.value + if (!type || !editor.value) return + editor.value.commands.insertContent({ + type: 'admonition', + attrs: { type: type.toLowerCase() }, + content: [{ type: 'paragraph' }] + }) + select.value = '' +} + +function handleImageInsert(data: { src: string; alt: string; title: string, 'data-original-src': string }) { + if (editor.value) { + (editor.value.commands as any).setImage({ + src: data.src, + alt: data.alt, + title: data.title, + 'data-original-src': data['data-original-src'] + }) + } +} + +onBeforeUnmount(() => { + editor.value?.destroy() + document.removeEventListener('click', closeContextMenu) +}) + +function closeContextMenu() { + if (contextMenu.value.show) { + contextMenu.value.show = false + } +} +import { onMounted } from 'vue' +onMounted(() => { + document.addEventListener('click', closeContextMenu) +}) function btnClass(active: boolean | undefined) { return [ @@ -132,11 +270,42 @@ function btnClass(active: boolean | undefined) { Kodblock +
+ + + +
+ + + + +
-
+
+ + + + + + +
+ + +
diff --git a/frontend/src/main.ts b/frontend/src/main.ts index b63d2d2..2589cdd 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -3,6 +3,7 @@ import { createPinia } from 'pinia' import { router } from '@/router' import App from '@/App.vue' import '@/assets/main.css' +import 'highlight.js/styles/github-dark.css' const app = createApp(App) app.use(createPinia()) diff --git a/frontend/src/views/DocumentView.vue b/frontend/src/views/DocumentView.vue index 9e00aa5..159dec5 100644 --- a/frontend/src/views/DocumentView.vue +++ b/frontend/src/views/DocumentView.vue @@ -311,6 +311,7 @@ function formatDate(dateStr: string) {
Loading version…
@@ -324,7 +325,7 @@ function formatDate(dateStr: string) { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 4e4f865..0bfaaf5 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -25,6 +25,7 @@ export default defineConfig({ // instead of waiting for all tabs to close. skipWaiting: true, clientsClaim: true, + maximumFileSizeToCacheInBytes: 5000000, // Only cache these explicit file types — do NOT cache HTML so the // SPA shell is always fetched fresh (prevents stale SW loops). globPatterns: ['**/*.{js,css,woff2,png,svg,ico}'], @@ -50,6 +51,7 @@ export default defineConfig({ port: 5173, proxy: { '/graphql': { target: 'http://localhost:4000', changeOrigin: true }, + '/media': { target: 'http://localhost:4000', changeOrigin: true }, }, }, }) diff --git a/package-lock.json b/package-lock.json index 25a5f74..8c365b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,23 @@ "packages": { "": { "dependencies": { - "@tailwindcss/typography": "^0.5.19" + "@tailwindcss/typography": "^0.5.19", + "@tiptap/extension-code-block-lowlight": "^3.22.3", + "@tiptap/extension-table": "^3.22.3", + "@tiptap/extension-table-cell": "^3.22.3", + "@tiptap/extension-table-header": "^3.22.3", + "@tiptap/extension-table-row": "^3.22.3", + "highlight.js": "^11.11.1", + "lowlight": "^3.3.0" } }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT", + "peer": true + }, "node_modules/@tailwindcss/typography": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", @@ -20,6 +34,190 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, + "node_modules/@tiptap/core": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz", + "integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.22.3" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.3.tgz", + "integrity": "sha512-RiQtEjDAPrHpdo6sw6b7fOw/PijqgFIsozKKkGcSeBgWHQuFg7q9OxJTj+l0e60rVwSu/5gmKEEobzM9bX+t2Q==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.3", + "@tiptap/pm": "^3.22.3" + } + }, + "node_modules/@tiptap/extension-code-block-lowlight": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.22.3.tgz", + "integrity": "sha512-NGFuD9zb1QfnCgD2zW4XaUEdQvd/ydm9FmXXh8eawx/+C8xt3p21DIKRERvxWrCvvVNzIUEpZRRYehPOJiD1eg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.3", + "@tiptap/extension-code-block": "^3.22.3", + "@tiptap/pm": "^3.22.3", + "highlight.js": "^11", + "lowlight": "^2 || ^3" + } + }, + "node_modules/@tiptap/extension-table": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.22.3.tgz", + "integrity": "sha512-inbQSusJad7H0T++L1APg/anfL5d15cNGp2YG3vwo6TQr71nn2c9pepvmz3xuAIt8eygZDRba+4GT/COP1f9QA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.3", + "@tiptap/pm": "^3.22.3" + } + }, + "node_modules/@tiptap/extension-table-cell": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-3.22.3.tgz", + "integrity": "sha512-LgaUgNwjHe6J7dq66N7iflC9efjgygsYWkHJtfLzaSU82tXzk8HSwBrMRCaj0PU+AR967/jGixEZIxH9xfXswQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-table": "^3.22.3" + } + }, + "node_modules/@tiptap/extension-table-header": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-3.22.3.tgz", + "integrity": "sha512-5wQ5rne9ccdbzeqC1AEDaUzlWjnpQNgBctgSPKEv+Da88lRRcGX21H+b/8jhL7fr4/sznr45toKGUg8NmRhboQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-table": "^3.22.3" + } + }, + "node_modules/@tiptap/extension-table-row": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-3.22.3.tgz", + "integrity": "sha512-dkoHnbOZgb2iQLXsp2FUu0KejAh3LOqDLMixYYU/0lnUwhqGG8kgu6+0YkCiUhedbfMFqVvemdKnRddpTQ4sqg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-table": "^3.22.3" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz", + "integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "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", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "peer": true + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT", + "peer": true + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -32,6 +230,120 @@ "node": ">=4" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT", + "peer": true + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT", + "peer": true + }, "node_modules/postcss-selector-parser": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", @@ -45,6 +357,236 @@ "node": ">=4" } }, + "node_modules/prosemirror-changeset": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", + "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", + "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", + "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "peer": true, + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", + "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz", + "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT", + "peer": true + }, "node_modules/tailwindcss": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", @@ -52,11 +594,25 @@ "license": "MIT", "peer": true }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT", + "peer": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT", + "peer": true } } } diff --git a/package.json b/package.json index d798df3..d9755ae 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,12 @@ { "dependencies": { - "@tailwindcss/typography": "^0.5.19" + "@tailwindcss/typography": "^0.5.19", + "@tiptap/extension-code-block-lowlight": "^3.22.3", + "@tiptap/extension-table": "^3.22.3", + "@tiptap/extension-table-cell": "^3.22.3", + "@tiptap/extension-table-header": "^3.22.3", + "@tiptap/extension-table-row": "^3.22.3", + "highlight.js": "^11.11.1", + "lowlight": "^3.3.0" } }