@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,97 @@
1
+ import type { MessageSchema } from '../i18n'
2
+
3
+ export const zhTW: MessageSchema = {
4
+ ctrlCenter: '控制中心',
5
+ msgCenter: '訊息中心',
6
+ noContent: '無內容',
7
+ searchHint: '關鍵字搜尋...',
8
+ allSites: '所有網站',
9
+ siteManage: '網站管理',
10
+ comment: '評論',
11
+ page: '頁面',
12
+ user: '用戶',
13
+ site: '網站',
14
+ transfer: '轉移',
15
+ settings: '設置',
16
+ all: '全部',
17
+ pending: '待審',
18
+ personal: '個人',
19
+ mentions: '提及',
20
+ mine: '我的',
21
+ admin: '管理員',
22
+ create: '建立',
23
+ import: '匯入',
24
+ export: '匯出',
25
+ settingSaved: '設定已儲存',
26
+ settingSaveFailed: '設定儲存失敗',
27
+ settingNotice: '注:某些設定選項可能需要手動重啟才能生效',
28
+ apply: '套用',
29
+ updateComplete: '更新完畢',
30
+ updateReady: '準備更新...',
31
+ opFailed: '操作失敗',
32
+ updateTitle: '擷取標題',
33
+ uploading: '上傳中',
34
+ cancel: '取消',
35
+ back: '返回',
36
+ cacheClear: '清除快取',
37
+ cacheWarm: '預熱快取',
38
+ editTitle: '編輯標題',
39
+ switchKey: 'KEY 變更',
40
+ commentAllowAll: '允許任何人評論',
41
+ commentOnlyAdmin: '僅允許管理員評論',
42
+ config: '配置文件',
43
+ envVarControlHint: '由環境變數 {key} 參照',
44
+ userAdminHint: '該用戶具有管理員權限',
45
+ userInConfHint: '該用戶存在於配置文件中',
46
+ userInConfCannotEditHint: '暫不支持線上編輯配置文件中的用戶,請手動修改配置文件',
47
+ userDeleteConfirm:
48
+ '該操作將刪除 用戶:"{name}" 郵箱:"{email}" 所有評論,包括其評論下面他人的回覆評論,是否繼續?',
49
+ userDeleteManuallyHint: '用戶已從數據庫刪除,請手動編輯配置文件並刪除用戶',
50
+ pageDeleteConfirm: '確認刪除頁面 "{title}"?將會刪除所有相關數據',
51
+ siteDeleteConfirm: '該操作將刪除網站:"{name}" 及其下所有數據,是否繼續?',
52
+ siteNameInputHint: '請輸入網站名稱',
53
+ edit: '編輯',
54
+ delete: '刪除',
55
+ siteCount: '共 {count} 個網站',
56
+ createSite: '建立網站',
57
+ siteName: '網站名稱',
58
+ siteUrls: '網站 URLs',
59
+ multiSepHint: '使用逗號分隔多個',
60
+ add: '新增',
61
+ rename: '重命名',
62
+ inputHint: '輸入文字...',
63
+ userCreate: '建立用戶',
64
+ userEdit: '用戶編輯',
65
+ comments: '評論',
66
+ last: '最後',
67
+ show: '展開',
68
+ username: '用戶名',
69
+ email: '郵箱',
70
+ link: '連結',
71
+ badgeText: '徽章文字',
72
+ badgeColor: '徽章顏色',
73
+ role: '身份角色',
74
+ normal: '一般',
75
+ password: '密碼',
76
+ passwordEmptyHint: '留空表示不變更密碼',
77
+ emailNotify: '郵件通知',
78
+ enabled: '啟用',
79
+ disabled: '停用',
80
+ save: '儲存',
81
+ dataFile: '資料檔案',
82
+ artransfer: '轉移工具',
83
+ targetSiteName: '目標網站名稱',
84
+ targetSiteURL: '目標網站 URL',
85
+ payload: '有效載荷',
86
+ optional: '選填',
87
+ uploadReadyToImport: '檔案已上傳並準備匯入',
88
+ artransferToolHint: '使用 {link} 將評論數據轉換為 Artrans 格式。',
89
+ moreDetails: '查看詳情',
90
+ loginFailure: '登入失敗',
91
+ login: '登入',
92
+ logout: '登出',
93
+ logoutConfirm: '確定要登出嗎?',
94
+ loginSelectHint: '請選擇您要登入的帳號:',
95
+ }
96
+
97
+ export default zhTW
package/src/i18n-en.ts ADDED
@@ -0,0 +1,99 @@
1
+ export const en = {
2
+ ctrlCenter: 'Admin',
3
+ msgCenter: 'Messages',
4
+ noContent: 'No Content',
5
+ searchHint: 'Search by keywords...',
6
+ allSites: 'All Sites',
7
+ siteManage: 'Site Management',
8
+ comment: 'Comment',
9
+ page: 'Page',
10
+ user: 'User',
11
+ site: 'Site',
12
+ transfer: 'Transfer',
13
+ settings: 'Settings',
14
+ all: 'All',
15
+ pending: 'Pending',
16
+ personal: 'Personal',
17
+ mentions: 'Mentions',
18
+ mine: 'Mine',
19
+ admin: 'Admin',
20
+ create: 'Create',
21
+ import: 'Import',
22
+ export: 'Export',
23
+ settingSaved: 'Setting saved',
24
+ settingSaveFailed: 'Setting save failed',
25
+ settingNotice: 'Note: Some config options may require a manual reboot to take effect.',
26
+ apply: 'Apply',
27
+ updateComplete: 'Update complete',
28
+ updateReady: 'Ready to update...',
29
+ opFailed: 'Operation failed',
30
+ updateTitle: 'Fetch Title',
31
+ uploading: 'Uploading',
32
+ cancel: 'Cancel',
33
+ back: 'Back',
34
+ cacheClear: 'Cache Clear',
35
+ cacheWarm: 'Cache Warm',
36
+ editTitle: 'Edit Title',
37
+ switchKey: 'Switch Key',
38
+ commentAllowAll: 'Anyone Comment',
39
+ commentOnlyAdmin: 'Admin Comment Only',
40
+ config: 'Config',
41
+ envVarControlHint: 'Referenced by the environment variable {key}',
42
+ userAdminHint: 'Admin user',
43
+ userInConfHint: 'This user is defined in config file',
44
+ edit: 'Edit',
45
+ delete: 'Delete',
46
+ siteCount: 'Total {count} Sites',
47
+ createSite: 'Create Site',
48
+ siteName: 'Site Name',
49
+ siteUrls: 'Site URLs',
50
+ multiSepHint: 'multiple separated by commas',
51
+ add: 'Add',
52
+ rename: 'Rename',
53
+ inputHint: 'Input text...',
54
+ userCreate: 'Create User',
55
+ userEdit: 'Edit User',
56
+ userInConfCannotEditHint:
57
+ 'Cannot edit user in config file, please modify the config file manually',
58
+ userDeleteConfirm:
59
+ 'This operation will delete all comments of user: "{name}" email: "{email}", including the reply comments under his comments. Continue?',
60
+ userDeleteManuallyHint:
61
+ 'User has been deleted from the database, please manually edit the config file and delete the user',
62
+ pageDeleteConfirm:
63
+ 'This operation will delete the page: "{title}" and all data under it. Continue?',
64
+ siteDeleteConfirm:
65
+ 'This operation will delete the site: "{name}" and all data under it. Continue?',
66
+ siteNameInputHint: 'Please enter the site name',
67
+ comments: 'Comments',
68
+ last: 'Last',
69
+ show: 'Show',
70
+ username: 'Username',
71
+ email: 'Email',
72
+ link: 'Link',
73
+ badgeText: 'Badge Text',
74
+ badgeColor: 'Badge Color',
75
+ role: 'Role',
76
+ normal: 'Normal',
77
+ password: 'Password',
78
+ passwordEmptyHint: 'leave blank not change your password',
79
+ emailNotify: 'Email notification',
80
+ enabled: 'Enabled',
81
+ disabled: 'Disabled',
82
+ save: 'Save',
83
+ dataFile: 'Data File',
84
+ artransfer: 'Artransfer',
85
+ targetSiteName: 'Target Site Name',
86
+ targetSiteURL: 'Target Site URL',
87
+ payload: 'Payload',
88
+ optional: 'Optional',
89
+ uploadReadyToImport: 'File uploaded and is ready for import',
90
+ artransferToolHint: 'Use the {link} to convert data to Artrans format.',
91
+ moreDetails: 'More details',
92
+ loginFailure: 'Login failure',
93
+ login: 'Login',
94
+ logout: 'Logout',
95
+ logoutConfirm: 'Are you sure you want to log out?',
96
+ loginSelectHint: 'Please select the account you wish to log into:',
97
+ }
98
+
99
+ export default en
package/src/i18n.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { createI18n, type I18n, type Locale } from 'vue-i18n'
2
+ import { en } from './i18n-en'
3
+
4
+ export type MessageSchema = typeof en
5
+
6
+ export function setupI18n() {
7
+ const i18n = createI18n({
8
+ legacy: false, // use i18n in Composition API
9
+ locale: 'en',
10
+ fallbackLocale: 'en',
11
+ messages: { en } as any,
12
+ })
13
+
14
+ const setLocale = async (value: string) => {
15
+ await loadLocaleMessages(i18n, value)
16
+ i18n.global.locale.value = value
17
+ }
18
+
19
+ return { i18n, setLocale }
20
+ }
21
+
22
+ export async function loadLocaleMessages(i18n: I18n, locale: Locale) {
23
+ if (i18n.global.availableLocales.includes(locale)) return
24
+
25
+ // Load locale messages with dynamic import
26
+ // @see https://vitejs.dev/guide/features#dynamic-import
27
+ const messages = await import(`./i18n/${locale}.ts`)
28
+ .then((r: any) => r.default || r)
29
+ .catch(() => {
30
+ console.error(`Failed to load locale messages for "${locale}"`)
31
+ return
32
+ })
33
+
34
+ // Set locale and locale message
35
+ i18n.global.setLocaleMessage(locale, messages)
36
+ return nextTick()
37
+ }
@@ -0,0 +1,9 @@
1
+ Promise.withResolvers ??= function <T>() {
2
+ let resolve: PromiseWithResolvers<T>['resolve']
3
+ let reject: PromiseWithResolvers<T>['reject']
4
+ const promise = new Promise<T>((res, rej) => {
5
+ resolve = res
6
+ reject = rej
7
+ })
8
+ return { promise, resolve: resolve!, reject: reject! }
9
+ }
@@ -0,0 +1,186 @@
1
+ import YAML from 'yaml'
2
+ type Pair = YAML.Pair<YAML.Scalar<any>, YAML.Scalar<any> & YAML.YAMLMap<any, any>>
3
+
4
+ export interface OptionNode {
5
+ name: string
6
+ path: string
7
+ level: number
8
+ default?: string | number | boolean
9
+ selector?: string[]
10
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array'
11
+ title: string
12
+ subTitle?: string
13
+ items?: OptionNode[]
14
+ }
15
+
16
+ function extractItemComment(item: Pair, index: number, parentPair?: Pair): string {
17
+ let comment = ''
18
+ if (index === 0 && parentPair) comment = parentPair?.value?.commentBefore || ''
19
+ else comment = item?.key?.commentBefore || ''
20
+ return comment
21
+ }
22
+
23
+ export function getTree(yamlObj: YAML.Document.Parsed): OptionNode {
24
+ const tree: OptionNode = {
25
+ name: '',
26
+ path: '',
27
+ title: '',
28
+ level: 0,
29
+ type: 'object',
30
+ items: [],
31
+ }
32
+
33
+ const traverse = (
34
+ pairs: Pair[],
35
+ parentNode: OptionNode = tree,
36
+ parentPath: string[] = [],
37
+ parentPair?: Pair,
38
+ ) => {
39
+ pairs.forEach((item, index) => {
40
+ // get key and value
41
+ const key = item.key?.value
42
+ const value = item.value?.toJSON ? item.value.toJSON() : undefined
43
+ if (!key) return
44
+
45
+ // get path
46
+ const path = [...parentPath, key]
47
+
48
+ // get comment
49
+ const comment = extractItemComment(item, index, parentPair)
50
+
51
+ // get type
52
+ const probablyTypes = ['string', 'number', 'boolean', 'object']
53
+ const type =
54
+ (Array.isArray(value) ? 'array' : probablyTypes.find((t) => typeof value === t)) ||
55
+ undefined
56
+
57
+ if (!type) return
58
+
59
+ // get default value
60
+ const defaultValue = type !== 'object' ? value : undefined
61
+
62
+ // create new node
63
+ const node: OptionNode = {
64
+ name: key,
65
+ path: path.join('.'),
66
+ level: parentNode ? parentNode.level + 1 : 0,
67
+ ...extractComment(key, comment),
68
+ default: defaultValue,
69
+ type: type as any,
70
+ }
71
+
72
+ // traverse children
73
+ if (type === 'object' && item.value?.items) {
74
+ node.items = []
75
+ traverse(item.value.items, node, path, item)
76
+ }
77
+
78
+ // add to parent
79
+ if (!parentNode.items) parentNode.items = []
80
+ parentNode.items.push(node)
81
+ })
82
+ }
83
+
84
+ traverse((yamlObj.contents as YAML.YAMLMap<any, any>)?.items)
85
+
86
+ return tree
87
+ }
88
+
89
+ /**
90
+ * Get flatten meta data from yaml object
91
+ *
92
+ * @param yamlObj
93
+ * @returns
94
+ */
95
+ export function getFlattenNodes(tree: OptionNode): {
96
+ [path: string]: OptionNode
97
+ } {
98
+ const metas: { [path: string]: OptionNode } = {}
99
+
100
+ const traverse = (node: OptionNode) => {
101
+ metas[node.path] = node
102
+ if (node.items) node.items.forEach(traverse)
103
+ }
104
+
105
+ traverse(tree)
106
+
107
+ return metas
108
+ }
109
+
110
+ /**
111
+ * Extract option info from comment
112
+ *
113
+ * @param name Option name
114
+ * @param comment Option comment in YAML
115
+ * @returns Option info
116
+ */
117
+ function extractComment(name: string, comment: string) {
118
+ comment = comment.trim()
119
+
120
+ // ignore comments begin and end with `--`
121
+ comment = comment.replace(/--(.*?)--/gm, '')
122
+
123
+ let title = ''
124
+ let subTitle = ''
125
+ let selector: string[] | undefined
126
+
127
+ const stReg = /\(.*?\)/gm
128
+ title = comment.replace(stReg, '').trim()
129
+ const stFind = stReg.exec(comment)
130
+ subTitle = stFind ? stFind[0].substring(1, stFind[0].length - 1) : ''
131
+ if (!title) {
132
+ title = snakeToCamel(name)
133
+ }
134
+
135
+ const optReg = /\[.*?\]/gm
136
+ const optFind = optReg.exec(title)
137
+ if (optFind) {
138
+ try {
139
+ selector = JSON.parse(optFind[0])
140
+ } catch (err) {
141
+ console.error(err)
142
+ }
143
+ title = title.replace(optReg, '').trim()
144
+ }
145
+
146
+ return {
147
+ title,
148
+ subTitle,
149
+ selector,
150
+ }
151
+ }
152
+
153
+ function snakeToCamel(str: string) {
154
+ return str.toLowerCase().replace(/([_][a-z]|^[a-z])/g, (group) => group.slice(-1).toUpperCase())
155
+ }
156
+
157
+ /**
158
+ * Patch the option value by meta data
159
+ *
160
+ * @param value User custom value
161
+ * @param meta Option meta data
162
+ * @returns Patched value
163
+ */
164
+ export function patchOptionValue(value: any, node: OptionNode) {
165
+ // console.log(value, node)
166
+ switch (node.type) {
167
+ case 'boolean':
168
+ if (value === 'true') value = true
169
+ else if (value === 'false') value = false
170
+ break
171
+ case 'string':
172
+ if (!node.selector)
173
+ // ignore option item
174
+ value = String(value).trim()
175
+ break
176
+ case 'number':
177
+ if (!isNaN(Number(value))) value = Number(value)
178
+ break
179
+ case 'array':
180
+ // trim string array
181
+ if (Array.isArray(value)) value = value.map((v) => (typeof v === 'string' ? v.trim() : v))
182
+ break
183
+ }
184
+
185
+ return value
186
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * List of sensitive config paths.
3
+ *
4
+ * (which should be hidden in the UI)
5
+ */
6
+ export const SensitiveConfigPaths = [
7
+ 'app_key',
8
+ 'admin_notify.ding_talk.secret',
9
+ 'admin_notify.line.channel_access_token',
10
+ 'admin_notify.line.channel_secret',
11
+ 'admin_notify.lark.webhook_url',
12
+ 'admin_notify.slack.oauth_token',
13
+ 'admin_notify.telegram.api_token',
14
+ 'admin_notify.webhook.url',
15
+ 'admin.notify.bark.server',
16
+ 'auth.apple.client_secret',
17
+ 'auth.auth0.client_secret',
18
+ 'auth.discord.client_secret',
19
+ 'auth.facebook.client_secret',
20
+ 'auth.gitea.client_secret',
21
+ 'auth.github.client_secret',
22
+ 'auth.gitlab.client_secret',
23
+ 'auth.google.client_secret',
24
+ 'auth.line.client_secret',
25
+ 'auth.mastodon.client_secret',
26
+ 'auth.microsoft.client_secret',
27
+ 'auth.patreon.client_secret',
28
+ 'auth.slack.client_secret',
29
+ 'auth.tiktok.client_secret',
30
+ 'auth.twitter.client_secret',
31
+ 'auth.wechat.client_secret',
32
+ 'auth.steam.api_key',
33
+ 'captcha.geetest.captcha_key',
34
+ 'captcha.hcaptcha.secret_key',
35
+ 'captcha.recaptcha.secret_key',
36
+ 'captcha.turnstile.secret_key',
37
+ 'db.password',
38
+ 'email.ali_dm.access_key_secret',
39
+ 'email.smtp.password',
40
+ ]
41
+
42
+ export function isSensitiveConfigPath(path: string) {
43
+ return SensitiveConfigPaths.includes(path)
44
+ }
@@ -0,0 +1,94 @@
1
+ import YAML from 'yaml'
2
+ import { getFlattenNodes, getTree, type OptionNode } from './settings-option'
3
+
4
+ export class Settings {
5
+ private tree: OptionNode
6
+ private flatten: { [path: string]: OptionNode }
7
+ private customs = shallowRef<YAML.Document.Parsed<YAML.ParsedNode>>()
8
+ private envs = shallowRef<{ [key: string]: string }>()
9
+
10
+ constructor(yamlObj: YAML.Document.Parsed) {
11
+ this.tree = getTree(yamlObj)
12
+ this.flatten = getFlattenNodes(this.tree)
13
+ }
14
+
15
+ getTree() {
16
+ return this.tree
17
+ }
18
+
19
+ getNode(path: string) {
20
+ return this.flatten[path]
21
+ }
22
+
23
+ getCustoms() {
24
+ return this.customs
25
+ }
26
+
27
+ setCustoms(yamlStr: string) {
28
+ this.customs.value = YAML.parseDocument(yamlStr)
29
+ }
30
+
31
+ setEnvs(envs: string[]) {
32
+ const envsObj: { [key: string]: string } = {}
33
+ envs.forEach((env) => {
34
+ const [key, value] = env.split('=')
35
+ envsObj[key] = value
36
+ })
37
+ this.envs.value = envsObj
38
+ }
39
+
40
+ getEnv(key: string) {
41
+ return this.envs.value?.[key] || null
42
+ }
43
+
44
+ getEnvByPath(path: string) {
45
+ // replace `.` to `_` and uppercase
46
+ // replace `ATK_TRUSTED_DOMAINS_0` to `ATK_TRUSTED_DOMAINS`
47
+ // replace `ATK_ADMIN_USERS_0_NAME` to `ATK_ADMIN_USERS`
48
+ return this.getEnv(
49
+ 'ATK_' +
50
+ path
51
+ .replace(/\./g, '_')
52
+ .toUpperCase()
53
+ .replace(/(_\d+?_\w+|_\d+)$/, ''),
54
+ )
55
+ }
56
+
57
+ getCustom(path: string) {
58
+ const env = this.getEnvByPath(path)
59
+ if (env) return env
60
+ return this.customs.value?.getIn(path.split('.')) as any
61
+ }
62
+
63
+ setCustom(path: string, value: any) {
64
+ const pathArr = path.split('.')
65
+
66
+ this.makeSureObject(pathArr)
67
+
68
+ this.customs.value?.setIn(pathArr, value)
69
+ }
70
+
71
+ // @see https://github.com/eemeli/yaml/issues/174#issuecomment-632281283
72
+ private makeSureObject(pathArr: string[]) {
73
+ for (let i = pathArr.length - 1; i >= 1; i--) {
74
+ const parentPath = pathArr.slice(0, -i)
75
+
76
+ const parentNode = this.customs.value?.getIn(parentPath)
77
+ if (!parentNode) {
78
+ this.customs.value?.setIn(parentPath, new YAML.YAMLMap())
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ // -------------------------------------------------------
85
+
86
+ export * from './settings-option'
87
+
88
+ // Singleton instance
89
+ let instance: Settings
90
+
91
+ export default {
92
+ init: (yamlObj: YAML.Document.Parsed) => (instance = new Settings(yamlObj)),
93
+ get: () => instance,
94
+ }
package/src/main.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
3
+ import Artalk from '@esershnr/artalk'
4
+ import { createRouter, createWebHashHistory } from 'vue-router'
5
+ import { routes } from 'vue-router/auto-routes'
6
+ import { setupI18n } from './i18n'
7
+ import '@esershnr/artalk/Artalk.css'
8
+ import './style.scss'
9
+ import App from './App.vue'
10
+ import { setArtalk } from './global'
11
+ import { setupArtalk, syncArtalkUser } from './artalk'
12
+ import './lib/promise-polyfill'
13
+
14
+ // I18n
15
+ // @see https://vue-i18n.intlify.dev
16
+ const { i18n, setLocale } = setupI18n()
17
+
18
+ // Router
19
+ // @see https://github.com/posva/unplugin-vue-router
20
+ const router = createRouter({
21
+ history: createWebHashHistory(),
22
+ routes,
23
+ })
24
+
25
+ // Pinia
26
+ // @see https://pinia.vuejs.org
27
+ const pinia = createPinia()
28
+
29
+ // Artalk
30
+ // @see https://artalk.js.org
31
+ const artalkLoader = () =>
32
+ new Promise<Artalk>((notifyArtalkLoaded) => {
33
+ let artalkLoaded = false
34
+ let artalk: Artalk | null = null
35
+
36
+ Artalk.use((ctx) => {
37
+ // When artalk is ready, notify the loader and load the locale
38
+ ctx.watchConf(['locale'], async (conf) => {
39
+ if (typeof conf.locale === 'string' && conf.locale !== 'auto') await setLocale(conf.locale) // update i18n locale
40
+
41
+ if (!artalkLoaded) {
42
+ artalkLoaded = true
43
+ notifyArtalkLoaded(artalk!)
44
+ }
45
+ })
46
+ })
47
+
48
+ artalk = setupArtalk()
49
+ })
50
+
51
+ // Mount Vue app
52
+ ;(async () => {
53
+ const artalk = await artalkLoader()
54
+ setArtalk(artalk)
55
+
56
+ const app = createApp(App)
57
+ app.use(i18n)
58
+ app.use(router)
59
+ app.use(pinia)
60
+
61
+ // user sync from artalk to sidebar
62
+ await syncArtalkUser(artalk.ctx, router)
63
+
64
+ app.mount('#app')
65
+ })()