mirror of
https://github.com/marktext/marktext.git
synced 2025-05-04 04:09:26 +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 |
|
| `formatStrong` | Set the font of the selected text to bold |
|
||||||
| `formatEmphasis` | Set the font of the selected text to italic |
|
| `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 |
|
| `formatInlineCode` | Change the selected text to inline code |
|
||||||
| `formatStrike` | Strike through the selected text |
|
| `formatStrike` | Strike through the selected text |
|
||||||
| `formatHyperlink` | Insert a hyperlink |
|
| `formatHyperlink` | Insert a hyperlink |
|
||||||
|
@ -20,6 +20,32 @@ export default {
|
|||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.format(browserWindow, 'em')
|
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',
|
id: 'inlineCodeMenuItem',
|
||||||
label: 'Inline Code',
|
label: 'Inline Code',
|
||||||
|
@ -75,6 +75,7 @@ class Keybindings {
|
|||||||
// format menu
|
// format menu
|
||||||
['formatStrong', 'CmdOrCtrl+B'],
|
['formatStrong', 'CmdOrCtrl+B'],
|
||||||
['formatEmphasis', 'CmdOrCtrl+I'],
|
['formatEmphasis', 'CmdOrCtrl+I'],
|
||||||
|
['formatUnderline', 'CmdOrCtrl+U'],
|
||||||
['formatInlineCode', 'CmdOrCtrl+`'],
|
['formatInlineCode', 'CmdOrCtrl+`'],
|
||||||
['formatInlineMath', 'Ctrl+M'],
|
['formatInlineMath', 'Ctrl+M'],
|
||||||
['formatStrike', 'CmdOrCtrl+D'],
|
['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': '`',
|
'inline_code': '`',
|
||||||
'strong': '**',
|
'strong': '**',
|
||||||
'del': '~~',
|
'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']
|
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 { tokenizer, generator } from '../parser/'
|
||||||
import { FORMAT_MARKER_MAP, FORMAT_TYPES, URL_REG } from '../config'
|
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 dis = offset - start
|
||||||
const len = end - start
|
const len = end - start
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -19,6 +19,16 @@ const getOffset = (offset, { range: { start, end }, type, anchor, alt }) => {
|
|||||||
if (dis > len) return -2 * MARKER_LEN
|
if (dis > len) return -2 * MARKER_LEN
|
||||||
break
|
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': {
|
case 'link': {
|
||||||
const MARKER_LEN = 1
|
const MARKER_LEN = 1
|
||||||
if (dis < MARKER_LEN) return 0
|
if (dis < MARKER_LEN) return 0
|
||||||
@ -50,7 +60,8 @@ const clearFormat = (token, { start, end }) => {
|
|||||||
case 'strong':
|
case 'strong':
|
||||||
case 'del':
|
case 'del':
|
||||||
case 'em':
|
case 'em':
|
||||||
case 'link': {
|
case 'link':
|
||||||
|
case 'html_tag': { // underline, sub, sup
|
||||||
const { parent } = token
|
const { parent } = token
|
||||||
const index = parent.indexOf(token)
|
const index = parent.indexOf(token)
|
||||||
parent.splice(index, 1, ...token.children)
|
parent.splice(index, 1, ...token.children)
|
||||||
@ -90,6 +101,18 @@ const addFormat = (type, block, { start, end }) => {
|
|||||||
end.offset += MARKER.length
|
end.offset += MARKER.length
|
||||||
break
|
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 'link':
|
||||||
case 'image': {
|
case 'image': {
|
||||||
const oldText = block.text
|
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 => {
|
const formatCtrl = ContentState => {
|
||||||
ContentState.prototype.selectionFormats = function ({ start, end } = selection.getCursorRange()) {
|
ContentState.prototype.selectionFormats = function ({ start, end } = selection.getCursorRange()) {
|
||||||
if (!start || !end) {
|
if (!start || !end) {
|
||||||
@ -119,14 +149,14 @@ const formatCtrl = ContentState => {
|
|||||||
;(function iterator (tks) {
|
;(function iterator (tks) {
|
||||||
for (const token of tks) {
|
for (const token of tks) {
|
||||||
if (
|
if (
|
||||||
FORMAT_TYPES.includes(token.type) &&
|
checkTokenIsInlineFormat(token) &&
|
||||||
start.offset >= token.range.start &&
|
start.offset >= token.range.start &&
|
||||||
end.offset <= token.range.end
|
end.offset <= token.range.end
|
||||||
) {
|
) {
|
||||||
formats.push(token)
|
formats.push(token)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
FORMAT_TYPES.includes(token.type) &&
|
checkTokenIsInlineFormat(token) &&
|
||||||
((start.offset >= token.range.start && start.offset <= token.range.end) ||
|
((start.offset >= token.range.start && start.offset <= token.range.end) ||
|
||||||
(end.offset >= token.range.start && end.offset <= token.range.end) ||
|
(end.offset >= token.range.start && end.offset <= token.range.end) ||
|
||||||
(start.offset <= token.range.start && token.range.end <= end.offset))
|
(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) {
|
for (const neighbor of neighbors) {
|
||||||
clearFormat(neighbor, { start, end })
|
clearFormat(neighbor, { start, end })
|
||||||
@ -263,8 +296,14 @@ const formatCtrl = ContentState => {
|
|||||||
start.delata = end.delata = 0
|
start.delata = end.delata = 0
|
||||||
if (start.key === end.key) {
|
if (start.key === end.key) {
|
||||||
const { formats, tokens, neighbors } = this.selectionFormats()
|
const { formats, tokens, neighbors } = this.selectionFormats()
|
||||||
const currentFormats = formats.filter(format => format.type === type).reverse()
|
const currentFormats = formats.filter(format => {
|
||||||
const currentNeightbors = neighbors.filter(format => format.type === type).reverse()
|
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
|
// cache delata
|
||||||
if (type === 'clear') {
|
if (type === 'clear') {
|
||||||
for (const neighbor of neighbors) {
|
for (const neighbor of neighbors) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import strongIcon from '../../assets/pngicon/format_strong/2.png'
|
import strongIcon from '../../assets/pngicon/format_strong/2.png'
|
||||||
import emphasisIcon from '../../assets/pngicon/format_emphasis/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 codeIcon from '../../assets/pngicon/code/2.png'
|
||||||
import imageIcon from '../../assets/pngicon/format_image/2.png'
|
import imageIcon from '../../assets/pngicon/format_image/2.png'
|
||||||
import linkIcon from '../../assets/pngicon/format_link/2.png'
|
import linkIcon from '../../assets/pngicon/format_link/2.png'
|
||||||
@ -14,6 +15,9 @@ const icons = [
|
|||||||
}, {
|
}, {
|
||||||
type: 'em',
|
type: 'em',
|
||||||
icon: emphasisIcon
|
icon: emphasisIcon
|
||||||
|
}, {
|
||||||
|
type: 'u',
|
||||||
|
icon: underlineIcon
|
||||||
}, {
|
}, {
|
||||||
type: 'del',
|
type: 'del',
|
||||||
icon: strikeIcon
|
icon: strikeIcon
|
||||||
|
@ -62,7 +62,7 @@ class FormatPicker extends BaseFloat {
|
|||||||
}
|
}
|
||||||
const iconWrapper = h(iconWrapperSelector, icon)
|
const iconWrapper = h(iconWrapperSelector, icon)
|
||||||
let itemSelector = `li.item.${i.type}`
|
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'
|
itemSelector += '.active'
|
||||||
}
|
}
|
||||||
return h(itemSelector, {
|
return h(itemSelector, {
|
||||||
|
Loading…
Reference in New Issue
Block a user