diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 4620dae2..fcc5a86d 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 0.11.33
+### 0.11.35
**:cactus:Feature**
@@ -9,6 +9,7 @@
- feature: Click filename to `rename` or `save` in title bar(**macOS ONLY**).
- feature: Support YAML Front Matter
- feature: Support `setext` heading but the default heading style is `atx`
+- feature: User list item marker setting in preference file.
**:butterfly:Optimization**
diff --git a/src/editor/config.js b/src/editor/config.js
index 20d4f55b..2df8e4d7 100644
--- a/src/editor/config.js
+++ b/src/editor/config.js
@@ -162,9 +162,9 @@ export const htmlBeautifyConfig = {
export const CURSOR_DNA = getLongUniqueId()
-export const turndownConfig = {
+export const DEFAULT_TURNDOWN_CONFIG = {
headingStyle: 'atx', // setext or atx
- bulletListMarker: '*', // -, +, or *
+ bulletListMarker: '-', // -, +, or *
codeBlockStyle: 'fenced', // fenced or indented
fence: '```', // ``` or ~~~
emDelimiter: '*', // _ or *
diff --git a/src/editor/contentState/enterCtrl.js b/src/editor/contentState/enterCtrl.js
index 5e29a356..aab6dd87 100644
--- a/src/editor/contentState/enterCtrl.js
+++ b/src/editor/contentState/enterCtrl.js
@@ -101,6 +101,9 @@ const enterCtrl = ContentState => {
} else {
newBlock = this.createBlockLi()
newBlock.listItemType = parent.listItemType
+ if (parent.listItemType === 'bullet') {
+ newBlock.bulletListItemMarker = parent.bulletListItemMarker
+ }
}
newBlock.isLooseListItem = parent.isLooseListItem
this.insertAfter(newBlock, parent)
@@ -310,6 +313,9 @@ const enterCtrl = ContentState => {
newBlock = this.chopBlockByCursor(block.children[0], start.key, start.offset)
newBlock = this.createBlockLi(newBlock)
newBlock.listItemType = block.listItemType
+ if (block.listItemType === 'bullet') {
+ newBlock.bulletListItemMarker = block.bulletListItemMarker
+ }
}
newBlock.isLooseListItem = block.isLooseListItem
}
@@ -326,6 +332,9 @@ const enterCtrl = ContentState => {
} else {
newBlock = this.createBlockLi()
newBlock.listItemType = block.listItemType
+ if (block.listItemType === 'bullet') {
+ newBlock.bulletListItemMarker = block.bulletListItemMarker
+ }
}
newBlock.isLooseListItem = block.isLooseListItem
} else {
diff --git a/src/editor/contentState/index.js b/src/editor/contentState/index.js
index 18501bbf..f074d08b 100644
--- a/src/editor/contentState/index.js
+++ b/src/editor/contentState/index.js
@@ -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 { getUniqueId } from '../utils'
import selection from '../selection'
@@ -42,7 +42,7 @@ const prototypes = [
class ContentState {
constructor (options) {
- const { eventCenter } = options
+ const { eventCenter, bulletListMarker } = options
Object.assign(this, options)
// Use to cache the keys which you don't want to remove.
this.exemption = new Set()
@@ -55,6 +55,7 @@ class ContentState {
this.prevCursor = null
this.historyTimer = null
this.history = new History(this)
+ this.turndownConfig = Object.assign(DEFAULT_TURNDOWN_CONFIG, { bulletListMarker })
this.init()
}
diff --git a/src/editor/contentState/updateCtrl.js b/src/editor/contentState/updateCtrl.js
index 4effee4f..483b079e 100644
--- a/src/editor/contentState/updateCtrl.js
+++ b/src/editor/contentState/updateCtrl.js
@@ -118,6 +118,12 @@ const updateCtrl = ContentState => {
newBlock.listItemType = type
newBlock.isLooseListItem = preferLooseListItem
+ if (type === 'task' || type === 'bullet') {
+ const { bulletListMarker } = this
+ const bulletListItemMarker = marker ? marker.charAt(0) : bulletListMarker
+ newBlock.bulletListItemMarker = bulletListItemMarker
+ }
+
if (
preSibling && preSibling.listType === type && this.checkSameLooseType(preSibling, preferLooseListItem) &&
nextSibling && nextSibling.listType === type && this.checkSameLooseType(nextSibling, preferLooseListItem)
diff --git a/src/editor/index.js b/src/editor/index.js
index 32e1a334..58823184 100644
--- a/src/editor/index.js
+++ b/src/editor/index.js
@@ -21,14 +21,21 @@ class Aganippe {
constructor (container, options) {
const {
focusMode = false, theme = 'light', markdown = '', preferLooseListItem = true,
- autoPairBracket = true, autoPairMarkdownSyntax = true, autoPairQuote = true
+ autoPairBracket = true, autoPairMarkdownSyntax = true, autoPairQuote = true, bulletListMarker = '-'
} = options
this.container = container
const eventCenter = this.eventCenter = new EventCenter()
const floatBox = this.floatBox = new FloatBox(eventCenter)
const tablePicker = this.tablePicker = new TablePicker(eventCenter)
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.focusMode = focusMode
@@ -359,7 +366,7 @@ class Aganippe {
eventCenter.dispatch('selectionChange', selectionChanges)
eventCenter.dispatch('selectionFormats', formats)
this.dispatchChange()
- }, 1000)
+ })
}
}
diff --git a/src/editor/parser/marked.js b/src/editor/parser/marked.js
index 571cb037..0f22c96b 100644
--- a/src/editor/parser/marked.js
+++ b/src/editor/parser/marked.js
@@ -297,8 +297,7 @@ Lexer.prototype.token = function(src, top, bq) {
// list
if (cap = this.rules.tasklist.exec(src) || this.rules.orderlist.exec(src) || this.rules.bulletlist.exec(src)) {
src = src.substring(cap[0].length);
- bull = cap[2];
-
+ bull = cap[2]
const ordered = bull.length > 1 && /\d/.test(bull)
this.tokens.push({
@@ -387,6 +386,7 @@ Lexer.prototype.token = function(src, top, bq) {
this.tokens.push({
checked: checked,
listItemType: bull.length > 1 ? (/\d/.test(bull) ? 'order' : 'task') : 'bullet',
+ bulletListItemMarker: /\d/.test(bull) ? '' : bull.charAt(0),
type: loose ?
'loose_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'
}
-Renderer.prototype.listitem = function(text, checked, listItemType, loose) {
- var classes;
+Renderer.prototype.listitem = function(text, checked, listItemType, bulletListItemMarker, loose) {
+ let classes
switch (listItemType) {
case 'order':
- classes = ' class="order-list-item';
+ classes = ' class="order-list-item'
break;
case 'task':
- classes = ' class="task-list-item';
+ classes = ' class="task-list-item'
break;
case 'bullet':
- classes = ' class="bullet-list-item';
+ classes = ' class="bullet-list-item'
break;
default:
throw new
- Error('Invalid state');
+ Error('Invalid state')
}
// "tight-list-item" is only used to remove
padding
classes += loose ? ` ${CLASS_OR_ID['AG_LOOSE_LIST_ITEM']}"` : ` ${CLASS_OR_ID['AG_TIGHT_LIST_ITEM']}"`;
if (checked === undefined) {
- return '
' + text + '\n';
+ return '' + text + '\n';
}
- return '' +
+ return '' +
' ' +
@@ -1152,29 +1152,27 @@ Parser.prototype.tok = function() {
}
case 'list_item_start':
{
- var body = '',
- checked = this.token.checked,
- listItemType = this.token.listItemType;
+ let body = ''
+ const { checked, listItemType, bulletListItemMarker } = this.token
while (this.next().type !== 'list_item_end') {
body += this.token.type === 'text' ?
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':
{
- var body = '',
- checked = this.token.checked,
- listItemType = this.token.listItemType;
+ let body = ''
+ const { checked, listItemType, bulletListItemMarker } = this.token
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':
{
diff --git a/src/editor/parser/render/renderBlock/renderContainerBlock.js b/src/editor/parser/render/renderBlock/renderContainerBlock.js
index c788e670..68db6b57 100644
--- a/src/editor/parser/render/renderBlock/renderContainerBlock.js
+++ b/src/editor/parser/render/renderBlock/renderContainerBlock.js
@@ -64,6 +64,9 @@ export default function renderContainerBlock (block, cursor, activeBlocks, match
default:
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']}`
}
if (block.type === 'ol') {
diff --git a/src/editor/utils/exportMarkdown.js b/src/editor/utils/exportMarkdown.js
index ee67e60d..82de4f1f 100644
--- a/src/editor/utils/exportMarkdown.js
+++ b/src/editor/utils/exportMarkdown.js
@@ -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/
* and GitHub Flavored Markdown Spec: https://github.github.com/gfm/
* The output markdown needs to obey the standards of the two Spec.
@@ -245,11 +248,12 @@ class ExportMarkdown {
normalizeListItem (block, indent) {
const result = []
const listInfo = this.listType[this.listType.length - 1]
- let { children } = block
+ let { children, bulletListItemMarker } = block
let itemMarker
if (listInfo.type === 'ul') {
- itemMarker = '- '
+ // console.log(block)
+ itemMarker = bulletListItemMarker ? `${bulletListItemMarker} ` : '- '
if (block.listItemType === 'task') {
const firstChild = children[0]
itemMarker += firstChild.checked ? '[x] ' : '[ ] '
diff --git a/src/editor/utils/importMarkdown.js b/src/editor/utils/importMarkdown.js
index 188c00d4..6d902fad 100644
--- a/src/editor/utils/importMarkdown.js
+++ b/src/editor/utils/importMarkdown.js
@@ -4,53 +4,15 @@
* Both of them add a p block in li block, use the CSS style to distinguish loose and tight.
*/
import parse5 from 'parse5'
-import TurndownService from 'turndown'
import marked from '../parser/marked'
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
-import { turndownConfig, CLASS_OR_ID, CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7, LINE_BREAK } from '../config'
-
-const turndownPluginGfm = require('turndown-plugin-gfm')
+import { CLASS_OR_ID, CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7 } from '../config'
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 trimedValue = value.trim()
const match = /^<([a-zA-Z\d-]+)(?=\s|>).*>/.exec(trimedValue)
@@ -215,9 +177,16 @@ const importRegister = ContentState => {
case 'li':
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']))
+
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
+
+ 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)
travel(block, child.childNodes)
break
@@ -331,6 +300,10 @@ const importRegister = ContentState => {
}
// transform `paste's text/html data` to content state blocks.
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
const markdown = turndownService.turndown(html).replace(/(\\)\\/g, '$1')
return this.getStateFragment(markdown)
diff --git a/src/editor/utils/turndownService.js b/src/editor/utils/turndownService.js
new file mode 100644
index 00000000..ecf72b79
--- /dev/null
+++ b/src/editor/utils/turndownService.js
@@ -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
diff --git a/src/renderer/components/editor.vue b/src/renderer/components/editor.vue
index d5ac28da..e4b58cac 100644
--- a/src/renderer/components/editor.vue
+++ b/src/renderer/components/editor.vue
@@ -100,7 +100,7 @@
},
computed: {
...mapState([
- 'preferLooseListItem', 'autoPairBracket', 'autoPairMarkdownSyntax', 'autoPairQuote'
+ 'preferLooseListItem', 'autoPairBracket', 'autoPairMarkdownSyntax', 'autoPairQuote', 'bulletListItemMarker'
])
},
data () {
@@ -156,12 +156,26 @@
this.$nextTick(() => {
const ele = this.$refs.editor
const {
- theme, focus: focusMode, markdown, preferLooseListItem, typewriter,
- autoPairBracket, autoPairMarkdownSyntax, autoPairQuote
+ theme,
+ focus: focusMode,
+ markdown,
+ preferLooseListItem,
+ typewriter,
+ autoPairBracket,
+ autoPairMarkdownSyntax,
+ autoPairQuote,
+ bulletListMarker
} = this
const { container } = this.editor = new Aganippe(ele, {
- theme, focusMode, markdown, preferLooseListItem, autoPairBracket, autoPairMarkdownSyntax, autoPairQuote
+ theme,
+ focusMode,
+ markdown,
+ preferLooseListItem,
+ autoPairBracket,
+ autoPairMarkdownSyntax,
+ autoPairQuote,
+ bulletListMarker
})
if (typewriter) {
diff --git a/src/renderer/store/editor.js b/src/renderer/store/editor.js
index ceeeff1a..c311a280 100644
--- a/src/renderer/store/editor.js
+++ b/src/renderer/store/editor.js
@@ -19,6 +19,7 @@ const state = {
darkColor: 'rgb(217, 217, 217)', // color in dark theme
autoSave: false,
preferLooseListItem: true, // prefer loose or tight list items
+ bulletListMarker: '-',
autoPairBracket: true,
autoPairMarkdownSyntax: true,
autoPairQuote: true,
@@ -123,7 +124,6 @@ const actions = {
ipcRenderer.send('AGANI::ask-for-user-preference')
ipcRenderer.on('AGANI::user-preference', (e, preference) => {
const { autoSave } = preference
-
commit('SET_USER_PREFERENCE', preference)
// handle autoSave
diff --git a/static/preference.md b/static/preference.md
index 50400778..bc9f1adf 100755
--- a/static/preference.md
+++ b/static/preference.md
@@ -8,6 +8,8 @@ Edit and save to update preferences. You can only change the JSON below!
- **endOfLine**: *String* `lf`, `crlf` or `default`
+- **bulletListMarker**: *String* `+`,`-` or `*`
+
```json
{
"fontSize": "16px",
@@ -19,6 +21,7 @@ Edit and save to update preferences. You can only change the JSON below!
"autoSave": false,
"aidou": false,
"preferLooseListItem": true,
+ "bulletListMarker": "-",
"autoPairBracket": true,
"autoPairMarkdownSyntax": true,
"autoPairQuote": true,