@hostlink/nuxt-light 1.64.3 → 1.66.0

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.
Files changed (30) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +5 -0
  3. package/dist/runtime/components/L/CustomField/Add.vue +6 -1
  4. package/dist/runtime/components/L/Database/create-table-dialog.vue +34 -26
  5. package/dist/runtime/components/L/DocumentViewer.d.vue.ts +50 -0
  6. package/dist/runtime/components/L/DocumentViewer.vue +154 -0
  7. package/dist/runtime/components/L/DocumentViewer.vue.d.ts +50 -0
  8. package/dist/runtime/components/L/DocumentViewerDialog.d.vue.ts +65 -0
  9. package/dist/runtime/components/L/DocumentViewerDialog.vue +67 -0
  10. package/dist/runtime/components/L/DocumentViewerDialog.vue.d.ts +65 -0
  11. package/dist/runtime/components/l-customizer.d.vue.ts +19 -3
  12. package/dist/runtime/components/l-customizer.vue.d.ts +19 -3
  13. package/dist/runtime/components/l-file-manager-labels.d.vue.ts +5 -1
  14. package/dist/runtime/components/l-file-manager-labels.vue.d.ts +5 -1
  15. package/dist/runtime/components/l-file-manager-preview.vue +23 -22
  16. package/dist/runtime/components/l-file-manager.vue +14 -26
  17. package/dist/runtime/components/l-form.vue +16 -10
  18. package/dist/runtime/components/l-repeater.d.vue.ts +1 -1
  19. package/dist/runtime/components/l-repeater.vue.d.ts +1 -1
  20. package/dist/runtime/composables/showDocumentDialog.d.ts +9 -0
  21. package/dist/runtime/composables/showDocumentDialog.js +15 -0
  22. package/dist/runtime/pages/CustomField/index.vue +5 -5
  23. package/dist/runtime/pages/System/database/table.vue +49 -62
  24. package/dist/runtime/pages/System/setting.vue +2 -2
  25. package/dist/runtime/pages/Translate/index.vue +178 -62
  26. package/dist/runtime/pages/User/createAccessToken.d.vue.ts +3 -0
  27. package/dist/runtime/pages/User/createAccessToken.vue +63 -0
  28. package/dist/runtime/pages/User/createAccessToken.vue.d.ts +3 -0
  29. package/dist/runtime/pages/User/profile.vue +1 -0
  30. package/package.json +4 -4
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { ref, reactive } from "vue";
2
+ import { ref, reactive, computed } from "vue";
3
3
  import { m, q, useAsyncData } from "#imports";
4
4
  import { useQuasar } from "quasar";
5
5
  import { useI18n } from "vue-i18n";
@@ -7,29 +7,74 @@ const $q = useQuasar();
7
7
  const { t } = useI18n();
8
8
  const { app } = await q({ app: { languages: true } });
9
9
  const splitterModel = ref(62);
10
+ const filter = ref("");
10
11
  const { data: all, refresh } = await useAsyncData("translate", () => {
11
12
  return q({ allTranslate: true }).then((res) => res.allTranslate);
12
13
  });
14
+ const filteredRows = computed(() => {
15
+ if (!filter.value || !all.value) return all.value;
16
+ const searchTerm = filter.value.toLowerCase();
17
+ return all.value.filter((row) => {
18
+ if (row.name?.toLowerCase().includes(searchTerm)) return true;
19
+ for (const language of app.languages) {
20
+ if (row[language.value]?.toLowerCase().includes(searchTerm)) return true;
21
+ }
22
+ return false;
23
+ });
24
+ });
13
25
  const obj = reactive({
14
- name: ""
26
+ name: "",
27
+ _originalName: ""
28
+ // 用於追蹤原始名稱(編輯模式)
15
29
  });
