mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 01:53:11 +08:00
List item marker (#290)
* add bullet list marker to preference file * list marker * update change log * update change log * code style and remove debug codes
This commit is contained in:
parent
2b05619ac2
commit
30caf53d08
3
.github/CHANGELOG.md
vendored
3
.github/CHANGELOG.md
vendored
@ -1,4 +1,4 @@
|
|||||||
### 0.11.33
|
### 0.11.35
|
||||||
|
|
||||||
**:cactus:Feature**
|
**:cactus:Feature**
|
||||||
|
|
||||||
@ -9,6 +9,7 @@
|
|||||||
- feature: Click filename to `rename` or `save` in title bar(**macOS ONLY**).
|
- feature: Click filename to `rename` or `save` in title bar(**macOS ONLY**).
|
||||||
- feature: Support YAML Front Matter
|
- feature: Support YAML Front Matter
|
||||||
- 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.
|
||||||
|
|
||||||
**:butterfly:Optimization**
|
**:butterfly:Optimization**
|
||||||
|
|
||||||
|
@ -162,9 +162,9 @@ export const htmlBeautifyConfig = {
|
|||||||
|
|
||||||
export const CURSOR_DNA = getLongUniqueId()
|
export const CURSOR_DNA = getLongUniqueId()
|
||||||
|
|
||||||
export const turndownConfig = {
|
export const DEFAULT_TURNDOWN_CONFIG = {
|
||||||
headingStyle: 'atx', // setext or atx
|
headingStyle: 'atx', // setext or atx
|
||||||
bulletListMarker: '*', // -, +, or *
|
bulletListMarker: '-', // -, +, or *
|
||||||
codeBlockStyle: 'fenced', // fenced or indented
|
codeBlockStyle: 'fenced', // fenced or indented
|
||||||
fence: '```', // ``` or ~~~
|
fence: '```', // ``` or ~~~
|
||||||
emDelimiter: '*', // _ or *
|
emDelimiter: '*', // _ or *
|
||||||
|
@ -101,6 +101,9 @@ const enterCtrl = ContentState => {
|
|||||||
} else {
|
} else {
|
||||||
newBlock = this.createBlockLi()
|
newBlock = this.createBlockLi()
|
||||||
newBlock.listItemType = parent.listItemType
|
newBlock.listItemType = parent.listItemType
|
||||||
|
if (parent.listItemType === 'bullet') {
|
||||||
|
newBlock.bulletListItemMarker = parent.bulletListItemMarker
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newBlock.isLooseListItem = parent.isLooseListItem
|
newBlock.isLooseListItem = parent.isLooseListItem
|
||||||
this.insertAfter(newBlock, parent)
|
this.insertAfter(newBlock, parent)
|
||||||
@ -310,6 +313,9 @@ const enterCtrl = ContentState => {
|
|||||||
newBlock = this.chopBlockByCursor(block.children[0], start.key, start.offset)
|
newBlock = this.chopBlockByCursor(block.children[0], start.key, start.offset)
|
||||||
newBlock = this.createBlockLi(newBlock)
|
newBlock = this.createBlockLi(newBlock)
|
||||||
newBlock.listItemType = block.listItemType
|
newBlock.listItemType = block.listItemType
|
||||||
|
if (block.listItemType === 'bullet') {
|
||||||
|
newBlock.bulletListItemMarker = block.bulletListItemMarker
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newBlock.isLooseListItem = block.isLooseListItem
|
newBlock.isLooseListItem = block.isLooseListItem
|
||||||
}
|
}
|
||||||
@ -326,6 +332,9 @@ const enterCtrl = ContentState => {
|
|||||||
} else {
|
} else {
|
||||||
newBlock = this.createBlockLi()
|
newBlock = this.createBlockLi()
|
||||||
newBlock.listItemType = block.listItemType
|
newBlock.listItemType = block.listItemType
|
||||||
|
if (block.listItemType === 'bullet') {
|
||||||
|
newBlock.bulletListItemMarker = block.bulletListItemMarker
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newBlock.isLooseListItem = block.isLooseListItem
|
newBlock.isLooseListItem = block.isLooseListItem
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HAS_TEXT_BLOCK_REG } from '../config'
|
import { HAS_TEXT_BLOCK_REG, DEFAULT_TURNDOWN_CONFIG } from '../config'
|
||||||
import { setCursorAtLastLine } from '../codeMirror'
|
import { setCursorAtLastLine } from '../codeMirror'
|
||||||
import { getUniqueId } from '../utils'
|
import { getUniqueId } from '../utils'
|
||||||
import selection from '../selection'
|
import selection from '../selection'
|
||||||
@ -42,7 +42,7 @@ const prototypes = [
|
|||||||
|
|
||||||
class ContentState {
|
class ContentState {
|
||||||
constructor (options) {
|
constructor (options) {
|
||||||
const { eventCenter } = options
|
const { eventCenter, bulletListMarker } = options
|
||||||
Object.assign(this, options)
|
Object.assign(this, options)
|
||||||
// Use to cache the keys which you don't want to remove.
|
// Use to cache the keys which you don't want to remove.
|
||||||
this.exemption = new Set()
|
this.exemption = new Set()
|
||||||
@ -55,6 +55,7 @@ class ContentState {
|
|||||||
this.prevCursor = null
|
this.prevCursor = null
|
||||||
this.historyTimer = null
|
this.historyTimer = null
|
||||||
this.history = new History(this)
|
this.history = new History(this)
|
||||||
|
this.turndownConfig = Object.assign(DEFAULT_TURNDOWN_CONFIG, { bulletListMarker })
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +118,12 @@ const updateCtrl = ContentState => {
|
|||||||
newBlock.listItemType = type
|
newBlock.listItemType = type
|
||||||
newBlock.isLooseListItem = preferLooseListItem
|
newBlock.isLooseListItem = preferLooseListItem
|
||||||
|
|
||||||
|
if (type === 'task' || type === 'bullet') {
|
||||||
|
const { bulletListMarker } = this
|
||||||
|
const bulletListItemMarker = marker ? marker.charAt(0) : bulletListMarker
|
||||||
|
newBlock.bulletListItemMarker = bulletListItemMarker
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
preSibling && preSibling.listType === type && this.checkSameLooseType(preSibling, preferLooseListItem) &&
|
preSibling && preSibling.listType === type && this.checkSameLooseType(preSibling, preferLooseListItem) &&
|
||||||
nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)
|
nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)
|
||||||
|
@ -21,14 +21,21 @@ class Aganippe {
|
|||||||
constructor (container, options) {
|
constructor (container, options) {
|
||||||
const {
|
const {
|
||||||
focusMode = false, theme = 'light', markdown = '', preferLooseListItem = true,
|
focusMode = false, theme = 'light', markdown = '', preferLooseListItem = true,
|
||||||
autoPairBracket = true, autoPairMarkdownSyntax = true, autoPairQuote = true
|
autoPairBracket = true, autoPairMarkdownSyntax = true, autoPairQuote = true, bulletListMarker = '-'
|
||||||
} = options
|
} = options
|
||||||
this.container = container
|
this.container = container
|
||||||
const eventCenter = this.eventCenter = new EventCenter()
|
const eventCenter = this.eventCenter = new EventCenter()
|
||||||
const floatBox = this.floatBox = new FloatBox(eventCenter)
|
const floatBox = this.floatBox = new FloatBox(eventCenter)
|
||||||
const tablePicker = this.tablePicker = new TablePicker(eventCenter)
|
const tablePicker = this.tablePicker = new TablePicker(eventCenter)
|
||||||
this.contentState = new ContentState({
|
this.contentState = new ContentState({
|
||||||
eventCenter, floatBox, tablePicker, preferLooseListItem, autoPairBracket, autoPairMarkdownSyntax, autoPairQuote
|
eventCenter,
|
||||||
|
floatBox,
|
||||||
|
tablePicker,
|
||||||
|
preferLooseListItem,
|
||||||
|
autoPairBracket,
|
||||||
|
autoPairMarkdownSyntax,
|
||||||
|
autoPairQuote,
|
||||||
|
bulletListMarker
|
||||||
})
|
})
|
||||||
this.emoji = new Emoji() // emoji instance: has search(text) clear() methods.
|
this.emoji = new Emoji() // emoji instance: has search(text) clear() methods.
|
||||||
this.focusMode = focusMode
|
this.focusMode = focusMode
|
||||||
@ -359,7 +366,7 @@ class Aganippe {
|
|||||||
eventCenter.dispatch('selectionChange', selectionChanges)
|
eventCenter.dispatch('selectionChange', selectionChanges)
|
||||||
eventCenter.dispatch('selectionFormats', formats)
|
eventCenter.dispatch('selectionFormats', formats)
|
||||||
this.dispatchChange()
|
this.dispatchChange()
|
||||||
}, 1000)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,8 +297,7 @@ Lexer.prototype.token = function(src, top, bq) {
|
|||||||
// list
|
// list
|
||||||
if (cap = this.rules.tasklist.exec(src) || this.rules.orderlist.exec(src) || this.rules.bulletlist.exec(src)) {
|
if (cap = this.rules.tasklist.exec(src) || this.rules.orderlist.exec(src) || this.rules.bulletlist.exec(src)) {
|
||||||
src = src.substring(cap[0].length);
|
src = src.substring(cap[0].length);
|
||||||
bull = cap[2];
|
bull = cap[2]
|
||||||
|
|
||||||
const ordered = bull.length > 1 && /\d/.test(bull)
|
const ordered = bull.length > 1 && /\d/.test(bull)
|
||||||
|
|
||||||
this.tokens.push({
|
this.tokens.push({
|
||||||
@ -387,6 +386,7 @@ Lexer.prototype.token = function(src, top, bq) {
|
|||||||
this.tokens.push({
|
this.tokens.push({
|
||||||
checked: checked,
|
checked: checked,
|
||||||
listItemType: bull.length > 1 ? (/\d/.test(bull) ? 'order' : 'task') : 'bullet',
|
listItemType: bull.length > 1 ? (/\d/.test(bull) ? 'order' : 'task') : 'bullet',
|
||||||
|
bulletListItemMarker: /\d/.test(bull) ? '' : bull.charAt(0),
|
||||||
type: loose ?
|
type: loose ?
|
||||||
'loose_item_start' :
|
'loose_item_start' :
|
||||||
'list_item_start'
|
'list_item_start'
|
||||||
@ -875,31 +875,31 @@ Renderer.prototype.list = function(body, ordered, start, taskList) {
|
|||||||
return '<' + type + classes + startatt + '>\n' + body + '</' + type + '>\n'
|
return '<' + type + classes + startatt + '>\n' + body + '</' + type + '>\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer.prototype.listitem = function(text, checked, listItemType, loose) {
|
Renderer.prototype.listitem = function(text, checked, listItemType, bulletListItemMarker, loose) {
|
||||||
var classes;
|
let classes
|
||||||
switch (listItemType) {
|
switch (listItemType) {
|
||||||
case 'order':
|
case 'order':
|
||||||
classes = ' class="order-list-item';
|
classes = ' class="order-list-item'
|
||||||
break;
|
break;
|
||||||
case 'task':
|
case 'task':
|
||||||
classes = ' class="task-list-item';
|
classes = ' class="task-list-item'
|
||||||
break;
|
break;
|
||||||
case 'bullet':
|
case 'bullet':
|
||||||
classes = ' class="bullet-list-item';
|
classes = ' class="bullet-list-item'
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new
|
throw new
|
||||||
Error('Invalid state');
|
Error('Invalid state')
|
||||||
}
|
}
|
||||||
|
|
||||||
// "tight-list-item" is only used to remove <p> padding
|
// "tight-list-item" is only used to remove <p> padding
|
||||||
classes += loose ? ` ${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}"` : ` ${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}"`;
|
classes += loose ? ` ${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}"` : ` ${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}"`;
|
||||||
|
|
||||||
if (checked === undefined) {
|
if (checked === undefined) {
|
||||||
return '<li ' + classes + '>' + text + '</li>\n';
|
return '<li ' + classes + ' data-marker="' + bulletListItemMarker + '">' + text + '</li>\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<li ' + classes + '>' +
|
return '<li ' + classes + ' data-marker="' + bulletListItemMarker + '">' +
|
||||||
'<input type="checkbox" class="task-list-item-checkbox"' +
|
'<input type="checkbox" class="task-list-item-checkbox"' +
|
||||||
(checked ? ' checked' : '') +
|
(checked ? ' checked' : '') +
|
||||||
'> ' +
|
'> ' +
|
||||||
@ -1152,29 +1152,27 @@ Parser.prototype.tok = function() {
|
|||||||
}
|
}
|
||||||
case 'list_item_start':
|
case 'list_item_start':
|
||||||
{
|
{
|
||||||
var body = '',
|
let body = ''
|
||||||
checked = this.token.checked,
|
const { checked, listItemType, bulletListItemMarker } = this.token
|
||||||
listItemType = this.token.listItemType;
|
|
||||||
|
|
||||||
while (this.next().type !== 'list_item_end') {
|
while (this.next().type !== 'list_item_end') {
|
||||||
body += this.token.type === 'text' ?
|
body += this.token.type === 'text' ?
|
||||||
this.parseText() :
|
this.parseText() :
|
||||||
this.tok();
|
this.tok()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.renderer.listitem(body, checked, listItemType, false);
|
return this.renderer.listitem(body, checked, listItemType, bulletListItemMarker, false)
|
||||||
}
|
}
|
||||||
case 'loose_item_start':
|
case 'loose_item_start':
|
||||||
{
|
{
|
||||||
var body = '',
|
let body = ''
|
||||||
checked = this.token.checked,
|
const { checked, listItemType, bulletListItemMarker } = this.token
|
||||||
listItemType = this.token.listItemType;
|
|
||||||
|
|
||||||
while (this.next().type !== 'list_item_end') {
|
while (this.next().type !== 'list_item_end') {
|
||||||
body += this.tok();
|
body += this.tok()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.renderer.listitem(body, checked, listItemType, true);
|
return this.renderer.listitem(body, checked, listItemType, bulletListItemMarker, true)
|
||||||
}
|
}
|
||||||
case 'html':
|
case 'html':
|
||||||
{
|
{
|
||||||
|
@ -64,6 +64,9 @@ export default function renderContainerBlock (block, cursor, activeBlocks, match
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if (block.bulletListItemMarker) {
|
||||||
|
Object.assign(data.dataset, { marker: block.bulletListItemMarker })
|
||||||
|
}
|
||||||
selector += block.isLooseListItem ? `.${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}` : `.${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}`
|
selector += block.isLooseListItem ? `.${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}` : `.${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}`
|
||||||
}
|
}
|
||||||
if (block.type === 'ol') {
|
if (block.type === 'ol') {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Before you edit or update codes in this file, make sure you have read the
|
* Hi contributors!
|
||||||
|
*
|
||||||
|
* Before you edit or update codes in this file,
|
||||||
|
* make sure you have read this bellow:
|
||||||
* Commonmark Spec: https://spec.commonmark.org/0.28/
|
* Commonmark Spec: https://spec.commonmark.org/0.28/
|
||||||
* and GitHub Flavored Markdown Spec: https://github.github.com/gfm/
|
* and GitHub Flavored Markdown Spec: https://github.github.com/gfm/
|
||||||
* The output markdown needs to obey the standards of the two Spec.
|
* The output markdown needs to obey the standards of the two Spec.
|
||||||
@ -245,11 +248,12 @@ class ExportMarkdown {
|
|||||||
normalizeListItem (block, indent) {
|
normalizeListItem (block, indent) {
|
||||||
const result = []
|
const result = []
|
||||||
const listInfo = this.listType[this.listType.length - 1]
|
const listInfo = this.listType[this.listType.length - 1]
|
||||||
let { children } = block
|
let { children, bulletListItemMarker } = block
|
||||||
let itemMarker
|
let itemMarker
|
||||||
|
|
||||||
if (listInfo.type === 'ul') {
|
if (listInfo.type === 'ul') {
|
||||||
itemMarker = '- '
|
// console.log(block)
|
||||||
|
itemMarker = bulletListItemMarker ? `${bulletListItemMarker} ` : '- '
|
||||||
if (block.listItemType === 'task') {
|
if (block.listItemType === 'task') {
|
||||||
const firstChild = children[0]
|
const firstChild = children[0]
|
||||||
itemMarker += firstChild.checked ? '[x] ' : '[ ] '
|
itemMarker += firstChild.checked ? '[x] ' : '[ ] '
|
||||||
|
@ -4,53 +4,15 @@
|
|||||||
* Both of them add a p block in li block, use the CSS style to distinguish loose and tight.
|
* Both of them add a p block in li block, use the CSS style to distinguish loose and tight.
|
||||||
*/
|
*/
|
||||||
import parse5 from 'parse5'
|
import parse5 from 'parse5'
|
||||||
import TurndownService from 'turndown'
|
|
||||||
import marked from '../parser/marked'
|
import marked from '../parser/marked'
|
||||||
import ExportMarkdown from './exportMarkdown'
|
import ExportMarkdown from './exportMarkdown'
|
||||||
|
import TurndownService, { usePluginAddRules } from './turndownService'
|
||||||
|
|
||||||
// To be disabled rules when parse markdown, Because content state don't need to parse inline rules
|
// To be disabled rules when parse markdown, Because content state don't need to parse inline rules
|
||||||
import { turndownConfig, CLASS_OR_ID, CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7, LINE_BREAK } from '../config'
|
import { CLASS_OR_ID, CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7 } from '../config'
|
||||||
|
|
||||||
const turndownPluginGfm = require('turndown-plugin-gfm')
|
|
||||||
|
|
||||||
const LINE_BREAKS_REG = /\n/
|
const LINE_BREAKS_REG = /\n/
|
||||||
|
|
||||||
// turn html to markdown
|
|
||||||
const turndownService = new TurndownService(turndownConfig)
|
|
||||||
const gfm = turndownPluginGfm.gfm
|
|
||||||
// Use the gfm plugin
|
|
||||||
turndownService.use(gfm)
|
|
||||||
// because the strikethrough rule in gfm is single `~`, So need rewrite the strikethrough rule.
|
|
||||||
turndownService.addRule('strikethrough', {
|
|
||||||
filter: ['del', 's', 'strike'],
|
|
||||||
replacement (content) {
|
|
||||||
return '~~' + content + '~~'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// handle `soft line break` and `hard line break`
|
|
||||||
// add `LINE_BREAK` to the end of soft line break and hard line break.
|
|
||||||
turndownService.addRule('lineBreak', {
|
|
||||||
filter (node, options) {
|
|
||||||
return node.nodeName === 'SPAN' && node.classList.contains(CLASS_OR_ID['AG_LINE']) && node.nextElementSibling
|
|
||||||
},
|
|
||||||
replacement (content, node, options) {
|
|
||||||
return content + LINE_BREAK
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// remove `\` in text when paste
|
|
||||||
turndownService.addRule('normalText', {
|
|
||||||
filter (node, options) {
|
|
||||||
return (node.nodeName === 'SPAN' &&
|
|
||||||
node.classList.contains(CLASS_OR_ID['AG_EMOJI_MARKED_TEXT'])) ||
|
|
||||||
node.classList.contains('plain-text')
|
|
||||||
},
|
|
||||||
replacement (content, node, options) {
|
|
||||||
return content.replace(/\\(?!\\)/g, '')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const checkIsHTML = value => {
|
const checkIsHTML = value => {
|
||||||
const trimedValue = value.trim()
|
const trimedValue = value.trim()
|
||||||
const match = /^<([a-zA-Z\d-]+)(?=\s|>).*>/.exec(trimedValue)
|
const match = /^<([a-zA-Z\d-]+)(?=\s|>).*>/.exec(trimedValue)
|
||||||
@ -215,9 +177,16 @@ const importRegister = ContentState => {
|
|||||||
case 'li':
|
case 'li':
|
||||||
const isTask = child.attrs.some(attr => attr.name === 'class' && attr.value.includes('task-list-item'))
|
const isTask = child.attrs.some(attr => attr.name === 'class' && attr.value.includes('task-list-item'))
|
||||||
const isLoose = child.attrs.some(attr => attr.name === 'class' && attr.value.includes(CLASS_OR_ID['AG_LOOSE_LIST_ITEM']))
|
const isLoose = child.attrs.some(attr => attr.name === 'class' && attr.value.includes(CLASS_OR_ID['AG_LOOSE_LIST_ITEM']))
|
||||||
|
|
||||||
block = this.createBlock('li')
|
block = this.createBlock('li')
|
||||||
block.listItemType = parent.nodeName === 'ul' ? (isTask ? 'task' : 'bullet') : 'order'
|
block.listItemType = parent.type === 'ul' ? (isTask ? 'task' : 'bullet') : 'order'
|
||||||
block.isLooseListItem = isLoose
|
block.isLooseListItem = isLoose
|
||||||
|
|
||||||
|
if (/task|bullet/.test(block.listItemType)) {
|
||||||
|
const bulletListItemMarker = child.attrs.find(attr => attr.name === 'data-marker').value
|
||||||
|
if (bulletListItemMarker) block.bulletListItemMarker = bulletListItemMarker
|
||||||
|
}
|
||||||
|
|
||||||
this.appendChild(parent, block)
|
this.appendChild(parent, block)
|
||||||
travel(block, child.childNodes)
|
travel(block, child.childNodes)
|
||||||
break
|
break
|
||||||
@ -331,6 +300,10 @@ const importRegister = ContentState => {
|
|||||||
}
|
}
|
||||||
// transform `paste's text/html data` to content state blocks.
|
// transform `paste's text/html data` to content state blocks.
|
||||||
ContentState.prototype.html2State = function (html) {
|
ContentState.prototype.html2State = function (html) {
|
||||||
|
// turn html to markdown
|
||||||
|
const { turndownConfig } = this
|
||||||
|
const turndownService = new TurndownService(turndownConfig)
|
||||||
|
usePluginAddRules(turndownService)
|
||||||
// remove double `\\` in Math but I dont know why there are two '\' when paste. @jocs
|
// remove double `\\` in Math but I dont know why there are two '\' when paste. @jocs
|
||||||
const markdown = turndownService.turndown(html).replace(/(\\)\\/g, '$1')
|
const markdown = turndownService.turndown(html).replace(/(\\)\\/g, '$1')
|
||||||
return this.getStateFragment(markdown)
|
return this.getStateFragment(markdown)
|
||||||
|
42
src/editor/utils/turndownService.js
Normal file
42
src/editor/utils/turndownService.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import TurndownService from 'turndown'
|
||||||
|
import { CLASS_OR_ID, LINE_BREAK } from '../config'
|
||||||
|
|
||||||
|
const turndownPluginGfm = require('turndown-plugin-gfm')
|
||||||
|
|
||||||
|
export const usePluginAddRules = turndownService => {
|
||||||
|
// Use the gfm plugin
|
||||||
|
const { gfm } = turndownPluginGfm
|
||||||
|
turndownService.use(gfm)
|
||||||
|
// because the strikethrough rule in gfm is single `~`, So need rewrite the strikethrough rule.
|
||||||
|
turndownService.addRule('strikethrough', {
|
||||||
|
filter: ['del', 's', 'strike'],
|
||||||
|
replacement (content) {
|
||||||
|
return '~~' + content + '~~'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// handle `soft line break` and `hard line break`
|
||||||
|
// add `LINE_BREAK` to the end of soft line break and hard line break.
|
||||||
|
turndownService.addRule('lineBreak', {
|
||||||
|
filter (node, options) {
|
||||||
|
return node.nodeName === 'SPAN' && node.classList.contains(CLASS_OR_ID['AG_LINE']) && node.nextElementSibling
|
||||||
|
},
|
||||||
|
replacement (content, node, options) {
|
||||||
|
return content + LINE_BREAK
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove `\` in text when paste
|
||||||
|
turndownService.addRule('normalText', {
|
||||||
|
filter (node, options) {
|
||||||
|
return (node.nodeName === 'SPAN' &&
|
||||||
|
node.classList.contains(CLASS_OR_ID['AG_EMOJI_MARKED_TEXT'])) ||
|
||||||
|
node.classList.contains('plain-text')
|
||||||
|
},
|
||||||
|
replacement (content, node, options) {
|
||||||
|
return content.replace(/\\(?!\\)/g, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TurndownService
|
@ -100,7 +100,7 @@
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
'preferLooseListItem', 'autoPairBracket', 'autoPairMarkdownSyntax', 'autoPairQuote'
|
'preferLooseListItem', 'autoPairBracket', 'autoPairMarkdownSyntax', 'autoPairQuote', 'bulletListItemMarker'
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@ -156,12 +156,26 @@
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const ele = this.$refs.editor
|
const ele = this.$refs.editor
|
||||||
const {
|
const {
|
||||||
theme, focus: focusMode, markdown, preferLooseListItem, typewriter,
|
theme,
|
||||||
autoPairBracket, autoPairMarkdownSyntax, autoPairQuote
|
focus: focusMode,
|
||||||
|
markdown,
|
||||||
|
preferLooseListItem,
|
||||||
|
typewriter,
|
||||||
|
autoPairBracket,
|
||||||
|
autoPairMarkdownSyntax,
|
||||||
|
autoPairQuote,
|
||||||
|
bulletListMarker
|
||||||
} = this
|
} = this
|
||||||
|
|
||||||
const { container } = this.editor = new Aganippe(ele, {
|
const { container } = this.editor = new Aganippe(ele, {
|
||||||
theme, focusMode, markdown, preferLooseListItem, autoPairBracket, autoPairMarkdownSyntax, autoPairQuote
|
theme,
|
||||||
|
focusMode,
|
||||||
|
markdown,
|
||||||
|
preferLooseListItem,
|
||||||
|
autoPairBracket,
|
||||||
|
autoPairMarkdownSyntax,
|
||||||
|
autoPairQuote,
|
||||||
|
bulletListMarker
|
||||||
})
|
})
|
||||||
|
|
||||||
if (typewriter) {
|
if (typewriter) {
|
||||||
|
@ -19,6 +19,7 @@ const state = {
|
|||||||
darkColor: 'rgb(217, 217, 217)', // color in dark theme
|
darkColor: 'rgb(217, 217, 217)', // color in dark theme
|
||||||
autoSave: false,
|
autoSave: false,
|
||||||
preferLooseListItem: true, // prefer loose or tight list items
|
preferLooseListItem: true, // prefer loose or tight list items
|
||||||
|
bulletListMarker: '-',
|
||||||
autoPairBracket: true,
|
autoPairBracket: true,
|
||||||
autoPairMarkdownSyntax: true,
|
autoPairMarkdownSyntax: true,
|
||||||
autoPairQuote: true,
|
autoPairQuote: true,
|
||||||
@ -123,7 +124,6 @@ const actions = {
|
|||||||
ipcRenderer.send('AGANI::ask-for-user-preference')
|
ipcRenderer.send('AGANI::ask-for-user-preference')
|
||||||
ipcRenderer.on('AGANI::user-preference', (e, preference) => {
|
ipcRenderer.on('AGANI::user-preference', (e, preference) => {
|
||||||
const { autoSave } = preference
|
const { autoSave } = preference
|
||||||
|
|
||||||
commit('SET_USER_PREFERENCE', preference)
|
commit('SET_USER_PREFERENCE', preference)
|
||||||
|
|
||||||
// handle autoSave
|
// handle autoSave
|
||||||
|
@ -8,6 +8,8 @@ Edit and save to update preferences. You can only change the JSON below!
|
|||||||
|
|
||||||
- **endOfLine**: *String* `lf`, `crlf` or `default`
|
- **endOfLine**: *String* `lf`, `crlf` or `default`
|
||||||
|
|
||||||
|
- **bulletListMarker**: *String* `+`,`-` or `*`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"fontSize": "16px",
|
"fontSize": "16px",
|
||||||
@ -19,6 +21,7 @@ Edit and save to update preferences. You can only change the JSON below!
|
|||||||
"autoSave": false,
|
"autoSave": false,
|
||||||
"aidou": false,
|
"aidou": false,
|
||||||
"preferLooseListItem": true,
|
"preferLooseListItem": true,
|
||||||
|
"bulletListMarker": "-",
|
||||||
"autoPairBracket": true,
|
"autoPairBracket": true,
|
||||||
"autoPairMarkdownSyntax": true,
|
"autoPairMarkdownSyntax": true,
|
||||||
"autoPairQuote": true,
|
"autoPairQuote": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user