refactor: 移除 bus 事件总线,使用 hook 代替

This commit is contained in:
purocean
2021-11-10 09:45:28 +08:00
parent 5a6278be51
commit bfd6eb8526
36 changed files with 198 additions and 262 deletions
+5 -6
View File
@@ -7,7 +7,7 @@
<script lang="ts">
import { defineComponent, nextTick, onBeforeMount, onMounted, ref, toRefs, watch } from 'vue'
import { useStore } from 'vuex'
import { useBus } from '@fe/core/bus'
import { registerHook, removeHook } from '@fe/core/hook'
import { isEncrypted, saveDoc, toUri } from '@fe/services/document'
import { whenEditorReady } from '@fe/services/editor'
import type { Doc } from '@fe/types'
@@ -18,7 +18,6 @@ export default defineComponent({
components: { MonacoEditor },
setup () {
let editorIsReady = false
const bus = useBus()
const store = useStore()
let timer: number | null = null
@@ -96,14 +95,14 @@ export default defineComponent({
watch(currentContent, restartTimer)
onMounted(() => {
bus.on('global.resize', resize)
bus.on('editor.change', setCurrentValue as any)
registerHook('GLOBAL_RESIZE', resize)
registerHook('EDITOR_CHANGE', setCurrentValue)
restartTimer()
})
onBeforeMount(() => {
bus.off('global.resize', resize)
bus.off('editor.change', setCurrentValue as any)
removeHook('GLOBAL_RESIZE', resize)
removeHook('EDITOR_CHANGE', setCurrentValue)
})
whenEditorReady().then(({ editor, monaco }) => {
+1 -1
View File
@@ -154,7 +154,7 @@ export default defineComponent({
return
}
triggerHook('ON_DOC_BEFORE_EXPORT')
triggerHook('DOC_BEFORE_EXPORT')
if (convert.toType === 'pdf') {
exportPdf(fileName.value)
+17 -10
View File
@@ -7,7 +7,7 @@ import { useStore } from 'vuex'
import { computed, defineComponent, onBeforeMount, onBeforeUnmount, ref, toRefs, watch } from 'vue'
import { Alt, Ctrl } from '@fe/core/command'
import type { Components, Doc } from '@fe/types'
import { useBus } from '@fe/core/bus'
import { registerHook, removeHook } from '@fe/core/hook'
import { registerAction, removeAction } from '@fe/core/action'
import { ensureCurrentFileSaved, isEncrypted, isSubOrSameFile, switchDoc, toUri } from '@fe/services/document'
import type { AppState } from '@fe/support/store'
@@ -20,7 +20,6 @@ export default defineComponent({
components: { Tabs },
setup () {
const store = useStore()
const bus = useBus()
const { currentFile, tabs } = toRefs<AppState>(store.state)
const { isSaved } = toRefs(store.getters)
@@ -103,11 +102,19 @@ export default defineComponent({
}
}
function handleDocCreated ({ doc }: { doc: Doc | null }) {
switchFile(doc)
}
function handleDocDeleted ({ doc }: { doc: Doc | null }) {
removeFile(doc)
}
onBeforeMount(() => {
bus.on('doc.created', switchFile)
bus.on('doc.deleted', removeFile)
bus.on('doc.switch-failed', handleSwitchFailed)
bus.on('doc.moved', handleMoved)
registerHook('DOC_MOVED', handleMoved)
registerHook('DOC_CREATED', handleDocCreated)
registerHook('DOC_DELETED', handleDocDeleted)
registerHook('DOC_SWITCH_FAILED', handleSwitchFailed)
registerAction({
name: 'file-tabs.switch-left',
@@ -129,10 +136,10 @@ export default defineComponent({
})
onBeforeUnmount(() => {
bus.off('doc.created', switchFile)
bus.off('doc.deleted', removeFile)
bus.off('doc.switch-failed', handleSwitchFailed)
bus.off('doc.moved', handleMoved)
removeHook('DOC_MOVED', handleMoved)
removeHook('DOC_CREATED', handleDocCreated)
removeHook('DOC_DELETED', handleDocDeleted)
removeHook('DOC_SWITCH_FAILED', handleSwitchFailed)
removeAction('file-tabs.switch-left')
removeAction('file-tabs.switch-next')
})
+2 -3
View File
@@ -33,7 +33,7 @@
<script lang="ts">
import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs } from 'vue'
import { useStore } from 'vuex'
import { useBus } from '@fe/core/bus'
import { triggerHook } from '@fe/core/hook'
import { $args, FLAG_DISABLE_XTERM } from '@fe/support/args'
import { emitResize } from '@fe/services/layout'
import { isElectron } from '@fe/support/env'
@@ -43,7 +43,6 @@ let resizeOrigin: any = null
export default defineComponent({
name: 'layout',
setup () {
const bus = useBus()
const store = useStore()
const { showView, showXterm, showSide, showEditor, presentation } = toRefs(store.state)
@@ -74,7 +73,7 @@ export default defineComponent({
const height = (resizeOrigin.targetHeight + offsetY)
ref.style.height = Math.min(resizeOrigin.max, Math.max(resizeOrigin.min, height)) + 'px'
}
bus.emit('global.resize')
triggerHook('GLOBAL_RESIZE')
}
function initResize (type: any, ref: any, min: any, max: any, e: any) {
+4 -5
View File
@@ -7,7 +7,7 @@ import type * as monaco from 'monaco-editor'
import { defineComponent, onMounted, ref } from 'vue'
import { defaultOptions } from '@fe/services/editor'
import { toUri } from '@fe/services/document'
import { useBus } from '@fe/core/bus'
import { triggerHook } from '@fe/core/hook'
const models: {[key: string]: monaco.editor.ITextModel} = {}
@@ -19,7 +19,6 @@ export default defineComponent({
setup () {
let editor: monaco.editor.IStandaloneCodeEditor | null = null
const refEditor = ref<HTMLElement | null>(null)
const bus = useBus()
const getMonaco = () => window.monaco
const getEditor = () => editor!
@@ -32,7 +31,7 @@ export default defineComponent({
model = getMonaco().editor.createModel(value, undefined, getMonaco().Uri.parse(uri))
model.onDidChangeContent(() => {
const value = model.getValue()
bus.emit('monaco.change-value', { uri, value })
triggerHook('MONACO_CHANGE_VALUE', { uri, value })
})
}
@@ -51,13 +50,13 @@ export default defineComponent({
}
function initMonaco () {
bus.emit('monaco.before-init', { monaco: getMonaco() })
triggerHook('MONACO_BEFORE_INIT', { monaco: getMonaco() })
editor = getMonaco().editor.create(refEditor.value, defaultOptions)
setModel(toUri(null), '')
setTimeout(() => {
bus.emit('monaco.ready', { editor: getEditor(), monaco: getMonaco() })
triggerHook('MONACO_READY', { editor: getEditor(), monaco: getMonaco() })
}, 500)
}
+13 -15
View File
@@ -45,8 +45,7 @@ import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, ref, t
import { extname } from '@fe/utils/path'
import { isElectron } from '@fe/support/env'
import { markdown } from '@fe/services/markdown'
import { triggerHook } from '@fe/core/hook'
import { useBus } from '@fe/core/bus'
import { registerHook, removeHook, triggerHook } from '@fe/core/hook'
import { registerAction, removeAction } from '@fe/core/action'
import { revealLineInCenter } from '@fe/services/editor'
import { showExport } from '@fe/services/document'
@@ -65,7 +64,6 @@ export default defineComponent({
name: 'xview',
components: { Render, SvgIcon },
setup () {
const bus = useBus()
const store = useStore()
const pinOutline = ref(false)
@@ -131,13 +129,13 @@ export default defineComponent({
}
function handleRender () {
triggerHook('ON_VIEW_RENDER', { getViewDom })
triggerHook('VIEW_RENDER', { getViewDom })
}
function handleRendered () {
updateOutline()
updateTodoCount()
triggerHook('ON_VIEW_RENDERED', { getViewDom })
triggerHook('VIEW_RENDERED', { getViewDom })
}
function render () {
@@ -153,12 +151,12 @@ export default defineComponent({
const renderDebonce = debounce(render, 100, { leading: true })
async function keydownHandler (e: KeyboardEvent) {
triggerHook('ON_VIEW_KEY_DOWN', e, getViewDom()!)
triggerHook('VIEW_KEY_DOWN', { e, view: getViewDom()! }, { breakable: true })
}
function handleScroll (e: any) {
scrollTop.value = e.target.scrollTop
triggerHook('ON_VIEW_SCROLL', e)
scrollTop.value = e.target.scrollTop || 0
triggerHook('VIEW_SCROLL', { e })
}
function syncScroll (line: number) {
@@ -175,11 +173,11 @@ export default defineComponent({
}
function handleDbClick (e: MouseEvent) {
triggerHook('ON_VIEW_ELEMENT_DBCLICK', e, getViewDom()!)
triggerHook('VIEW_ELEMENT_DBCLICK', { e, view: getViewDom()! }, { breakable: true })
}
async function handleClick (e: MouseEvent) {
triggerHook('ON_VIEW_ELEMENT_CLICK', e, getViewDom()!)
triggerHook('VIEW_ELEMENT_CLICK', { e, view: getViewDom()! }, { breakable: true })
}
function revealLine (line: number) {
@@ -209,18 +207,18 @@ export default defineComponent({
function refresh () {
logger.debug('refresh')
renderDebonce()
triggerHook('ON_VIEW_REFRESH', { getViewDom })
triggerHook('VIEW_REFRESH', { getViewDom })
}
onMounted(() => {
nextTick(renderDebonce)
triggerHook('ON_VIEW_MOUNTED', { getViewDom })
triggerHook('VIEW_MOUNTED', { getViewDom })
registerAction({ name: 'view.refresh', handler: refresh })
registerAction({ name: 'view.reveal-line', handler: revealLine })
registerAction({ name: 'view.scroll-top-to', handler: scrollTopTo })
registerAction({ name: 'view.get-content-html', handler: getContentHtml })
registerHook('GLOBAL_RESIZE', resizeHandler)
window.addEventListener('keydown', keydownHandler, true)
bus.on('global.resize', resizeHandler)
resizeHandler()
})
@@ -229,15 +227,15 @@ export default defineComponent({
removeAction('view.reveal-line')
removeAction('view.scroll-top-to')
removeAction('view.get-content-html')
removeHook('GLOBAL_RESIZE', resizeHandler)
window.removeEventListener('keydown', keydownHandler)
bus.off('global.resize', resizeHandler)
})
watch(() => currentContent.value + filePath.value, () => autoPreview.value && renderDebonce())
watch(filePath, () => {
// 切换文件后,开启自动预览
toggleAutoPreview(true)
triggerHook('ON_VIEW_FILE_CHANGE', { getViewDom })
triggerHook('VIEW_FILE_CHANGE', { getViewDom })
})
return {
+9 -5
View File
@@ -26,8 +26,9 @@
<script lang="ts">
import { defineComponent, onBeforeUnmount, ref } from 'vue'
import { hookAction } from '@fe/core/action'
import { getMenus, MenuItem } from '@fe/services/status-bar'
import { registerHook, removeHook } from '@fe/core/hook'
import type { BuildInActionName } from '@fe/types'
import SvgIcon from './SvgIcon.vue'
export default defineComponent({
@@ -55,13 +56,16 @@ export default defineComponent({
}, 0)
}
const updateMenu = () => {
list.value = getMenus(props.position)
const updateMenu = ({ name }: { name: BuildInActionName }) => {
if (name === 'status-bar.refresh-menu') {
list.value = getMenus(props.position)
}
return false
}
const cleanup = hookAction('before-run', 'status-bar.refresh-menu', updateMenu)
registerHook('ACTION_BEFORE_RUN', updateMenu as any)
onBeforeUnmount(() => {
cleanup()
removeHook('ACTION_BEFORE_RUN', updateMenu as any)
})
return {
+6 -6
View File
@@ -67,13 +67,13 @@ export default defineComponent({
await createDoc({ repo: props.item.repo }, props.item)
}
async function select (item: Components.Tree.Node) {
if (item.type !== 'dir') {
if (extensions.supported(item.name)) {
switchDoc(item)
async function select (node: Components.Tree.Node) {
if (node.type !== 'dir') {
if (extensions.supported(node.name)) {
switchDoc(node)
} else {
if (!(await triggerHook('ON_TREE_NODE_SELECT', item))) {
openInOS(item)
if (!(await triggerHook('TREE_NODE_SELECT', { node }, { breakable: true }))) {
openInOS(node)
}
}
}
+7 -6
View File
@@ -13,11 +13,11 @@ import { defineComponent, nextTick, onBeforeMount, onBeforeUnmount, ref } from '
import { useStore } from 'vuex'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { useBus } from '@fe/core/bus'
import { getLogger } from '@fe/utils'
import { registerHook, removeHook } from '@fe/core/hook'
import { registerAction, removeAction } from '@fe/core/action'
import { $args, FLAG_DEMO, FLAG_DISABLE_XTERM } from '@fe/support/args'
import { toggleXterm } from '@fe/services/layout'
import { registerAction, removeAction } from '@fe/core/action'
import { getLogger } from '@fe/utils'
import { getColorScheme } from '@fe/services/theme'
import { OneHalfLight, OneHalfDark } from 'xterm-theme'
import SvgIcon from './SvgIcon.vue'
@@ -30,7 +30,6 @@ export default defineComponent({
name: 'xterm',
components: { SvgIcon },
setup () {
const bus = useBus()
const store = useStore()
const refXterm = ref<HTMLElement | null>(null)
@@ -84,8 +83,8 @@ export default defineComponent({
xterm.open(refXterm.value!)
fitAddon.fit()
bus.on('theme.change', changeTheme)
bus.on('global.resize', fitXterm)
registerHook('THEME_CHANGE', changeTheme)
registerHook('GLOBAL_RESIZE', fitXterm)
if (FLAG_DEMO) {
const message = 'DEMO 模式终端不可用'
@@ -182,6 +181,8 @@ export default defineComponent({
removeAction('xterm.run-code')
removeAction('xterm.run')
removeAction('xterm.init')
removeHook('THEME_CHANGE', changeTheme)
removeHook('GLOBAL_RESIZE', fitXterm)
})
return {
-2
View File
@@ -4,7 +4,6 @@ import * as plugin from '@fe/core/plugin'
import * as hook from '@fe/core/hook'
import * as action from '@fe/core/action'
import * as command from '@fe/core/command'
import { useBus } from '@fe/core/bus'
import { useToast } from '@fe/support/ui/toast'
import { useModal } from '@fe/support/ui/modal'
import * as env from '@fe/support/env'
@@ -45,7 +44,6 @@ const ctx = {
lib,
env,
utils,
bus: useBus(),
ui: { useToast, useModal },
registerHook: hook.registerHook,
removeHook: hook.removeHook,
+3 -15
View File
@@ -1,10 +1,9 @@
import { getLogger } from '@fe/utils'
import { useBus } from '@fe/core/bus'
import { BuildInActions, BuildInActionName } from '@fe/types'
import { registerCommand, removeCommand } from './command'
import { triggerHook } from './hook'
const logger = getLogger('action')
const bus = useBus()
export type ActionHandler<T extends string> = T extends BuildInActionName ? BuildInActions[T] : (...args: any[]) => any
export type HookType = 'before-run' | 'after-run'
@@ -33,17 +32,6 @@ export interface Action<T extends string> {
const actions: { [id: string]: Action<string> } = {}
/**
* Hook 一个 Action
* @param type 钩子类型
* @param name Action 名
* @param handler 处理方法
*/
export function hookAction (type: HookType, name: BuildInActionName, handler: (payload: any) => void) {
bus.on(`action.${type}.${name}` as any, handler)
return () => bus.off(`action.${type}.${name}` as any, handler)
}
/**
* 注册一个 Action
* @param action Action
@@ -72,7 +60,7 @@ export function getActionHandler <T extends string> (name: T): ActionHandler<T>
export function getActionHandler <T extends string> (name: T): ActionHandler<T> {
logger.debug('getActionHandler', name)
return ((...args: any[]) => {
bus.emit(`action.before-run.${name}` as any, args)
triggerHook('ACTION_BEFORE_RUN', { name }, { breakable: true })
let result: any
@@ -83,7 +71,7 @@ export function getActionHandler <T extends string> (name: T): ActionHandler<T>
}
}
bus.emit(`action.after-run.${name}` as any, result)
triggerHook('ACTION_AFTER_RUN', { name }, { breakable: true })
return result
}) as ActionHandler<T>
}
-47
View File
@@ -1,47 +0,0 @@
import type { BuildInEvents } from '@fe/types'
import mitt, { Emitter, WildcardHandler } from 'mitt'
const emitter = mitt()
// debug
// emitter.on('*', (type, payload) => {
// console.log('debug bus >', type, payload)
// })
export type BuildInEventType = keyof BuildInEvents
export type Handler<T = any> = (event: T) => void;
export type BuildInEventTypeWithoutPayload = { [K in keyof BuildInEvents]: BuildInEvents[K] extends never ? K : never }[keyof BuildInEvents]
export type BuildInEventTypeWithPayload = keyof Omit<BuildInEvents, BuildInEventTypeWithoutPayload>
export interface XEmitter extends Emitter {
once<T extends BuildInEventType>(type: T, handler: Handler<BuildInEvents[T]>): void;
once(type: '*', handler: WildcardHandler): void;
on<T extends BuildInEventType>(type: T, handler: Handler<BuildInEvents[T]>): void;
on(type: '*', handler: WildcardHandler): void;
off<T extends BuildInEventType>(type: T, handler: Handler<BuildInEvents[T]>): void;
off(type: '*', handler: WildcardHandler): void;
emit<T extends BuildInEventTypeWithPayload>(type: T, event: BuildInEvents[T]): void;
emit<T extends BuildInEventTypeWithoutPayload>(type: T): void;
emit(type: '*', event?: any): void;
}
(emitter as any).once = (type: any, handler: any) => {
const wrappedHandler = (evt: any) => {
handler(evt)
emitter.off(type, wrappedHandler)
}
emitter.on(type, wrappedHandler)
}
/**
* 获取一个事件总线
* @returns 事件总线
*/
export function useBus () {
return emitter as XEmitter
}
export default function install () {
window.globalBus = emitter
}
+19 -7
View File
@@ -2,7 +2,10 @@ import type { BuildInHookTypes } from '@fe/types'
import { getLogger } from '@fe/utils'
export type HookType = keyof BuildInHookTypes
export type HookFun<T extends any[]> = (...args: T) => boolean | void | Promise<boolean | void>
export type HookFun<T> = (arg: T) => boolean | void | Promise<boolean | void>
export type HookTypeWithoutPayload = { [K in keyof BuildInHookTypes]: BuildInHookTypes[K] extends never ? K : never }[keyof BuildInHookTypes]
export type HookTypeWithPayload = keyof Omit<BuildInHookTypes, HookTypeWithoutPayload>
const logger = getLogger('hook')
@@ -36,17 +39,26 @@ export function removeHook<T extends HookType> (type: T, fun: HookFun<BuildInHoo
/**
* 触发一个钩子
* @param type 钩子类型
* @param args 参数
* @param arg 参数
* @returns 所有钩子执行结果
*/
export async function triggerHook<T extends HookType> (type: T, ...args: BuildInHookTypes[T]) {
logger.debug('triggerHook', type, args)
export async function triggerHook<T extends HookTypeWithoutPayload> (type: T): Promise<void>
export async function triggerHook<T extends HookTypeWithPayload> (type: T, arg: BuildInHookTypes[T], options?: { breakable?: boolean }): Promise<boolean>
export async function triggerHook<T extends HookType> (type: T, arg?: BuildInHookTypes[T], options?: { breakable?: boolean }): Promise<boolean | void> {
logger.debug('triggerHook', type, arg)
for (const { fun, once } of (hooks[type] || ([] as any))) {
once && removeHook(type, fun)
if (await fun(...args)) {
return true
if (options?.breakable) {
if (await fun(arg)) {
logger.debug('triggerHook', 'break', fun)
return true
}
} else {
fun(arg)
}
}
return false
if (options?.breakable) {
return false
}
}
-2
View File
@@ -4,7 +4,6 @@ import { createApp } from 'vue'
import App from '@fe/App.vue'
import router from '@fe/router'
import bus from '@fe/core/bus'
import store from '@fe/support/store'
import toast from '@fe/support/ui/toast'
import modal from '@fe/support/ui/modal'
@@ -12,7 +11,6 @@ import contextmenu from '@fe/support/ui/context-menu'
const app = createApp(App)
app.use(bus)
app.use(store)
app.use(router)
app.use(toast)
+1 -1
View File
@@ -6,7 +6,7 @@ import store from '@fe/support/store'
export default {
name: 'copy-text',
register: (ctx) => {
ctx.registerHook('ON_VIEW_ELEMENT_CLICK', async (e: MouseEvent) => {
ctx.registerHook('VIEW_ELEMENT_CLICK', async ({ e }) => {
const target = e.target as HTMLElement
const preventEvent = () => {
@@ -57,19 +57,19 @@ export default {
updateMenu()
}
ctx.bus.on('doc.switched', (file?: Doc | null) => {
if (file) {
if (!ctx.doc.isSameFile(stack[idx], file)) {
ctx.registerHook('DOC_SWITCHED', ({ doc }) => {
if (doc) {
if (!ctx.doc.isSameFile(stack[idx], doc)) {
stack.splice(idx + 1, stack.length)
stack.push({ type: file.type, repo: file.repo, name: file.name, path: file.path })
stack.push({ type: doc.type, repo: doc.repo, name: doc.name, path: doc.path })
idx = stack.length - 1
}
}
updateMenu()
})
ctx.bus.on('doc.deleted', removeFromStack)
ctx.bus.on('doc.moved', ({ oldDoc }) => removeFromStack(oldDoc))
ctx.registerHook('DOC_DELETED', ({ doc }) => removeFromStack(doc))
ctx.registerHook('DOC_MOVED', ({ oldDoc }) => removeFromStack(oldDoc))
ctx.action.registerAction({
name: backId,
+1 -1
View File
@@ -34,7 +34,7 @@ async function pasteImage (file: File, asBase64: boolean) {
throw new Error('当前未打开文件')
}
if (!(await triggerHook('ON_EDITOR_PASTE_IMAGE', file))) {
if (!(await triggerHook('EDITOR_PASTE_IMAGE', { file }, { breakable: true }))) {
const assetPath = await upload(file, store.state.currentFile)
insert(`![图片](${encodeMarkdownLink(assetPath)})\n`)
}
+1 -1
View File
@@ -77,7 +77,7 @@ export default {
}
})
ctx.registerHook('ON_EDITOR_PASTE_IMAGE', async (file: File) => {
ctx.registerHook('EDITOR_PASTE_IMAGE', async ({ file }) => {
if (!ctx.setting.getSettings()[settingKeyPaste]) {
return false
}
+1 -1
View File
@@ -8,7 +8,7 @@ let viewer: any
export default {
name: 'image-viewer',
register: ctx => {
ctx.registerHook('ON_VIEW_RENDERED', debounce(({ getViewDom }) => {
ctx.registerHook('VIEW_RENDERED', debounce(({ getViewDom }) => {
if (!viewer) {
viewer = new Viewer(getViewDom(), {
zIndex: 299999,
+5 -6
View File
@@ -3,7 +3,7 @@ import { defineComponent, h, ref, watch } from 'vue'
import { Plugin } from '@fe/context'
import { buildSrc, IFrame } from '@fe/support/embed'
import * as api from '@fe/support/api'
import { useBus } from '@fe/core/bus'
import { triggerHook } from '@fe/core/hook'
import { openWindow } from '@fe/support/env'
const Drawio = defineComponent({
@@ -13,7 +13,6 @@ const Drawio = defineComponent({
content: String
},
setup (props) {
const bus = useBus()
const srcdoc = ref('')
const refIFrame = ref<any>()
@@ -29,7 +28,7 @@ const Drawio = defineComponent({
iframe.height = iframe.contentDocument.documentElement.offsetHeight + 'px'
iframe.contentDocument.body.style.height = iframe.contentDocument.body.clientHeight + 'px'
iframe.contentDocument.documentElement.style.height = '100%'
bus.emit('global.resize')
triggerHook('GLOBAL_RESIZE')
}
}
@@ -177,9 +176,9 @@ export default {
register: ctx => {
ctx.markdown.registerPlugin(MarkdownItPlugin)
ctx.registerHook('ON_TREE_NODE_SELECT', async (item) => {
if (item.path.toLowerCase().endsWith('.drawio')) {
const srcdoc = await buildSrcdoc({ content: '', ...item })
ctx.registerHook('TREE_NODE_SELECT', async ({ node }) => {
if (node.path.toLowerCase().endsWith('.drawio')) {
const srcdoc = await buildSrcdoc({ content: '', ...node })
openWindow(buildSrc(srcdoc, '查看图形'))
return true
+3 -3
View File
@@ -101,14 +101,14 @@ export default {
`)
ctx.markdown.registerPlugin(EChartsPlugin)
ctx.registerHook('ON_VIEW_RENDERED', () => {
ctx.registerHook('VIEW_RENDERED', () => {
update()
})
ctx.registerHook('ON_DOC_BEFORE_EXPORT', () => {
ctx.registerHook('DOC_BEFORE_EXPORT', () => {
preparePrint() // 转换成图片形式,设置主题
setTimeout(() => update(), 0) // 恢复
})
ctx.bus.on('theme.change', () => update())
ctx.registerHook('THEME_CHANGE', () => update())
}
} as Plugin
+1 -1
View File
@@ -153,7 +153,7 @@ function convertLink (state: StateCore) {
export default {
name: 'markdown-link',
register: (ctx) => {
ctx.registerHook('ON_VIEW_ELEMENT_CLICK', async (e, view) => {
ctx.registerHook('VIEW_ELEMENT_CLICK', async ({ e, view }) => {
const target = e.target as HTMLElement
const preventEvent = () => {
+3 -3
View File
@@ -428,9 +428,9 @@ export default {
name: 'markdown-luckysheet',
register: ctx => {
ctx.markdown.registerPlugin(MarkdownItPlugin)
ctx.registerHook('ON_TREE_NODE_SELECT', async (item: Doc) => {
if (item.path.toLowerCase().endsWith(fileExt)) {
const srcdoc = buildSrcdoc(item.repo, item.path, true)
ctx.registerHook('TREE_NODE_SELECT', async ({ node }) => {
if (node.path.toLowerCase().endsWith(fileExt)) {
const srcdoc = buildSrcdoc(node.repo, node.path, true)
openWindow(buildSrc(srcdoc, '编辑表格', false), '_blank', { alwaysOnTop: false })
return true
+1 -1
View File
@@ -38,6 +38,6 @@ export default {
}
`)
ctx.markdown.registerPlugin(MermaidPlugin)
ctx.registerHook('ON_VIEW_RENDERED', () => mermaid.init('.mermaid'))
ctx.registerHook('VIEW_RENDERED', () => mermaid.init('.mermaid'))
}
} as Plugin
+1 -1
View File
@@ -30,7 +30,7 @@ export default {
}
})
ctx.registerHook('ON_VIEW_ELEMENT_CLICK', async (e: MouseEvent) => {
ctx.registerHook('VIEW_ELEMENT_CLICK', async ({ e }) => {
const target = e.target as HTMLElement
const preventEvent = () => {
+4 -5
View File
@@ -1,6 +1,5 @@
import { debounce } from 'lodash-es'
import type { Plugin } from '@fe/context'
import type { Doc } from '@fe/types'
import { sleep } from '@fe/utils'
export default {
@@ -29,8 +28,8 @@ export default {
}
// 切换文件后恢复滚动位置
ctx.bus.on('doc.switched', async (file?: Doc | null) => {
if (file) {
ctx.registerHook('DOC_SWITCHED', async ({ doc }) => {
if (doc) {
await sleep(0)
const key = ctx.doc.toUri(ctx.store.state.currentFile)
@@ -62,14 +61,14 @@ export default {
})
const savePosition = debounce(saveScrollPosition, 500)
ctx.registerHook('ON_VIEW_SCROLL', (e: WheelEvent) => {
ctx.registerHook('VIEW_SCROLL', ({ e }) => {
if (ctx.store.state.currentFile?.status) {
const { scrollTop } = e.target as HTMLElement
savePosition({ view: scrollTop })
}
})
ctx.registerHook('ON_VIEW_ELEMENT_CLICK', async (e: MouseEvent) => {
ctx.registerHook('VIEW_ELEMENT_CLICK', async ({ e }) => {
const target = e.target as HTMLElement
if (
+3 -3
View File
@@ -184,9 +184,9 @@ export default {
return false
}
ctx.registerHook('ON_VIEW_ELEMENT_DBCLICK', e => handleClick(e, false))
ctx.registerHook('ON_VIEW_ELEMENT_CLICK', e => handleClick(e, true))
ctx.registerHook('ON_VIEW_RENDERED', ({ getViewDom }) => {
ctx.registerHook('VIEW_ELEMENT_DBCLICK', ({ e }) => handleClick(e, false))
ctx.registerHook('VIEW_ELEMENT_CLICK', ({ e }) => handleClick(e, true))
ctx.registerHook('VIEW_RENDERED', ({ getViewDom }) => {
const view: HTMLElement = getViewDom()!
view.querySelectorAll('.yank-table-cell').forEach(td => {
(td as HTMLElement).title = '双击编辑'
@@ -80,7 +80,7 @@ async function transformAll () {
refreshTree()
}
async function handleClick (e: MouseEvent) {
async function handleClick ({ e }: { e: MouseEvent }) {
const target = e.target as HTMLElement
if (target.tagName !== 'IMG') {
return false
@@ -118,8 +118,8 @@ export default {
handler: null
})
ctx.registerHook('ON_VIEW_ELEMENT_CLICK', handleClick)
ctx.registerHook('ON_VIEW_RENDERED', ({ getViewDom }) => {
ctx.registerHook('VIEW_ELEMENT_CLICK', handleClick)
ctx.registerHook('VIEW_RENDERED', ({ getViewDom }) => {
refView = getViewDom()!
})
+10 -11
View File
@@ -5,13 +5,12 @@ import { useToast } from '@fe/support/ui/toast'
import store from '@fe/support/store'
import type { Doc } from '@fe/types'
import { basename, dirname, isBelongTo, join } from '@fe/utils/path'
import { useBus } from '@fe/core/bus'
import { triggerHook } from '@fe/core/hook'
import * as api from '@fe/support/api'
import { getLogger } from '@fe/utils'
import { inputPassword } from './base'
const logger = getLogger('document')
const bus = useBus()
function decrypt (content: any, password: string) {
if (!password) {
@@ -139,7 +138,7 @@ export async function createDoc (doc: Optional<Pick<Doc, 'repo' | 'path' | 'cont
await api.writeFile(file, file.content)
bus.emit('doc.created', file)
triggerHook('DOC_CREATED', { doc: file })
} catch (error: any) {
useToast().show('warning', error.message)
console.error(error)
@@ -192,7 +191,7 @@ export async function deleteDoc (doc: Doc) {
if (confirm) {
await api.deleteFile(doc)
bus.emit('doc.deleted', doc)
triggerHook('DOC_DELETED', { doc })
}
}
@@ -244,7 +243,7 @@ export async function moveDoc (doc: Doc, newPath?: string) {
await api.moveFile(doc, newPath)
bus.emit('doc.moved', { oldDoc: doc, newDoc })
triggerHook('DOC_MOVED', { oldDoc: doc, newDoc })
}
/**
@@ -283,7 +282,7 @@ export async function saveDoc (doc: Doc, content: string) {
contentHash: hash,
status: 'saved'
})
bus.emit('doc.saved', store.state.currentFile!)
triggerHook('DOC_SAVED', { doc: store.state.currentFile! })
} catch (error: any) {
store.commit('setCurrentFile', { ...doc, status: 'save-failed' })
useToast().show('warning', error.message)
@@ -335,7 +334,7 @@ export async function switchDoc (doc: Doc | null) {
try {
if (!doc) {
store.commit('setCurrentFile', null)
bus.emit('doc.switched', null)
triggerHook('DOC_SWITCHED', { doc: null })
return
}
@@ -363,9 +362,9 @@ export async function switchDoc (doc: Doc | null) {
status: 'loaded'
})
bus.emit('doc.switched', store.state.currentFile)
triggerHook('DOC_SWITCHED', { doc: store.state.currentFile })
} catch (error: any) {
bus.emit('doc.switch-failed', { doc, message: error.message })
triggerHook('DOC_SWITCH_FAILED', { doc, message: error.message })
useToast().show('warning', error.message.includes('Malformed') ? '密码错误' : error.message)
throw error
}
@@ -377,7 +376,7 @@ export async function switchDoc (doc: Doc | null) {
*/
export async function markDoc (doc: Doc) {
await api.markFile(doc)
bus.emit('doc.changed', doc)
triggerHook('DOC_CHANGED', { doc })
}
/**
@@ -386,7 +385,7 @@ export async function markDoc (doc: Doc) {
*/
export async function unmarkDoc (doc: Doc) {
await api.unmarkFile(doc)
bus.emit('doc.changed', doc)
triggerHook('DOC_CHANGED', { doc })
}
/**
+10 -16
View File
@@ -1,12 +1,10 @@
import * as Monaco from 'monaco-editor'
import { FLAG_READONLY } from '@fe/support/args'
import { isElectron } from '@fe/support/env'
import { useBus } from '@fe/core/bus'
import { registerHook, triggerHook } from '@fe/core/hook'
import { registerAction } from '@fe/core/action'
import { Alt } from '@fe/core/command'
import { getColorScheme } from './theme'
import { registerAction } from '../core/action'
import { Alt } from '../core/command'
const bus = useBus()
let monaco: typeof Monaco
let editor: Monaco.editor.IStandaloneCodeEditor
@@ -61,9 +59,7 @@ export function whenEditorReady (): Promise<{ editor: typeof editor, monaco: typ
}
return new Promise(resolve => {
bus.once('editor.ready', payload => {
resolve(payload)
})
registerHook('EDITOR_READY', resolve)
})
}
@@ -171,9 +167,7 @@ export function toggleWrap () {
registerAction({ name: 'editor.toggle-wrap', handler: toggleWrap, keys: [Alt, 'w'] })
bus.on('monaco.before-init', (payload: any) => {
monaco = payload.monaco
registerHook('MONACO_BEFORE_INIT', ({ monaco }) => {
monaco.editor.defineTheme('vs', {
base: 'vs',
inherit: true,
@@ -195,17 +189,17 @@ bus.on('monaco.before-init', (payload: any) => {
})
})
bus.on('monaco.ready', (payload) => {
registerHook('MONACO_READY', (payload) => {
monaco = payload.monaco
editor = payload.editor
bus.emit('editor.ready', payload)
triggerHook('EDITOR_READY', payload)
})
bus.on('monaco.change-value', payload => {
bus.emit('editor.change', payload)
registerHook('MONACO_CHANGE_VALUE', payload => {
triggerHook('EDITOR_CHANGE', payload)
})
bus.on('theme.change', () => {
registerHook('THEME_CHANGE', () => {
monaco?.editor.setTheme(getColorScheme() === 'dark' ? 'vs-dark' : 'vs')
})
+4 -6
View File
@@ -1,17 +1,15 @@
import { nextTick } from 'vue'
import store from '@fe/support/store'
import { useBus } from '@fe/core/bus'
import { getActionHandler, registerAction } from '../core/action'
import { Alt } from '../core/command'
import { triggerHook } from '@fe/core/hook'
import { getActionHandler, registerAction } from '@fe/core/action'
import { Alt } from '@fe/core/command'
import * as view from './view'
const bus = useBus()
/**
* 下一个 tick 抛出 resize 事件
*/
export function emitResize () {
nextTick(() => bus.emit('global.resize'))
nextTick(() => triggerHook('GLOBAL_RESIZE'))
}
/**
+2 -4
View File
@@ -1,9 +1,7 @@
import { useBus } from '@fe/core/bus'
import { triggerHook } from '@fe/core/hook'
import type { ThemeName } from '@fe/types'
import * as storage from '@fe/utils/storage'
const bus = useBus()
/**
* 获取当前主题设置
* @returns 主题名
@@ -38,7 +36,7 @@ export function getColorScheme () {
*/
export function setTheme (name: ThemeName) {
document.documentElement.setAttribute('app-theme', name)
bus.emit('theme.change', name)
triggerHook('THEME_CHANGE', { name })
storage.set('app.theme', name)
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { useBus } from '@fe/core/bus'
import { Escape } from '@fe/core/command'
import { triggerHook } from '@fe/core/hook'
import { getActionHandler, registerAction } from '@fe/core/action'
import { useToast } from '@fe/support/ui/toast'
import store from '@fe/support/store'
@@ -10,7 +10,7 @@ function present (flag: boolean) {
}
store.commit('setPresentation', flag)
setTimeout(() => {
useBus().emit('global.resize')
triggerHook('GLOBAL_RESIZE')
}, 0)
}
+8 -12
View File
@@ -1,7 +1,6 @@
import { useBus } from '@fe/core/bus'
import { isElectron, nodeRequire } from '@fe/support/env'
import { init } from '@fe/core/plugin'
import { triggerHook } from '@fe/core/hook'
import { registerHook, triggerHook } from '@fe/core/hook'
import store from '@fe/support/store'
import * as storage from '@fe/utils/storage'
import { basename } from '@fe/utils/path'
@@ -16,8 +15,6 @@ import ctx from '@fe/context'
init(plugins, ctx)
const bus = useBus()
function getLastOpenFile (repoName?: string): Doc | null {
const currentFile = storage.get<Doc>('currentFile')
const recentOpenTime = storage.get('recentOpenTime', {}) as {[key: string]: number}
@@ -49,7 +46,7 @@ function getLastOpenFile (repoName?: string): Doc | null {
}
export default function startup () {
triggerHook('ON_STARTUP')
triggerHook('STARTUP')
// 在 Electron 环境中开启缩放页面功能
if (isElectron) {
@@ -89,13 +86,12 @@ export default function startup () {
const doc = getLastOpenFile()
switchDoc(doc)
bus.on('doc.created', refreshTree)
bus.on('doc.deleted', refreshTree)
bus.on('doc.moved', refreshTree)
bus.on('doc.changed', refreshTree)
bus.on('doc.switch-failed', refreshTree)
bus.on('doc.switch-failed', (payload?: { doc?: Doc | null, message: string }) => {
registerHook('DOC_CREATED', refreshTree)
registerHook('DOC_DELETED', refreshTree)
registerHook('DOC_MOVED', refreshTree)
registerHook('DOC_CHANGED', refreshTree)
registerHook('DOC_SWITCH_FAILED', refreshTree)
registerHook('DOC_SWITCH_FAILED', (payload?: { doc?: Doc | null, message: string }) => {
if (payload && payload.doc && payload?.message?.indexOf('NOENT')) {
unmarkDoc(payload.doc)
}
+10 -12
View File
@@ -1,8 +1,8 @@
import { debounce } from 'lodash-es'
import { defineComponent, h, IframeHTMLAttributes, nextTick, onBeforeMount, onBeforeUnmount, PropType, ref, watch } from 'vue'
import { useBus } from '@fe/core/bus'
import { md5 } from '@fe/utils'
import { registerHook, removeHook } from '@fe/core/hook'
import { registerHook, removeHook, triggerHook } from '@fe/core/hook'
import type { ThemeName } from '@fe/types'
/**
* 构建一个嵌入页面的 src 路径
@@ -46,7 +46,7 @@ export const IFrame = defineComponent({
onBeforeMount(update)
watch(props, debounce(update, props.debounce))
const changeTheme = (name?: string) => {
const changeTheme = ({ name }: { name: ThemeName }) => {
if (name) {
iframe.value?.contentDocument?.documentElement.setAttribute('app-theme', name)
}
@@ -57,23 +57,21 @@ export const IFrame = defineComponent({
nextTick(update)
}
const bus = useBus()
bus.on('theme.change', changeTheme)
registerHook('ON_VIEW_REFRESH', refresh)
registerHook('ON_VIEW_FILE_CHANGE', refresh)
registerHook('THEME_CHANGE', changeTheme)
registerHook('VIEW_REFRESH', refresh)
registerHook('VIEW_FILE_CHANGE', refresh)
onBeforeUnmount(() => {
bus.off('theme.change', changeTheme)
removeHook('ON_VIEW_REFRESH', refresh)
removeHook('ON_VIEW_FILE_CHANGE', refresh)
removeHook('THEME_CHANGE', changeTheme)
removeHook('VIEW_REFRESH', refresh)
removeHook('VIEW_FILE_CHANGE', refresh)
})
const onLoad = function () {
const frame = iframe.value!
const resize = () => {
frame.height = frame.contentDocument!.documentElement.scrollHeight + 'px'
bus.emit('global.resize')
triggerHook('GLOBAL_RESIZE')
}
const win = frame.contentWindow as any
+32 -33
View File
@@ -84,23 +84,6 @@ export namespace Components {
export type ThemeName = 'system' | 'dark' | 'light'
export type BuildInEvents = {
'global.resize': never,
'theme.change': ThemeName,
'monaco.change-value': { uri: string, value: string },
'monaco.before-init': { monaco: typeof Monaco },
'monaco.ready': { editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco },
'editor.ready': { editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco },
'editor.change': { uri: string, value: string },
'doc.created': Doc,
'doc.deleted': Doc,
'doc.moved': { oldDoc: Doc, newDoc: Doc },
'doc.saved': Doc,
'doc.switched': Doc | null,
'doc.switch-failed': { doc?: Doc | null, message: string },
'doc.changed': Doc,
}
export type BuildInSettings = {
'repos': { name: string, path: string }[],
'theme': ThemeName,
@@ -110,22 +93,6 @@ export type BuildInSettings = {
'plugin.image-hosting-picgo.enable-paste-image': boolean
}
export type BuildInHookTypes = {
ON_STARTUP: [],
ON_EDITOR_PASTE_IMAGE: [File],
ON_VIEW_ELEMENT_CLICK: [MouseEvent, HTMLElement],
ON_VIEW_ELEMENT_DBCLICK: [MouseEvent, HTMLElement],
ON_VIEW_KEY_DOWN: [KeyboardEvent, HTMLElement],
ON_VIEW_SCROLL: [WheelEvent],
ON_VIEW_RENDER: [{ getViewDom: ()=> HTMLElement | null }],
ON_VIEW_RENDERED: [{ getViewDom: ()=> HTMLElement | null }],
ON_VIEW_MOUNTED: [{ getViewDom: ()=> HTMLElement | null }],
ON_VIEW_FILE_CHANGE: [{ getViewDom: ()=> HTMLElement | null }],
ON_VIEW_REFRESH: [{ getViewDom: ()=> HTMLElement | null }],
ON_TREE_NODE_SELECT: [Components.Tree.Node],
ON_DOC_BEFORE_EXPORT: [],
}
export type BuildInActions = {
'view.refresh': () => void,
'view.reveal-line': (line: number) => void,
@@ -161,6 +128,38 @@ export type BuildInActions = {
export type BuildInActionName = keyof BuildInActions
export type BuildInHookTypes = {
STARTUP: never,
GLOBAL_RESIZE: never,
ACTION_BEFORE_RUN: { name: string },
ACTION_AFTER_RUN: { name: string }
THEME_CHANGE: { name: ThemeName },
EDITOR_PASTE_IMAGE: { file: File },
VIEW_ELEMENT_CLICK: { e: MouseEvent, view: HTMLElement },
VIEW_ELEMENT_DBCLICK: { e: MouseEvent, view: HTMLElement },
VIEW_KEY_DOWN: { e: KeyboardEvent, view: HTMLElement },
VIEW_SCROLL: { e: WheelEvent },
VIEW_RENDER: { getViewDom: ()=> HTMLElement | null },
VIEW_RENDERED: { getViewDom: ()=> HTMLElement | null },
VIEW_MOUNTED: { getViewDom: ()=> HTMLElement | null },
VIEW_FILE_CHANGE: { getViewDom: ()=> HTMLElement | null },
VIEW_REFRESH: { getViewDom: ()=> HTMLElement | null },
TREE_NODE_SELECT: { node: Components.Tree.Node },
MONACO_CHANGE_VALUE : { uri: string, value: string },
MONACO_BEFORE_INIT: { monaco: typeof Monaco },
MONACO_READY: { editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco },
EDITOR_READY: { editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco },
EDITOR_CHANGE: { uri: string, value: string },
DOC_CREATED: { doc: Doc },
DOC_DELETED: { doc: Doc },
DOC_MOVED: { oldDoc: Doc, newDoc: Doc },
DOC_SAVED: { doc: Doc },
DOC_SWITCHED: { doc: Doc | null },
DOC_SWITCH_FAILED: { doc?: Doc | null, message: string },
DOC_CHANGED: { doc: Doc },
DOC_BEFORE_EXPORT: never,
}
export type FrontMatterAttrs = {
headingNumber?: boolean,
enableMacro?: boolean,