30
+ const isEditMode = computed(() => !!obj._originalName);
31
+ const onSelectRow = (row) => {
32
+ obj.name = row.name;
33
+ obj._originalName = row.name;
34
+ for (const language of app.languages) {
35
+ obj[language.value] = row[language.value] || "";
36
+ }
37
+ };
38
+ const onClearForm = () => {
39
+ obj.name = "";
40
+ obj._originalName = "";
41
+ for (const language of app.languages) {
42
+ obj[language.value] = "";
43
+ }
44
+ };
16
45
  const onSave = async () => {
17
- await m("addTranslate", {
18
- data: {
19
- name: obj.name,
20
- values: app.languages.map((language) => {
21
- return {
22
- language: language.value,
23
- value: obj[language.value]
24
- };
25
- })
46
+ if (isEditMode.value) {
47
+ for (const language of app.languages) {
48
+ await m("updateTranslate", {
49
+ name: obj._originalName,
50
+ language: language.value,
51
+ value: obj[language.value] || ""
52
+ });
26
53
  }
27
- });
28
- $q.notify({
29
- message: "Save success",
30
- color: "positive",
31
- icon: "check"
32
- });
54
+ $q.notify({
55
+ message: t("Update success"),
56
+ color: "positive",
57
+ icon: "check"
58
+ });
59
+ } else {
60
+ await m("addTranslate", {
61
+ data: {
62
+ name: obj.name,
63
+ values: app.languages.map((language) => {
64
+ return {
65
+ language: language.value,
66
+ value: obj[language.value] || ""
67
+ };
68
+ })
69
+ }
70
+ });
71
+ $q.notify({
72
+ message: t("Save success"),
73
+ color: "positive",
74
+ icon: "check"
75
+ });
76
+ }
77
+ onClearForm();
33
78
  await refresh();
34
79
  };
35
80
  const columns = [
@@ -52,31 +97,25 @@ for (const language of app.languages) {
52
97
  align: "left"
53
98
  });
54
99
  }
55
- const onUpdateTranslate = async (value, language, name) => {
56
- if (await m("updateTranslate", {
57
- name,
58
- language,
59
- value
60
- })) {
61
- $q.notify({
62
- message: "Update success",
63
- color: "positive",
64
- icon: "check"
65
- });
66
- }
67
- await refresh();
68
- };
69
100
  const onDelete = async (name) => {
70
- if (await m("deleteTranslate", {
71
- name
72
- })) {
73
- $q.notify({
74
- message: "Delete success",
75
- color: "positive",
76
- icon: "check"
77
- });
78
- await refresh();
79
- }
101
+ $q.dialog({
102
+ title: t("Confirm"),
103
+ message: t("Are you sure you want to delete this item?"),
104
+ cancel: true,
105
+ persistent: true
106
+ }).onOk(async () => {
107
+ if (await m("deleteTranslate", { name })) {
108
+ $q.notify({
109
+ message: t("Delete success"),
110
+ color: "positive",
111
+ icon: "check"
112
+ });
113
+ if (obj._originalName === name) {
114
+ onClearForm();
115
+ }
116
+ await refresh();
117
+ }
118
+ });
80
119
  };
81
120
  </script>
82
121
 
