Skip to content

Commit

Permalink
refactor(editor): refactor editor plugin manager (#609)
Browse files Browse the repository at this point in the history
* refactor(editor): refactor editor plugin manager

* update

* modify editorTravelBack to editorResetUI
  • Loading branch information
qwqcode committed Oct 15, 2023
1 parent 8867eb2 commit 8d11a23
Show file tree
Hide file tree
Showing 33 changed files with 1,006 additions and 938 deletions.
2 changes: 2 additions & 0 deletions ui/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ module.exports = {
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-use-before-define': 0,
'@typescript-eslint/naming-convention': 0,
'@typescript-eslint/no-useless-constructor': 0,
'@typescript-eslint/no-unused-expressions': 0, // for `func && func()` expressions
},
settings: {
'import/resolver': {
Expand Down
2 changes: 2 additions & 0 deletions ui/packages/artalk/src/artalk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export default class Artalk {
// 初始化 Context
this.ctx = new ConcreteContext(this.conf, this.$root)

this.ctx.getApi()

// 内建服务初始化
Object.entries(Services).forEach(([name, initService]) => {
if (Artalk.DisabledComponents.includes(name)) return
Expand Down
8 changes: 2 additions & 6 deletions ui/packages/artalk/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,8 @@ class Context implements ContextApi {
this.editor.showNotify(msg, type)
}

public editorTravel($el: HTMLElement): void {
this.editor.travel($el)
}

public editorTravelBack(): void {
this.editor.travelBack()
public editorResetUI(): void {
this.editor.resetUI()
}

/* 侧边栏 */
Expand Down
8 changes: 8 additions & 0 deletions ui/packages/artalk/src/editor/core/_sample-plug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Editor from '../editor'
import EditorPlug from '../editor-plug'

export default class SamplePlug extends EditorPlug {
constructor(editor: Editor) {
super(editor)
}
}
31 changes: 31 additions & 0 deletions ui/packages/artalk/src/editor/core/closable-plug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import User from '@/lib/user'
import * as Utils from '@/lib/utils'
import Editor from '../editor'
import EditorPlug from '../editor-plug'

export default class ClosablePlug extends EditorPlug {
constructor(editor: Editor) {
super(editor)
}

close() {
if (!this.editor.getUI().$textareaWrap.querySelector('.atk-comment-closed'))
this.editor.getUI().$textareaWrap.prepend(Utils.createElement(`<div class="atk-comment-closed">${this.editor.$t('onlyAdminCanReply')}</div>`))

if (!User.data.isAdmin) {
this.editor.getUI().$textarea.style.display = 'none'
this.editor.getPlugs()?.closePlugPanel()
this.editor.getUI().$bottom.style.display = 'none'
} else {
// 管理员一直打开评论
this.editor.getUI().$textarea.style.display = ''
this.editor.getUI().$bottom.style.display = ''
}
}

open() {
this.editor.getUI().$textareaWrap.querySelector('.atk-comment-closed')?.remove()
this.editor.getUI().$textarea.style.display = ''
this.editor.getUI().$bottom.style.display = ''
}
}
120 changes: 120 additions & 0 deletions ui/packages/artalk/src/editor/core/edit-plug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { CommentData } from '~/types/artalk-data'
import * as Utils from '../../lib/utils'
import Editor from '../editor'
import User from '../../lib/user'
import EditorPlug from '../editor-plug'
import SubmitPlug from './submit-plug'
import MoverPlug from './mover-plug'

export default class EditPlug extends EditorPlug {
private comment?: CommentData

getComment() {
return this.comment
}

getIsEditMode() {
return !!this.comment
}

constructor(editor: Editor) {
super(editor)

this.kit.useMounted(() => {
const submitPlug = this.editor.getPlugs()?.get(SubmitPlug)
if (!submitPlug) throw Error("SubmitPlug not initialized")

submitPlug.registerCustom({
activeCond: () => !!this.comment, // active this custom submit when edit mode
req: async () => {
const saveData = {
content: this.editor.getContentFinal(),
nick: this.editor.getUI().$nick.value,
email: this.editor.getUI().$email.value,
link: this.editor.getUI().$link.value,
}
const nComment = await this.editor.ctx.getApi().comment.commentEdit({
...this.comment, ...saveData
})
return nComment
},
post: (nComment: CommentData) => {
this.editor.ctx.updateComment(nComment)
}
})
})
}

edit(comment: CommentData, $comment: HTMLElement) {
this.cancelEdit()
this.editor.cancelReply()

const ui = this.editor.getUI()
if (!ui.$editCancelBtn) {
const $btn = Utils.createElement(
`<div class="atk-send-reply">` +
`${this.editor.$t('editCancel')} ` +
`<span class="atk-cancel">×</span>` +
`</div>`
)
$btn.onclick = () => {
this.cancelEdit()
}
ui.$textareaWrap.append($btn)
ui.$editCancelBtn = $btn
}
this.comment = comment

ui.$header.style.display = 'none' // TODO support modify header information

this.editor.getPlugs()?.get(MoverPlug)?.move($comment)

ui.$nick.value = comment.nick || ''
ui.$email.value = comment.email || ''
ui.$link.value = comment.link || ''

this.editor.setContent(comment.content)
ui.$textarea.focus()

this.updateSubmitBtnText(this.editor.$t('save'))
}

cancelEdit() {
if (!this.comment) return

const ui = this.editor.getUI()

if (ui.$editCancelBtn) {
ui.$editCancelBtn.remove()
ui.$editCancelBtn = undefined
}

this.comment = undefined
this.editor.getPlugs()?.get(MoverPlug)?.back()

const { nick, email, link } = User.data
ui.$nick.value = nick
ui.$email.value = email
ui.$link.value = link

this.editor.setContent('')
this.restoreSubmitBtnText()

ui.$header.style.display = '' // TODO support modify header information
}

// -------------------------------------------------------------------
// Submit Btn Text Modifier
// -------------------------------------------------------------------

private originalSubmitBtnText = 'Send'

private updateSubmitBtnText(text: string) {
this.originalSubmitBtnText = this.editor.getUI().$submitBtn.innerText
this.editor.getUI().$submitBtn.innerText = text
}

private restoreSubmitBtnText() {
this.editor.getUI().$submitBtn.innerText = this.originalSubmitBtnText
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import Editor from '../editor'
import User from '../../lib/user'
import EditorPlug from './editor-plug'
import EditorPlug from '../editor-plug'

export default class HeaderInputPlug extends EditorPlug {
public static Name = 'headerInput'

public constructor(editor: Editor) {
constructor(editor: Editor) {
super(editor)

this.registerHeaderInputEvt((key, $input) => {
this.kit.useHeaderInput((key, $input) => {
if (key === 'nick' || key === 'email') {
this.fetchUserInfo()
}
})

// Link URL 自动补全协议
this.editor.getUI().$link.addEventListener('change', () => {
const link = this.editor.getUI().$link.value.trim()
if (!!link && !/^(http|https):\/\//.test(link)) {
this.editor.getUI().$link.value = `https://${link}`
User.update({ link: this.editor.getUI().$link.value })
}
const onLinkInputChange = () => this.onLinkInputChange()

this.kit.useMounted(() => {
this.editor.getUI().$link.addEventListener('change', onLinkInputChange)
})
this.kit.useUnmounted(() => {
this.editor.getUI().$link.addEventListener('change', onLinkInputChange)
})
}

queryUserInfo = {
private queryUserInfo = {
timeout: <number|null>null,
abortFunc: <(() => void)|null>null
}

/** 远程获取用户数据 */
fetchUserInfo() {
private fetchUserInfo() {
User.logout()

// 获取用户信息
Expand All @@ -40,7 +38,7 @@ export default class HeaderInputPlug extends EditorPlug {
this.queryUserInfo.timeout = window.setTimeout(() => {
this.queryUserInfo.timeout = null // 清理

const {req, abort} = this.ctx.getApi().user.userGet(
const {req, abort} = this.editor.ctx.getApi().user.userGet(
User.data.nick, User.data.email
)
this.queryUserInfo.abortFunc = abort
Expand All @@ -50,11 +48,14 @@ export default class HeaderInputPlug extends EditorPlug {
}

// 未读消息更新
this.ctx.updateNotifies(data.unread)
this.editor.ctx.updateNotifies(data.unread)

// 若用户为管理员,执行登陆操作
if (User.checkHasBasicUserInfo() && !data.is_login && data.user?.is_admin) {
this.showLoginDialog()
// 显示登录窗口
this.editor.ctx.checkAdmin({
onSuccess: () => {}
})
}

// 自动填入 link
Expand All @@ -70,10 +71,12 @@ export default class HeaderInputPlug extends EditorPlug {
}, 400) // 延迟执行,减少请求次数
}

showLoginDialog() {
this.ctx.checkAdmin({
onSuccess: () => {
}
})
private onLinkInputChange() {
// Link URL 自动补全协议
const link = this.editor.getUI().$link.value.trim()
if (!!link && !/^(http|https):\/\//.test(link)) {
this.editor.getUI().$link.value = `https://${link}`
User.update({ link: this.editor.getUI().$link.value })
}
}
}
46 changes: 46 additions & 0 deletions ui/packages/artalk/src/editor/core/header-plug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import User from '@/lib/user'
import Editor from '../editor'
import EditorPlug from '../editor-plug'
import EditPlug from './edit-plug'

export default class HeaderPlug extends EditorPlug {
private get $inputs() {
return this.editor.getHeaderInputEls()
}

constructor(editor: Editor) {
super(editor)

const inputEventFns: {[name: string]: () => void} = {}

// the input event
const onInput = ($input: HTMLInputElement, key: string) => () => {
if (editor.getPlugs()?.get(EditPlug)?.getIsEditMode()) return // 评论编辑模式,不修改个人信息

User.update({ [key]: $input.value.trim() })

// trigger header input event
editor.getPlugs()?.triggerHeaderInputEvt(key, $input)
}

this.kit.useMounted(() => {
// set placeholder and sync header input value
Object.entries(this.$inputs).forEach(([key, $input]) => {
$input.placeholder = `${editor.$t(key as any)}`
$input.value = User.data[key] || ''
})

// bind the event
Object.entries(this.$inputs).forEach(([key, $input]) => {
$input.addEventListener('input', inputEventFns[key] = onInput($input, key))
})
})

this.kit.useUnmounted(() => {
// unmount the event
Object.entries(this.$inputs).forEach(([key, $input]) => {
$input.removeEventListener('input', inputEventFns[key])
})
})
}
}
31 changes: 31 additions & 0 deletions ui/packages/artalk/src/editor/core/local-storage-plug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Editor from '../editor'
import EditorPlug from '../editor-plug'

const LocalStorageKey = 'ArtalkContent'

export default class LocalStoragePlug extends EditorPlug {
constructor(editor: Editor) {
super(editor)

this.kit.useMounted(() => {
// load editor content from localStorage when init
const localContent = window.localStorage.getItem(LocalStorageKey) || ''
if (localContent.trim() !== '') {
editor.showNotify(editor.$t('restoredMsg'), 'i')
editor.setContent(localContent)
}
})

this.kit.useUnmounted(() => {
})

this.kit.useContentUpdated(() => {
this.save()
})
}

// Save editor content to localStorage
public save() {
window.localStorage.setItem(LocalStorageKey, this.editor.getContentRaw().trim())
}
}

0 comments on commit 8d11a23

Please sign in to comment.