add list indentation settings (#900)

This commit is contained in:
Felix Häusler 2019-04-10 16:43:21 +02:00 committed by Ran Luo
parent 2e50b62d99
commit 1c4ec7cae4
18 changed files with 347 additions and 20 deletions

View File

@ -8,4 +8,3 @@ end_of_line = lf
insert_final_newline = true insert_final_newline = true
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

View File

@ -1,7 +1,8 @@
{ {
"editor.insertSpaces": true, "editor.insertSpaces": true,
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.trimAutoWhitespace": true, // Issues with unit test files
//"editor.trimAutoWhitespace": false,
"files.eol": "\n", "files.eol": "\n",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,

View File

@ -10,6 +10,10 @@
- **codeFontFamily**: The code block font family name. - **codeFontFamily**: The code block font family name.
- **lineHeight**: The line height of the editor. - **lineHeight**: The line height of the editor.
- **tabSize**: The number of spaces a tab is equal to. - **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. - **autoPairBracket**: If `true` the editor automatically closes brackets.
- **autoPairMarkdownSyntax**: If `true` the editor automatically closes inline markdown like `*` or `_`. - **autoPairMarkdownSyntax**: If `true` the editor automatically closes inline markdown like `*` or `_`.
- **autoPairQuote**: If `true` the editor automatically closes quotes (`'` and `"`). - **autoPairQuote**: If `true` the editor automatically closes quotes (`'` and `"`).

View File

@ -48,7 +48,6 @@ class Preference {
this.validateSettings(userSetting) this.validateSettings(userSetting)
} else { } else {
userSetting = this.loadJson(userDataPath) userSetting = this.loadJson(userDataPath)
this.validateSettings(userSetting)
// Update outdated settings // Update outdated settings
const requiresUpdate = !hasSameKeys(defaultSettings, userSetting) const requiresUpdate = !hasSameKeys(defaultSettings, userSetting)
@ -65,8 +64,11 @@ class Preference {
userSetting[key] = defaultSettings[key] userSetting[key] = defaultSettings[key]
} }
} }
this.validateSettings(userSetting)
this.writeJson(userSetting, false) this.writeJson(userSetting, false)
.catch(log) .catch(log)
} else {
this.validateSettings(userSetting)
} }
} }
@ -75,7 +77,6 @@ class Preference {
userSetting = defaultSettings userSetting = defaultSettings
this.validateSettings(userSetting) this.validateSettings(userSetting)
} }
this.cache = userSetting this.cache = userSetting
} }
@ -104,7 +105,7 @@ class Preference {
writeJson (json, async = true) { writeJson (json, async = true) {
const { userDataPath } = this const { userDataPath } = this
return new Promise((resolve, reject) => { 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 tokens = content.split('```')
const newContent = tokens[0] + const newContent = tokens[0] +
'```json\n' + '```json\n' +
@ -139,14 +140,35 @@ class Preference {
brokenSettings = true brokenSettings = true
settings.theme = 'light' settings.theme = 'light'
} }
if (!settings.bulletListMarker || if (!settings.bulletListMarker ||
(settings.bulletListMarker && !/^(?:\+|-|\*)$/.test(settings.bulletListMarker))) { (settings.bulletListMarker && !/^(?:\+|-|\*)$/.test(settings.bulletListMarker))) {
brokenSettings = true brokenSettings = true
settings.bulletListMarker = '-' settings.bulletListMarker = '-'
} }
if (!settings.titleBarStyle || !/^(?:native|csd|custom)$/.test(settings.titleBarStyle)) { if (!settings.titleBarStyle || !/^(?:native|csd|custom)$/.test(settings.titleBarStyle)) {
settings.titleBarStyle = 'csd' 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) { if (brokenSettings) {
log('Broken settings detected; fallback to default value(s).') log('Broken settings detected; fallback to default value(s).')
} }

View File

@ -236,6 +236,8 @@ export const MUYA_DEFAULT_OPTION = {
bulletListMarker: '-', bulletListMarker: '-',
orderListMarker: '.', orderListMarker: '.',
tabSize: 4, tabSize: 4,
// bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation
listIndentation: 1,
sequenceTheme: 'hand', // hand or simple sequenceTheme: 'hand', // hand or simple
mermaidTheme: 'forest', // dark or forest mermaidTheme: 'forest', // dark or forest
hideQuickInsertHint: false hideQuickInsertHint: false

View File

@ -155,7 +155,8 @@ const copyCutCtrl = ContentState => {
case 'copyTable': { case 'copyTable': {
const table = this.getTableBlock() const table = this.getTableBlock()
if (!table) return 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/html', '')
event.clipboardData.setData('text/plain', markdown) event.clipboardData.setData('text/plain', markdown)
break break

View File

@ -251,7 +251,7 @@ const paragraphCtrl = ContentState => {
this.appendChild(startBlock, codeBlock) this.appendChild(startBlock, codeBlock)
const { key } = inputBlock const { key } = inputBlock
const offset = 0 const offset = 0
this.cursor = { this.cursor = {
start: { key, offset }, start: { key, offset },
end: { key, offset } end: { key, offset }
@ -270,7 +270,8 @@ const paragraphCtrl = ContentState => {
const codeBlock = this.createBlock('code') const codeBlock = this.createBlock('code')
preBlock.functionType = 'fencecode' preBlock.functionType = 'fencecode'
preBlock.lang = codeBlock.lang = '' 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 => { markdown.split(LINE_BREAKS_REG).forEach(text => {
const codeLine = this.createBlock('span', text) const codeLine = this.createBlock('span', text)

View File

@ -59,7 +59,8 @@ class Muya {
getMarkdown () { getMarkdown () {
const blocks = this.contentState.getBlocks() const blocks = this.contentState.getBlocks()
return new ExportMarkdown(blocks).generate() const listIndentation = this.contentState.listIndentation
return new ExportMarkdown(blocks, listIndentation).generate()
} }
getHistory () { getHistory () {
@ -141,11 +142,23 @@ class Muya {
tabSize = 4 tabSize = 4
} else if (tabSize < 1) { } else if (tabSize < 1) {
tabSize = 1 tabSize = 1
} else if (tabSize > 4) {
tabSize = 4
} }
this.contentState.tabSize = tabSize 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) { updateParagraph (type) {
this.contentState.updateParagraph(type) this.contentState.updateParagraph(type)
} }

View File

@ -7,14 +7,29 @@
* 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.
*/ */
// const LINE_BREAKS = /\n/
class ExportMarkdown { class ExportMarkdown {
constructor (blocks) { constructor (blocks, listIndentation = 1) {
this.blocks = blocks this.blocks = blocks
this.listType = [] // 'ul' or 'ol' this.listType = [] // 'ul' or 'ol'
// helper to translate the first tight item in a nested list // helper to translate the first tight item in a nested list
this.isLooseParentList = true 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 () { generate () {
@ -302,26 +317,45 @@ 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]
const isUnorderedList = listInfo.type === 'ul'
let { children, bulletMarkerOrDelimiter } = block let { children, bulletMarkerOrDelimiter } = block
let itemMarker let itemMarker
if (listInfo.type === 'ul') { if (isUnorderedList) {
itemMarker = bulletMarkerOrDelimiter ? `${bulletMarkerOrDelimiter} ` : '- ' itemMarker = bulletMarkerOrDelimiter ? `${bulletMarkerOrDelimiter} ` : '- '
if (block.listItemType === 'task') {
const firstChild = children[0]
itemMarker += firstChild.checked ? '[x] ' : '[ ] '
children = children.slice(1)
}
} else { } else {
const delimiter = bulletMarkerOrDelimiter ? bulletMarkerOrDelimiter : '.' const delimiter = bulletMarkerOrDelimiter ? bulletMarkerOrDelimiter : '.'
itemMarker = `${listInfo.listCount++}${delimiter} ` 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(`${indent}${itemMarker}`)
result.push(this.translateBlocks2Markdown(children, newIndent).substring(newIndent.length)) result.push(this.translateBlocks2Markdown(children, newIndent).substring(newIndent.length))
return result.join('') 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 export default ExportMarkdown

View File

@ -273,7 +273,8 @@ const importRegister = ContentState => {
const block = this.getBlock(key) const block = this.getBlock(key)
const { text } = block const { text } = block
block.text = text.substring(0, offset) + CURSOR_DNA + text.substring(offset) 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 cursor = markdown.split('\n').reduce((acc, line, index) => {
const ch = line.indexOf(CURSOR_DNA) const ch = line.indexOf(CURSOR_DNA)
if (ch > -1) { if (ch > -1) {

View File

@ -121,6 +121,7 @@
'autoPairQuote': state => state.preferences.autoPairQuote, 'autoPairQuote': state => state.preferences.autoPairQuote,
'bulletListMarker': state => state.preferences.bulletListMarker, 'bulletListMarker': state => state.preferences.bulletListMarker,
'tabSize': state => state.preferences.tabSize, 'tabSize': state => state.preferences.tabSize,
'listIndentation': state => state.preferences.listIndentation,
'lineHeight': state => state.preferences.lineHeight, 'lineHeight': state => state.preferences.lineHeight,
'fontSize': state => state.preferences.fontSize, 'fontSize': state => state.preferences.fontSize,
'lightColor': state => state.preferences.lightColor, 'lightColor': state => state.preferences.lightColor,
@ -181,6 +182,12 @@
if (value !== oldValue && editor) { if (value !== oldValue && editor) {
editor.setTabSize(value) editor.setTabSize(value)
} }
},
listIndentation: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setListIndentation(value)
}
} }
}, },
created () { created () {
@ -197,6 +204,7 @@
autoPairQuote, autoPairQuote,
bulletListMarker, bulletListMarker,
tabSize, tabSize,
listIndentation,
hideQuickInsertHint hideQuickInsertHint
} = this } = this
@ -218,6 +226,7 @@
autoPairQuote, autoPairQuote,
bulletListMarker, bulletListMarker,
tabSize, tabSize,
listIndentation,
hideQuickInsertHint hideQuickInsertHint
}) })

View File

@ -18,6 +18,8 @@ const state = {
autoPairMarkdownSyntax: true, autoPairMarkdownSyntax: true,
autoPairQuote: true, autoPairQuote: true,
tabSize: 4, tabSize: 4,
// bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation
listIndentation: 1,
hideQuickInsertHint: false, hideQuickInsertHint: false,
titleBarStyle: 'csd', titleBarStyle: 'csd',
// edit modes (they are not in preference.md, but still put them here) // edit modes (they are not in preference.md, but still put them here)

View File

@ -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`
- **listIndentation**: `"dfm"`, `"tab"` or number (`1-4`)
- **bulletListMarker**: *String* `+`,`-` or `*` - **bulletListMarker**: *String* `+`,`-` or `*`
- **textDirection**: *String* `ltr` or `rtl` - **textDirection**: *String* `ltr` or `rtl`
@ -34,6 +36,7 @@ Edit and save to update preferences. You can only change the JSON below!
"autoPairQuote": true, "autoPairQuote": true,
"endOfLine": "default", "endOfLine": "default",
"tabSize": 4, "tabSize": 4,
"listIndentation": 1,
"textDirection": "ltr", "textDirection": "ltr",
"titleBarStyle": "csd", "titleBarStyle": "csd",
"openFilesInNewWindow": true "openFilesInNewWindow": true

View File

@ -0,0 +1,2 @@
[*.md]
trim_trailing_whitespace = false

View File

@ -9,3 +9,9 @@
\`\`\` \`\`\`
This isn't a code block without language identifier. This isn't a code block without language identifier.
\`\`\` \`\`\`
\$ You can also escape math \$.
\$\$
This isn't a math block.
\$\$

View File

@ -165,6 +165,8 @@ Foo
- baz - baz
``` ```
Issue `-` is replaced by `- `:
``` ```
- foo - foo
- -

View File

@ -1,3 +1,5 @@
# Basic Text Formatting # Basic Text Formatting
~~this is strike through text~~ ~~this is strike through text~~
\~\~and this not\~\~

View 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")
})
})