diff --git a/doc/KEYBINDINGS.md b/doc/KEYBINDINGS.md
index e346f22b..628f1be7 100644
--- a/doc/KEYBINDINGS.md
+++ b/doc/KEYBINDINGS.md
@@ -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 |
diff --git a/src/main/menus/format.js b/src/main/menus/format.js
index aca5664b..e1c9b6c0 100644
--- a/src/main/menus/format.js
+++ b/src/main/menus/format.js
@@ -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',
diff --git a/src/main/shortcutHandler.js b/src/main/shortcutHandler.js
index abd57883..5a077f8b 100644
--- a/src/main/shortcutHandler.js
+++ b/src/main/shortcutHandler.js
@@ -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'],
diff --git a/src/muya/lib/assets/pngicon/format_underline/1.png b/src/muya/lib/assets/pngicon/format_underline/1.png
new file mode 100755
index 00000000..f536e64f
Binary files /dev/null and b/src/muya/lib/assets/pngicon/format_underline/1.png differ
diff --git a/src/muya/lib/assets/pngicon/format_underline/2.png b/src/muya/lib/assets/pngicon/format_underline/2.png
new file mode 100755
index 00000000..fd560fb7
Binary files /dev/null and b/src/muya/lib/assets/pngicon/format_underline/2.png differ
diff --git a/src/muya/lib/assets/pngicon/format_underline/3.png b/src/muya/lib/assets/pngicon/format_underline/3.png
new file mode 100755
index 00000000..c04588be
Binary files /dev/null and b/src/muya/lib/assets/pngicon/format_underline/3.png differ
diff --git a/src/muya/lib/config/index.js b/src/muya/lib/config/index.js
index 4bebbeab..3fb14a53 100644
--- a/src/muya/lib/config/index.js
+++ b/src/muya/lib/config/index.js
@@ -167,7 +167,19 @@ export const FORMAT_MARKER_MAP = {
'inline_code': '`',
'strong': '**',
'del': '~~',
- 'inline_math': '$'
+ 'inline_math': '$',
+ 'u': {
+ open: '',
+ close: ''
+ },
+ 'sub': {
+ open: '',
+ close: ''
+ },
+ 'sup': {
+ open: '',
+ close: ''
+ }
}
export const FORMAT_TYPES = ['strong', 'em', 'del', 'inline_code', 'link', 'image', 'inline_math']
diff --git a/src/muya/lib/contentState/formatCtrl.js b/src/muya/lib/contentState/formatCtrl.js
index 544fb5fb..acf05c13 100644
--- a/src/muya/lib/contentState/formatCtrl.js
+++ b/src/muya/lib/contentState/formatCtrl.js
@@ -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) {
diff --git a/src/muya/lib/ui/formatPicker/config.js b/src/muya/lib/ui/formatPicker/config.js
index 72876218..79ac3704 100644
--- a/src/muya/lib/ui/formatPicker/config.js
+++ b/src/muya/lib/ui/formatPicker/config.js
@@ -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
diff --git a/src/muya/lib/ui/formatPicker/index.js b/src/muya/lib/ui/formatPicker/index.js
index 3fde9851..05737e6f 100644
--- a/src/muya/lib/ui/formatPicker/index.js
+++ b/src/muya/lib/ui/formatPicker/index.js
@@ -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, {