@esershnr/artalk-sidebar 1.0.3

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 (75) hide show
  1. package/README.md +16 -0
  2. package/auto-imports.d.ts +76 -0
  3. package/components.d.ts +31 -0
  4. package/env.d.ts +2 -0
  5. package/index.html +15 -0
  6. package/package.json +32 -0
  7. package/public/favicon.png +0 -0
  8. package/public/robots.txt +2 -0
  9. package/src/App.vue +89 -0
  10. package/src/artalk.ts +82 -0
  11. package/src/assets/favicon.png +0 -0
  12. package/src/assets/icon-darkmode-off.svg +1 -0
  13. package/src/assets/icon-darkmode-on.svg +1 -0
  14. package/src/assets/icon-eye-off.svg +1 -0
  15. package/src/assets/icon-eye-on.svg +1 -0
  16. package/src/assets/nav-icon-comments.svg +1 -0
  17. package/src/assets/nav-icon-pages.svg +1 -0
  18. package/src/assets/nav-icon-search.svg +1 -0
  19. package/src/assets/nav-icon-settings.svg +1 -0
  20. package/src/assets/nav-icon-sites.svg +1 -0
  21. package/src/assets/nav-icon-transfer.svg +1 -0
  22. package/src/assets/nav-icon-users.svg +1 -0
  23. package/src/components/AppHeader.vue +235 -0
  24. package/src/components/AppNavigation.vue +11 -0
  25. package/src/components/AppNavigationDesktop.vue +176 -0
  26. package/src/components/AppNavigationMenu.ts +152 -0
  27. package/src/components/AppNavigationMobile.vue +187 -0
  28. package/src/components/AppNavigationSearch.vue +137 -0
  29. package/src/components/FileUploader.vue +149 -0
  30. package/src/components/ItemTextEditor.vue +130 -0
  31. package/src/components/LoadingLayer.vue +37 -0
  32. package/src/components/LogTerminal.vue +89 -0
  33. package/src/components/PageEditor.vue +171 -0
  34. package/src/components/Pagination.vue +253 -0
  35. package/src/components/PreferenceArr.vue +105 -0
  36. package/src/components/PreferenceGrp.vue +153 -0
  37. package/src/components/PreferenceItem.vue +159 -0
  38. package/src/components/SiteCreate.vue +96 -0
  39. package/src/components/SiteEditor.vue +138 -0
  40. package/src/components/SiteSwitcher.vue +184 -0
  41. package/src/components/UserEditor.vue +229 -0
  42. package/src/global.ts +62 -0
  43. package/src/hooks/MobileWidth.ts +27 -0
  44. package/src/i18n/fr.ts +103 -0
  45. package/src/i18n/ja.ts +100 -0
  46. package/src/i18n/ko.ts +99 -0
  47. package/src/i18n/ru.ts +102 -0
  48. package/src/i18n/tr.ts +102 -0
  49. package/src/i18n/zh-CN.ts +97 -0
  50. package/src/i18n/zh-TW.ts +97 -0
  51. package/src/i18n-en.ts +99 -0
  52. package/src/i18n.ts +37 -0
  53. package/src/lib/promise-polyfill.ts +9 -0
  54. package/src/lib/settings-option.ts +186 -0
  55. package/src/lib/settings-sensitive.ts +44 -0
  56. package/src/lib/settings.ts +94 -0
  57. package/src/main.ts +65 -0
  58. package/src/pages/comments.vue +110 -0
  59. package/src/pages/index.vue +33 -0
  60. package/src/pages/login.vue +245 -0
  61. package/src/pages/pages.vue +309 -0
  62. package/src/pages/settings.vue +181 -0
  63. package/src/pages/sites.vue +353 -0
  64. package/src/pages/transfer.vue +204 -0
  65. package/src/pages/users.vue +271 -0
  66. package/src/stores/nav.ts +114 -0
  67. package/src/stores/user.ts +48 -0
  68. package/src/style/_extends.scss +100 -0
  69. package/src/style/_variables.scss +18 -0
  70. package/src/style.scss +245 -0
  71. package/src/vue-i18n.d.ts +7 -0
  72. package/tsconfig.json +40 -0
  73. package/tsconfig.node.json +11 -0
  74. package/typed-router.d.ts +30 -0
  75. package/vite.config.ts +71 -0
