如何在 Summernote 中插入占位符元素?
Posted
技术标签:
【中文标题】如何在 Summernote 中插入占位符元素?【英文标题】:How to insert a placeholder element in Summernote? 【发布时间】:2016-06-24 01:19:37 【问题描述】:我正在为 Summernote 所见即所得编辑器(版本 0.8.1)开发一个插件,用于将 iframe 元素插入代码中。
使用提供的示例,我设法在菜单中找到了插件按钮,它会打开一个对话框,我可以在其中输入 URL 和标题。给源加个iframe Tag是没问题的,但这不是我想要的。
我想在代码中插入一个占位符,带有类似(或类似)这样的标记:
<div class="ext-iframe-subst" data-src="http://www.test.example" data-title="iframe title"><span>iframe URL: http://www.test.example</span></div>
现在,summernote 允许我编辑 span 的内容,但我想要一个无法在编辑器中修改的占位符。
如何在编辑器中插入一个占位符,它具有以下属性:
可作为单个块进行编辑(可以通过单个删除来删除) 点击后,我可以打开类似于链接或图像弹出框的弹出框来调整大小 f.i. 内部内容不可修改这是我目前所拥有的:
// Extends plugins for adding iframes.
// - plugin is external module for customizing.
$.extend($.summernote.plugins,
/**
* @param Object context - context object has status of editor.
*/
'iframe': function (context)
var self = this;
// ui has renders to build ui elements.
// - you can create a button with `ui.button`
var ui = $.summernote.ui;
var $editor = context.layoutInfo.editor;
var options = context.options;
var lang = options.langInfo;
// add context menu button
context.memo('button.iframe', function ()
return ui.button(
contents: '<i class="fa fa-newspaper-o"/>',
tooltip: lang.iframe.iframe,
click: context.createInvokeHandler('iframe.show')
).render();
);
// This events will be attached when editor is initialized.
this.events =
// This will be called after modules are initialized.
'summernote.init': function (we, e)
console.log('IFRAME initialized', we, e);
,
// This will be called when user releases a key on editable.
'summernote.keyup': function (we, e)
console.log('IFRAME keyup', we, e);
;
// This method will be called when editor is initialized by $('..').summernote();
// You can create elements for plugin
this.initialize = function ()
var $container = options.dialogsInBody ? $(document.body) : $editor;
var body = '<div class="form-group row-fluid">' +
'<label>' + lang.iframe.url + '</label>' +
'<input class="ext-iframe-url form-control" type="text" />' +
'<label>' + lang.iframe.title + '</label>' +
'<input class="ext-iframe-title form-control" type="text" />' +
'<label>' + lang.iframe.alt + '</label>' +
'<textarea class="ext-iframe-alt form-control" placeholder="' + lang.iframe.alttext + '" rows=""10""></textarea>' +
'</div>';
var footer = '<button href="#" class="btn btn-primary ext-iframe-btn disabled" disabled>' + lang.iframe.insert + '</button>';
this.$dialog = ui.dialog(
title: lang.iframe.insert,
fade: options.dialogsFade,
body: body,
footer: footer
).render().appendTo($container);
;
// This methods will be called when editor is destroyed by $('..').summernote('destroy');
// You should remove elements on `initialize`.
this.destroy = function ()
this.$dialog.remove();
this.$dialog = null;
;
this.bindEnterKey = function ($input, $btn)
$input.on('keypress', function (event)
if (event.keyCode === 13) //key.code.ENTER)
$btn.trigger('click');
);
;
this.createIframeNode = function (data)
var $iframeSubst = $('<div class="ext-iframe-subst"><span>' + lang.iframe.iframe + '</span></div>');
$iframeSubst.attr("data-src", data.url).attr("data-title", data.title);
return $iframeSubst[0];
;
this.show = function ()
var text = context.invoke('editor.getSelectedText');
context.invoke('editor.saveRange');
console.log("iframe.getInfo: " + text);
this
.showIframeDialog(text)
.then(function (data)
// [workaround] hide dialog before restore range for IE range focus
ui.hideDialog(self.$dialog);
context.invoke('editor.restoreRange');
// build node
var $node = self.createIframeNode(data);
if ($node)
// insert iframe node
context.invoke('editor.insertNode', $node);
)
.fail(function ()
context.invoke('editor.restoreRange');
);
;
this.showIframeDialog = function (text)
return $.Deferred(function (deferred)
var $iframeUrl = self.$dialog.find('.ext-iframe-url');
var $iframeTitle = self.$dialog.find('.ext-iframe-title');
var $iframeBtn = self.$dialog.find('.ext-iframe-btn');
ui.onDialogShown(self.$dialog, function ()
context.triggerEvent('dialog.shown');
$iframeUrl.val(text).on('input', function ()
ui.toggleBtn($iframeBtn, $iframeUrl.val());
).trigger('focus');
$iframeBtn.click(function (event)
event.preventDefault();
deferred.resolve( url: $iframeUrl.val(), title: $iframeTitle.val() );
);
self.bindEnterKey($iframeUrl, $iframeBtn);
);
ui.onDialogHidden(self.$dialog, function ()
$iframeUrl.off('input');
$iframeBtn.off('click');
if (deferred.state() === 'pending')
deferred.reject();
);
ui.showDialog(self.$dialog);
);
;
);
// add localization texts
$.extend($.summernote.lang['en-US'],
iframe:
iframe: 'iframe',
url: 'iframe URL',
title: 'title',
insert: 'insert iframe',
alt: 'Text alternative',
alttext: 'you should provide a text alternative for the content in this iframe.',
test: 'Test'
);
【问题讨论】:
只需在 Summernote 对象中添加“占位符”选项即可。 【参考方案1】:尝试使用
$(document).ready(function()
$('#summernote').summernote(
placeholder: 'Hello stand alone ui',
tabsize: 2,
height: 120
);
);
<!-- include libraries(jQuery, bootstrap) -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<div id="summernote"></div>
【讨论】:
“占位符”并不是指您可以在输入和文本区域中拥有的占位符属性。想想一个邮件合并文档和你合并它的数据。在文档中放置占位符以标记每个数据文件应插入的位置。【参考方案2】:您可以在您的span
元素上使用contenteditable
属性,它会起作用并将iframe
插件html 保留在编辑器中,当点击Del 或Backspace 键。
Github repository 中有一些演示插件,其中有一个演示了对话框和弹出框编辑的用法,您可以检查逻辑和代码here。
在createIframeNode
中我们创建元素并设置数据属性
this.createIframeNode = function (data)
var $iframeSubst = $(
'<div class="ext-iframe-subst"><span contenteditable="false">' +
lang.iframe.url + ': ' + data.url +
'</span></div>'
);
$iframeSubst
.attr("data-src", data.url)
.attr("data-title", data.title);
return $iframeSubst[0];
;
我们还创建了一个currentEditing
变量来在弹出菜单弹出时保存光标下的元素,以便弹出对话框知道我们正在编辑一个元素而不是创建一个新元素。
在updateIframeNode
中,我们使用currentEditing
元素进行更新
这里我们只重新创建span
元素,因为currentEditing
是实际的div.ext-iframe-subst
,然后我们更新数据属性:
this.updateIframeNode = function (data)
$(currentEditing).html(
'<span contenteditable="false">' +
lang.iframe.url + ': ' + data.url +
'</span>'
)
$(currentEditing)
.attr("data-src", data.url)
.attr("data-title", data.title);
完整的工作插件
运行代码 sn-p 并尝试使用带有方形图标的按钮插入 iframe。您可以编辑现有的 iFrame 元素,然后将块一起删除。
/**
* @param Object context - context object has status of editor.
*/
var iframePlugin = function (context)
var self = this;
// ui has renders to build ui elements.
// - you can create a button with `ui.button`
var ui = $.summernote.ui;
var dom = $.summernote.dom;
var $editor = context.layoutInfo.editor;
var currentEditing = null;
var options = context.options;
var lang = options.langInfo;
// add context menu button
context.memo('button.iframe', function ()
return ui.button(
contents: '<i class="note-icon-frame"/>',
tooltip: lang.iframe.iframe,
click: (event) =>
currentEditing = null;
context.createInvokeHandler('iframe.show')(event);
).render();
);
context.memo('button.iframeDialog', function ()
return ui.button(
contents: '<i class="note-icon-frame"/>',
tooltip: lang.iframe.iframe,
click: (event) =>
context.createInvokeHandler('iframe.show')(event);
// currentEditing
).render();
);
// This events will be attached when editor is initialized.
this.events =
// This will be called after modules are initialized.
'summernote.init': function (we, e)
$('data.ext-iframe', e.editable).each(function() self.setContent($(this)); );
,
// This will be called when user releases a key on editable.
'summernote.keyup summernote.mouseup summernote.change summernote.scroll': function()
self.update();
,
'summernote.dialog.shown': function()
self.hidePopover();
,
;
// This method will be called when editor is initialized by $('..').summernote();
// You can create elements for plugin
this.initialize = function ()
var $container = options.dialogsInBody ? $(document.body) : $editor;
var body = '<div class="form-group row-fluid">' +
'<label>' + lang.iframe.url + '</label>' +
'<input class="ext-iframe-url form-control" type="text" />' +
'<label>' + lang.iframe.title + '</label>' +
'<input class="ext-iframe-title form-control" type="text" />' +
// '<label>' + lang.iframe.alt + '</label>' +
// '<textarea class="ext-iframe-alt form-control" placeholder="' + lang.iframe.alttext + '" rows=""10""></textarea>' +
'</div>';
var footer = '<button href="#" class="btn btn-primary ext-iframe-btn disabled" disabled>' + lang.iframe.insertOrUpdate + '</button>';
this.$dialog = ui.dialog(
title: lang.iframe.insert,
fade: options.dialogsFade,
body: body,
footer: footer
).render().appendTo($container);
// create popover
this.$popover = ui.popover(
className: 'ext-iframe-popover',
).render().appendTo('body');
var $content = self.$popover.find('.popover-content');
context.invoke('buttons.build', $content, options.popover.iframe);
;
// This methods will be called when editor is destroyed by $('..').summernote('destroy');
// You should remove elements on `initialize`.
this.destroy = function ()
self.$popover.remove();
self.$popover = null;
self.$dialog.remove();
self.$dialog = null;
;
this.bindEnterKey = function ($input, $btn)
$input.on('keypress', function (event)
if (event.keyCode === 13) //key.code.ENTER)
$btn.trigger('click');
);
;
self.update = function()
// Prevent focusing on editable when invoke('code') is executed
if (!context.invoke('editor.hasFocus'))
self.hidePopover();
return;
var rng = context.invoke('editor.createRange');
var visible = false;
var $data = $(rng.sc).closest('div.ext-iframe-subst');
if ($data.length)
currentEditing = $data[0];
var pos = dom.posFromPlaceholder(currentEditing);
const containerOffset = $(options.container).offset();
pos.top -= containerOffset.top;
pos.left -= containerOffset.left;
self.$popover.css(
display: 'block',
left: pos.left,
top: pos.top,
);
// save editor target to let size buttons resize the container
context.invoke('editor.saveTarget', currentEditing);
visible = true;
// hide if not visible
if (!visible)
self.hidePopover();
;
self.hidePopover = function()
self.$popover.hide();
;
this.createIframeNode = function (data)
var $iframeSubst = $(
'<div class="ext-iframe-subst"><span contenteditable="false">' +
lang.iframe.url + ': ' + data.url +
'</span></div>'
);
$iframeSubst.attr("data-src", data.url).attr("data-title", data.title);
return $iframeSubst[0];
;
this.updateIframeNode = function (data)
$(currentEditing).html(
'<span contenteditable="false">' +
lang.iframe.url + ': ' + data.url +
'</span>'
)
$(currentEditing).attr("data-src", data.url).attr("data-title", data.title);
this.show = function ()
var text = context.invoke('editor.getSelectedText');
context.invoke('editor.saveRange');
this
.showIframeDialog(text)
.then(function (data)
// [workaround] hide dialog before restore range for IE range focus
ui.hideDialog(self.$dialog);
context.invoke('editor.restoreRange');
if (currentEditing)
self.updateIframeNode(data);
else
// build node
var $node = self.createIframeNode(data);
if ($node)
// insert iframe node
context.invoke('editor.insertNode', $node);
)
.fail(function ()
context.invoke('editor.restoreRange');
);
;
this.showIframeDialog = function (text)
return $.Deferred(function (deferred)
var $iframeUrl = self.$dialog.find('.ext-iframe-url');
var $iframeTitle = self.$dialog.find('.ext-iframe-title');
var $iframeBtn = self.$dialog.find('.ext-iframe-btn');
ui.onDialogShown(self.$dialog, function ()
context.triggerEvent('dialog.shown');
var dataSrc = currentEditing ? $(currentEditing).attr('data-src') : '';
var dataTitle = currentEditing ? $(currentEditing).attr('data-title') : '';
$iframeTitle.val(dataTitle);
$iframeUrl.val(dataSrc).on('input', function ()
ui.toggleBtn($iframeBtn, $iframeUrl.val());
).trigger('focus');
$iframeBtn.click(function (event)
event.preventDefault();
deferred.resolve( url: $iframeUrl.val(), title: $iframeTitle.val() );
);
self.bindEnterKey($iframeUrl, $iframeBtn);
);
ui.onDialogHidden(self.$dialog, function ()
$iframeUrl.off('input');
$iframeBtn.off('click');
if (deferred.state() === 'pending')
deferred.reject();
);
ui.showDialog(self.$dialog);
);
;
// Extends plugins for adding iframes.
// - plugin is external module for customizing.
$.extend(true, $.summernote,
plugins:
iframe: iframePlugin,
,
options:
popover:
iframe: [
['iframe', ['iframeDialog']],
],
,
,
lang:
'en-US':
iframe:
iframe: 'iframe',
url: 'iframe URL',
title: 'title',
insertOrUpdate: 'insert/update iframe',
alt: 'Text alternative',
alttext: 'you should provide a text alternative for the content in this iframe.',
test: 'Test',
,
,
,
);
$(document).ready(function()
$('#editor').summernote(
height: 200,
toolbar: [
['operation', ['undo', 'redo']],
['style', ['bold', 'italic', 'underline']],
['color', ['color']],
['insert', ['iframe', 'link','picture', 'hr']],
['view', ['codeview']],
],
);
);
<!-- include libraries(jQuery, bootstrap) -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<div id="editor">Hello Summernote</div>
【讨论】:
【参考方案3】:默认情况下,summernote 创建它的编辑器 div 包含内容
/p> 所以如果你清空summernote onload 那么问题就会得到解决。有删除内容的代码:
$(function ()
$('#summernote').summernote(
inheritPlaceholder: true,
placeholder: 'Enter your Inquiry here...',
);
$('#summernote').summernote('code', ''); //This line remove summercontent when load
);
【讨论】:
以上是关于如何在 Summernote 中插入占位符元素?的主要内容,如果未能解决你的问题,请参考以下文章