From 0da9c0ecc77765bc09489f227a326c1bf08ddfb2 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 9 Aug 2024 00:53:56 +0200 Subject: [PATCH 01/31] Add Tiptap and a first integration in NotesModal * Should handle existing markdown content * The editor is configured with extensions: * all what's in StarterKit * code block lowlight * highlight * link * task * typography (with french double quotes) * Some styles have been added to fit DSFR styling Still work in progress, but a nice start! To do: * image uploading and placement inside editor * menu buttons * accessibility tests and improvements --- confiture-web-app/package.json | 20 +- .../src/components/audit/NotesModal.vue | 13 +- .../src/components/ui/Tiptap.vue | 75 +++ confiture-web-app/src/main.ts | 1 + confiture-web-app/src/styles/main.css | 79 +++ yarn.lock | 503 ++++++++++++++++++ 6 files changed, 681 insertions(+), 10 deletions(-) create mode 100644 confiture-web-app/src/components/ui/Tiptap.vue diff --git a/confiture-web-app/package.json b/confiture-web-app/package.json index 1d5c66b1..5ddddb7f 100644 --- a/confiture-web-app/package.json +++ b/confiture-web-app/package.json @@ -15,21 +15,33 @@ "dependencies": { "@gouvfr/dsfr": "1.12.1", "@sentry/tracing": "^7.37.2", + "@sentry/vite-plugin": "^0.3.0", "@sentry/vue": "^7.37.2", + "@tiptap/extension-code-block-lowlight": "^2.5.9", + "@tiptap/extension-highlight": "^2.5.9", + "@tiptap/extension-link": "^2.5.9", + "@tiptap/extension-task-item": "^2.5.9", + "@tiptap/extension-task-list": "^2.5.9", + "@tiptap/extension-typography": "^2.5.9", + "@tiptap/pm": "^2.5.9", + "@tiptap/starter-kit": "^2.5.9", + "@tiptap/vue-3": "^2.5.9", "@unhead/vue": "^1.5.3", + "@vitejs/plugin-vue": "^4.4.1", "dompurify": "^2.4.1", + "highlight.js": "^11.10.0", "jwt-decode": "^3.1.2", "ky": "^0.33.0", "lodash-es": "^4.17.21", + "lowlight": "^3.1.0", "marked": "^4.2.4", "pinia": "^2.0.28", "slugify": "^1.6.5", + "tiptap-markdown": "^0.8.10", + "vite": "^4.5.0", "vue": "^3.3.8", "vue-matomo": "^4.2.0", - "vue-router": "^4.2.5", - "vite": "^4.5.0", - "@vitejs/plugin-vue": "^4.4.1", - "@sentry/vite-plugin": "^0.3.0" + "vue-router": "^4.2.5" }, "devDependencies": { "@types/dompurify": "^2.4.0", diff --git a/confiture-web-app/src/components/audit/NotesModal.vue b/confiture-web-app/src/components/audit/NotesModal.vue index b84eee71..0e7d98c8 100644 --- a/confiture-web-app/src/components/audit/NotesModal.vue +++ b/confiture-web-app/src/components/audit/NotesModal.vue @@ -10,6 +10,7 @@ import { AuditFile, StoreName } from "../../types"; import { handleFileDeleteError, handleFileUploadError } from "../../utils"; import DsfrModal from "../ui/DsfrModal.vue"; import FileUpload from "../ui/FileUpload.vue"; +import Tiptap from "../ui/Tiptap.vue"; import MarkdownHelpButton from "./MarkdownHelpButton.vue"; import SaveIndicator from "./SaveIndicator.vue"; @@ -104,15 +105,15 @@ function handleDeleteFile(file: AuditFile) { - + @update:content="($content) => (notes = $content)" + /> +import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; +import Heading from "@tiptap/extension-heading"; +import Highlight from "@tiptap/extension-highlight"; +import Link from "@tiptap/extension-link"; +import TaskItem from "@tiptap/extension-task-item"; +import TaskList from "@tiptap/extension-task-list"; +import Typography from "@tiptap/extension-typography"; +import { useEditor, EditorContent } from "@tiptap/vue-3"; +import StarterKit from "@tiptap/starter-kit"; + +import css from "highlight.js/lib/languages/css"; +import js from "highlight.js/lib/languages/javascript"; +import ts from "highlight.js/lib/languages/typescript"; +import html from "highlight.js/lib/languages/xml"; + +import { Markdown } from "tiptap-markdown"; + +// load common languages +import { common, createLowlight } from "lowlight"; + +// create a lowlight instance +const lowlight = createLowlight(common); + +// you can also register languages +lowlight.register("html", html); +lowlight.register("css", css); +lowlight.register("js", js); +lowlight.register("ts", ts); + +const props = defineProps(["content"]); +const emit = defineEmits(["update:content"]); +//JSON.stringify(document.querySelector(".tiptap").editor.getJSON()) + +function getContent() { + let jsonContent; + try { + jsonContent = JSON.parse(props.content); + } catch (e) { + return ""; + } + return jsonContent; +} + +const editor = useEditor({ + content: getContent(), + extensions: [ + CodeBlockLowlight.configure({ lowlight }), + Highlight, + Heading.configure({ + levels: [2, 3, 4, 5, 6] + }), + Link, + Markdown, + StarterKit.configure({ + codeBlock: false, + heading: false + }), + TaskItem, + TaskList, + Typography.configure({ + openDoubleQuote: "« ", + closeDoubleQuote: " »" + }) + ], + onUpdate({ editor }) { + // The content has changed. + emit("update:content", JSON.stringify(editor.getJSON())); + } +}); + + + diff --git a/confiture-web-app/src/main.ts b/confiture-web-app/src/main.ts index 787d9fa1..1dcc140b 100644 --- a/confiture-web-app/src/main.ts +++ b/confiture-web-app/src/main.ts @@ -2,6 +2,7 @@ import "./styles/main.css"; import "@gouvfr/dsfr/dist/dsfr.min.css"; import "@gouvfr/dsfr/dist/dsfr.module.min.js"; import "@gouvfr/dsfr/dist/utility/icons/icons.css"; +import "highlight.js/styles/github.css"; import { BrowserTracing } from "@sentry/tracing"; import * as Sentry from "@sentry/vue"; diff --git a/confiture-web-app/src/styles/main.css b/confiture-web-app/src/styles/main.css index fd842b7d..5b5f2dd8 100644 --- a/confiture-web-app/src/styles/main.css +++ b/confiture-web-app/src/styles/main.css @@ -65,6 +65,85 @@ from DSFR links with `target="_blank"` */ color: var(--background-action-high-error) !important; } +.tiptap { + padding: 1rem; + border: 1px solid var(--border-default-grey); + min-height: 10rem; +} + +.tiptap pre { + padding: 0.75rem; +} +.tiptap code { + padding: 0.2em 0.4em; + font-size: 85%; +} +.tiptap pre, +.tiptap code { + background-color: var(--background-alt-grey); + border-radius: 0.25rem; +} +.tiptap blockquote:before { + --icon-size: 2rem; + color: var(--artwork-minor-blue-france); + content: ""; + display: block; + margin-bottom: 0.5rem; + background-color: currentColor; + display: inline-block; + flex: 0 0 auto; + height: var(--icon-size); + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTE0IDNhOCA4IDAgMSAxIDAgMTZ2My41Yy01LTItMTItNS0xMi0xMS41YTggOCAwIDAgMSA4LThoNFptMCAyaC00YTYgNiAwIDAgMC02IDZjMCAzLjYxIDIuNDYyIDUuOTY2IDggOC40OFYxN2gyYTYgNiAwIDEgMCAwLTEyWm0tMiAyLjUtMi41MDYgMy43NUwxMiAxNUg5LjI5NUw2Ljc1IDExLjI1IDkuMjk1IDcuNUgxMlptNC41IDAtMi41MDYgMy43NUwxNi41IDE1aC0yLjcwNWwtMi41NDUtMy43NSAyLjU0NS0zLjc1SDE2LjVaIi8+PC9zdmc+); + mask-size: 100% 100%; + vertical-align: calc((0.75em - var(--icon-size)) * 0.5); + width: var(--icon-size); +} + +.tiptap blockquote p { + font-size: 1.25rem; + font-weight: 700; + line-height: 2rem; +} + +.tiptap li > p { + margin-bottom: 0.25em; +} + +/* FIXME: tiptap tasklist are not accessible yet. */ +/* https://github.com/ueberdosis/tiptap/issues/4774 */ +.tiptap ul[data-type="taskList"] { + list-style: none; + margin-left: 0; + padding: 0; +} + +.tiptap ul[data-type="taskList"] li { + align-items: flex-start; + display: flex; +} + +.tiptap ul[data-type="taskList"] li > label { + flex: 0 0 auto; + margin-right: 0.5rem; + user-select: none; +} + +.tiptap ul[data-type="taskList"] li > div { + flex: 1 1 auto; +} + +.tiptap ul[data-type="taskList"] li > div p { + margin-bottom: 0.25em; +} + +.tiptap ul[data-type="taskList"] input[type="checkbox"] { + cursor: pointer; +} + +.tiptap ul[data-type="taskList"] ul[data-type="taskList"] { + margin: 0; +} + /* File upload styling */ /* input[type="file" i]::-webkit-file-upload-button { background-color: transparent; diff --git a/yarn.lock b/yarn.lock index 5dd55513..072e088e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1180,6 +1180,11 @@ dependencies: debug "^4.3.1" +"@popperjs/core@^2.9.0": + version "2.11.8" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@prisma/client@^4.1.1": version "4.16.2" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.16.2.tgz#3bb9ebd49b35c8236b3d468d0215192267016e2b" @@ -1330,6 +1335,11 @@ tmp "0.2.1" ts-pattern "^4.0.1" +"@remirror/core-constants@^2.0.2": + version "2.0.2" + resolved "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz#f05eccdc69e3a65e7d524b52548f567904a11a1a" + integrity sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ== + "@sentry-internal/feedback@7.88.0": version "7.88.0" resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.88.0.tgz#fa4db4a27d1fa7fe51dc67af185b13519d7fbc76" @@ -1995,6 +2005,204 @@ resolved "https://registry.yarnpkg.com/@timsuchanek/sleep-promise/-/sleep-promise-8.0.1.tgz#81c0754b345138a519b51c2059771eb5f9b97818" integrity sha512-cxHYbrXfnCWsklydIHSw5GCMHUPqpJ/enxWSyVHNOgNe61sit/+aOXTTI+VOdWkvVaJsI2vsB9N4+YDNITawOQ== +"@tiptap/core@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/core/-/core-2.5.9.tgz#1deb0b7c748e24ec32613263e0af8d55a3b3c2ca" + integrity sha512-PPUR+0tbr+wX2G8RG4FEps4qhbnAPEeXK1FUtirLXSRh8vm+TDgafu3sms7wBc4fAyw9zTO/KNNZ90GBe04guA== + +"@tiptap/extension-blockquote@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.5.9.tgz#d873a8496fcf572c69aaac2a7a341e035fdbae22" + integrity sha512-LhGyigmd/v1OjYPeoVK8UvFHbH6ffh175ZuNvseZY4PsBd7kZhrSUiuMG8xYdNX8FxamsxAzr2YpsYnOzu3W7A== + +"@tiptap/extension-bold@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.5.9.tgz#00c9b7b5211048b1e1c5d67e355935b9c92e3532" + integrity sha512-XUJdzFb31t0+bwiRquJf0btBpqOB3axQNHTKM9XADuL4S+Z6OBPj0I5rYINeElw/Q7muvdWrHWHh/ovNJA1/5A== + +"@tiptap/extension-bubble-menu@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.9.tgz#d600bbcaa1d98a99f32b3b8b8c3d35752161200c" + integrity sha512-NddZ8Qn5dgPPa1W4yk0jdhF4tDBh0FwzBpbnDu2Xz/0TUHrA36ugB2CvR5xS1we4zUKckgpVqOqgdelrmqqFVg== + dependencies: + tippy.js "^6.3.7" + +"@tiptap/extension-bullet-list@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.5.9.tgz#2852aba9a1dacbf2c673cda6a4994b1f3c33cd5c" + integrity sha512-hJTv1x4omFgaID4LMRT5tOZb/VKmi8Kc6jsf4JNq4Grxd2sANmr9qpmKtBZvviK+XD5PpTXHvL+1c8C1SQtuHQ== + +"@tiptap/extension-code-block-lowlight@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.5.9.tgz#ccd6569422d98b11813df3e0dbd09b1dcf957def" + integrity sha512-taIXxXQ/Lka9CegHFHQS+nx6cX9i9Ws63ZFMPbrXLMSJRhXk8+m4UAoGZQJH9CGGb5/Rv0p3Z8I59AGiyUHLEw== + +"@tiptap/extension-code-block@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.5.9.tgz#8cd99515b286fc62ad1215a411aea5da9a7d9701" + integrity sha512-+MUwp0VFFv2aFiZ/qN6q10vfIc6VhLoFFpfuETX10eIRks0Xuj2nGiqCDj7ca0/M44bRg2VvW8+tg/ZEHFNl9g== + +"@tiptap/extension-code@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.5.9.tgz#93c4433eca8b2aa239ea7f408b90f152b7fc4603" + integrity sha512-Q1PL3DUXiEe5eYUwOug1haRjSaB0doAKwx7KFVI+kSGbDwCV6BdkKAeYf3us/O2pMP9D0im8RWX4dbSnatgwBA== + +"@tiptap/extension-document@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.5.9.tgz#13a22b2d3bdc1463844872b1f1c926633df431a8" + integrity sha512-VdNZYDyCzC3W430UdeRXR9IZzPeODSbi5Xz/JEdV93THVp8AC9CrZR7/qjqdBTgbTB54VP8Yr6bKfCoIAF0BeQ== + +"@tiptap/extension-dropcursor@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.5.9.tgz#648f683f929056a0526620f530f73e6b052c1481" + integrity sha512-nEOb37UryG6bsU9JAs/HojE6Jg43LupNTAMISbnuB1CPAeAqNsFMwORd9eEPkyEwnQT7MkhsMOSJM44GoPGIFA== + +"@tiptap/extension-floating-menu@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.9.tgz#b970905f3c1af49a916dcbd477a4302086187974" + integrity sha512-MWJIQQT6e5MgqHny8neeH2Dx926nVPF7sv4p84nX4E0dnkRbEYUP8mCsWYhSUvxxIif6e+yY+4654f2Q9qTx1w== + dependencies: + tippy.js "^6.3.7" + +"@tiptap/extension-gapcursor@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.5.9.tgz#68b9e227cd7876aac353a8ac029995b4c092a763" + integrity sha512-yW7V2ebezsa7mWEDWCg4A1ZGsmSV5bEHKse9wzHCDkb7TutSVhLZxGo72U6hNN9PnAksv+FJQk03NuZNYvNyRQ== + +"@tiptap/extension-hard-break@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.5.9.tgz#4f38f06dbeb5fb3e58ff7fc0c48b9db9c4ee4ecd" + integrity sha512-8hQ63SgZRG4BqHOeSfeaowG2eMr2beced018pOGbpHbE3XSYoISkMVuFz4Z8UEVR3W9dTbKo4wxNufSTducocQ== + +"@tiptap/extension-heading@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.5.9.tgz#b9ec3b3b48dea939606d06eff56c4bdc7bed0662" + integrity sha512-HHowAlGUbFn1qvmY02ydM7qiPPMTGhAJn2A46enDRjNHW5UoqeMfkMpTEYaioOexyguRFSfDT3gpK68IHkQORQ== + +"@tiptap/extension-highlight@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.5.9.tgz#290426538abcbb2299809d3e1274ba5af1ba9f68" + integrity sha512-tRaSIIbCI7aBlvlmgUgBI5lVBqnMy49lc++UVAx1Pjey1j2KW031vUyvZfEwf6wk8Y7W3kVSkN0mW9IYCcOAOQ== + +"@tiptap/extension-history@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.5.9.tgz#f48f64ff95407f0ce27bcdd020762e49d0dd60d1" + integrity sha512-hGPtJgoZSwnVVqi/xipC2ET/9X2G2UI/Y+M3IYV1ZlM0tCYsv4spNi3uXlZqnXRwYcBXLk5u6e/dmsy5QFbL8g== + +"@tiptap/extension-horizontal-rule@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.5.9.tgz#9f91a17b80700670e53e241fcee40365c57aa994" + integrity sha512-/ES5NdxCndBmZAgIXSpCJH8YzENcpxR0S8w34coSWyv+iW0Sq7rW/mksQw8ZIVsj8a7ntpoY5OoRFpSlqcvyGw== + +"@tiptap/extension-italic@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.5.9.tgz#8ea0e19e650f0f1d6fc30425ec28291511143dda" + integrity sha512-Bw+P139L4cy+B56zpUiRjP8BZSaAUl3JFMnr/FO+FG55QhCxFMXIc6XrC3vslNy5ef3B3zv4gCttS3ee8ByMiw== + +"@tiptap/extension-link@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.5.9.tgz#6cb323d36b82700963ad2b9d189a7d07c81c7d6e" + integrity sha512-7v9yRsX7NuiY8DPslIsPIlFqcD8aGBMLqfEGXltJDvuG6kykdr+khEZeWcJ8ihHIL4yWR3/MAgeT2W72Z/nxiQ== + dependencies: + linkifyjs "^4.1.0" + +"@tiptap/extension-list-item@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.5.9.tgz#0805b7216371b8b54649abe5ab29bd2c2155f05f" + integrity sha512-d9Eo+vBz74SMxP0r25aqiErV256C+lGz+VWMjOoqJa6xWLM1keYy12JtGQWJi8UDVZrDskJKCHq81A0uLt27WA== + +"@tiptap/extension-ordered-list@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.5.9.tgz#44aab6ec3e19429a8e3b73e42c04156f2b0bc730" + integrity sha512-9MsWpvVvzILuEOd/GdroF7RI7uDuE1M6at9rzsaVGvCPVHZBvu1XR3MSVK5OdiJbbJuPGttlzEFLaN/rQdCGFg== + +"@tiptap/extension-paragraph@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.5.9.tgz#05210b6e7a9940b1acc09fdd4ec769fc6406da2b" + integrity sha512-HDXGiHTJ/V85dbDMjcFj4XfqyTQZqry6V21ucMzgBZYX60X3gIn7VpQTQnnRjvULSgtfOASSJP6BELc5TyiK0w== + +"@tiptap/extension-strike@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.5.9.tgz#f2d54161d24ee37dc8a41b5077c553048ed69f99" + integrity sha512-QezkOZpczpl09S8lp5JL7sRkwREoPY16Y/lTvBcFKm3TZbVzYZZ/KwS0zpwK9HXTfXr8os4L9AGjQf0tHonX+w== + +"@tiptap/extension-task-item@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.5.9.tgz#623590d549aa0e21ccd1d34396ebbeeea45886f5" + integrity sha512-g4HK3r3yNE0RcXQOkJHs94Ws/fhhTqa1L5iAy4gwYKNNFFnIQl8BpE6nn9d5h33kWDN9jjY+PZmq+0PvxCLODQ== + +"@tiptap/extension-task-list@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.5.9.tgz#9934b3ae84dbfc27804d0d54e64aeb9e8fcaf418" + integrity sha512-OylVo5cAh0117PzhyM8MGaUIrCskGiF7v7x6/zAHMFIqVdcbKsq+hMueVPnABfOyLcIH5Zojo3NzNOJeKeblCg== + +"@tiptap/extension-text@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.5.9.tgz#a5bef0b9c5324511dbc2804a3a5ac8b9b5d5dc4c" + integrity sha512-W0pfiQUPsMkwaV5Y/wKW4cFsyXAIkyOFt7uN5u6LrZ/iW9KZ/IsDODPJDikWp0aeQnXzT9NNQULTpCjbHzzS6g== + +"@tiptap/extension-typography@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-2.5.9.tgz#bd6a68889ab8479be593d31a930f98ea575f4f08" + integrity sha512-S+r4m3J0eK4qOszUcCU7NeOEUMuOwj0pGO4YYbIJs3AjWOyLrXD04grb64u8sCGcM8hiibQ7uZKSLJOmLjuoEA== + +"@tiptap/pm@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.9.tgz#f97889210374993a1ce78e9ecb23461d0e4644bf" + integrity sha512-YSUaEQVtvZnGzGjif2Tl2o9utE+6tR2Djhz0EqFUcAUEVhOMk7UYUO+r/aPfcCRraIoKKuDQzyCpjKmJicjCUA== + dependencies: + prosemirror-changeset "^2.2.1" + prosemirror-collab "^1.3.1" + prosemirror-commands "^1.5.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.0" + prosemirror-menu "^1.2.4" + prosemirror-model "^1.22.2" + prosemirror-schema-basic "^1.2.3" + prosemirror-schema-list "^1.4.1" + prosemirror-state "^1.4.3" + prosemirror-tables "^1.4.0" + prosemirror-trailing-node "^2.0.9" + prosemirror-transform "^1.9.0" + prosemirror-view "^1.33.9" + +"@tiptap/starter-kit@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.5.9.tgz#fec0955b873ebcbdeefdfaab0e9254011df3f41b" + integrity sha512-nZ4V+vRayomjxUsajFMHv1iJ5SiSaEA65LAXze/CzyZXGMXfL2OLzY7wJoaVJ4BgwINuO0dOSAtpNDN6jI+6mQ== + dependencies: + "@tiptap/core" "^2.5.9" + "@tiptap/extension-blockquote" "^2.5.9" + "@tiptap/extension-bold" "^2.5.9" + "@tiptap/extension-bullet-list" "^2.5.9" + "@tiptap/extension-code" "^2.5.9" + "@tiptap/extension-code-block" "^2.5.9" + "@tiptap/extension-document" "^2.5.9" + "@tiptap/extension-dropcursor" "^2.5.9" + "@tiptap/extension-gapcursor" "^2.5.9" + "@tiptap/extension-hard-break" "^2.5.9" + "@tiptap/extension-heading" "^2.5.9" + "@tiptap/extension-history" "^2.5.9" + "@tiptap/extension-horizontal-rule" "^2.5.9" + "@tiptap/extension-italic" "^2.5.9" + "@tiptap/extension-list-item" "^2.5.9" + "@tiptap/extension-ordered-list" "^2.5.9" + "@tiptap/extension-paragraph" "^2.5.9" + "@tiptap/extension-strike" "^2.5.9" + "@tiptap/extension-text" "^2.5.9" + +"@tiptap/vue-3@^2.5.9": + version "2.5.9" + resolved "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.5.9.tgz#822e3b52c51b582b5c7238f782d543125da0f3b1" + integrity sha512-Iz7HMW9A0jinYnMs2wZxjI+e5fc5MQmjgmfE0kQmimpgISBregW8vJyDKDPIZVJz5LQPLL045G3mL+7V8fExrQ== + dependencies: + "@tiptap/extension-bubble-menu" "^2.5.9" + "@tiptap/extension-floating-menu" "^2.5.9" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -2119,6 +2327,13 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + "@types/http-cache-semantics@*": version "4.0.4" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" @@ -2153,6 +2368,11 @@ dependencies: "@types/node" "*" +"@types/linkify-it@^3": + version "3.0.5" + resolved "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz#1e78a3ac2428e6d7e6c05c1665c242023a4601d8" + integrity sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw== + "@types/lodash-es@^4.17.6": version "4.17.12" resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b" @@ -2165,11 +2385,24 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== +"@types/markdown-it@^13.0.7": + version "13.0.9" + resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz#df79221eae698df5b4e982c7e91128dd8e525743" + integrity sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw== + dependencies: + "@types/linkify-it" "^3" + "@types/mdurl" "^1" + "@types/marked@^4.0.8": version "4.3.2" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.2.tgz#e2e0ad02ebf5626bd215c5bae2aff6aff0ce9eac" integrity sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w== +"@types/mdurl@^1": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz#3e0d2db570e9fb6ccb2dc8fde0be1d79ac810d39" + integrity sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA== + "@types/mime@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" @@ -2306,6 +2539,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== +"@types/unist@*": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20" + integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ== + "@types/yauzl@^2.9.1": version "2.10.3" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" @@ -3745,6 +3983,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +crelt@^1.0.0: + version "1.0.6" + resolved "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" + integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== + croner@~4.1.92: version "4.1.97" resolved "https://registry.yarnpkg.com/croner/-/croner-4.1.97.tgz#6e373dc7bb3026fab2deb0d82685feef20796766" @@ -4008,6 +4251,11 @@ depd@2.0.0, depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -4028,6 +4276,13 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +devlop@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -5228,6 +5483,16 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +highlight.js@^11.10.0: + version "11.10.0" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92" + integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ== + +highlight.js@~11.9.0: + version "11.9.0" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" + integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== + hookable@^5.5.3: version "5.5.3" resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d" @@ -5885,6 +6150,18 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + +linkifyjs@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz#0edbc346428a7390a23ea2e5939f76112c9ae07f" + integrity sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg== + lint-staged@^15.2.0: version "15.2.0" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.0.tgz#3111534ca58096a3c8f70b044b6e7fe21b36f859" @@ -6055,6 +6332,15 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lowlight@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/lowlight/-/lowlight-3.1.0.tgz#aa394c5f3a7689fce35fa49a7c850ba3ead4f590" + integrity sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.0.0" + highlight.js "~11.9.0" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -6110,11 +6396,33 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +markdown-it-task-lists@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz#f68f4d2ac2bad5a2c373ba93081a1a6848417088" + integrity sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA== + +markdown-it@^14.0.0, markdown-it@^14.1.0: + version "14.1.0" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + 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" + marked@^4.2.4: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -6947,6 +7255,11 @@ ora@5.4.1, ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" +orderedmap@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" + integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g== + os-name@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/os-name/-/os-name-4.0.1.tgz#32cee7823de85a8897647ba4d76db46bf845e555" @@ -7397,6 +7710,159 @@ prompts@2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" +prosemirror-changeset@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz#dae94b63aec618fac7bb9061648e6e2a79988383" + integrity sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ== + dependencies: + prosemirror-transform "^1.0.0" + +prosemirror-collab@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33" + integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ== + dependencies: + prosemirror-state "^1.0.0" + +prosemirror-commands@^1.0.0, prosemirror-commands@^1.5.2: + version "1.6.0" + resolved "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz#b79f034ed371576e7bf83ddd4ede689c8ccbd9ab" + integrity sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-dropcursor@^1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz#49b9fb2f583e0d0f4021ff87db825faa2be2832d" + integrity sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + prosemirror-view "^1.1.0" + +prosemirror-gapcursor@^1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz#5fa336b83789c6199a7341c9493587e249215cb4" + integrity sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ== + dependencies: + prosemirror-keymap "^1.0.0" + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-view "^1.0.0" + +prosemirror-history@^1.0.0, prosemirror-history@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz#cc370a46fb629e83a33946a0e12612e934ab8b98" + integrity sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ== + dependencies: + prosemirror-state "^1.2.2" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.31.0" + rope-sequence "^1.3.0" + +prosemirror-inputrules@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz#ef1519bb2cb0d1e0cec74bad1a97f1c1555068bb" + integrity sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz#14a54763a29c7b2704f561088ccf3384d14eb77e" + integrity sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ== + dependencies: + prosemirror-state "^1.0.0" + w3c-keyname "^2.2.0" + +prosemirror-markdown@^1.11.1, prosemirror-markdown@^1.13.0: + version "1.13.0" + resolved "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.0.tgz#67ebfa40af48a22d1e4ed6cad2e29851eb61e649" + integrity sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g== + dependencies: + markdown-it "^14.0.0" + prosemirror-model "^1.20.0" + +prosemirror-menu@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz#3cfdc7c06d10f9fbd1bce29082c498bd11a0a79a" + integrity sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA== + dependencies: + crelt "^1.0.0" + prosemirror-commands "^1.0.0" + prosemirror-history "^1.0.0" + prosemirror-state "^1.0.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.22.2, prosemirror-model@^1.8.1: + version "1.22.3" + resolved "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz#52fdf5897f348b0f07f64bea89156d90afdf645a" + integrity sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q== + dependencies: + orderedmap "^2.0.0" + +prosemirror-schema-basic@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz#649c349bb21c61a56febf9deb71ac68fca4cedf2" + integrity sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA== + dependencies: + prosemirror-model "^1.19.0" + +prosemirror-schema-list@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz#78b8d25531db48ca9688836dbde50e13ac19a4a1" + integrity sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.7.3" + +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080" + integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.27.0" + +prosemirror-tables@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.4.0.tgz#59c3dc241e03fc4ba8c093995b130d2980f0ffdc" + integrity sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA== + dependencies: + prosemirror-keymap "^1.1.2" + prosemirror-model "^1.8.1" + prosemirror-state "^1.3.1" + prosemirror-transform "^1.2.1" + prosemirror-view "^1.13.3" + +prosemirror-trailing-node@^2.0.9: + version "2.0.9" + resolved "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.9.tgz#a087e6d1372e888cd3e57c977507b6b85dc658e4" + integrity sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg== + dependencies: + "@remirror/core-constants" "^2.0.2" + escape-string-regexp "^4.0.0" + +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.3, prosemirror-transform@^1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.9.0.tgz#81fd1fbd887929a95369e6dd3d240c23c19313f8" + integrity sha512-5UXkr1LIRx3jmpXXNKDhv8OyAOeLTGuXNwdVfg8x27uASna/wQkr9p6fD3eupGOi4PLJfbezxTyi/7fSJypXHg== + dependencies: + prosemirror-model "^1.21.0" + +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.33.9: + version "1.33.9" + resolved "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.9.tgz#0ed61ae42405cfc9799bde4db86badbb1ad99b08" + integrity sha512-xV1A0Vz9cIcEnwmMhKKFAOkfIp8XmJRnaZoPqNXrPS7EK5n11Ov8V76KhR0RsfQd/SIzmWY+bg+M44A2Lx/Nnw== + dependencies: + prosemirror-model "^1.20.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -7447,6 +7913,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^2.1.0, punycode@^2.1.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -7737,6 +8208,11 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" +rope-sequence@^1.3.0: + version "1.3.4" + resolved "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425" + integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ== + run-applescript@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" @@ -8423,6 +8899,23 @@ through@^2.3.6, through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tippy.js@^6.3.7: + version "6.3.7" + resolved "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" + integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== + dependencies: + "@popperjs/core" "^2.9.0" + +tiptap-markdown@^0.8.10: + version "0.8.10" + resolved "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.8.10.tgz#864a54befc17b25e7f475ff6072de3d49814f09b" + integrity sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ== + dependencies: + "@types/markdown-it" "^13.0.7" + markdown-it "^14.1.0" + markdown-it-task-lists "^2.1.1" + prosemirror-markdown "^1.11.1" + titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" @@ -8651,6 +9144,11 @@ typescript@^5.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + uglify-js@^3.1.4, uglify-js@^3.5.1: version "3.17.4" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" @@ -8889,6 +9387,11 @@ vue@^3.3.8: "@vue/server-renderer" "3.3.11" "@vue/shared" "3.3.11" +w3c-keyname@^2.2.0: + version "2.2.8" + resolved "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" + integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== + watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" From af2388071eb3c629db487ef48e7b9230f916b0f4 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 9 Aug 2024 11:15:15 +0200 Subject: [PATCH 02/31] Fix markdown content recovery --- confiture-web-app/src/components/ui/Tiptap.vue | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/confiture-web-app/src/components/ui/Tiptap.vue b/confiture-web-app/src/components/ui/Tiptap.vue index a9c08a6c..ca5909c4 100644 --- a/confiture-web-app/src/components/ui/Tiptap.vue +++ b/confiture-web-app/src/components/ui/Tiptap.vue @@ -30,16 +30,14 @@ lowlight.register("ts", ts); const props = defineProps(["content"]); const emit = defineEmits(["update:content"]); -//JSON.stringify(document.querySelector(".tiptap").editor.getJSON()) function getContent() { - let jsonContent; + let jsonContent = props.content; try { jsonContent = JSON.parse(props.content); - } catch (e) { - return ""; + } finally { + return jsonContent; } - return jsonContent; } const editor = useEditor({ From ffb60467f7f8f50d8bf2f46621ace7c7b1ce6d42 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 16 Aug 2024 19:48:01 +0200 Subject: [PATCH 03/31] Set default code block language to html --- confiture-web-app/src/components/ui/Tiptap.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confiture-web-app/src/components/ui/Tiptap.vue b/confiture-web-app/src/components/ui/Tiptap.vue index ca5909c4..6a4bef9b 100644 --- a/confiture-web-app/src/components/ui/Tiptap.vue +++ b/confiture-web-app/src/components/ui/Tiptap.vue @@ -43,7 +43,7 @@ function getContent() { const editor = useEditor({ content: getContent(), extensions: [ - CodeBlockLowlight.configure({ lowlight }), + CodeBlockLowlight.configure({ lowlight, defaultLanguage: "html" }), Highlight, Heading.configure({ levels: [2, 3, 4, 5, 6] From 4becae3662a9f32d09e3988dcec1b66465bcdbd0 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 16 Aug 2024 20:25:13 +0200 Subject: [PATCH 04/31] Fix eslint imports --- confiture-web-app/src/components/ui/Tiptap.vue | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/confiture-web-app/src/components/ui/Tiptap.vue b/confiture-web-app/src/components/ui/Tiptap.vue index 6a4bef9b..ed15a1d5 100644 --- a/confiture-web-app/src/components/ui/Tiptap.vue +++ b/confiture-web-app/src/components/ui/Tiptap.vue @@ -6,18 +6,15 @@ import Link from "@tiptap/extension-link"; import TaskItem from "@tiptap/extension-task-item"; import TaskList from "@tiptap/extension-task-list"; import Typography from "@tiptap/extension-typography"; -import { useEditor, EditorContent } from "@tiptap/vue-3"; import StarterKit from "@tiptap/starter-kit"; - +import { EditorContent, useEditor } from "@tiptap/vue-3"; import css from "highlight.js/lib/languages/css"; import js from "highlight.js/lib/languages/javascript"; import ts from "highlight.js/lib/languages/typescript"; import html from "highlight.js/lib/languages/xml"; - -import { Markdown } from "tiptap-markdown"; - // load common languages import { common, createLowlight } from "lowlight"; +import { Markdown } from "tiptap-markdown"; // create a lowlight instance const lowlight = createLowlight(common); From 8bfbe3568620f5126fc5b399420a8c313de3c39b Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 16 Aug 2024 20:26:27 +0200 Subject: [PATCH 05/31] Fix eslint finally (and possibly importing JSON content?) --- confiture-web-app/src/components/ui/Tiptap.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/confiture-web-app/src/components/ui/Tiptap.vue b/confiture-web-app/src/components/ui/Tiptap.vue index ed15a1d5..63293483 100644 --- a/confiture-web-app/src/components/ui/Tiptap.vue +++ b/confiture-web-app/src/components/ui/Tiptap.vue @@ -29,12 +29,14 @@ const props = defineProps(["content"]); const emit = defineEmits(["update:content"]); function getContent() { - let jsonContent = props.content; + let jsonContent; try { jsonContent = JSON.parse(props.content); - } finally { - return jsonContent; + } catch { + jsonContent = props.content; } + + return jsonContent; } const editor = useEditor({ From ba218d0c4327a6e8f5612add5bcaed728a8b3308 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Sat, 17 Aug 2024 22:34:35 +0200 Subject: [PATCH 06/31] Fix tiptap code blocks style So you can see it in dark mode --- confiture-web-app/src/styles/main.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/confiture-web-app/src/styles/main.css b/confiture-web-app/src/styles/main.css index 5b5f2dd8..5b75ff74 100644 --- a/confiture-web-app/src/styles/main.css +++ b/confiture-web-app/src/styles/main.css @@ -75,12 +75,14 @@ from DSFR links with `target="_blank"` */ padding: 0.75rem; } .tiptap code { - padding: 0.2em 0.4em; font-size: 85%; } +.tiptap :not(pre) code { + padding: 0.2em 0.4em; +} .tiptap pre, .tiptap code { - background-color: var(--background-alt-grey); + background-color: var(--background-contrast-overlap-grey); border-radius: 0.25rem; } .tiptap blockquote:before { From df48b9621ec48bf7b3114288f55a893b935b4e32 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Mon, 26 Aug 2024 18:05:01 +0200 Subject: [PATCH 07/31] Fix eslint warning (type) Prop 'content' should define at least its type --- confiture-web-app/src/components/ui/Tiptap.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/confiture-web-app/src/components/ui/Tiptap.vue b/confiture-web-app/src/components/ui/Tiptap.vue index 63293483..e0f36448 100644 --- a/confiture-web-app/src/components/ui/Tiptap.vue +++ b/confiture-web-app/src/components/ui/Tiptap.vue @@ -25,7 +25,9 @@ lowlight.register("css", css); lowlight.register("js", js); lowlight.register("ts", ts); -const props = defineProps(["content"]); +const props = defineProps<{ + content: string; +}>(); const emit = defineEmits(["update:content"]); function getContent() { From c8d12625206d1843100899214d2f99b62fa0139b Mon Sep 17 00:00:00 2001 From: Yaacov Date: Sun, 8 Sep 2024 11:11:13 +0200 Subject: [PATCH 08/31] Factorize code in AuditService Use uploadFileToStorage within saveExampleImage and saveNotesFile Fix dark background on notes thumbnails --- .../src/audits/audit.service.ts | 89 +++++++++---------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/confiture-rest-api/src/audits/audit.service.ts b/confiture-rest-api/src/audits/audit.service.ts index f613c9e8..7acc7208 100644 --- a/confiture-rest-api/src/audits/audit.service.ts +++ b/confiture-rest-api/src/audits/audit.service.ts @@ -420,28 +420,11 @@ export class AuditService { criterium: number, file: Express.Multer.File ) { - const randomPrefix = nanoid(); - - const key = `audits/${editUniqueId}/${randomPrefix}/${file.originalname}`; - - const thumbnailKey = `audits/${editUniqueId}/${randomPrefix}/thumbnail_${file.originalname}`; - - const thumbnailBuffer = await sharp(file.buffer) - .jpeg({ - mozjpeg: true - }) - .flatten({ background: { r: 255, g: 255, b: 255, alpha: 0 } }) - .resize(200, 200, { fit: "inside" }) - .toBuffer(); - - await Promise.all([ - this.fileStorageService.uploadFile(file.buffer, file.mimetype, key), - this.fileStorageService.uploadFile( - thumbnailBuffer, - "image/jpeg", - thumbnailKey - ) - ]); + const { key, thumbnailKey } = await this.uploadFileToStorage( + editUniqueId, + file, + { createThumbnail: display === FileDisplay.ATTACHMENT } + ); const storedFile = await this.prisma.storedFile.create({ data: { @@ -501,21 +484,53 @@ export class AuditService { } async saveNotesFile(editUniqueId: string, file: Express.Multer.File) { + const { key, thumbnailKey } = await this.uploadFileToStorage( + editUniqueId, + file, + { createThumbnail: display === FileDisplay.ATTACHMENT } + ); + + const storedFile = await this.prisma.auditFile.create({ + data: { + audit: { + connect: { + editUniqueId + } + }, + + key, + originalFilename: file.originalname, + mimetype: file.mimetype, + size: file.size, + + thumbnailKey, + } + }); + + return storedFile; + } + + async uploadFileToStorage( + uniqueId: string, + file: Express.Multer.File, + options?: { createThumbnail: boolean } + ): Promise<{ key: string; thumbnailKey?: string }> { const randomPrefix = nanoid(); - const key = `audits/${editUniqueId}/${randomPrefix}/${file.originalname}`; + const key: string = `audits/${uniqueId}/${randomPrefix}/${file.originalname}`; - let thumbnailKey; + let thumbnailKey: string; - if (file.mimetype.startsWith("image")) { + if (file.mimetype.startsWith("image") && options.createThumbnail) { // If it's an image, create a thumbnail and upload it - thumbnailKey = `audits/${editUniqueId}/${randomPrefix}/thumbnail_${file.originalname}`; + thumbnailKey = `audits/${uniqueId}/${randomPrefix}/thumbnail_${file.originalname}`; const thumbnailBuffer = await sharp(file.buffer) - .resize(200, 200, { fit: "inside" }) .jpeg({ mozjpeg: true }) + .flatten({ background: { r: 255, g: 255, b: 255, alpha: 0 } }) + .resize(200, 200, { fit: "inside" }) .toBuffer(); await Promise.all([ @@ -529,25 +544,7 @@ export class AuditService { } else { await this.fileStorageService.uploadFile(file.buffer, file.mimetype, key); } - - const storedFile = await this.prisma.auditFile.create({ - data: { - audit: { - connect: { - editUniqueId - } - }, - - key, - originalFilename: file.originalname, - mimetype: file.mimetype, - size: file.size, - - thumbnailKey - } - }); - - return storedFile; + return { key, thumbnailKey }; } /** From beeef1ba39405310ce2ac4de2b9e0b287a030680 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Mon, 9 Sep 2024 11:06:34 +0200 Subject: [PATCH 09/31] Add display field for uploads (ATTACHMENT or EDITOR) --- .../migration.sql | 8 ++++++++ confiture-rest-api/prisma/schema.prisma | 13 +++++++++++- .../src/audits/audit.service.ts | 20 ++++++++++++++----- .../src/audits/audits.controller.ts | 9 ++++++--- .../src/audits/dto/audit-report.dto.ts | 18 +++++++++++++---- .../src/audits/dto/notes-file.dto.ts | 9 +++++++++ .../src/audits/dto/upload-image.dto.ts | 3 +++ .../src/components/audit/NotesModal.vue | 9 +++++++-- confiture-web-app/src/store/audit.ts | 10 +++++++++- confiture-web-app/src/store/results.ts | 9 +++++++-- confiture-web-app/src/types/types.ts | 6 ++++++ 11 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 confiture-rest-api/prisma/migrations/20240906161141_add_display_field_on_files_attachment_or_editor/migration.sql create mode 100644 confiture-rest-api/src/audits/dto/notes-file.dto.ts diff --git a/confiture-rest-api/prisma/migrations/20240906161141_add_display_field_on_files_attachment_or_editor/migration.sql b/confiture-rest-api/prisma/migrations/20240906161141_add_display_field_on_files_attachment_or_editor/migration.sql new file mode 100644 index 00000000..503297a7 --- /dev/null +++ b/confiture-rest-api/prisma/migrations/20240906161141_add_display_field_on_files_attachment_or_editor/migration.sql @@ -0,0 +1,8 @@ +-- CreateEnum +CREATE TYPE "FileDisplay" AS ENUM ('EDITOR', 'ATTACHMENT'); + +-- AlterTable +ALTER TABLE "AuditFile" ADD COLUMN "display" "FileDisplay" NOT NULL DEFAULT 'ATTACHMENT'; + +-- AlterTable +ALTER TABLE "StoredFile" ADD COLUMN "display" "FileDisplay" NOT NULL DEFAULT 'ATTACHMENT'; diff --git a/confiture-rest-api/prisma/schema.prisma b/confiture-rest-api/prisma/schema.prisma index 3c1a51b2..3da624b4 100644 --- a/confiture-rest-api/prisma/schema.prisma +++ b/confiture-rest-api/prisma/schema.prisma @@ -169,6 +169,11 @@ model AuditTrace { Audit Audit? } +enum FileDisplay { + EDITOR + ATTACHMENT +} + model StoredFile { id Int @id @default(autoincrement()) originalFilename String @@ -184,6 +189,9 @@ model StoredFile { key String thumbnailKey String + // Inside TipTap editor (EDITOR) or added as an attachment (ATTACHMENT)? + display FileDisplay @default(ATTACHMENT) + criterionResult CriterionResult? @relation(fields: [criterionResultId], references: [id], onDelete: Cascade, onUpdate: Cascade) criterionResultId Int? } @@ -203,7 +211,10 @@ model AuditFile { key String thumbnailKey String? - audit Audit? @relation(fields: [auditUniqueId], references: [editUniqueId], onDelete: Cascade) + audit Audit? @relation(fields: [auditUniqueId], references: [editUniqueId], onDelete: Cascade) + // Inside TipTap editor (EDITOR) or added as an attachment (ATTACHMENT)? + display FileDisplay @default(ATTACHMENT) + auditUniqueId String? } diff --git a/confiture-rest-api/src/audits/audit.service.ts b/confiture-rest-api/src/audits/audit.service.ts index 7acc7208..61d5e602 100644 --- a/confiture-rest-api/src/audits/audit.service.ts +++ b/confiture-rest-api/src/audits/audit.service.ts @@ -4,6 +4,7 @@ import { CriterionResult, CriterionResultStatus, CriterionResultUserImpact, + FileDisplay, Prisma, StoredFile } from "@prisma/client"; @@ -418,7 +419,8 @@ export class AuditService { pageId: number, topic: number, criterium: number, - file: Express.Multer.File + file: Express.Multer.File, + display?: FileDisplay ) { const { key, thumbnailKey } = await this.uploadFileToStorage( editUniqueId, @@ -442,7 +444,8 @@ export class AuditService { originalFilename: file.originalname, mimetype: file.mimetype, size: file.size, - thumbnailKey + thumbnailKey, + display } }); @@ -483,7 +486,11 @@ export class AuditService { return true; } - async saveNotesFile(editUniqueId: string, file: Express.Multer.File) { + async saveNotesFile( + editUniqueId: string, + file: Express.Multer.File, + display: FileDisplay = FileDisplay.ATTACHMENT + ) { const { key, thumbnailKey } = await this.uploadFileToStorage( editUniqueId, file, @@ -504,6 +511,7 @@ export class AuditService { size: file.size, thumbnailKey, + display } }); @@ -829,7 +837,8 @@ export class AuditService { key: file.key, thumbnailKey: file.thumbnailKey, size: file.size, - mimetype: file.mimetype + mimetype: file.mimetype, + display: file.display })), criteriaCount: { @@ -996,7 +1005,8 @@ export class AuditService { exampleImages: r.exampleImages.map((img) => ({ filename: img.originalFilename, key: img.key, - thumbnailKey: img.thumbnailKey + thumbnailKey: img.thumbnailKey, + display: img.display })) })) }; diff --git a/confiture-rest-api/src/audits/audits.controller.ts b/confiture-rest-api/src/audits/audits.controller.ts index d2a7c92a..391737ae 100644 --- a/confiture-rest-api/src/audits/audits.controller.ts +++ b/confiture-rest-api/src/audits/audits.controller.ts @@ -28,6 +28,7 @@ import { import { Audit } from "src/generated/nestjs-dto/audit.entity"; import { CriterionResult } from "src/generated/nestjs-dto/criterionResult.entity"; import { MailService } from "../mail/mail.service"; +import { NotesFileDto } from "./dto/notes-file.dto"; import { AuditExportService } from "./audit-export.service"; import { AuditService } from "./audit.service"; import { CreateAuditDto } from "./dto/create-audit.dto"; @@ -174,7 +175,8 @@ export class AuditsController { body.pageId, body.topic, body.criterium, - file + file, + body.display ); } @@ -191,7 +193,8 @@ export class AuditsController { errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }) ) - file: Express.Multer.File + file: Express.Multer.File, + @Body() body: NotesFileDto ) { const audit = await this.auditService.getAuditWithEditUniqueId(uniqueId); @@ -199,7 +202,7 @@ export class AuditsController { return this.sendAuditNotFoundStatus(uniqueId); } - return await this.auditService.saveNotesFile(uniqueId, file); + return await this.auditService.saveNotesFile(uniqueId, file, body.display); } @Delete("/:uniqueId/results/examples/:exampleId") diff --git a/confiture-rest-api/src/audits/dto/audit-report.dto.ts b/confiture-rest-api/src/audits/dto/audit-report.dto.ts index 0ac3e5be..dca8d774 100644 --- a/confiture-rest-api/src/audits/dto/audit-report.dto.ts +++ b/confiture-rest-api/src/audits/dto/audit-report.dto.ts @@ -2,7 +2,8 @@ import { ApiProperty } from "@nestjs/swagger"; import { AuditType, CriterionResultStatus, - CriterionResultUserImpact + CriterionResultUserImpact, + FileDisplay } from "@prisma/client"; export class AuditReportDto { @@ -190,18 +191,27 @@ class ReportCriterionResult { } class ExampleImage { - /** @example "mon-image.jpg" */ + /** @example "my-image.jpg" */ filename: string; - /** @example "audit/xxxx/my-image.jpg" */ + /** @example "audit/EWIsM6sYI2cC0lI7Ok2PE/3gnCTQ5ztOdEnKRraIMYG/my-image.jpg" */ key: string; - /** @example "audit/xxxx/my-image_thumbnail.jpg" */ + /** @example "audit/EWIsM6sYI2cC0lI7Ok2PE/3gnCTQ5ztOdEnKRraIMYG/my-image_thumbnail.jpg" */ thumbnailKey: string; + /** @example "ATTACHMENT" */ + display: FileDisplay; } class NotesFile { + /** @example "screenshot_001.png" */ originalFilename: string; + /** @example "audits/EWIsM6sYI2cC0lI7Ok2PE/uqoOes4QqhFyKV8v0s2AQ/screenshot_001.png" */ key: string; + /** @example "audits/EWIsM6sYI2cC0lI7Ok2PE/uqoOes4QqhFyKV8v0s2AQ/thumbnail_screenshot_001.png" */ thumbnailKey: string; + /** @example 4631 */ size: number; + /** @example "image/png" */ mimetype: string; + /** @example "ATTACHMENT" */ + display: FileDisplay; } diff --git a/confiture-rest-api/src/audits/dto/notes-file.dto.ts b/confiture-rest-api/src/audits/dto/notes-file.dto.ts new file mode 100644 index 00000000..8e3bc9cb --- /dev/null +++ b/confiture-rest-api/src/audits/dto/notes-file.dto.ts @@ -0,0 +1,9 @@ +import { FileDisplay } from "@prisma/client"; +import { IsIn, IsOptional, IsString } from "class-validator"; + +export class NotesFileDto { + @IsOptional() + @IsString() + @IsIn(Object.values(FileDisplay)) + display?: FileDisplay; +} diff --git a/confiture-rest-api/src/audits/dto/upload-image.dto.ts b/confiture-rest-api/src/audits/dto/upload-image.dto.ts index 810f5847..5b9c9826 100644 --- a/confiture-rest-api/src/audits/dto/upload-image.dto.ts +++ b/confiture-rest-api/src/audits/dto/upload-image.dto.ts @@ -1,6 +1,7 @@ import { Type } from "class-transformer"; import { IsInt, IsNumber, IsPositive, Max, Min } from "class-validator"; import { IsRgaaCriterium } from "./update-results.dto"; +import { FileDisplay } from "@prisma/client"; /* The `@Type(() => Number)` decorator is required to correctly parse strings into numbers @@ -34,4 +35,6 @@ export class UploadImageDto { "topic and criterium numbers must be a valid RGAA criterium combination" }) criterium: number; + + display: FileDisplay; } diff --git a/confiture-web-app/src/components/audit/NotesModal.vue b/confiture-web-app/src/components/audit/NotesModal.vue index 0e7d98c8..f793c463 100644 --- a/confiture-web-app/src/components/audit/NotesModal.vue +++ b/confiture-web-app/src/components/audit/NotesModal.vue @@ -6,7 +6,7 @@ import { useRoute } from "vue-router"; import { useIsOffline } from "../../composables/useIsOffline"; import { FileErrorMessage } from "../../enums"; import { useAuditStore } from "../../store/audit"; -import { AuditFile, StoreName } from "../../types"; +import { AuditFile, FileDisplay, StoreName } from "../../types"; import { handleFileDeleteError, handleFileUploadError } from "../../utils"; import DsfrModal from "../ui/DsfrModal.vue"; import FileUpload from "../ui/FileUpload.vue"; @@ -40,7 +40,12 @@ const isOffline = useIsOffline(); const notes = ref(auditStore.currentAudit?.notes || ""); const uniqueId = computed(() => route.params.uniqueId as string); -const files = computed(() => auditStore.currentAudit?.notesFiles || []); +const files = computed( + () => + auditStore.currentAudit?.notesFiles?.filter( + (e) => e.display === FileDisplay.ATTACHMENT + ) || [] +); const handleNotesChange = debounce(() => emit("confirm", notes.value), 500); diff --git a/confiture-web-app/src/store/audit.ts b/confiture-web-app/src/store/audit.ts index 4db3a3a3..92a484f2 100644 --- a/confiture-web-app/src/store/audit.ts +++ b/confiture-web-app/src/store/audit.ts @@ -5,6 +5,7 @@ import { Audit, AuditFile, CreateAuditRequestData, + FileDisplay, UpdateAuditRequestData } from "../types"; import { AccountAudit } from "../types/account"; @@ -126,10 +127,17 @@ export const useAuditStore = defineStore("audit", { } }, - async uploadAuditFile(uniqueId: string, file: File) { + async uploadAuditFile( + uniqueId: string, + file: File, + display?: FileDisplay + ): Promise { const formData = new FormData(); // To handle non-ascii characters, we encode the filename here and decode it on the back formData.set("file", file, encodeURI(file.name)); + if (display) { + formData.set("display", display.toString()); + } this.increaseCurrentRequestCount(); const notesFile = (await ky diff --git a/confiture-web-app/src/store/results.ts b/confiture-web-app/src/store/results.ts index 709064eb..b52a4e7d 100644 --- a/confiture-web-app/src/store/results.ts +++ b/confiture-web-app/src/store/results.ts @@ -6,7 +6,8 @@ import { AuditFile, CriterionResultUserImpact, CriteriumResult, - CriteriumResultStatus + CriteriumResultStatus, + FileDisplay } from "../types"; import { useAuditStore } from "./audit"; import { useFiltersStore } from "./filters"; @@ -343,7 +344,8 @@ export const useResultsStore = defineStore("results", { pageId: number, topic: number, criterium: number, - file: File + file: File, + display?: FileDisplay ) { const formData = new FormData(); formData.set("pageId", pageId.toString()); @@ -351,6 +353,9 @@ export const useResultsStore = defineStore("results", { formData.set("criterium", criterium.toString()); // To handle non-ascii characters, we encode the filename here and decode it on the back formData.set("image", file, encodeURI(file.name)); + if (display) { + formData.set("display", display.toString()); + } this.increaseCurrentRequestCount(); diff --git a/confiture-web-app/src/types/types.ts b/confiture-web-app/src/types/types.ts index fc48a08d..7621b44e 100644 --- a/confiture-web-app/src/types/types.ts +++ b/confiture-web-app/src/types/types.ts @@ -113,6 +113,12 @@ export interface AuditFile { key: string; mimetype: string; thumbnailKey: string; + display: FileDisplay; +} + +export enum FileDisplay { + ATTACHMENT = "ATTACHMENT", + EDITOR = "EDITOR" } export interface CriteriumResult { From 7a165f8b38abbe368ab0d983e6d18e954e2acd83 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 13 Sep 2024 15:59:39 +0200 Subject: [PATCH 10/31] Tiptap: handle images drag and drop Add a new extension: ImageUploadTiptapExtension --- confiture-web-app/package.json | 1 + .../src/components/audit/NotesModal.vue | 12 +- .../src/components/ui/Tiptap.vue | 19 +- confiture-web-app/src/store/audit.ts | 1 + confiture-web-app/src/styles/main.css | 39 ++++ .../src/tiptap/ImageUploadTiptapExtension.ts | 166 ++++++++++++++++++ yarn.lock | 5 + 7 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 confiture-web-app/src/tiptap/ImageUploadTiptapExtension.ts diff --git a/confiture-web-app/package.json b/confiture-web-app/package.json index 5ddddb7f..1a8eea77 100644 --- a/confiture-web-app/package.json +++ b/confiture-web-app/package.json @@ -19,6 +19,7 @@ "@sentry/vue": "^7.37.2", "@tiptap/extension-code-block-lowlight": "^2.5.9", "@tiptap/extension-highlight": "^2.5.9", + "@tiptap/extension-image": "^2.6.6", "@tiptap/extension-link": "^2.5.9", "@tiptap/extension-task-item": "^2.5.9", "@tiptap/extension-task-list": "^2.5.9", diff --git a/confiture-web-app/src/components/audit/NotesModal.vue b/confiture-web-app/src/components/audit/NotesModal.vue index f793c463..8869f352 100644 --- a/confiture-web-app/src/components/audit/NotesModal.vue +++ b/confiture-web-app/src/components/audit/NotesModal.vue @@ -47,7 +47,10 @@ const files = computed( ) || [] ); -const handleNotesChange = debounce(() => emit("confirm", notes.value), 500); +const handleNotesChange = debounce((notesContent: string) => { + notes.value = notesContent; + emit("confirm", notes.value); +}, 500); function handleUploadFile(file: File) { auditStore @@ -116,8 +119,7 @@ function handleDeleteFile(file: AuditFile) { rows="10" :disabled="isOffline" aria-describedby="notes-markdown" - @input="handleNotesChange" - @update:content="($content) => (notes = $content)" + @update:content="handleNotesChange" /> (); const emit = defineEmits(["update:content"]); +const uniqueId = computed(() => route.params.uniqueId as string); + function getContent() { let jsonContent; try { @@ -57,6 +68,10 @@ const editor = useEditor({ }), TaskItem, TaskList, + ImageExtension.configure({ inline: false }), + ImageUploadTiptapExtension.configure({ + uniqueId: uniqueId.value + }), Typography.configure({ openDoubleQuote: "« ", closeDoubleQuote: " »" @@ -66,7 +81,7 @@ const editor = useEditor({ // The content has changed. emit("update:content", JSON.stringify(editor.getJSON())); } -}); +}) as ShallowRef;