mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-04 05:19:12 -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']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
|
|
14
package.json
14
package.json
|
@ -38,9 +38,17 @@
|
|||
"@it-tools/bip39": "^0.0.4",
|
||||
"@it-tools/oggen": "^1.3.0",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@tiptap/pm": "2.1.6",
|
||||
"@tiptap/starter-kit": "2.1.6",
|
||||
"@tiptap/vue-3": "2.0.3",
|
||||
"@tiptap/extension-color": "^2.7.4",
|
||||
"@tiptap/extension-gapcursor": "^2.7.4",
|
||||
"@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",
|
||||
"@vicons/material": "^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 StarterKit from '@tiptap/starter-kit';
|
||||
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';
|
||||
|
||||
const props = defineProps<{ html: string }>();
|
||||
|
@ -12,7 +19,18 @@ const html = useVModel(props, 'html', emit);
|
|||
|
||||
const editor = new Editor({
|
||||
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()));
|
||||
|
@ -63,6 +81,63 @@ tryOnBeforeUnmount(() => {
|
|||
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 {
|
||||
background-color: v-bind('themeVars.codeColor');
|
||||
padding: 2px 4px;
|
||||
|
@ -84,10 +159,6 @@ tryOnBeforeUnmount(() => {
|
|||
}
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #faf594;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import type { Component } from 'vue';
|
||||
|
||||
const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean }>();
|
||||
const { icon, title, action, isActive } = toRefs(props);
|
||||
const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean; enabled?: () => boolean }>();
|
||||
const { icon, title, action, isActive, enabled } = toRefs(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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" />
|
||||
</c-button>
|
||||
</c-tooltip>
|
||||
|
|
|
@ -8,15 +8,25 @@ import {
|
|||
ClearFormatting,
|
||||
Code,
|
||||
CodePlus,
|
||||
ColorPicker,
|
||||
ColumnInsertLeft,
|
||||
ColumnInsertRight,
|
||||
Cross,
|
||||
H1,
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
Heading,
|
||||
Italic,
|
||||
LayersIntersect2,
|
||||
LayersUnion,
|
||||
LayoutDistributeHorizontal,
|
||||
LayoutDistributeVertical,
|
||||
List,
|
||||
ListNumbers,
|
||||
Strikethrough,
|
||||
TextWrap,
|
||||
RowInsertBottom,
|
||||
RowInsertTop,
|
||||
SeparatorVertical, Strikethrough, Table, TableOff, TextWrap, Tool,
|
||||
} from '@vicons/tabler';
|
||||
import type { Component } from 'vue';
|
||||
import MenuBarItem from './menu-bar-item.vue';
|
||||
|
@ -29,9 +39,18 @@ type MenuItem =
|
|||
icon: Component
|
||||
title: string
|
||||
action: () => void
|
||||
value?: () => string
|
||||
isActive?: () => boolean
|
||||
enabled?: () => boolean
|
||||
type: 'button'
|
||||
}
|
||||
| {
|
||||
icon: Component
|
||||
title: string
|
||||
action: (color: string) => void
|
||||
value: () => string
|
||||
type: 'color'
|
||||
}
|
||||
| { type: 'divider' };
|
||||
|
||||
const items: MenuItem[] = [
|
||||
|
@ -141,7 +160,42 @@ const items: MenuItem[] = [
|
|||
title: 'Clear format',
|
||||
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',
|
||||
icon: ArrowBack,
|
||||
|
@ -154,14 +208,152 @@ const items: MenuItem[] = [
|
|||
title: 'Redo',
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div flex items-center>
|
||||
<div flex flex-wrap items-center>
|
||||
<template v-for="(item, index) in items">
|
||||
<n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical />
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue