如何限制在contenteditable区域粘贴的文本样式?

7
我看到了这篇Stack Overflow 帖子,其中讨论了我所需要的确切内容:将文本粘贴到可编辑区域中,仅保留少量样式。我运行了那里的代码片段,并且它可以正常工作。然而,当我在我的页面上尝试时,所有样式都被删除了,包括那些我想保留的,比如粗体和斜体。经过比较代码和一些实验,我意识到不起作用的原因是我在使用外部 CSS,而不是内联 CSS。 有没有办法让它与外部 CSS 一起使用?我永远不会知道用户将发布什么样式应用于内容可编辑性的文本的来源,因此我正在寻找解决所有可能性的方法。 此外,是否有办法使它适用于拖放的文本,而不仅仅是粘贴的文本?我尝试将监听事件从“粘贴”更改为“拖放”,但我得到错误e.clipboardData is undefined

const el = document.querySelector('p');

el.addEventListener('paste', (e) => {
  // Get user's pasted data
  let data = e.clipboardData.getData('text/html') ||
      e.clipboardData.getData('text/plain');
  
  // Filter out everything except simple text and allowable HTML elements
  let regex = /<(?!(\/\s*)?(b|i|em|strong|u)[>,\s])([^>])*>/g;
  data = data.replace(regex, '');
  
  // Insert the filtered content
  document.execCommand('insertHTML', false, data);

  // Prevent the standard paste behavior
  e.preventDefault();
});
.editable {
  width: 100%;
  min-height: 20px;
  font-size: 14px;
  color: black;
  font-family: arial;
  line-height: 1.5;
  border: solid 1px black;
  margin-bottom: 30px;
  }
  
.big {
  font-size: 20px;
}

.red {
  color: red;
}

.bold {
  font-weight: bold;
}

.italic {
  text-decoration: italic;
}
<p class="editable" contenteditable></p>

<p class="notEditable">
  Try pasting this paragraph into the contenteditable paragraph above. This text includes <b>BOLD</b>, <i>ITALIC</i>, <s>STRIKE</s>, <u>UNDERLINE</u>, a <a href='#'>LINK</a>, and <span style="font-size:30px; color:red; font-family:Times New Roman">a few other styles.</span> All styles are inline, and it works as expected.
</p>

<p>Now, try pasting this paragraph with external styles. <span class="big">Big</span > <span class="red">red</span> <span class="bold">bold</span> <span class="italic">italic</span>. It no longer works.</p> 


2
当我们从VSCode复制到Google Docs时,它完全按照预期工作。可以对研究有所帮助。 - doğukan
2
我正在寻找解决所有可能性的方法。没有通用的方法可以解决所有可能性。你无法通过剪贴板获取源代码中使用的外部CSS,因为这些信息根本没有被存储。 - Teemu
1
@dgknca是正确的-尝试将OP的可运行片段中的第三段复制到Google文档中-也许数据仍然被存储。顺便说一句,我记得在以前的项目中曾经开发过一个所见即所得的编辑器(我想我们分叉了trumbowyg),可以实现这个功能-不过还需要验证。 - 95faf8e76605e973
谢谢大家的帮助。经过大量的研究和尝试,我已经认识到现在的情况下无法实现我想要的东西。我会把奖励授予最接近它的答案。 - MikeMichaels
@95faf8e76605e973 我在使用MAC OSX上的Firefox和Chrome浏览器尝试复制谷歌文档中的第三段文字,但是无法成功。 - ikiK
3个回答

2
正如其他答案所指出的,我不知道如何使用剪贴板从其他页面中获取CSS样式。但是在您自己的页面上,您可以这样做: 获取所有元素的getComputedStyle(仅CSS),过滤出所需的样式,在本例中为fontStyle和fontWeight。然后,您可以根据条件将其包装到HTML标签中,例如 fontStyle ===“italic”或 fontWeight ===“700”(加粗),textDecoration ===“underline rgb(0, 0, 0)”等。 您这样操作是因为您的正则表达式函数仅针对标签进行目标定位,甚至不包括内联CSS属性font-style: italic;。这有点可惜,因为如果您能够读取每个元素的CSS类样式并将其应用于内联样式,那么这些操作会更加容易,但现在我们需要根据条件应用HTML标签。
if ( style.fontStyle==="italic"){
element.innerHTML = "<i>" + element.innerHTML + "</i>";
;}

if ( style.fontWeight==="700"){
element.innerHTML = "<b>" + element.innerHTML + "</b>";
;}

if (style.textDecoration==="underline rgb(0, 0, 0)"){
element.innerHTML = "<u>" + element.innerHTML + "</u>";
;}
在下面的示例中,如果您复制现在,尝试使用外部样式粘贴此段落。大红色粗体斜体。它不再起作用。,您将获得粗体、下划线和斜体。您可以对其余过滤选项执行相同的操作。

const el = document.querySelector('p');

el.addEventListener('paste', (e) => {
  // Get user's pasted data
  let data = e.clipboardData.getData('text/html') ||
      e.clipboardData.getData('text/plain');
  //console.log(data)
  // Filter out everything except simple text and allowable HTML elements
  let regex = /<(?!(\/\s*)?(b|i|em|strong|u)[>,\s])([^>])*>/g;
  data = data.replace(regex, '');
 //console.log(data) 
  // Insert the filtered content
  document.execCommand('insertHTML', false, data);

  // Prevent the standard paste behavior
  e.preventDefault();
});

