mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-04 13:29:13 -04:00
parent
318fb6efb9
commit
e3e8e9d4e4
6 changed files with 2160 additions and 1491 deletions
2
components.d.ts
vendored
2
components.d.ts
vendored
|
@ -131,7 +131,9 @@ declare module '@vue/runtime-core' {
|
||||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||||
NCode: typeof import('naive-ui')['NCode']
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
|
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
|
NDivider: typeof import('naive-ui')['NDivider']
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
|
14
package.json
14
package.json
|
@ -38,9 +38,17 @@
|
||||||
"@it-tools/bip39": "^0.0.4",
|
"@it-tools/bip39": "^0.0.4",
|
||||||
"@it-tools/oggen": "^1.3.0",
|
"@it-tools/oggen": "^1.3.0",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"@tiptap/pm": "2.1.6",
|
"@tiptap/extension-color": "^2.7.4",
|
||||||
"@tiptap/starter-kit": "2.1.6",
|
"@tiptap/extension-gapcursor": "^2.7.4",
|
||||||
"@tiptap/vue-3": "2.0.3",
|
"@tiptap/extension-highlight": "^2.7.4",
|
||||||
|
"@tiptap/extension-table": "^2.7.4",
|
||||||
|
"@tiptap/extension-table-cell": "^2.7.4",
|
||||||
|
"@tiptap/extension-table-header": "^2.7.4",
|
||||||
|
"@tiptap/extension-table-row": "^2.7.4",
|
||||||
|
"@tiptap/extension-text-style": "^2.7.4",
|
||||||
|
"@tiptap/pm": "2.7.4",
|
||||||
|
"@tiptap/starter-kit": "2.7.4",
|
||||||
|
"@tiptap/vue-3": "2.7.4",
|
||||||
"@types/figlet": "^1.5.8",
|
"@types/figlet": "^1.5.8",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
"@vicons/tabler": "^0.12.0",
|
"@vicons/tabler": "^0.12.0",
|
||||||
|
|
3348
pnpm-lock.yaml
generated
3348
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,13 @@ import { tryOnBeforeUnmount, useVModel } from '@vueuse/core';
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3';
|
import { Editor, EditorContent } from '@tiptap/vue-3';
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
import { useThemeVars } from 'naive-ui';
|
import { useThemeVars } from 'naive-ui';
|
||||||
|
import { Color } from '@tiptap/extension-color';
|
||||||
|
import TextStyle from '@tiptap/extension-text-style';
|
||||||
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
|
import Table from '@tiptap/extension-table';
|
||||||
|
import TableCell from '@tiptap/extension-table-cell';
|
||||||
|
import TableHeader from '@tiptap/extension-table-header';
|
||||||
|
import TableRow from '@tiptap/extension-table-row';
|
||||||
import MenuBar from './menu-bar.vue';
|
import MenuBar from './menu-bar.vue';
|
||||||
|
|
||||||
const props = defineProps<{ html: string }>();
|
const props = defineProps<{ html: string }>();
|
||||||
|
@ -12,7 +19,18 @@ const html = useVModel(props, 'html', emit);
|
||||||
|
|
||||||
const editor = new Editor({
|
const editor = new Editor({
|
||||||
content: html.value,
|
content: html.value,
|
||||||
extensions: [StarterKit],
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
TextStyle,
|
||||||
|
Color,
|
||||||
|
Highlight.configure({ multicolor: true }),
|
||||||
|
Table.configure({
|
||||||
|
resizable: true,
|
||||||
|
}),
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell,
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.on('update', ({ editor }) => emit('update:html', editor.getHTML()));
|
editor.on('update', ({ editor }) => emit('update:html', editor.getHTML()));
|
||||||
|
@ -63,6 +81,63 @@ tryOnBeforeUnmount(() => {
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Table-specific styling */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid v-bind('themeVars.borderColor');
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 1em;
|
||||||
|
padding: 6px 8px;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: v-bind('themeVars.tableHeaderColor');
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedCell:after {
|
||||||
|
content: "";
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-resize-handle {
|
||||||
|
background-color: v-bind('themeVars.actionColor');
|
||||||
|
bottom: -2px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
top: 0;
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWrapper {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.resize-cursor {
|
||||||
|
cursor: ew-resize;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: v-bind('themeVars.codeColor');
|
background-color: v-bind('themeVars.codeColor');
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
|
@ -84,10 +159,6 @@ tryOnBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mark {
|
|
||||||
background-color: #faf594;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean }>();
|
const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean; enabled?: () => boolean }>();
|
||||||
const { icon, title, action, isActive } = toRefs(props);
|
const { icon, title, action, isActive, enabled } = toRefs(props);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-tooltip :tooltip="title">
|
<c-tooltip :tooltip="title">
|
||||||
<c-button circle variant="text" :type="isActive?.() ? 'primary' : 'default'" @click="action">
|
<c-button circle variant="text" :disabled="enabled && !enabled()" :type="isActive?.() ? 'primary' : 'default'" @click="action">
|
||||||
<n-icon :component="icon" />
|
<n-icon :component="icon" />
|
||||||
</c-button>
|
</c-button>
|
||||||
</c-tooltip>
|
</c-tooltip>
|
||||||
|
|
|
@ -8,15 +8,25 @@ import {
|
||||||
ClearFormatting,
|
ClearFormatting,
|
||||||
Code,
|
Code,
|
||||||
CodePlus,
|
CodePlus,
|
||||||
|
ColorPicker,
|
||||||
|
ColumnInsertLeft,
|
||||||
|
ColumnInsertRight,
|
||||||
|
Cross,
|
||||||
H1,
|
H1,
|
||||||
H2,
|
H2,
|
||||||
H3,
|
H3,
|
||||||
H4,
|
H4,
|
||||||
|
Heading,
|
||||||
Italic,
|
Italic,
|
||||||
|
LayersIntersect2,
|
||||||
|
LayersUnion,
|
||||||
|
LayoutDistributeHorizontal,
|
||||||
|
LayoutDistributeVertical,
|
||||||
List,
|
List,
|
||||||
ListNumbers,
|
ListNumbers,
|
||||||
Strikethrough,
|
RowInsertBottom,
|
||||||
TextWrap,
|
RowInsertTop,
|
||||||
|
SeparatorVertical, Strikethrough, Table, TableOff, TextWrap, Tool,
|
||||||
} from '@vicons/tabler';
|
} from '@vicons/tabler';
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import MenuBarItem from './menu-bar-item.vue';
|
import MenuBarItem from './menu-bar-item.vue';
|
||||||
|
@ -29,9 +39,18 @@ type MenuItem =
|
||||||
icon: Component
|
icon: Component
|
||||||
title: string
|
title: string
|
||||||
action: () => void
|
action: () => void
|
||||||
|
value?: () => string
|
||||||
isActive?: () => boolean
|
isActive?: () => boolean
|
||||||
|
enabled?: () => boolean
|
||||||
type: 'button'
|
type: 'button'
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
icon: Component
|
||||||
|
title: string
|
||||||
|
action: (color: string) => void
|
||||||
|
value: () => string
|
||||||
|
type: 'color'
|
||||||
|
}
|
||||||
| { type: 'divider' };
|
| { type: 'divider' };
|
||||||
|
|
||||||
const items: MenuItem[] = [
|
const items: MenuItem[] = [
|
||||||
|
@ -141,7 +160,42 @@ const items: MenuItem[] = [
|
||||||
title: 'Clear format',
|
title: 'Clear format',
|
||||||
action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(),
|
action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'color',
|
||||||
|
title: 'Forecolor',
|
||||||
|
icon: ColorPicker,
|
||||||
|
action: color => editor.value.chain().focus().setColor(color).run(),
|
||||||
|
value: () => editor.value.getAttributes('textStyle').color,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
icon: ClearFormatting,
|
||||||
|
title: 'Clear Forecolor',
|
||||||
|
action: () => editor.value.chain().focus().unsetColor().run(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'color',
|
||||||
|
title: 'Highlight color',
|
||||||
|
icon: ColorPicker,
|
||||||
|
action: color => editor.value.chain().focus().setHighlight({ color }).run(),
|
||||||
|
value: () => '#FAF594',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
icon: ClearFormatting,
|
||||||
|
title: 'Clear Highlight',
|
||||||
|
action: () => editor.value.chain().focus().unsetHighlight().run(),
|
||||||
|
isActive: () => editor.value.isActive('highlight'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'button',
|
type: 'button',
|
||||||
icon: ArrowBack,
|
icon: ArrowBack,
|
||||||
|
@ -154,14 +208,152 @@ const items: MenuItem[] = [
|
||||||
title: 'Redo',
|
title: 'Redo',
|
||||||
action: () => editor.value.chain().focus().redo().run(),
|
action: () => editor.value.chain().focus().redo().run(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
|
||||||
|
enabled: () => editor.value.can().insertTable(),
|
||||||
|
title: 'Insert table',
|
||||||
|
icon: Table,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().addColumnBefore().run(),
|
||||||
|
enabled: () => editor.value.can().addColumnBefore(),
|
||||||
|
title: 'Add column before',
|
||||||
|
icon: ColumnInsertLeft,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().addColumnAfter().run(),
|
||||||
|
enabled: () => editor.value.can().addColumnAfter(),
|
||||||
|
title: 'Add column after',
|
||||||
|
icon: ColumnInsertRight,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().deleteColumn().run(),
|
||||||
|
enabled: () => editor.value.can().deleteColumn(),
|
||||||
|
title: 'Delete column',
|
||||||
|
icon: Cross,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().addRowBefore().run(),
|
||||||
|
enabled: () => editor.value.can().addRowBefore(),
|
||||||
|
title: 'Add row before',
|
||||||
|
icon: RowInsertTop,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().addRowAfter().run(),
|
||||||
|
enabled: () => editor.value.can().addRowAfter(),
|
||||||
|
title: 'Add row after',
|
||||||
|
icon: RowInsertBottom,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().deleteRow().run(),
|
||||||
|
enabled: () => editor.value.can().deleteRow(),
|
||||||
|
title: 'Delete row',
|
||||||
|
icon: Cross,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().deleteTable().run(),
|
||||||
|
enabled: () => editor.value.can().deleteTable(),
|
||||||
|
title: 'Delete table',
|
||||||
|
icon: TableOff,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().mergeCells().run(),
|
||||||
|
enabled: () => editor.value.can().mergeCells(),
|
||||||
|
title: 'Merge cells',
|
||||||
|
icon: LayersUnion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().splitCell().run(),
|
||||||
|
enabled: () => editor.value.can().splitCell(),
|
||||||
|
title: 'Split cell',
|
||||||
|
icon: SeparatorVertical,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().mergeOrSplit().run(),
|
||||||
|
enabled: () => editor.value.can().mergeOrSplit(),
|
||||||
|
title: 'Merge or split',
|
||||||
|
icon: LayersIntersect2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().toggleHeaderColumn().run(),
|
||||||
|
enabled: () => editor.value.can().toggleHeaderColumn(),
|
||||||
|
title: 'Toggle header column',
|
||||||
|
icon: LayoutDistributeVertical,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().toggleHeaderRow().run(),
|
||||||
|
enabled: () => editor.value.can().toggleHeaderRow(),
|
||||||
|
title: 'Toggle header row',
|
||||||
|
icon: LayoutDistributeHorizontal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().toggleHeaderCell().run(),
|
||||||
|
enabled: () => editor.value.can().toggleHeaderCell(),
|
||||||
|
title: 'Toggle header cell',
|
||||||
|
icon: Heading,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
action: () => editor.value.chain().focus().fixTables().run(),
|
||||||
|
enabled: () => editor.value.can().fixTables(),
|
||||||
|
title: 'Fix tables',
|
||||||
|
icon: Tool,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div flex items-center>
|
<div flex flex-wrap items-center>
|
||||||
<template v-for="(item, index) in items">
|
<template v-for="(item, index) in items">
|
||||||
<n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical />
|
<n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical />
|
||||||
<MenuBarItem v-else-if="item.type === 'button'" :key="index" v-bind="item" />
|
<MenuBarItem v-else-if="item.type === 'button'" :key="index" v-bind="item" />
|
||||||
|
<c-tooltip
|
||||||
|
v-if="item.type === 'color'" :key="`color${index}`"
|
||||||
|
:tooltip="item.title"
|
||||||
|
>
|
||||||
|
<n-color-picker
|
||||||
|
style="width: 120px"
|
||||||
|
:show-alpha="false"
|
||||||
|
:actions="['confirm']"
|
||||||
|
:value="item.value()"
|
||||||
|
@confirm="item.action"
|
||||||
|
/>
|
||||||
|
</c-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue