Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14b6849f6e | ||
|
|
22371f8156 |
@@ -43,6 +43,10 @@ rc_joins: { local: { per_second: 1000, burst_count: 10000 }, remote: { per_secon
|
||||
rc_presence: { per_user: { per_second: 1000, burst_count: 10000 } }
|
||||
max_upload_size: 50M
|
||||
suppress_key_server_warning: true
|
||||
# scheduled messages (MSC4140 delayed events)
|
||||
max_event_delay_duration: 24h
|
||||
experimental_features:
|
||||
msc4140_enabled: true
|
||||
"""
|
||||
open(p, "w").write(s)
|
||||
PY
|
||||
|
||||
@@ -65,3 +65,27 @@ test('no math conversion inside a code block', () => {
|
||||
test('a message with no math is unchanged', () => {
|
||||
assert.equal(toMatrixCustomHTML(txt('just hello'), OPTS), 'just hello');
|
||||
});
|
||||
|
||||
// Gitea #184 O3 — markdown mode: fences and backtick spans typed as plain
|
||||
// paragraphs are literal too (the block markdown parser only sees the fence
|
||||
// after the lines are joined, so math must be skipped while serialising them).
|
||||
const MD_OPTS = { ...OPTS, allowInlineMarkdown: true, allowBlockMarkdown: true };
|
||||
|
||||
test('markdown: no math conversion inside a typed ``` fence', () => {
|
||||
const paragraphs = [
|
||||
el(BlockType.Paragraph, [txt('```')]),
|
||||
el(BlockType.Paragraph, [txt('$x$ literal')]),
|
||||
el(BlockType.Paragraph, [txt('```')]),
|
||||
el(BlockType.Paragraph, [txt('after $y$')]),
|
||||
];
|
||||
const out = toMatrixCustomHTML(paragraphs, MD_OPTS);
|
||||
assert.ok(out.includes('<pre'), 'fence became a code block');
|
||||
assert.ok(out.includes('$x$ literal'), 'code block content stays literal');
|
||||
assert.ok(out.includes('data-mx-maths="y"'), 'math after the fence still converts');
|
||||
});
|
||||
|
||||
test('markdown: no math conversion inside a backtick span, math outside still converts', () => {
|
||||
const out = toMatrixCustomHTML(el(BlockType.Paragraph, [txt('use `$x$` and $y$')]), MD_OPTS);
|
||||
assert.ok(/<code[^>]*>\$x\$<\/code>/.test(out), 'backtick span stays literal');
|
||||
assert.ok(out.includes('data-mx-maths="y"'));
|
||||
});
|
||||
|
||||
@@ -36,6 +36,18 @@ const textToCustomHtml = (node: Text, opts: OutputOptions): string => {
|
||||
// applied inside inline code. Non-math text recurses with allowMath off so it
|
||||
// still gets the normal marks + inline-markdown treatment.
|
||||
if (opts.allowMath && !node.code) {
|
||||
// Markdown inline code spans (`…`) are literal too: apply math only to the
|
||||
// text between them (Gitea #184 O3 — `$x$` inside backticks stayed math).
|
||||
if (opts.allowInlineMarkdown && /`[^`]*`/.test(node.text)) {
|
||||
return node.text
|
||||
.split(/(`+[^`]*`+)/)
|
||||
.map((part) =>
|
||||
part.startsWith('`')
|
||||
? textToCustomHtml({ ...node, text: part }, { ...opts, allowMath: false })
|
||||
: textToCustomHtml({ ...node, text: part }, opts),
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
const segments = splitMathSegments(node.text);
|
||||
if (segments.some((seg) => seg.type !== 'text')) {
|
||||
return segments
|
||||
@@ -128,12 +140,20 @@ export const toMatrixCustomHTML = (
|
||||
opts: OutputOptions,
|
||||
): string => {
|
||||
let markdownLines = '';
|
||||
// Inside a markdown ``` fence every line is literal: no `$…$` math conversion
|
||||
// (the fence is only recognised by parseBlockMD after the lines are joined,
|
||||
// which is too late — Gitea #184 O3).
|
||||
let inFence = false;
|
||||
const parseNode = (n: Descendant, index: number, targetNodes: Descendant[]) => {
|
||||
if (opts.allowBlockMarkdown && 'type' in n && n.type === BlockType.Paragraph) {
|
||||
const isFenceLine = /^\s*```/.test(toPlainText(n, false));
|
||||
const literal = inFence || isFenceLine;
|
||||
if (isFenceLine) inFence = !inFence;
|
||||
const line = toMatrixCustomHTML(n, {
|
||||
...opts,
|
||||
allowInlineMarkdown: false,
|
||||
allowBlockMarkdown: false,
|
||||
allowMath: opts.allowMath && !literal,
|
||||
})
|
||||
.replace(/<br\/>$/, '\n')
|
||||
.replace(/^(\\*)>/, '$1>');
|
||||
|
||||
@@ -246,8 +246,15 @@ export function MessageSearch({
|
||||
const [cacheVersion, setCacheVersion] = useState(0);
|
||||
const handleCacheLoaded = useCallback(() => setCacheVersion((v) => v + 1), []);
|
||||
// Explicit wipe of the persistent on-disk index, then re-run the merge.
|
||||
// After a clear, the active search re-runs (cacheVersion) and would persist
|
||||
// the in-memory rows straight back — skip that one write so "Clear cached
|
||||
// index" actually leaves the disk empty until the next new search.
|
||||
const skipPersistOnceRef = useRef(false);
|
||||
const handleClearSearchCache = useCallback(() => {
|
||||
clearSearchCache().then(() => setCacheVersion((v) => v + 1));
|
||||
clearSearchCache().then(() => {
|
||||
skipPersistOnceRef.current = true;
|
||||
setCacheVersion((v) => v + 1);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// The rooms actually in scope for this search (mirrors server-side logic)
|
||||
@@ -277,12 +284,15 @@ export function MessageSearch({
|
||||
return undefined;
|
||||
}
|
||||
let cancelled = false;
|
||||
const persist = !skipPersistOnceRef.current;
|
||||
skipPersistOnceRef.current = false;
|
||||
searchLocalMessages({
|
||||
term: msgSearchParams.term ?? '',
|
||||
roomIds: localSearchRooms,
|
||||
senders: msgSearchParams.senders,
|
||||
fromTs: msgSearchParams.fromTs,
|
||||
toTs: msgSearchParams.toTs,
|
||||
persist,
|
||||
}).then((result) => {
|
||||
if (!cancelled) setLocalResult(result);
|
||||
});
|
||||
|
||||
@@ -18,6 +18,12 @@ export type LocalSearchParams = {
|
||||
/** Optional date-range filter (ms). Applied to both memory and cached rows. */
|
||||
fromTs?: number;
|
||||
toTs?: number;
|
||||
/**
|
||||
* Set false to run the scan without writing it back to IndexedDB — used
|
||||
* right after "Clear cached index" so the clear is not undone by the
|
||||
* re-run of the active search (Gitea #184 O4).
|
||||
*/
|
||||
persist?: boolean;
|
||||
};
|
||||
|
||||
export type LocalSearchResult = {
|
||||
@@ -149,6 +155,7 @@ export const useLocalMessageSearch = () => {
|
||||
senders,
|
||||
fromTs,
|
||||
toTs,
|
||||
persist = true,
|
||||
}: LocalSearchParams): Promise<LocalSearchResult> => {
|
||||
const trimmedTerm = term.trim();
|
||||
const senderSet = senders && senders.length > 0 ? new Set(senders) : null;
|
||||
@@ -302,7 +309,7 @@ export const useLocalMessageSearch = () => {
|
||||
// Fire-and-forget persist of freshly scanned rows + coverage.
|
||||
// saveRoomIndex swallows all errors internally, so a floating promise
|
||||
// here can never reject.
|
||||
if (cacheEnabled && rowsToPersist.length > 0) {
|
||||
if (cacheEnabled && persist && rowsToPersist.length > 0) {
|
||||
saveRoomIndex(roomId, rowsToPersist);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user