fix: 修复a标签样式重置

This commit is contained in:
hahaaha 2021-02-23 18:28:13 +08:00
parent dc96c34d0e
commit dafee8fd79
5 changed files with 356 additions and 47 deletions

View File

@ -8,6 +8,7 @@ import { PanelConf } from '../menu-constructors/Panel'
import { getRandom } from '../../utils/util'
import $, { DomElement } from '../../utils/dom-core'
import isActive from './is-active'
import { insertHtml } from './util'
export default function (editor: Editor, text: string, link: string): PanelConf {
// panel 中需要用到的id
@ -125,12 +126,35 @@ export default function (editor: Editor, text: string, link: string): PanelConf
selector: '#' + btnOkId,
type: 'click',
fn: () => {
// 获取选取
editor.selection.restoreSelection()
const topNode = editor.selection
.getSelectionRangeTopNodes()[0]
.getNode()
const selection = window.getSelection()
// 执行插入链接
const $link = $('#' + inputLinkId)
const $text = $('#' + inputTextId)
let link = $link.val().trim()
let text = $text.val().trim()
let html: string = ''
if (selection && !selection?.isCollapsed)
html = insertHtml(selection, topNode)?.trim()
// 去除html的tag标签
let htmlText = html?.replace(/<.*?>/g, '')
let htmlTextLen = htmlText?.length ?? 0
// 当input中的text的长度大于等于选区的文字时
// 需要判断两者相同的长度的text内容是否相同
// 相同则只需把多余的部分添加上去即可否则使用input中的内容
if (htmlTextLen <= text.length) {
let startText = text.substring(0, htmlTextLen)
let endText = text.substring(htmlTextLen)
if (htmlText === startText) {
text = html + endText
}
}
// 链接为空,则不插入
if (!link) return
// 文本为空,则用链接代替

155
src/menus/link/util.ts Normal file
View File

@ -0,0 +1,155 @@
/**
* Node
* @param node node下的某个childNode
* @param topText node中文本内容
*/
function getTopNode(node: Node, topText: string): Node {
let pointerNode: Node = node
let topNode: Node = node
do {
if (pointerNode.textContent === topText) break
topNode = pointerNode
if (pointerNode.parentNode) {
pointerNode = pointerNode?.parentNode
}
} while (pointerNode?.nodeName !== 'P')
return topNode
}
/**
* html的string形式
* @param tagName
* @param content
*/
function makeHtmlString(node: Node, content: string): string {
let tagName = node.nodeName
let attr = ''
if (node.nodeType === 3) {
return content
}
if (node.nodeType === 1) {
const style = (node as Element).getAttribute('style')
const face = (node as Element).getAttribute('face')
const color = (node as Element).getAttribute('color')
if (style) attr = attr + ` style="${style}"`
if (face) attr = attr + ` face="${face}"`
if (color) attr = attr + ` color="${color}"`
}
tagName = tagName.toLowerCase()
return `<${tagName}${attr}>${content}</${tagName}>`
}
/**
* html字符片段
* @param topText
* @param node node节点
* @param startPos node文本内容选取的开始位置
* @param endPos node文本内容选取的结束位置
*/
function createPartHtml(topText: string, node: Node, startPos: number, endPost?: number): string {
let selectionContent = node.textContent?.substring(startPos, endPost)
let pointerNode = node
let content = ''
do {
content = makeHtmlString(pointerNode, selectionContent ?? '')
selectionContent = content
if (pointerNode.parentElement) pointerNode = pointerNode?.parentElement
} while (pointerNode.textContent !== topText)
return content
}
/**
* html内容的字符串形式
* @param selection
* @param topNode node节点
*/
function insertHtml(selection: Selection, topNode: Node): string {
const { anchorNode, focusNode, anchorOffset: anchorPos, focusOffset: focusPos } = selection
const topText = topNode.textContent ?? ''
const TagArr = getContainerTag(topNode)
let content: string = ''
let startContent: string = ''
let middleContent: string = ''
let endContent: string = ''
let startNode = anchorNode
let endNode = focusNode
// 用来保存 anchorNode的非p最外层节点
let pointerNode = anchorNode
// 节点是同一个的处理
if (anchorNode?.isEqualNode(focusNode ?? null)) {
let innerContent = createPartHtml(topText, anchorNode, anchorPos, focusPos)
innerContent = addContainer(TagArr, innerContent)
return innerContent
}
// 选中开始位置节点的处理
if (anchorNode) startContent = createPartHtml(topText, anchorNode, anchorPos ?? 0)
// 结束位置节点的处理
if (focusNode) endContent = createPartHtml(topText, focusNode, 0, focusPos)
// 将指针节点位置放置到开始的节点
if (anchorNode) {
// 获取start的非p顶级node
startNode = getTopNode(anchorNode, topText)
}
if (focusNode) {
// 获取end的非p顶级node
endNode = getTopNode(focusNode, topText)
}
// 处于开始和结束节点位置之间的节点的处理
pointerNode = startNode?.nextSibling ?? anchorNode
while (!pointerNode?.isEqualNode(endNode ?? null)) {
const pointerNodeName = pointerNode?.nodeName
if (pointerNodeName === '#text') {
middleContent = middleContent + pointerNode?.textContent
} else {
let htmlString = pointerNode?.firstChild?.parentElement?.innerHTML
if (pointerNode)
middleContent = middleContent + makeHtmlString(pointerNode, htmlString ?? '')
}
pointerNode = pointerNode?.nextSibling ?? pointerNode
}
content = `${startContent}${middleContent}${endContent}`
// 增加最外层包裹标签
content = addContainer(TagArr, content)
return content
}
/**
* p Node tagName
* @param node node节点
*/
function getContainerTag(node: Node): Node[] {
const topText = node.textContent ?? ''
let tagArr = []
while (node?.textContent === topText) {
if (node.nodeName !== 'P') {
tagArr.push(node)
}
node = node.childNodes[0]
}
return tagArr
}
/**
*
* @param tagArr tag数组tag越在外面
* @param content tag要包裹的内容
*/
function addContainer(tagArr: Node[], content: string): string {
tagArr.forEach(v => {
content = makeHtmlString(v, content)
})
return content
}
export { getTopNode, makeHtmlString, createPartHtml, insertHtml }

View File

@ -1,47 +0,0 @@
/**
* @description link test
* @author wangfupeng
*/
import $ from 'jquery'
import Editor from '../../../src/editor'
import createEditor from '../../helpers/create-editor'
import mockCmdFn from '../../helpers/command-mock'
import Link from '../../../src/menus/link/index'
import { getMenuInstance } from '../../helpers/menus'
import Panel from '../../../src/menus/menu-constructors/Panel'
let editor: Editor
let linkMenu: Link
test('link 菜单:点击弹出 panel', () => {
editor = createEditor(document, 'div1')
linkMenu = getMenuInstance(editor, Link) as Link
linkMenu.clickHandler()
expect(linkMenu.panel).not.toBeNull()
})
test('link 菜单:插入链接', () => {
const panel = linkMenu.panel as Panel
const panelElem = panel.$container.elems[0]
const $panelElem = $(panelElem) // jquery 对象
// panel 里的 input 和 button 元素
const $btnInsert = $panelElem.find(":button[id^='btn-ok']") // id 以 'btn-ok' 的 button
// const $btnDel = $panelElem.find(":button[id^='btn-del']")
const $inputLink = $panelElem.find(":input[id^='input-link']")
const $inputText = $panelElem.find(":input[id^='input-text']")
// 插入链接
mockCmdFn(document)
const text = '文字'
const link = 'http://www.baidu.com/'
$inputText.val(text)
$inputLink.val(link)
$btnInsert.click()
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
expect(
editor.$textElem.html().indexOf(`<a href="${link}" target="_blank">${text}</a>`)
).toBeGreaterThan(0)
})

View File

@ -0,0 +1,49 @@
/**
* @description link test
* @author wangfupeng
*/
import $ from 'jquery'
import Editor from '../../../../src/editor'
import createEditor from '../../../helpers/create-editor'
import mockCmdFn from '../../../helpers/command-mock'
import Link from '../../../../src/menus/link/index'
import { getMenuInstance } from '../../../helpers/menus'
import Panel from '../../../../src/menus/menu-constructors/Panel'
let editor: Editor
let linkMenu: Link
describe('link菜单', () => {
test('link 菜单:点击弹出 panel', () => {
editor = createEditor(document, 'div1')
linkMenu = getMenuInstance(editor, Link) as Link
linkMenu.clickHandler()
expect(linkMenu.panel).not.toBeNull()
})
test('link 菜单:插入链接', () => {
const panel = linkMenu.panel as Panel
const panelElem = panel.$container.elems[0]
const $panelElem = $(panelElem) // jquery 对象
// panel 里的 input 和 button 元素
const $btnInsert = $panelElem.find(":button[id^='btn-ok']") // id 以 'btn-ok' 的 button
// const $btnDel = $panelElem.find(":button[id^='btn-del']")
const $inputLink = $panelElem.find(":input[id^='input-link']")
const $inputText = $panelElem.find(":input[id^='input-text']")
// 插入链接
mockCmdFn(document)
const text = '文字'
const link = 'http://www.baidu.com/'
$inputText.val(text)
$inputLink.val(link)
$btnInsert.click()
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
expect(
editor.$textElem.html().indexOf(`<a href="${link}" target="_blank">${text}</a>`)
).toBeGreaterThan(0)
})
})

View File

@ -0,0 +1,128 @@
import { insertHtml } from '../../../../src/menus/link/util'
/**
* selection对象
*/
function createSelection(
anchorNode: Node,
anchorPos: number,
focusNode: Node,
focusPos: number
): Selection {
const selection = window.getSelection() as Selection
const range = new Range()
range.setStart(anchorNode, anchorPos)
range.setEnd(focusNode, focusPos)
selection.removeAllRanges()
selection.addRange(range)
return selection
}
describe('测试insertHtml函数', () => {
test('选区anchorNode和focusNode是同一个且外层只有p标签包裹', () => {
document.body.innerHTML = `<p id="link">123456</p>`
const p = document.getElementById('link') as Node
const anchorNode = p.childNodes[0]
const selection = createSelection(anchorNode, 2, anchorNode, 5)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('345')
})
test('anchorNode和focusNode父节点p标签且两者间是一个带标签的节点', () => {
document.body.innerHTML = `<p id="link">123456<b>test</b>7890</p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0]
const focusNode = p.childNodes[len - 1]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('3456<b>test</b>789')
})
test('anchorNode和focusNode父节点为p标签且两者间是多个带标签的节点', () => {
document.body.innerHTML = `<p id="link">123456<b><i>test</i></b><i>test1</i>7890</p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0]
const focusNode = p.childNodes[len - 1]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('3456<b><i>test</i></b><i>test1</i>789')
})
test('anchorNode和focusNode父节点为p标签且两者间是带标签的节点以及文本节点', () => {
document.body.innerHTML = `<p id="link">123456<b><i>test</i></b>middle<i>test1</i>7890</p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0]
const focusNode = p.childNodes[len - 1]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('3456<b><i>test</i></b>middle<i>test1</i>789')
})
test('anchorNode和focusNode父节点为非p标签', () => {
document.body.innerHTML = `<p id="link"><b>123456</b>0000<b>7890</b></p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0].childNodes[0]
const focusNode = p.childNodes[len - 1].childNodes[0]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('<b>3456</b>0000<b>789</b>')
})
test('选中的行中最外层包裹有除了p之外的其他标签', () => {
document.body.innerHTML = `<p id="link"><i><b>123456</b>0000<b>7890</b></i></p>`
const p = document.getElementById('link') as Node
const anchorNode = p.childNodes[0].childNodes[0].childNodes[0]
const focusNode = p.childNodes[0].childNodes[2].childNodes[0]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('<i><b>3456</b>0000<b>789</b></i>')
})
test('测试背景颜色是否保存', () => {
document.body.innerHTML = `<p id="link">123456<span style="background-color: rgb(139, 170, 74);">test</span>78900</p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0]
const focusNode = p.childNodes[len - 1]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe(
'3456<span style="background-color: rgb(139, 170, 74);">test</span>789'
)
})
test('测试字体颜色是否保存', () => {
document.body.innerHTML = `<p id="link">12345678<font color="#8baa4a">test</font>67890</p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0]
const focusNode = p.childNodes[len - 1]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('345678<font color="#8baa4a">test</font>678')
})
test('测试设置后的字体是否保存', () => {
document.body.innerHTML = `<p id="link">12345<font face="华文仿宋">test</font>67890</p>`
const p = document.getElementById('link') as Node
const len = p.childNodes.length
const anchorNode = p.childNodes[0]
const focusNode = p.childNodes[len - 1]
const selection = createSelection(anchorNode, 2, focusNode, 3)
const htmlString = insertHtml(selection, p)
expect(htmlString).toBe('345<font face="华文仿宋">test</font>678')
})
})