mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(app): isolate bidi usernames from message timestamps (#2560)
This commit is contained in:
1 parent
6c0ce9369b
commit
f38619d974
2 files changed
+62
No files matched your search
@@ -428,6 +428,7 @@
|
||||
max-inline-size: none;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
unicode-bidi: plaintext;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
@@ -505,6 +506,7 @@
|
||||
font-weight: 400;
|
||||
color: var(--message-timestamp-color);
|
||||
line-height: var(--message-line-height);
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
.messageTimestamp {
|
||||
@@ -557,6 +559,7 @@
|
||||
font-size: var(--message-timestamp-compact-font-size);
|
||||
font-weight: 500;
|
||||
line-height: var(--message-line-height);
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
.messageTimestampHover {
|
||||
@@ -948,6 +951,7 @@
|
||||
white-space: nowrap;
|
||||
max-width: 30%;
|
||||
vertical-align: baseline;
|
||||
unicode-bidi: plaintext;
|
||||
font-weight: 500;
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const MESSAGE_CSS = readFileSync(fileURLToPath(new URL('./Message.module.css', import.meta.url)), 'utf8');
|
||||
|
||||
const ISOLATED_NAMES = ['.messageUsername', '.repliedUsername'];
|
||||
const ISOLATED_TIMESTAMPS = [
|
||||
'.messageTimestamp',
|
||||
'.messageTimestampCompact',
|
||||
'.messageTimestampHover',
|
||||
'.messageTimestampCompactHover',
|
||||
];
|
||||
|
||||
function rules(css: string): Array<{selector: string; declarations: string}> {
|
||||
const source = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
const out: Array<{selector: string; declarations: string}> = [];
|
||||
const chain: Array<string> = [];
|
||||
let buffer = '';
|
||||
for (const char of source) {
|
||||
if (char === '{') {
|
||||
chain.push(buffer.split(/[;}]/).pop()?.trim().replace(/\s+/g, ' ') ?? '');
|
||||
buffer = '';
|
||||
} else if (char === '}') {
|
||||
if (chain.length > 0) {
|
||||
out.push({selector: chain.join(' '), declarations: buffer});
|
||||
chain.pop();
|
||||
}
|
||||
buffer = '';
|
||||
} else {
|
||||
buffer += char;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function declaredBidi(className: string): Array<string> {
|
||||
const selectorPattern = new RegExp(`\\${className}(?![\\w-])`);
|
||||
const out: Array<string> = [];
|
||||
for (const rule of rules(MESSAGE_CSS)) {
|
||||
if (!rule.selector.split(',').some((selector) => selectorPattern.test(selector))) continue;
|
||||
const match = rule.declarations.match(/unicode-bidi\s*:\s*([^;]+)/);
|
||||
if (match) out.push(match[1].trim());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe('message bidi isolation', () => {
|
||||
it.each(ISOLATED_NAMES)('resolves %s in its own bidi paragraph', (className) => {
|
||||
expect(declaredBidi(className)).toContain('plaintext');
|
||||
});
|
||||
|
||||
it.each(ISOLATED_TIMESTAMPS)('keeps %s out of a neighbouring right-to-left run', (className) => {
|
||||
expect(declaredBidi(className)).toContain('isolate');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user