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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flameresttable",
3
- "version": "1.0.105",
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": "^7.4.1",
15
+ "@vuepic/vue-datepicker": "^10.0.0",
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.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.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.2",
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.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.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.17.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.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, } })
@@ -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,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: 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
+ })