Skip to content

Commit

Permalink
perf(ui/sidebar): optimize setup process and i18n lazy load (#962)
Browse files Browse the repository at this point in the history
  • Loading branch information
qwqcode authored Aug 29, 2024
1 parent 2efd342 commit 1b0eff9
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 400 deletions.
1 change: 1 addition & 0 deletions docs/docs/guide/frontend/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ locale: zh-CN
| -------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| 后端程序 | [/i18n/[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/i18n) | [zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/i18n/zh-CN.yml) |
| 前端界面 | [/ui/artalk/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/tree/master/ui/artalk/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk/src/i18n/zh-CN.ts) |
| 后台界面 | [/ui/artalk-sidebar/src/i18n/[LANG].ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk-sidebar/src/i18n) | [zh-CN.ts](https://github.com/ArtalkJS/Artalk/blob/master/ui/artalk-sidebar/src/i18n/zh-CN.ts) |
| 配置文件 | [/conf/artalk.example.[LANG].yml](https://github.com/ArtalkJS/Artalk/tree/master/conf) | [artalk.example.zh-CN.yml](https://github.com/ArtalkJS/Artalk/blob/master/conf/artalk.example.zh-CN.yml) |
| 说明文档 | [/docs/[LANG]/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs) | [/docs/\*\*/\*.md](https://github.com/ArtalkJS/Artalk/tree/master/docs) |

Expand Down
86 changes: 3 additions & 83 deletions ui/artalk-sidebar/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,87 +1,11 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import type Artalk from 'artalk'
import { useNavStore } from './stores/nav'
import { useUserStore } from './stores/user'
import { getArtalk, bootParams } from './global'
import { bootParams } from './global'
const nav = useNavStore()
const user = useUserStore()
const route = useRoute()
const router = useRouter()
const { scrollableArea } = storeToRefs(nav)
const artalkLoaded = ref(false)
onBeforeMount(() => {
const artalk = getArtalk()
if (!artalk) {
throw new Error('Artalk instance not initialized')
}
artalk.on('mounted', () => {
if (artalkLoaded.value) return
artalkLoaded.value = true
syncArtalk(artalk)
})
})
function syncArtalk(artalk: Artalk) {
// access from open sidebar or directly by url
if (bootParams.user?.email) {
// sync user from sidebar to artalk
artalk.ctx.get('user').update(bootParams.user)
} else {
// sync user from artalk to sidebar
try {
useUserStore().sync()
} catch {
nextTick(() => {
router.replace('/login')
})
return
}
}
// 验证登录身份有效性
const artalkUser = artalk.ctx.get('user')
const artalkUserData = artalkUser.getData()
const logout = () => {
user.logout()
nextTick(() => {
router.replace('/login')
})
}
// Remove login failed dialog if sidebar
artalk.ctx.getApiHandlers().remove('need_login')
artalk.ctx.getApiHandlers().add('need_login', async () => {
logout()
throw new Error('Need login')
})
// Check user status
artalk.ctx
.getApi()
.user.getUserStatus({
email: artalkUserData.email,
name: artalkUserData.name,
})
.then((res) => {
if (res.data.is_admin && !res.data.is_login) {
logout()
} else {
// 将全部通知标记为已读
artalk.ctx.getApi().notifies.markAllNotifyRead({
email: artalkUserData.email,
name: artalkUserData.name,
})
}
})
}
const darkMode = ref(bootParams.darkMode)
;(function initDarkModeWatchMedia() {
Expand All @@ -94,11 +18,7 @@ const darkMode = ref(bootParams.darkMode)
</script>

<template>
<div
v-if="artalkLoaded"
class="app-wrap artalk atk-sidebar"
:class="{ 'atk-dark-mode': darkMode }"
>
<div class="app-wrap artalk atk-sidebar" :class="{ 'atk-dark-mode': darkMode }">
<AppHeader />
<AppNavigation />

Expand Down Expand Up @@ -137,7 +57,7 @@ $sidebarWidth: 280px;
}
}
// 分页条占位
// The placeholder area for the pagination bar
:deep(.atk-pagination-wrap) {
z-index: 200;
position: fixed;
Expand Down
84 changes: 84 additions & 0 deletions ui/artalk-sidebar/src/artalk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Artalk from 'artalk'
import { Router } from 'vue-router'
import { bootParams } from './global'
import { useUserStore } from './stores/user'

export function setupArtalk() {
// Create virtual element for Artalk
const artalkEl = document.createElement('div')
artalkEl.style.display = 'none'
document.body.append(artalkEl)

// Init Artalk
return Artalk.init({
el: artalkEl,
server: '../',
pageKey: bootParams.pageKey,
site: bootParams.site,
darkMode: bootParams.darkMode,
useBackendConf: true,
pvAdd: false,
remoteConfModifier: (conf) => {
conf.noComment = `<div class="atk-sidebar-no-content"></div>`
conf.flatMode = true
conf.pagination = {
pageSize: 20,
readMore: false,
autoLoad: false,
}
conf.listUnreadHighlight = true
},
})
}

export async function syncArtalkUser(artalk: Artalk, router: Router) {
const user = useUserStore()
const logout = () => {
user.logout()
nextTick(() => {
router.replace('/login')
})
}

// Access from open sidebar or directly by url
if (bootParams.user?.email) {
// sync user from sidebar to artalk
artalk.ctx.get('user').update(bootParams.user)
} else {
// Sync user from artalk to sidebar
try {
user.sync()
} catch {
logout()
return
}
}

// Async check user status (no await)
checkUser(artalk, logout)
}

function checkUser(artalk: Artalk, logout: () => void) {
// Get user info from artalk
const { name, email } = artalk.ctx.get('user').getData()

// Remove login failed dialog if sidebar
artalk.ctx.getApiHandlers().remove('need_login')
artalk.ctx.getApiHandlers().add('need_login', async () => {
logout()
throw new Error('Need login')
})

// Check user status
artalk.ctx
.getApi()
.user.getUserStatus({ email, name })
.then((res) => {
if (res.data.is_admin && !res.data.is_login) {
logout()
} else {
// Mark all notifications as read
artalk.ctx.getApi().notifies.markAllNotifyRead({ email, name })
}
})
}
32 changes: 5 additions & 27 deletions ui/artalk-sidebar/src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export function getArtalk() {
return artalk
}

// 启动参数
/**
* Boot params from URL search params
*
* TODO: Refactor to a singleton store
*/
export const bootParams = getBootParams()

function getBootParams() {
Expand Down Expand Up @@ -43,32 +47,6 @@ function getBootParams() {
}
}

export function initArtalk() {
const artalkEl = document.createElement('div')
artalkEl.style.display = 'none'
document.body.append(artalkEl)

return Artalk.init({
el: artalkEl,
server: '../',
pageKey: bootParams.pageKey,
site: bootParams.site,
darkMode: bootParams.darkMode,
useBackendConf: true,
pvAdd: false,
remoteConfModifier: (conf) => {
conf.noComment = `<div class="atk-sidebar-no-content"></div>` // TODO i18n t('noComment')
conf.flatMode = true
conf.pagination = {
pageSize: 20,
readMore: false,
autoLoad: false,
}
conf.listUnreadHighlight = true
},
})
}

export function isOpenFromSidebar() {
return !!bootParams.user?.email
}
99 changes: 99 additions & 0 deletions ui/artalk-sidebar/src/i18n-en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
export const en = {
ctrlCenter: 'Admin',
msgCenter: 'Messages',
noContent: 'No Content',
searchHint: 'Search by keywords...',
allSites: 'All Sites',
siteManage: 'Site Management',
comment: 'Comment',
page: 'Page',
user: 'User',
site: 'Site',
transfer: 'Transfer',
settings: 'Settings',
all: 'All',
pending: 'Pending',
personal: 'Personal',
mentions: 'Mentions',
mine: 'Mine',
admin: 'Admin',
create: 'Create',
import: 'Import',
export: 'Export',
settingSaved: 'Setting saved',
settingSaveFailed: 'Setting save failed',
settingNotice: 'Note: Some config options may require a manual reboot to take effect.',
apply: 'Apply',
updateComplete: 'Update complete',
updateReady: 'Ready to update...',
opFailed: 'Operation failed',
updateTitle: 'Fetch Title',
uploading: 'Uploading',
cancel: 'Cancel',
back: 'Back',
cacheClear: 'Cache Clear',
cacheWarm: 'Cache Warm',
editTitle: 'Edit Title',
switchKey: 'Switch Key',
commentAllowAll: 'Anyone Comment',
commentOnlyAdmin: 'Admin Comment Only',
config: 'Config',
envVarControlHint: 'Referenced by the environment variable {key}',
userAdminHint: 'Admin user',
userInConfHint: 'This user is defined in config file',
edit: 'Edit',
delete: 'Delete',
siteCount: 'Total {count} Sites',
createSite: 'Create Site',
siteName: 'Site Name',
siteUrls: 'Site URLs',
multiSepHint: 'multiple separated by commas',
add: 'Add',
rename: 'Rename',
inputHint: 'Input text...',
userCreate: 'Create User',
userEdit: 'Edit User',
userInConfCannotEditHint:
'Cannot edit user in config file, please modify the config file manually',
userDeleteConfirm:
'This operation will delete all comments of user: "{name}" email: "{email}", including the reply comments under his comments. Continue?',
userDeleteManuallyHint:
'User has been deleted from the database, please manually edit the config file and delete the user',
pageDeleteConfirm:
'This operation will delete the page: "{title}" and all data under it. Continue?',
siteDeleteConfirm:
'This operation will delete the site: "{name}" and all data under it. Continue?',
siteNameInputHint: 'Please enter the site name',
comments: 'Comments',
last: 'Last',
show: 'Show',
username: 'Username',
email: 'Email',
link: 'Link',
badgeText: 'Badge Text',
badgeColor: 'Badge Color',
role: 'Role',
normal: 'Normal',
password: 'Password',
passwordEmptyHint: 'leave blank not change your password',
emailNotify: 'Email notification',
enabled: 'Enabled',
disabled: 'Disabled',
save: 'Save',
dataFile: 'Data File',
artransfer: 'Artransfer',
targetSiteName: 'Target Site Name',
targetSiteURL: 'Target Site URL',
payload: 'Payload',
optional: 'Optional',
uploadReadyToImport: 'File uploaded and is ready for import',
artransferToolHint: 'Use the {link} to convert data to Artrans format.',
moreDetails: 'More details',
loginFailure: 'Login failure',
login: 'Login',
logout: 'Logout',
logoutConfirm: 'Are you sure you want to log out?',
loginSelectHint: 'Please select the account you wish to log into:',
}

export default en
37 changes: 37 additions & 0 deletions ui/artalk-sidebar/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createI18n, type I18n, type Locale } from 'vue-i18n'
import { en } from './i18n-en'

export type MessageSchema = typeof en

export function setupI18n() {
const i18n = createI18n({
legacy: false, // use i18n in Composition API
locale: 'en',
fallbackLocale: 'en',
messages: { en } as any,
})

const setLocale = async (value: string) => {
await loadLocaleMessages(i18n, value)
i18n.global.locale.value = value
}

return { i18n, setLocale }
}

export async function loadLocaleMessages(i18n: I18n, locale: Locale) {
if (i18n.global.availableLocales.includes(locale)) return

// Load locale messages with dynamic import
// @see https://vitejs.dev/guide/features#dynamic-import
const messages = await import(`./i18n/${locale}.ts`)
.then((r: any) => r.default || r)
.catch(() => {
console.error(`Failed to load locale messages for "${locale}"`)
return
})

// Set locale and locale message
i18n.global.setLocaleMessage(locale, messages)
return nextTick()
}
Loading

0 comments on commit 1b0eff9

Please sign in to comment.