@@ -85,38 +124,115 @@ const onDelete = async (name) => {
85
124
  <l-card>
86
125
  <q-splitter v-model="splitterModel" style="height:680px">
87
126
  <template #before>
88
- <q-table :rows="all" flat :rows-per-page-options="[0]" :columns="columns" dense separator="cell"
89
- :bordered="false">
127
+ <q-table
128
+ :rows="filteredRows"
129
+ flat
130
+ :rows-per-page-options="[0]"
131
+ :columns="columns"
132
+ dense
133
+ separator="cell"
134
+ :bordered="false"
135
+ >
136
+ <template #top>
137
+ <q-input
138
+ v-model="filter"
139
+ dense
140
+ outlined
141
+ :placeholder="t('Search...')"
142
+ clearable
143
+ class="full-width"
144
+ >
145
+ <template #prepend>
146
+ <q-icon name="sym_o_search" />
147
+ </template>
148
+ </q-input>
149
+ </template>
90
150
  <template #body="props">
91
- <q-tr :props="props">
92
- <q-td key="_delete" auto-width>
93
- <q-btn dense flat round icon="sym_o_delete"
94
- @click="onDelete(props.row.name)"></q-btn>
151
+ <q-tr
152
+ :props="props"
153
+ :class="{ 'bg-blue-1': obj._originalName === props.row.name }"
154
+ class="cursor-pointer"
155
+ @click="onSelectRow(props.row)"
156
+ >
157
+ <q-td key="_delete" auto-width @click.stop>
158
+ <q-btn dense flat round icon="sym_o_delete" color="negative"
159
+ @click="onDelete(props.row.name)">
160
+ <q-tooltip>{{ t("Delete") }}</q-tooltip>
161
+ </q-btn>
95
162
  </q-td>
96
- <q-td key="name">
97
- {{ props.row.name }}
163
+ <q-td key="name" style="max-width: 200px;">
164
+ <div class="text-weight-medium ellipsis">
165
+ {{ props.row.name }}
166
+ <q-tooltip v-if="props.row.name?.length > 25" anchor="top middle" self="bottom middle">
167
+ {{ props.row.name }}
168
+ </q-tooltip>
169
+ </div>
98
170
  </q-td>
99
- <q-td :key="language.value" v-for="language in app.languages" :props="props">
100
- <div class="text-pre-wrap">{{ props.row[language.value] }}</div>
101
- <q-popup-edit v-model="props.row[language.value]" v-slot="scope" buttons
102
- :title="language.name"
103
- @save="onUpdateTranslate($event, language.value, props.row.name)">
104
- <q-input v-model="scope.value" dense autofocus @keyup.enter="scope.set" />
105
- </q-popup-edit>
171
+ <q-td :key="language.value" v-for="language in app.languages" :props="props" style="max-width: 250px;">
172
+ <div class="text-grey-8 ellipsis-2-lines">
173
+ {{ props.row[language.value] || '-' }}
174
+ <q-tooltip v-if="props.row[language.value]?.length > 50" anchor="top middle" self="bottom middle" max-width="400px">
175
+ <div class="text-pre-wrap">{{ props.row[language.value] }}</div>
176
+ </q-tooltip>
177
+ </div>
106
178
  </q-td>
107
179
  </q-tr>
108
180
  </template>
109
181
  </q-table>
110
-
111
182
  </template>
112
183
  <template #after>
113
- <l-form :bordered="false" @save="onSave">
114
- <l-input label="Name" required v-model.trim="obj.name" clearable></l-input>
115
- <l-input v-for="language in app.languages" :label="language.name" v-model="obj[language.value]"
116
- clearable></l-input>
117
- </l-form>
184
+ <div class="q-pa-md">
185
+ <div class="row items-center q-mb-md">
186
+ <div class="text-h6">
187
+ {{ isEditMode ? t("Edit Translation") : t("Add Translation") }}
188
+ </div>
189
+ <q-space />
190
+ <q-btn
191
+ v-if="isEditMode"
192
+ flat
193
+ dense
194
+ icon="sym_o_add"
195
+ :label="t('New')"
196
+ color="primary"
197
+ @click="onClearForm"
198
+ >
199
+ <q-tooltip>{{ t("Clear form and add new") }}</q-tooltip>
200
+ </q-btn>
201
+ </div>
202
+ <l-form :bordered="false" @submit="onSave">
203
+ <l-input
204
+ label="Name"
205
+ required
206
+ v-model.trim="obj.name"
207
+ clearable
208
+ :disable="isEditMode"
209
+ :hint="isEditMode ? t('Name cannot be changed in edit mode') : ''"
210
+ />
211
+ <l-input
212
+ v-for="language in app.languages"
213
+ :key="language.value"
214
+ :label="language.name"
215
+ v-model="obj[language.value]"
216
+ type="textarea"
217
+ />
218
+ <template #buttons>
219
+ <q-btn
220
+ v-if="isEditMode"
221
+ flat
222
+ :label="t('Cancel')"
223
+ @click="onClearForm"
224
+ class="q-mr-sm"
225
+ />
226
+ <l-save-btn :label="isEditMode ? t('Update') : t('Save')" />
227
+ </template>
228
+ </l-form>
229
+ </div>
118
230
  </template>
119
231
  </q-splitter>
120
232
  </l-card>
121
233
  </l-page>
122
234
  </template>
235
+
236
+ <style scoped>
237
+ .ellipsis{white-space:nowrap}.ellipsis,.ellipsis-2-lines{overflow:hidden;text-overflow:ellipsis}.ellipsis-2-lines{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word}
238
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,63 @@
1
+ <script setup>
2
+ import { ref } from "vue";
3
+ import { useLight } from "#imports";
4
+ import m from "../../composables/m";
5
+ const light = useLight();
6
+ const token = ref(null);
7
+ const expiresOptions = [
8
+ { label: "7 \u65E5", value: 7 * 24 * 60 * 60 },
9
+ { label: "30 \u65E5", value: 30 * 24 * 60 * 60 },
10
+ { label: "3 \u500B\u6708", value: 90 * 24 * 60 * 60 }
11
+ ];
12
+ const onSubmit = async (data) => {
13
+ token.value = null;
14
+ try {
15
+ const result = await m("createAccessToken", {
16
+ name: data.name,
17
+ expired_time: data.expired_time
18
+ });
19
+ token.value = result;
20
+ } catch (e) {
21
+ light.notify({ type: "negative", message: "\u5EFA\u7ACB\u5931\u6557" });
22
+ }
23
+ };
24
+ const copyToken = () => {
25
+ if (!token.value) return;
26
+ navigator.clipboard.writeText(token.value);
27
+ light.notify({ type: "positive", message: "\u5DF2\u8907\u88FD\u5230\u526A\u8CBC\u7C3F" });
28
+ };
29
+ const reset = () => {
30
+ token.value = null;
31
+ };
32
+ </script>
33
+
34
+ <template>
35
+ <l-page title="Create Access Token" :back-btn="true">
36
+ <div style="max-width: 560px;">
37
+ <form-kit type="l-form" :disabled="!!token" @submit="onSubmit">
38
+ <form-kit type="l-input" name="name" label="Token Name" validation="required" />
39
+ <form-kit type="l-select" name="expired_time" label="Expiration" :options="expiresOptions"
40
+ validation="required" />
41
+ </form-kit>
42
+ </div>
43
+
44
+ <!-- Token result -->
45
+ <q-card v-if="token" flat bordered class="q-pa-md q-mt-md" style="max-width: 560px;">
46
+ <q-card-section>
47
+ <div class="text-subtitle1 q-mb-sm text-positive">Token Created</div>
48
+ <div class="text-caption text-grey q-mb-sm">Please copy and save this token immediately. It will only be
49
+ shown once.</div>
50
+ <q-input :model-value="token" v-bind="light.styles.input" readonly type="textarea" autogrow>
51
+ <template #append>
52
+ <q-btn flat round icon="content_copy" @click="copyToken">
53
+ <q-tooltip>複製</q-tooltip>
54
+ </q-btn>
55
+ </template>
56
+ </q-input>
57
+ </q-card-section>
58
+ <q-card-actions>
59
+ <q-btn flat label="建立另一個" @click="reset" />
60
+ </q-card-actions>
61
+ </q-card>
62
+ </l-page>
63
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -53,6 +53,7 @@ eventLogCols.forEach((col) => {
53
53
  <template #header>
54
54
  <l-btn icon="sym_o_password" to="setting/password" label="Update password" />
55
55
  <l-btn icon="sym_o_key" to="setting/two-factor-auth" label="Two factor auth" />
56
+ <l-btn icon="sym_o_token" to="createAccessToken" label="Create access token" />
56
57
  </template>
57
58
 
58
59
  <l-row>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.64.3",
3
+ "version": "1.66.0",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,14 +51,14 @@
51
51
  "devDependencies": {
52
52
  "@nuxt/devtools": "latest",
53
53
  "@nuxt/eslint-config": "^1.10.0",
54
- "@nuxt/kit": "^4.2.2",
55
- "@nuxt/schema": "^4.2.2",
54
+ "@nuxt/kit": "^4.3.0",
55
+ "@nuxt/schema": "^4.3.0",
56
56
  "@nuxt/test-utils": "^3.17.2",
57
57
  "@types/google.accounts": "^0.0.18",
58
58
  "@types/node": "^24.10.1",
59
59
  "changelogen": "^0.6.2",
60
60
  "eslint": "^9.39.1",
61
- "nuxt": "^4.2.2",
61
+ "nuxt": "^4.3.0",
62
62
  "typescript": "^5.9.2",
63
63
  "vue-tsc": "^3.1.5"
64
64
  }