mirror of
https://github.com/marktext/marktext.git
synced 2025-05-04 01:41:39 +08:00
feat: inline formats
This commit is contained in:
parent
fdf0eab97e
commit
d23e4a3f8c
1
TODO.md
1
TODO.md
@ -8,6 +8,7 @@
|
|||||||
- [ ] 在通过 Aganippe 打开文件时,通过右键选择软件,但是打开无内容。(严重 bug)
|
- [ ] 在通过 Aganippe 打开文件时,通过右键选择软件,但是打开无内容。(严重 bug)
|
||||||
- [ ] export html: (3) keyframe 和 font-face 以及 bar-top 的样式都可以删除。(4) 打包后的应用 axios 获取样式有问题。
|
- [ ] export html: (3) keyframe 和 font-face 以及 bar-top 的样式都可以删除。(4) 打包后的应用 axios 获取样式有问题。
|
||||||
- [ ] table: 如果 table 在 selection 后面,那么删除cell 的时候,会把整个 row 删除了。(小 bug)
|
- [ ] table: 如果 table 在 selection 后面,那么删除cell 的时候,会把整个 row 删除了。(小 bug)
|
||||||
|
- [ ] 处理 inline-format 选择中有 table 和 codeblock 的情况
|
||||||
|
|
||||||
**菜单**
|
**菜单**
|
||||||
|
|
||||||
|
248
src/editor/contentState/formatCtrl.js
Normal file
248
src/editor/contentState/formatCtrl.js
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import selection from '../selection'
|
||||||
|
import { tokenizer, generator } from '../parser/parse'
|
||||||
|
|
||||||
|
const FORMAT_TYPES = ['strong', 'em', 'del', 'inline_code', 'link', 'image']
|
||||||
|
|
||||||
|
const getOffset = (offset, { range: { start, end }, type, anchor, title }) => {
|
||||||
|
const dis = offset - start
|
||||||
|
const len = end - start
|
||||||
|
switch (type) {
|
||||||
|
case 'strong':
|
||||||
|
case 'del':
|
||||||
|
case 'em':
|
||||||
|
case 'inline_code': {
|
||||||
|
const MARKER_LEN = (type === 'strong' || type === 'del') ? 2 : 1
|
||||||
|
if (dis < 0) return 0
|
||||||
|
if (dis >= 0 && dis < MARKER_LEN) return -dis
|
||||||
|
if (dis >= MARKER_LEN && dis <= len - MARKER_LEN) return -MARKER_LEN
|
||||||
|
if (dis > len - MARKER_LEN && dis <= len) return len - dis - 2 * MARKER_LEN
|
||||||
|
if (dis > len) return -2 * MARKER_LEN
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'link': {
|
||||||
|
const MARKER_LEN = 1
|
||||||
|
if (dis < MARKER_LEN) return 0
|
||||||
|
if (dis >= MARKER_LEN && dis <= MARKER_LEN + anchor.length) return -1
|
||||||
|
if (dis > MARKER_LEN + anchor.length) return anchor.length - dis
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'image': {
|
||||||
|
const MARKER_LEN = 1
|
||||||
|
if (dis < MARKER_LEN) return 0
|
||||||
|
if (dis >= MARKER_LEN && dis < MARKER_LEN * 2) return -1
|
||||||
|
if (dis >= MARKER_LEN * 2 && dis <= MARKER_LEN * 2 + title.length) return -2
|
||||||
|
if (dis > MARKER_LEN * 2 + title.length) return title.length - dis
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFormat = (token, { start, end }) => {
|
||||||
|
if (start) {
|
||||||
|
const deltaStart = getOffset(start.offset, token)
|
||||||
|
start.delata += deltaStart
|
||||||
|
}
|
||||||
|
if (end) {
|
||||||
|
const delataEnd = getOffset(end.offset, token)
|
||||||
|
end.delata += delataEnd
|
||||||
|
}
|
||||||
|
switch (token.type) {
|
||||||
|
case 'strong':
|
||||||
|
case 'del':
|
||||||
|
case 'em':
|
||||||
|
case 'link':
|
||||||
|
case 'image':
|
||||||
|
const parent = token.parent
|
||||||
|
const index = parent.indexOf(token)
|
||||||
|
parent.splice(index, 1, ...token.children)
|
||||||
|
break
|
||||||
|
case 'inline_code':
|
||||||
|
token.type = 'text'
|
||||||
|
delete token.marker
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addFormat = (type, block, { start, end }) => {
|
||||||
|
console.log(type, block, start, end)
|
||||||
|
const MARKER_MAP = {
|
||||||
|
'em': '*',
|
||||||
|
'inline_code': '`',
|
||||||
|
'strong': '**',
|
||||||
|
'del': '~~'
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 'em':
|
||||||
|
case 'del':
|
||||||
|
case 'inline_code':
|
||||||
|
case 'strong': {
|
||||||
|
const MARKER = MARKER_MAP[type]
|
||||||
|
const oldText = block.text
|
||||||
|
block.text = oldText.substring(0, start.offset) +
|
||||||
|
MARKER + oldText.substring(start.offset, end.offset) +
|
||||||
|
MARKER + oldText.substring(end.offset)
|
||||||
|
start.offset += MARKER.length
|
||||||
|
end.offset += MARKER.length
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'link':
|
||||||
|
case 'image': {
|
||||||
|
const oldText = block.text
|
||||||
|
block.text = oldText.substring(0, start.offset) +
|
||||||
|
(type === 'link' ? '[' : '![') +
|
||||||
|
oldText.substring(start.offset, end.offset) + ']()' +
|
||||||
|
oldText.substring(end.offset)
|
||||||
|
start.offset += type === 'link' ? 1 : 2
|
||||||
|
end.offset += type === 'link' ? 1 : 2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatCtrl = ContentState => {
|
||||||
|
ContentState.prototype.selectionFormats = function ({ start, end } = selection.getCursorRange()) {
|
||||||
|
const startBlock = this.getBlock(start.key)
|
||||||
|
const formats = []
|
||||||
|
const neighbors = []
|
||||||
|
let tokens = []
|
||||||
|
if (start.key === end.key) {
|
||||||
|
const text = startBlock.text
|
||||||
|
tokens = tokenizer(text)
|
||||||
|
;(function iterator (tks) {
|
||||||
|
for (const token of tks) {
|
||||||
|
if (
|
||||||
|
FORMAT_TYPES.includes(token.type) &&
|
||||||
|
start.offset >= token.range.start &&
|
||||||
|
end.offset <= token.range.end
|
||||||
|
) {
|
||||||
|
formats.push(token)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
FORMAT_TYPES.includes(token.type) &&
|
||||||
|
((start.offset >= token.range.start && start.offset <= token.range.end) ||
|
||||||
|
(end.offset >= token.range.start && end.offset <= token.range.end) ||
|
||||||
|
(start.offset <= token.range.start && token.range.end <= end.offset))
|
||||||
|
) {
|
||||||
|
neighbors.push(token)
|
||||||
|
}
|
||||||
|
if (token.children && token.children.length) {
|
||||||
|
iterator(token.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { formats, tokens, neighbors }
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentState.prototype.clearBlockFormat = function (block, { start, end } = selection.getCursorRange(), type) {
|
||||||
|
const { key } = block
|
||||||
|
let tokens
|
||||||
|
let neighbors
|
||||||
|
if (start.key === end.key && start.key === key) {
|
||||||
|
({ tokens, neighbors } = this.selectionFormats({ start, end }))
|
||||||
|
} else if (start.key !== end.key && start.key === key) {
|
||||||
|
({ tokens, neighbors } = this.selectionFormats({ start, end: { key: start.key, offset: block.text.length } }))
|
||||||
|
} else if (start.key !== end.key && end.key === key) {
|
||||||
|
({ tokens, neighbors } = this.selectionFormats({
|
||||||
|
start: {
|
||||||
|
key: end.key,
|
||||||
|
offset: 0
|
||||||
|
},
|
||||||
|
end
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
({ tokens, neighbors } = this.selectionFormats({
|
||||||
|
start: {
|
||||||
|
key,
|
||||||
|
offset: 0
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
key,
|
||||||
|
offset: block.text.length
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
console.log(neighbors)
|
||||||
|
neighbors = type ? neighbors.filter(n => n.type === type) : neighbors
|
||||||
|
|
||||||
|
for (const neighbor of neighbors) {
|
||||||
|
clearFormat(neighbor, { start, end })
|
||||||
|
}
|
||||||
|
start.offset += start.delata
|
||||||
|
end.offset += end.delata
|
||||||
|
block.text = generator(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentState.prototype.format = function (type) {
|
||||||
|
const { start, end } = selection.getCursorRange()
|
||||||
|
const startBlock = this.getBlock(start.key)
|
||||||
|
const endBlock = this.getBlock(end.key)
|
||||||
|
start.delata = end.delata = 0
|
||||||
|
if (start.key === end.key) {
|
||||||
|
const { formats, tokens, neighbors } = this.selectionFormats()
|
||||||
|
const currentFormats = formats.filter(format => format.type === type).reverse()
|
||||||
|
const currentNeightbors = neighbors.filter(format => format.type === type).reverse()
|
||||||
|
// cache delata
|
||||||
|
if (type === 'clear') {
|
||||||
|
for (const neighbor of neighbors) {
|
||||||
|
clearFormat(neighbor, { start, end })
|
||||||
|
}
|
||||||
|
start.offset += start.delata
|
||||||
|
end.offset += end.delata
|
||||||
|
startBlock.text = generator(tokens)
|
||||||
|
} else if (currentFormats.length) {
|
||||||
|
for (const token of currentFormats) {
|
||||||
|
clearFormat(token, { start, end })
|
||||||
|
}
|
||||||
|
start.offset += start.delata
|
||||||
|
end.offset += end.delata
|
||||||
|
startBlock.text = generator(tokens)
|
||||||
|
} else {
|
||||||
|
if (currentNeightbors.length) {
|
||||||
|
for (const neighbor of currentNeightbors) {
|
||||||
|
clearFormat(neighbor, { start, end })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start.offset += start.delata
|
||||||
|
end.offset += end.delata
|
||||||
|
startBlock.text = generator(tokens)
|
||||||
|
addFormat(type, startBlock, { start, end })
|
||||||
|
}
|
||||||
|
this.cursor = { start, end }
|
||||||
|
this.render()
|
||||||
|
} else {
|
||||||
|
let nextBlock = startBlock
|
||||||
|
const formatType = type !== 'clear' ? type : undefined
|
||||||
|
while (nextBlock && nextBlock !== endBlock) {
|
||||||
|
this.clearBlockFormat(nextBlock, { start, end }, formatType)
|
||||||
|
nextBlock = this.findNextBlockInLocation(nextBlock)
|
||||||
|
}
|
||||||
|
this.clearBlockFormat(endBlock, { start, end }, formatType)
|
||||||
|
|
||||||
|
if (type !== 'clear') {
|
||||||
|
addFormat(type, startBlock, {
|
||||||
|
start,
|
||||||
|
end: { offset: startBlock.text.length }
|
||||||
|
})
|
||||||
|
nextBlock = this.findNextBlockInLocation(startBlock)
|
||||||
|
while (nextBlock && nextBlock !== endBlock) {
|
||||||
|
addFormat(type, nextBlock, {
|
||||||
|
start: { offset: 0 },
|
||||||
|
end: { offset: nextBlock.text.length }
|
||||||
|
})
|
||||||
|
nextBlock = this.findNextBlockInLocation(nextBlock)
|
||||||
|
}
|
||||||
|
addFormat(type, endBlock, {
|
||||||
|
start: { offset: 0 },
|
||||||
|
end
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cursor = { start, end }
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default formatCtrl
|
@ -14,6 +14,7 @@ import pasteCtrl from './pasteCtrl'
|
|||||||
import copyCutCtrl from './copyCutCtrl'
|
import copyCutCtrl from './copyCutCtrl'
|
||||||
import paragraphCtrl from './paragraphCtrl'
|
import paragraphCtrl from './paragraphCtrl'
|
||||||
import tabCtrl from './tabCtrl'
|
import tabCtrl from './tabCtrl'
|
||||||
|
import formatCtrl from './formatCtrl'
|
||||||
import importMarkdown from '../utils/importMarkdown'
|
import importMarkdown from '../utils/importMarkdown'
|
||||||
|
|
||||||
const prototypes = [
|
const prototypes = [
|
||||||
@ -29,6 +30,7 @@ const prototypes = [
|
|||||||
copyCutCtrl,
|
copyCutCtrl,
|
||||||
tableBlockCtrl,
|
tableBlockCtrl,
|
||||||
paragraphCtrl,
|
paragraphCtrl,
|
||||||
|
formatCtrl,
|
||||||
importMarkdown
|
importMarkdown
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -88,7 +90,6 @@ class ContentState {
|
|||||||
this.stateRender.render(blocks, cursor, activeBlocks)
|
this.stateRender.render(blocks, cursor, activeBlocks)
|
||||||
this.setCursor()
|
this.setCursor()
|
||||||
this.pre2CodeMirror()
|
this.pre2CodeMirror()
|
||||||
console.log('render')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createBlock (type = 'p', text = '') {
|
createBlock (type = 'p', text = '') {
|
||||||
|
@ -303,7 +303,9 @@ class Aganippe {
|
|||||||
}
|
}
|
||||||
if (event.type === 'click' || event.type === 'keyup') {
|
if (event.type === 'click' || event.type === 'keyup') {
|
||||||
const selectionChanges = this.contentState.selectionChange()
|
const selectionChanges = this.contentState.selectionChange()
|
||||||
|
const { formats } = this.contentState.selectionFormats()
|
||||||
eventCenter.dispatch('selectionChange', selectionChanges)
|
eventCenter.dispatch('selectionChange', selectionChanges)
|
||||||
|
eventCenter.dispatch('selectionFormats', formats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,6 +381,10 @@ class Aganippe {
|
|||||||
this.contentState.updateParagraph(type)
|
this.contentState.updateParagraph(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format (type) {
|
||||||
|
this.contentState.format(type)
|
||||||
|
}
|
||||||
|
|
||||||
on (event, listener) {
|
on (event, listener) {
|
||||||
const { eventCenter } = this
|
const { eventCenter } = this
|
||||||
eventCenter.subscribe(event, listener)
|
eventCenter.subscribe(event, listener)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { LOWERCASE_TAGS, CLASS_OR_ID } from '../config'
|
import { LOWERCASE_TAGS, CLASS_OR_ID } from '../config'
|
||||||
import { conflict, isLengthEven, isEven, getIdWithoutSet, loadImage, getImageSrc } from '../utils'
|
import { conflict, isLengthEven, isEven, getIdWithoutSet, loadImage, getImageSrc } from '../utils'
|
||||||
import { insertAfter, operateClassName } from '../utils/domManipulate.js'
|
import { insertAfter, operateClassName } from '../utils/domManipulate.js'
|
||||||
import { tokenizer, generator } from './parse'
|
import { tokenizer } from './parse'
|
||||||
import { validEmoji } from '../emojis'
|
import { validEmoji } from '../emojis'
|
||||||
|
|
||||||
const snabbdom = require('snabbdom')
|
const snabbdom = require('snabbdom')
|
||||||
@ -115,8 +115,6 @@ class StateRender {
|
|||||||
|
|
||||||
return h(blockSelector, data, block.children.map(child => renderBlock(child)))
|
return h(blockSelector, data, block.children.map(child => renderBlock(child)))
|
||||||
} else {
|
} else {
|
||||||
const tokens = tokenizer(block.text)
|
|
||||||
console.log(generator(tokens))
|
|
||||||
let children = block.text
|
let children = block.text
|
||||||
? tokenizer(block.text).reduce((acc, token) => {
|
? tokenizer(block.text).reduce((acc, token) => {
|
||||||
const chunk = this[token.type](h, cursor, block, token)
|
const chunk = this[token.type](h, cursor, block, token)
|
||||||
|
@ -31,6 +31,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
if (to) {
|
if (to) {
|
||||||
const token = {
|
const token = {
|
||||||
type: beginR[i],
|
type: beginR[i],
|
||||||
|
parent: tokens,
|
||||||
marker: to[1],
|
marker: to[1],
|
||||||
content: to[2] || '',
|
content: to[2] || '',
|
||||||
range: {
|
range: {
|
||||||
@ -54,6 +55,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
tokens.push({
|
tokens.push({
|
||||||
type: 'backlash',
|
type: 'backlash',
|
||||||
marker: backTo[1],
|
marker: backTo[1],
|
||||||
|
parent: tokens,
|
||||||
content: '',
|
content: '',
|
||||||
range: {
|
range: {
|
||||||
start: pos,
|
start: pos,
|
||||||
@ -86,6 +88,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
type,
|
type,
|
||||||
range,
|
range,
|
||||||
marker,
|
marker,
|
||||||
|
parent: tokens,
|
||||||
content: to[2],
|
content: to[2],
|
||||||
backlash: to[3]
|
backlash: to[3]
|
||||||
})
|
})
|
||||||
@ -94,6 +97,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
type,
|
type,
|
||||||
range,
|
range,
|
||||||
marker,
|
marker,
|
||||||
|
parent: tokens,
|
||||||
children: tokenizerFac(to[2], undefined, inlineRules, pos + to[1].length),
|
children: tokenizerFac(to[2], undefined, inlineRules, pos + to[1].length),
|
||||||
backlash: to[3]
|
backlash: to[3]
|
||||||
})
|
})
|
||||||
@ -112,6 +116,8 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
marker: linkTo[1],
|
marker: linkTo[1],
|
||||||
href: linkTo[4],
|
href: linkTo[4],
|
||||||
|
parent: tokens,
|
||||||
|
anchor: linkTo[2],
|
||||||
range: {
|
range: {
|
||||||
start: pos,
|
start: pos,
|
||||||
end: pos + linkTo[0].length
|
end: pos + linkTo[0].length
|
||||||
@ -136,6 +142,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
type: 'image',
|
type: 'image',
|
||||||
marker: imageTo[1],
|
marker: imageTo[1],
|
||||||
src: imageTo[4],
|
src: imageTo[4],
|
||||||
|
parent: tokens,
|
||||||
range: {
|
range: {
|
||||||
start: pos,
|
start: pos,
|
||||||
end: pos + imageTo[0].length
|
end: pos + imageTo[0].length
|
||||||
@ -158,6 +165,7 @@ const tokenizerFac = (src, beginRules, inlineRules, pos = 0) => {
|
|||||||
tokens.push({
|
tokens.push({
|
||||||
type: 'auto_link',
|
type: 'auto_link',
|
||||||
href: autoLTo[0],
|
href: autoLTo[0],
|
||||||
|
parent: tokens,
|
||||||
range: {
|
range: {
|
||||||
start: pos,
|
start: pos,
|
||||||
end: pos + autoLTo[0].length
|
end: pos + autoLTo[0].length
|
||||||
@ -182,9 +190,10 @@ export const tokenizer = src => {
|
|||||||
return tokenizerFac(src, beginRules, inlineRules, 0)
|
return tokenizerFac(src, beginRules, inlineRules, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform `tokens` to text
|
// transform `tokens` to text ignore the range of token
|
||||||
export const generator = tokens => {
|
export const generator = tokens => {
|
||||||
let result = ''
|
let result = ''
|
||||||
|
const getBash = bash => bash !== undefined ? bash : ''
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'hr':
|
case 'hr':
|
||||||
@ -199,17 +208,17 @@ export const generator = tokens => {
|
|||||||
case 'em':
|
case 'em':
|
||||||
case 'del':
|
case 'del':
|
||||||
case 'strong':
|
case 'strong':
|
||||||
result += `${token.marker}${generator(token.children)}${token.backlash}${token.marker}`
|
result += `${token.marker}${generator(token.children)}${getBash(token.backlash)}${token.marker}`
|
||||||
break
|
break
|
||||||
case 'emoji':
|
case 'emoji':
|
||||||
case 'inline_code':
|
case 'inline_code':
|
||||||
result += `${token.marker}${token.content}${token.backlash}${token.marker}`
|
result += `${token.marker}${token.content}${getBash(token.backlash)}${token.marker}`
|
||||||
break
|
break
|
||||||
case 'link':
|
case 'link':
|
||||||
result += `[${generator(token.children)}${token.backlash.first}](${token.href}${token.backlash.second})`
|
result += `[${generator(token.children)}${getBash(token.backlash.first)}](${token.href}${getBash(token.backlash.second)})`
|
||||||
break
|
break
|
||||||
case 'image':
|
case 'image':
|
||||||
result += ``
|
result += `})`
|
||||||
break
|
break
|
||||||
case 'auto_link':
|
case 'auto_link':
|
||||||
result += token.href
|
result += token.href
|
||||||
|
@ -1,3 +1,30 @@
|
|||||||
|
import { ipcMain } from 'electron'
|
||||||
|
import { getMenuItem } from '../utils'
|
||||||
|
|
||||||
|
const FORMAT_MAP = {
|
||||||
|
'Strong': 'strong',
|
||||||
|
'Emphasis': 'em',
|
||||||
|
'Inline Code': 'inline_code',
|
||||||
|
'Strike': 'del',
|
||||||
|
'Hyperlink': 'link',
|
||||||
|
'Image': 'image'
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectFormat = formats => {
|
||||||
|
const formatMenuItem = getMenuItem('Format')
|
||||||
|
formatMenuItem.submenu.items.forEach(item => (item.checked = false))
|
||||||
|
formatMenuItem.submenu.items
|
||||||
|
.forEach(item => {
|
||||||
|
if (formats.some(format => format.type === FORMAT_MAP[item.label])) {
|
||||||
|
item.checked = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const format = (win, type) => {
|
export const format = (win, type) => {
|
||||||
win.webContents.send('AGANI::format', { type })
|
win.webContents.send('AGANI::format', { type })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.on('AGANI::selection-formats', (e, formats) => {
|
||||||
|
selectFormat(formats)
|
||||||
|
})
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
import { Menu, ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
|
import { getMenuItem } from '../utils'
|
||||||
|
|
||||||
const getParagraph = () => {
|
const DISABLE_LABELS = [
|
||||||
const menus = Menu.getApplicationMenu()
|
'Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6',
|
||||||
return menus.items.filter(menu => menu.label === 'Paragraph')[0]
|
'Upgrade Heading Level', 'Degrade Heading Level',
|
||||||
}
|
'Table', 'Hyperlink', 'Image'
|
||||||
|
]
|
||||||
|
|
||||||
const allCtrl = bool => {
|
const allCtrl = bool => {
|
||||||
const paragraphMenuItem = getParagraph()
|
const paragraphMenuItem = getMenuItem('Paragraph')
|
||||||
paragraphMenuItem.submenu.items.forEach(item => (item.enabled = bool))
|
paragraphMenuItem.submenu.items
|
||||||
|
.forEach(item => (item.enabled = bool))
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableNoMultiple = () => {
|
const disableNoMultiple = () => {
|
||||||
const paragraphMenuItem = getParagraph()
|
const paragraphMenuItem = getMenuItem('Paragraph')
|
||||||
const disableLabels = [
|
|
||||||
'Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6',
|
|
||||||
'Upgrade Heading Level', 'Degrade Heading Level',
|
|
||||||
'Table'
|
|
||||||
]
|
|
||||||
paragraphMenuItem.submenu.items
|
paragraphMenuItem.submenu.items
|
||||||
.filter(item => disableLabels.includes(item.label))
|
.filter(item => DISABLE_LABELS.includes(item.label))
|
||||||
.forEach(item => (item.enabled = false))
|
.forEach(item => (item.enabled = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,10 +26,15 @@ export const paragraph = (win, type) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on('AGANI::selection-change', (e, { start, end }) => {
|
ipcMain.on('AGANI::selection-change', (e, { start, end }) => {
|
||||||
|
const formatMenuItem = getMenuItem('Format')
|
||||||
|
formatMenuItem.submenu.items.forEach(item => (item.enabled = true))
|
||||||
allCtrl(true)
|
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)
|
allCtrl(false)
|
||||||
} else if (start.key !== end.key) {
|
} else if (start.key !== end.key) {
|
||||||
|
formatMenuItem.submenu.items
|
||||||
|
.filter(item => DISABLE_LABELS.includes(item.label))
|
||||||
|
.forEach(item => (item.enabled = false))
|
||||||
disableNoMultiple()
|
disableNoMultiple()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,41 +4,47 @@ export default {
|
|||||||
label: 'Format',
|
label: 'Format',
|
||||||
submenu: [{
|
submenu: [{
|
||||||
label: 'Strong',
|
label: 'Strong',
|
||||||
|
type: 'checkbox',
|
||||||
accelerator: 'Shift+CmdOrCtrl+B',
|
accelerator: 'Shift+CmdOrCtrl+B',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'strong')
|
actions.format(browserWindow, 'strong')
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Emphasis',
|
label: 'Emphasis',
|
||||||
|
type: 'checkbox',
|
||||||
accelerator: 'CmdOrCtrl+E',
|
accelerator: 'CmdOrCtrl+E',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'em')
|
actions.format(browserWindow, 'em')
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Inline Code',
|
label: 'Inline Code',
|
||||||
|
type: 'checkbox',
|
||||||
accelerator: 'CmdOrCtrl+`',
|
accelerator: 'CmdOrCtrl+`',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'code')
|
actions.format(browserWindow, 'inline_code')
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
}, {
|
}, {
|
||||||
label: 'Strike',
|
label: 'Strike',
|
||||||
|
type: 'checkbox',
|
||||||
accelerator: 'CmdOrCtrl+D',
|
accelerator: 'CmdOrCtrl+D',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'del')
|
actions.format(browserWindow, 'del')
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Hyperlink',
|
label: 'Hyperlink',
|
||||||
|
type: 'checkbox',
|
||||||
accelerator: 'CmdOrCtrl+L',
|
accelerator: 'CmdOrCtrl+L',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'a')
|
actions.format(browserWindow, 'link')
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Image',
|
label: 'Image',
|
||||||
|
type: 'checkbox',
|
||||||
accelerator: 'Shift+CmdOrCtrl+I',
|
accelerator: 'Shift+CmdOrCtrl+I',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'img')
|
actions.format(browserWindow, 'image')
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
|
6
src/main/utils.js
Normal file
6
src/main/utils.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Menu } from 'electron'
|
||||||
|
|
||||||
|
export const getMenuItem = menuName => {
|
||||||
|
const menus = Menu.getApplicationMenu()
|
||||||
|
return menus.items.find(menu => menu.label === menuName)
|
||||||
|
}
|
@ -70,6 +70,9 @@
|
|||||||
this.editor.on('selectionChange', changes => {
|
this.editor.on('selectionChange', changes => {
|
||||||
this.$store.dispatch('SELECTION_CHANGE', changes)
|
this.$store.dispatch('SELECTION_CHANGE', changes)
|
||||||
})
|
})
|
||||||
|
this.editor.on('selectionFormats', formats => {
|
||||||
|
this.$store.dispatch('SELECTION_FORMATS', formats)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -110,7 +113,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleInlineFormat (type) {
|
handleInlineFormat (type) {
|
||||||
console.log(type)
|
this.editor && this.editor.format(type)
|
||||||
},
|
},
|
||||||
handleDialogTableConfirm () {
|
handleDialogTableConfirm () {
|
||||||
this.dialogTableVisible = false
|
this.dialogTableVisible = false
|
||||||
|
@ -104,6 +104,9 @@ const actions = {
|
|||||||
SELECTION_CHANGE ({ commit }, changes) {
|
SELECTION_CHANGE ({ commit }, changes) {
|
||||||
ipcRenderer.send('AGANI::selection-change', changes)
|
ipcRenderer.send('AGANI::selection-change', changes)
|
||||||
},
|
},
|
||||||
|
SELECTION_FORMATS ({ commit }, formats) {
|
||||||
|
ipcRenderer.send('AGANI::selection-formats', formats)
|
||||||
|
},
|
||||||
LISTEN_FOR_EXPORT ({ commit }) {
|
LISTEN_FOR_EXPORT ({ commit }) {
|
||||||
ipcRenderer.on('AGANI::export', (e, { type }) => {
|
ipcRenderer.on('AGANI::export', (e, { type }) => {
|
||||||
bus.$emit('export', type)
|
bus.$emit('export', type)
|
||||||
|
Loading…
Reference in New Issue
Block a user