gpt4 book ai didi

javascript - 可编辑 block 中的插入符号位置、新行和撤消

转载 作者:行者123 更新时间:2023-11-29 23:26:11 24 4
gpt4 key购买 nike

在下面的示例中,我有一个 contenteditable block 我实现了多个下划线算法。每行都从一个字母的第一次出现开始,到最后一次出现结束。例如,蓝线从第一个字母“a”开始,最后停止: enter image description here

用户可以输入新字母来更新行: enter image description here enter image description here

这个例子有3个问题:

  1. 在每次输入时,插入符号都会跳转到文本的开头。发生这种情况是因为在每次按键时我都会更新 contenteditable 中的整个 html .我尝试按照 Saving and Restoring caret position for contentEditable div 中的建议保存和恢复插入符位置.但我不确定这个解决方案是否适用于不同的浏览器。而且一般代码看起来很脏。

  2. 用户不能输入新行。在 contenteditable而不是 \n符号 <div><br/></div>已添加。

  3. 当我按下 Ctrl+Z 时,撤消不会发生。

我一般没有 Javascript 和 Web 开发经验。你能帮我解决这些问题吗?

看来一定有什么好的解决办法。互联网上有很多所见即所得的编辑器。他们一定以某种方式解决了这些问题?

也许有一些标准库可以解决这些问题?

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();
#text {
border: 1px solid silver;
padding: 1em;
line-height: 2em;
}
<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>

最佳答案

所见即所得的编辑器通常让用户编辑一个 contenteditable div 并在另一个不可编辑的 div 中巧妙地显示输出。因此,他们克服了许多复杂性,例如跟踪插入符位置和撤消-重做序列。

我添加了 #textresult显示输出和 .wrapper包含 div 和问题 1 和 3 就这样解决了。

<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 , 使用 native 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();
.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;
}
<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>

关于javascript - 可编辑 block 中的插入符号位置、新行和撤消,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49189914/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com