mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 12:33:40 +00:00
compose: Format button for spoilers.
Note that toggling off spoiler formatting works if either all the content inside, or the header (if it exists) or both are selected. Co-authored-by: N-Shar-ma <bablinaneh@gmail.com>
This commit is contained in:
committed by
Tim Abbott
parent
df143137ef
commit
2db8563a7e
@@ -580,6 +580,151 @@ export function format_text($textarea, type, inserted_content) {
|
||||
wrapSelection(field, syntax_start, syntax_end);
|
||||
};
|
||||
|
||||
const format_spoiler = () => {
|
||||
let spoiler_syntax_start = "```spoiler \n";
|
||||
const spoiler_syntax_start_without_break = "```spoiler ";
|
||||
let spoiler_syntax_end = "\n```";
|
||||
|
||||
// For when the entire spoiler block (with no header) is selected.
|
||||
if (is_inner_text_formatted(spoiler_syntax_start, spoiler_syntax_end)) {
|
||||
text =
|
||||
text.slice(0, range.start) +
|
||||
text.slice(
|
||||
range.start + spoiler_syntax_start.length,
|
||||
range.end - spoiler_syntax_end.length,
|
||||
) +
|
||||
text.slice(range.end);
|
||||
if (text.startsWith("\n")) {
|
||||
text = text.slice(1);
|
||||
}
|
||||
set(field, text);
|
||||
field.setSelectionRange(
|
||||
range.start,
|
||||
range.end - spoiler_syntax_start.length - spoiler_syntax_end.length,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For when the entire spoiler block (with a header) is selected.
|
||||
if (is_inner_text_formatted(spoiler_syntax_start_without_break, spoiler_syntax_end)) {
|
||||
text =
|
||||
text.slice(0, range.start) +
|
||||
text.slice(
|
||||
range.start + spoiler_syntax_start_without_break.length,
|
||||
range.end - spoiler_syntax_end.length,
|
||||
) +
|
||||
text.slice(range.end);
|
||||
if (text.startsWith("\n")) {
|
||||
text = text.slice(1);
|
||||
}
|
||||
set(field, text);
|
||||
field.setSelectionRange(
|
||||
range.start,
|
||||
range.end - spoiler_syntax_start_without_break.length - spoiler_syntax_end.length,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For when the text (including the header) inside a spoiler block is selected.
|
||||
if (is_selection_formatted(spoiler_syntax_start_without_break, spoiler_syntax_end)) {
|
||||
text =
|
||||
text.slice(0, range.start - spoiler_syntax_start_without_break.length) +
|
||||
selected_text +
|
||||
text.slice(range.end + spoiler_syntax_end.length);
|
||||
set(field, text);
|
||||
field.setSelectionRange(
|
||||
range.start - spoiler_syntax_start_without_break.length,
|
||||
range.end - spoiler_syntax_start_without_break.length,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For when only the text inside a spoiler block (without a header) is selected.
|
||||
if (is_selection_formatted(spoiler_syntax_start, spoiler_syntax_end)) {
|
||||
text =
|
||||
text.slice(0, range.start - spoiler_syntax_start.length) +
|
||||
selected_text +
|
||||
text.slice(range.end + spoiler_syntax_end.length);
|
||||
set(field, text);
|
||||
field.setSelectionRange(
|
||||
range.start - spoiler_syntax_start.length,
|
||||
range.end - spoiler_syntax_start.length,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const is_inner_content_selected = () =>
|
||||
range.start >= spoiler_syntax_start.length &&
|
||||
text.length - range.end >= spoiler_syntax_end.length &&
|
||||
text.slice(range.end, range.end + spoiler_syntax_end.length) === spoiler_syntax_end &&
|
||||
text[range.start - 1] === "\n" &&
|
||||
text.lastIndexOf(spoiler_syntax_start_without_break, range.start - 1) ===
|
||||
text.lastIndexOf("\n", range.start - 2) + 1;
|
||||
|
||||
// For when only the text inside a spoiler block (with a header) is selected.
|
||||
if (is_inner_content_selected()) {
|
||||
const new_selection_start = text.lastIndexOf(
|
||||
spoiler_syntax_start_without_break,
|
||||
range.start,
|
||||
);
|
||||
text =
|
||||
text.slice(0, new_selection_start) +
|
||||
text.slice(
|
||||
new_selection_start + spoiler_syntax_start_without_break.length,
|
||||
range.start,
|
||||
) +
|
||||
selected_text +
|
||||
text.slice(range.end + spoiler_syntax_end.length);
|
||||
set(field, text);
|
||||
field.setSelectionRange(
|
||||
new_selection_start,
|
||||
range.end - spoiler_syntax_start_without_break.length,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const is_header_selected = () =>
|
||||
range.start >= spoiler_syntax_start_without_break.length &&
|
||||
text.slice(range.start - spoiler_syntax_start_without_break.length, range.start) ===
|
||||
spoiler_syntax_start_without_break &&
|
||||
text.length - range.end >= spoiler_syntax_end.length &&
|
||||
text[range.end] === "\n";
|
||||
|
||||
// For when only the header of a spoiler block is selected.
|
||||
if (is_header_selected()) {
|
||||
const header = range.text;
|
||||
const new_range_end = text.indexOf(spoiler_syntax_end, range.start);
|
||||
const new_range_start = header ? range.start : range.start + 1;
|
||||
text =
|
||||
text.slice(0, range.start - spoiler_syntax_start_without_break.length) +
|
||||
text.slice(new_range_start, new_range_end) +
|
||||
text.slice(new_range_end + spoiler_syntax_end.length);
|
||||
set(field, text);
|
||||
field.setSelectionRange(
|
||||
new_range_start - spoiler_syntax_start_without_break.length - (header ? 0 : 1),
|
||||
new_range_end - spoiler_syntax_start_without_break.length - (header ? 0 : 1),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (range.start > 0 && text[range.start - 1] !== "\n") {
|
||||
spoiler_syntax_start = "\n" + spoiler_syntax_start;
|
||||
}
|
||||
if (range.end < text.length && text[range.end] !== "\n") {
|
||||
spoiler_syntax_end = spoiler_syntax_end + "\n";
|
||||
}
|
||||
|
||||
const spoiler_syntax_start_with_header = spoiler_syntax_start_without_break + "Header\n";
|
||||
|
||||
// Otherwise, we don't have spoiler syntax, so we add it.
|
||||
wrapSelection(field, spoiler_syntax_start_with_header, spoiler_syntax_end);
|
||||
|
||||
field.setSelectionRange(
|
||||
range.start + spoiler_syntax_start.length - 1,
|
||||
range.start + spoiler_syntax_start_with_header.length - 1,
|
||||
);
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case "bold":
|
||||
// Ctrl + B: Toggle bold syntax on selection.
|
||||
@@ -739,6 +884,9 @@ export function format_text($textarea, type, inserted_content) {
|
||||
format(quote_syntax_start, quote_syntax_end);
|
||||
break;
|
||||
}
|
||||
case "spoiler":
|
||||
format_spoiler();
|
||||
break;
|
||||
case "latex": {
|
||||
const inline_latex_syntax = "$$";
|
||||
let block_latex_syntax_start = "```math\n";
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<a role="button" data-format-type="bulleted" class="compose_control_button fa fa-list-ul formatting_button" aria-label="{{t 'Bulleted list' }}" {{#unless preview_mode_on}} tabindex=0 {{/unless}} data-tippy-content="{{t 'Bulleted list' }}"></a>
|
||||
<div class="divider">|</div>
|
||||
<a role="button" data-format-type="quote" class="compose_control_button fa fa-quote-left formatting_button" aria-label="{{t 'Quote' }}" {{#unless preview_mode_on}} tabindex=0 {{/unless}} data-tippy-content="{{t 'Quote' }}"></a>
|
||||
<a role="button" data-format-type="spoiler" class="compose_control_button fa fa-minus formatting_button" aria-label="{{t 'Spoiler' }}" {{#unless preview_mode_on}} tabindex=0 {{/unless}} data-tippy-content="{{t 'Spoiler' }}"></a>
|
||||
<a role="button" data-format-type="code" class="compose_control_button fa fa-code formatting_button" aria-label="{{t 'Code' }}" {{#unless preview_mode_on}} tabindex=0 {{/unless}} data-tippy-content="{{t 'Code' }}"></a>
|
||||
<a role="button" data-format-type="latex" class="compose_control_button fa fa-superscript formatting_button" aria-label="{{t 'LaTeX' }}" {{#unless preview_mode_on}} tabindex=0 {{/unless}} data-tippy-content="{{t 'LaTeX' }}"></a>
|
||||
<div class="divider">|</div>
|
||||
|
||||
@@ -1167,6 +1167,137 @@ run_test("format_text - quote", ({override}) => {
|
||||
assert.equal(wrap_selection_called, false);
|
||||
});
|
||||
|
||||
run_test("format_text - spoiler", ({override}) => {
|
||||
override(text_field_edit, "set", (_field, text) => {
|
||||
set_text = text;
|
||||
});
|
||||
override(text_field_edit, "wrapSelection", (_field, syntax_start, syntax_end) => {
|
||||
wrap_selection_called = true;
|
||||
wrap_syntax_start = syntax_start;
|
||||
wrap_syntax_end = syntax_end;
|
||||
});
|
||||
|
||||
const spoiler_syntax_start_with_header = "```spoiler Header\n";
|
||||
const spoiler_syntax_end = "\n```";
|
||||
|
||||
// Spoiler selected text
|
||||
reset_state();
|
||||
init_textarea("abc", {
|
||||
start: 0,
|
||||
end: 3,
|
||||
text: "abc",
|
||||
length: 3,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "");
|
||||
assert.equal(wrap_selection_called, true);
|
||||
assert.equal(wrap_syntax_start, spoiler_syntax_start_with_header);
|
||||
assert.equal(wrap_syntax_end, spoiler_syntax_end);
|
||||
|
||||
// Undo spoiler, header selected
|
||||
reset_state();
|
||||
init_textarea("```spoiler Header\nabc\n```", {
|
||||
start: 11,
|
||||
end: 17,
|
||||
text: "Header",
|
||||
length: 6,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "Header\nabc");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
reset_state();
|
||||
init_textarea("abc\n```spoiler \ndef\n```\nghi", {
|
||||
start: 15,
|
||||
end: 15,
|
||||
text: "",
|
||||
length: 0,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\ndef\nghi");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
// Undo spoiler selected text, only content selected
|
||||
reset_state();
|
||||
init_textarea("```spoiler \nabc\n```", {
|
||||
start: 12,
|
||||
end: 15,
|
||||
text: "abc",
|
||||
length: 3,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
reset_state();
|
||||
init_textarea("```spoiler abc\ndef\n```", {
|
||||
start: 15,
|
||||
end: 18,
|
||||
text: "def",
|
||||
length: 3,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\ndef");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
reset_state();
|
||||
init_textarea("abc\n```spoiler d\nef\n```\nghi", {
|
||||
start: 17,
|
||||
end: 19,
|
||||
text: "ef",
|
||||
length: 2,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\nd\nef\nghi");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
// Undo spoiler selected text, content and title selected
|
||||
reset_state();
|
||||
init_textarea("```spoiler abc\ndef\n```", {
|
||||
start: 11,
|
||||
end: 18,
|
||||
text: "abc\ndef",
|
||||
length: 7,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\ndef");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
reset_state();
|
||||
init_textarea("abc\n```spoiler d\nef\n```\nghi", {
|
||||
start: 15,
|
||||
end: 19,
|
||||
text: "d\nef",
|
||||
length: 4,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\nd\nef\nghi");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
// Undo spoiler selected text, syntax selected
|
||||
reset_state();
|
||||
init_textarea("```spoiler abc\ndef\n```", {
|
||||
start: 0,
|
||||
end: 22,
|
||||
text: "```spoiler abc\ndef\n```",
|
||||
length: 22,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\ndef");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
|
||||
reset_state();
|
||||
init_textarea("abc\n```spoiler d\nef\n```\nghi", {
|
||||
start: 4,
|
||||
end: 23,
|
||||
text: "```spoiler d\nef\n```",
|
||||
length: 19,
|
||||
});
|
||||
compose_ui.format_text($textarea, "spoiler");
|
||||
assert.equal(set_text, "abc\nd\nef\nghi");
|
||||
assert.equal(wrap_selection_called, false);
|
||||
});
|
||||
|
||||
run_test("markdown_shortcuts", ({override_rewire}) => {
|
||||
let format_text_type;
|
||||
override_rewire(compose_ui, "format_text", (_$textarea, type) => {
|
||||
|
||||
Reference in New Issue
Block a user