@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,253 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Pagination Component
4
+ *
5
+ * (The Vue implementation which should correspond to the Artalk VanillaJS)
6
+ */
7
+ const props = defineProps<{
8
+ /** Total page number */
9
+ total: number
10
+
11
+ /** Page size */
12
+ pageSize: number
13
+
14
+ /** Disabled */
15
+ disabled?: boolean
16
+ }>()
17
+
18
+ const { pageSize, total, disabled } = toRefs(props)
19
+
20
+ const emit = defineEmits<{
21
+ /** Page change event */
22
+ (evt: 'change', offset: number): void
23
+ }>()
24
+
25
+ const curtPage = ref(1)
26
+ const offset = computed(() => pageSize.value * (curtPage.value - 1))
27
+ const maxPage = computed(() => Math.ceil(total.value / pageSize.value))
28
+ const prevDisabled = computed(() => curtPage.value - 1 < 1)
29
+ const nextDisabled = computed(() => curtPage.value + 1 > maxPage.value)
30
+
31
+ const inputValue = ref(String(curtPage.value))
32
+ let inputTimer: number | undefined
33
+
34
+ function changePage(page: number) {
35
+ curtPage.value = page
36
+ emit('change', offset.value)
37
+ fillInput(page)
38
+ }
39
+
40
+ /**
41
+ * Change to previous page
42
+ */
43
+ function prev() {
44
+ if (disabled?.value) return
45
+ const page = curtPage.value - 1
46
+ if (page < 1) {
47
+ return
48
+ }
49
+ changePage(page)
50
+ }
51
+
52
+ /**
53
+ * Change to next page
54
+ */
55
+ function next() {
56
+ if (disabled?.value) return
57
+ const page = curtPage.value + 1
58
+ if (page > maxPage.value) {
59
+ return
60
+ }
61
+ changePage(page)
62
+ }
63
+
64
+ function reset() {
65
+ curtPage.value = 1
66
+ fillInput(1)
67
+ }
68
+
69
+ /**
70
+ * Fill input value
71
+ */
72
+ function fillInput(page: number) {
73
+ inputValue.value = String(page)
74
+ }
75
+
76
+ function revokeInput() {
77
+ fillInput(curtPage.value)
78
+ }
79
+
80
+ function triggerInput(now: boolean = false) {
81
+ window.clearTimeout(inputTimer)
82
+
83
+ const value = inputValue.value.trim()
84
+
85
+ const modify = () => {
86
+ if (value === '') {
87
+ revokeInput()
88
+ return
89
+ }
90
+ let page = Number(value)
91
+ if (Number.isNaN(page)) {
92
+ revokeInput()
93
+ return
94
+ }
95
+ if (page < 1) {
96
+ revokeInput()
97
+ return
98
+ }
99
+ if (page > maxPage.value) {
100
+ page = maxPage.value
101
+ }
102
+ changePage(page)
103
+ }
104
+
105
+ // Delay input trigger
106
+ if (!now) inputTimer = window.setTimeout(() => modify(), 800)
107
+ else modify()
108
+ }
109
+
110
+ function onInputKeydown(evt: KeyboardEvent) {
111
+ const keyCode = evt.keyCode || evt.which
112
+
113
+ if (keyCode === 38) {
114
+ // Up key
115
+ const page = Number(inputValue.value) + 1
116
+ if (page > maxPage.value) {
117
+ return
118
+ }
119
+ fillInput(page)
120
+ triggerInput(false)
121
+ } else if (keyCode === 40) {
122
+ // Down key
123
+ const page = Number(inputValue.value) - 1
124
+ if (page < 1) {
125
+ return
126
+ }
127
+ fillInput(page)
128
+ triggerInput(false)
129
+ } else if (keyCode === 13) {
130
+ // Enter key
131
+ triggerInput(true)
132
+ }
133
+ }
134
+
135
+ defineExpose({ prev, next, reset })
136
+ </script>
137
+
138
+ <template>
139
+ <div class="atk-pagination-wrap">
140
+ <div class="atk-pagination">
141
+ <div
142
+ class="atk-btn atk-btn-prev"
143
+ :class="{ 'atk-disabled': disabled || prevDisabled }"
144
+ aria-label="Previous page"
145
+ @click="prev()"
146
+ >
147
+ <svg
148
+ stroke="currentColor"
149
+ fill="currentColor"
150
+ stroke-width="0"
151
+ viewBox="0 0 512 512"
152
+ height="14px"
153
+ width="14px"
154
+ xmlns="http://www.w3.org/2000/svg"
155
+ >
156
+ <path
157
+ d="M217.9 256L345 129c9.4-9.4 9.4-24.6 0-33.9-9.4-9.4-24.6-9.3-34 0L167 239c-9.1 9.1-9.3 23.7-.7 33.1L310.9 417c4.7 4.7 10.9 7 17 7s12.3-2.3 17-7c9.4-9.4 9.4-24.6 0-33.9L217.9 256z"
158
+ ></path>
159
+ </svg>
160
+ </div>
161
+ <input
162
+ v-model="inputValue"
163
+ type="text"
164
+ class="atk-input"
165
+ aria-label="Enter the number of page"
166
+ :disabled="disabled"
167
+ @input="triggerInput(false)"
168
+ @keydown="onInputKeydown"
169
+ />
170
+ <div
171
+ class="atk-btn atk-btn-next"
172
+ :class="{ 'atk-disabled': disabled || nextDisabled }"
173
+ aria-label="Next page"
174
+ @click="next()"
175
+ >
176
+ <svg
177
+ stroke="currentColor"
178
+ fill="currentColor"
179
+ stroke-width="0"
180
+ viewBox="0 0 512 512"
181
+ height="14px"
182
+ width="14px"
183
+ xmlns="http://www.w3.org/2000/svg"
184
+ >
185
+ <path
186
+ d="M294.1 256L167 129c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.3 34 0L345 239c9.1 9.1 9.3 23.7.7 33.1L201.1 417c-4.7 4.7-10.9 7-17 7s-12.3-2.3-17-7c-9.4-9.4-9.4-24.6 0-33.9l127-127.1z"
187
+ ></path>
188
+ </svg>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </template>
193
+
194
+ <style scoped lang="scss">
195
+ .atk-pagination {
196
+ display: flex;
197
+ flex-direction: row;
198
+ justify-content: center;
199
+ padding: 10px 0;
200
+ position: relative;
201
+
202
+ & > .atk-btn,
203
+ & > .atk-input {
204
+ font-size: 15px;
205
+ height: 30px;
206
+ border: 1px solid var(--at-color-border);
207
+ border-radius: 3px;
208
+ padding: 0 5px;
209
+ text-align: center;
210
+ background: var(--at-color-bg);
211
+ }
212
+
213
+ & > .atk-btn {
214
+ user-select: none;
215
+ width: 60px;
216
+ cursor: pointer;
217
+ display: flex;
218
+ justify-content: center;
219
+ align-items: center;
220
+
221
+ &:hover {
222
+ background: var(--at-color-bg-grey);
223
+ }
224
+
225
+ &.atk-disabled {
226
+ color: var(--at-color-sub);
227
+
228
+ &:hover {
229
+ cursor: default;
230
+ background: initial;
231
+ }
232
+ }
233
+ }
234
+
235
+ & > .atk-input {
236
+ background: transparent;
237
+ color: var(--at-color-font);
238
+ font-size: 18px;
239
+ width: 60px;
240
+ outline: none;
241
+
242
+ &:focus {
243
+ border-color: var(--at-color-main);
244
+ }
245
+ }
246
+
247
+ & > * {
248
+ &:not(:last-child) {
249
+ margin-right: 10px;
250
+ }
251
+ }
252
+ }
253
+ </style>
@@ -0,0 +1,105 @@
1
+ <script setup lang="ts">
2
+ import settings, { patchOptionValue, type OptionNode } from '../lib/settings'
3
+
4
+ const props = defineProps<{
5
+ node: OptionNode
6
+ }>()
7
+
8
+ const customValue = ref<string[]>([])
9
+ const disabled = ref(false)
10
+
11
+ onMounted(() => {
12
+ sync()
13
+ })
14
+
15
+ function sync() {
16
+ const value = settings.get().getCustom(props.node.path)
17
+ disabled.value = !!settings.get().getEnvByPath(props.node.path)
18
+ if (typeof value === 'object' && 'toJSON' in value && typeof value.toJSON === 'function') {
19
+ customValue.value = value.toJSON()
20
+ } else if (typeof value === 'string') {
21
+ customValue.value = value.split(' ')
22
+ } else {
23
+ customValue.value = []
24
+ }
25
+ }
26
+
27
+ function save() {
28
+ const v = patchOptionValue(customValue.value, props.node)
29
+ settings.get().setCustom(props.node.path, v)
30
+ }
31
+
32
+ function onChange(index: number, val: string) {
33
+ customValue.value[index] = val
34
+ save()
35
+ }
36
+
37
+ function remove(index: number) {
38
+ customValue.value.splice(index, 1)
39
+ save()
40
+ }
41
+
42
+ function add() {
43
+ customValue.value.push('')
44
+ save()
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <div class="arr-grp">
50
+ <div v-for="(item, index) in customValue" :key="index" class="arr-item">
51
+ <input
52
+ type="text"
53
+ :value="String(item)"
54
+ :disabled="disabled"
55
+ @change="onChange(index, ($event.target as any).value)"
56
+ />
57
+ <button v-if="!disabled" class="act-btn" @click="remove(index)">-</button>
58
+ </div>
59
+ <div v-if="!disabled" class="act-grp">
60
+ <button class="act-btn" @click="add()">+</button>
61
+ </div>
62
+ </div>
63
+ </template>
64
+
65
+ <style scoped lang="scss">
66
+ .arr-grp {
67
+ width: 100%;
68
+ }
69
+
70
+ .arr-item {
71
+ position: relative;
72
+ margin-bottom: 20px;
73
+ padding-right: 40px;
74
+
75
+ .act-btn {
76
+ position: absolute;
77
+ top: 50%;
78
+ transform: translateY(-50%);
79
+ right: 0;
80
+ }
81
+ }
82
+
83
+ .act-grp {
84
+ margin-left: 10px;
85
+
86
+ .act-btn {
87
+ padding: 2px 30px;
88
+ }
89
+ }
90
+
91
+ .act-btn {
92
+ display: inline-block;
93
+ padding: 2px 10px;
94
+ cursor: pointer;
95
+ border: 0;
96
+ color: var(--at-color-font);
97
+ background: var(--at-color-bg-grey);
98
+ border-radius: 2px;
99
+
100
+ &:hover {
101
+ color: var(--at-color-light);
102
+ background: var(--at-color-bg-light);
103
+ }
104
+ }
105
+ </style>
@@ -0,0 +1,153 @@
1
+ <script setup lang="ts">
2
+ import type { OptionNode } from '../lib/settings'
3
+
4
+ const props = defineProps<{
5
+ node: OptionNode
6
+ }>()
7
+
8
+ const expanded = ref(true)
9
+
10
+ const expandable = computed(() => {
11
+ return props.node.level === 1 && (props.node.type === 'object' || props.node.type === 'array')
12
+ })
13
+
14
+ onMounted(() => {
15
+ if (expandable.value) expanded.value = false
16
+ })
17
+
18
+ function onHeadClick(evt: Event) {
19
+ if (!expandable.value) return
20
+ if (!expanded.value) {
21
+ expanded.value = true
22
+ // nextTick(() => {
23
+ // nav.scrollPageToEl(evt.target as HTMLElement)
24
+ // })
25
+ } else {
26
+ expanded.value = false
27
+ }
28
+ }
29
+
30
+ const hiddenNodes = ['admin_users']
31
+ </script>
32
+
33
+ <template>
34
+ <div
35
+ v-if="!hiddenNodes.includes(node.name)"
36
+ class="pf-grp"
37
+ :class="[`level-${node.level}`, expanded ? 'expand' : '']"
38
+ >
39
+ <div
40
+ v-if="node.level > 0 && (node.type === 'object' || node.type === 'array')"
41
+ class="pf-head"
42
+ @click="onHeadClick"
43
+ >
44
+ <div class="title">{{ node.title }}</div>
45
+ <div v-if="!!node.subTitle" class="sub-title">{{ node.subTitle }}</div>
46
+ </div>
47
+ <div v-show="expanded" class="pf-body">
48
+ <!-- Grp -->
49
+ <template v-if="node.items">
50
+ <PreferenceGrp v-for="n in node.items" :key="n.path" :node="n" />
51
+ </template>
52
+
53
+ <!-- Item -->
54
+ <PreferenceItem v-else :node="node" />
55
+ </div>
56
+ </div>
57
+ </template>
58
+
59
+ <style scoped lang="scss">
60
+ .pf-grp {
61
+ background: var(--at-color-bg);
62
+ margin-bottom: 10px;
63
+ border-radius: 4px;
64
+ }
65
+
66
+ .pf-grp.level-1 {
67
+ & > .pf-head {
68
+ margin-top: 30px;
69
+ margin-bottom: 20px;
70
+ cursor: pointer;
71
+
72
+ .title {
73
+ user-select: none;
74
+ font-size: 1.4em;
75
+ font-weight: bold;
76
+ padding-left: 12px;
77
+
78
+ &::before {
79
+ position: absolute;
80
+ top: 50%;
81
+ transform: translateY(-50%);
82
+ transition: height ease 0.2s;
83
+ left: -10px;
84
+ content: '';
85
+ height: 10px;
86
+ width: 10px;
87
+ background: #8ecee2;
88
+ border-radius: 2px;
89
+ }
90
+ }
91
+ }
92
+
93
+ & > .pf-body {
94
+ }
95
+
96
+ &.expand > .pf-head .title::before {
97
+ height: 25px;
98
+ }
99
+ }
100
+
101
+ .pf-grp.level-2 {
102
+ & > .pf-head {
103
+ margin-top: 15px;
104
+ margin-bottom: 20px;
105
+
106
+ .title {
107
+ position: relative;
108
+ font-weight: bold;
109
+ font-size: 1.2em;
110
+ }
111
+ }
112
+ }
113
+
114
+ .pf-grp.level-1,
115
+ .pf-grp.level-2 {
116
+ & > .pf-head > .sub-title {
117
+ padding: 0 10px 0 10px;
118
+ margin-left: 4px;
119
+ margin-top: 15px;
120
+ border-left: 2px solid var(--at-color-border);
121
+ }
122
+ }
123
+
124
+ .pf-grp.level-3 {
125
+ margin-left: 10px;
126
+
127
+ & > .pf-head {
128
+ margin-top: 15px;
129
+ margin-bottom: 20px;
130
+
131
+ & > .title {
132
+ position: relative;
133
+ font-weight: bold;
134
+ font-size: 0.9em;
135
+ }
136
+ }
137
+
138
+ & > .pf-body {
139
+ margin-left: 15px;
140
+ }
141
+ }
142
+
143
+ .pf-head {
144
+ & > .title {
145
+ position: relative;
146
+ }
147
+
148
+ & > .sub-title {
149
+ font-size: 14px;
150
+ margin-top: 5px;
151
+ }
152
+ }
153
+ </style>
@@ -0,0 +1,159 @@
1
+ <script setup lang="ts">
2
+ import settings, { patchOptionValue, type OptionNode } from '../lib/settings'
3
+ import { isSensitiveConfigPath } from '@/lib/settings-sensitive'
4
+
5
+ const props = defineProps<{
6
+ node: OptionNode
7
+ }>()
8
+
9
+ const value = ref('')
10
+ const disabled = ref(false)
11
+ const sensitiveHidden = ref(true)
12
+
13
+ const { t } = useI18n()
14
+
15
+ onBeforeMount(() => {
16
+ // initial value
17
+ value.value = settings.get().getCustom(props.node.path)
18
+ disabled.value = !!settings.get().getEnvByPath(props.node.path)
19
+ })
20
+
21
+ function onChange() {
22
+ const v = patchOptionValue(value.value, props.node)
23
+ settings.get().setCustom(props.node.path, v)
24
+ // console.log('[SET]', props.node.path, v)
25
+ }
26
+
27
+ const envVariableName = computed(() => `ATK_${props.node.path.replace(/\./g, '_').toUpperCase()}`)
28
+ const isSensitive = computed(() => isSensitiveConfigPath(props.node.path))
29
+
30
+ function toggleSensitiveHidden() {
31
+ sensitiveHidden.value = !sensitiveHidden.value
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <div class="pf-item">
37
+ <div class="info">
38
+ <div class="title" :title="envVariableName">{{ node.title }}</div>
39
+ <div v-if="node.subTitle" class="sub-title">{{ node.subTitle }}</div>
40
+ </div>
41
+
42
+ <div class="value">
43
+ <div v-if="disabled" class="disable-note">
44
+ {{ t('envVarControlHint', { key: 'ATK_' + props.node.path.toUpperCase() }) }}
45
+ </div>
46
+
47
+ <!-- Array -->
48
+ <template v-if="node.type === 'array'">
49
+ <PreferenceArr :node="node" />
50
+ </template>
51
+
52
+ <!-- Dropdown -->
53
+ <template v-else-if="node.selector">
54
+ <select v-model="value" :disabled="disabled" @change="onChange">
55
+ <option v-for="(item, i) in node.selector" :key="i" :value="item">
56
+ {{ item }}
57
+ </option>
58
+ </select>
59
+ </template>
60
+
61
+ <!-- Toggle -->
62
+ <template v-else-if="node.type === 'boolean'">
63
+ <input v-model="value" type="checkbox" :disabled="disabled" @change="onChange" />
64
+ </template>
65
+
66
+ <!-- Text -->
67
+ <template v-else>
68
+ <input
69
+ v-model="value"
70
+ :type="!isSensitive || !sensitiveHidden ? 'text' : 'password'"
71
+ :disabled="disabled"
72
+ @change="onChange"
73
+ />
74
+ <div v-if="isSensitive" class="input-suffix">
75
+ <div class="hidden-switch" @click="toggleSensitiveHidden()">
76
+ <i :class="['atk-icon', `atk-icon-eye-${sensitiveHidden ? 'off' : 'on'}`]" />
77
+ </div>
78
+ </div>
79
+ </template>
80
+ </div>
81
+ </div>
82
+ </template>
83
+
84
+ <style scoped lang="scss">
85
+ .pf-item {
86
+ display: flex;
87
+ flex-direction: row;
88
+ margin-bottom: 20px;
89
+
90
+ & > .info {
91
+ display: flex;
92
+ justify-content: center;
93
+ flex-direction: column;
94
+ flex: 1;
95
+ padding-right: 20px;
96
+
97
+ .title {
98
+ }
99
+
100
+ .sub-title {
101
+ font-size: 14px;
102
+ margin-top: 4px;
103
+ color: #697182;
104
+ }
105
+ }
106
+
107
+ & > .value {
108
+ position: relative;
109
+ flex: 1;
110
+ display: flex;
111
+ flex-direction: row;
112
+ justify-content: flex-end;
113
+ align-items: center;
114
+ min-height: 35px;
115
+
116
+ &:hover {
117
+ .disable-note {
118
+ opacity: 1;
119
+ }
120
+ }
121
+
122
+ .disable-note {
123
+ z-index: 999;
124
+ padding: 3px 8px;
125
+ background: rgba(105, 113, 130, 0.9);
126
+ color: #fff;
127
+ position: absolute;
128
+ top: -30px;
129
+ font-size: 13px;
130
+ left: 0;
131
+ opacity: 0;
132
+ transition: opacity 0.2s;
133
+ }
134
+
135
+ .input-suffix {
136
+ margin-left: 5px;
137
+ }
138
+
139
+ .hidden-switch {
140
+ cursor: pointer;
141
+ padding-left: 10px;
142
+
143
+ .atk-icon {
144
+ &::after {
145
+ background-color: #697182;
146
+ }
147
+
148
+ &.atk-icon-eye-on::after {
149
+ mask-image: url('@/assets/icon-eye-on.svg');
150
+ }
151
+
152
+ &.atk-icon-eye-off::after {
153
+ mask-image: url('@/assets/icon-eye-off.svg');
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ </style>