aboutsummaryrefslogtreecommitdiff
path: root/src/tools
diff options
context:
space:
mode:
authorGravatar Corentin Thomasset <corentin.thomasset74@gmail.com> 2023-03-26 19:04:42 +0200
committerGravatar Corentin THOMASSET <corentin.thomasset74@gmail.com> 2023-03-26 20:21:00 +0200
commitf3b1863f093124309963c3ad7c275142cd370b0f (patch)
treec8dd861e9d5d253bcb655c2c055965fae7f952c5 /src/tools
parentb1d6bfd2dcb718b486dcd708adcdea65341b3d16 (diff)
downloadit-tools-f3b1863f093124309963c3ad7c275142cd370b0f.tar.gz
it-tools-f3b1863f093124309963c3ad7c275142cd370b0f.tar.zst
it-tools-f3b1863f093124309963c3ad7c275142cd370b0f.zip
feat(new-tool): html wysiwyg editor
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/html-wysiwyg-editor/editor/editor.vue135
-rw-r--r--src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue22
-rw-r--r--src/tools/html-wysiwyg-editor/editor/menu-bar.vue171
-rw-r--r--src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue17
-rw-r--r--src/tools/html-wysiwyg-editor/index.ts11
-rw-r--r--src/tools/index.ts2
6 files changed, 358 insertions, 0 deletions
diff --git a/src/tools/html-wysiwyg-editor/editor/editor.vue b/src/tools/html-wysiwyg-editor/editor/editor.vue
new file mode 100644
index 0000000..346cb10
--- /dev/null
+++ b/src/tools/html-wysiwyg-editor/editor/editor.vue
@@ -0,0 +1,135 @@
+<template>
+ <n-card v-if="editor" class="editor">
+ <template #header>
+ <menu-bar class="editor-header" :editor="editor" />
+ <n-divider style="margin-top: 0" />
+ </template>
+
+ <editor-content class="editor-content" :editor="editor" />
+ </n-card>
+</template>
+
+<script setup lang="ts">
+import { tryOnBeforeUnmount, useVModel } from '@vueuse/core';
+import { Editor, EditorContent } from '@tiptap/vue-3';
+import StarterKit from '@tiptap/starter-kit';
+import { useThemeVars } from 'naive-ui';
+import MenuBar from './menu-bar.vue';
+
+const themeVars = useThemeVars();
+const props = defineProps<{ html: string }>();
+const emit = defineEmits(['update:html']);
+const html = useVModel(props, 'html', emit);
+
+const editor = new Editor({
+ content: html.value,
+ extensions: [StarterKit],
+});
+
+editor.on('update', ({ editor }) => emit('update:html', editor.getHTML()));
+
+tryOnBeforeUnmount(() => {
+ editor.destroy();
+});
+</script>
+
+<style scoped lang="less">
+::v-deep(.n-card-header) {
+ padding: 0;
+}
+
+::v-deep(.ProseMirror-focused) {
+ outline: none;
+}
+</style>
+
+<style scoped lang="less">
+::v-deep(.ProseMirror) {
+ > * + * {
+ margin-top: 0.75em;
+ }
+
+ p {
+ margin: 0;
+ }
+
+ ul,
+ ol {
+ padding: 0 1rem;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ line-height: 1.1;
+ }
+
+ code {
+ background-color: v-bind('themeVars.codeColor');
+ padding: 2px 4px;
+ border-radius: 5px;
+ font-size: 85%;
+ }
+
+ pre {
+ background: v-bind('themeVars.codeColor');
+ font-family: monospace;
+ padding: 0.75rem 1rem;
+ border-radius: 0.5rem;
+
+ code {
+ color: inherit;
+ padding: 0;
+ background: none;
+ font-size: 0.8rem;
+ }
+ }
+
+ mark {
+ background-color: #faf594;
+ }
+
+ img {
+ max-width: 100%;
+ height: auto;
+ }
+
+ hr {
+ margin: 1rem 0;
+ }
+
+ blockquote {
+ padding-left: 1rem;
+ border-left: 2px solid rgba(#0d0d0d, 0.1);
+ }
+
+ hr {
+ border: none;
+ border-top: 2px solid rgba(#0d0d0d, 0.1);
+ margin: 2rem 0;
+ }
+
+ ul[data-type='taskList'] {
+ list-style: none;
+ padding: 0;
+
+ li {
+ display: flex;
+ align-items: center;
+
+ > label {
+ flex: 0 0 auto;
+ margin-right: 0.5rem;
+ user-select: none;
+ }
+
+ > div {
+ flex: 1 1 auto;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue b/src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue
new file mode 100644
index 0000000..52bc0d2
--- /dev/null
+++ b/src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue
@@ -0,0 +1,22 @@
+<template>
+ <n-tooltip trigger="hover">
+ <template #trigger>
+ <n-button circle quaternary :type="isActive?.() ? 'primary' : 'default'" @click="action">
+ <template #icon>
+ <n-icon :component="icon" />
+ </template>
+ </n-button>
+ </template>
+
+ {{ title }}
+ </n-tooltip>
+</template>
+
+<script setup lang="ts">
+import { toRefs, type Component } from 'vue';
+
+const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean }>();
+const { icon, title, action, isActive } = toRefs(props);
+</script>
+
+<style scoped></style>
diff --git a/src/tools/html-wysiwyg-editor/editor/menu-bar.vue b/src/tools/html-wysiwyg-editor/editor/menu-bar.vue
new file mode 100644
index 0000000..5304cd3
--- /dev/null
+++ b/src/tools/html-wysiwyg-editor/editor/menu-bar.vue
@@ -0,0 +1,171 @@
+<template>
+ <n-space align="center" :size="0">
+ <template v-for="(item, index) in items">
+ <n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical />
+ <menu-bar-item v-else-if="item.type === 'button'" :key="index" v-bind="item" />
+ </template>
+ </n-space>
+</template>
+
+<script setup lang="ts">
+import type { Editor } from '@tiptap/vue-3';
+import {
+ ArrowBack,
+ ArrowForwardUp,
+ Blockquote,
+ Bold,
+ ClearFormatting,
+ Code,
+ CodePlus,
+ H1,
+ H2,
+ H3,
+ H4,
+ Italic,
+ Link,
+ List,
+ ListNumbers,
+ Separator,
+ Strikethrough,
+ TextWrap,
+} from '@vicons/tabler';
+import { toRefs, type Component } from 'vue';
+import MenuBarItem from './menu-bar-item.vue';
+
+const props = defineProps<{ editor: Editor }>();
+const { editor } = toRefs(props);
+
+type MenuItem =
+ | {
+ icon: Component;
+ title: string;
+ action: () => void;
+ isActive?: () => boolean;
+ type: 'button';
+ }
+ | { type: 'divider' };
+
+const items: MenuItem[] = [
+ {
+ type: 'button',
+ icon: Bold,
+ title: 'Bold',
+ action: () => editor.value.chain().focus().toggleBold().run(),
+ isActive: () => editor.value.isActive('bold'),
+ },
+ {
+ type: 'button',
+ icon: Italic,
+ title: 'Italic',
+ action: () => editor.value.chain().focus().toggleItalic().run(),
+ isActive: () => editor.value.isActive('italic'),
+ },
+ {
+ type: 'button',
+ icon: Strikethrough,
+ title: 'Strike',
+ action: () => editor.value.chain().focus().toggleStrike().run(),
+ isActive: () => editor.value.isActive('strike'),
+ },
+ {
+ type: 'button',
+ icon: Code,
+ title: 'Inline code',
+ action: () => editor.value.chain().focus().toggleCode().run(),
+ isActive: () => editor.value.isActive('code'),
+ },
+ {
+ type: 'divider',
+ },
+ {
+ type: 'button',
+ icon: H1,
+ title: 'Heading 1',
+ action: () => editor.value.chain().focus().toggleHeading({ level: 1 }).run(),
+ isActive: () => editor.value.isActive('heading', { level: 1 }),
+ },
+ {
+ type: 'button',
+ icon: H2,
+ title: 'Heading 2',
+ action: () => editor.value.chain().focus().toggleHeading({ level: 2 }).run(),
+ isActive: () => editor.value.isActive('heading', { level: 2 }),
+ },
+ {
+ type: 'button',
+ icon: H3,
+ title: 'Heading 3',
+ action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(),
+ isActive: () => editor.value.isActive('heading', { level: 4 }),
+ },
+ {
+ type: 'button',
+ icon: H4,
+ title: 'Heading 4',
+ action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(),
+ isActive: () => editor.value.isActive('heading', { level: 4 }),
+ },
+ {
+ type: 'divider',
+ },
+ {
+ type: 'button',
+ icon: List,
+ title: 'Bullet list',
+ action: () => editor.value.chain().focus().toggleBulletList().run(),
+ isActive: () => editor.value.isActive('bulletList'),
+ },
+ {
+ type: 'button',
+ icon: ListNumbers,
+ title: 'Ordered list',
+ action: () => editor.value.chain().focus().toggleOrderedList().run(),
+ isActive: () => editor.value.isActive('orderedList'),
+ },
+ {
+ type: 'button',
+ icon: CodePlus,
+ title: 'Code block',
+ action: () => editor.value.chain().focus().toggleCodeBlock().run(),
+ isActive: () => editor.value.isActive('codeBlock'),
+ },
+
+ {
+ type: 'button',
+ icon: Blockquote,
+ title: 'Blockquote',
+ action: () => editor.value.chain().focus().toggleBlockquote().run(),
+ isActive: () => editor.value.isActive('blockquote'),
+ },
+ {
+ type: 'divider',
+ },
+ {
+ type: 'button',
+ icon: TextWrap,
+ title: 'Hard break',
+ action: () => editor.value.chain().focus().setHardBreak().run(),
+ },
+ {
+ type: 'button',
+ icon: ClearFormatting,
+ title: 'Clear format',
+ action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(),
+ },
+
+ {
+ type: 'button',
+ icon: ArrowBack,
+ title: 'Undo',
+ action: () => editor.value.chain().focus().undo().run(),
+ },
+ {
+ type: 'button',
+ icon: ArrowForwardUp,
+ title: 'Redo',
+ action: () => editor.value.chain().focus().redo().run(),
+ },
+];
+</script>
+
+<style scoped></style>
diff --git a/src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue b/src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue
new file mode 100644
index 0000000..b153769
--- /dev/null
+++ b/src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue
@@ -0,0 +1,17 @@
+<template>
+ <editor v-model:html="html" />
+ <textarea-copyable :value="format(html, { parser: 'html', plugins: [htmlParser] })" language="html" />
+</template>
+
+<script setup lang="ts">
+import TextareaCopyable from '@/components/TextareaCopyable.vue';
+import { ref } from 'vue';
+import { format } from 'prettier';
+import htmlParser from 'prettier/parser-html';
+import { useStorage } from '@vueuse/core';
+import Editor from './editor/editor.vue';
+
+const html = useStorage('html-wysiwyg-editor--html', '<h1>Hey!</h1><p>Welcome to this html wysiwyg editor</p>');
+</script>
+
+<style lang="less" scoped></style>
diff --git a/src/tools/html-wysiwyg-editor/index.ts b/src/tools/html-wysiwyg-editor/index.ts
new file mode 100644
index 0000000..985f3c1
--- /dev/null
+++ b/src/tools/html-wysiwyg-editor/index.ts
@@ -0,0 +1,11 @@
+import { Edit } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Html wysiwyg editor',
+ path: '/html-wysiwyg-editor',
+ description: 'Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately.',
+ keywords: ['html', 'wysiwyg', 'editor', 'p', 'ul', 'ol', 'converter', 'live'],
+ component: () => import('./html-wysiwyg-editor.vue'),
+ icon: Edit,
+});
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 18208dd..4b4ff22 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -1,6 +1,7 @@
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
+import { tool as htmlWysiwygEditor } from './html-wysiwyg-editor';
import { tool as rsaKeyPairGenerator } from './rsa-key-pair-generator';
import { tool as textToNatoAlphabet } from './text-to-nato-alphabet';
import { tool as slugifyString } from './slugify-string';
@@ -74,6 +75,7 @@ export const toolsByCategory: ToolCategory[] = [
jwtParser,
keycodeInfo,
slugifyString,
+ htmlWysiwygEditor,
],
},
{