flameresttable 1.0.105 → 1.0.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +22 -20
- package/src/App.vue +3 -1
- package/src/Table/FlameTable.ts +2 -0
- package/src/Table/Table.vue +49 -8
- package/src/components/default/Modal.vue +1 -1
- package/src/components/editor/EditorPopup.vue +104 -0
- package/src/components/editor/LinkDialog.vue +106 -0
- package/src/components/editor/SelectImage.vue +53 -0
- package/src/components/editor/TextEditor.ts +129 -0
- package/src/components/editor/TextEditorConstants.ts +145 -0
- package/src/components/editor/styles/github.scss +1135 -0
- package/src/components/editor/styles/maidragon.scss +746 -0
- package/src/main.ts +25 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flameresttable",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.107",
|
|
4
4
|
"main": "./src/plugin.ts",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,37 +12,39 @@
|
|
|
12
12
|
"dev": "vite"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@vuepic/vue-datepicker": "^
|
|
15
|
+
"@vuepic/vue-datepicker": "^10.0.0",
|
|
16
16
|
"flamerest": "^1.0.109",
|
|
17
17
|
"lodash-es": "^4.17.21",
|
|
18
|
-
"pinia": "^2.
|
|
18
|
+
"pinia": "^2.3.0",
|
|
19
19
|
"pug": "github:FlameArt/pug-tailwind-only",
|
|
20
20
|
"pug-lexer": "github:FlameArt/pug-lexer-only",
|
|
21
|
-
"sass": "^1.
|
|
22
|
-
"vue": "^3.
|
|
23
|
-
"vue-router": "^4.
|
|
24
|
-
"vue-select": "^4.0.0-beta.6"
|
|
21
|
+
"sass": "^1.83.0",
|
|
22
|
+
"vue": "^3.5.13",
|
|
23
|
+
"vue-router": "^4.5.0",
|
|
24
|
+
"vue-select": "^4.0.0-beta.6",
|
|
25
|
+
"vuetify": "^3.7.6",
|
|
26
|
+
"vuetify-pro-tiptap": "^2.5.5"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
|
-
"@heroicons/vue": "^2.
|
|
29
|
+
"@heroicons/vue": "^2.2.0",
|
|
28
30
|
"@types/lodash-es": "^4.17.12",
|
|
29
31
|
"@types/lodash.merge": "^4.6.9",
|
|
30
|
-
"@types/node": "
|
|
32
|
+
"@types/node": "22.10.2",
|
|
31
33
|
"@types/vue-select": "^3.16.8",
|
|
32
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
33
|
-
"@typescript-eslint/parser": "^
|
|
34
|
-
"@vitejs/plugin-vue": "^
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
35
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
36
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
35
37
|
"@volar/vue-language-plugin-pug": "^1.6.5",
|
|
36
|
-
"@vue/compiler-sfc": "^3.
|
|
38
|
+
"@vue/compiler-sfc": "^3.5.13",
|
|
37
39
|
"autoprefixer": "^10.4.20",
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint-plugin-vue": "^
|
|
40
|
+
"eslint": "^9.17.0",
|
|
41
|
+
"eslint-plugin-vue": "^9.32.0",
|
|
40
42
|
"postcss": "^8.4.49",
|
|
41
|
-
"postcss-import": "^
|
|
42
|
-
"tailwindcss": "^3.4.
|
|
43
|
-
"typescript": "^5.
|
|
44
|
-
"vite": "^
|
|
45
|
-
"vue-tsc": "^
|
|
43
|
+
"postcss-import": "^16.1.0",
|
|
44
|
+
"tailwindcss": "^3.4.17",
|
|
45
|
+
"typescript": "^5.7.2",
|
|
46
|
+
"vite": "^6.0.5",
|
|
47
|
+
"vue-tsc": "^2.2.0",
|
|
46
48
|
"vue-universal-modal": "^1.1.4"
|
|
47
49
|
},
|
|
48
50
|
"rules": {}
|
package/src/App.vue
CHANGED
|
@@ -6,6 +6,7 @@ import Model from '@models/Testtable';
|
|
|
6
6
|
import TableOpts from './Table/TableOpts';
|
|
7
7
|
import Relatedtable from '@models/Relatedtable';
|
|
8
8
|
import SubRow from './pages/SubRow.vue';
|
|
9
|
+
import { useTheme } from 'vuetify'
|
|
9
10
|
|
|
10
11
|
const store = storeFile(), router = useRouter(), route = useRoute();
|
|
11
12
|
|
|
@@ -23,7 +24,7 @@ onMounted(() => {
|
|
|
23
24
|
const TableComponent = ref<InstanceType<typeof Table>>();
|
|
24
25
|
const opts = new TableOpts;
|
|
25
26
|
|
|
26
|
-
opts.set('name', { title: 'Название' });
|
|
27
|
+
opts.set('name', { title: 'Название', Popup:{popupType: 'text'} });
|
|
27
28
|
opts.set('dt1', { title: 'Дата 1' });
|
|
28
29
|
opts.set('dt2', { title: 'Дата 2' });
|
|
29
30
|
opts.set('id', { title: 'ID', Selector: { model: Relatedtable }, Filter: { isShow: true, type: 'selector', selector: { multiselect: true, mode: 'vertical' } } });
|
|
@@ -38,6 +39,7 @@ opts.set("name", {
|
|
|
38
39
|
Popup: { isShow: true }
|
|
39
40
|
});
|
|
40
41
|
|
|
42
|
+
const theme = useTheme()
|
|
41
43
|
|
|
42
44
|
opts.addVirtual('test', { title: 'test title', Table: { isShow: true, } })
|
|
43
45
|
opts.addVirtual('date', { title: 'dt', Filter: { isShow: true, type: 'number' }, Table: { isShow: true, } })
|
package/src/Table/FlameTable.ts
CHANGED
|
@@ -447,6 +447,8 @@ export default class FlameTable<T> {
|
|
|
447
447
|
if (findedIdx !== -1) {
|
|
448
448
|
this.Rows.rows[findedIdx] = reactive(res.data as any);
|
|
449
449
|
this.RowsParams[columns[indexKey]].previousAttributes = res.data;
|
|
450
|
+
|
|
451
|
+
// TODO: тут можно очищать Event в columns.Popup, что бы он не пересохранял при повторном сохранении
|
|
450
452
|
}
|
|
451
453
|
}
|
|
452
454
|
|
package/src/Table/Table.vue
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
!==
|
|
41
41
|
'' ?
|
|
42
42
|
column.Table.title : column.title !== '' ? column.title :
|
|
43
|
-
|
|
43
|
+
column.name }}</span>
|
|
44
44
|
</div>
|
|
45
45
|
<div v-if="opts.Edit.can" class="table-cell w-[93px] border-r border-r-slate-100 px-2">
|
|
46
46
|
<button class="bg-green-600 invisible">
|
|
@@ -129,8 +129,7 @@
|
|
|
129
129
|
<div class="text-xs text-slate-400">{{ Table.columns[column].Popup.desc }}</div>
|
|
130
130
|
</div>
|
|
131
131
|
|
|
132
|
-
<div
|
|
133
|
-
v-if="Table.columns[column].Popup.popupType === 'string' || Table.columns[column].Popup.popupType === 'text'"
|
|
132
|
+
<div v-if="Table.columns[column].Popup.popupType === 'string'"
|
|
134
133
|
class="flex-1 mr-5 w-full flex flex-col self-stretch">
|
|
135
134
|
<input v-model="Table.columns[column].Popup.model"
|
|
136
135
|
class="h-full self-stretch py-1 px-2 w-full outline-none border border-slate-100" type="text"
|
|
@@ -149,7 +148,7 @@
|
|
|
149
148
|
<option v-for="(selectorVal, selectorKey) in Table.columns[column].Popup.Selector.values"
|
|
150
149
|
:key="'sel_' + selectorKey"
|
|
151
150
|
:value="getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[1]">{{
|
|
152
|
-
|
|
151
|
+
getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[0] }}</option>
|
|
153
152
|
</select>
|
|
154
153
|
</div>
|
|
155
154
|
|
|
@@ -198,8 +197,23 @@
|
|
|
198
197
|
</label>
|
|
199
198
|
</div>
|
|
200
199
|
|
|
200
|
+
<div class="flex-1 mr-5 w-full flex flex-col self-stretch"
|
|
201
|
+
v-if="Table.columns[column].Popup.popupType === 'text'">
|
|
202
|
+
{{ Table.columns[column].Popup.model }}
|
|
203
|
+
<EditorPopup :value="Table.columns[column].Popup.model" @save="PopupTextSaved" :column="column" :title="Table.columns[column].Popup.title ?? column">
|
|
204
|
+
|
|
205
|
+
</EditorPopup>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- ОШИБКИ СОХРАНЕНИЯ -->
|
|
209
|
+
<div class="mx-2 my-1 text-red-600" v-if="(typeof popupErrors[column]!== 'undefined')">{{ popupErrors[column]
|
|
210
|
+
}}</div>
|
|
211
|
+
|
|
201
212
|
</label>
|
|
202
213
|
|
|
214
|
+
<div class="text-red-600 mx-2 my-1" v-if="(typeof popupErrors['globalErrorStack']!== 'undefined')">{{
|
|
215
|
+
popupErrors['globalErrorStack'] }}</div>
|
|
216
|
+
|
|
203
217
|
<!-- КНОПКИ СОХРАНЕНИЯ-->
|
|
204
218
|
<div class="flex w-full mt-3 mb-2 justify-between items-center">
|
|
205
219
|
<div v-show="$props.opts?.Add.can"
|
|
@@ -226,6 +240,7 @@ import { XCircleIcon } from '@icons/24/solid'
|
|
|
226
240
|
import { computed } from '@vue/reactivity';
|
|
227
241
|
import { Column, IColumn } from './Columns';
|
|
228
242
|
import { defineComponent } from 'vue';
|
|
243
|
+
|
|
229
244
|
import TableOpts from './TableOpts';
|
|
230
245
|
import FlameTable from './FlameTable';
|
|
231
246
|
import ModalVue from './../components/default/Modal.vue';
|
|
@@ -238,10 +253,11 @@ import '@vuepic/vue-datepicker/dist/main.css';
|
|
|
238
253
|
import { isNumber } from 'lodash';
|
|
239
254
|
import 'vue-select/dist/vue-select.css';
|
|
240
255
|
import TableTasksPanel from './TableTasksPanel.vue';
|
|
241
|
-
|
|
256
|
+
import EditorPopup from '@/components/editor/EditorPopup.vue';
|
|
257
|
+
import { title } from 'process';
|
|
242
258
|
|
|
243
259
|
export default defineComponent({
|
|
244
|
-
components: { ModalVue, Paginator, Datepicker, TableFilters, TableTasksPanel },
|
|
260
|
+
components: { ModalVue, Paginator, Datepicker, TableFilters, TableTasksPanel, EditorPopup },
|
|
245
261
|
props: {
|
|
246
262
|
rows: {
|
|
247
263
|
default: [] as Array<any>,
|
|
@@ -265,6 +281,9 @@ export default defineComponent({
|
|
|
265
281
|
|
|
266
282
|
const ColumnNames = Object.keys(Table.model as any);
|
|
267
283
|
|
|
284
|
+
// Ошибки сохранения
|
|
285
|
+
let popupErrors: {[key:string]:string} = reactive({});
|
|
286
|
+
|
|
268
287
|
// Добавляем событие скролла
|
|
269
288
|
|
|
270
289
|
/**
|
|
@@ -308,8 +327,21 @@ export default defineComponent({
|
|
|
308
327
|
else {
|
|
309
328
|
await Table.save()
|
|
310
329
|
}
|
|
330
|
+
popupErrors={};
|
|
331
|
+
}
|
|
332
|
+
catch (ex: any) {
|
|
333
|
+
|
|
334
|
+
for (const keyx in popupErrors)
|
|
335
|
+
delete popupErrors[keyx];
|
|
336
|
+
|
|
337
|
+
if(typeof ex.stack !== 'undefined' && typeof ex.message !== 'undefined') {
|
|
338
|
+
popupErrors.globalErrorStack = ex.message;
|
|
339
|
+
}
|
|
340
|
+
else
|
|
341
|
+
Object.assign(popupErrors, ex);
|
|
342
|
+
|
|
343
|
+
return;
|
|
311
344
|
}
|
|
312
|
-
catch (Ex) { return; }
|
|
313
345
|
|
|
314
346
|
FlameTableModal.value?.close();
|
|
315
347
|
|
|
@@ -380,6 +412,10 @@ export default defineComponent({
|
|
|
380
412
|
return Table.RowsParams[row[primaryKey]].collapsed
|
|
381
413
|
}
|
|
382
414
|
|
|
415
|
+
const PopupTextSaved = (txt: string, column: string)=> {
|
|
416
|
+
Table.columns[column].Popup.model = txt;
|
|
417
|
+
}
|
|
418
|
+
|
|
383
419
|
const MainTableElement = ref();
|
|
384
420
|
|
|
385
421
|
|
|
@@ -397,7 +433,12 @@ export default defineComponent({
|
|
|
397
433
|
primaryKey,
|
|
398
434
|
columnClick,
|
|
399
435
|
isRowCollapsed,
|
|
400
|
-
MainTableElement
|
|
436
|
+
MainTableElement,
|
|
437
|
+
|
|
438
|
+
// TODO: проверить
|
|
439
|
+
popupErrors,
|
|
440
|
+
|
|
441
|
+
PopupTextSaved
|
|
401
442
|
}
|
|
402
443
|
|
|
403
444
|
},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// УНИВЕРСАЛЬНОЕ МОДАЛЬНОЕ ОКНО
|
|
4
4
|
Позволяет вызывать себя через show() и close()
|
|
5
5
|
|
|
6
|
-
CustomModal.overall(v-model="isShow" :close="close" style="z-index:
|
|
6
|
+
CustomModal.overall(v-model="isShow" :close="close" style="z-index: 999;")
|
|
7
7
|
|
|
8
8
|
// Этот слот для замены самого окна
|
|
9
9
|
slot
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog class="z-[9999999]" v-model="state.dialog" max-width="400" persistent>
|
|
3
|
+
<div class="text-slate-500">
|
|
4
|
+
</div>
|
|
5
|
+
<template v-slot:activator="{ props: activatorProps }">
|
|
6
|
+
<v-btn v-bind="activatorProps">
|
|
7
|
+
Изменить
|
|
8
|
+
</v-btn>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<v-card prepend-icon="mdi-map-marker" :title="'Изменить: ' + $props.title">
|
|
12
|
+
|
|
13
|
+
<VuetifyTiptap ref="VuetifyTiptapRef" v-model="state.data" />
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
<template v-slot:actions>
|
|
17
|
+
<v-spacer></v-spacer>
|
|
18
|
+
|
|
19
|
+
<v-btn @click="state.dialog = false">
|
|
20
|
+
Отмена
|
|
21
|
+
</v-btn>
|
|
22
|
+
|
|
23
|
+
<v-btn @click="saveButton()">
|
|
24
|
+
Сохранить
|
|
25
|
+
</v-btn>
|
|
26
|
+
</template>
|
|
27
|
+
</v-card>
|
|
28
|
+
</v-dialog>
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import { onMounted, reactive, ref, defineProps, defineEmits, nextTick, defineComponent } from '@vue/runtime-core'; import type { Ref } from 'vue'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST from "flamerest";
|
|
35
|
+
import { vuetifyProTipTap } from './TextEditor'
|
|
36
|
+
// Иконки
|
|
37
|
+
import { } from '@icons/24/solid'
|
|
38
|
+
|
|
39
|
+
export default defineComponent({
|
|
40
|
+
|
|
41
|
+
name: 'EditorPopup',
|
|
42
|
+
|
|
43
|
+
components: { vuetifyProTipTap },
|
|
44
|
+
|
|
45
|
+
props: {
|
|
46
|
+
title: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: "",
|
|
49
|
+
required: false
|
|
50
|
+
},
|
|
51
|
+
column: {
|
|
52
|
+
type: String,
|
|
53
|
+
default: "",
|
|
54
|
+
required: true
|
|
55
|
+
},
|
|
56
|
+
value: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: "",
|
|
59
|
+
required: true
|
|
60
|
+
},
|
|
61
|
+
closeButton: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: "",
|
|
64
|
+
required: false
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
emits: [
|
|
69
|
+
'save'
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
setup(props, { emit }) {
|
|
73
|
+
|
|
74
|
+
// Глобальное хранилище, роуты, локали
|
|
75
|
+
const store = storeFile(), router = useRouter(), route = useRoute();
|
|
76
|
+
|
|
77
|
+
// Входящие данные компонента
|
|
78
|
+
//const emit = defineEmits(['save'])
|
|
79
|
+
|
|
80
|
+
// Локальное состояние компонента
|
|
81
|
+
const state = reactive({
|
|
82
|
+
data: "",
|
|
83
|
+
dialog: false
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const saveButton = () => {
|
|
87
|
+
state.dialog = false;
|
|
88
|
+
emit('save', state.data, props.column);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
state.data = props.value;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
state,
|
|
95
|
+
store,
|
|
96
|
+
saveButton
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<style scoped lang="scss"></style>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Editor } from '@tiptap/vue-3'
|
|
3
|
+
import { computed, ref, watch, withDefaults, defineProps } from 'vue'
|
|
4
|
+
import { locale } from 'vuetify-pro-tiptap'
|
|
5
|
+
|
|
6
|
+
interface LinkAttrs {
|
|
7
|
+
href?: string
|
|
8
|
+
target?: '_self' | '_blank'
|
|
9
|
+
rel?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
value?: string
|
|
14
|
+
target?: '_self' | '_blank'
|
|
15
|
+
rel?: string
|
|
16
|
+
editor: Editor
|
|
17
|
+
destroy?: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
21
|
+
value: undefined,
|
|
22
|
+
target: '_blank',
|
|
23
|
+
rel: undefined,
|
|
24
|
+
destroy: undefined
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const generateLinkAttrs = (): LinkAttrs => ({
|
|
28
|
+
href: '',
|
|
29
|
+
target: '_blank',
|
|
30
|
+
rel: ''
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const attrs = ref(generateLinkAttrs())
|
|
34
|
+
|
|
35
|
+
const dialog = ref<boolean>(false)
|
|
36
|
+
|
|
37
|
+
const isDisabled = computed(() => {
|
|
38
|
+
const { href, target, rel } = attrs.value
|
|
39
|
+
if (!href) return true
|
|
40
|
+
|
|
41
|
+
return props.value === href && props.target === target && props.rel === rel
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function apply() {
|
|
45
|
+
const { href, target, rel } = attrs.value
|
|
46
|
+
|
|
47
|
+
if (href) {
|
|
48
|
+
props.editor.chain().focus().extendMarkRange('link').setLink({ href, target, rel }).run()
|
|
49
|
+
}
|
|
50
|
+
close()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function close() {
|
|
54
|
+
dialog.value = false
|
|
55
|
+
attrs.value = generateLinkAttrs()
|
|
56
|
+
|
|
57
|
+
setTimeout(() => props.destroy?.(), 300)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
watch(dialog, val => {
|
|
61
|
+
if (!val) return
|
|
62
|
+
|
|
63
|
+
attrs.value = {
|
|
64
|
+
href: props.value,
|
|
65
|
+
target: props.target,
|
|
66
|
+
rel: props.rel
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<VDialog v-model="dialog" max-width="400" activator="parent" @click:outside="close">
|
|
73
|
+
<VCard>
|
|
74
|
+
<VToolbar class="px-6" density="compact">
|
|
75
|
+
<span class="headline">{{ locale.message[locale.lang]['editor.link.dialog.title'] }}</span>
|
|
76
|
+
|
|
77
|
+
<VSpacer />
|
|
78
|
+
|
|
79
|
+
<VBtn class="mx-0" icon @click="close">
|
|
80
|
+
<VIcon icon="$close" />
|
|
81
|
+
</VBtn>
|
|
82
|
+
</VToolbar>
|
|
83
|
+
|
|
84
|
+
<VCardText>
|
|
85
|
+
<div>This is a custom dialog component;</div>
|
|
86
|
+
|
|
87
|
+
<div>please use the <strong>dialogComponent</strong> parameter.</div>
|
|
88
|
+
|
|
89
|
+
<VTextField v-model="attrs.href" :label="locale.message[locale.lang]['editor.link.dialog.link']"
|
|
90
|
+
autofocus />
|
|
91
|
+
|
|
92
|
+
<VTextField v-model="attrs.rel" :label="locale.message[locale.lang]['editor.link.dialog.rel']"
|
|
93
|
+
hide-details />
|
|
94
|
+
|
|
95
|
+
<VCheckbox v-model="attrs.target" :label="locale.message[locale.lang]['editor.link.dialog.openInNewTab']"
|
|
96
|
+
color="primary" false-value="_self" true-value="_blank" hide-details />
|
|
97
|
+
</VCardText>
|
|
98
|
+
|
|
99
|
+
<VCardActions>
|
|
100
|
+
<VBtn :disabled="isDisabled" @click="apply">
|
|
101
|
+
{{ locale.message[locale.lang]['editor.link.dialog.button.apply'] }}
|
|
102
|
+
</VBtn>
|
|
103
|
+
</VCardActions>
|
|
104
|
+
</VCard>
|
|
105
|
+
</VDialog>
|
|
106
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, unref, watch, withDefaults, defineProps, defineEmits } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface SelectImageProps {
|
|
5
|
+
modelValue?: Record<string, string>
|
|
6
|
+
upload?: (file: File) => Promise<string> // vue warn
|
|
7
|
+
t: (path: string) => string // vue warn
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SelectImageEmits {
|
|
11
|
+
(event: 'update:modelValue', form?: Record<string, string>): void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<SelectImageProps>(), {
|
|
15
|
+
modelValue: () => ({}),
|
|
16
|
+
upload: undefined
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const emits = defineEmits<SelectImageEmits>()
|
|
20
|
+
|
|
21
|
+
const form = computed({
|
|
22
|
+
get: () => props.modelValue,
|
|
23
|
+
set: val => {
|
|
24
|
+
emits('update:modelValue', val)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const items = [
|
|
29
|
+
{ alt: 'Test 1', src: 'https://picsum.photos/1920/1080.webp?t=1' },
|
|
30
|
+
{ alt: 'Test 2', src: 'https://picsum.photos/1920/1080.webp?t=2' },
|
|
31
|
+
{ alt: 'Test 3', src: 'https://picsum.photos/1920/1080.webp?t=3' }
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
watch(
|
|
35
|
+
() => unref(form).src,
|
|
36
|
+
val => {
|
|
37
|
+
const find = items.find(k => k.src === val)
|
|
38
|
+
if (find) {
|
|
39
|
+
form.value.alt = find.alt
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<VAlert class="mb-4" type="info" text="Select tab is custom component" />
|
|
47
|
+
|
|
48
|
+
<VSelect v-model="form.src" label="Select Image" :items="items" item-value="src" item-title="alt" />
|
|
49
|
+
|
|
50
|
+
<VTextField v-model="form.alt" label="Alt" />
|
|
51
|
+
|
|
52
|
+
<VCheckbox v-model="form.lockAspectRatio" label="Lock original aspect ratio" />
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { NODE_TYPE_MENU } from './TextEditorConstants.js'
|
|
2
|
+
import { markRaw } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
BaseKit,
|
|
5
|
+
Blockquote,
|
|
6
|
+
Bold,
|
|
7
|
+
BulletList,
|
|
8
|
+
Clear,
|
|
9
|
+
Code,
|
|
10
|
+
CodeBlock,
|
|
11
|
+
Color,
|
|
12
|
+
createVuetifyProTipTap,
|
|
13
|
+
defaultBubbleList,
|
|
14
|
+
FontFamily,
|
|
15
|
+
FontSize,
|
|
16
|
+
Fullscreen,
|
|
17
|
+
Heading,
|
|
18
|
+
Highlight,
|
|
19
|
+
History,
|
|
20
|
+
HorizontalRule,
|
|
21
|
+
Image,
|
|
22
|
+
Indent,
|
|
23
|
+
Italic,
|
|
24
|
+
Link,
|
|
25
|
+
MarkdownTheme,
|
|
26
|
+
OrderedList,
|
|
27
|
+
Strike,
|
|
28
|
+
SubAndSuperScript,
|
|
29
|
+
Table,
|
|
30
|
+
TaskList,
|
|
31
|
+
TextAlign,
|
|
32
|
+
Underline,
|
|
33
|
+
Video,
|
|
34
|
+
VuetifyTiptap,
|
|
35
|
+
VuetifyViewer
|
|
36
|
+
} from 'vuetify-pro-tiptap'
|
|
37
|
+
import LinkDialog from './LinkDialog.vue'
|
|
38
|
+
|
|
39
|
+
import SelectImage from './SelectImage.vue'
|
|
40
|
+
/*
|
|
41
|
+
* @Date: 2023-05-11 22:14:19
|
|
42
|
+
* @LastEditors: yikoyu 2282373181@qq.com
|
|
43
|
+
* @LastEditTime: 2023-06-13 20:39:47
|
|
44
|
+
* @FilePath: \vuetify-pro-tiptap\examples\tiptap.ts
|
|
45
|
+
*/
|
|
46
|
+
import 'vuetify-pro-tiptap/styles/editor.css'
|
|
47
|
+
|
|
48
|
+
import 'vuetify-pro-tiptap/styles/markdown.css'
|
|
49
|
+
import './styles/github.scss'
|
|
50
|
+
|
|
51
|
+
import './styles/maidragon.scss'
|
|
52
|
+
|
|
53
|
+
export const vuetifyProTipTap = createVuetifyProTipTap({
|
|
54
|
+
lang: 'ru',
|
|
55
|
+
markdownTheme: 'github',
|
|
56
|
+
components: {
|
|
57
|
+
VuetifyTiptap,
|
|
58
|
+
VuetifyViewer
|
|
59
|
+
},
|
|
60
|
+
extensions: [
|
|
61
|
+
BaseKit.configure({
|
|
62
|
+
placeholder: {
|
|
63
|
+
placeholder: 'Enter some text...'
|
|
64
|
+
},
|
|
65
|
+
/*
|
|
66
|
+
bubble: {
|
|
67
|
+
// default config
|
|
68
|
+
list: {
|
|
69
|
+
...NODE_TYPE_MENU
|
|
70
|
+
},
|
|
71
|
+
defaultBubbleList: editor => {
|
|
72
|
+
// You can customize the bubble menu here
|
|
73
|
+
return defaultBubbleList(editor) // default customize bubble list
|
|
74
|
+
}
|
|
75
|
+
}*/
|
|
76
|
+
}),
|
|
77
|
+
Bold,
|
|
78
|
+
Italic,
|
|
79
|
+
Underline,
|
|
80
|
+
Strike,
|
|
81
|
+
Code.configure({ divider: true }),
|
|
82
|
+
Heading,
|
|
83
|
+
TextAlign,
|
|
84
|
+
FontFamily,
|
|
85
|
+
FontSize,
|
|
86
|
+
Color,
|
|
87
|
+
Highlight.configure({ divider: true }),
|
|
88
|
+
SubAndSuperScript.configure({ divider: true }),
|
|
89
|
+
Clear.configure({ divider: true }),
|
|
90
|
+
BulletList,
|
|
91
|
+
OrderedList,
|
|
92
|
+
TaskList,
|
|
93
|
+
Indent.configure({ divider: true }),
|
|
94
|
+
Link.configure({
|
|
95
|
+
dialogComponent: () => LinkDialog
|
|
96
|
+
}),
|
|
97
|
+
Image.configure({
|
|
98
|
+
imageTabs: [{ name: 'SELECT', component: markRaw(SelectImage) }],
|
|
99
|
+
width: 500,
|
|
100
|
+
// hiddenTabs: ['upload'],
|
|
101
|
+
upload(file: File) {
|
|
102
|
+
const url = URL.createObjectURL(file)
|
|
103
|
+
console.log('mock upload api :>> ', url)
|
|
104
|
+
return Promise.resolve(url)
|
|
105
|
+
}
|
|
106
|
+
}),
|
|
107
|
+
Video,
|
|
108
|
+
Table.configure({ divider: true }),
|
|
109
|
+
Blockquote,
|
|
110
|
+
HorizontalRule,
|
|
111
|
+
CodeBlock.configure({ divider: true }),
|
|
112
|
+
History.configure({ divider: true }),
|
|
113
|
+
MarkdownTheme.configure({
|
|
114
|
+
markdownThemes: [
|
|
115
|
+
{
|
|
116
|
+
title: 'Github',
|
|
117
|
+
value: 'github'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
title: 'Maidragon',
|
|
121
|
+
value: 'maidragon'
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}),
|
|
125
|
+
Fullscreen.configure({
|
|
126
|
+
useWindow: true
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
})
|