Merge branch 'release/3.67.0'

This commit is contained in:
purocean
2024-01-30 15:33:07 +08:00
29 changed files with 252 additions and 128 deletions
+15 -8
View File
@@ -76,16 +76,23 @@ For more information on how to use the following functions, please see [characte
## Changelogs
### [v3.66.1](https://github.com/purocean/yn/releases/tag/v3.66.1) 2024-01-11
### [v3.67.0](https://github.com/purocean/yn/releases/tag/v3.67.0) 2024-01-11
[Windows](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-win-x64-3.66.1.exe) | [macOS arm64](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-mac-arm64-3.66.1.dmg) | [macOS x64](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-mac-x64-3.66.1.dmg) | [Linux AppImage](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-linux-x86_64-3.66.1.AppImage) | [Linux deb](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-linux-amd64-3.66.1.deb)
[Windows](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-win-x64-3.67.0.exe) | [macOS arm64](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-mac-arm64-3.67.0.dmg) | [macOS x64](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-mac-x64-3.67.0.dmg) | [Linux AppImage](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-linux-x86_64-3.67.0.AppImage) | [Linux deb](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-linux-amd64-3.67.0.deb)
1. feat: Support [Github-style warning prompts](https://github.com/orgs/community/discussions/16925)
2. feat: Support displaying open windows in the Dock menu on macOS
3. feat: Support rendering PlantUML using code blocks
4. fix: Fix the issue of abnormal line number display when the code block exceeds 100 lines
5. fix: Fix the issue of not being able to jump to the corresponding position in the preview area after selecting text in the editor
6. feat(plugin): Custom editor supports defining using classes
1. feat: Add "Services" menu on macOS
2. feat: Add HTML file preview functionality
3. feat: Allow parsing of Markdown `style` tags
4. feat: Support Vue code block highlighting in the editor (HTML mode)
5. fix: Fix slight performance degradation in Markdown rendering after introducing custom editor
6. fix: Fix caching issue with updated images
7. fix: Fix potential unresponsiveness of built-in terminal to window size changes
8. fix: Fix incorrect indentation of Python code in terminal execution
9. fix: Fix editor loading issue when opening files supported by custom editor
10. fix: Fix correct path retrieval when opening files from the system
11. feat(plugin): Support registering custom renderer `ctx.renderer.registerRenderer`
12. refactor(plugin): Rename `ctx.api.watchFile` to `ctx.api.watchFs`
13. refactor(plugin): Rename `ctx.markdown.getRenderCache` to `ctx.renderer.getRenderCache`
[More release notes](https://github.com/purocean/yn/releases)
+15 -8
View File
@@ -76,16 +76,23 @@
## 更新日志
### [v3.66.1](https://github.com/purocean/yn/releases/tag/v3.66.1) 2024-01-11
### [v3.67.0](https://github.com/purocean/yn/releases/tag/v3.67.0) 2024-01-11
[Windows](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-win-x64-3.66.1.exe) | [macOS arm64](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-mac-arm64-3.66.1.dmg) | [macOS x64](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-mac-x64-3.66.1.dmg) | [Linux AppImage](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-linux-x86_64-3.66.1.AppImage) | [Linux deb](https://github.com/purocean/yn/releases/download/v3.66.1/Yank-Note-linux-amd64-3.66.1.deb)
[Windows](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-win-x64-3.67.0.exe) | [macOS arm64](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-mac-arm64-3.67.0.dmg) | [macOS x64](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-mac-x64-3.67.0.dmg) | [Linux AppImage](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-linux-x86_64-3.67.0.AppImage) | [Linux deb](https://github.com/purocean/yn/releases/download/v3.67.0/Yank-Note-linux-amd64-3.67.0.deb)
1. feat: 支持 [Github 风格的警告提示](https://github.com/orgs/community/discussions/16925)
2. feat: 在 macOS 上支持在 Dock 菜单中显示打开的窗口
3. feat: 支持使用代码块的方式渲染 PlantUML
4. fix: 修复代码块超过 100 行后,行号显示异常问题
5. fix: 修复编辑器选中文字后,点击预览区不能跳转到对应位置的问题
6. feat(plugin): 自定义编辑器支持使用类的方式定义
1. feat: 在 macOS 上增加“服务”菜单
2. feat: 增加 HTML 文件预览功能
3. feat: 放开 Markdown `style` 标签解析
4. feat: 编辑器中支持 Vue 代码块高亮(HTML模式)
5. fix: 修复引入自定义编辑器后 Markdown 渲染性能轻微退化
6. fix: 修复图片更新后,图片被缓存的问题
7. fix: 修复内置终端可能不能响应窗口尺寸变化问题
8. fix: 修复终端运行 python 代码缩进不正确问题
9. fix: 修复打开自定义编辑器支持的文件,可能编辑器加载异常问题
10. fix: 修复从系统中打开文件,路径获取正确问题
11. feat(plugin): 支持注册自定义渲染器 `ctx.renderer.registerRenderer`
12. refactor(plugin): `ctx.api.watchFile` 被命名为 `ctx.api.watchFs`
13. refactor(plugin): `ctx.markdown.getRenderCache` 被命名为 `ctx.renderer.getRenderCache`
[更多发布说明](https://github.com/purocean/yn/releases)
+2
View File
@@ -399,6 +399,8 @@ If there is no title, there will be no outer border decoration
<button onclick="ctx.ui.useToast().show(`info`, `HELLOWORLD!`)">HELLO</button>
```
You can also *<a href="javascript: ctx.showExtensionManager('@yank-note/extension-repl')">install and activate the Vue Repl extension</a>* to write applet using Vue SFC.
## ECharts
The string containing `--echarts--` in the first line of the Js code block will be resolved into ECharts graphics, the example is as follows
+2
View File
@@ -397,6 +397,8 @@ function run (type) {
<button onclick="ctx.ui.useToast().show(`info`, `HELLOWORLD!`)">HELLO</button>
```
你也可以 *<a href="javascript: ctx.showExtensionManager('@yank-note/extension-repl')">安装并启用 Vue Repl 扩展</a>*,使用 Vue SFC 来编写小工具。
## ECharts 图形
Js 代码块第一行包含以 `--echarts--` 字符串会被解析成 ECharts 图形,示例如下
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "yank.note",
"version": "3.66.1",
"version": "3.67.0",
"description": "Yank Note: A highly extensible Markdown editor, designed for productivity.",
"main": "dist/main/app.js",
"license": "AGPL-3.0",
@@ -148,7 +148,7 @@
"typescript": "^4.9.5",
"utility-types": "^3.10.0",
"viewerjs": "^1.11.0",
"vite": "^4.5.1",
"vite": "^4.5.2",
"vue": "^3.3.13",
"vue-router": "^4.0.0-0",
"vue-tsc": "^1.8.26",
+4 -1
View File
@@ -11,7 +11,10 @@ export const getMainMenus = () => process.platform === 'darwin' ? Menu.buildFrom
submenu: [
{ type: 'normal', label: $t('app.preferences'), click: () => getAction('show-main-window-setting')() },
{ type: 'normal', label: $t('app.toggle-fullscreen'), accelerator: 'Ctrl+command+F', click: () => getAction('toggle-fullscreen')() },
{ type: 'normal', label: $t('app.quit'), accelerator: 'Command+Q', click: () => getAction('quit')() }
{ type: 'separator' },
{ role: 'services', submenu: [] },
{ type: 'separator' },
{ type: 'normal', label: $t('app.quit'), accelerator: 'Command+Q', click: () => getAction('quit')() },
]
},
{
+1
View File
@@ -208,6 +208,7 @@ const attachment = async (ctx: any, next: any) => {
checkPrivateRepo(ctx, repo)
noCache(ctx)
ctx.type = mime.getType(path)
ctx.body = await file.read(repo, path)
}
@@ -44,6 +44,7 @@ import { FileTabs } from '@fe/services/workbench'
import { isMarkdownFile } from '@fe/services/document'
import store from '@fe/support/store'
import type { Components } from '@fe/types'
import { CSS_VAR_NAME } from '@fe/support/args'
import DefaultPreviewerRender from './DefaultPreviewerRender.ce.vue'
import SvgIcon from './SvgIcon.vue'
@@ -264,6 +265,12 @@ onBeforeUnmount(() => {
FileTabs.removeActionBtnTapper(tabsActionBtnTapper)
})
watch([container, height], () => {
if (container.value) {
container.value.ownerDocument.documentElement.style.setProperty(CSS_VAR_NAME.PREVIEWER_HEIGHT, height.value + 'px')
}
})
async function scrollToTop () {
disableSyncScrollAwhile(() => {
scrollTopTo(0)
@@ -9,12 +9,11 @@
<script lang="ts" setup>
import { debounce } from 'lodash-es'
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { isElectron } from '@fe/support/env'
import { markdown } from '@fe/services/markdown'
import { triggerHook } from '@fe/core/hook'
import { registerAction, removeAction } from '@fe/core/action'
import { CtrlCmd } from '@fe/core/keybinding'
import { toUri, isMarkdownFile, isOutOfRepo } from '@fe/services/document'
import { toUri, isOutOfRepo } from '@fe/services/document'
import * as renderer from '@fe/services/renderer'
import { getContextMenuItems, getRenderIframe, scrollTopTo } from '@fe/services/view'
import { useContextMenu } from '@fe/support/ui/context-menu'
import { DOM_ATTR_NAME } from '@fe/support/args'
@@ -80,12 +79,7 @@ async function render (checkInComposition = false) {
updateRender = debounce(render, 25)
}
let content = currentContent.value
if (currentFile.value && !isMarkdownFile(currentFile.value)) {
content = `## ${currentFile.value.name} \n *Not a markdown file.*`
currentFile.value.name
}
const content = currentContent.value
const file = currentFile.value || null
const safeMode = isOutOfRepo(file) // enable safe mode for root repo
@@ -93,7 +87,7 @@ async function render (checkInComposition = false) {
const startTime = performance.now()
renderEnv = { tokens: [], source: content, file, renderCount, safeMode }
try {
renderContent.value = markdown.render(content, renderEnv)
renderContent.value = renderer.render(content, renderEnv)
} catch (error: any) {
logger.error('render', error)
renderContent.value = h('div', [
@@ -132,25 +126,21 @@ function handleClick (e: MouseEvent) {
}
function handleContextMenu (e: MouseEvent) {
const tagName = (e.target as HTMLElement).tagName
const allowTags = ['TD', 'TH']
if (isElectron || e.altKey || allowTags.includes(tagName)) {
const contextMenuItems = getContextMenuItems(e)
if (contextMenuItems.length > 0) {
e.stopPropagation()
e.preventDefault()
const contextMenuItems = getContextMenuItems(e)
if (contextMenuItems.length > 0) {
e.stopPropagation()
e.preventDefault()
const clientX = e.clientX
const clientY = e.clientY
const clientX = e.clientX
const clientY = e.clientY
getRenderIframe().then((iframe) => {
const iframeRect = iframe.getBoundingClientRect()
useContextMenu().show(contextMenuItems, {
mouseX: iframeRect.left + clientX,
mouseY: iframeRect.top + clientY,
})
getRenderIframe().then((iframe) => {
const iframeRect = iframe.getBoundingClientRect()
useContextMenu().show(contextMenuItems, {
mouseX: iframeRect.left + clientX,
mouseY: iframeRect.top + clientY,
})
}
})
}
}
@@ -264,7 +254,13 @@ onBeforeUnmount(() => {
})
watch([currentContent, fileUri, inComposition], () => {
autoPreview.value && updateRender()
if (autoPreview.value) {
if (renderCount === 0) {
render()
} else {
updateRender()
}
}
}, { flush: 'post' })
watch(fileUri, () => {
+2 -2
View File
@@ -4,7 +4,7 @@
</template>
<script lang="ts" setup>
import { computed, nextTick, onBeforeUnmount, onMounted, shallowRef, watchEffect } from 'vue'
import { computed, nextTick, onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'
import { getAllCustomEditors, isDefault, getValue, setValue, switchEditor } from '@fe/services/editor'
import { registerAction, removeAction } from '@fe/core/action'
import { registerHook, removeHook, triggerHook } from '@fe/core/hook'
@@ -145,7 +145,7 @@ onBeforeUnmount(() => {
FileTabs.removeActionBtnTapper(tabsActionBtnTapper)
})
watchEffect(() => {
watch(() => currentEditor.value?.name, () => {
triggerHook('EDITOR_CURRENT_EDITOR_CHANGE', { current: currentEditor.value })
FileTabs.refreshActionBtns()
recordEditorUsageTime(currentEditor.value?.name || 'default')
@@ -406,6 +406,7 @@ async function fetchContent (type: 'readme' | 'changelog', extension: Extension)
})
contentMap.value[type][extension.id] = `
<base target="_blank" />
<link rel="stylesheet" href="${location.origin}/github.css">
<div style="padding: 12px" class="markdown-body">
${markdownIt.render(markdown.replaceAll(/<small>([^<]+)<\/small>/g, '**$1**'))}
+4 -1
View File
@@ -114,6 +114,9 @@ export default defineComponent({
})
}
// force trigger resize event
xterm.resize(xterm.cols, xterm.cols)
if (!socket.connected) {
socket.io.opts.query = query
socket.connect()
@@ -148,7 +151,7 @@ export default defineComponent({
}
cmd.code.split('\n').forEach(x => {
input(x.trim())
input(x)
input(eol)
})
+2
View File
@@ -29,6 +29,7 @@ import * as theme from '@fe/services/theme'
import * as setting from '@fe/services/setting'
import * as i18n from '@fe/services/i18n'
import * as runner from '@fe/services/runner'
import * as renderer from '@fe/services/renderer'
import * as exportDoc from '@fe/services/export'
import * as directives from '@fe/directives/index'
import * as lib from './lib'
@@ -51,6 +52,7 @@ const ctx = Object.freeze({
markdown,
statusBar,
runner,
renderer,
layout,
editor,
view,
+2
View File
@@ -1,3 +1,4 @@
import buildInRenderers from '@fe/plugins/build-in-renderers'
import customStyles from '@fe/plugins/custom-styles'
import customKeybindings from '@fe/plugins/custom-keybindings'
import electronZoom from '@fe/plugins/electron-zoom'
@@ -70,6 +71,7 @@ import dropToOpenFile from '@fe/plugins/drop-to-open-file'
import recordRecentDocument from '@fe/plugins/record-recent-document'
export default [
buildInRenderers,
customStyles,
customKeybindings,
electronZoom,
@@ -0,0 +1,43 @@
import type { Plugin } from '@fe/context'
export default {
name: 'build-in-renderers',
register: (ctx) => {
ctx.renderer.registerRenderer({
name: 'not-markdown-file',
order: 999999999,
when (env) {
return !env.file || !ctx.doc.isMarkdownFile(env.file)
},
render (_, env) {
const { h } = ctx.lib.vue
return [
h('h1', env.file?.name),
h('p', [h('em', 'Not a markdown file.')])
]
},
})
ctx.renderer.registerRenderer({
name: 'markdown',
order: -255,
when (env) {
return !!(env.file && ctx.doc.isMarkdownFile(env.file))
},
render (src, env) {
return ctx.markdown.markdown.render(src, env)
},
})
ctx.renderer.registerRenderer({
name: 'html',
when (env) {
return !!(env.file && !env.safeMode && env.file.path.toLowerCase().endsWith('.html'))
},
render (src) {
const iframeProps = { style: `background: #fff; position: fixed; left: 0; top: 0; height: var(${ctx.args.CSS_VAR_NAME.PREVIEWER_HEIGHT})` }
return ctx.lib.vue.h(ctx.embed.IFrame, { html: src, triggerParentKeyBoardEvent: true, iframeProps })
},
})
}
} as Plugin
+1 -1
View File
@@ -208,7 +208,7 @@ export default {
items.push({
id: 'plugin.image-hosting-picgo.upload-single-image',
type: 'normal',
ellipsis: true,
ellipsis: false,
label: ctx.i18n.t('upload-image') + ' (PicGo)',
onClick: async () => {
try {
+1 -1
View File
@@ -21,7 +21,7 @@ const comment = '(?:<!(--)|(--)>)'
const HTML_TAG_RE = new RegExp('^(?:' + comment + '|' + openTag + '|' + closeTag + ')')
const HTML_SELF_CLOSE_TAG_RE = new RegExp('^' + selfCloseTag, 'i')
const INVALID_HTML_TAG_NAME_RE = /script|style/i
const INVALID_HTML_TAG_NAME_RE = /script/i
const INVALID_ATTR_NAME_RE = /^on|^xmlns$|^xml$|^aria-|^srcdoc$/i
const SAFE_MODE_ALLOWED_TAGS = [
+1 -1
View File
@@ -18,7 +18,7 @@ import type { Plugin } from '@fe/context'
import type Token from 'markdown-it/lib/token'
import styles from 'katex/dist/katex.min.css?inline'
import monacoLatex from '@fe/others/monaco-latex'
import { getRenderCache } from '@fe/services/markdown'
import { getRenderCache } from '@fe/services/renderer'
import { triggerHook } from '@fe/core/hook'
// Test if potential opening or closing delimieter
+21 -13
View File
@@ -10,6 +10,8 @@ import { basename, dirname, join, resolve } from '@fe/utils/path'
import { switchDoc } from '@fe/services/document'
import { getAttachmentURL, getRepo, openExternal, openPath } from '@fe/services/base'
import { getRenderIframe } from '@fe/services/view'
import { getAllCustomEditors } from '@fe/services/editor'
import type { Doc } from '@share/types'
async function getElement (id: string) {
id = id.replaceAll('%28', '(').replaceAll('%29', ')')
@@ -88,20 +90,20 @@ function handleLink (link: HTMLAnchorElement): boolean {
el.classList.remove(DOM_CLASS_NAME.PREVIEW_HIGHLIGHT)
}
if (/(\.md$|\.md#)/.test(href)) { // markdown file
const tmp = decodeURI(href).split('#')
const tmp = decodeURI(href).split('#')
let path = tmp[0]
if (!path.startsWith('/')) { // to absolute path
path = join(dirname(filePath || ''), path)
}
let path = tmp[0]
if (!path.startsWith('/')) { // to absolute path
path = join(dirname(filePath || ''), path)
}
switchDoc({
path,
name: basename(path),
repo: fileRepo,
type: 'file'
}).then(async () => {
const file: Doc = { path, type: 'file', name: basename(path), repo: fileRepo }
const isMarkdownFile = /(\.md$|\.md#)/.test(href)
const supportOpenDirectly = isMarkdownFile || getAllCustomEditors().some(x => x.when?.({ doc: file }))
if (supportOpenDirectly) {
switchDoc(file).then(async () => {
const hash = tmp.slice(1).join('#')
// jump anchor
if (hash) {
@@ -133,7 +135,7 @@ function handleLink (link: HTMLAnchorElement): boolean {
}
function convertLink (state: StateCore) {
const tags = ['audio', 'img', 'source', 'video', 'track', 'a', 'iframe']
const tags = ['audio', 'img', 'source', 'video', 'track', 'a', 'iframe', 'embed']
const { repo, path, name } = state.env.file || {}
if (!repo || !path || !name) {
@@ -166,6 +168,12 @@ function convertLink (state: StateCore) {
return
}
// support custom editor
const file: Doc = { path: resolve(basePath, attrVal), type: 'file', name: fileName, repo }
if (getAllCustomEditors().some(x => x.when?.({ doc: file }))) {
return
}
// open other file in os
token.attrJoin('class', DOM_CLASS_NAME.MARK_OPEN)
} else {
+1 -1
View File
@@ -309,7 +309,7 @@ export default {
return false
}
const cache = ctx.markdown.getRenderCache('plugin-macro', 'cache', { $define: {} } as CacheItem)
const cache = ctx.renderer.getRenderCache('plugin-macro', 'cache', { $define: {} } as CacheItem)
const options = {
purchased: getPurchased() || file.repo === ctx.args.HELP_REPO_NAME,
+2 -2
View File
@@ -15,7 +15,7 @@ export default {
const lock = new AsyncLock()
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
let handler: Awaited<ReturnType<typeof ctx.api.watchFile>> | null = null
let handler: Awaited<ReturnType<typeof ctx.api.watchFs>> | null = null
function stopWatch () {
logger.debug('stopWatch', !!handler)
@@ -33,7 +33,7 @@ export default {
const { repo, path } = doc
logger.debug('startWatch', repo, path)
const watchHandler = await ctx.api.watchFile(
const watchHandler = await ctx.api.watchFs(
repo,
path,
{ awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 50 }, alwaysStat: true },
+2 -2
View File
@@ -10,7 +10,7 @@ import { useModal } from '@fe/support/ui/modal'
import { useToast } from '@fe/support/ui/toast'
import store from '@fe/support/store'
import type { Doc, PathItem } from '@fe/types'
import { basename, dirname, extname, isBelongTo, join, normalizeSep, relative } from '@fe/utils/path'
import { basename, dirname, extname, isBelongTo, join, normalizeSep, relative, resolve } from '@fe/utils/path'
import { getActionHandler } from '@fe/core/action'
import { triggerHook } from '@fe/core/hook'
import { FLAG_MAS, HELP_REPO_NAME } from '@fe/support/args'
@@ -701,7 +701,7 @@ export async function switchDocByPath (path: string): Promise<void> {
type: 'file',
repo: repo.name,
name: basename(path),
path: relative(repo.path, path)
path: resolve(relative(repo.path, path))
})
} else {
if (FLAG_MAS) {
+2
View File
@@ -527,6 +527,8 @@ registerHook('MONACO_BEFORE_INIT', ({ monaco }) => {
lang.aliases?.push('node')
} else if (lang.id === 'shell') {
lang.aliases?.push('bash')
} else if (lang.id === 'html') {
lang.aliases?.push('vue')
}
})
+1 -51
View File
@@ -5,7 +5,7 @@ import MarkdownItMark from 'markdown-it-mark'
import MarkdownItAbbr from 'markdown-it-abbr'
import MarkdownItAttributes from 'markdown-it-attributes'
import MarkdownItMultimdTable from 'markdown-it-multimd-table'
import { registerHook, triggerHook } from '@fe/core/hook'
import { triggerHook } from '@fe/core/hook'
import { getSetting } from './setting'
/**
@@ -22,60 +22,10 @@ export function registerPlugin (plugin: (md: Markdown, ...args: any) => void, pa
markdown.use(plugin, params)
}
const renderCache: Map<string, Map<string, any>> = new Map()
/**
* Get render cache
* @param domain
* @param key
* @returns
*/
export function getRenderCache (domain: string): Map<string, any>
export function getRenderCache<T> (domain: string, key: string, fallback?: T | (() => T)): T
export function getRenderCache (domain: string, key?: string, fallback?: any) {
if (!domain) {
throw new Error('Domain is required')
}
if (!renderCache.has(domain)) {
renderCache.set(domain, new Map())
}
const cache = renderCache.get(domain)!
if (!key) {
return cache
}
const value = cache.get(key)
if (value) {
return value
}
const newValue = typeof fallback === 'function' ? fallback() : fallback
cache.set(key, newValue)
return newValue
}
registerHook('VIEW_BEFORE_REFRESH', () => {
renderCache.clear()
})
const render = markdown.render
markdown.render = (src: string, env?: any) => {
triggerHook('MARKDOWN_BEFORE_RENDER', { src, env, md: markdown })
// build render cache
if (env.file) {
const cacheKey = `__file_${env.file.repo}:${env.file.path}`
if (!renderCache.has(cacheKey)) {
renderCache.clear()
renderCache.set(cacheKey, new Map())
}
} else {
renderCache.clear()
}
markdown.options.html = getSetting('render.md-html', true)
markdown.options.breaks = getSetting('render.md-breaks', true)
markdown.options.linkify = getSetting('render.md-linkify', true)
+75
View File
@@ -0,0 +1,75 @@
import type { VNode } from 'vue'
import { registerHook } from '@fe/core/hook'
import type { RenderEnv, Renderer } from '@fe/types'
import * as ioc from '@fe/core/ioc'
const renderCache: Map<string, Map<string, any>> = new Map()
registerHook('VIEW_BEFORE_REFRESH', () => {
renderCache.clear()
})
/**
* Get render cache
* @param domain
* @param key
* @returns
*/
export function getRenderCache (domain: string): Map<string, any>
export function getRenderCache<T> (domain: string, key: string, fallback?: T | (() => T)): T
export function getRenderCache (domain: string, key?: string, fallback?: any) {
if (!domain) {
throw new Error('Domain is required')
}
if (!renderCache.has(domain)) {
renderCache.set(domain, new Map())
}
const cache = renderCache.get(domain)!
if (!key) {
return cache
}
const value = cache.get(key)
if (value) {
return value
}
const newValue = typeof fallback === 'function' ? fallback() : fallback
cache.set(key, newValue)
return newValue
}
export function render (src: string, env: RenderEnv): VNode | VNode[] | string {
// build render cache
if (env.file) {
const cacheKey = `__file_${env.file.repo}:${env.file.path}`
if (!renderCache.has(cacheKey)) {
renderCache.clear()
renderCache.set(cacheKey, new Map())
}
} else {
renderCache.clear()
}
const renderer = ioc.get('RENDERERS').find(x => x.when(env))
if (!renderer) {
throw new Error('No renderer found')
}
return renderer.render(src, env)
}
export function registerRenderer (renderer: Renderer) {
ioc.register('RENDERERS', renderer)
// sort renderers by order
ioc.getRaw('RENDERERS')?.sort((a, b) => (a.order || 0) - (b.order || 0))
}
export function removeRenderer (name: string) {
ioc.removeWhen('RENDERERS', item => item.name === name)
}
+3 -3
View File
@@ -324,16 +324,16 @@ export async function search (controller: AbortController, query: ITextQuery): P
}
/**
* Watch a file.
* Watch file or dir.
* @param controller
* @param query
* @returns
*/
export async function watchFile (
export async function watchFs (
repo: string,
path: string,
options: WatchOptions,
onResult: (result: { eventName: 'add' | 'change' | 'unlink', path: string, stats?: Stats }) => void,
onResult: (result: { eventName: 'add' | 'change' | 'unlink' | 'addDir' | 'unlinkDir', path: string, stats?: Stats }) => void,
onError: (error: Error) => void
) {
const controller: AbortController = new AbortController()
+4
View File
@@ -25,6 +25,10 @@ export const HELP_REPO_NAME = '__help__'
export const MONACO_EDITOR_NLS = { de: 'Deutsch', es: 'Español', fr: 'Français', it: 'Italiano', ja: '日本語', ko: '한국어', ru: 'Русский', 'zh-cn': '简体中文' }
export const CSS_VAR_NAME = {
PREVIEWER_HEIGHT: '--previewer-height'
}
export const DOM_ATTR_NAME = {
SOURCE_LINE_START: 'data-source-line',
SOURCE_LINE_END: 'data-source-line-end',
+9
View File
@@ -1,3 +1,4 @@
import type { VNode } from 'vue'
import type { Language, MsgPath } from '@share/i18n'
import type { Doc, FileItem, Repo } from '@share/types'
import type MarkdownIt from 'markdown-it'
@@ -502,6 +503,13 @@ export type CustomEditor = {
getIsDirty?: () => boolean | Promise<boolean>,
}
export type Renderer = {
name: string,
order?: number,
when: (env: RenderEnv) => boolean | Promise<boolean>,
render (src: string, env: RenderEnv): string | VNode | VNode[]
}
export interface CodeRunner {
name: string;
order?: number;
@@ -527,6 +535,7 @@ export type BuildInIOCTypes = { [key in keyof BuildInHookTypes]: any; } & {
THEME_STYLES: any;
VIEW_PREVIEWER: Previewer;
EDITOR_CUSTOM_EDITOR: CustomEditor,
RENDERERS: Renderer,
CODE_RUNNER: CodeRunner;
}
+4 -4
View File
@@ -9063,10 +9063,10 @@ viewerjs@^1.11.0:
resolved "https://registry.yarnpkg.com/viewerjs/-/viewerjs-1.11.0.tgz#eda25d96998356b4576fe25ef8d7cd6fd795a374"
integrity sha512-TE3XvfEXqKV+LdHsRdMSPsCaq0PfKDXm4SjGzfty7CzVxlDbBbT4x38vAqfXfNITmHhHUQefdxdg0Vv4ia+Z9w==
vite@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.1.tgz#3370986e1ed5dbabbf35a6c2e1fb1e18555b968a"
integrity sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==
vite@^4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82"
integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.27"