feat: add underline format (#946)

* feat: add underline format

* update doc

* add support superscript and subscript in format menu
This commit is contained in:
Ran Luo 2019-04-17 02:15:02 +08:00 committed by Felix Häusler
parent 6479928168
commit ef9fe7566a
10 changed files with 92 additions and 9 deletions

View File

@ -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 |

View File

@ -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',

View File

@ -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'],

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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']

View File

@ -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) {

View File

@ -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

View File

@ -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, {