Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c62fb4722 |
@@ -69,8 +69,15 @@ export const Reactions = as<'div', ReactionsProps>(
|
|||||||
const measure = useCallback(() => {
|
const measure = useCallback(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
const rowWidth = container.getBoundingClientRect().width;
|
// [Gitea #305] The width the chips actually get (content box). The border
|
||||||
const gap = parseFloat(getComputedStyle(container).columnGap) || 8;
|
// box grows by 4 px while collapsed (margin -2 / padding 2), and measuring
|
||||||
|
// it made a row within 4 px of fitting flip collapsed↔expanded every frame.
|
||||||
|
const cs = getComputedStyle(container);
|
||||||
|
const rowWidth =
|
||||||
|
container.clientWidth -
|
||||||
|
(parseFloat(cs.paddingLeft) || 0) -
|
||||||
|
(parseFloat(cs.paddingRight) || 0);
|
||||||
|
const gap = parseFloat(cs.columnGap) || 8;
|
||||||
const chips = keys ? keys.split('\u0000').map((k) => chipRefs.current.get(k)) : [];
|
const chips = keys ? keys.split('\u0000').map((k) => chipRefs.current.get(k)) : [];
|
||||||
const widths = chips.map((el) => el?.getBoundingClientRect().width ?? 0);
|
const widths = chips.map((el) => el?.getBoundingClientRect().width ?? 0);
|
||||||
const moreWidth = moreRef.current?.getBoundingClientRect().width ?? 48;
|
const moreWidth = moreRef.current?.getBoundingClientRect().width ?? 48;
|
||||||
@@ -110,8 +117,21 @@ export const Reactions = as<'div', ReactionsProps>(
|
|||||||
style={{
|
style={{
|
||||||
...props.style,
|
...props.style,
|
||||||
...(collapsed && rowHeight
|
...(collapsed && rowHeight
|
||||||
? // 2px breathing room so row-1 focus outlines are not clipped.
|
? // 2px breathing room so row-1 focus outlines are not clipped. Longhand
|
||||||
{ maxHeight: rowHeight + 4, overflow: 'hidden', padding: 2, margin: -2 }
|
// margins so the timeline's marginTop survives (Gitea #305: the
|
||||||
|
// `margin` shorthand overrode it and, once removed, cleared it).
|
||||||
|
{
|
||||||
|
maxHeight: rowHeight + 4,
|
||||||
|
overflow: 'hidden',
|
||||||
|
padding: 2,
|
||||||
|
marginLeft: -2,
|
||||||
|
marginRight: -2,
|
||||||
|
marginBottom: -2,
|
||||||
|
marginTop:
|
||||||
|
props.style?.marginTop !== undefined
|
||||||
|
? `calc(${typeof props.style.marginTop === 'number' ? `${props.style.marginTop}px` : props.style.marginTop} - 2px)`
|
||||||
|
: -2,
|
||||||
|
}
|
||||||
: {}),
|
: {}),
|
||||||
}}
|
}}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {
|
import {
|
||||||
plain,
|
|
||||||
DEFAULT_GROUPS,
|
DEFAULT_GROUPS,
|
||||||
kumaBeatTime,
|
kumaBeatTime,
|
||||||
kumaUrls,
|
kumaUrls,
|
||||||
@@ -261,27 +260,3 @@ test('a dismissed calls-down banner lets the next one through, never hides serve
|
|||||||
'server-down',
|
'server-down',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('plain: strips Markdown syntax only, keeps ordinary #, _, > and * (#308)', () => {
|
|
||||||
assert.equal(
|
|
||||||
plain('join #lotus-general or #announcements:lotusguild.org'),
|
|
||||||
'join #lotus-general or #announcements:lotusguild.org',
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
plain('ping @some_user about snake_case and C#'),
|
|
||||||
'ping @some_user about snake_case and C#',
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
plain('calls -> fixed, > 5 people, 5 * 3 = 15'),
|
|
||||||
'calls -> fixed, > 5 people, 5 * 3 = 15',
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
plain('# Heading\n* item one\n- item two\n> quoted'),
|
|
||||||
'Heading\nitem one\nitem two\nquoted',
|
|
||||||
);
|
|
||||||
assert.equal(plain('**bold**, *em*, _em_, `/sfu/get`'), 'bold, em, em, /sfu/get');
|
|
||||||
assert.equal(
|
|
||||||
plain('Read the <b>guide</b> <img src=x onerror=alert(1)> [docs](https://x.y)'),
|
|
||||||
'Read the guide docs',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -240,25 +240,12 @@ const incidentTone = (style: string): BannerTone => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Markdown-ish incident text as plain text for the Details line. */
|
/** Markdown-ish incident text as plain text for the Details line. */
|
||||||
/**
|
const plain = (s: string): string =>
|
||||||
* [Gitea #308] Markdown → plain text for the Details line. Strips only the
|
|
||||||
* syntax: removing every `*_\`#>` also broke "#lotus-general", "snake_case",
|
|
||||||
* "->" and "5 * 3". HTML tags are dropped (React escapes the text anyway).
|
|
||||||
*/
|
|
||||||
export const plain = (s: string): string =>
|
|
||||||
s
|
s
|
||||||
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
||||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
||||||
.replace(/<\/?[a-z][^>]*>/gi, '')
|
.replace(/[*_`#>]/g, '')
|
||||||
.replace(/^[ \t]{0,3}#{1,6}[ \t]+/gm, '')
|
.replace(/\s+\n/g, '\n')
|
||||||
.replace(/^[ \t]*>[ \t]?/gm, '')
|
|
||||||
.replace(/^[ \t]*[*+-][ \t]+/gm, '')
|
|
||||||
.replace(/`([^`\n]+)`/g, '$1')
|
|
||||||
.replace(/\*\*(?=\S)([^*\n]*?\S)\*\*/g, '$1')
|
|
||||||
.replace(/__(?=\S)([^_\n]*?\S)__/g, '$1')
|
|
||||||
.replace(/(^|[^\w*])\*(?=\S)([^*\n]*?\S)\*(?![\w*])/g, '$1$2')
|
|
||||||
.replace(/(^|[^\w])_(?=\S)([^_\n]*?\S)_(?!\w)/g, '$1$2')
|
|
||||||
.replace(/[ \t]+\n/g, '\n')
|
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user