mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
fix(sonar): locale-aware sorts, linear share and redaction patterns, focus only on controls
This commit is contained in:
@@ -66,9 +66,9 @@ export default function AssistantFitCheck({ changes }: { changes: readonly DayCh
|
||||
if (ok) return pill;
|
||||
return (
|
||||
<Tooltip label={check.slice(0, MAX_LINES).join('\n')} placement="top">
|
||||
<span tabIndex={0} className="rounded-full outline-none focus-visible:ring-2 focus-visible:ring-accent">
|
||||
<button type="button" className="rounded-full outline-none focus-visible:ring-2 focus-visible:ring-accent">
|
||||
{pill}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,23 @@ describe('markdownToText', () => {
|
||||
it('FE-AI-SHARE-002: a link whose text is its address stays one address', () => {
|
||||
expect(markdownToText('[https://trek.app](https://trek.app)')).toBe('https://trek.app');
|
||||
});
|
||||
|
||||
it('FE-AI-SHARE-006: pictures, odd links and divider rows read as before', () => {
|
||||
expect(markdownToText(' and [[nested]](u) and [a b](x y) and [](z) and [ok](u)')).toBe(
|
||||
'Map and [[nested]](u) and [a b](x y) and [](z) and ok (u)'
|
||||
);
|
||||
expect(markdownToText('|:-|-:|\n| a | b |')).toBe('a, b');
|
||||
expect(markdownToText('| - | x |')).toBe('-, x');
|
||||
expect(markdownToText('# Wide heading\n- wide item')).toBe('Wide heading\n• wide item');
|
||||
});
|
||||
|
||||
it('FE-AI-SHARE-007: long runs of brackets, spaces and dashes are read in one pass', () => {
|
||||
const started = Date.now();
|
||||
const long = 20000;
|
||||
markdownToText(`${'['.repeat(long)}\n${'}\n${'-'.repeat(long)}\n#${' '.repeat(long)}x`);
|
||||
markdownToText(`|${' - |'.repeat(long / 4)} x`);
|
||||
expect(Date.now() - started).toBeLessThan(2000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chatTextOf', () => {
|
||||
|
||||
@@ -14,8 +14,19 @@ export const SHARE_MAX_CHARS = 4800;
|
||||
|
||||
type Translate = (key: string, params?: Record<string, string | number>) => string;
|
||||
|
||||
/** A table's divider row (`|---|:--:|`), which has no words. */
|
||||
const TABLE_RULE = /^\|?(?:\s*:?-+:?\s*\|)+\s*:?-*:?\s*$/;
|
||||
/** What a divider cell holds once trimmed: dashes, with a colon at either end for alignment. */
|
||||
const RULE_CELL = /^:?-*:?$/;
|
||||
|
||||
/** A table's divider row (`|---|:--:|`), which has no words. Read cell by cell, so no pattern backtracks over it. */
|
||||
function isTableRule(line: string): boolean {
|
||||
const cells = (line.startsWith('|') ? line.slice(1) : line).split('|');
|
||||
const last = cells.pop() ?? '';
|
||||
if (cells.length === 0 || !RULE_CELL.test(last.trim())) return false;
|
||||
return cells.every((cell) => {
|
||||
const trimmed = cell.trim();
|
||||
return trimmed.includes('-') && RULE_CELL.test(trimmed);
|
||||
});
|
||||
}
|
||||
|
||||
/** One table row as its cells in a line. */
|
||||
function tableRow(line: string): string {
|
||||
@@ -28,11 +39,45 @@ function tableRow(line: string): string {
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites every `[text](url)` of a line (with a leading `!` for a picture), found by scanning:
|
||||
* a `[` and the first `]` after it decide the match, so a long line of brackets is read once.
|
||||
*/
|
||||
function rewriteBrackets(line: string, picture: boolean, build: (text: string, url: string) => string): string {
|
||||
let out = '';
|
||||
let from = 0;
|
||||
let paren = -1;
|
||||
let open = line.indexOf('[');
|
||||
while (open !== -1) {
|
||||
if (picture && (open === 0 || line[open - 1] !== '!')) {
|
||||
open = line.indexOf('[', open + 1);
|
||||
continue;
|
||||
}
|
||||
const close = line.indexOf(']', open + 1);
|
||||
if (close === -1) break;
|
||||
let next = close;
|
||||
if (line[close + 1] === '(') {
|
||||
if (paren < close + 2) paren = line.indexOf(')', close + 2);
|
||||
if (paren === -1) break;
|
||||
let clean = true;
|
||||
for (let at = close + 2; at < paren && clean; at++) clean = !/\s/.test(line.charAt(at));
|
||||
const text = line.slice(open + 1, close);
|
||||
const url = line.slice(close + 2, paren);
|
||||
if (clean && (picture || (text !== '' && url !== ''))) {
|
||||
out += line.slice(from, picture ? open - 1 : open) + build(text, url);
|
||||
from = paren + 1;
|
||||
next = paren;
|
||||
}
|
||||
}
|
||||
open = line.indexOf('[', next + 1);
|
||||
}
|
||||
return out + line.slice(from);
|
||||
}
|
||||
|
||||
/** The marks of one line of running text: emphasis, code, links and pictures. */
|
||||
function inline(line: string): string {
|
||||
return line
|
||||
.replace(/!\[([^\]]*)\]\([^)\s]*\)/g, '$1')
|
||||
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, text: string, url: string) => (text === url ? url : `${text} (${url})`))
|
||||
const pictures = rewriteBrackets(line, true, (text) => text);
|
||||
return rewriteBrackets(pictures, false, (text, url) => (text === url ? url : `${text} (${url})`))
|
||||
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
||||
.replace(/__([^_]+)__/g, '$1')
|
||||
.replace(/(^|[^\w*])\*([^*\s][^*]*)\*/g, '$1$2')
|
||||
@@ -42,11 +87,11 @@ function inline(line: string): string {
|
||||
/** One line of Markdown as text, or null for a line that only carries layout (a fence, a table rule). */
|
||||
function plainLine(line: string): string | null {
|
||||
if (/^\s*(?:```|~~~)/.test(line)) return null;
|
||||
if (TABLE_RULE.test(line.trim())) return null;
|
||||
const heading = /^\s{0,3}#{1,6}\s+(.*)$/.exec(line);
|
||||
if (heading) return inline(heading[1] ?? '');
|
||||
const bullet = /^(\s*)[-*+]\s+(.*)$/.exec(line);
|
||||
if (bullet) return `${bullet[1] ?? ''}• ${inline(bullet[2] ?? '')}`;
|
||||
if (isTableRule(line.trim())) return null;
|
||||
const heading = /^\s{0,3}#{1,6}\s(.*)$/.exec(line);
|
||||
if (heading) return inline((heading[1] ?? '').trimStart());
|
||||
const bullet = /^(\s*)[-*+]\s(.*)$/.exec(line);
|
||||
if (bullet) return `${bullet[1] ?? ''}• ${inline((bullet[2] ?? '').trimStart())}`;
|
||||
const quote = /^\s*>\s?(.*)$/.exec(line);
|
||||
if (quote) return inline(quote[1] ?? '');
|
||||
if (line.trim().startsWith('|')) return tableRow(inline(line));
|
||||
|
||||
@@ -329,8 +329,10 @@ export function ChangeRows({ items, folded, excluded, disabled, undone, undoable
|
||||
const Group = view?.Group;
|
||||
const Run = view?.Run;
|
||||
const body: ReactNode[] = [];
|
||||
for (let i = 0; i < run.length; i++) {
|
||||
let i = 0;
|
||||
while (i < run.length) {
|
||||
let end = i;
|
||||
let next = i + 1;
|
||||
while (end < run.length && runnable(run[end] as ChangeItem, view)) end++;
|
||||
if (Run && end - i >= 2) {
|
||||
const taken = run.slice(i, end);
|
||||
@@ -342,8 +344,9 @@ export function ChangeRows({ items, folded, excluded, disabled, undone, undoable
|
||||
</CardBoundary>
|
||||
);
|
||||
}
|
||||
i = end - 1;
|
||||
next = end;
|
||||
} else body.push(row(run[i] as ChangeItem));
|
||||
i = next;
|
||||
}
|
||||
if (!Group || !first) return body;
|
||||
const group = (place: 'head' | 'foot') => (
|
||||
|
||||
@@ -75,7 +75,7 @@ function LabelChips({ labels }: { labels: readonly CollectionCardLabel[] }) {
|
||||
))}
|
||||
{rest.length > 0 && (
|
||||
<Tooltip label={rest.map((label) => label.name).join(', ')} placement="top">
|
||||
<span tabIndex={0} className="rounded-full outline-none focus-visible:ring-2 focus-visible:ring-accent">
|
||||
<span className="rounded-full">
|
||||
<WhiteBadge>+{rest.length}</WhiteBadge>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
Reference in New Issue
Block a user