在下面的示例中,我有一个contenteditable
块,我在其中实现了多个下划线算法。每行首先出现一个字母,并在最后一次出现时停止。例如,蓝线从第一个字母开始" a"并最后停止:
这个例子有3个问题:
在每个输入插入符号跳转到文本开头。这是因为在每次按键时都会更新contenteditable
内的整个html。我试图按Saving and Restoring caret position for contentEditable div中的建议保存和恢复插入位置。但我不确定这个解决方案适用于不同的浏览器。通常代码看起来很脏。
用户无法输入新行。在contenteditable
代替\n
符号<div><br/></div>
已添加。
当我按 Ctrl + Z 撤消时不会发生。
我不是一般的Javascript和Web开发经验。你能帮我解决一下这些问题吗?
在我看来,必须有一些好的解决方案。互联网上有很多WYSIWYG编辑器。他们必须以某种方式解决这些问题?
也许有一些标准库可以解决这些问题?
var TEXT = $('#text');
var COLORS = [
'#1f77b4',
'#ff7f0e',
'#2ca02c',
'#d62728',
'#9467bd',
'#e377c2',
'#bcbd22',
'#17becf',
];
function makeSpan(start, stop, type, level) {
return {
start: start,
stop: stop,
type: type,
level: level
}
}
function parse(text) {
var mins = {};
var maxes = {};
for (var index = 0; index < text.length; index++) {
var char = text[index];
if (char.match(/\s/)) {
continue;
}
var min = mins[char];
if (min == undefined) {
mins[char] = index;
}
var max = maxes[char];
if ((max == undefined) || (index > max)) {
maxes[char] = index;
}
}
var spans = [];
for (var char in mins) {
var min = mins[char];
var max = maxes[char];
if (max > min) {
var span = makeSpan(min, max + 1, char);
spans.push(span);
}
}
return spans;
}
function querySpans(spans, value) {
var results = [];
spans.forEach(function(span) {
if ((span.start <= value) && (value < span.stop)) {
results.push(span)
}
});
return results;
}
function getMaxLevel(spans) {
var level = -1;
spans.forEach(function(span) {
if (level < span.level) {
level = span.level;
}
});
return level;
}
function levelSpans(spans) {
var results = [];
spans.forEach(function(span) {
var found = querySpans(results, span.start);
var level = getMaxLevel(found);
span.level = level + 1;
results.push(span);
});
return results;
}
function sortSpans(spans) {
spans.sort(function(a, b) {
return ((a.start - b.start) ||
(a.stop - b.stop) ||
a.type.localeCompare(b.type));
})
return spans;
}
function getBoundValues(spans) {
var values = [];
spans.forEach(function(span) {
values.push(span.start);
values.push(span.stop);
});
return values;
}
function uniqueValues(values) {
var set = {};
values.forEach(function(value) {
set[value] = value;
});
var values = [];
for (var key in set) {
values.push(set[key]);
}
values.sort(function(a, b) {
return a - b;
});
return values;
}
function chunkSpan(span, bounds) {
var results = [];
var previous = span.start;
bounds.forEach(function(bound) {
if ((span.start < bound) && (bound < span.stop)) {
results.push(makeSpan(
previous, bound,
span.type, span.level
));
previous = bound
}
});
results.push(makeSpan(
previous, span.stop,
span.type, span.level
));
return results;
}
function chunkSpans(spans) {
var bounds = getBoundValues(spans);
bounds = uniqueValues(bounds);
var results = [];
spans.forEach(function(span) {
var chunks = chunkSpan(span, bounds);
chunks.forEach(function(chunk) {
results.push(chunk);
});
});
return results;
}
function makeGroup(start, stop) {
return {
start: start,
stop: stop,
items: []
}
}
function groupSpans(spans) {
var previous = undefined;
var results = [];
spans.forEach(function(span) {
if (previous == undefined) {
previous = makeGroup(span.start, span.stop);
}
if (previous.start == span.start) {
previous.items.push(span);
} else {
results.push(previous)
previous = makeGroup(span.start, span.stop);
previous.items.push(span);
}
});
if (previous != undefined) {
results.push(previous)
}
return results;
}
function formatTag(span, types) {
var size = 2;
var padding = 1 + span.level * (size + 1);
var index = types.indexOf(span.type);
color = COLORS[index % COLORS.length];
return {
open: ('<span style="' +
'border-bottom: ' + size + 'px solid; ' +
'padding-bottom: ' + padding + 'px; ' +
'border-color: ' + color + '">'),
close: '</span>'
}
}
function formatSpans(text, groups, types) {
var html = '';
var previous = 0;
groups.forEach(function(group) {
html += text.slice(previous, group.start);
var tags = [];
group.items.forEach(function(span) {
tags.push(formatTag(span, types));
});
tags.forEach(function(tag) {
html += tag.open;
});
html += text.slice(group.start, group.stop);
tags.forEach(function(tag) {
html += tag.close;
});
previous = group.stop;
});
html += text.slice(previous, text.length);
return html;
}
function getSpanTypes(spans) {
var results = [];
spans.forEach(function(span) {
if (span.type != undefined) {
results.push(span.type)
}
});
return results;
}
function updateSpans(text, spans) {
types = getSpanTypes(spans);
types = uniqueValues(types);
spans = sortSpans(spans);
spans = levelSpans(spans);
spans = chunkSpans(spans);
spans = sortSpans(spans);
groups = groupSpans(spans);
html = formatSpans(text, groups, types);
TEXT.html(html);
}
function update() {
var text = TEXT.text();
var spans = parse(text);
updateSpans(text, spans);
}
TEXT.on('input propertychange', update);
TEXT.focus();
update();
&#13;
#text {
border: 1px solid silver;
padding: 1em;
line-height: 2em;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">
a d a b a a a b c c c f d
</div>
&#13;
答案 0 :(得分:1)
WYSIWYG编辑器通常让用户在一个contenteditable
div上进行编辑,并在另一个不可编辑的div中显示输出,巧妙地显示一个。因此,它们克服了许多复杂性,例如跟踪插入符号位置和撤消重做序列。
我添加#textresult
以显示输出,并添加.wrapper
以包含div和问题1&amp;这就解决了这个问题。
<div class="wrapper">
<div contenteditable="true" id="text">
a d a b a a a b c c c f d
</div>
<div id="textresult"></div>
</div>
要解决问题2,您不应使用jQuery.text
,请使用原生HTMLELement.innerText
获取包含新换行符的内容,并在跨度格式化后将其替换为<br>
。
var TEXT = $('#text');
var TEXTRESULT = $('#textresult');
var COLORS = [
'#1f77b4',
'#ff7f0e',
'#2ca02c',
'#d62728',
'#9467bd',
'#e377c2',
'#bcbd22',
'#17becf',
];
function makeSpan(start, stop, type, level) {
return {
start: start,
stop: stop,
type: type,
level: level
}
}
function parse(text) {
var mins = {};
var maxes = {};
for (var index = 0; index < text.length; index++) {
var char = text[index];
if (char.match(/\s/)) {
continue;
}
var min = mins[char];
if (min == undefined) {
mins[char] = index;
}
var max = maxes[char];
if ((max == undefined) || (index > max)) {
maxes[char] = index;
}
}
var spans = [];
for (var char in mins) {
var min = mins[char];
var max = maxes[char];
if (max > min) {
var span = makeSpan(min, max + 1, char);
spans.push(span);
}
}
return spans;
}
function querySpans(spans, value) {
var results = [];
spans.forEach(function(span) {
if ((span.start <= value) && (value < span.stop)) {
results.push(span)
}
});
return results;
}
function getMaxLevel(spans) {
var level = -1;
spans.forEach(function(span) {
if (level < span.level) {
level = span.level;
}
});
return level;
}
function levelSpans(spans) {
var results = [];
spans.forEach(function(span) {
var found = querySpans(results, span.start);
var level = getMaxLevel(found);
span.level = level + 1;
results.push(span);
});
return results;
}
function sortSpans(spans) {
spans.sort(function(a, b) {
return ((a.start - b.start) ||
(a.stop - b.stop) ||
a.type.localeCompare(b.type));
})
return spans;
}
function getBoundValues(spans) {
var values = [];
spans.forEach(function(span) {
values.push(span.start);
values.push(span.stop);
});
return values;
}
function uniqueValues(values) {
var set = {};
values.forEach(function(value) {
set[value] = value;
});
var values = [];
for (var key in set) {
values.push(set[key]);
}
values.sort(function(a, b) {
return a - b;
});
return values;
}
function chunkSpan(span, bounds) {
var results = [];
var previous = span.start;
bounds.forEach(function(bound) {
if ((span.start < bound) && (bound < span.stop)) {
results.push(makeSpan(
previous, bound,
span.type, span.level
));
previous = bound
}
});
results.push(makeSpan(
previous, span.stop,
span.type, span.level
));
return results;
}
function chunkSpans(spans) {
var bounds = getBoundValues(spans);
bounds = uniqueValues(bounds);
var results = [];
spans.forEach(function(span) {
var chunks = chunkSpan(span, bounds);
chunks.forEach(function(chunk) {
results.push(chunk);
});
});
return results;
}
function makeGroup(start, stop) {
return {
start: start,
stop: stop,
items: []
}
}
function groupSpans(spans) {
var previous = undefined;
var results = [];
spans.forEach(function(span) {
if (previous == undefined) {
previous = makeGroup(span.start, span.stop);
}
if (previous.start == span.start) {
previous.items.push(span);
} else {
results.push(previous)
previous = makeGroup(span.start, span.stop);
previous.items.push(span);
}
});
if (previous != undefined) {
results.push(previous)
}
return results;
}
function formatTag(span, types) {
var size = 2;
var padding = 1 + span.level * (size + 1);
var index = types.indexOf(span.type);
color = COLORS[index % COLORS.length];
return {
open: ('<span style="' +
'border-bottom: ' + size + 'px solid; ' +
'padding-bottom: ' + padding + 'px; ' +
'border-color: ' + color + '">'),
close: '</span>'
}
}
function formatSpans(text, groups, types) {
var html = '';
var previous = 0;
groups.forEach(function(group) {
html += text.slice(previous, group.start);
var tags = [];
group.items.forEach(function(span) {
tags.push(formatTag(span, types));
});
tags.forEach(function(tag) {
html += tag.open;
});
html += text.slice(group.start, group.stop);
tags.forEach(function(tag) {
html += tag.close;
});
previous = group.stop;
});
html += text.slice(previous, text.length);
return html;
}
function getSpanTypes(spans) {
var results = [];
spans.forEach(function(span) {
if (span.type != undefined) {
results.push(span.type)
}
});
return results;
}
function updateSpans(text, spans) {
types = getSpanTypes(spans);
types = uniqueValues(types);
spans = sortSpans(spans);
spans = levelSpans(spans);
spans = chunkSpans(spans);
spans = sortSpans(spans);
groups = groupSpans(spans);
html = formatSpans(text, groups, types);
TEXTRESULT.html(html.replace(/\n/g,'<br>'));
}
function update() {
var text = TEXT[0].innerText;
var spans = parse(text);
updateSpans(text, spans);
}
TEXT.on('input propertychange', update);
TEXT.focus();
update();
&#13;
.wrapper{
position: relative;
}
#text {
border: 1px solid silver;
padding: 1em;
line-height: 2em;
}
#textresult {
border: 1px solid transparent;
padding: 1em;
line-height: 2em;
color: transparent;
position: absolute;
top: 0;
z-index: -1;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div contenteditable="true" id="text">
a d a b a a a b c c c f d
</div>
<div id="textresult"></div>
</div>
&#13;