[...document.querySelectorAll('body *')].forEach(element=>{
const style = getComputedStyle(element)

if ( style.fontStyle==="italic"){
element.innerHTML = "<i>" + element.innerHTML + "</i>";
;}

if ( style.fontWeight==="700"){
element.innerHTML = "<b>" + element.innerHTML + "</b>";
;}

if (style.textDecoration==="underline rgb(0, 0, 0)"){
element.innerHTML = "<u>" + element.innerHTML + "</u>";
;}
});
.editable {
  width: 100%;
  min-height: 20px;
  font-size: 14px;
  color: black;
  font-family: arial;
  line-height: 1.5;
  border: solid 1px black;
  margin-bottom: 30px;
}

.big {
  font-size: 20px;
}

.red {
  color: red;
}

.bold {
  font-weight: bold;
}
.underline{
 text-decoration: underline;
}
.italic {
 font-style: italic;
}
<p class="editable" contenteditable></p>

<p class="notEditable">
  Try pasting this paragraph into the contenteditable paragraph above. This text includes <b>BOLD</b>, <i>ITALIC</i>, <s>STRIKE</s>, <u>UNDERLINE</u>, a <a href='#'>LINK</a>, and <span style="font-size:30px; color:red; font-family:Times New Roman">a few other styles.</span>  All styles are inline, and it works as expected.
</p>

<p id="container"><span class="underline">Now</span>, try pasting this paragraph with external styles. <span class="big">Big</span > <span class="red">red</span> <span class="bold" >bold</span> <span class="italic">italic</span>. It no longer works.</p>


这是一个有趣的方法。但它只解决了一半的问题 - 从我的页面粘贴文本。当文本来源于具有外部CSS的不同网站时,它仍然无法正常工作。 - MikeMichaels
@MikeMichaels 我尝试了更多的研究,但没有找到任何有用的信息。我想到一个主意,如果您能让用户提供源并在iframe中加载它,以运行iframe中的脚本,但是存在跨域阻止,我无法找到绕过它的方法。不确定我所要求的是否可能实现。 - ikiK

1

很遗憾,没有办法从外部源保留类的属性。如果您打印剪贴板的内容,您会看到您接收到的就是外部页面上的原始HTML内容,例如:

<div class="some-class">this is the text</div>

浏览器不会将类属性内联!而且由于内容来自外部源,您对其没有控制权。

另一方面,如果内容来自您的页面(因此定义了类),则可以解析接收到的HTML并过滤CSS属性,仅保留您想要的内容。以下是使用纯JavaScript的代码示例,无需任何库(也可在Codepen上使用):

const targetEditable = document.querySelector('p');

targetEditable.addEventListener('paste', (event) => {
    let data = event.clipboardData.getData('text/html') ||
        event.clipboardData.getData('text/plain');

    // Filter the string using your already existing rules
    // But allow <p> and <div>
    let regex = /<(?!(\/\s*)?(div|b|i|em|strong|u|p)[>,\s])([^>])*>/g;
    data = data.replace(regex, '');

    const newElement = createElementFromHTMLString(data);
    const cssContent = generateFilteredCSS(newElement);
    addCssToDocument(cssContent);

    document.execCommand('insertHTML', false, newElement.innerHTML);
    event.preventDefault();
});

// Scan the HTML elements recursively and generate CSS classes containing only the allowed properties
function generateFilteredCSS(node) {
    const newClassName = randomString(5);
    let content = `.${newClassName}{\n`;

    if (node.className !== undefined && node.className !== '') {
        // Get an element that has the class
        const elemOfClass = document.getElementsByClassName(node.className)[0];
        // Get the computed style properties
        const styles = window.getComputedStyle(elemOfClass);

        // Properties whitelist, keep only those
        const propertiesToKeep = ['font-weight'];
        for (const property of propertiesToKeep) {
            content += `${property}: ${styles.getPropertyValue(property)};\n`;
        }
    }
    content += '}\n';
    node.className = newClassName;

    for (const child of node.childNodes) {
        content += generateFilteredCSS(child);
    }

    return content;
}

function createElementFromHTMLString(htmlString) {
    var div = document.createElement('div');
    div.innerHTML = htmlString.trim();
    return div;
}

function addCssToDocument(cssContent) {
    var element = document.createElement("style");
    element.innerHTML = cssContent;
    var header = document.getElementsByTagName("HEAD")[0];
    header.appendChild(element);
}

function randomString(length) {
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
}
.editable {
  width: 100%;
  min-height: 20px;
  font-size: 14px;
  color: black;
  font-family: arial;
  line-height: 1.5;
  border: solid 1px black;
  margin-bottom: 30px;
  }
  
.red-bg {
  background-color: red;
  font-weight: bold;
}
<p class="editable" contenteditable></p>

<p class="red-bg test">
  This is some text
</p>

关于拖放功能,您需要在drop事件监听器中使用event.dataTransfer.getData(),其余部分相同。 参考资料
  1. 如何从HTML字符串生成DOM元素
  2. 如何使用Javascript在运行时添加CSS类
  3. 如何在Javascript中生成随机字符串(唯一ID)
  4. 拖放数据传输

0

你可以实现你想要的,但需要考虑一组额外的问题。

首先,您需要添加一个“添加源资产”按钮,用户将提供源页面的URL...然后,您将获取源HTML并匹配粘贴的内容与源内容,然后查询源元素以获取所有相关属性、引用类等。

您甚至可以处理源URL的标记以提取其CSS和图像作为引用...基本上根据可接受媒体和样式的白名单来启动资源。

根据您的需求和DOM Kung Fu,您可以提取所有“消耗”的CSS并导入这些样式...这真的取决于您想走多远。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,