mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 17:50:02 +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',
|
||||
hyperlinkMenuItem: 'link',
|
||||
imageMenuItem: 'image',
|
||||
mathMenuItem: 'inline_math'
|
||||
inlineMathMenuItem: 'inline_math'
|
||||
}
|
||||
|
||||
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
|
||||
// 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) => {
|
||||
const formatMenuItem = applicationMenu.getMenuItemById('formatMenuItem')
|
||||
formatMenuItem.submenu.items.forEach(item => (item.checked = false))
|
||||
formatMenuItem.submenu.items
|
||||
.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
|
||||
}
|
||||
})
|
||||
|
@ -17,13 +17,15 @@ const MENU_ID_MAP = {
|
||||
heading6MenuItem: 'h6',
|
||||
tableMenuItem: 'figure',
|
||||
codeFencesMenuItem: 'pre',
|
||||
htmlBlockMenuItem: 'html',
|
||||
mathBlockMenuItem: 'multiplemath',
|
||||
quoteBlockMenuItem: 'blockquote',
|
||||
orderListMenuItem: 'ol',
|
||||
bulletListMenuItem: 'ul',
|
||||
taskListMenuItem: 'ul',
|
||||
// taskListMenuItem: 'ul',
|
||||
paragraphMenuItem: 'p',
|
||||
horizontalLineMenuItem: 'hr',
|
||||
frontMatterMenuItem: 'pre'
|
||||
frontMatterMenuItem: 'frontmatter' // 'pre'
|
||||
}
|
||||
|
||||
export const paragraph = (win, type) => {
|
||||
@ -50,76 +52,95 @@ const setMultipleStatus = (applicationMenu, list, status) => {
|
||||
.forEach(item => (item.enabled = status))
|
||||
}
|
||||
|
||||
const setCheckedMenuItem = (applicationMenu, affiliation) => {
|
||||
const setCheckedMenuItem = (applicationMenu, { affiliation, isTable, isLooseListItem, isTaskList }) => {
|
||||
const paragraphMenuItem = applicationMenu.getMenuItemById('paragraphMenuEntry')
|
||||
paragraphMenuItem.submenu.items.forEach(item => (item.checked = false))
|
||||
paragraphMenuItem.submenu.items.forEach(item => {
|
||||
if (!item.id) {
|
||||
return false
|
||||
} else if (item.id === 'looseListItemMenuItem') {
|
||||
let checked = false
|
||||
if (affiliation.length >= 1 && /ul|ol/.test(affiliation[0].type)) {
|
||||
checked = affiliation[0].children[0].isLooseListItem
|
||||
} else if (affiliation.length >= 3 && affiliation[1].type === 'li') {
|
||||
checked = affiliation[1].isLooseListItem
|
||||
}
|
||||
item.checked = checked
|
||||
} else if (affiliation.some(b => {
|
||||
if (b.type === 'ul') {
|
||||
if (b.listType === 'bullet') {
|
||||
return item.id === 'bulletListMenuItem'
|
||||
} 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]
|
||||
item.checked = !!isLooseListItem
|
||||
} else if (Object.keys(affiliation).some(b => {
|
||||
if (b === 'ul' && isTaskList) {
|
||||
if (item.id === 'taskListMenuItem') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else if (isTable && item.id === 'tableMenuItem') {
|
||||
return true
|
||||
} else if (item.id === 'codeFencesMenuItem' && /code$/.test(b)) {
|
||||
return true
|
||||
}
|
||||
return b === MENU_ID_MAP[item.id]
|
||||
})) {
|
||||
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')
|
||||
formatMenuItem.submenu.items.forEach(item => (item.enabled = true))
|
||||
// handle menu checked
|
||||
setCheckedMenuItem(applicationMenu, affiliation)
|
||||
// handle disable
|
||||
setParagraphMenuItemStatus(applicationMenu, true)
|
||||
|
||||
if (
|
||||
(start.block.functionType === 'cellContent' && end.block.functionType === 'cellContent') ||
|
||||
(start.type === 'span' && start.block.functionType === 'codeContent') ||
|
||||
(end.type === 'span' && end.block.functionType === 'codeContent')
|
||||
) {
|
||||
// Handle menu checked.
|
||||
setCheckedMenuItem(applicationMenu, state)
|
||||
|
||||
// Reset paragraph menu.
|
||||
setParagraphMenuItemStatus(applicationMenu, !isDisabled)
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isCodeFences) {
|
||||
setParagraphMenuItemStatus(applicationMenu, false)
|
||||
|
||||
if (start.block.functionType === 'codeContent' || end.block.functionType === 'codeContent') {
|
||||
setMultipleStatus(applicationMenu, ['codeFencesMenuItem'], true)
|
||||
// A code line is selected.
|
||||
if (isCodeContent) {
|
||||
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
|
||||
.filter(item => item.id && DISABLE_LABELS.includes(item.id))
|
||||
.forEach(item => (item.enabled = 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)
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ const formatCtrl = ContentState => {
|
||||
if (!start || !end) {
|
||||
return { formats: [], tokens: [], neighbors: [] }
|
||||
}
|
||||
|
||||
const startBlock = this.getBlock(start.key)
|
||||
const formats = []
|
||||
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) {
|
||||
const { start, end, affiliation } = this.selectionChange(this.cursor)
|
||||
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
|
||||
ipcRenderer.send('mt::editor-selection-changed', windowId, changes)
|
||||
ipcRenderer.send('mt::editor-selection-changed', windowId, createApplicationMenuState(changes))
|
||||
},
|
||||
|
||||
SELECTION_FORMATS (_, formats) {
|
||||
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 }) {
|
||||
@ -1241,4 +1238,109 @@ const trimTrailingNewlines = text => {
|
||||
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 }
|
||||
|
Loading…
Reference in New Issue
Block a user