FIX: Do not autocomplete categories or emojis in code blocks (#8459)

This reapplies commit b643526d9a407b8abb826dba78a954cdfe6d6133 after
being reverted in commit f65c4535556eeff24944369d6f262ef6be147eec.

Unlike the original commit, this does a single pass and does not take
into account unfinished code blocks.
This commit is contained in:
Dan Ungureanu 2019-12-09 15:07:15 +02:00 committed by GitHub
parent 192ada0067
commit f62b8990ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 11 deletions

View File

@ -33,7 +33,9 @@ import {
tinyAvatar, tinyAvatar,
formatUsername, formatUsername,
clipboardData, clipboardData,
safariHacksDisabled safariHacksDisabled,
caretPosition,
inCodeBlock
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import { import {
validateUploadedFiles, validateUploadedFiles,
@ -192,7 +194,9 @@ export default Component.extend({
afterComplete() { afterComplete() {
// ensures textarea scroll position is correct // ensures textarea scroll position is correct
scheduleOnce("afterRender", () => $input.blur().focus()); scheduleOnce("afterRender", () => $input.blur().focus());
} },
triggerRule: textarea =>
!inCodeBlock(textarea.value, caretPosition(textarea))
}); });
} }

View File

@ -20,7 +20,9 @@ import { siteDir } from "discourse/lib/text-direction";
import { import {
determinePostReplaceSelection, determinePostReplaceSelection,
clipboardData, clipboardData,
safariHacksDisabled safariHacksDisabled,
caretPosition,
inCodeBlock
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import toMarkdown from "discourse/lib/to-markdown"; import toMarkdown from "discourse/lib/to-markdown";
import deprecated from "discourse-common/lib/deprecated"; import deprecated from "discourse-common/lib/deprecated";
@ -420,6 +422,10 @@ export default Component.extend({
}, },
onKeyUp: (text, cp) => { onKeyUp: (text, cp) => {
if (inCodeBlock(text, cp)) {
return false;
}
const matches = /(?:^|[^a-z])(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi.exec( const matches = /(?:^|[^a-z])(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi.exec(
text.substring(0, cp) text.substring(0, cp)
); );
@ -511,7 +517,10 @@ export default Component.extend({
} }
return list; return list;
}); });
} },
triggerRule: textarea =>
!inCodeBlock(textarea.value, caretPosition(textarea))
}); });
}, },

View File

@ -1,5 +1,9 @@
export const SEPARATOR = ":"; export const SEPARATOR = ":";
import { caretRowCol } from "discourse/lib/utilities"; import {
caretRowCol,
caretPosition,
inCodeBlock
} from "discourse/lib/utilities";
export function replaceSpan($elem, categorySlug, categoryLink) { export function replaceSpan($elem, categorySlug, categoryLink) {
$elem.replaceWith( $elem.replaceWith(
@ -21,10 +25,14 @@ export function categoryHashtagTriggerRule(textarea, opts) {
if (/^#{1}\w+/.test(line)) return false; if (/^#{1}\w+/.test(line)) return false;
} }
if (col < 6) { // Don't trigger autocomplete when ATX-style headers are used
// Don't trigger autocomplete when ATX-style headers are used if (col < 6 && line.slice(0, col) === "#".repeat(col)) {
return line.slice(0, col) !== "#".repeat(col); return false;
} else {
return true;
} }
if (inCodeBlock(textarea.value, caretPosition(textarea))) {
return false;
}
return true;
} }

View File

@ -410,5 +410,30 @@ export function rescueThemeError(name, error, api) {
document.body.prepend(alertDiv); document.body.prepend(alertDiv);
} }
const CODE_BLOCKS_REGEX = /^( |\t).*|`[^`]+`|^```[^]*?^```|\[code\][^]*?\[\/code\]/gm;
// | ^ | ^ | ^ | ^ |
// | | | |
// | | | code blocks between [code]
// | | |
// | | +--- code blocks between three backquote
// | |
// | +----- inline code between backquotes
// |
// +------- paragraphs starting with 4 spaces or tab
export function inCodeBlock(text, pos) {
const matches = text.matchAll(CODE_BLOCKS_REGEX);
for (const match of matches) {
const begin = match.index;
const end = match.index + match[0].length;
if (begin <= pos && pos <= end) {
return true;
}
}
return false;
}
// This prevents a mini racer crash // This prevents a mini racer crash
export default {}; export default {};

View File

@ -9,7 +9,8 @@ import {
setDefaultHomepage, setDefaultHomepage,
caretRowCol, caretRowCol,
setCaretPosition, setCaretPosition,
fillMissingDates fillMissingDates,
inCodeBlock
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
QUnit.module("lib:utilities"); QUnit.module("lib:utilities");
@ -186,3 +187,32 @@ QUnit.test("fillMissingDates", assert => {
"it returns a JSON array with 31 dates" "it returns a JSON array with 31 dates"
); );
}); });
QUnit.test("inCodeBlock", assert => {
const text =
"000\n\n```\n111\n```\n\n000\n\n`111 111`\n\n000\n\n[code]\n111\n[/code]\n\n 111\n\t111\n\n000`000";
for (let i = 0; i < text.length; ++i) {
if (text[i] === "0") {
assert.notOk(inCodeBlock(text, i), `position ${i} is not in code block`);
} else if (text[i] === "1") {
assert.ok(inCodeBlock(text, i), `position ${i} is in code block`);
}
}
});
QUnit.skip("inCodeBlock - runs fast", assert => {
const phrase = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
const text = `${phrase}\n\n\`\`\`\n${phrase}\n\`\`\`\n\n${phrase}\n\n\`${phrase}\n${phrase}\n\n${phrase}\n\n[code]\n${phrase}\n[/code]\n\n${phrase}\n\n ${phrase}\n\n\`${phrase}\`\n\n${phrase}`;
let time = Number.MAX_VALUE;
for (let i = 0; i < 10; ++i) {
const start = performance.now();
inCodeBlock(text, text.length);
const end = performance.now();
time = Math.min(time, end - start);
}
// This runs in 'keyUp' event handler so it should run as fast as
// possible. It should take less than 1ms for the test text.
assert.ok(time < 10);
});