fix: 修复a标签样式重置
This commit is contained in:
parent
dc96c34d0e
commit
dafee8fd79
|
@ -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
|
||||
// 文本为空,则用链接代替
|
||||
|
|
|
@ -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 }
|
|
@ -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)
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -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')
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue