Support multiple lines math input (#294)

* change another way to render math

* open\save\edit multiple lines math block

* rewrite header label style

* inline code style update

* update dark theme style

* update change log

* typo error

* update webpack to v4

* update license

* fix unexpected to delete math preview block

* fix cursor error when change mode
This commit is contained in:
冉四夕 2018-05-26 00:58:16 +08:00 committed by GitHub
parent 1a7a3d5c06
commit 16bb1de82e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 4837 additions and 1632 deletions

View File

@ -51,7 +51,7 @@ function startRenderer () {
compiler.plugin('compilation', compilation => { compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' }) hotMiddleware.publish({ action: 'reload' })
cb() cb && cb()
}) })
}) })

View File

@ -5,10 +5,10 @@ process.env.BABEL_ENV = 'main'
const path = require('path') const path = require('path')
const { dependencies } = require('../package.json') const { dependencies } = require('../package.json')
const webpack = require('webpack') const webpack = require('webpack')
const proMode = process.env.NODE_ENV === 'production'
const BabiliWebpackPlugin = require('babili-webpack-plugin') const mainConfig = {
mode: 'development',
let mainConfig = {
entry: { entry: {
main: path.join(__dirname, '../src/main/index.js') main: path.join(__dirname, '../src/main/index.js')
}, },
@ -40,8 +40,8 @@ let mainConfig = {
] ]
}, },
node: { node: {
__dirname: process.env.NODE_ENV !== 'production', __dirname: !proMode,
__filename: process.env.NODE_ENV !== 'production' __filename: !proMode
}, },
output: { output: {
filename: '[name].js', filename: '[name].js',
@ -60,7 +60,7 @@ let mainConfig = {
/** /**
* Adjust mainConfig for development settings * Adjust mainConfig for development settings
*/ */
if (process.env.NODE_ENV !== 'production') { if (!proMode) {
mainConfig.plugins.push( mainConfig.plugins.push(
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
@ -71,12 +71,10 @@ if (process.env.NODE_ENV !== 'production') {
/** /**
* Adjust mainConfig for production settings * Adjust mainConfig for production settings
*/ */
if (process.env.NODE_ENV === 'production') { if (proMode) {
mainConfig.mode = 'production'
mainConfig.plugins.push( mainConfig.plugins.push(
new BabiliWebpackPlugin(), // new BabiliWebpackPlugin()
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
})
) )
} }

View File

@ -3,14 +3,14 @@
process.env.BABEL_ENV = 'renderer' process.env.BABEL_ENV = 'renderer'
const path = require('path') const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack') const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin') const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const { dependencies } = require('../package.json')
const proMode = process.env.NODE_ENV === 'production'
/** /**
* List of node_modules to include in webpack bundle * List of node_modules to include in webpack bundle
* *
@ -18,9 +18,10 @@ const HtmlWebpackPlugin = require('html-webpack-plugin')
* that provide pure *.vue files that need compiling * that provide pure *.vue files that need compiling
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
*/ */
let whiteListedModules = ['vue'] const whiteListedModules = ['vue']
let rendererConfig = { const rendererConfig = {
mode: 'development',
devtool: '#cheap-module-eval-source-map', devtool: '#cheap-module-eval-source-map',
entry: { entry: {
renderer: path.join(__dirname, '../src/renderer/main.js') renderer: path.join(__dirname, '../src/renderer/main.js')
@ -43,10 +44,10 @@ let rendererConfig = {
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: ExtractTextPlugin.extract({ use: [
fallback: 'style-loader', proMode ? MiniCssExtractPlugin.loader : 'style-loader',
use: 'css-loader' "css-loader"
}) ]
}, },
{ {
test: /\.html$/, test: /\.html$/,
@ -64,14 +65,7 @@ let rendererConfig = {
{ {
test: /\.vue$/, test: /\.vue$/,
use: { use: {
loader: 'vue-loader', loader: 'vue-loader'
options: {
extractCSS: process.env.NODE_ENV === 'production',
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader'
}
}
} }
}, },
{ {
@ -109,7 +103,6 @@ let rendererConfig = {
__filename: process.env.NODE_ENV !== 'production' __filename: process.env.NODE_ENV !== 'production'
}, },
plugins: [ plugins: [
new ExtractTextPlugin('styles.css'),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'index.html', filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'), template: path.resolve(__dirname, '../src/index.ejs'),
@ -123,7 +116,8 @@ let rendererConfig = {
: false : false
}), }),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin() new webpack.NoEmitOnErrorsPlugin(),
new VueLoaderPlugin()
], ],
output: { output: {
filename: '[name].js', filename: '[name].js',
@ -154,11 +148,16 @@ if (process.env.NODE_ENV !== 'production') {
/** /**
* Adjust rendererConfig for production settings * Adjust rendererConfig for production settings
*/ */
if (process.env.NODE_ENV === 'production') { if (proMode) {
rendererConfig.devtool = '' rendererConfig.devtool = ''
rendererConfig.mode = 'production'
rendererConfig.plugins.push( rendererConfig.plugins.push(
new BabiliWebpackPlugin(), new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css'
}),
new CopyWebpackPlugin([ new CopyWebpackPlugin([
{ {
from: path.join(__dirname, '../static'), from: path.join(__dirname, '../static'),
@ -170,9 +169,6 @@ if (process.env.NODE_ENV === 'production') {
to: path.join(__dirname, '../dist/electron/codemirror/mode/[name]/[name].js') to: path.join(__dirname, '../dist/electron/codemirror/mode/[name]/[name].js')
} }
]), ]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
minimize: true minimize: true
}) })

View File

@ -5,12 +5,15 @@ process.env.BABEL_ENV = 'web'
const path = require('path') const path = require('path')
const webpack = require('webpack') const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin') const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
let webConfig = { const proMode = process.env.NODE_ENV === 'production'
const webConfig = {
mode: 'development',
devtool: '#cheap-module-eval-source-map', devtool: '#cheap-module-eval-source-map',
entry: { entry: {
web: path.join(__dirname, '../src/renderer/main.js') web: path.join(__dirname, '../src/renderer/main.js')
@ -30,10 +33,10 @@ let webConfig = {
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: ExtractTextPlugin.extract({ use: [
fallback: 'style-loader', proMode ? MiniCssExtractPlugin.loader : 'style-loader',
use: 'css-loader' "css-loader"
}) ]
}, },
{ {
test: /\.html$/, test: /\.html$/,
@ -48,14 +51,7 @@ let webConfig = {
{ {
test: /\.vue$/, test: /\.vue$/,
use: { use: {
loader: 'vue-loader', loader: 'vue-loader'
options: {
extractCSS: true,
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader'
}
}
} }
}, },
{ {
@ -81,7 +77,6 @@ let webConfig = {
] ]
}, },
plugins: [ plugins: [
new ExtractTextPlugin('styles.css'),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'index.html', filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'), template: path.resolve(__dirname, '../src/index.ejs'),
@ -96,7 +91,8 @@ let webConfig = {
'process.env.IS_WEB': 'true' 'process.env.IS_WEB': 'true'
}), }),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin() new webpack.NoEmitOnErrorsPlugin(),
new VueLoaderPlugin()
], ],
output: { output: {
filename: '[name].js', filename: '[name].js',
@ -115,11 +111,17 @@ let webConfig = {
/** /**
* Adjust webConfig for production settings * Adjust webConfig for production settings
*/ */
if (process.env.NODE_ENV === 'production') { if (proMode) {
webConfig.devtool = '' webConfig.devtool = ''
webConfig.mode ='production'
webConfig.plugins.push( webConfig.plugins.push(
new BabiliWebpackPlugin(), new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css'
}),
new CopyWebpackPlugin([ new CopyWebpackPlugin([
{ {
from: path.join(__dirname, '../static'), from: path.join(__dirname, '../static'),
@ -127,9 +129,6 @@ if (process.env.NODE_ENV === 'production') {
ignore: ['.*'] ignore: ['.*']
} }
]), ]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
minimize: true minimize: true
}) })

View File

@ -1,4 +1,4 @@
### 0.11.37 ### 0.11.38
**:cactus:Feature** **:cactus:Feature**
@ -11,6 +11,7 @@
- feature: Support `setext` heading but the default heading style is `atx` - feature: Support `setext` heading but the default heading style is `atx`
- feature: User list item marker setting in preference file. - feature: User list item marker setting in preference file.
- feature: Select text from selected table (cell) only if you press Ctrl+A - feature: Select text from selected table (cell) only if you press Ctrl+A
- feature: Support Multiple lines math #242
**:butterfly:Optimization** **:butterfly:Optimization**

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017-2018 Jocs Copyright (c) 2017-Present Jocs
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +0,0 @@
!macro customUnInstall
MessageBox MB_YESNO "Do you want to delete user settings?" /SD IDNO IDNO SkipRemoval
SetShellVarContext current
RMDir /r "$APPDATA\marktext"
SkipRemoval:
!macroend

View File

0
dist/web/.gitkeep vendored
View File

5543
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "marktext", "name": "marktext",
"version": "0.10.21", "version": "0.10.21",
"author": "Jocs <luoran1988@126.com>", "author": "Jocs <ransixi@gmail.com>",
"description": "Next generation markdown editor", "description": "Next generation markdown editor",
"license": "MIT", "license": "MIT",
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
@ -105,61 +105,59 @@
"codemirror": "^5.36.0", "codemirror": "^5.36.0",
"css-tree": "^1.0.0-alpha.28", "css-tree": "^1.0.0-alpha.28",
"dompurify": "^1.0.3", "dompurify": "^1.0.3",
"electron-window-state": "^4.1.1", "element-ui": "^2.3.9",
"element-ui": "^2.3.3",
"file-icons-js": "^1.0.3", "file-icons-js": "^1.0.3",
"fs-extra": "^5.0.0", "fs-extra": "^6.0.1",
"fuzzaldrin": "^2.1.0", "fuzzaldrin": "^2.1.0",
"html-tags": "^2.0.0", "html-tags": "^2.0.0",
"katex": "^0.9.0", "katex": "^0.10.0-alpha",
"mousetrap": "^1.6.1", "mousetrap": "^1.6.1",
"parse5": "^4.0.0", "parse5": "^5.0.0",
"snabbdom": "^0.7.1", "snabbdom": "^0.7.1",
"snabbdom-to-html": "^5.1.1", "snabbdom-to-html": "^5.1.1",
"snabbdom-virtualize": "^0.7.0", "snabbdom-virtualize": "^0.7.0",
"turndown": "^4.0.1", "turndown": "^4.0.2",
"turndown-plugin-gfm": "^1.0.1", "turndown-plugin-gfm": "^1.0.2",
"vue": "^2.5.16", "vue": "^2.5.16",
"vue-electron": "^1.0.6", "vue-electron": "^1.0.6",
"vuex": "^2.3.1" "vuex": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-eslint": "^7.2.3", "babel-eslint": "^8.2.3",
"babel-loader": "^7.1.1", "babel-loader": "^7.1.4",
"babel-plugin-component": "^1.1.0", "babel-plugin-component": "^1.1.1",
"babel-plugin-istanbul": "^4.1.6", "babel-plugin-istanbul": "^4.1.6",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1", "babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.24.1", "babel-register": "^6.26.0",
"babili-webpack-plugin": "^0.1.2", "cfonts": "^2.1.2",
"cfonts": "^1.2.0",
"chai": "^4.0.0", "chai": "^4.0.0",
"chalk": "^2.1.0", "chalk": "^2.4.1",
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.0.5", "cross-env": "^5.1.6",
"css-loader": "^0.28.4", "css-loader": "^0.28.11",
"del": "^3.0.0", "del": "^3.0.0",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron": "^2.0.2", "electron": "^2.0.2",
"electron-builder": "^20.14.7", "electron-builder": "^20.14.7",
"electron-debug": "^1.5.0", "electron-debug": "^1.5.0",
"electron-devtools-installer": "^2.2.0", "electron-devtools-installer": "^2.2.4",
"electron-updater": "^2.21.8", "electron-updater": "^2.21.10",
"electron-window-state": "^4.1.1",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-standard": "^10.2.1", "eslint-config-standard": "^11.0.0",
"eslint-friendly-formatter": "^3.0.0", "eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^1.9.0", "eslint-loader": "^2.0.0",
"eslint-plugin-html": "^3.1.1", "eslint-plugin-html": "^4.0.3",
"eslint-plugin-import": "^2.10.0", "eslint-plugin-import": "^2.12.0",
"eslint-plugin-node": "^5.1.1", "eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.5.0", "eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^3.1.0",
"extract-text-webpack-plugin": "^3.0.0", "file-loader": "^1.1.11",
"file-loader": "^0.11.2", "html-webpack-plugin": "^3.2.0",
"html-webpack-plugin": "^2.30.1", "inject-loader": "^4.0.1",
"inject-loader": "^3.0.0",
"karma": "^1.3.0", "karma": "^1.3.0",
"karma-chai": "^0.1.0", "karma-chai": "^0.1.0",
"karma-coverage": "^1.1.1", "karma-coverage": "^1.1.1",
@ -168,20 +166,22 @@
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31", "karma-spec-reporter": "^0.0.31",
"karma-webpack": "^2.0.1", "karma-webpack": "^2.0.1",
"mini-css-extract-plugin": "^0.4.0",
"mocha": "^3.0.2", "mocha": "^3.0.2",
"multispinner": "^0.2.1", "multispinner": "^0.2.1",
"node-loader": "^0.6.0", "node-loader": "^0.6.0",
"require-dir": "^0.3.0", "require-dir": "^1.0.0",
"spectron": "^3.7.1", "spectron": "^3.8.0",
"style-loader": "^0.18.2", "style-loader": "^0.21.0",
"url-loader": "^0.5.9", "url-loader": "^1.0.1",
"vue-html-loader": "^1.2.4", "vue-html-loader": "^1.2.4",
"vue-loader": "^14.2.2", "vue-loader": "^15.2.0",
"vue-style-loader": "^3.0.1", "vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.4.2", "vue-template-compiler": "^2.5.16",
"webpack": "^3.5.2", "webpack": "^4.8.3",
"webpack-dev-server": "^2.7.1", "webpack-cli": "^2.1.4",
"webpack-hot-middleware": "^2.22.0", "webpack-dev-server": "^3.1.4",
"webpack-merge": "^4.1.0" "webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.2"
} }
} }

View File

@ -2,9 +2,10 @@ import { generateKeyHash, genUpper2LowerKeyHash, getLongUniqueId } from './utils
import htmlTags from 'html-tags' import htmlTags from 'html-tags'
import voidHtmlTags from 'html-tags/void' import voidHtmlTags from 'html-tags/void'
export const UNDO_DEPTH = 100
// [0.25, 0.5, 1, 2, 4, 8] <—?—> [256M, 500M/768M, 1G/1000M, 2G, 4G, 8G] // [0.25, 0.5, 1, 2, 4, 8] <—?—> [256M, 500M/768M, 1G/1000M, 2G, 4G, 8G]
export const DEVICE_MEMORY = navigator.deviceMemory // Get the divice memory number // Electron 2.0.2 not support yet! So give a default value 4
export const DEVICE_MEMORY = navigator.deviceMemory || 4 // Get the divice memory number(Chrome >= 63)
export const UNDO_DEPTH = DEVICE_MEMORY >= 4 ? 100 : 50
export const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr|pre)/i export const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr|pre)/i
export const VOID_HTML_TAGS = voidHtmlTags export const VOID_HTML_TAGS = voidHtmlTags
export const HTML_TAGS = htmlTags export const HTML_TAGS = htmlTags
@ -80,6 +81,7 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
'AG_HTML_ESCAPE', 'AG_HTML_ESCAPE',
'AG_FRONT_MATTER', 'AG_FRONT_MATTER',
'AG_FRONT_MATTER_LINE', 'AG_FRONT_MATTER_LINE',
'AG_MULTIPLE_MATH_LINE',
'AG_CODEMIRROR_BLOCK', 'AG_CODEMIRROR_BLOCK',
'AG_SHOW_PREVIEW', 'AG_SHOW_PREVIEW',
'AG_HTML_PREVIEW', 'AG_HTML_PREVIEW',
@ -113,7 +115,11 @@ export const CLASS_OR_ID = genUpper2LowerKeyHash([
'AG_MATH_TEXT', 'AG_MATH_TEXT',
'AG_MATH_RENDER', 'AG_MATH_RENDER',
'AG_MATH_ERROR', 'AG_MATH_ERROR',
'AG_MATH_EMPTY',
'AG_MATH_MARKER', 'AG_MATH_MARKER',
'AG_MATH_PREVIEW',
'AG_MULTIPLE_MATH_BLOCK',
'AG_MULTIPLE_MATH',
'AG_LOOSE_LIST_ITEM', 'AG_LOOSE_LIST_ITEM',
'AG_TIGHT_LIST_ITEM', 'AG_TIGHT_LIST_ITEM',
'AG_HTML_TAG', 'AG_HTML_TAG',

View File

@ -89,7 +89,7 @@ const arrowCtrl = ContentState => {
return return
} }
if (block.type === 'pre' && block.functionType !== 'frontmatter') { if (block.type === 'pre' && /code|html/.test(block.functionType)) {
// handle cursor in code block. the case at firstline or lastline. // handle cursor in code block. the case at firstline or lastline.
const cm = this.codeBlocks.get(id) const cm = this.codeBlocks.get(id)
const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
@ -205,8 +205,8 @@ const arrowCtrl = ContentState => {
} }
if ( if (
(preBlock && preBlock.type === 'pre' && preBlock.functionType !== 'frontmatter' && event.key === EVENT_KEYS.ArrowUp) || (preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowUp) ||
(preBlock && preBlock.type === 'pre' && preBlock.functionType !== 'frontmatter' && event.key === EVENT_KEYS.ArrowLeft && left === 0) (preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowLeft && left === 0)
) { ) {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -222,8 +222,8 @@ const arrowCtrl = ContentState => {
return this.partialRender() return this.partialRender()
} else if ( } else if (
(nextBlock && nextBlock.type === 'pre' && nextBlock.functionType !== 'frontmatter' && event.key === EVENT_KEYS.ArrowDown) || (nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowDown) ||
(nextBlock && nextBlock.type === 'pre' && nextBlock.functionType !== 'frontmatter' && event.key === EVENT_KEYS.ArrowRight && right === 0) (nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowRight && right === 0)
) { ) {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()

View File

@ -104,6 +104,7 @@ const backspaceCtrl = ContentState => {
const { start, end } = selection.getCursorRange() const { start, end } = selection.getCursorRange()
const startBlock = this.getBlock(start.key) const startBlock = this.getBlock(start.key)
const endBlock = this.getBlock(end.key) const endBlock = this.getBlock(end.key)
// fix: #67 problem 1 // fix: #67 problem 1
if (startBlock.icon) return event.preventDefault() if (startBlock.icon) return event.preventDefault()
// fix: unexpect remove all editor html. #67 problem 4 // fix: unexpect remove all editor html. #67 problem 4
@ -131,11 +132,11 @@ const backspaceCtrl = ContentState => {
if (start.key !== end.key) { if (start.key !== end.key) {
event.preventDefault() event.preventDefault()
const { key, offset } = start const { key, offset } = start
const startRemainText = startBlock.type === 'pre' && startBlock.functionType !== 'frontmatter' const startRemainText = startBlock.type === 'pre' && /code|html/.test(startBlock.functionType)
? startBlock.text.substring(0, offset - 1) ? startBlock.text.substring(0, offset - 1)
: startBlock.text.substring(0, offset) : startBlock.text.substring(0, offset)
const endRemainText = endBlock.type === 'pre' && endBlock.functionType !== 'frontmatter' const endRemainText = endBlock.type === 'pre' && /code|html/.test(endBlock.functionType)
? endBlock.text.substring(end.offset - 1) ? endBlock.text.substring(end.offset - 1)
: endBlock.text.substring(end.offset) : endBlock.text.substring(end.offset)
@ -183,7 +184,7 @@ const backspaceCtrl = ContentState => {
return tHeadHasContent || tBodyHasContent return tHeadHasContent || tBodyHasContent
} }
if (block.type === 'pre' && block.functionType !== 'frontmatter') { if (block.type === 'pre' && /code|html/.test(block.functionType)) {
const cm = this.codeBlocks.get(id) const cm = this.codeBlocks.get(id)
// if event.preventDefault(), you can not use backspace in language input. // if event.preventDefault(), you can not use backspace in language input.
if (isCursorAtBegin(cm) && onlyHaveOneLine(cm)) { if (isCursorAtBegin(cm) && onlyHaveOneLine(cm)) {
@ -204,9 +205,10 @@ const backspaceCtrl = ContentState => {
this.partialRender() this.partialRender()
} }
} else if ( } else if (
block.type === 'span' && block.functionType === 'frontmatter' && block.type === 'span' && /frontmatter|multiplemath/.test(block.functionType) &&
left === 0 && !preBlock left === 0 && !block.preSibling
) { ) {
const isMathLine = block.functionType === 'multiplemath'
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
const { key } = block const { key } = block
@ -216,6 +218,9 @@ const backspaceCtrl = ContentState => {
delete line.functionType delete line.functionType
this.appendChild(pBlock, line) this.appendChild(pBlock, line)
} }
if (isMathLine) {
parent = this.getParent(parent)
}
this.insertBefore(pBlock, parent) this.insertBefore(pBlock, parent)
this.removeBlock(parent) this.removeBlock(parent)
@ -326,7 +331,7 @@ const backspaceCtrl = ContentState => {
const { text } = block const { text } = block
const key = preBlock.key const key = preBlock.key
const offset = preBlock.text.length const offset = preBlock.text.length
if (preBlock.type === 'pre' && preBlock.functionType !== 'frontmatter') { if (preBlock.type === 'pre' && /code|html/.test(preBlock.functionType)) {
const cm = this.codeBlocks.get(key) const cm = this.codeBlocks.get(key)
const value = cm.getValue() + text const value = cm.getValue() + text
cm.setValue(value) cm.setValue(value)

View File

@ -52,7 +52,7 @@ const copyCutCtrl = ContentState => {
$( $(
`.${CLASS_OR_ID['AG_REMOVE']}, .${CLASS_OR_ID['AG_TOOL_BAR']}, `.${CLASS_OR_ID['AG_REMOVE']}, .${CLASS_OR_ID['AG_TOOL_BAR']},
.${CLASS_OR_ID['AG_MATH_RENDER']}, .${CLASS_OR_ID['AG_HTML_PREVIEW']}, .${CLASS_OR_ID['AG_MATH_RENDER']}, .${CLASS_OR_ID['AG_HTML_PREVIEW']},
.${CLASS_OR_ID['AG_COPY_REMOVE']}` .${CLASS_OR_ID['AG_MATH_PREVIEW']}, .${CLASS_OR_ID['AG_COPY_REMOVE']}`
).remove() ).remove()
$(`.${CLASS_OR_ID['AG_EMOJI_MARKER']}`).text(':') $(`.${CLASS_OR_ID['AG_EMOJI_MARKER']}`).text(':')
$(`.${CLASS_OR_ID['AG_NOTEXT_LINK']}`).empty() $(`.${CLASS_OR_ID['AG_NOTEXT_LINK']}`).empty()
@ -102,6 +102,18 @@ const copyCutCtrl = ContentState => {
}) })
} }
const mathBlock = $(`figure.ag-multiple-math-block`)
if (mathBlock.length > 0) {
mathBlock.each((i, hb) => {
const ele = $(hb)
const id = ele.attr('id')
const { math } = this.getBlock(id).children[1]
const pre = $('<pre class="multiple-math"></pre>')
pre.text(math)
ele.replaceWith(pre)
})
}
event.clipboardData.setData('text/html', $('body').html()) event.clipboardData.setData('text/html', $('body').html())
event.clipboardData.setData('text/plain', text) event.clipboardData.setData('text/plain', text)
} }

View File

@ -150,7 +150,7 @@ const enterCtrl = ContentState => {
return return
} }
// handle cursor in code block // handle cursor in code block
if (block.type === 'pre' && block.functionType !== 'frontmatter') { if (block.type === 'pre' && block.functionType === 'code') {
return return
} }
@ -197,15 +197,13 @@ const enterCtrl = ContentState => {
// only cursor in `line block` can create `soft line break` and `hard line break` // only cursor in `line block` can create `soft line break` and `hard line break`
if ( if (
(event.shiftKey && block.type === 'span') || (event.shiftKey && block.type === 'span') ||
(block.type === 'span' && block.functionType === 'frontmatter') (block.type === 'span' && /frontmatter|multiplemath/.test(block.functionType))
) { ) {
const { text } = block const { text } = block
const newLineText = text.substring(start.offset) const newLineText = text.substring(start.offset)
block.text = text.substring(0, start.offset) block.text = text.substring(0, start.offset)
const newLine = this.createBlock('span', newLineText) const newLine = this.createBlock('span', newLineText)
if (block.functionType === 'frontmatter') { newLine.functionType = block.functionType
newLine.functionType = 'frontmatter'
}
this.insertAfter(newLine, block) this.insertAfter(newLine, block)
const { key } = newLine const { key } = newLine
const offset = 0 const offset = 0
@ -374,6 +372,7 @@ const enterCtrl = ContentState => {
const blockNeedFocus = this.codeBlockUpdate(preParagraphBlock) const blockNeedFocus = this.codeBlockUpdate(preParagraphBlock)
let tableNeedFocus = this.tableBlockUpdate(preParagraphBlock) let tableNeedFocus = this.tableBlockUpdate(preParagraphBlock)
let htmlNeedFocus = this.updateHtmlBlock(preParagraphBlock) let htmlNeedFocus = this.updateHtmlBlock(preParagraphBlock)
let mathNeedFocus = this.updateMathBlock(preParagraphBlock)
let cursorBlock let cursorBlock
switch (true) { switch (true) {
@ -386,6 +385,9 @@ const enterCtrl = ContentState => {
case !!htmlNeedFocus: case !!htmlNeedFocus:
cursorBlock = htmlNeedFocus cursorBlock = htmlNeedFocus
break break
case !!mathNeedFocus:
cursorBlock = mathNeedFocus
break
default: default:
cursorBlock = newBlock cursorBlock = newBlock
break break

View File

@ -8,8 +8,7 @@ const DOMPurify = createDOMPurify(window)
const htmlBlock = ContentState => { const htmlBlock = ContentState => {
ContentState.prototype.createToolBar = function (tools, toolBarType) { ContentState.prototype.createToolBar = function (tools, toolBarType) {
const toolBar = this.createBlock('div') const toolBar = this.createBlock('div', '', false)
toolBar.editable = false
toolBar.toolBarType = toolBarType toolBar.toolBarType = toolBarType
const ul = this.createBlock('ul') const ul = this.createBlock('ul')
@ -28,8 +27,7 @@ const htmlBlock = ContentState => {
ContentState.prototype.createCodeInHtml = function (code, selection) { ContentState.prototype.createCodeInHtml = function (code, selection) {
const codeContainer = this.createBlock('div') const codeContainer = this.createBlock('div')
codeContainer.functionType = 'html' codeContainer.functionType = 'html'
const preview = this.createBlock('div') const preview = this.createBlock('div', '', false)
preview.editable = false
preview.htmlContent = DOMPurify.sanitize(escapeInBlockHtml(code), DOMPURIFY_CONFIG) preview.htmlContent = DOMPurify.sanitize(escapeInBlockHtml(code), DOMPURIFY_CONFIG)
preview.functionType = 'preview' preview.functionType = 'preview'
const codePre = this.createBlock('pre') const codePre = this.createBlock('pre')

View File

@ -51,7 +51,6 @@ class ContentState {
this.blocks = [ this.createBlockP() ] this.blocks = [ this.createBlockP() ]
this.stateRender = new StateRender(eventCenter) this.stateRender = new StateRender(eventCenter)
this.codeBlocks = new Map() this.codeBlocks = new Map()
this.loadMathMap = new Map()
this.renderRange = [ null, null ] this.renderRange = [ null, null ]
this.currentCursor = null this.currentCursor = null
this.prevCursor = null this.prevCursor = null
@ -122,7 +121,7 @@ class ContentState {
setCursor () { setCursor () {
const { start: { key } } = this.cursor const { start: { key } } = this.cursor
const block = this.getBlock(key) const block = this.getBlock(key)
if (block.type === 'pre' && block.functionType !== 'frontmatter') { if (block.type === 'pre' && /code|html/.test(block.functionType)) {
const cm = this.codeBlocks.get(key) const cm = this.codeBlocks.get(key)
const { selection } = block const { selection } = block
if (selection) { if (selection) {
@ -156,7 +155,6 @@ class ContentState {
this.setNextRenderRange() this.setNextRenderRange()
this.stateRender.render(blocks, cursor, activeBlocks, matches) this.stateRender.render(blocks, cursor, activeBlocks, matches)
this.pre2CodeMirror(isRenderCursor) this.pre2CodeMirror(isRenderCursor)
this.renderMath()
if (isRenderCursor) this.setCursor() if (isRenderCursor) this.setCursor()
} }
@ -175,7 +173,6 @@ class ContentState {
this.setNextRenderRange() this.setNextRenderRange()
this.stateRender.partialRender(needRenderBlocks, cursor, activeBlocks, matches, startKey, endKey) this.stateRender.partialRender(needRenderBlocks, cursor, activeBlocks, matches, startKey, endKey)
this.pre2CodeMirror(true, [...new Set([cursorOutMostBlock, ...needRenderBlocks])]) this.pre2CodeMirror(true, [...new Set([cursorOutMostBlock, ...needRenderBlocks])])
this.renderMath([...new Set([cursorOutMostBlock, ...needRenderBlocks])])
this.setCursor() this.setCursor()
} }
@ -183,12 +180,13 @@ class ContentState {
* A block in Aganippe present a paragraph(block syntax in GFM) or a line in paragraph. * A block in Aganippe present a paragraph(block syntax in GFM) or a line in paragraph.
* a line block must in a `p block` or `pre block(frontmatter)` and `p block`'s children must be line blocks. * a line block must in a `p block` or `pre block(frontmatter)` and `p block`'s children must be line blocks.
*/ */
createBlock (type = 'span', text = '') { // span type means it is a line block. createBlock (type = 'span', text = '', editable = true) { // span type means it is a line block.
const key = getUniqueId() const key = getUniqueId()
return { return {
key, key,
type, type,
text, text,
editable,
parent: null, parent: null,
preSibling: null, preSibling: null,
nextSibling: null, nextSibling: null,
@ -310,7 +308,7 @@ class ContentState {
if (children.length) { if (children.length) {
children.forEach(child => this.removeTextOrBlock(child)) children.forEach(child => this.removeTextOrBlock(child))
} }
} else { } else if (block.editable) {
this.removeBlock(block) this.removeBlock(block)
} }
} }
@ -365,8 +363,8 @@ class ContentState {
if (!afterEnd) { if (!afterEnd) {
const parent = this.getParent(after) const parent = this.getParent(after)
if (parent) { if (parent) {
const isOnlyChild = this.isOnlyChild(after) const removeAfter = isRemoveAfter && this.isOnlyEditableChild(after)
this.removeBlocks(before, parent, isOnlyChild, true) this.removeBlocks(before, parent, removeAfter, true)
} }
} }
if (isRemoveAfter) { if (isRemoveAfter) {
@ -505,6 +503,13 @@ class ContentState {
return !block.nextSibling && !block.preSibling return !block.nextSibling && !block.preSibling
} }
isOnlyEditableChild (block) {
if (block.editable === false) return false
const parent = this.getParent(block)
if (!parent) throw new Error('isOnlyEditableChild method only apply for child block')
return parent.children.filter(child => child.editable).length === 1
}
getLastChild (block) { getLastChild (block) {
if (block) { if (block) {
const len = block.children.length const len = block.children.length

View File

@ -1,40 +1,69 @@
import katex from 'katex' // import { CLASS_OR_ID } from '../config'
import { CLASS_OR_ID } from '../config' const LINE_BREAKS_REG = /\n/
import 'katex/dist/katex.min.css'
const mathCtrl = ContentState => { const mathCtrl = ContentState => {
ContentState.prototype.renderMath = function (blocks) { ContentState.prototype.createMathBlock = function (value = '') {
let selector = '' const FUNCTION_TYPE = 'multiplemath'
const mathBlock = this.createBlock('figure')
if (blocks) { const textArea = this.createBlock('pre')
selector = blocks.map(block => `#${block.key} .${CLASS_OR_ID['AG_MATH_RENDER']}`).join(', ') const mathPreview = this.createBlock('div', '', false)
if (typeof value === 'string' && value) {
const lines = value.replace(/^\s+/, '').split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
for (const line of lines) {
line.functionType = FUNCTION_TYPE
this.appendChild(textArea, line)
}
} else { } else {
selector = `.${CLASS_OR_ID['AG_MATH_RENDER']}` const emptyLine = this.createBlock('span')
emptyLine.functionType = FUNCTION_TYPE
this.appendChild(textArea, emptyLine)
} }
const mathEles = document.querySelectorAll(selector) mathBlock.functionType = textArea.functionType = mathPreview.functionType = FUNCTION_TYPE
const { loadMathMap } = this mathPreview.math = value
for (const math of mathEles) { this.appendChild(mathBlock, textArea)
const content = math.getAttribute('data-math') this.appendChild(mathBlock, mathPreview)
const type = math.getAttribute('data-type') return mathBlock
const displayMode = type === 'display_math' }
const key = `${content}_${type}`
if (loadMathMap.has(key)) { ContentState.prototype.initMathBlock = function (block) { // p block
math.innerHTML = loadMathMap.get(key) const FUNCTION_TYPE = 'multiplemath'
continue const textArea = this.createBlock('pre')
} const emptyLine = this.createBlock('span')
try { textArea.functionType = emptyLine.functionType = FUNCTION_TYPE
const html = katex.renderToString(content, { this.appendChild(textArea, emptyLine)
displayMode block.type = 'figure'
}) block.functionType = FUNCTION_TYPE
loadMathMap.set(key, html) block.children = []
math.innerHTML = html
} catch (err) { const mathPreview = this.createBlock('div', '', false)
math.innerHTML = 'Invalid' mathPreview.math = ''
math.classList.add(CLASS_OR_ID['AG_MATH_ERROR']) mathPreview.functionType = FUNCTION_TYPE
}
this.appendChild(block, textArea)
this.appendChild(block, mathPreview)
return emptyLine
}
ContentState.prototype.handleMathBlockClick = function (mathFigure) {
const { id } = mathFigure
const mathBlock = this.getBlock(id)
const textAreaBlock = mathBlock.children[0]
const firstLine = textAreaBlock.children[0]
const { key } = firstLine
const offset = 0
this.cursor = {
start: { key, offset },
end: { key, offset }
} }
this.partialRender()
}
ContentState.prototype.updateMathBlock = function (block) {
const { type } = block
if (type !== 'p') return false
const { text } = block.children[0]
return text.trim() === '$$' ? this.initMathBlock(block) : false
} }
} }

View File

@ -318,6 +318,27 @@ const paragraphCtrl = ContentState => {
} }
} }
ContentState.prototype.insertMathBlock = function () {
const { start, end } = selection.getCursorRange()
if (start.key !== end.key) return
let block = this.getBlock(start.key)
if (block.type === 'span') {
block = this.getParent(block)
}
const mathBlock = this.createMathBlock()
this.insertAfter(mathBlock, block)
if (block.type === 'p' && block.children.length === 1 && !block.children[0].text) {
this.removeBlock(block)
}
const cursorBlock = mathBlock.children[0].children[0]
const { key } = cursorBlock
const offset = 0
this.cursor = {
start: { key, offset },
end: { key, offset }
}
}
ContentState.prototype.updateParagraph = function (paraType) { ContentState.prototype.updateParagraph = function (paraType) {
const { start, end } = selection.getCursorRange() const { start, end } = selection.getCursorRange()
const block = this.getBlock(start.key) const block = this.getBlock(start.key)
@ -346,6 +367,10 @@ const paragraphCtrl = ContentState => {
this.handleQuoteMenu() this.handleQuoteMenu()
break break
} }
case 'mathblock': {
this.insertMathBlock()
break
}
case 'heading 1': case 'heading 1':
case 'heading 2': case 'heading 2':
case 'heading 3': case 'heading 3':

View File

@ -131,10 +131,15 @@ const pasteCtrl = ContentState => {
throw new Error('unknown paste type') throw new Error('unknown paste type')
} }
// step 3: set cursor and render // step 3: set cursor and render
const cursorBlock = this.getBlock(key) let cursorBlock = this.getBlock(key)
if (!cursorBlock) { if (!cursorBlock) {
key = startBlock.key key = startBlock.key
offset = startBlock.text.length - cacheText.length offset = startBlock.text.length - cacheText.length
cursorBlock = startBlock
}
// TODO @Jocs duplicate with codes in updateCtrl.js
if (cursorBlock && cursorBlock.type === 'span' && cursorBlock.functionType === 'multiplemath') {
this.updateMathContent(cursorBlock)
} }
this.cursor = { this.cursor = {
start: { start: {

View File

@ -284,6 +284,13 @@ const updateCtrl = ContentState => {
return null return null
} }
ContentState.prototype.updateMathContent = function (block) {
const preBlock = this.getParent(block)
const mathPreview = this.getNextSibling(preBlock)
const math = preBlock.children.map(line => line.text).join('\n')
mathPreview.math = math
}
ContentState.prototype.updateState = function (event) { ContentState.prototype.updateState = function (event) {
const { floatBox } = this const { floatBox } = this
const { start, end } = selection.getCursorRange() const { start, end } = selection.getCursorRange()
@ -314,7 +321,7 @@ const updateCtrl = ContentState => {
this.removeBlocks(startBlock, endBlock) this.removeBlocks(startBlock, endBlock)
// there still has little bug, when the oldstart block is `pre`, the input value will be ignored. // there still has little bug, when the oldstart block is `pre`, the input value will be ignored.
// and act as `backspace` // and act as `backspace`
if (startBlock.type === 'pre' && startBlock.functionType !== 'frontmatter') { if (startBlock.type === 'pre' && /code|html/.test(startBlock.functionType)) {
event.preventDefault() event.preventDefault()
const startRemainText = startBlock.type === 'pre' const startRemainText = startBlock.type === 'pre'
? startBlock.text.substring(0, oldStart.offset - 1) ? startBlock.text.substring(0, oldStart.offset - 1)
@ -379,7 +386,7 @@ const updateCtrl = ContentState => {
} }
} }
if (block && block.type === 'pre' && block.functionType !== 'frontmatter') { if (block && block.type === 'pre' && /code|html/.test(block.functionType)) {
if (block.key !== oldKey) { if (block.key !== oldKey) {
this.cursor = lastCursor = { start, end } this.cursor = lastCursor = { start, end }
if (event.type === 'click' && oldKey) { if (event.type === 'click' && oldKey) {
@ -388,6 +395,7 @@ const updateCtrl = ContentState => {
} }
return return
} }
// auto pair // auto pair
if (block && block.text !== text) { if (block && block.text !== text) {
const BRACKET_HASH = { const BRACKET_HASH = {
@ -423,13 +431,16 @@ const updateCtrl = ContentState => {
block.text = text block.text = text
} }
if (block && block.type === 'span' && block.functionType === 'multiplemath') {
this.updateMathContent(block)
}
if (oldKey !== key || oldStart.offset !== start.offset || oldEnd.offset !== end.offset) { if (oldKey !== key || oldStart.offset !== start.offset || oldEnd.offset !== end.offset) {
needRender = true needRender = true
} }
this.cursor = lastCursor = { start, end } this.cursor = lastCursor = { start, end }
const checkMarkedUpdate = this.checkNeedRender(block) const checkMarkedUpdate = this.checkNeedRender(block)
const inlineUpdatedBlock = this.isCollapse() && block.functionType !== 'frontmatter' && this.checkInlineUpdate(block) const inlineUpdatedBlock = this.isCollapse() && !/frontmatter|multiplemath/.test(block.functionType) && this.checkInlineUpdate(block)
if (checkMarkedUpdate || inlineUpdatedBlock || needRender) { if (checkMarkedUpdate || inlineUpdatedBlock || needRender) {
this.partialRender() this.partialRender()
} }

View File

@ -10,12 +10,12 @@
} }
} }
h1.ag-active::before, #ag-editor-id > h1.ag-active::before,
h2.ag-active::before, #ag-editor-id > h2.ag-active::before,
h3.ag-active::before, #ag-editor-id > h3.ag-active::before,
h4.ag-active::before, #ag-editor-id > h4.ag-active::before,
h5.ag-active::before, #ag-editor-id > h5.ag-active::before,
h6.ag-active::before { #ag-editor-id > h6.ag-active::before {
content: attr(data-head); content: attr(data-head);
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -25,12 +25,10 @@ h6.ag-active::before {
position: absolute; position: absolute;
top: 0; top: 0;
left: -25px; left: -25px;
border: 1px solid #C0C4CC; font-size: 14px;
border-radius: 3px;
font-size: 12px;
color: #C0C4CC; color: #C0C4CC;
transform: scale(.7); font-weight: 400;
font-weight: 100; font-style: italic;
} }
.ag-paragraph:empty::after, .ag-paragraph:empty::after,
@ -43,6 +41,10 @@ h6.ag-active::before {
white-space: pre-wrap; white-space: pre-wrap;
} }
.ag-gray {
font-family: monospace;
}
/* .ag-soft-line-break::after { /* .ag-soft-line-break::after {
content: '↓'; content: '↓';
opacity: .5; opacity: .5;
@ -58,6 +60,7 @@ h6.ag-active::before {
color: #303133; color: #303133;
} }
figure pre.ag-multiple-math,
div.ag-function-html pre.ag-html-block { div.ag-function-html pre.ag-html-block {
width: 0; width: 0;
height: 0; height: 0;
@ -67,7 +70,8 @@ div.ag-function-html pre.ag-html-block {
position: absolute; position: absolute;
} }
div.ag-function-html.ag-active pre.ag-html-block { div.ag-function-html.ag-active pre.ag-html-block,
figure.ag-active pre.ag-multiple-math {
position: static; position: static;
width: 100%; width: 100%;
height: auto; height: auto;
@ -101,37 +105,38 @@ span.ag-html-tag {
span.ag-math { span.ag-math {
position: relative; position: relative;
color: purple; color: purple;
letter-spacing: 0.1em; font-family: monospace;
display: inline-block; display: inline-block;
vertical-align: bottom; vertical-align: bottom;
} }
.ag-math > .ag-math-render { .ag-math > .ag-math-render {
display: inline-block; display: inline-block;
background: rgb(79, 79, 79); padding: .5rem;
padding: .3em 1em; background: #fff;
border-radius: 5px; border: 1px solid #ebeef5;
color: #fff; border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
color: #333;
position: absolute; position: absolute;
top: 30px; top: 30px;
left: 0; left: 0;
z-index: 10; z-index: 10;
} }
.ag-math > .ag-math-error { div.ag-math-empty {
color: rgba(242, 134, 94, .7); color: #999;
font-size: 14px;
font-style: italic;
font-family: monospace;
} }
.ag-math > .ag-math-render::before { div.ag-math-error,
content: ''; span.ag-math > .ag-math-render.ag-math-error {
width: 10px; color: #e6a23c;
height: 10px; font-size: 14px;
display: inline-block; font-style: italic;
position: absolute; font-family: monospace;
transform: rotate(45deg) translateX(-50%);
background: rgb(79, 79, 79);
left: 10px;
top: -1px;
} }
.ag-math > .ag-math-render .katex-display { .ag-math > .ag-math-render .katex-display {
@ -151,9 +156,11 @@ span.ag-math {
} }
.ag-hide.ag-math > .ag-math-render { .ag-hide.ag-math > .ag-math-render {
padding: 0;
top: 0; top: 0;
position: relative; position: relative;
padding: 0; border: none;
box-shadow: none;
background: transparent; background: transparent;
} }
@ -164,7 +171,7 @@ span.ag-math {
figure { figure {
padding: 0; padding: 0;
margin: 0; margin: 0;
margin-top: 25px; margin: 1rem 0;
position: relative; position: relative;
} }
.ag-tool-bar { .ag-tool-bar {
@ -221,29 +228,18 @@ figure.ag-active .ag-tool-bar {
display: block; display: block;
} }
figure[data-role=HTML] { figure.ag-active[data-role=HTML]::before {
margin-top: 0;
}
figure.ag-active[data-role=HTML]::before,
pre.ag-active[data-role=YAML]::before {
content: attr(data-role); content: attr(data-role);
width: auto;
padding: 0 .3rem;
letter-spacing: .1rem;
height: 20px; height: 20px;
text-align: center; text-align: center;
line-height: 22px;
display: block; display: block;
position: absolute; position: absolute;
top: 0; top: 0;
left: -45px; left: -45px;
border: 1px solid #C0C4CC;
border-radius: 3px;
font-size: 12px; font-size: 12px;
color: #C0C4CC; color: #C0C4CC;
transform: scale(.7); font-weight: 400;
font-weight: 300; font-style: italic;
} }
table { table {
@ -340,12 +336,17 @@ p:not(.ag-active)[data-role="hr"] * {
color: transparent; color: transparent;
} }
pre.ag-multiple-math,
pre.ag-front-matter { pre.ag-front-matter {
position: relative; position: relative;
background: #f7f7f7; background: #f6f8fa;
padding: 1rem; padding: .5rem;
border: 5px; border: 5px;
font-size: 15px; font-size: 14px;
margin: 0;
}
pre.ag-front-matter {
margin: 1rem 0;
} }
span.ag-front-matter-line:first-of-type:empty::after { span.ag-front-matter-line:first-of-type:empty::after {
@ -353,6 +354,11 @@ span.ag-front-matter-line:first-of-type:empty::after {
color: #999; color: #999;
} }
span.ag-multiple-math-line:first-of-type:empty::after {
content: 'Input Mathematical Formula...';
color: #999;
}
figure, figure,
pre.ag-html-block, pre.ag-html-block,
div.ag-function-html, div.ag-function-html,
@ -365,22 +371,70 @@ pre.ag-code-block {
pre.ag-code-block { pre.ag-code-block {
margin: 1rem 0; margin: 1rem 0;
padding: 0 .5rem;
}
pre.ag-active.ag-front-matter::before,
pre.ag-active.ag-front-matter::after {
content: '---';
}
pre.ag-active.ag-multiple-math::before,
pre.ag-active.ag-multiple-math::after {
content: '$$';
} }
pre.ag-active.ag-code-block::before, pre.ag-active.ag-code-block::before,
pre.ag-active.ag-code-block::after { pre.ag-active.ag-code-block::after {
content: '```'; content: '```';
}
pre.ag-active.ag-front-matter::before,
pre.ag-active.ag-front-matter::after,
pre.ag-active.ag-code-block::before,
pre.ag-active.ag-code-block::after,
pre.ag-active.ag-multiple-math::before,
pre.ag-active.ag-multiple-math::after {
color: #909399; color: #909399;
font-family: monospace;
position: absolute; position: absolute;
left: 0; left: 0;
} }
pre.ag-active.ag-front-matter::before,
pre.ag-active.ag-multiple-math::before,
pre.ag-active.ag-code-block::before { pre.ag-active.ag-code-block::before {
top: -20px; top: -20px;
} }
pre.ag-active.ag-front-matter::after,
pre.ag-active.ag-multiple-math::after,
pre.ag-active.ag-code-block::after { pre.ag-active.ag-code-block::after {
bottom: -25px; bottom: -23px;
}
span.ag-multiple-math-line {
color: purple;
font-family: monospace;
}
figure div.ag-math-preview {
width: 100%;
text-align: center;
}
figure.ag-active div.ag-math-preview {
position: absolute;
top: calc(100% + 8px);
left: 50%;
width: auto;
z-index: 1;
transform: translateX(-50%);
padding: .5rem;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
} }
div.ag-html-preview { div.ag-html-preview {

View File

@ -80,6 +80,7 @@ class Aganippe {
this.dispatchTableToolBar() this.dispatchTableToolBar()
this.dispatchCodeBlockClick() this.dispatchCodeBlockClick()
this.htmlPreviewClick() this.htmlPreviewClick()
this.mathPreviewClick()
contentState.listenForPathChange() contentState.listenForPathChange()
@ -429,6 +430,21 @@ class Aganippe {
eventCenter.attachDOMEvent(container, 'click', handler) eventCenter.attachDOMEvent(container, 'click', handler)
} }
mathPreviewClick () {
const { eventCenter, container } = this
const handler = event => {
const target = event.target
const mathFigure = isInElement(target, 'ag-multiple-math-block')
if (mathFigure && !mathFigure.classList.contains(CLASS_OR_ID['AG_ACTIVE'])) {
event.preventDefault()
event.stopPropagation()
this.contentState.handleMathBlockClick(mathFigure)
}
}
eventCenter.attachDOMEvent(container, 'click', handler)
}
listItemCheckBoxClick () { listItemCheckBoxClick () {
const { container, eventCenter } = this const { container, eventCenter } = this
const handler = event => { const handler = event => {

View File

@ -29,7 +29,8 @@ var block = {
table: noop, table: noop,
paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
text: /^[^\n]+/, text: /^[^\n]+/,
frontmatter: /^---\n([\s\S]+?)---(?:\n+|$)/ frontmatter: /^---\n([\s\S]+?)---(?:\n+|$)/,
multiplemath: /^\$\$\n([\s\S]+?)\n\$\$(?:\n+|$)/
}; };
block.checkbox = /^\[([ x])\] +/; block.checkbox = /^\[([ x])\] +/;
@ -195,16 +196,25 @@ Lexer.prototype.token = function(src, top, bq) {
continue; continue;
} }
// multiple line math
if (cap = this.rules.multiplemath.exec(src)) {
src = src.substring(cap[0].length)
this.tokens.push({
type: 'multiplemath',
text: cap[1]
})
}
// fences (gfm) // fences (gfm)
if (cap = this.rules.fences.exec(src)) { if (cap = this.rules.fences.exec(src)) {
src = src.substring(cap[0].length); src = src.substring(cap[0].length)
this.tokens.push({ this.tokens.push({
type: 'code', type: 'code',
codeBlockStyle: 'fenced', codeBlockStyle: 'fenced',
lang: cap[2], lang: cap[2],
text: cap[3] text: cap[3]
}); })
continue; continue
} }
// heading // heading
@ -822,6 +832,10 @@ Renderer.prototype.frontmatter = function (text) {
return `<pre class="front-matter">\n${text}</pre>\n` return `<pre class="front-matter">\n${text}</pre>\n`
} }
Renderer.prototype.multiplemath = function (text) {
return `<pre class="multiple-math">\n${text}</pre>\n`
}
Renderer.prototype.code = function (code, lang, escaped, codeBlockStyle) { Renderer.prototype.code = function (code, lang, escaped, codeBlockStyle) {
if (this.options.highlight) { if (this.options.highlight) {
var out = this.options.highlight(code, lang); var out = this.options.highlight(code, lang);
@ -1073,13 +1087,17 @@ Parser.prototype.tok = function() {
return this.renderer.hr() return this.renderer.hr()
} }
case 'heading': { case 'heading': {
return this.renderer.heading( return this.renderer.heading(
this.inline.output(this.token.text), this.inline.output(this.token.text),
this.token.depth, this.token.depth,
this.token.text, this.token.text,
this.token.headingStyle this.token.headingStyle
) )
} }
case 'multiplemath': {
const { text } = this.token
return this.renderer.multiplemath(text)
}
case 'code': case 'code':
{ {
const { codeBlockStyle, text, lang, escaped } = this.token const { codeBlockStyle, text, lang, escaped } = this.token

View File

@ -101,7 +101,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0, top) => {
} }
if (beginRules && pos === 0) { if (beginRules && pos === 0) {
const beginR = ['header', 'hr', 'code_fense', 'display_math'] const beginR = ['header', 'hr', 'code_fense', 'display_math', 'multiple_math']
for (const ruleName of beginR) { for (const ruleName of beginR) {
const to = beginRules[ruleName].exec(src) const to = beginRules[ruleName].exec(src)
@ -488,6 +488,7 @@ export const generator = tokens => {
result += token.escapeCharacter result += token.escapeCharacter
break break
case 'tail_header': case 'tail_header':
case 'multiple_math':
result += token.marker result += token.marker
break break
case 'hard_line_break': case 'hard_line_break':

View File

@ -9,6 +9,7 @@ class StateRender {
constructor (eventCenter) { constructor (eventCenter) {
this.eventCenter = eventCenter this.eventCenter = eventCenter
this.loadImageMap = new Map() this.loadImageMap = new Map()
this.loadMathMap = new Map()
this.tokenCache = new Map() this.tokenCache = new Map()
this.container = null this.container = null
} }

View File

@ -24,6 +24,9 @@ export default function renderContainerBlock (block, cursor, activeBlocks, match
if (block.functionType === 'html') { // HTML Block if (block.functionType === 'html') { // HTML Block
Object.assign(data.dataset, { role: block.functionType.toUpperCase() }) Object.assign(data.dataset, { role: block.functionType.toUpperCase() })
} }
if (block.functionType === 'multiplemath') {
selector += `.${CLASS_OR_ID['AG_MULTIPLE_MATH_BLOCK']}`
}
} }
// hanle list block // hanle list block
if (/ul|ol/.test(block.type) && block.listType) { if (/ul|ol/.test(block.type) && block.listType) {
@ -72,9 +75,11 @@ export default function renderContainerBlock (block, cursor, activeBlocks, match
if (block.type === 'ol') { if (block.type === 'ol') {
Object.assign(data.attrs, { start: block.start }) Object.assign(data.attrs, { start: block.start })
} }
if (block.type === 'pre' && block.functionType === 'frontmatter') { if (block.type === 'pre' && /frontmatter|multiplemath/.test(block.functionType)) {
Object.assign(data.dataset, { role: 'YAML' }) const role = block.functionType === 'frontmatter' ? 'YAML' : 'MATH'
selector += `.${CLASS_OR_ID['AG_FRONT_MATTER']}` const className = block.functionType === 'frontmatter' ? CLASS_OR_ID['AG_FRONT_MATTER'] : CLASS_OR_ID['AG_MULTIPLE_MATH']
Object.assign(data.dataset, { role })
selector += `.${className}`
} }
return h(selector, data, block.children.map(child => this.renderBlock(child, cursor, activeBlocks, matches, useCache))) return h(selector, data, block.children.map(child => this.renderBlock(child, cursor, activeBlocks, matches, useCache)))

View File

@ -1,3 +1,4 @@
import katex from 'katex'
import { CLASS_OR_ID, DEVICE_MEMORY } from '../../../config' import { CLASS_OR_ID, DEVICE_MEMORY } from '../../../config'
import { tokenizer } from '../../parse' import { tokenizer } from '../../parse'
import { snakeToCamel } from '../../../utils' import { snakeToCamel } from '../../../utils'
@ -9,13 +10,26 @@ const PRE_BLOCK_HASH = {
'frontmatter': `.${CLASS_OR_ID['AG_FRONT_MATTER']}` 'frontmatter': `.${CLASS_OR_ID['AG_FRONT_MATTER']}`
} }
export default function renderLeafBlock (block, cursor, activeBlocks, matches, useCache = false) { export default function renderLeafBlock (block, cursor, activeBlocks, matches, useCache = false) {
const { loadMathMap } = this
let selector = this.getSelector(block, cursor, activeBlocks) let selector = this.getSelector(block, cursor, activeBlocks)
// highlight search key in block // highlight search key in block
const highlights = matches.filter(m => m.key === block.key) const highlights = matches.filter(m => m.key === block.key)
const { text, type, headingStyle, align, htmlContent, icon, checked, key, lang, functionType, codeBlockStyle } = block const {
text,
type,
headingStyle,
align,
htmlContent,
icon,
checked,
key,
lang,
functionType,
codeBlockStyle,
math,
editable
} = block
const data = { const data = {
attrs: {}, attrs: {},
dataset: {} dataset: {}
@ -33,16 +47,41 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
children = tokens.reduce((acc, token) => [...acc, ...this[snakeToCamel(token.type)](h, cursor, block, token)], []) children = tokens.reduce((acc, token) => [...acc, ...this[snakeToCamel(token.type)](h, cursor, block, token)], [])
} }
if (editable === false) {
Object.assign(data.attrs, {
contenteditable: 'false'
})
}
if (/th|td/.test(type) && align) { if (/th|td/.test(type) && align) {
Object.assign(data.attrs, { Object.assign(data.attrs, {
style: `text-align:${align}` style: `text-align:${align}`
}) })
} else if (type === 'div' && htmlContent !== undefined) { } else if (type === 'div') {
selector += `.${CLASS_OR_ID['AG_HTML_PREVIEW']}` if (typeof htmlContent === 'string') {
Object.assign(data.attrs, { selector += `.${CLASS_OR_ID['AG_HTML_PREVIEW']}`
contenteditable: 'false' children = htmlToVNode(htmlContent)
}) } else if (typeof math === 'string') {
children = htmlToVNode(htmlContent) const key = `${math}_display_math`
selector += `.${CLASS_OR_ID['AG_MATH_PREVIEW']}`
if (math === '') {
children = '< Empty Mathematical Formula >'
selector += `.${CLASS_OR_ID['AG_MATH_EMPTY']}`
} else if (loadMathMap.has(key)) {
children = loadMathMap.get(key)
} else {
try {
const html = katex.renderToString(math, {
displayMode: true
})
children = htmlToVNode(html)
loadMathMap.set(key, children)
} catch (err) {
children = '< Invalid Mathematical Formula >'
selector += `.${CLASS_OR_ID['AG_MATH_ERROR']}`
}
}
}
} else if (type === 'svg' && icon) { } else if (type === 'svg' && icon) {
selector += '.icon' selector += '.icon'
Object.assign(data.attrs, { Object.assign(data.attrs, {
@ -98,12 +137,17 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
}) })
} }
if (functionType !== 'frontmatter') { if (/code|html/.test(functionType)) {
// do not set it to '' (empty string) // do not set it to '' (empty string)
children = [] children = []
} }
} else if (type === 'span' && functionType === 'frontmatter') { } else if (type === 'span' && /frontmatter|multiplemath/.test(functionType)) {
selector += `.${CLASS_OR_ID['AG_FRONT_MATTER_LINE']}` if (functionType === 'frontmatter') {
selector += `.${CLASS_OR_ID['AG_FRONT_MATTER_LINE']}`
}
if (functionType === 'multiplemath') {
selector += `.${CLASS_OR_ID['AG_MULTIPLE_MATH_LINE']}`
}
children = text children = text
} }

View File

@ -1,4 +1,8 @@
import katex from 'katex'
import { CLASS_OR_ID } from '../../../config' import { CLASS_OR_ID } from '../../../config'
import { htmlToVNode } from '../snabbdom'
import 'katex/dist/katex.min.css'
export default function displayMath (h, cursor, block, token, outerClass) { export default function displayMath (h, cursor, block, token, outerClass) {
const className = this.getClassName(outerClass, block, token, cursor) const className = this.getClassName(outerClass, block, token, cursor)
@ -11,14 +15,34 @@ export default function displayMath (h, cursor, block, token, outerClass) {
const { content: math, type } = token const { content: math, type } = token
const { loadMathMap } = this
const displayMode = type === 'display_math'
const key = `${math}_${type}`
let mathVnode = null
let previewSelector = `span.${CLASS_OR_ID['AG_MATH_RENDER']}`
if (loadMathMap.has(key)) {
mathVnode = loadMathMap.get(key)
} else {
try {
const html = katex.renderToString(math, {
displayMode
})
mathVnode = htmlToVNode(html)
loadMathMap.set(key, mathVnode)
} catch (err) {
mathVnode = '< Invalid Mathematical Formula >'
previewSelector += `.${CLASS_OR_ID['AG_MATH_ERROR']}`
}
}
return [ return [
h(`span.${className}.${CLASS_OR_ID['AG_MATH_MARKER']}`, startMarker), h(`span.${className}.${CLASS_OR_ID['AG_MATH_MARKER']}`, startMarker),
h(`span.${className}.${CLASS_OR_ID['AG_MATH']}`, [ h(`span.${className}.${CLASS_OR_ID['AG_MATH']}`, [
h(`span.${CLASS_OR_ID['AG_MATH_TEXT']}`, content), h(`span.${CLASS_OR_ID['AG_MATH_TEXT']}`, content),
h(`span.${CLASS_OR_ID['AG_MATH_RENDER']}`, { h(previewSelector, {
dataset: { math, type },
attrs: { contenteditable: 'false' } attrs: { contenteditable: 'false' }
}, 'Loading') }, mathVnode)
]), ]),
h(`span.${className}.${CLASS_OR_ID['AG_MATH_MARKER']}`, endMarker) h(`span.${className}.${CLASS_OR_ID['AG_MATH_MARKER']}`, endMarker)
] ]

View File

@ -23,6 +23,7 @@ import del from './del'
import em from './em' import em from './em'
import strong from './strong' import strong from './strong'
import htmlEscape from './htmlEscape' import htmlEscape from './htmlEscape'
import multipleMath from './multipleMath'
export default { export default {
backlashInToken, backlashInToken,
@ -49,5 +50,6 @@ export default {
del, del,
em, em,
strong, strong,
htmlEscape htmlEscape,
multipleMath
} }

View File

@ -0,0 +1,9 @@
import { CLASS_OR_ID } from '../../../config'
export default function multipleMath (h, cursor, block, token, outerClass) {
const { start, end } = token.range
const content = this.highlight(h, block, start, end, token)
return [
h(`span.${CLASS_OR_ID['AG_GRAY']}.${CLASS_OR_ID['AG_REMOVE']}`, content)
]
}

View File

@ -5,7 +5,8 @@ export const beginRules = {
'hr': /^(\*{3,}$|^\-{3,}$|^\_{3,}$)/, 'hr': /^(\*{3,}$|^\-{3,}$|^\_{3,}$)/,
'code_fense': /^(`{3,})([^`]*)$/, 'code_fense': /^(`{3,})([^`]*)$/,
'header': /(^\s{0,3}#{1,6}(\s{1,}|$))/, 'header': /(^\s{0,3}#{1,6}(\s{1,}|$))/,
'display_math': /^(\$\$)([^\$]*?[^\$\\])(\\*)\1$/ 'display_math': /^(\$\$)([^\$]*?[^\$\\])(\\*)\1$/,
'multiple_math': /^(\$\$)$/
} }
export const inlineRules = { export const inlineRules = {

View File

@ -55,6 +55,8 @@ class ExportMarkdown {
result.push(this.normalizeTable(table, indent)) result.push(this.normalizeTable(table, indent))
} else if (block.functionType === 'html') { } else if (block.functionType === 'html') {
result.push(this.normalizeHTML(block, indent)) result.push(this.normalizeHTML(block, indent))
} else if (block.functionType === 'multiplemath') {
result.push(this.normalizeMultipleMath(block, indent))
} }
break break
@ -159,6 +161,16 @@ class ExportMarkdown {
return result.join('') return result.join('')
} }
normalizeMultipleMath (block, /* figure */ indent) {
const result = []
result.push('$$\n')
for (const line of block.children[0].children) {
result.push(`${line.text}\n`)
}
result.push('$$\n')
return result.join('')
}
normalizeCodeBlock (block, indent) { normalizeCodeBlock (block, indent) {
const result = [] const result = []
const textList = block.text.split(LINE_BREAKS) const textList = block.text.split(LINE_BREAKS)

View File

@ -65,12 +65,15 @@ const importRegister = ContentState => {
return { lang, codeBlockStyle } return { lang, codeBlockStyle }
} }
const isFrontMatter = node => { const getPreFunctionType = node => {
let type = 'code'
const classAttr = node.attrs.filter(attr => attr.name === 'class')[0] const classAttr = node.attrs.filter(attr => attr.name === 'class')[0]
if (classAttr && classAttr.value) { if (classAttr && classAttr.value) {
return /front-matter/.test(classAttr.value) const { value } = classAttr
if (/front-matter/.test(value)) type = 'frontmatter'
if (/multiple-math/.test(value)) type = 'multiplemath'
} }
return false return type
} }
const getRowColumnCount = childNodes => { const getRowColumnCount = childNodes => {
@ -219,17 +222,20 @@ const importRegister = ContentState => {
break break
case 'pre': case 'pre':
const frontMatter = isFrontMatter(child) const functionType = getPreFunctionType(child)
if (frontMatter) { if (functionType === 'frontmatter') {
value = child.childNodes[0].value value = child.childNodes[0].value
block = this.createBlock('pre') block = this.createBlock('pre')
const lines = value.replace(/^\s+/, '').split(LINE_BREAKS_REG).map(line => this.createBlock('span', line)) const lines = value.replace(/^\s+/, '').split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
for (const line of lines) { for (const line of lines) {
line.functionType = 'frontmatter' line.functionType = functionType
this.appendChild(block, line) this.appendChild(block, line)
} }
block.functionType = 'frontmatter' block.functionType = functionType
} else { } else if (functionType === 'multiplemath') {
value = child.childNodes[0].value
block = this.createMathBlock(value)
} else if (functionType === 'code') {
const codeNode = child.childNodes[0] const codeNode = child.childNodes[0]
const { lang, codeBlockStyle } = getLangAndType(codeNode) const { lang, codeBlockStyle } = getLangAndType(codeNode)
value = codeNode.childNodes[0].value value = codeNode.childNodes[0].value
@ -343,19 +349,29 @@ const importRegister = ContentState => {
// set cursor // set cursor
const travel = blocks => { const travel = blocks => {
for (const block of blocks) { for (const block of blocks) {
const { key, text, children } = block const { key, text, children, editable, type, functionType } = block
if (text) { if (text) {
const offset = text.indexOf(CURSOR_DNA) const offset = text.indexOf(CURSOR_DNA)
if (offset > -1) { if (offset > -1) {
block.text = text.substring(0, offset) + text.substring(offset + CURSOR_DNA.length) block.text = text.substring(0, offset) + text.substring(offset + CURSOR_DNA.length)
this.cursor = { if (editable) {
start: { key, offset }, this.cursor = {
end: { key, offset } start: { key, offset },
end: { key, offset }
}
// handle cursor in Math block, need to remove `CURSOR_DNA` in preview block
if (type === 'span' && functionType === 'multiplemath') {
const mathPreview = this.getNextSibling(this.getParent(block))
const { math } = mathPreview
const offset = math.indexOf(CURSOR_DNA)
if (offset > -1) {
mathPreview.math = math.substring(0, offset) + math.substring(offset + CURSOR_DNA.length)
}
}
return
} }
return
} }
} } else if (children.length) {
if (children.length) {
travel(children) travel(children)
} }
} }

View File

@ -15,6 +15,16 @@ export const usePluginAddRules = turndownService => {
} }
}) })
// handle multiple lines math
turndownService.addRule('multiplemath', {
filter (node, options) {
return node.nodeName === 'PRE' && node.classList.contains('multiple-math')
},
replacement (content, node, options) {
return `$$\n${content}\n$$`
}
})
// handle `soft line break` and `hard line break` // handle `soft line break` and `hard line break`
// add `LINE_BREAK` to the end of soft line break and hard line break. // add `LINE_BREAK` to the end of soft line break and hard line break.
turndownService.addRule('lineBreak', { turndownService.addRule('lineBreak', {

View File

@ -68,6 +68,14 @@ const setCheckedMenuItem = affiliation => {
return item.id === 'frontMatterMenuItem' return item.id === 'frontMatterMenuItem'
} else if (b.functionType === 'code') { } else if (b.functionType === 'code') {
return item.id === 'codeFencesMenuItem' return item.id === 'codeFencesMenuItem'
} else if (b.functionType === 'html') {
return false
} else if (b.functionType === 'multiplemath') {
return item.id === 'mathBlockMenuItem'
}
} else if (b.type === 'figure' && b.functionType) {
if (b.functionType === 'table') {
return item.id === 'tableMenuItem'
} }
} else { } else {
return b.type === MENU_ID_MAP[item.id] return b.type === MENU_ID_MAP[item.id]
@ -90,10 +98,15 @@ ipcMain.on('AGANI::selection-change', (e, { start, end, affiliation }) => {
setCheckedMenuItem(affiliation) setCheckedMenuItem(affiliation)
// handle disable // handle disable
setParagraphMenuItemStatus(true) setParagraphMenuItemStatus(true)
if ( if (
(/th|td/.test(start.type) && /th|td/.test(end.type)) || (/th|td/.test(start.type) && /th|td/.test(end.type)) ||
(start.type === 'span' && start.block.functionType === 'frontmatter') || (start.type === 'span' && start.block.functionType === 'frontmatter') ||
(end.type === 'span' && end.block.functionType === 'frontmatter') (end.type === 'span' && end.block.functionType === 'frontmatter') ||
(start.type === 'span' && start.block.functionType === 'multiplemath') ||
(end.type === 'span' && end.block.functionType === 'multiplemath') ||
(start.type === 'pre' && start.block.functionType === 'html') ||
(end.type === 'pre' && end.block.functionType === 'html')
) { ) {
setParagraphMenuItemStatus(false) setParagraphMenuItemStatus(false)
} else if (start.key !== end.key) { } else if (start.key !== end.key) {

View File

@ -7,9 +7,6 @@
/* eslint-disable */ /* eslint-disable */
// Set environment for development
process.env.NODE_ENV = 'development'
// Install `electron-debug` with `devtron` // Install `electron-debug` with `devtron`
require('electron-debug')({ showDevTools: false }) require('electron-debug')({ showDevTools: false })

View File

@ -93,6 +93,14 @@ export default {
click (menuItem, browserWindow) { click (menuItem, browserWindow) {
actions.paragraph(browserWindow, 'blockquote') actions.paragraph(browserWindow, 'blockquote')
} }
}, {
id: 'mathBlockMenuItem',
label: 'Math Block',
type: 'checkbox',
accelerator: 'Alt+CmdOrCtrl+M',
click (menuItem, browserWindow) {
actions.paragraph(browserWindow, 'mathblock')
}
}, { }, {
type: 'separator' type: 'separator'
}, { }, {

View File

@ -67,9 +67,9 @@
const STANDAR_Y = 320 const STANDAR_Y = 320
const PARAGRAPH_CMD = [ const PARAGRAPH_CMD = [
'ul-bullet', 'ul-task', 'ol-order', 'pre', 'blockquote', 'heading 1', 'heading 2', 'heading 3', 'ul-bullet', 'ul-task', 'ol-order', 'pre', 'blockquote', 'mathblock', 'heading 1', 'heading 2',
'heading 4', 'heading 5', 'heading 6', 'upgrade heading', 'degrade heading', 'paragraph', 'hr', 'heading 3', 'heading 4', 'heading 5', 'heading 6', 'upgrade heading', 'degrade heading',
'loose-list-item', 'front-matter' 'paragraph', 'hr', 'loose-list-item', 'front-matter'
] ]
export default { export default {

View File

@ -109,8 +109,8 @@ export const adjustCursor = (cursor, preline, line, nextline) => {
} }
} }
// Need to adjust the cursor when cursor in the first or last line of code block. // Need to adjust the cursor when cursor in the first or last line of code/math block.
if (/```[\S]*/.test(line)) { if (/```[\S]*/.test(line) || /^\$\$$/.test(line)) {
if (typeof nextline === 'string' && /\S/.test(nextline)) { if (typeof nextline === 'string' && /\S/.test(nextline)) {
newCursor.line += 1 newCursor.line += 1
newCursor.ch = 0 newCursor.ch = 0

View File

@ -372,10 +372,30 @@ code {
} }
#ag-editor-id pre.ag-html-block { #ag-editor-id pre.ag-html-block {
padding: .4rem 1rem; padding: 0 .5rem;
margin-top: 0; margin-top: 0;
} }
#ag-editor-id pre.ag-front-matter {
background: transparent;
border-bottom: 1px dashed #efefef;
}
#ag-editor-id pre.ag-multiple-math {
background: transparent;
border: 1px solid #909399;
}
#ag-editor-id span.ag-math-text,
#ag-editor-id pre.ag-multiple-math span.ag-multiple-math-line {
color: lightsalmon;
}
#ag-editor-id div.ag-math-preview {
background: #303133;
border-color: #333;
}
#ag-editor-id pre.ag-code-block, #ag-editor-id pre.ag-code-block,
#ag-editor-id pre.ag-html-block { #ag-editor-id pre.ag-html-block {
font-size: 90%; font-size: 90%;

View File

@ -48,7 +48,7 @@ body {
} }
.ag-gray { .ag-gray {
color: #E4E7ED; color: #C0C4CC;
text-decoration: none; text-decoration: none;
} }
@ -317,12 +317,13 @@ tt {
/* custom add */ /* custom add */
code { code {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
border: none; border: none;
padding: 2px 4px; color: #24292e;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
} }
@ -355,12 +356,12 @@ code {
#ag-editor-id pre.ag-html-block { #ag-editor-id pre.ag-html-block {
background: transparent; background: transparent;
padding: .4rem 1rem; padding: 0 .5rem;
margin-top: 0; margin-top: 0;
} }
#ag-editor-id pre.ag-active.ag-html-block { #ag-editor-id pre.ag-active.ag-html-block {
background: #F2F6FC; background: #f6f8fa;
} }
p:not(.ag-active)[data-role="hr"]::before { p:not(.ag-active)[data-role="hr"]::before {