Merge branch 'release/2.4.8'

This commit is contained in:
purocean
2020-12-15 18:44:36 +08:00
15 changed files with 385 additions and 33 deletions
+17 -3
View File
@@ -6,7 +6,19 @@ on:
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: windows-latest
platform: 'win'
- os: ubuntu-latest
platform: 'linux'
runs-on: ${{ matrix.os }}
env:
npm_config_disturl: https://atom.io/download/electron
npm_config_target: 9.3.1
npm_config_runtime: "electron"
steps:
- name: Checkout
@@ -19,7 +31,8 @@ jobs:
- name: Install
run: |
./win-install.bat
yarn
yarn run download-pandoc
cd frontend
yarn
cd ..
@@ -29,7 +42,8 @@ jobs:
cd frontend
yarn run build
cd ..
./win-build.bat
yarn run build
yarn run electron-builder --${{ matrix.platform }} -p never
- name: GH Release
uses: softprops/action-gh-release@v0.1.5
+6 -2
View File
@@ -59,6 +59,7 @@
+ 嵌入 drawio 图形:文档支持内嵌 drawio 图形,示例见 FEATURE.md
+ 嵌入 ECharts 图形:在文档中嵌入 Echarts 图形,示例见 FEATURE.md
+ 嵌入 Mermaid 图形:在文档中嵌入 Mermaid 图形,示例见 FEATURE.md
+ 嵌套列表转脑图展示:可将嵌套列表用脑图的方式展示,示例见 FEATURE.md
+ 元素属性书写:可自定义元素的任意属性,示例见 FEATURE.md
+ 表格解析增强:表格支持表格标题多行文本,列表等特性,示例见 FEATURE.md
+ 文档交叉链接跳转:支持在文档中引入其他文档,互相跳转
@@ -146,12 +147,15 @@ yarn run start
## 更新日志
[最新发布](https://github.com/purocean/yn/releases)
### [v2.4.7](https://github.com/purocean/yn/releases/tag/v2.4.7) 2020-12-02
1. 修复编辑表格跨列单元格问题
### [v2.4.8](https://github.com/purocean/yn/releases/tag/v2.4.8) 2020-12-15
1. 增加大纲列表脑图展示功能
<details>
<summary>展开查看更多版本记录</summary>
### [v2.4.7](https://github.com/purocean/yn/releases/tag/v2.4.7) 2020-12-02
1. 修复编辑表格跨列单元格问题
### [v2.4.6](https://github.com/purocean/yn/releases/tag/v2.4.6) 2020-11-26
1. 增加编辑单元格内容功能
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+9
View File
@@ -23,6 +23,15 @@
],
"icon": "./build/icon.ico"
},
"linux": {
"executableName": "yank-note",
"category": "Markdown",
"maintainer": "purocean <purocean@outlook.com>",
"icon": "./build/icon.png",
"target": [
"deb"
]
},
"nsis": {
"perMachine": false
},
+3
View File
@@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="./kityminder.core.css" rel="stylesheet">
<title>Yank Note</title>
<script>
// 强制 monaco-editor 使用 浏览器模式
@@ -19,5 +20,7 @@
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="./kity.min.js"></script>
<script src="./kityminder.core.min.js"></script>
</body>
</html>
+12
View File
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
.km-view {
font-family: "STHeitiSC-Light", "STHeiti", "Hei", "Heiti SC", "Microsoft Yahei", Arial, sans-serif;
-webkit-user-select: none;
user-select: none;
position: relative;
}
.km-view .km-receiver {
position: absolute;
left: -99999px;
top: -99999px;
width: 20px;
height: 20px;
outline: none;
margin: 0;
}
.km-view image {
cursor: zoom-in;
}
.km-image-viewer {
position: fixed;
z-index: 99999;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, .75);
}
.km-image-viewer .km-image-viewer-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
text-align: center;
white-space: nowrap;
overflow: auto;
}
.km-image-viewer .km-image-viewer-container::before {
content: '';
display: inline-block;
height: 100%;
width: 0;
font-size: 0;
vertical-align: middle;
}
.km-image-viewer .km-image-viewer-container img {
cursor: zoom-out;
vertical-align: middle;
}
.km-image-viewer .km-image-viewer-container img.limited {
cursor: zoom-in;
max-width: 100%;
max-height: 100%;
}
.km-image-viewer .km-image-viewer-toolbar {
z-index: 1;
background: rgba(0, 0, 0, .75);
text-align: right;
transition: all .25s;
}
.km-image-viewer .km-image-viewer-toolbar.hidden {
transform: translate(0, -100%);
opacity: 0;
}
.km-image-viewer .km-image-viewer-btn {
cursor: pointer;
outline: 0;
border: 0;
width: 44px;
height: 44px;
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjY0IiBoZWlnaHQ9Ijg4IiB2aWV3Qm94PSIwIDAgMjY0IDg4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZT5kZWZhdWx0LXNraW4gMjwvdGl0bGU+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48Zz48cGF0aCBkPSJNNjcuMDAyIDU5LjV2My43NjhjLTYuMzA3Ljg0LTkuMTg0IDUuNzUtMTAuMDAyIDkuNzMyIDIuMjItMi44MyA1LjU2NC01LjA5OCAxMC4wMDItNS4wOThWNzEuNUw3MyA2NS41ODUgNjcuMDAyIDU5LjV6IiBpZD0iU2hhcGUiIGZpbGw9IiNmZmYiLz48ZyBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMTMgMjl2LTVoMnYzaDN2MmgtNXpNMTMgMTVoNXYyaC0zdjNoLTJ2LTV6TTMxIDE1djVoLTJ2LTNoLTN2LTJoNXpNMzEgMjloLTV2LTJoM3YtM2gydjV6IiBpZD0iU2hhcGUiLz48L2c+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTYyIDI0djVoLTJ2LTNoLTN2LTJoNXpNNjIgMjBoLTV2LTJoM3YtM2gydjV6TTcwIDIwdi01aDJ2M2gzdjJoLTV6TTcwIDI0aDV2MmgtM3YzaC0ydi01eiIvPjwvZz48cGF0aCBkPSJNMjAuNTg2IDY2bC01LjY1Ni01LjY1NiAxLjQxNC0xLjQxNEwyMiA2NC41ODZsNS42NTYtNS42NTYgMS40MTQgMS40MTRMMjMuNDE0IDY2bDUuNjU2IDUuNjU2LTEuNDE0IDEuNDE0TDIyIDY3LjQxNGwtNS42NTYgNS42NTYtMS40MTQtMS40MTRMMjAuNTg2IDY2eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xMTEuNzg1IDY1LjAzTDExMCA2My41bDMtMy41aC0xMHYtMmgxMGwtMy0zLjUgMS43ODUtMS40NjhMMTE3IDU5bC01LjIxNSA2LjAzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xNTIuMjE1IDY1LjAzTDE1NCA2My41bC0zLTMuNWgxMHYtMmgtMTBsMy0zLjUtMS43ODUtMS40NjhMMTQ3IDU5bDUuMjE1IDYuMDN6IiBmaWxsPSIjZmZmIi8+PGc+PHBhdGggaWQ9IlJlY3RhbmdsZS0xMSIgZmlsbD0iI2ZmZiIgZD0iTTE2MC45NTcgMjguNTQzbC0zLjI1LTMuMjUtMS40MTMgMS40MTQgMy4yNSAzLjI1eiIvPjxwYXRoIGQ9Ik0xNTIuNSAyN2MzLjAzOCAwIDUuNS0yLjQ2MiA1LjUtNS41cy0yLjQ2Mi01LjUtNS41LTUuNS01LjUgMi40NjItNS41IDUuNSAyLjQ2MiA1LjUgNS41IDUuNXoiIGlkPSJPdmFsLTEiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTUwIDIxaDV2MWgtNXoiLz48L2c+PGc+PHBhdGggZD0iTTExNi45NTcgMjguNTQzbC0xLjQxNCAxLjQxNC0zLjI1LTMuMjUgMS40MTQtMS40MTQgMy4yNSAzLjI1eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xMDguNSAyN2MzLjAzOCAwIDUuNS0yLjQ2MiA1LjUtNS41cy0yLjQ2Mi01LjUtNS41LTUuNS01LjUgMi40NjItNS41IDUuNSAyLjQ2MiA1LjUgNS41IDUuNXoiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA2IDIxaDV2MWgtNXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA5LjA0MyAxOS4wMDhsLS4wODUgNS0xLS4wMTcuMDg1LTV6Ii8+PC9nPjwvZz48L2c+PC9zdmc+);
}
.km-image-viewer .km-image-viewer-toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.km-image-viewer .km-image-viewer-close {
background-position: 0 -44px;
}
File diff suppressed because one or more lines are too long
+15 -1
View File
@@ -61,6 +61,7 @@ import DrawioPlugin from '../plugins/DrawioPlugin'
import PlantumlPlugin from '../plugins/PlantumlPlugin'
import MermaidPlugin from '../plugins/MermaidPlugin'
import CodePlugin from '../plugins/CodePlugin'
import MindMapPlugin from '../plugins/MindMapPlugin'
import file from '../useful/file'
import env from '../useful/env'
import { encodeMarkdownLink } from '../useful/utils'
@@ -101,6 +102,7 @@ const markdown = Markdown({
.use(MarkdownItECharts)
.use(MermaidPlugin)
.use(CodePlugin)
.use(MindMapPlugin)
export default defineComponent({
name: 'xview',
@@ -148,6 +150,11 @@ export default defineComponent({
nodes.forEach(AppletPlugin.runScript)
}
function renderMindMap () {
const nodes = refView.value!!.querySelectorAll<HTMLElement>('.mindmap-list[data-mindmap-source]')
nodes.forEach(MindMapPlugin.render)
}
const updatePlantumlDebounce = loadsh.debounce(() => {
updatePlantuml()
}, 3000, { leading: true })
@@ -156,6 +163,10 @@ export default defineComponent({
runAppletScript()
}, 1000, { leading: true })
const renderMindmapDebounce = loadsh.debounce(() => {
renderMindMap()
}, 1000, { leading: true })
function replaceRelativeLink (md: string) {
if (!fileRepo.value) {
return md
@@ -243,8 +254,11 @@ export default defineComponent({
const content = (filePath.value || '').endsWith('.md')
? currentContent.value
: '```' + file.extname(fileName.value || '').replace(/^\./, '') + '\n' + currentContent.value + '```'
refView.value!!.innerHTML = markdown.render(replaceRelativeLink(content))
const source = replaceRelativeLink(content)
refView.value!!.innerHTML = markdown.render(source, { source })
runAppletScriptDebounce()
renderMindmapDebounce()
initDrawio()
updateOutline()
updateTodoCount()
+1 -25
View File
@@ -1,7 +1,7 @@
import Markdown from 'markdown-it'
import file from '@/useful/file'
import env from '@/useful/env'
import { useBus } from '@/useful/bus'
import { openInNewWindow } from '@/useful/utils'
const Plugin = (md: Markdown) => {
const renderHtml = ({ url, content }: any) => {
@@ -119,30 +119,6 @@ const buildSrcdoc = async ({ repo, path, content, url }: F) => {
`
}
const openInNewWindow = (srcdoc: string) => {
const opener = env.openAlwaysOnTopWindow('about:blank')
const frame = document.createElement('iframe')
frame.width = '100%'
frame.height = '100%'
frame.frameBorder = '0'
frame.srcdoc = srcdoc
if (env.isElectron) {
const json = JSON.stringify(frame.outerHTML)
opener.eval(`
document.title = '查看图形'
document.body.style.height = '100vh'
document.body.style.margin = '0'
document.body.innerHTML = ${json}
`)
} else {
opener.document.title = '查看图形'
opener.document.body.style.height = '100vh'
opener.document.body.style.margin = '0'
opener.document.body.appendChild(frame)
}
}
Plugin.load = async (el: HTMLIFrameElement, url?: string) => {
const bus = useBus()
const content = el.dataset.content || ''
+155
View File
@@ -0,0 +1,155 @@
import Markdown from 'markdown-it'
import Renderer from 'markdown-it/lib/renderer'
import crypto from '@/useful/crypto'
import { dataURItoBlobLink, openInNewWindow } from '@/useful/utils'
const renderRule: Renderer.RenderRule = (tokens, idx, options, { source }, slf) => {
const token = tokens[idx]
const nextToken = tokens[idx + 1]
if (token.level === 0 && token.map && nextToken && nextToken.attrGet('class')?.includes('mindmap')) {
const content = source
.split('\n')
.slice(token.map[0], token.map[1])
.join('\n')
.replace(/\{.mindmap[^}]*\}/gm, '')
.replace(/^(\s*)([+-]*|\d+.) /gm, '$1')
token.attrJoin('class', 'mindmap-list')
token.attrSet('data-mindmap-source', content)
}
return slf.renderToken(tokens, idx, options)
}
const Plugin = (md: Markdown) => {
md.renderer.rules.bullet_list_open = renderRule
}
const buildSrcdoc = (json: string) => {
return `
<html>
<head>
<link rel="stylesheet" href="${location.origin}/kityminder.core.css" rel="stylesheet">
<script src="${location.origin}/kity.min.js"></script>
<script src="${location.origin}/kityminder.core.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
height: 100%;
}
#minder-view {
position: absolute;
border: 1px solid #ccc;
left: 10px;
top: 10px;
bottom: 10px;
right: 10px;
}
</style>
</head>
<body style="width: 100%; height: 100vh; padding: 0; margin: 0">
<script id="minder-view" type="application/kityminder" minder-data-type="json">${json}</script>
<script type="text/javascript">
var km = window.km = new kityminder.Minder();
km.setup('#minder-view');
km.disable();
km.execCommand('hand');
</script>
</body>
</html>
`
}
Plugin.render = async (ele: HTMLElement) => {
const code = (ele.dataset.mindmapSource || '').trim()
const div = document.createElement('div')
div.setAttribute('minder-data-type', 'text')
div.style.position = 'relative'
div.style.height = '400px'
ele.replaceWith(div)
const km = new (window as any).kityminder.Minder()
km.setup(div)
km.disable()
try {
await km.importData('text', code)
km.useTemplate('default')
} catch (error) {
await km.importData('text', '转换错误\n 1. 请保证大纲只有一个根项目\n 2. 请保证大纲层级正确')
km.useTemplate('structure')
}
km.execCommand('hand')
km.useTheme('fresh-green-compat')
km.execCommand('camera')
const tplList = ['default', 'right', 'structure', 'filetree', 'tianpan', 'fish-bone']
const switchLayout = () => {
const tpl = km.getTemplate()
const index = tplList.indexOf(tpl)
const nextIndex = index > tplList.length - 2 ? 0 : index + 1
km.useTemplate(tplList[nextIndex])
km.execCommand('camera')
}
const switchCompat = () => {
const theme = km.getTheme().split('-')
if (theme[theme.length - 1] === 'compat') {
theme.pop()
} else {
theme.push('compat')
}
km.useTheme(theme.join('-'))
km.execCommand('camera')
}
const exportData = async (type: 'png' | 'svg' | 'km') => {
const download = (url: string, name: string) => {
const link = document.createElement('a')
link.href = dataURItoBlobLink(url)
link.target = '_blank'
link.download = name
link.click()
}
switch (type) {
case 'svg':
download('data:image/svg+xml;base64,' + crypto.strToBase64(await km.exportData('svg')), 'mindmap.svg')
break
case 'km':
download('data:application/octet-stream;base64,' + crypto.strToBase64(await km.exportData('json')), 'mindmap.km')
break
case 'png':
download(await km.exportData('png'), 'mindmap.png')
break
default:
break
}
}
const buildButton = (text: string, fun: () => void) => {
const button = document.createElement('button')
button.style.cssText = 'margin-left: 5px;font-size: 14px;background: #cacaca; border: 0; padding: 0 6px; color: #2c2b2b; cursor: pointer; border-radius: 2px; transition: all .3s ease-in-out; line-height: 24px;'
button.innerText = text
button.onclick = fun
return button
}
const action = document.createElement('div')
action.className = 'no-print'
action.style.cssText = 'position: absolute; right: 15px; top: 3px; z-index: 1;'
action.appendChild(buildButton('导出 PNG', () => exportData('png')))
action.appendChild(buildButton('导出 SVG', () => exportData('svg')))
action.appendChild(buildButton('导出 KM', () => exportData('km')))
action.appendChild(buildButton('新窗口打开', () => openInNewWindow(buildSrcdoc(JSON.stringify(km.exportJson())))))
action.appendChild(buildButton('切换布局', switchLayout))
action.appendChild(buildButton('紧凑/宽松', switchCompat))
div.appendChild(action)
}
export default Plugin
+6 -1
View File
@@ -55,9 +55,14 @@ const binMd5 = (data: any) => {
return md5(CryptoJS.enc.Latin1.parse(data))
}
export const strToBase64 = (str: string) => {
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(str))
}
export default {
encrypt,
decrypt,
md5,
binMd5
binMd5,
strToBase64,
}
+40
View File
@@ -1,6 +1,46 @@
import env from '@/useful/env'
export const encodeMarkdownLink = (path: string) => {
return path
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/ /g, '%20')
}
export const openInNewWindow = (srcdoc: string) => {
const opener = env.openAlwaysOnTopWindow('about:blank')
const frame = document.createElement('iframe')
frame.width = '100%'
frame.height = '100%'
frame.frameBorder = '0'
frame.srcdoc = srcdoc
if (env.isElectron) {
const json = JSON.stringify(frame.outerHTML)
opener.eval(`
document.title = '查看图形'
document.body.style.height = '100vh'
document.body.style.margin = '0'
document.body.innerHTML = ${json}
`)
} else {
opener.document.title = '查看图形'
opener.document.body.style.height = '100vh'
opener.document.body.style.margin = '0'
opener.document.body.appendChild(frame)
}
}
export const dataURItoBlobLink = (dataURI: string) => {
const byteString = atob(dataURI.split(',')[1])
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
const ab = new ArrayBuffer(byteString.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
const blob = new Blob([ab], { type: mimeString })
return window.URL.createObjectURL(blob)
}
+17
View File
@@ -35,6 +35,23 @@ Yank-Note 很多功能可以直接使用的快捷键操作
## 脚注功能
支持使用脚注[^1]语法[^2]
## 嵌套列表脑图形式展示
只需要在列表根节点加上 `{.mindmap}` 即可。
+ 中心节点{.mindmap}
+ 状态可见原则
+ 环境贴切原则
+ 用户可控原则
+ 一致性原则
+ 防错原则
+ 易取原则
+ 灵活高效原则
+ 优美且简约原则
+ 容错原则
+ 人性化帮助原则
脑图使用 [kityminder-core](https://github.com/fex-team/kityminder-core) 实现。
## Mermaid 图形解析
```mermaid
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "yank.note.yang",
"version": "2.4.7",
"version": "2.4.8",
"description": "Yank Note 一款面向程序员的 Markdown 笔记应用",
"main": "dist/app.js",
"license": "Apache-2.0",