mirror of
https://github.com/marktext/marktext.git
synced 2025-05-01 16:21:51 +08:00
add list indentation settings (#900)
This commit is contained in:
parent
2e50b62d99
commit
1c4ec7cae4
@ -8,4 +8,3 @@ end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,7 +1,8 @@
|
||||
{
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.trimAutoWhitespace": true,
|
||||
// Issues with unit test files
|
||||
//"editor.trimAutoWhitespace": false,
|
||||
"files.eol": "\n",
|
||||
"files.insertFinalNewline": true,
|
||||
|
||||
|
@ -10,6 +10,10 @@
|
||||
- **codeFontFamily**: The code block font family name.
|
||||
- **lineHeight**: The line height of the editor.
|
||||
- **tabSize**: The number of spaces a tab is equal to.
|
||||
- **listIndentation**: The list indentation of list items (`"dfm"`, `"tab"` or number `1-4`)
|
||||
- `tab`: Indent subsequent paragraphs by one tab.
|
||||
- `dfm`: Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab, we are using 4 spaces (used by Bitbucket and Daring Fireball Markdown Spec).
|
||||
- `number`: Dynamic indent subsequent paragraphs by the given number (1-4) plus list marker width (default).
|
||||
- **autoPairBracket**: If `true` the editor automatically closes brackets.
|
||||
- **autoPairMarkdownSyntax**: If `true` the editor automatically closes inline markdown like `*` or `_`.
|
||||
- **autoPairQuote**: If `true` the editor automatically closes quotes (`'` and `"`).
|
||||
|
@ -48,7 +48,6 @@ class Preference {
|
||||
this.validateSettings(userSetting)
|
||||
} else {
|
||||
userSetting = this.loadJson(userDataPath)
|
||||
this.validateSettings(userSetting)
|
||||
|
||||
// Update outdated settings
|
||||
const requiresUpdate = !hasSameKeys(defaultSettings, userSetting)
|
||||
@ -65,8 +64,11 @@ class Preference {
|
||||
userSetting[key] = defaultSettings[key]
|
||||
}
|
||||
}
|
||||
this.validateSettings(userSetting)
|
||||
this.writeJson(userSetting, false)
|
||||
.catch(log)
|
||||
} else {
|
||||
this.validateSettings(userSetting)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +77,6 @@ class Preference {
|
||||
userSetting = defaultSettings
|
||||
this.validateSettings(userSetting)
|
||||
}
|
||||
|
||||
this.cache = userSetting
|
||||
}
|
||||
|
||||
@ -104,7 +105,7 @@ class Preference {
|
||||
writeJson (json, async = true) {
|
||||
const { userDataPath } = this
|
||||
return new Promise((resolve, reject) => {
|
||||
const content = fs.readFileSync(userDataPath, 'utf-8')
|
||||
const content = fs.readFileSync(this.staticPath, 'utf-8')
|
||||
const tokens = content.split('```')
|
||||
const newContent = tokens[0] +
|
||||
'```json\n' +
|
||||
@ -139,14 +140,35 @@ class Preference {
|
||||
brokenSettings = true
|
||||
settings.theme = 'light'
|
||||
}
|
||||
|
||||
if (!settings.bulletListMarker ||
|
||||
(settings.bulletListMarker && !/^(?:\+|-|\*)$/.test(settings.bulletListMarker))) {
|
||||
brokenSettings = true
|
||||
settings.bulletListMarker = '-'
|
||||
}
|
||||
|
||||
if (!settings.titleBarStyle || !/^(?:native|csd|custom)$/.test(settings.titleBarStyle)) {
|
||||
settings.titleBarStyle = 'csd'
|
||||
}
|
||||
|
||||
if (!settings.tabSize || typeof settings.tabSize !== 'number') {
|
||||
settings.tabSize = 4
|
||||
} else if (settings.tabSize < 1) {
|
||||
settings.tabSize = 1
|
||||
} else if (settings.tabSize > 4) {
|
||||
settings.tabSize = 4
|
||||
}
|
||||
|
||||
if (!settings.listIndentation) {
|
||||
settings.listIndentation = 1
|
||||
} else if (typeof settings.listIndentation === 'number') {
|
||||
if (settings.listIndentation < 1 || settings.listIndentation > 4) {
|
||||
settings.listIndentation = 1
|
||||
}
|
||||
} else if (settings.listIndentation !== 'tab' && settings.listIndentation !== 'dfm') {
|
||||
settings.listIndentation = 1
|
||||
}
|
||||
|
||||
if (brokenSettings) {
|
||||
log('Broken settings detected; fallback to default value(s).')
|
||||
}
|
||||
|
@ -236,6 +236,8 @@ export const MUYA_DEFAULT_OPTION = {
|
||||
bulletListMarker: '-',
|
||||
orderListMarker: '.',
|
||||
tabSize: 4,
|
||||
// bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation
|
||||
listIndentation: 1,
|
||||
sequenceTheme: 'hand', // hand or simple
|
||||
mermaidTheme: 'forest', // dark or forest
|
||||
hideQuickInsertHint: false
|
||||
|
@ -155,7 +155,8 @@ const copyCutCtrl = ContentState => {
|
||||
case 'copyTable': {
|
||||
const table = this.getTableBlock()
|
||||
if (!table) return
|
||||
const markdown = new ExportMarkdown([ table ]).generate()
|
||||
const listIndentation = this.listIndentation
|
||||
const markdown = new ExportMarkdown([ table ], listIndentation).generate()
|
||||
event.clipboardData.setData('text/html', '')
|
||||
event.clipboardData.setData('text/plain', markdown)
|
||||
break
|
||||
|
@ -251,7 +251,7 @@ const paragraphCtrl = ContentState => {
|
||||
this.appendChild(startBlock, codeBlock)
|
||||
const { key } = inputBlock
|
||||
const offset = 0
|
||||
|
||||
|
||||
this.cursor = {
|
||||
start: { key, offset },
|
||||
end: { key, offset }
|
||||
@ -270,7 +270,8 @@ const paragraphCtrl = ContentState => {
|
||||
const codeBlock = this.createBlock('code')
|
||||
preBlock.functionType = 'fencecode'
|
||||
preBlock.lang = codeBlock.lang = ''
|
||||
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1)).generate()
|
||||
const listIndentation = this.listIndentation
|
||||
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1), listIndentation).generate()
|
||||
|
||||
markdown.split(LINE_BREAKS_REG).forEach(text => {
|
||||
const codeLine = this.createBlock('span', text)
|
||||
|
@ -59,7 +59,8 @@ class Muya {
|
||||
|
||||
getMarkdown () {
|
||||
const blocks = this.contentState.getBlocks()
|
||||
return new ExportMarkdown(blocks).generate()
|
||||
const listIndentation = this.contentState.listIndentation
|
||||
return new ExportMarkdown(blocks, listIndentation).generate()
|
||||
}
|
||||
|
||||
getHistory () {
|
||||
@ -141,11 +142,23 @@ class Muya {
|
||||
tabSize = 4
|
||||
} else if (tabSize < 1) {
|
||||
tabSize = 1
|
||||
} else if (tabSize > 4) {
|
||||
tabSize = 4
|
||||
}
|
||||
|
||||
this.contentState.tabSize = tabSize
|
||||
}
|
||||
|
||||
setListIndentation (listIndentation) {
|
||||
if (typeof listIndentation === 'number') {
|
||||
if (listIndentation < 1 || listIndentation > 4) {
|
||||
listIndentation = 1
|
||||
}
|
||||
} else if (listIndentation !== 'tab' && listIndentation !== 'dfm') {
|
||||
listIndentation = 1
|
||||
}
|
||||
this.contentState.listIndentation = listIndentation
|
||||
}
|
||||
|
||||
updateParagraph (type) {
|
||||
this.contentState.updateParagraph(type)
|
||||
}
|
||||
|
@ -7,14 +7,29 @@
|
||||
* and GitHub Flavored Markdown Spec: https://github.github.com/gfm/
|
||||
* The output markdown needs to obey the standards of the two Spec.
|
||||
*/
|
||||
// const LINE_BREAKS = /\n/
|
||||
|
||||
class ExportMarkdown {
|
||||
constructor (blocks) {
|
||||
constructor (blocks, listIndentation = 1) {
|
||||
this.blocks = blocks
|
||||
this.listType = [] // 'ul' or 'ol'
|
||||
// helper to translate the first tight item in a nested list
|
||||
this.isLooseParentList = true
|
||||
|
||||
// set and validate settings
|
||||
if (listIndentation === 'tab') {
|
||||
this.listIndentation = '\t'
|
||||
this.listIndentationCount = null
|
||||
} else if (listIndentation === 'dfm') {
|
||||
// static 4 spaces
|
||||
this.listIndentation = ' '
|
||||
this.listIndentationCount = null
|
||||
} else if (typeof listIndentation === 'number') {
|
||||
this.listIndentation = null
|
||||
this.listIndentationCount = Math.min(Math.max(listIndentation, 1), 4)
|
||||
} else {
|
||||
this.listIndentation = null
|
||||
this.listIndentationCount = 1
|
||||
}
|
||||
}
|
||||
|
||||
generate () {
|
||||
@ -302,26 +317,45 @@ class ExportMarkdown {
|
||||
normalizeListItem (block, indent) {
|
||||
const result = []
|
||||
const listInfo = this.listType[this.listType.length - 1]
|
||||
const isUnorderedList = listInfo.type === 'ul'
|
||||
let { children, bulletMarkerOrDelimiter } = block
|
||||
let itemMarker
|
||||
|
||||
if (listInfo.type === 'ul') {
|
||||
if (isUnorderedList) {
|
||||
itemMarker = bulletMarkerOrDelimiter ? `${bulletMarkerOrDelimiter} ` : '- '
|
||||
if (block.listItemType === 'task') {
|
||||
const firstChild = children[0]
|
||||
itemMarker += firstChild.checked ? '[x] ' : '[ ] '
|
||||
children = children.slice(1)
|
||||
}
|
||||
} else {
|
||||
const delimiter = bulletMarkerOrDelimiter ? bulletMarkerOrDelimiter : '.'
|
||||
itemMarker = `${listInfo.listCount++}${delimiter} `
|
||||
}
|
||||
|
||||
const newIndent = indent + ' '.repeat(itemMarker.length)
|
||||
// We already added one space to the indentation
|
||||
const listIndent = this.getListIndentation(itemMarker.length - 1)
|
||||
const newIndent = indent + listIndent
|
||||
|
||||
if (isUnorderedList && block.listItemType === 'task') {
|
||||
const firstChild = children[0]
|
||||
itemMarker += firstChild.checked ? '[x] ' : '[ ] '
|
||||
children = children.slice(1)
|
||||
}
|
||||
|
||||
result.push(`${indent}${itemMarker}`)
|
||||
result.push(this.translateBlocks2Markdown(children, newIndent).substring(newIndent.length))
|
||||
return result.join('')
|
||||
}
|
||||
|
||||
getListIndentation (listMarkerWidth) {
|
||||
// listIndentation:
|
||||
// tab: Indent subsequent paragraphs by one tab.
|
||||
// dfm: Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab (used by Bitbucket and Daring Fireball).
|
||||
// number: Dynamic indent subsequent paragraphs by the given number (1-4) plus list marker width.
|
||||
|
||||
if (this.listIndentation) {
|
||||
return this.listIndentation
|
||||
} else if (this.listIndentationCount) {
|
||||
return ' '.repeat(listMarkerWidth + this.listIndentationCount)
|
||||
}
|
||||
return ' '.repeat(listMarkerWidth)
|
||||
}
|
||||
}
|
||||
|
||||
export default ExportMarkdown
|
||||
|
@ -273,7 +273,8 @@ const importRegister = ContentState => {
|
||||
const block = this.getBlock(key)
|
||||
const { text } = block
|
||||
block.text = text.substring(0, offset) + CURSOR_DNA + text.substring(offset)
|
||||
const markdown = new ExportMarkdown(blocks).generate()
|
||||
const listIndentation = this.listIndentation
|
||||
const markdown = new ExportMarkdown(blocks, listIndentation).generate()
|
||||
const cursor = markdown.split('\n').reduce((acc, line, index) => {
|
||||
const ch = line.indexOf(CURSOR_DNA)
|
||||
if (ch > -1) {
|
||||
|
@ -121,6 +121,7 @@
|
||||
'autoPairQuote': state => state.preferences.autoPairQuote,
|
||||
'bulletListMarker': state => state.preferences.bulletListMarker,
|
||||
'tabSize': state => state.preferences.tabSize,
|
||||
'listIndentation': state => state.preferences.listIndentation,
|
||||
'lineHeight': state => state.preferences.lineHeight,
|
||||
'fontSize': state => state.preferences.fontSize,
|
||||
'lightColor': state => state.preferences.lightColor,
|
||||
@ -181,6 +182,12 @@
|
||||
if (value !== oldValue && editor) {
|
||||
editor.setTabSize(value)
|
||||
}
|
||||
},
|
||||
listIndentation: function (value, oldValue) {
|
||||
const { editor } = this
|
||||
if (value !== oldValue && editor) {
|
||||
editor.setListIndentation(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@ -197,6 +204,7 @@
|
||||
autoPairQuote,
|
||||
bulletListMarker,
|
||||
tabSize,
|
||||
listIndentation,
|
||||
hideQuickInsertHint
|
||||
} = this
|
||||
|
||||
@ -218,6 +226,7 @@
|
||||
autoPairQuote,
|
||||
bulletListMarker,
|
||||
tabSize,
|
||||
listIndentation,
|
||||
hideQuickInsertHint
|
||||
})
|
||||
|
||||
|
@ -18,6 +18,8 @@ const state = {
|
||||
autoPairMarkdownSyntax: true,
|
||||
autoPairQuote: true,
|
||||
tabSize: 4,
|
||||
// bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation
|
||||
listIndentation: 1,
|
||||
hideQuickInsertHint: false,
|
||||
titleBarStyle: 'csd',
|
||||
// edit modes (they are not in preference.md, but still put them here)
|
||||
|
@ -8,6 +8,8 @@ Edit and save to update preferences. You can only change the JSON below!
|
||||
|
||||
- **endOfLine**: *String* `lf`, `crlf` or `default`
|
||||
|
||||
- **listIndentation**: `"dfm"`, `"tab"` or number (`1-4`)
|
||||
|
||||
- **bulletListMarker**: *String* `+`,`-` or `*`
|
||||
|
||||
- **textDirection**: *String* `ltr` or `rtl`
|
||||
@ -34,6 +36,7 @@ Edit and save to update preferences. You can only change the JSON below!
|
||||
"autoPairQuote": true,
|
||||
"endOfLine": "default",
|
||||
"tabSize": 4,
|
||||
"listIndentation": 1,
|
||||
"textDirection": "ltr",
|
||||
"titleBarStyle": "csd",
|
||||
"openFilesInNewWindow": true
|
||||
|
2
test/unit/data/.editorconfig
Normal file
2
test/unit/data/.editorconfig
Normal file
@ -0,0 +1,2 @@
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
@ -9,3 +9,9 @@
|
||||
\`\`\`
|
||||
This isn't a code block without language identifier.
|
||||
\`\`\`
|
||||
|
||||
\$ You can also escape math \$.
|
||||
|
||||
\$\$
|
||||
This isn't a math block.
|
||||
\$\$
|
||||
|
@ -165,6 +165,8 @@ Foo
|
||||
- baz
|
||||
```
|
||||
|
||||
Issue `-` is replaced by `- `:
|
||||
|
||||
```
|
||||
- foo
|
||||
-
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Basic Text Formatting
|
||||
|
||||
~~this is strike through text~~
|
||||
|
||||
\~\~and this not\~\~
|
||||
|
223
test/unit/specs/markdown-list-indentation.spec.js
Normal file
223
test/unit/specs/markdown-list-indentation.spec.js
Normal file
@ -0,0 +1,223 @@
|
||||
import ContentState from '../../../src/muya/lib/contentState'
|
||||
import EventCenter from '../../../src/muya/lib/eventHandler/event'
|
||||
import ExportMarkdown from '../../../src/muya/lib/utils/exportMarkdown'
|
||||
import { MUYA_DEFAULT_OPTION } from '../../../src/muya/lib/config'
|
||||
|
||||
const createMuyaContext = listIdentation => {
|
||||
const ctx = {}
|
||||
ctx.options = Object.assign({}, MUYA_DEFAULT_OPTION, { listIdentation })
|
||||
ctx.eventCenter = new EventCenter()
|
||||
ctx.contentState = new ContentState(ctx, ctx.options)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Muya parser (Markdown to HTML to Markdown)
|
||||
//
|
||||
|
||||
const verifyMarkdown = (expectedMarkdown, listIdentation) => {
|
||||
const markdown = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
3. foo
|
||||
3. foo
|
||||
20. foo
|
||||
141. foo
|
||||
1. foo
|
||||
`
|
||||
|
||||
const ctx = createMuyaContext(listIdentation)
|
||||
ctx.contentState.importMarkdown(markdown)
|
||||
|
||||
const blocks = ctx.contentState.getBlocks()
|
||||
const exportedMarkdown = new ExportMarkdown(blocks, listIdentation).generate()
|
||||
expect(exportedMarkdown).to.equal(expectedMarkdown)
|
||||
}
|
||||
|
||||
describe('Muya tab identation', () => {
|
||||
it('Indent by 1 space', () => {
|
||||
const md = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
3. foo
|
||||
3. foo
|
||||
20. foo
|
||||
141. foo
|
||||
1. foo
|
||||
`
|
||||
verifyMarkdown(md, 1)
|
||||
})
|
||||
it('Indent by 2 spaces', () => {
|
||||
const md = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
3. foo
|
||||
3. foo
|
||||
20. foo
|
||||
141. foo
|
||||
1. foo
|
||||
`
|
||||
verifyMarkdown(md, 2)
|
||||
})
|
||||
it('Indent by 3 spaces', () => {
|
||||
const md = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
3. foo
|
||||
3. foo
|
||||
20. foo
|
||||
141. foo
|
||||
1. foo
|
||||
`
|
||||
verifyMarkdown(md, 3)
|
||||
})
|
||||
it('Indent by 4 spaces', () => {
|
||||
const md = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
3. foo
|
||||
3. foo
|
||||
20. foo
|
||||
141. foo
|
||||
1. foo
|
||||
`
|
||||
|
||||
verifyMarkdown(md, 4)
|
||||
})
|
||||
it('Indent by one tab', () => {
|
||||
const md = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
\t- foo
|
||||
\t- foo
|
||||
\t\t- foo
|
||||
\t\t- foo
|
||||
\t\t\t- foo
|
||||
\t- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
\t1. foo
|
||||
\t2. foo
|
||||
\t\t1. foo
|
||||
\t3. foo
|
||||
3. foo
|
||||
\t20. foo
|
||||
\t\t141. foo
|
||||
\t\t\t1. foo
|
||||
`
|
||||
verifyMarkdown(md, "tab")
|
||||
})
|
||||
it('Indent using Daring Fireball Markdown Spec', () => {
|
||||
const md = `start
|
||||
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
- foo
|
||||
|
||||
sep
|
||||
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
2. foo
|
||||
1. foo
|
||||
3. foo
|
||||
3. foo
|
||||
20. foo
|
||||
141. foo
|
||||
1. foo
|
||||
`
|
||||
verifyMarkdown(md, "dfm")
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user