From 1c6a5c9d04217c931c199ffe168c78e3af09921c Mon Sep 17 00:00:00 2001 From: James Collins Date: Fri, 7 Jul 2023 22:54:34 +1000 Subject: [PATCH] editor expansion --- package-lock.json | 26 ++ package.json | 2 + resources/js/components/SMDropdown.vue | 15 +- resources/js/components/SMEditor.vue | 583 +++++++++++++++++-------- resources/js/extensions/info.ts | 60 +++ resources/views/app.blade.php | 52 +++ 6 files changed, 549 insertions(+), 189 deletions(-) create mode 100644 resources/js/extensions/info.ts diff --git a/package-lock.json b/package-lock.json index 81462b0..57dc48d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,8 @@ "packages": { "": { "dependencies": { + "@tiptap/extension-text-align": "^2.0.3", + "@tiptap/extension-underline": "^2.0.3", "@tiptap/pm": "^2.0.3", "@tiptap/starter-kit": "^2.0.3", "@tiptap/vue-3": "^2.0.3", @@ -1632,6 +1634,30 @@ "@tiptap/core": "^2.0.0" } }, + "node_modules/@tiptap/extension-text-align": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.0.3.tgz", + "integrity": "sha512-VlLgqncKdjMjVjbU60/ALYhFs0wUdjAyvjDXnH1OoM/HuzbILvufPMYz4DUieJIWVJOYUKHQgg4XwBWceAM2Tw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.0.3.tgz", + "integrity": "sha512-oMYa7qib/5wJjpUp79GZEe+E/iyf1oZBsgiG26IspEtVTHZmpn3+Ktud7l43y/hpTeEzFTKOF1/uVbayHtSERg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, "node_modules/@tiptap/pm": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.3.tgz", diff --git a/package.json b/package.json index 8f76d68..42b1e77 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "vitest": "^0.32.0" }, "dependencies": { + "@tiptap/extension-text-align": "^2.0.3", + "@tiptap/extension-underline": "^2.0.3", "@tiptap/pm": "^2.0.3", "@tiptap/starter-kit": "^2.0.3", "@tiptap/vue-3": "^2.0.3", diff --git a/resources/js/components/SMDropdown.vue b/resources/js/components/SMDropdown.vue index 36bb7a7..df45afb 100644 --- a/resources/js/components/SMDropdown.vue +++ b/resources/js/components/SMDropdown.vue @@ -29,6 +29,7 @@ 'px-4', 'pt-5', 'flex-1', + 'bg-white', { 'bg-gray-1': disabled }, ]" v-bind="{ @@ -121,21 +122,21 @@ const label = ref( ? props.label : typeof props.control == "string" ? toTitleCase(props.control) - : "" + : "", ); const value = ref( props.modelValue != undefined ? props.modelValue : control != null ? control.value - : "" + : "", ); const id = ref( props.id != undefined ? props.id : typeof props.control == "string" && props.control.length > 0 ? props.control - : generateRandomElementId() + : generateRandomElementId(), ); const active = ref(value.value?.toString().length ?? 0 > 0); const focused = ref(false); @@ -145,7 +146,7 @@ watch( () => value.value, (newValue) => { active.value = newValue.toString().length > 0 || focused.value == true; - } + }, ); if (props.modelValue != undefined) { @@ -153,7 +154,7 @@ if (props.modelValue != undefined) { () => props.modelValue, (newValue) => { value.value = newValue; - } + }, ); } @@ -161,7 +162,7 @@ watch( () => props.disabled, (newValue) => { disabled.value = newValue; - } + }, ); if (typeof control === "object" && control !== null) { @@ -170,7 +171,7 @@ if (typeof control === "object" && control !== null) { (newValue) => { value.value = newValue; }, - { deep: true } + { deep: true }, ); } diff --git a/resources/js/components/SMEditor.vue b/resources/js/components/SMEditor.vue index 31e9344..5f6f474 100644 --- a/resources/js/components/SMEditor.vue +++ b/resources/js/components/SMEditor.vue @@ -1,156 +1,387 @@ @@ -158,6 +389,9 @@ import { onBeforeUnmount, watch } from "vue"; import { useEditor, EditorContent } from "@tiptap/vue-3"; import StarterKit from "@tiptap/starter-kit"; +import Underline from "@tiptap/extension-underline"; +import TextAlign from "@tiptap/extension-text-align"; +import { Info } from "../extensions/info"; const props = defineProps({ modelValue: { @@ -170,12 +404,40 @@ const emits = defineEmits(["update:modelValue"]); const editor = useEditor({ content: props.modelValue, - extensions: [StarterKit], + extensions: [StarterKit, Underline, TextAlign, Info], onUpdate: () => { emits("update:modelValue", editor.value.getHTML()); }, }); +const updateNode = (event) => { + if (event.target.value) { + switch (event.target.value) { + case "paragraph": + editor.value.chain().focus().setParagraph().run(); + break; + case "h1": + editor.value.chain().focus().setHeading({ level: 1 }).run(); + break; + case "h2": + editor.value.chain().focus().setHeading({ level: 2 }).run(); + break; + case "h3": + editor.value.chain().focus().setHeading({ level: 3 }).run(); + break; + case "h4": + editor.value.chain().focus().setHeading({ level: 4 }).run(); + break; + case "h5": + editor.value.chain().focus().setHeading({ level: 5 }).run(); + break; + case "h6": + editor.value.chain().focus().setHeading({ level: 6 }).run(); + break; + } + } +}; + onBeforeUnmount(() => { editor.value.destroy(); }); @@ -193,46 +455,3 @@ watch( }, ); - - diff --git a/resources/js/extensions/info.ts b/resources/js/extensions/info.ts new file mode 100644 index 0000000..80868dc --- /dev/null +++ b/resources/js/extensions/info.ts @@ -0,0 +1,60 @@ +import { mergeAttributes, Node } from "@tiptap/core"; + +export interface InfoOptions { + HTMLAttributes: Record; +} + +declare module "@tiptap/core" { + interface Commands { + info: { + /** + * Toggle a paragraph + */ + setInfo: () => ReturnType; + toggleInfo: () => ReturnType; + }; + } +} + +export const Info = Node.create({ + name: "info", + + priority: 1000, + + addOptions() { + return { + HTMLAttributes: { class: "info" }, + }; + }, + + group: "block", + + content: "inline*", + + parseHTML() { + return [{ tag: "p", class: "info" }]; + }, + + renderHTML({ HTMLAttributes }) { + return [ + "p", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + 0, + ]; + }, + + addCommands() { + return { + setInfo: + () => + ({ commands }) => { + return commands.setNode(this.name); + }, + toggleInfo: + () => + ({ commands }) => { + return commands.toggleNode(this.name, "paragraph"); + }, + }; + }, +}); diff --git a/resources/views/app.blade.php b/resources/views/app.blade.php index 684e65b..8b0bb02 100644 --- a/resources/views/app.blade.php +++ b/resources/views/app.blade.php @@ -38,6 +38,58 @@ .scrollbar-width-none { scrollbar-width: none; } .scrollbar-width-none::-webkit-scrollbar { display: none; } .spin{animation:rotate 1s infinite linear} + .sm-html .ProseMirror { + outline: none; + } + + .sm-html hr { + border-top: 1px solid #aaa; + margin: 1.5rem 0; + } + + .sm-html pre { + padding: 0 1rem; + line-height: 1rem; + } + + .sm-html blockquote { + border-left: 4px solid #ddd; + margin-left: 1rem; + padding-left: 1rem; + } + + .sm-html p.info { + display: flex; + border: 1px solid rgba(14,165,233,1); + background-color: rgba(14,165,233,0.25); + border-radius: 0.5rem; + padding: 0.5rem 1rem 0.5rem 0.75rem; + margin: 0.5rem; + font-size: 80%; + } + + .sm-html p.info::before { + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z' fill='currentColor' /%3E%3C/svg%3E"); + display: inline-block; + color: rgba(14,165,233,1); + width: 1.5rem; + height: 1.5rem; + margin-right: 0.5rem; + margin-top: 0.1rem; + } + + .sm-editor::-webkit-scrollbar { + background-color: transparent; + width: 16px; + } + + .sm-editor::-webkit-scrollbar-thumb { + background-color: #aaa; + border: 4px solid transparent; + border-radius: 8px; + background-clip: padding-box; + } + @keyframes rotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}