compose: Trigger language typeahead on using code formatting button.

To increase the discoverability of the possibility of specifying the
language for a code block, we trigger the language typeahead when code
syntax is added using the code formatting button. A blank option is
shown preselected in the typeahead, so that pressing enter will not
mistakenly add a language to the code block.

We only trigger the typeahead on empty opening fences when added by the
button, by setting a state variable to true when adding the syntax using
the button, checking for this state when sourcing languages for the code
typeahead, and then resetting the state variable to false.

Fixes: #29150.
This commit is contained in:
N-Shar-ma 2024-03-07 23:14:48 +05:30 committed by Tim Abbott
parent 4f051d653c
commit 9cedf0e8bc
5 changed files with 37 additions and 6 deletions

View File

@ -46,8 +46,13 @@ type SelectedLinesSections = {
export let compose_spinner_visible = false; export let compose_spinner_visible = false;
export let shift_pressed = false; // true or false export let shift_pressed = false; // true or false
export let code_formatting_button_triggered = false; // true or false
let full_size_status = false; // true or false let full_size_status = false; // true or false
export function set_code_formatting_button_triggered(value: boolean): void {
code_formatting_button_triggered = value;
}
// Some functions to handle the full size status explicitly // Some functions to handle the full size status explicitly
export function set_full_size(is_full: boolean): void { export function set_full_size(is_full: boolean): void {
full_size_status = is_full; full_size_status = is_full;
@ -1011,7 +1016,15 @@ export function format_text(
if (range.end < text.length && text[range.end] !== "\n") { if (range.end < text.length && text[range.end] !== "\n") {
block_code_syntax_end = block_code_syntax_end + "\n"; block_code_syntax_end = block_code_syntax_end + "\n";
} }
format(block_code_syntax_start, block_code_syntax_end); const added_fence = format(block_code_syntax_start, block_code_syntax_end);
if (added_fence) {
const cursor_after_opening_fence =
range.start + block_code_syntax_start.length - 1;
field.setSelectionRange(cursor_after_opening_fence, cursor_after_opening_fence);
set_code_formatting_button_triggered(true);
// Trigger typeahead lookup with a click.
field.click();
}
} else { } else {
format(inline_code_syntax); format(inline_code_syntax);
} }

View File

@ -689,13 +689,15 @@ export function get_candidates(query) {
const syntax_token = current_token.slice(0, 3); const syntax_token = current_token.slice(0, 3);
if (ALLOWED_MARKDOWN_FEATURES.syntax && (syntax_token === "```" || syntax_token === "~~~")) { if (ALLOWED_MARKDOWN_FEATURES.syntax && (syntax_token === "```" || syntax_token === "~~~")) {
// Only autocomplete if user starts typing a language after ``` // Only autocomplete if user starts typing a language after ```
if (current_token.length === 3) { // unless the fence was added via the code formatting button.
if (current_token.length === 3 && !compose_ui.code_formatting_button_triggered) {
return false; return false;
} }
// If the only input is a space, don't autocomplete // If the only input is a space, don't autocomplete
current_token = current_token.slice(3); current_token = current_token.slice(3);
if (current_token === " ") { if (current_token === " ") {
compose_ui.set_code_formatting_button_triggered(false);
return false; return false;
} }
@ -705,7 +707,13 @@ export function get_candidates(query) {
} }
this.completing = "syntax"; this.completing = "syntax";
this.token = current_token; this.token = current_token;
return realm_playground.get_pygments_typeahead_list_for_composebox(); // If the code formatting button was triggered, we want to show a blank option
// to improve the discoverability of the possibility of specifying a language.
const language_list = compose_ui.code_formatting_button_triggered
? ["", ...realm_playground.get_pygments_typeahead_list_for_composebox()]
: realm_playground.get_pygments_typeahead_list_for_composebox();
compose_ui.set_code_formatting_button_triggered(false);
return language_list;
} }
// Only start the emoji autocompleter if : is directly after one // Only start the emoji autocompleter if : is directly after one

View File

@ -795,6 +795,14 @@ strong {
color: hsl(0deg 0% 20%); color: hsl(0deg 0% 20%);
white-space: nowrap; white-space: nowrap;
/* hidden text just to maintain line height for a blank option */
strong:empty {
&::after {
content: ".";
visibility: hidden;
}
}
&:hover { &:hover {
text-decoration: none; text-decoration: none;
} }

View File

@ -509,6 +509,7 @@ $textarea.get = () => ({
length: end - start, length: end - start,
}); });
}, },
click() {},
}); });
// The argument `text_representation` is a string representing the text // The argument `text_representation` is a string representing the text
@ -860,17 +861,17 @@ run_test("format_text - code", ({override, override_rewire}) => {
compose_ui.format_text($textarea, "code"); compose_ui.format_text($textarea, "code");
assert.equal( assert.equal(
get_textarea_state(), get_textarea_state(),
"Before\nBefore \n```\n<this should\nbe code>\n```\n After\nAfter", "Before\nBefore \n```|\nthis should\nbe code\n```\n After\nAfter",
); );
init_textarea_state("<abc\ndef>"); init_textarea_state("<abc\ndef>");
compose_ui.format_text($textarea, "code"); compose_ui.format_text($textarea, "code");
assert.equal(get_textarea_state(), "```\n<abc\ndef>\n```"); assert.equal(get_textarea_state(), "```|\nabc\ndef\n```");
// Code, no selection // Code, no selection
init_textarea_state("|"); init_textarea_state("|");
compose_ui.format_text($textarea, "code"); compose_ui.format_text($textarea, "code");
assert.equal(get_textarea_state(), "```\n|\n```"); assert.equal(get_textarea_state(), "```|\n\n```");
// Undo code selected text, syntax not selected // Undo code selected text, syntax not selected
init_textarea_state("before `<abc>` after"); init_textarea_state("before `<abc>` after");

View File

@ -16,6 +16,7 @@ const compose_ui = mock_esm("../src/compose_ui", {
autosize_called = true; autosize_called = true;
}, },
cursor_inside_code_block: () => false, cursor_inside_code_block: () => false,
set_code_formatting_button_triggered: noop,
}); });
const compose_validate = mock_esm("../src/compose_validate", { const compose_validate = mock_esm("../src/compose_validate", {
validate_message_length: () => true, validate_message_length: () => true,