mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 03:12:18 +08:00
feat: add underline format (#946)
* feat: add underline format * update doc * add support superscript and subscript in format menu
This commit is contained in:
parent
6479928168
commit
ef9fe7566a
@ -89,6 +89,7 @@ Here is an example:
|
||||
| ------------------- | ------------------------------------------- |
|
||||
| `formatStrong` | Set the font of the selected text to bold |
|
||||
| `formatEmphasis` | Set the font of the selected text to italic |
|
||||
| `formatUnderline` | Change the selected text to underline |
|
||||
| `formatInlineCode` | Change the selected text to inline code |
|
||||
| `formatStrike` | Strike through the selected text |
|
||||
| `formatHyperlink` | Insert a hyperlink |
|
||||
|
@ -20,6 +20,32 @@ export default {
|
||||
click (menuItem, browserWindow) {
|
||||
actions.format(browserWindow, 'em')
|
||||
}
|
||||
}, {
|
||||
id: 'underlineMenuItem',
|
||||
label: 'Underline',
|
||||
type: 'checkbox',
|
||||
accelerator: keybindings.getAccelerator('formatUnderline'),
|
||||
click (menuItem, browserWindow) {
|
||||
actions.format(browserWindow, 'u')
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
id: 'superscriptMenuItem',
|
||||
label: 'Superscript',
|
||||
type: 'checkbox',
|
||||
click (menuItem, browserWindow) {
|
||||
actions.format(browserWindow, 'sup')
|
||||
}
|
||||
}, {
|
||||
id: 'subscriptMenuItem',
|
||||
label: 'Subscript',
|
||||
type: 'checkbox',
|
||||
click (menuItem, browserWindow) {
|
||||
actions.format(browserWindow, 'sub')
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
id: 'inlineCodeMenuItem',
|
||||
label: 'Inline Code',
|
||||
|
@ -75,6 +75,7 @@ class Keybindings {
|
||||
// format menu
|
||||
['formatStrong', 'CmdOrCtrl+B'],
|
||||
['formatEmphasis', 'CmdOrCtrl+I'],
|
||||
['formatUnderline', 'CmdOrCtrl+U'],
|
||||
['formatInlineCode', 'CmdOrCtrl+`'],
|
||||
['formatInlineMath', 'Ctrl+M'],
|
||||
['formatStrike', 'CmdOrCtrl+D'],
|
||||
|
BIN
src/muya/lib/assets/pngicon/format_underline/1.png
Executable file
BIN
src/muya/lib/assets/pngicon/format_underline/1.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 359 B |
BIN
src/muya/lib/assets/pngicon/format_underline/2.png
Executable file
BIN
src/muya/lib/assets/pngicon/format_underline/2.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 662 B |
BIN
src/muya/lib/assets/pngicon/format_underline/3.png
Executable file
BIN
src/muya/lib/assets/pngicon/format_underline/3.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -167,7 +167,19 @@ export const FORMAT_MARKER_MAP = {
|
||||
'inline_code': '`',
|
||||
'strong': '**',
|
||||
'del': '~~',
|
||||
'inline_math': '$'
|
||||
'inline_math': '$',
|
||||
'u': {
|
||||
open: '<u>',
|
||||
close: '</u>'
|
||||
},
|
||||
'sub': {
|
||||
open: '<sub>',
|
||||
close: '</sub>'
|
||||
},
|
||||
'sup': {
|
||||
open: '<sup>',
|
||||
close: '</sup>'
|
||||
}
|
||||
}
|
||||
|
||||
export const FORMAT_TYPES = ['strong', 'em', 'del', 'inline_code', 'link', 'image', 'inline_math']
|
||||
|
@ -2,7 +2,7 @@ import selection from '../selection'
|
||||
import { tokenizer, generator } from '../parser/'
|
||||
import { FORMAT_MARKER_MAP, FORMAT_TYPES, URL_REG } from '../config'
|
||||
|
||||
const getOffset = (offset, { range: { start, end }, type, anchor, alt }) => {
|
||||
const getOffset = (offset, { range: { start, end }, type, tag, anchor, alt }) => {
|
||||
const dis = offset - start
|
||||
const len = end - start
|
||||
switch (type) {
|
||||
@ -19,6 +19,16 @@ const getOffset = (offset, { range: { start, end }, type, anchor, alt }) => {
|
||||
if (dis > len) return -2 * MARKER_LEN
|
||||
break
|
||||
}
|
||||
case 'html_tag': { // handle underline, sup, sub
|
||||
const OPEN_MARKER_LEN = FORMAT_MARKER_MAP[tag].open.length
|
||||
const CLOSE_MARKER_LEN = FORMAT_MARKER_MAP[tag].close.length
|
||||
if (dis < 0) return 0
|
||||
if (dis >= 0 && dis < OPEN_MARKER_LEN) return -dis
|
||||
if (dis >= OPEN_MARKER_LEN && dis <= len - CLOSE_MARKER_LEN) return -OPEN_MARKER_LEN
|
||||
if (dis > len - CLOSE_MARKER_LEN && dis <= len) return len - dis - OPEN_MARKER_LEN - CLOSE_MARKER_LEN
|
||||
if (dis > len) return - OPEN_MARKER_LEN - CLOSE_MARKER_LEN
|
||||
break
|
||||
}
|
||||
case 'link': {
|
||||
const MARKER_LEN = 1
|
||||
if (dis < MARKER_LEN) return 0
|
||||
@ -50,7 +60,8 @@ const clearFormat = (token, { start, end }) => {
|
||||
case 'strong':
|
||||
case 'del':
|
||||
case 'em':
|
||||
case 'link': {
|
||||
case 'link':
|
||||
case 'html_tag': { // underline, sub, sup
|
||||
const { parent } = token
|
||||
const index = parent.indexOf(token)
|
||||
parent.splice(index, 1, ...token.children)
|
||||
@ -90,6 +101,18 @@ const addFormat = (type, block, { start, end }) => {
|
||||
end.offset += MARKER.length
|
||||
break
|
||||
}
|
||||
case 'sub':
|
||||
case 'sup':
|
||||
case 'u': {
|
||||
const MARKER = FORMAT_MARKER_MAP[type]
|
||||
const oldText = block.text
|
||||
block.text = oldText.substring(0, start.offset) +
|
||||
MARKER.open + oldText.substring(start.offset, end.offset) +
|
||||
MARKER.close + oldText.substring(end.offset)
|
||||
start.offset += MARKER.open.length
|
||||
end.offset += MARKER.open.length
|
||||
break
|
||||
}
|
||||
case 'link':
|
||||
case 'image': {
|
||||
const oldText = block.text
|
||||
@ -104,6 +127,13 @@ const addFormat = (type, block, { start, end }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const checkTokenIsInlineFormat = token => {
|
||||
const { type, tag } = token
|
||||
if (FORMAT_TYPES.includes(type)) return true
|
||||
if (type === 'html_tag' && /^(?:u|sub|sup)$/i.test(tag)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const formatCtrl = ContentState => {
|
||||
ContentState.prototype.selectionFormats = function ({ start, end } = selection.getCursorRange()) {
|
||||
if (!start || !end) {
|
||||
@ -119,14 +149,14 @@ const formatCtrl = ContentState => {
|
||||
;(function iterator (tks) {
|
||||
for (const token of tks) {
|
||||
if (
|
||||
FORMAT_TYPES.includes(token.type) &&
|
||||
checkTokenIsInlineFormat(token) &&
|
||||
start.offset >= token.range.start &&
|
||||
end.offset <= token.range.end
|
||||
) {
|
||||
formats.push(token)
|
||||
}
|
||||
if (
|
||||
FORMAT_TYPES.includes(token.type) &&
|
||||
checkTokenIsInlineFormat(token) &&
|
||||
((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))
|
||||
@ -176,7 +206,10 @@ const formatCtrl = ContentState => {
|
||||
}))
|
||||
}
|
||||
|
||||
neighbors = type ? neighbors.filter(n => n.type === type) : neighbors
|
||||
neighbors = type ? neighbors.filter(n => {
|
||||
return n.type === type ||
|
||||
n.type === 'html_tag' && n.tag === type
|
||||
}) : neighbors
|
||||
|
||||
for (const neighbor of neighbors) {
|
||||
clearFormat(neighbor, { start, end })
|
||||
@ -263,8 +296,14 @@ const formatCtrl = ContentState => {
|
||||
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()
|
||||
const currentFormats = formats.filter(format => {
|
||||
return format.type === type ||
|
||||
format.type === 'html_tag' && format.tag === type
|
||||
}).reverse()
|
||||
const currentNeightbors = neighbors.filter(format => {
|
||||
return format.type === type ||
|
||||
format.type === 'html_tag' && format.tag === type
|
||||
}).reverse()
|
||||
// cache delata
|
||||
if (type === 'clear') {
|
||||
for (const neighbor of neighbors) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import strongIcon from '../../assets/pngicon/format_strong/2.png'
|
||||
import emphasisIcon from '../../assets/pngicon/format_emphasis/2.png'
|
||||
import underlineIcon from '../../assets/pngicon/format_underline/2.png'
|
||||
import codeIcon from '../../assets/pngicon/code/2.png'
|
||||
import imageIcon from '../../assets/pngicon/format_image/2.png'
|
||||
import linkIcon from '../../assets/pngicon/format_link/2.png'
|
||||
@ -14,6 +15,9 @@ const icons = [
|
||||
}, {
|
||||
type: 'em',
|
||||
icon: emphasisIcon
|
||||
}, {
|
||||
type: 'u',
|
||||
icon: underlineIcon
|
||||
}, {
|
||||
type: 'del',
|
||||
icon: strikeIcon
|
||||
|
@ -62,7 +62,7 @@ class FormatPicker extends BaseFloat {
|
||||
}
|
||||
const iconWrapper = h(iconWrapperSelector, icon)
|
||||
let itemSelector = `li.item.${i.type}`
|
||||
if (formats.some(f => f.type === i.type)) {
|
||||
if (formats.some(f => f.type === i.type || f.type === 'html_tag' && f.tag === i.type)) {
|
||||
itemSelector += '.active'
|
||||
}
|
||||
return h(itemSelector, {
|
||||
|
Loading…
Reference in New Issue
Block a user