文章目录
不建议使用官网的 CDN,亲测不是很稳定。官网安装文档
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Page title</title>
<link href="/lib/layui-2.7.0/css/layui.css" rel="stylesheet">
<style>
</style>
</head>
<body>
<script src="/lib/layui-2.7.0/layui.js"></script>
<script>
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Page title</title>
<link href="/lib/layui-2.7.0/css/layui.css" rel="stylesheet">
<link href="/lib/wangeditor/style.css" rel="stylesheet">
<style>
</style>
</head>
<body>
<div>
<!-- 工具栏 -->
<div id="toolbar-container"></div>
<!-- 编辑器 -->
<div id="editor-container"></div>
</div>
<script src="/lib/layui-2.7.0/layui.js"></script>
<script src="/lib/wangeditor/index.js"></script>
<script>
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Page title</title>
<link href="/lib/layui-2.7.0/css/layui.css" rel="stylesheet">
<link href="/lib/wangeditor/style.css" rel="stylesheet">
<style>
body {
padding: 20px;
}
</style>
</head>
<body>
<div style="border: 1px solid #e1e1e1;">
<!-- 工具栏 -->
<div id="toolbar-container" style="border-bottom: 1px solid #e1e1e1;"></div>
<!-- 编辑器 -->
<div id="editor-container" style="height: 400px;"></div>
</div>
<script src="/lib/layui-2.7.0/layui.js"></script>
<script src="/lib/wangeditor/index.js"></script>
<script>
const {
createEditor,
createToolbar
} = window.wangEditor;
// 编辑器配置
const editorConfig = {};
editorConfig.placeholder = '请输入内容';
// 工具栏配置
const toolbarConfig = {};
// 创建编辑器
const editor = createEditor({
selector: '#editor-container',
config: editorConfig,
mode: 'default'
});
// 创建工具栏
const toolbar = createToolbar({
editor,
selector: '#toolbar-container',
config: toolbarConfig,
mode: 'default'
});
</script>
</body>
</html>
至此 wangEditor 编辑器已经创建成功了。刷新页面后,就可以看到和官网一样界面效果了。
结果: 文字样式部分丢失,表格尚可,列表溢出,图片直接丢失。
针对上面的实践结果,逐个寻找解决方案:
样式丢失
体现:如文本样式部分丢失、列表溢出等。
为什么会丢失呢?
这个问题其实很容易回答,word 中的文本样式肯定与我们平时写的 HTML 样式有差异,会导致样式丢失。
另外一方面,我们平时写的 HTML 格式非常灵活,但是 wangEditor 无法兼容所有的 HTML 格式,这一点官方文档有特别标红说明。也就是说,我们在编辑器输入内容时,wangEdior 会做一些处理(过滤,筛选,转换等)。
例如,wangEditor 可以识别
<strong>hello</strong>为加粗,但无法识别<span style="font-weight: bold;">hello</span>等其他加粗方式。
解决措施
// 例如缩进会超出边框,直接过滤掉相关样式即可
html = html.replace(/text\-indent:\-(.*?)pt/gi, '')
图片丢失
【开门见山】
通过 wangEditor 的编辑器配置 API 中的 customPaste 自定义粘贴。
通过该 API 可以阻止编辑器的默认粘贴处理逻辑,可以实现自己的粘贴逻辑。
即:这里的自己的粘贴逻辑,就是解决图片粘贴的关键所在。
~~原来,其实道理就这么简单!!
// 其他代码
....
<script>
const {
createEditor,
createToolbar
} = window.wangEditor;
// 编辑器配置
const editorConfig = {};
editorConfig.placeholder = '请输入内容';
// 自定义粘贴
editorConfig.customPaste = (editor, event) => {
// 获取粘贴的html部分(??没错粘贴word时候,一部分内容就是html),该部分包含了图片img标签
let html = event.clipboardData.getData('text/html');
// 获取rtf数据(从word、wps复制粘贴时有),复制粘贴过程中图片的数据就保存在rtf中
const rtf = event.clipboardData.getData('text/rtf');
if (html && rtf) { // 该条件分支即表示要自定义word粘贴
// 列表缩进会超出边框,直接过滤掉
html = html.replace(/text\-indent:\-(.*?)pt/gi, '')
// 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合
const imgSrcs = findAllImgSrcsFromHtml(html);
// 如果有
if (imgSrcs && Array.isArray(imgSrcs) && imgSrcs.length) {
// 从rtf内容中查找图片数据
const rtfImageData = extractImageDataFromRtf(rtf);
// 如果找到
if (rtfImageData.length) {
// TODO:此处可以将图片上传到自己的服务器上
// 执行替换:将html内容中的img标签的src替换成ref中的图片数据,如果上面上传了则为图片路径
html = replaceImagesFileSourceWithInlineRepresentation(html, imgSrcs, rtfImageData)
editor.dangerouslyInsertHtml(html);
}
}
// 阻止默认的粘贴行为
event.preventDefault();
return false;
} else {
return true;
}
}
// 工具栏配置
const toolbarConfig = {};
// 创建编辑器
const editor = createEditor({
selector: '#editor-container',
config: editorConfig,
mode: 'default'
});
// 创建工具栏
const toolbar = createToolbar({
editor,
selector: '#toolbar-container',
config: toolbarConfig,
mode: 'default'
});
</script>
.....
// 其他代码
<script>
/**
* 从html代码中匹配返回图片标签img的属性src的值的集合
* @param htmlData
* @return Array
*/
function findAllImgSrcsFromHtml(htmlData) {
let imgReg = /<img.*?(?:>|\/>)/gi; //匹配图片中的img标签
let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src
let arr = htmlData.match(imgReg); //筛选出所有的img
if (!arr || (Array.isArray(arr) && !arr.length)) {
return false;
}
let srcArr = [];
for (let i = 0; i < arr.length; i++) {
let src = arr[i].match(srcReg);
// 获取图片地址
srcArr.push(src[1]);
}
return srcArr;
}
/**
* 从rtf内容中匹配返回图片数据的集合
* @param rtfData
* @return Array
*/
function extractImageDataFromRtf(rtfData) {
if (!rtfData) {
return [];
}
const regexPictureHeader = /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/
const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g');
const images = rtfData.match(regexPicture);
const result = [];
if (images) {
for (const image of images) {
let imageType = false;
if (image.includes('\\pngblip')) {
imageType = 'image/png';
} else if (image.includes('\\jpegblip')) {
imageType = 'image/jpeg';
}
if (imageType) {
result.push({
hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
type: imageType
});
}
}
}
return result;
}
/**
* 将html内容中img标签的属性值替换
* @param htmlData html内容
* @param imageSrcs html中img的属性src的值的集合
* @param imagesHexSources rtf中图片数据的集合,与html内容中的img标签对应
* @param isBase64Data 是否是Base64的图片数据
* @return String
*/
function replaceImagesFileSourceWithInlineRepresentation(htmlData, imageSrcs, imagesHexSources, isBase64Data =
true) {
if (imageSrcs.length === imagesHexSources.length) {
for (let i = 0; i < imageSrcs.length; i++) {
const newSrc = isBase64Data ?
`data:${imagesHexSources[i].type};base64,${_convertHexToBase64(imagesHexSources[i].hex)}` :
imagesHexSources[i];
htmlData = htmlData.replace(imageSrcs[i], newSrc);
}
}
return htmlData;
}
/**
* 十六进制转base64
*/
function _convertHexToBase64(hexString) {
return btoa(hexString.match(/\w{2}/g).map(char => {
return String.fromCharCode(parseInt(char, 16));
}).join(''));
}
</script>
本文基本上完美实现了从 word 复制粘贴图片的需求。
至于样式部分丢失的问题,目前不可能 100%解决,wangEditor 本身解析内容的原理导致,就目前而言,只能尽可能对损失或丢失的样式做一些额外的处理,使其接近复制粘贴的预期。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]
我看到这个错误:translationmissing:da.datetime.distance_in_words.about_x_hours我的语言环境文件:http://pastie.org/2944890我的看法:我已将其添加到我的application.rb中:config.i18n.load_path+=Dir[Rails.root.join('my','locales','*.{rb,yml}').to_s]config.i18n.default_locale=:da如果我删除I18配置,帮助程序会处理英语。更新:我在config/enviorments/devolpment
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s
我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来