mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 17:31:26 +08:00
Refactor IPC selection messages (#1833)
This commit is contained in:
parent
3f2e3340b6
commit
593ca5f83b
@ -5,7 +5,7 @@ const MENU_ID_FORMAT_MAP = {
|
|||||||
strikeMenuItem: 'del',
|
strikeMenuItem: 'del',
|
||||||
hyperlinkMenuItem: 'link',
|
hyperlinkMenuItem: 'link',
|
||||||
imageMenuItem: 'image',
|
imageMenuItem: 'image',
|
||||||
mathMenuItem: 'inline_math'
|
inlineMathMenuItem: 'inline_math'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const format = (win, type) => {
|
export const format = (win, type) => {
|
||||||
@ -19,12 +19,18 @@ export const format = (win, type) => {
|
|||||||
// NOTE: Don't use static `getMenuItemById` here, instead request the menu by
|
// NOTE: Don't use static `getMenuItemById` here, instead request the menu by
|
||||||
// window id from `AppMenu` manager.
|
// window id from `AppMenu` manager.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update format menu entires from given state.
|
||||||
|
*
|
||||||
|
* @param {Electron.MenuItem} applicationMenu The application menu instance.
|
||||||
|
* @param {Object.<string, boolean>} formats A object map with selected formats.
|
||||||
|
*/
|
||||||
export const updateFormatMenu = (applicationMenu, formats) => {
|
export const updateFormatMenu = (applicationMenu, formats) => {
|
||||||
const formatMenuItem = applicationMenu.getMenuItemById('formatMenuItem')
|
const formatMenuItem = applicationMenu.getMenuItemById('formatMenuItem')
|
||||||
formatMenuItem.submenu.items.forEach(item => (item.checked = false))
|
formatMenuItem.submenu.items.forEach(item => (item.checked = false))
|
||||||
formatMenuItem.submenu.items
|
formatMenuItem.submenu.items
|
||||||
.forEach(item => {
|
.forEach(item => {
|
||||||
if (item.id && formats.some(format => format.type === MENU_ID_FORMAT_MAP[item.id])) {
|
if (item.id && formats[MENU_ID_FORMAT_MAP[item.id]]) {
|
||||||
item.checked = true
|
item.checked = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,13 +17,15 @@ const MENU_ID_MAP = {
|
|||||||
heading6MenuItem: 'h6',
|
heading6MenuItem: 'h6',
|
||||||
tableMenuItem: 'figure',
|
tableMenuItem: 'figure',
|
||||||
codeFencesMenuItem: 'pre',
|
codeFencesMenuItem: 'pre',
|
||||||
|
htmlBlockMenuItem: 'html',
|
||||||
|
mathBlockMenuItem: 'multiplemath',
|
||||||
quoteBlockMenuItem: 'blockquote',
|
quoteBlockMenuItem: 'blockquote',
|
||||||
orderListMenuItem: 'ol',
|
orderListMenuItem: 'ol',
|
||||||
bulletListMenuItem: 'ul',
|
bulletListMenuItem: 'ul',
|
||||||
taskListMenuItem: 'ul',
|
// taskListMenuItem: 'ul',
|
||||||
paragraphMenuItem: 'p',
|
paragraphMenuItem: 'p',
|
||||||
horizontalLineMenuItem: 'hr',
|
horizontalLineMenuItem: 'hr',
|
||||||
frontMatterMenuItem: 'pre'
|
frontMatterMenuItem: 'frontmatter' // 'pre'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const paragraph = (win, type) => {
|
export const paragraph = (win, type) => {
|
||||||
@ -50,76 +52,95 @@ const setMultipleStatus = (applicationMenu, list, status) => {
|
|||||||
.forEach(item => (item.enabled = status))
|
.forEach(item => (item.enabled = status))
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCheckedMenuItem = (applicationMenu, affiliation) => {
|
const setCheckedMenuItem = (applicationMenu, { affiliation, isTable, isLooseListItem, isTaskList }) => {
|
||||||
const paragraphMenuItem = applicationMenu.getMenuItemById('paragraphMenuEntry')
|
const paragraphMenuItem = applicationMenu.getMenuItemById('paragraphMenuEntry')
|
||||||
paragraphMenuItem.submenu.items.forEach(item => (item.checked = false))
|
paragraphMenuItem.submenu.items.forEach(item => (item.checked = false))
|
||||||
paragraphMenuItem.submenu.items.forEach(item => {
|
paragraphMenuItem.submenu.items.forEach(item => {
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
return false
|
return false
|
||||||
} else if (item.id === 'looseListItemMenuItem') {
|
} else if (item.id === 'looseListItemMenuItem') {
|
||||||
let checked = false
|
item.checked = !!isLooseListItem
|
||||||
if (affiliation.length >= 1 && /ul|ol/.test(affiliation[0].type)) {
|
} else if (Object.keys(affiliation).some(b => {
|
||||||
checked = affiliation[0].children[0].isLooseListItem
|
if (b === 'ul' && isTaskList) {
|
||||||
} else if (affiliation.length >= 3 && affiliation[1].type === 'li') {
|
if (item.id === 'taskListMenuItem') {
|
||||||
checked = affiliation[1].isLooseListItem
|
return true
|
||||||
}
|
}
|
||||||
item.checked = checked
|
return false
|
||||||
} else if (affiliation.some(b => {
|
} else if (isTable && item.id === 'tableMenuItem') {
|
||||||
if (b.type === 'ul') {
|
return true
|
||||||
if (b.listType === 'bullet') {
|
} else if (item.id === 'codeFencesMenuItem' && /code$/.test(b)) {
|
||||||
return item.id === 'bulletListMenuItem'
|
return true
|
||||||
} else {
|
|
||||||
return item.id === 'taskListMenuItem'
|
|
||||||
}
|
|
||||||
} else if (b.type === 'pre' && b.functionType) {
|
|
||||||
if (b.functionType === 'frontmatter') {
|
|
||||||
return item.id === 'frontMatterMenuItem'
|
|
||||||
} else if (/code$/.test(b.functionType)) {
|
|
||||||
return item.id === 'codeFencesMenuItem'
|
|
||||||
} else if (b.functionType === 'html') {
|
|
||||||
return item.id === 'htmlBlockMenuItem'
|
|
||||||
} else if (b.functionType === 'multiplemath') {
|
|
||||||
return item.id === 'mathBlockMenuItem'
|
|
||||||
}
|
|
||||||
} else if (b.type === 'figure' && b.functionType) {
|
|
||||||
if (b.functionType === 'table') {
|
|
||||||
return item.id === 'tableMenuItem'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return b.type === MENU_ID_MAP[item.id]
|
|
||||||
}
|
}
|
||||||
|
return b === MENU_ID_MAP[item.id]
|
||||||
})) {
|
})) {
|
||||||
item.checked = true
|
item.checked = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateSelectionMenus = (applicationMenu, { start, end, affiliation }) => {
|
/**
|
||||||
// format menu
|
* Update paragraph menu entires from given state.
|
||||||
|
*
|
||||||
|
* @param {Electron.MenuItem} applicationMenu The application menu instance.
|
||||||
|
* @param {*} state The selection information.
|
||||||
|
*/
|
||||||
|
export const updateSelectionMenus = (applicationMenu, state) => {
|
||||||
|
const {
|
||||||
|
// Key/boolean object like "ul: true" of block elements that are selected.
|
||||||
|
// This may be an empty object when multiple block elements are selected.
|
||||||
|
affiliation,
|
||||||
|
isDisabled,
|
||||||
|
isMultiline,
|
||||||
|
isCodeFences,
|
||||||
|
isCodeContent
|
||||||
|
} = state
|
||||||
|
|
||||||
|
// Reset format menu.
|
||||||
const formatMenuItem = applicationMenu.getMenuItemById('formatMenuItem')
|
const formatMenuItem = applicationMenu.getMenuItemById('formatMenuItem')
|
||||||
formatMenuItem.submenu.items.forEach(item => (item.enabled = true))
|
formatMenuItem.submenu.items.forEach(item => (item.enabled = true))
|
||||||
// handle menu checked
|
|
||||||
setCheckedMenuItem(applicationMenu, affiliation)
|
|
||||||
// handle disable
|
|
||||||
setParagraphMenuItemStatus(applicationMenu, true)
|
|
||||||
|
|
||||||
if (
|
// Handle menu checked.
|
||||||
(start.block.functionType === 'cellContent' && end.block.functionType === 'cellContent') ||
|
setCheckedMenuItem(applicationMenu, state)
|
||||||
(start.type === 'span' && start.block.functionType === 'codeContent') ||
|
|
||||||
(end.type === 'span' && end.block.functionType === 'codeContent')
|
// Reset paragraph menu.
|
||||||
) {
|
setParagraphMenuItemStatus(applicationMenu, !isDisabled)
|
||||||
|
if (isDisabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCodeFences) {
|
||||||
setParagraphMenuItemStatus(applicationMenu, false)
|
setParagraphMenuItemStatus(applicationMenu, false)
|
||||||
|
|
||||||
if (start.block.functionType === 'codeContent' || end.block.functionType === 'codeContent') {
|
// A code line is selected.
|
||||||
setMultipleStatus(applicationMenu, ['codeFencesMenuItem'], true)
|
if (isCodeContent) {
|
||||||
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
|
formatMenuItem.submenu.items.forEach(item => (item.enabled = false))
|
||||||
|
|
||||||
|
// TODO: Allow to transform to paragraph for other code blocks too but
|
||||||
|
// currently not supported by Muya.
|
||||||
|
// // Allow to transform to paragraph.
|
||||||
|
// if (affiliation.frontmatter) {
|
||||||
|
// setMultipleStatus(applicationMenu, ['frontMatterMenuItem'], true)
|
||||||
|
// } else if (affiliation.html) {
|
||||||
|
// setMultipleStatus(applicationMenu, ['htmlBlockMenuItem'], true)
|
||||||
|
// } else if (affiliation.multiplemath) {
|
||||||
|
// setMultipleStatus(applicationMenu, ['mathBlockMenuItem'], true)
|
||||||
|
// } else {
|
||||||
|
// setMultipleStatus(applicationMenu, ['codeFencesMenuItem'], true)
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (Object.keys(affiliation).some(b => /code$/.test(b))) {
|
||||||
|
setMultipleStatus(applicationMenu, ['codeFencesMenuItem'], true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (start.key !== end.key) {
|
} else if (isMultiline) {
|
||||||
formatMenuItem.submenu.items
|
formatMenuItem.submenu.items
|
||||||
.filter(item => item.id && DISABLE_LABELS.includes(item.id))
|
.filter(item => item.id && DISABLE_LABELS.includes(item.id))
|
||||||
.forEach(item => (item.enabled = false))
|
.forEach(item => (item.enabled = false))
|
||||||
setMultipleStatus(applicationMenu, DISABLE_LABELS, false)
|
setMultipleStatus(applicationMenu, DISABLE_LABELS, false)
|
||||||
} else if (!affiliation.slice(0, 3).some(p => /ul|ol/.test(p.type))) {
|
}
|
||||||
|
|
||||||
|
// Disable loose list item.
|
||||||
|
if (!affiliation.ul && !affiliation.ol) {
|
||||||
setMultipleStatus(applicationMenu, ['looseListItemMenuItem'], false)
|
setMultipleStatus(applicationMenu, ['looseListItemMenuItem'], false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,7 @@ const formatCtrl = ContentState => {
|
|||||||
if (!start || !end) {
|
if (!start || !end) {
|
||||||
return { formats: [], tokens: [], neighbors: [] }
|
return { formats: [], tokens: [], neighbors: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
const startBlock = this.getBlock(start.key)
|
const startBlock = this.getBlock(start.key)
|
||||||
const formats = []
|
const formats = []
|
||||||
const neighbors = []
|
const neighbors = []
|
||||||
|
@ -113,6 +113,8 @@ const paragraphCtrl = ContentState => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: New created nestled list items missing "listType" key and value.
|
||||||
|
|
||||||
ContentState.prototype.handleListMenu = function (paraType, insertMode) {
|
ContentState.prototype.handleListMenu = function (paraType, insertMode) {
|
||||||
const { start, end, affiliation } = this.selectionChange(this.cursor)
|
const { start, end, affiliation } = this.selectionChange(this.cursor)
|
||||||
const { orderListDelimiter, bulletListMarker, preferLooseListItem } = this.muya.options
|
const { orderListDelimiter, bulletListMarker, preferLooseListItem } = this.muya.options
|
||||||
|
@ -995,16 +995,13 @@ const actions = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We should only send a map of booleans to improve performance and not send
|
|
||||||
// the full change with all block elements with every change.
|
|
||||||
|
|
||||||
const { windowId } = global.marktext.env
|
const { windowId } = global.marktext.env
|
||||||
ipcRenderer.send('mt::editor-selection-changed', windowId, changes)
|
ipcRenderer.send('mt::editor-selection-changed', windowId, createApplicationMenuState(changes))
|
||||||
},
|
},
|
||||||
|
|
||||||
SELECTION_FORMATS (_, formats) {
|
SELECTION_FORMATS (_, formats) {
|
||||||
const { windowId } = global.marktext.env
|
const { windowId } = global.marktext.env
|
||||||
ipcRenderer.send('mt::update-format-menu', windowId, formats)
|
ipcRenderer.send('mt::update-format-menu', windowId, createSelectionFormatState(formats))
|
||||||
},
|
},
|
||||||
|
|
||||||
EXPORT ({ state }, { type, content, pageOptions }) {
|
EXPORT ({ state }, { type, content, pageOptions }) {
|
||||||
@ -1241,4 +1238,109 @@ const trimTrailingNewlines = text => {
|
|||||||
return text.replace(/[\r?\n]+$/, '')
|
return text.replace(/[\r?\n]+$/, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a object that contains the application menu state.
|
||||||
|
*
|
||||||
|
* @param {*} selection The selection.
|
||||||
|
* @returns A object that represents the application menu state.
|
||||||
|
*/
|
||||||
|
const createApplicationMenuState = ({ start, end, affiliation }) => {
|
||||||
|
const state = {
|
||||||
|
isDisabled: false,
|
||||||
|
// Whether multiple lines are selected.
|
||||||
|
isMultiline: start.key !== end.key,
|
||||||
|
// List information - a list must be selected.
|
||||||
|
isLooseListItem: false,
|
||||||
|
isTaskList: false,
|
||||||
|
// Whether the selection is code block like (math, html or code block).
|
||||||
|
isCodeFences: false,
|
||||||
|
// Whether a code block line is selected.
|
||||||
|
isCodeContent: false,
|
||||||
|
// Whether the selection contains a table.
|
||||||
|
isTable: false,
|
||||||
|
// Contains keys about the selection type(s) (string, boolean) like "ul: true".
|
||||||
|
affiliation: {}
|
||||||
|
}
|
||||||
|
const { isMultiline } = state
|
||||||
|
|
||||||
|
// Get code block information from selection.
|
||||||
|
if (
|
||||||
|
(start.block.functionType === 'cellContent' && end.block.functionType === 'cellContent') ||
|
||||||
|
(start.type === 'span' && start.block.functionType === 'codeContent') ||
|
||||||
|
(end.type === 'span' && end.block.functionType === 'codeContent')
|
||||||
|
) {
|
||||||
|
// A code block like block is selected (code, math, ...).
|
||||||
|
state.isCodeFences = true
|
||||||
|
|
||||||
|
// A code block line is selected.
|
||||||
|
if (start.block.functionType === 'codeContent' || end.block.functionType === 'codeContent') {
|
||||||
|
state.isCodeContent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query list information.
|
||||||
|
if (affiliation.length >= 1 && /ul|ol/.test(affiliation[0].type)) {
|
||||||
|
const listBlock = affiliation[0]
|
||||||
|
state.affiliation[listBlock.type] = true
|
||||||
|
state.isLooseListItem = listBlock.children[0].isLooseListItem
|
||||||
|
state.isTaskList = listBlock.listType === 'task'
|
||||||
|
} else if (affiliation.length >= 3 && affiliation[1].type === 'li') {
|
||||||
|
const listItem = affiliation[1]
|
||||||
|
const listType = listItem.listItemType === 'order' ? 'ol' : 'ul'
|
||||||
|
state.affiliation[listType] = true
|
||||||
|
state.isLooseListItem = listItem.isLooseListItem
|
||||||
|
state.isTaskList = listItem.listItemType === 'task'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search with block depth 3 (e.g. "ul -> li -> p" where p is the actually paragraph inside the list (item)).
|
||||||
|
for (const b of affiliation.slice(0, 3)) {
|
||||||
|
if (b.type === 'pre' && b.functionType) {
|
||||||
|
if (/frontmatter|html|multiplemath|code$/.test(b.functionType)) {
|
||||||
|
state.isCodeFences = true
|
||||||
|
state.affiliation[b.functionType] = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if (b.type === 'figure' && b.functionType) {
|
||||||
|
if (b.functionType === 'table') {
|
||||||
|
state.isTable = true
|
||||||
|
state.isDisabled = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if (isMultiline && /^h{1,6}$/.test(b.type)) {
|
||||||
|
// Multiple block elements are selected.
|
||||||
|
state.affiliation = {}
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if (!state.affiliation[b.type]) {
|
||||||
|
state.affiliation[b.type] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
if (Object.getOwnPropertyNames(state.affiliation).length >= 2 && state.affiliation.p) {
|
||||||
|
delete state.affiliation.p
|
||||||
|
}
|
||||||
|
if ((state.affiliation.ul || state.affiliation.ol) && state.affiliation.li) {
|
||||||
|
delete state.affiliation.li
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a object that contains the formats selection state.
|
||||||
|
*
|
||||||
|
* @param {*} selection The selection.
|
||||||
|
* @returns A object that represents the formats menu state.
|
||||||
|
*/
|
||||||
|
const createSelectionFormatState = formats => {
|
||||||
|
// NOTE: Normally only one format can be selected but the selection is
|
||||||
|
// given as array by Muya.
|
||||||
|
const state = {}
|
||||||
|
for (const item of formats) {
|
||||||
|
state[item.type] = true
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
export default { state, mutations, actions }
|
export default { state, mutations, actions }
|
||||||
|
Loading…
Reference in New Issue
Block a user