@@ -0,0 +1,181 @@
1
+ <script setup lang="ts">
2
+ import YAML from 'yaml'
3
+ import { shallowRef } from 'vue'
4
+ import { storeToRefs } from 'pinia'
5
+ import { useNavStore } from '../stores/nav'
6
+ import { artalk } from '../global'
7
+ import settings, { type OptionNode } from '../lib/settings'
8
+ import LoadingLayer from '../components/LoadingLayer.vue'
9
+
10
+ const nav = useNavStore()
11
+ const router = useRouter()
12
+ const { t } = useI18n()
13
+ const { curtTab } = storeToRefs(nav)
14
+ const isLoading = ref(false)
15
+ const tree = shallowRef<OptionNode>()
16
+
17
+ onMounted(() => {
18
+ nav.updateTabs({
19
+ sites: 'site',
20
+ transfer: 'transfer',
21
+ })
22
+
23
+ watch(curtTab, (tab) => {
24
+ if (tab === 'sites') router.replace('/sites')
25
+ else if (tab === 'transfer') router.replace('/transfer')
26
+ })
27
+
28
+ Promise.all([
29
+ artalk!.ctx.getApi().settings.getSettingsTemplate(''),
30
+ artalk!.ctx.getApi().settings.getSettings(),
31
+ ]).then(([template, custom]) => {
32
+ const yamlObj = YAML.parseDocument(template.data.yaml)
33
+ tree.value = settings.init(yamlObj).getTree()
34
+ // console.log(tree.value)
35
+ settings.get().setCustoms(custom.data.yaml)
36
+ settings.get().setEnvs(custom.data.envs)
37
+ })
38
+ })
39
+
40
+ function save() {
41
+ let yamlStr = ''
42
+ try {
43
+ yamlStr = settings.get().getCustoms().value?.toString() || ''
44
+ } catch (err) {
45
+ alert('YAML export error: ' + err)
46
+ console.error(err)
47
+ return
48
+ }
49
+
50
+ // console.log(yamlStr)
51
+ if (!yamlStr) {
52
+ alert('YAML export error: data is empty')
53
+ return
54
+ }
55
+
56
+ if (isLoading.value) return
57
+ isLoading.value = true
58
+ artalk!.ctx
59
+ .getApi()
60
+ .settings.applySettings({
61
+ yaml: yamlStr,
62
+ })
63
+ .then(() => {
64
+ alert(t('settingSaved'))
65
+ })
66
+ .catch((err) => {
67
+ console.error(err)
68
+ alert(t('settingSaveFailed') + ': ' + err)
69
+ })
70
+ .finally(() => {
71
+ isLoading.value = false
72
+ })
73
+ }
74
+ </script>
75
+
76
+ <template>
77
+ <div class="settings">
78
+ <div class="act-bar">
79
+ <div class="atk-sidebar-container">
80
+ <div class="status-text"></div>
81
+ <button class="save-btn" @click="save()">
82
+ <i class="atk-icon atk-icon-yes" />
83
+ {{ t('apply') }}
84
+ </button>
85
+ </div>
86
+ <LoadingLayer v-if="isLoading" />
87
+ </div>
88
+ <div v-if="tree" class="pfs">
89
+ <PreferenceGrp :node="tree" />
90
+ <div class="notice">{{ t('settingNotice') }}</div>
91
+ </div>
92
+ </div>
93
+ </template>
94
+
95
+ <style scoped lang="scss">
96
+ .settings {
97
+ .notice {
98
+ font-size: 13px;
99
+ background: var(--at-color-bg-light);
100
+ color: var(--at-color-light);
101
+ border-radius: 2px;
102
+ text-align: center;
103
+ padding: 8px 10px;
104
+ margin-top: 10px;
105
+ margin-bottom: 20px;
106
+ }
107
+
108
+ .act-bar {
109
+ z-index: 999;
110
+ position: fixed;
111
+ height: 55px;
112
+ width: 100%;
113
+ bottom: 0;
114
+ left: 0;
115
+ background: var(--at-color-bg-transl);
116
+ border-top: 1px solid var(--at-color-border);
117
+ padding: 0 20px;
118
+
119
+ .atk-sidebar-container {
120
+ display: flex;
121
+ height: 100%;
122
+ align-items: center;
123
+ justify-content: space-between;
124
+ flex-direction: row;
125
+ }
126
+
127
+ .status-text {
128
+ padding: 0 5px;
129
+ flex: 1;
130
+ }
131
+
132
+ button {
133
+ font-size: 14px;
134
+ display: inline-flex;
135
+ align-items: center;
136
+ padding: 4px 16px;
137
+ cursor: pointer;
138
+ background: transparent;
139
+ border-radius: 2px;
140
+ background: #36abcf;
141
+ color: #fff;
142
+ border: 0;
143
+
144
+ &:active {
145
+ opacity: 0.9;
146
+ }
147
+
148
+ i {
149
+ margin-right: 8px;
150
+
151
+ &::after {
152
+ background-color: #fff;
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ .pfs {
159
+ padding: 10px 30px;
160
+ }
161
+
162
+ :deep(input[type='text']),
163
+ :deep(input[type='password']),
164
+ :deep(select) {
165
+ font-size: 17px;
166
+ width: 100%;
167
+ height: 35px;
168
+ padding: 3px 5px;
169
+ border: 0;
170
+ border-bottom: 1px solid var(--at-color-border);
171
+ outline: none;
172
+ background: transparent;
173
+ -webkit-appearance: none;
174
+ border-radius: 0;
175
+
176
+ &:focus {
177
+ border-bottom-color: var(--at-color-main);
178
+ }
179
+ }
180
+ }
181
+ </style>
@@ -0,0 +1,353 @@
1
+ <script setup lang="ts">
2
+ import type { ArtalkType } from '@esershnr/artalk'
3
+ import { useNavStore } from '../stores/nav'
4
+ import { artalk, bootParams } from '../global'
5
+
6
+ const nav = useNavStore()
7
+ const sites = ref<ArtalkType.SiteData[]>([])
8
+ const curtEditSite = ref<ArtalkType.SiteData | null>(null)
9
+ const showSiteCreate = ref(false)
10
+ const siteCreateInitVal = ref()
11
+ const { t } = useI18n()
12
+
13
+ onMounted(() => {
14
+ nav.updateTabs({}, 'sites')
15
+
16
+ nav.setPageLoading(true)
17
+ artalk?.ctx
18
+ .getApi()
19
+ .sites.getSites()
20
+ .then((res) => {
21
+ sites.value = res.data.sites
22
+ })
23
+ .finally(() => {
24
+ nav.setPageLoading(false)
25
+ })
26
+
27
+ // Open site create dialog by view params (from URL query)
28
+ const vp = bootParams.viewParams
29
+ if (vp && vp.create_name && vp.create_urls) {
30
+ siteCreateInitVal.value = { name: vp.create_name, urls: vp.create_urls }
31
+ showSiteCreate.value = true
32
+ nextTick(() => {
33
+ siteCreateInitVal.value = null
34
+ bootParams.viewParams = null
35
+ })
36
+ }
37
+ })
38
+
39
+ function create() {
40
+ curtEditSite.value = null
41
+ showSiteCreate.value = true
42
+ }
43
+
44
+ const sitesGrouped = computed(() => {
45
+ if (sites.value.length === 0) return []
46
+
47
+ const grp: ArtalkType.SiteData[][] = []
48
+ let j = -1
49
+ for (let i = 0; i < sites.value.length; i++) {
50
+ const item = sites.value[i]
51
+ if (i % 4 === 0) {
52
+ // Each row has 4 items
53
+ grp.push([])
54
+ j++
55
+ }
56
+ grp[j].push(item)
57
+ }
58
+ return grp
59
+ })
60
+
61
+ function edit(site: ArtalkType.SiteData) {
62
+ showSiteCreate.value = false
63
+ curtEditSite.value = site
64
+ }
65
+
66
+ function onNewSiteCreated(siteNew: ArtalkType.SiteData) {
67
+ sites.value.push(siteNew)
68
+ showSiteCreate.value = false
69
+ nav.refreshSites()
70
+ }
71
+
72
+ function onSiteItemUpdate(site: ArtalkType.SiteData) {
73
+ const index = sites.value.findIndex((s) => s.id === site.id)
74
+ if (index != -1) {
75
+ const orgSite = sites.value[index]
76
+ Object.keys(site).forEach((key) => {
77
+ ;(orgSite as any)[key] = (site as any)[key]
78
+ })
79
+ }
80
+ nav.refreshSites()
81
+ }
82
+
83
+ function onSiteItemRemove(id: number) {
84
+ const index = sites.value.findIndex((p) => p.id === id)
85
+ sites.value.splice(index, 1)
86
+ nav.refreshSites()
87
+ }
88
+ </script>
89
+
90
+ <template>
91
+ <div class="atk-site-list">
92
+ <div class="atk-header">
93
+ <div class="atk-title">{{ t('siteCount', { count: sites.length }) }}</div>
94
+ <div class="atk-actions">
95
+ <div class="atk-item atk-site-add-btn" @click="create()">
96
+ <i class="atk-icon atk-icon-plus"></i>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ <SiteCreate
101
+ v-if="showSiteCreate"
102
+ :init-val="siteCreateInitVal"
103
+ @close="showSiteCreate = false"
104
+ @done="onNewSiteCreated"
105
+ />
106
+ <div class="atk-site-rows-wrap">
107
+ <template v-for="(ss, i) in sitesGrouped" :key="i">
108
+ <template v-if="curtEditSite !== null">
109
+ <SiteEditor
110
+ v-if="!!ss.includes(curtEditSite)"
111
+ :site="curtEditSite"
112
+ @close="curtEditSite = null"
113
+ @update="onSiteItemUpdate"
114
+ @remove="onSiteItemRemove"
115
+ />
116
+ </template>
117
+ <div class="atk-site-row">
118
+ <div
119
+ v-for="site in ss"
120
+ :key="site.id"
121
+ class="atk-site-item"
122
+ :class="{ 'atk-active': curtEditSite === site }"
123
+ @click="edit(site)"
124
+ >
125
+ <div class="atk-site-logo">{{ site.name.substring(0, 1) }}</div>
126
+ <div class="atk-site-name">{{ site.name }}</div>
127
+ </div>
128
+ </div>
129
+ </template>
130
+ </div>
131
+ </div>
132
+ </template>
133
+
134
+ <style scoped lang="scss">
135
+ .atk-site-list {
136
+ & > .atk-header {
137
+ display: flex;
138
+ flex-direction: row;
139
+ padding: 15px 30px;
140
+ align-items: center;
141
+
142
+ .atk-title {
143
+ flex: auto;
144
+ padding-right: 10px;
145
+ }
146
+
147
+ .atk-actions {
148
+ display: flex;
149
+ flex-direction: row;
150
+
151
+ .atk-item {
152
+ display: flex;
153
+ height: 30px;
154
+ width: 30px;
155
+ justify-content: center;
156
+ align-items: center;
157
+ user-select: none;
158
+ cursor: pointer;
159
+ border-radius: 2px;
160
+
161
+ &:hover {
162
+ background: var(--at-color-bg-grey);
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ .atk-site-rows-wrap {
169
+ position: relative;
170
+
171
+ .atk-site-row {
172
+ display: flex;
173
+ flex-direction: row;
174
+ padding: 10px 20px;
175
+ padding-bottom: 0;
176
+ }
177
+
178
+ .atk-site-item {
179
+ display: flex;
180
+ flex-basis: 25%;
181
+ flex-direction: column;
182
+ align-items: center;
183
+ padding-bottom: 5px;
184
+ user-select: none;
185
+ cursor: pointer;
186
+ border: 1px solid transparent;
187
+
188
+ .atk-site-logo {
189
+ margin: 15px;
190
+ text-align: center;
191
+ font-size: 20px;
192
+ height: 65px;
193
+ width: 65px;
194
+ line-height: 65px;
195
+ background: #5b6f7e;
196
+ color: #fff;
197
+ border-radius: 4px;
198
+ }
199
+
200
+ .atk-site-name {
201
+ text-align: center;
202
+ font-size: 15px;
203
+ color: var(--at-color-sub);
204
+ padding: 0 17px;
205
+ word-break: break-word;
206
+ }
207
+
208
+ &.atk-active {
209
+ background-color: var(--at-color-bg-grey);
210
+ border: 1px solid var(--at-color-border);
211
+ margin-top: -1px;
212
+ border-radius: 0 0 4px 4px;
213
+
214
+ .atk-site-name {
215
+ color: var(--at-color-deep);
216
+ }
217
+ }
218
+
219
+ &:hover {
220
+ .atk-site-name {
221
+ color: var(--at-color-font);
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ :deep(.atk-site-edit),
228
+ :deep(.atk-site-add) {
229
+ position: relative;
230
+ min-height: 120px;
231
+ width: 100%;
232
+ border-top: 1px solid var(--at-color-border);
233
+ border-bottom: 1px solid var(--at-color-border);
234
+ margin-bottom: -10px;
235
+
236
+ @media (min-width: 1024px) {
237
+ border-left: 1px solid var(--at-color-border);
238
+ border-right: 1px solid var(--at-color-border);
239
+ border-radius: 4px;
240
+ padding-top: 10px;
241
+ }
242
+
243
+ .atk-header {
244
+ display: flex;
245
+ flex-direction: row;
246
+ align-items: center;
247
+ padding: 10px 30px 0 35px;
248
+ justify-content: space-between;
249
+
250
+ .atk-site-info {
251
+ .atk-site-name {
252
+ cursor: pointer;
253
+ display: inline-block;
254
+ font-size: 23px;
255
+ position: relative;
256
+ line-height: 1.6em;
257
+
258
+ &:after {
259
+ content: ' ';
260
+ position: absolute;
261
+ width: 100%;
262
+ height: 6px;
263
+ background: var(--at-color-main);
264
+ opacity: 0.4;
265
+ left: 0;
266
+ bottom: 6px;
267
+ }
268
+ }
269
+
270
+ .atk-site-urls {
271
+ display: flex;
272
+ width: 100%;
273
+ margin-top: 6px;
274
+ flex-wrap: wrap;
275
+ min-height: 23px;
276
+ margin-bottom: 15px;
277
+
278
+ .atk-url-item {
279
+ background: var(--at-color-bg-grey);
280
+ color: var(--at-color-font);
281
+ border-radius: 2px;
282
+ padding: 0 8px;
283
+ font-size: 13px;
284
+ margin-bottom: 3px;
285
+ margin-right: 3px;
286
+ cursor: pointer;
287
+
288
+ &:hover {
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ .atk-close-btn {
295
+ width: 50px;
296
+ height: 50px;
297
+ display: flex;
298
+ justify-content: center;
299
+ align-items: center;
300
+ cursor: pointer;
301
+
302
+ &:hover i::after {
303
+ background-color: var(--at-color-red);
304
+ }
305
+ }
306
+ }
307
+
308
+ .atk-main {
309
+ position: relative;
310
+ display: flex;
311
+ flex-direction: row;
312
+ padding: 0 30px 6px 35px;
313
+ padding-bottom: 10px;
314
+
315
+ .atk-site-text-actions {
316
+ @extend .atk-list-text-actions;
317
+ height: 90px;
318
+ padding: 0;
319
+ padding-left: 10px;
320
+
321
+ .atk-item {
322
+ margin-bottom: 17px;
323
+ margin-right: 25px;
324
+ }
325
+ }
326
+
327
+ .atk-site-btn-actions {
328
+ @extend .atk-list-btn-actions;
329
+
330
+ padding-right: 9px;
331
+ }
332
+
333
+ .atk-item-text-editor-layer {
334
+ padding: 10px 20px;
335
+ }
336
+ }
337
+ }
338
+
339
+ :deep(.atk-site-add) {
340
+ position: relative;
341
+
342
+ .atk-header {
343
+ .atk-title {
344
+ font-size: 20px;
345
+ }
346
+ }
347
+
348
+ .atk-form {
349
+ padding: 20px 40px;
350
+ }
351
+ }
352
+ }
353
+ </style>