flameresttable 1.0.106 → 1.0.108

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flameresttable",
3
- "version": "1.0.106",
3
+ "version": "1.0.108",
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": "^7.4.1",
15
+ "@vuepic/vue-datepicker": "^11.0.1",
16
16
  "flamerest": "^1.0.109",
17
17
  "lodash-es": "^4.17.21",
18
- "pinia": "^2.2.6",
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.81.0",
22
- "vue": "^3.3.7",
23
- "vue-router": "^4.4.5",
24
- "vue-select": "^4.0.0-beta.6"
21
+ "sass": "^1.83.1",
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.7"
25
27
  },
26
28
  "devDependencies": {
27
- "@heroicons/vue": "^2.1.5",
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": "20.8.9",
32
+ "@types/node": "22.10.5",
31
33
  "@types/vue-select": "^3.16.8",
32
- "@typescript-eslint/eslint-plugin": "^6.21.0",
33
- "@typescript-eslint/parser": "^6.21.0",
34
- "@vitejs/plugin-vue": "^4.6.2",
34
+ "@typescript-eslint/eslint-plugin": "^8.19.1",
35
+ "@typescript-eslint/parser": "^8.19.1",
36
+ "@vitejs/plugin-vue": "^5.2.1",
35
37
  "@volar/vue-language-plugin-pug": "^1.6.5",
36
- "@vue/compiler-sfc": "^3.3.7",
38
+ "@vue/compiler-sfc": "^3.5.13",
37
39
  "autoprefixer": "^10.4.20",
38
- "eslint": "^8.57.1",
39
- "eslint-plugin-vue": "^8.7.1",
40
+ "eslint": "^9.18.0",
41
+ "eslint-plugin-vue": "^9.32.0",
40
42
  "postcss": "^8.4.49",
41
- "postcss-import": "^14.1.0",
42
- "tailwindcss": "^3.4.15",
43
- "typescript": "^5.6.3",
44
- "vite": "^4.5.5",
45
- "vue-tsc": "^1.8.27",
43
+ "postcss-import": "^16.1.0",
44
+ "tailwindcss": "^3.4.17",
45
+ "typescript": "^5.7.3",
46
+ "vite": "^6.0.7",
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, } })
@@ -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
 
@@ -40,7 +40,7 @@
40
40
  !==
41
41
  '' ?
42
42
  column.Table.title : column.title !== '' ? column.title :
43
- column.name }}</span>
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
- getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[0] }}</option>
151
+ getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[0] }}</option>
153
152
  </select>
154
153
  </div>
155
154
 
@@ -198,8 +197,22 @@
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
+ <EditorPopup :value="Table.columns[column].Popup.model" @save="PopupTextSaved" :column="column" :title="Table.columns[column].Popup.title ?? column">
203
+
204
+ </EditorPopup>
205
+ </div>
206
+
207
+ <!-- ОШИБКИ СОХРАНЕНИЯ -->
208
+ <div class="mx-2 my-1 text-red-600" v-if="(typeof popupErrors[column]!== 'undefined')">{{ popupErrors[column]
209
+ }}</div>
210
+
201
211
  </label>
202
212
 
213
+ <div class="text-red-600 mx-2 my-1" v-if="(typeof popupErrors['globalErrorStack']!== 'undefined')">{{
214
+ popupErrors['globalErrorStack'] }}</div>
215
+
203
216
  <!-- КНОПКИ СОХРАНЕНИЯ-->
204
217
  <div class="flex w-full mt-3 mb-2 justify-between items-center">
205
218
  <div v-show="$props.opts?.Add.can"
@@ -226,6 +239,7 @@ import { XCircleIcon } from '@icons/24/solid'
226
239
  import { computed } from '@vue/reactivity';
227
240
  import { Column, IColumn } from './Columns';
228
241
  import { defineComponent } from 'vue';
242
+
229
243
  import TableOpts from './TableOpts';
230
244
  import FlameTable from './FlameTable';
231
245
  import ModalVue from './../components/default/Modal.vue';
@@ -238,10 +252,11 @@ import '@vuepic/vue-datepicker/dist/main.css';
238
252
  import { isNumber } from 'lodash';
239
253
  import 'vue-select/dist/vue-select.css';
240
254
  import TableTasksPanel from './TableTasksPanel.vue';
241
-
255
+ import EditorPopup from '../components/editor/EditorPopup.vue';
256
+ import { title } from 'process';
242
257
 
243
258
  export default defineComponent({
244
- components: { ModalVue, Paginator, Datepicker, TableFilters, TableTasksPanel },
259
+ components: { ModalVue, Paginator, Datepicker, TableFilters, TableTasksPanel, EditorPopup },
245
260
  props: {
246
261
  rows: {
247
262
  default: [] as Array<any>,
@@ -265,6 +280,9 @@ export default defineComponent({
265
280
 
266
281
  const ColumnNames = Object.keys(Table.model as any);
267
282
 
283
+ // Ошибки сохранения
284
+ let popupErrors: {[key:string]:string} = reactive({});
285
+
268
286
  // Добавляем событие скролла
269
287
 
270
288
  /**
@@ -308,8 +326,21 @@ export default defineComponent({
308
326
  else {
309
327
  await Table.save()
310
328
  }
329
+ popupErrors={};
330
+ }
331
+ catch (ex: any) {
332
+
333
+ for (const keyx in popupErrors)
334
+ delete popupErrors[keyx];
335
+
336
+ if(typeof ex.stack !== 'undefined' && typeof ex.message !== 'undefined') {
337
+ popupErrors.globalErrorStack = ex.message;
338
+ }
339
+ else
340
+ Object.assign(popupErrors, ex);
341
+
342
+ return;
311
343
  }
312
- catch (Ex) { return; }
313
344
 
314
345
  FlameTableModal.value?.close();
315
346
 
@@ -380,6 +411,10 @@ export default defineComponent({
380
411
  return Table.RowsParams[row[primaryKey]].collapsed
381
412
  }
382
413
 
414
+ const PopupTextSaved = (txt: string, column: string)=> {
415
+ Table.columns[column].Popup.model = txt;
416
+ }
417
+
383
418
  const MainTableElement = ref();
384
419
 
385
420
 
@@ -397,7 +432,12 @@ export default defineComponent({
397
432
  primaryKey,
398
433
  columnClick,
399
434
  isRowCollapsed,
400
- MainTableElement
435
+ MainTableElement,
436
+
437
+ // TODO: проверить
438
+ popupErrors,
439
+
440
+ PopupTextSaved
401
441
  }
402
442
 
403
443
  },
@@ -3,7 +3,7 @@
3
3
  // УНИВЕРСАЛЬНОЕ МОДАЛЬНОЕ ОКНО
4
4
  Позволяет вызывать себя через show() и close()
5
5
 
6
- CustomModal.overall(v-model="isShow" :close="close" style="z-index: 999999;")
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
+ })