Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion KNOWLEDGE_BASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Livestream history: the team agreed to build semantic search on [day two at 2:57

### Writing commits

A **commit** is a post on /dev/null, not a Git commit in a repository. Open the composer or press **c**, write your update, and choose **Commit** or press **⌘/Ctrl+Enter**. Text-only commits accept 1–4,000 characters; as you type, the ring around your avatar in the composer fills toward that limit. On the web and in the app, an image attachment makes the text optional (terminal commits still need text), but the 4,000-character maximum still applies. Runs of blank lines outside code collapse to one before the length is checked. Lists fold posts longer than 280 characters or 8 lines behind **Show more**, without cutting through code. Commits can be created and deleted, but not edited after publication. There are no plans to add commit editing, and it will never be added: to change a commit, delete it and post a new one.
A **commit** is a post on /dev/null, not a Git commit in a repository. Open the composer or press **c**, write your update, and choose **Commit** or press **⌘/Ctrl+Enter**. Text-only commits accept 1–4,000 characters; as you type, the ring around your avatar in the composer fills toward that limit. On the web and in the app, an image attachment makes the text optional (terminal commits still need text), but the 4,000-character maximum still applies. Runs of blank lines outside code collapse to one before the length is checked. Lists fold posts longer than 280 characters or 8 lines behind **Show more**. When a commit starts with a code block too long to fit, its first lines fade out to show that the code continues. **Show more** reveals the full snippet, and the copy control copies the whole block even while folded. Commits can be created and deleted, but not edited after publication. There are no plans to add commit editing, and it will never be added: to change a commit, delete it and post a new one.

Commits, replies, quotes, scheduling, terminal submissions, and app (signed API) commits share a limit of 10 attempts per minute per user. Posts go through [moderation](#moderation), so a newly submitted commit may be pending rather than public immediately.

Expand Down
44 changes: 29 additions & 15 deletions resources/js/components/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ export function CodeBlock({
};

return (
<div className="my-2 rounded-md border bg-muted/60">
<div
className={cn(
'relative isolate my-2 rounded-md border border-transparent before:pointer-events-none before:absolute before:-inset-px before:-z-10 before:rounded-md before:border before:border-border before:bg-muted/60',
fullHtml !== undefined &&
fullHtml !== html &&
'before:mask-b-from-[calc(100%-3rem)] before:mask-b-to-100%',
)}
>
{(language || isCopyable) && (
<div className="flex h-8 items-center justify-between gap-2 border-b pr-1 pl-3">
<span
Expand Down Expand Up @@ -120,20 +127,27 @@ export function CodeBlock({
)}
</div>
)}
{/* The server escapes the code while highlighting it. */}
<pre
ref={codeRef}
tabIndex={isCopyable ? undefined : -1}
className={cn(
'px-3 py-2.5 font-mono text-[13px] leading-normal',
isWrapped
? 'overflow-x-hidden wrap-anywhere whitespace-pre-wrap'
: 'overflow-x-auto wrap-normal whitespace-pre',
)}
dangerouslySetInnerHTML={{
__html: withCertificateHashes(html),
}}
/>
<div
className={isWrapped ? 'overflow-x-hidden' : 'overflow-x-auto'}
>
{/* The server escapes the code while highlighting it. */}
<pre
ref={codeRef}
tabIndex={isCopyable ? undefined : -1}
className={cn(
'px-3 py-2.5 font-mono text-[13px] leading-normal',
isWrapped
? 'wrap-anywhere whitespace-pre-wrap'
: 'w-max min-w-full wrap-normal whitespace-pre',
fullHtml !== undefined &&
fullHtml !== html &&
'mask-b-from-[calc(100%-3rem)] mask-b-to-100%',
)}
dangerouslySetInnerHTML={{
__html: withCertificateHashes(html),
}}
/>
</div>
</div>
);
}
13 changes: 12 additions & 1 deletion resources/js/components/commit-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,22 @@ function withFullBlocks(
const blocks = segments.filter((segment) => segment.type === 'block');
let index = 0;

return previewSegments.map((segment) =>
const preview = previewSegments.map((segment) =>
segment.type === 'block'
? { ...segment, fullHtml: blocks[index++]?.html }
: segment,
);
const last = preview.at(-1);
const previous = preview.at(-2);

// A faded block already shows that it continues; keep the ellipsis for completed blocks and prose.
return last?.type === 'text' &&
last.text.trim() === '…' &&
previous?.type === 'block' &&
previous.fullHtml !== undefined &&
previous.fullHtml !== previous.html
? preview.slice(0, -1)
: preview;
}

export function CommitBodyExcerpt({
Expand Down
75 changes: 75 additions & 0 deletions tests/Unit/FoldedCommitBody.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import { App } from '@inertiajs/react';
import type { ComponentProps } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { test } from 'vite-plus/test';
import { FoldedCommitBody } from '../../resources/js/components/commit-body';
import type { BodySegment } from '../../resources/js/types';

function renderBody(
segments: BodySegment[],
previewSegments: BodySegment[] | null,
) {
return renderToStaticMarkup(
<App
initialComponent={() => (
<FoldedCommitBody
segments={segments}
previewSegments={previewSegments}
mentions={null}
/>
)}
initialPage={
{
component: 'Preview',
url: '/',
version: null,
props: {
errors: {},
auth: { user: null, pasteis: null, isAdmin: false },
},
clearHistory: false,
encryptHistory: false,
} as unknown as ComponentProps<typeof App>['initialPage']
}
/>,
);
}

const block = (html: string): BodySegment => ({
type: 'block',
language: 'bash',
filename: null,
html,
});

test('a shortened code preview uses its continuation cue without a loose ellipsis', () => {
const markup = renderBody(
[block('echo before\necho after')],
[block('echo before'), { type: 'text', text: '\n…' }],
);
assert.ok(markup.includes('mask-b-from-'));
assert.ok(markup.includes('echo before'));
assert.ok(!markup.includes('echo after'));
assert.ok(!markup.includes('…'));
assert.ok(markup.includes('Show more'));
assert.ok(markup.includes('aria-label="Copy code"'));
});

test('a completed code block keeps the ellipsis when later prose is hidden', () => {
const markup = renderBody(
[block('echo ready'), { type: 'text', text: '\nMore explanation' }],
[block('echo ready'), { type: 'text', text: '\n…' }],
);
assert.ok(!markup.includes('mask-b-from-'));
assert.ok(markup.includes('echo ready'));
assert.ok(markup.includes('…'));
assert.ok(markup.includes('Show more'));
});

test('a complete body shows all its code without an expansion control', () => {
const markup = renderBody([block('echo before\necho after')], null);
assert.ok(!markup.includes('mask-b-from-'));
assert.ok(markup.includes('echo after'));
assert.ok(!markup.includes('Show more'));
});
Loading