feat: paragraph menu

This commit is contained in:
Jocs 2018-02-17 02:47:46 +08:00
parent 52c9f6dde7
commit fea3afb056
9 changed files with 234 additions and 25 deletions

View File

@ -8,6 +8,7 @@
- [ ] 在通过 Aganippe 打开文件时,通过右键选择软件,但是打开无内容。(严重 bug
- [ ] export html: (3) keyframe 和 font-face 以及 bar-top 的样式都可以删除。(4) 打包后的应用 axios 获取样式有问题。
- [ ] table: 如果 table 在 selection 后面那么删除cell 的时候,会把整个 row 删除了。(小 bug)
- [ ] task list 中 checkbox 无反应
**菜单**

View File

@ -6,7 +6,7 @@ import { generateKeyHash, genUpper2LowerKeyHash } from './utils'
* configs
*/
// export const INLINE_RULES = ['autolink', 'backticks', 'emphasis', 'escape', 'image', 'link', 'strikethrough']
export const PARAGRAPH_TYPES = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ul', 'ol', 'li', 'figure']
export const blockContainerElementNames = [
// elements our editor generates
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ul', 'li', 'ol',

View File

@ -29,9 +29,9 @@ const enterCtrl = ContentState => {
return trBlock
}
ContentState.prototype.createBlockLi = function (text = '') {
ContentState.prototype.createBlockLi = function (text = '', type = 'p') {
const liBlock = this.createBlock('li')
const pBlock = this.createBlock('p', text)
const pBlock = this.createBlock(type, text)
this.appendChild(liBlock, pBlock)
return liBlock
}

View File

@ -135,7 +135,7 @@ class ContentState {
}
return null
}
// return block and its parents
getParents (block) {
const result = []
result.push(block)

View File

@ -1,5 +1,10 @@
import selection from '../selection'
import { PARAGRAPH_TYPES } from '../config'
import ExportMarkdown from '../utils/exportMarkdown'
// get header level
// eg: h1 => 1
// h2 => 2
const getCurrentLevel = type => {
if (/\d/.test(type)) {
return Number(/\d/.exec(type)[0])
@ -11,14 +16,16 @@ const getCurrentLevel = type => {
const paragraphCtrl = ContentState => {
ContentState.prototype.selectionChange = function () {
const { start, end } = selection.getCursorRange()
start.type = this.getBlock(start.key).type
end.type = this.getBlock(end.key).type
const startBlock = this.getBlock(start.key)
const endBlock = this.getBlock(end.key)
const startParents = this.getParents(startBlock)
const endParents = this.getParents(endBlock)
const affiliation = startParents
.filter(p => endParents.includes(p))
.filter(p => PARAGRAPH_TYPES.includes(p.type))
const affiliation = startParents.filter(p => endParents.includes(p))
start.type = startBlock.type
end.type = endBlock.type
return {
start,
@ -27,16 +34,212 @@ const paragraphCtrl = ContentState => {
}
}
ContentState.prototype.updateParagraph = function (paraType) {
const {
start, end
} = selection.getCursorRange()
ContentState.prototype.getCommonParent = function () {
const { start, end, affiliation } = this.selectionChange()
const parent = affiliation.length ? affiliation[0] : null
const startBlock = this.getBlock(start.key)
const endBlock = this.getBlock(end.key)
const startParentKeys = this.getParents(startBlock).map(b => b.key)
const endParentKeys = this.getParents(endBlock).map(b => b.key)
const children = parent ? parent.children : this.blocks
let startIndex
let endIndex
for (const child of children) {
if (startParentKeys.includes(child.key)) {
startIndex = children.indexOf(child)
}
if (endParentKeys.includes(child.key)) {
endIndex = children.indexOf(child)
}
}
return { parent, startIndex, endIndex }
}
ContentState.prototype.handleListMenu = function (paraType) {
const { start, end, affiliation } = this.selectionChange()
const [blockType, listType] = paraType.split('-')
const isListed = affiliation.slice(0, 3).filter(b => /ul|ol/.test(b.type))
if (isListed.length && listType !== isListed[0].listType) {
const listBlock = isListed[0]
// if the old list block is task list, remove checkbox
if (listBlock.listType === 'task') {
const listItems = listBlock.children
listItems.forEach(item => {
const inputBlock = item.children[0]
inputBlock && this.removeBlock(inputBlock)
})
}
listBlock.type = blockType
listBlock.listType = listType
listBlock.children.forEach(b => (b.listItemType = listType))
if (listType === 'order') {
listBlock.start = listBlock.start || 1
}
// if the new block is task list, add checkbox
if (listType === 'task') {
const listItems = listBlock.children
listItems.forEach(item => {
const checkbox = this.createBlock('input')
checkbox.checked = false
this.insertBefore(checkbox, item.children[0])
})
}
} else {
if (start.key === end.key) {
const block = this.getBlock(start.key)
const {
type, text
} = block
if (listType === 'task') {
// 1. first update the block to bullet list
const listItemParagraph = this.updateList(block, 'bullet')
// 2. second update bullet list to task list
setTimeout(() => {
this.updateTaskListItem(listItemParagraph, listType)
this.render()
})
} else {
this.updateList(block, listType)
}
} else {
const { parent, startIndex, endIndex } = this.getCommonParent()
const children = parent ? parent.children : this.blocks
const referBlock = children[endIndex]
const listWrapper = this.createBlock(listType === 'order' ? 'ol' : 'ul')
listWrapper.listType = listType
this.insertAfter(listWrapper, referBlock)
if (listType === 'order') listWrapper.start = 1
let i
let child
const removeCache = []
for (i = startIndex; i <= endIndex; i++) {
child = children[i]
removeCache.push(child)
const listItem = this.createBlock('li')
listItem.listItemType = listType
this.appendChild(listWrapper, listItem)
if (listType === 'task') {
const checkbox = this.createBlock('input')
checkbox.checked = false
this.appendChild(listItem, checkbox)
}
this.appendChild(listItem, child)
}
removeCache.forEach(b => this.removeBlock(b))
}
}
}
ContentState.prototype.handleCodeBlockMenu = function () {
const { start, end, affiliation } = this.selectionChange()
const startBlock = this.getBlock(start.key)
const endBlock = this.getBlock(end.key)
const startParents = this.getParents(startBlock)
const endParents = this.getParents(endBlock)
const hasPreParent = () => {
return startParents.some(b => b.type === 'pre') || endParents.some(b => b.type === 'pre')
}
if (affiliation.length && affiliation[0].type === 'pre') {
const codeBlock = affiliation[0]
delete codeBlock.pos
delete codeBlock.history
delete codeBlock.lang
this.codeBlocks.delete(codeBlock.key)
codeBlock.type = 'p'
const key = codeBlock.key
const offset = codeBlock.text.length
this.cursor = {
start: { key, offset },
end: { key, offset }
}
} else {
if (start.key === end.key) {
startBlock.type = 'pre'
startBlock.history = null
startBlock.lang = ''
} else if (!hasPreParent()) {
const { parent, startIndex, endIndex } = this.getCommonParent()
const children = parent ? parent.children : this.blocks
const referBlock = children[endIndex]
const codeBlock = this.createBlock('pre')
codeBlock.history = null
codeBlock.lang = ''
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1)).generate()
codeBlock.text = markdown
this.insertAfter(codeBlock, referBlock)
let i
const removeCache = []
for (i = startIndex; i <= endIndex; i++) {
const child = children[i]
removeCache.push(child)
}
removeCache.forEach(b => this.removeBlock(b))
const key = codeBlock.key
this.cursor = {
start: { key, offset: 0 },
end: { key, offset: 0 }
}
}
}
}
ContentState.prototype.handleQuoteMenu = function () {
const { start, end, affiliation } = this.selectionChange()
const startBlock = this.getBlock(start.key)
const isBlockQuote = affiliation.slice(0, 2).filter(b => /blockquote/.test(b.type))
if (isBlockQuote.length) {
const quoteBlock = isBlockQuote[0]
console.log(quoteBlock)
const children = quoteBlock.children
for (const child of children) {
this.insertBefore(child, quoteBlock)
}
this.removeBlock(quoteBlock)
} else {
if (start.key === end.key) {
const quoteBlock = this.createBlock('blockquote')
this.insertAfter(quoteBlock, startBlock)
this.removeBlock(startBlock)
this.appendChild(quoteBlock, startBlock)
} else {
const { parent, startIndex, endIndex } = this.getCommonParent()
const children = parent ? parent.children : this.blocks
const referBlock = children[endIndex]
const quoteBlock = this.createBlock('blockquote')
this.insertAfter(quoteBlock, referBlock)
let i
const removeCache = []
for (i = startIndex; i <= endIndex; i++) {
const child = children[i]
removeCache.push(child)
this.appendChild(quoteBlock, child)
}
removeCache.forEach(b => this.removeBlock(b))
}
}
}
ContentState.prototype.updateParagraph = function (paraType) {
const { start, end } = selection.getCursorRange()
const block = this.getBlock(start.key)
const { type, text } = block
switch (paraType) {
case 'ul-bullet':
case 'ul-task':
case 'ol-order': {
this.handleListMenu(paraType)
break
}
case 'pre': {
this.handleCodeBlockMenu()
break
}
case 'blockquote': {
this.handleQuoteMenu()
break
}
case 'heading 1':
case 'heading 2':
case 'heading 3':

View File

@ -78,7 +78,7 @@ const updateCtrl = ContentState => {
return false
}
ContentState.prototype.updateTaskListItem = function (block, type, marker) {
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
const parent = this.getParent(block)
const grandpa = this.getParent(parent)
const checked = /\[x\]\s/i.test(marker) // use `i` flag to ignore upper case or lower case
@ -142,7 +142,7 @@ const updateCtrl = ContentState => {
block.checked = checked
}
ContentState.prototype.updateList = function (block, type, marker) {
ContentState.prototype.updateList = function (block, type, marker = '') {
const parent = this.getParent(block)
const preSibling = this.getPreSibling(block)
const nextSibling = this.getNextSibling(block)
@ -151,7 +151,7 @@ const updateCtrl = ContentState => {
const { start, end } = this.cursor
const startOffset = start.offset
const endOffset = end.offset
const newBlock = this.createBlockLi(newText)
const newBlock = this.createBlockLi(newText, block.type)
newBlock.listItemType = type
if (preSibling && preSibling.listType === type && nextSibling && nextSibling.listType === type) {
@ -178,7 +178,7 @@ const updateCtrl = ContentState => {
block.text = ''
if (wrapperTag === 'ol') {
const start = marker.split('.')[0]
block.start = start
block.start = /^\d+$/.test(start) ? start : 1
}
this.appendChild(block, newBlock)
}
@ -194,6 +194,7 @@ const updateCtrl = ContentState => {
offset: Math.max(0, endOffset - marker.length)
}
}
return newBlock.children[0]
}
ContentState.prototype.updateBlockQuote = function (block) {

View File

@ -16,7 +16,7 @@ const LABEL_MAP = {
'Heading 6': 'h6',
'Table': 'figure',
'Code Fences': 'pre',
'Quote Block': 'quoteblock',
'Quote Block': 'blockquote',
'Order List': 'ol',
'Bullet List': 'ul',
'Task List': 'ul',
@ -70,7 +70,7 @@ ipcMain.on('AGANI::selection-change', (e, { start, end, affiliation }) => {
setCheckedMenuItem(affiliation)
// handle disable
allCtrl(true)
if (/th|td/.test(start.type) || /th|td/.test(end.type)) {
if (/th|td/.test(start.type) && /th|td/.test(end.type)) {
allCtrl(false)
} else if (start.key !== end.key) {
formatMenuItem.submenu.items

View File

@ -72,14 +72,14 @@ export default {
type: 'checkbox',
accelerator: 'Alt+CmdOrCtrl+C',
click (menuItem, browserWindow) {
//
actions.paragraph(browserWindow, 'pre')
}
}, {
label: 'Quote Block',
type: 'checkbox',
accelerator: 'Alt+CmdOrCtrl+Q',
click (menuItem, browserWindow) {
//
actions.paragraph(browserWindow, 'blockquote')
}
}, {
type: 'separator'
@ -88,21 +88,21 @@ export default {
type: 'checkbox',
accelerator: 'Alt+CmdOrCtrl+O',
click (menuItem, browserWindow) {
//
actions.paragraph(browserWindow, 'ol-order')
}
}, {
label: 'Bullet List',
type: 'checkbox',
accelerator: 'Alt+CmdOrCtrl+U',
click (menuItem, browserWindow) {
//
actions.paragraph(browserWindow, 'ul-bullet')
}
}, {
label: 'Task List',
type: 'checkbox',
accelerator: 'Alt+CmdOrCtrl+X',
click (menuItem, browserWindow) {
//
actions.paragraph(browserWindow, 'ul-task')
}
}, {
type: 'separator'

View File

@ -92,12 +92,16 @@
}
},
handleEditParagraph (type) {
console.log(type)
switch (type) {
case 'table':
this.tableChecker = { rows: 2, columns: 2 }
this.dialogTableVisible = true
break
case 'ul-bullet':
case 'ul-task':
case 'ol-order':
case 'pre':
case 'blockquote':
case 'heading 1':
case 'heading 2':
case 'heading 3':