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
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,40 @@
information does not exist for a screen reader. -->
<span class="visually-hidden">{{ 'notes.pinnedState' | transloco }}</span>
}
<span class="card-top">
<!-- One line for the badge, the title and the glyphs. It was two, and the first of
them was a whole row spent on a badge and three small marks. -->
<span class="card-head">
<!-- No format for a todo list: a "TXT" badge would announce a language nothing
shows. -->
@if (!isChecklist()) {
<app-language-badge [language]="note().language" />
}
@if (hasPlaceholders()) {
<span class="card-fields" data-testid="note-card-fields" aria-hidden="true">⚡</span>
<span class="visually-hidden">
{{ 'notes.placeholdersCount' | transloco: { count: note().placeholders.length } }}
</span>
}
@if (note().attachmentCount > 0) {
<span class="card-clip" data-testid="note-card-clip" aria-hidden="true"
>📎 {{ note().attachmentCount }}</span
>
<span class="visually-hidden">
{{ 'notes.attachmentsCount' | transloco: { count: note().attachmentCount } }}
</span>
}
@if (note().pinned) {
<span class="card-pin" data-testid="note-card-pin" aria-hidden="true">📌</span>
}
</span>
<span class="card-title" data-testid="note-card-title">
@if (note().title) {
{{ note().title }}
} @else {
{{ 'notes.untitled' | transloco }}
}
<span class="card-title" data-testid="note-card-title">
@if (note().title) {
{{ note().title }}
} @else {
{{ 'notes.untitled' | transloco }}
}
</span>
<span class="card-marks">
@if (hasPlaceholders()) {
<span class="card-fields" data-testid="note-card-fields" aria-hidden="true">⚡</span>
<span class="visually-hidden">
{{ 'notes.placeholdersCount' | transloco: { count: note().placeholders.length } }}
</span>
}
@if (note().attachmentCount > 0) {
<span class="card-clip" data-testid="note-card-clip" aria-hidden="true"
>📎 {{ note().attachmentCount }}</span
>
<span class="visually-hidden">
{{ 'notes.attachmentsCount' | transloco: { count: note().attachmentCount } }}
</span>
}
@if (note().pinned) {
<span class="card-pin" data-testid="note-card-pin" aria-hidden="true">📌</span>
}
</span>
</span>
@if (isChecklist()) {
<!-- Decorative: the count beside it carries the same information as text. -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,55 @@ app-copy-button:focus-within {

// The children are <span>s — the parent <button> allows nothing else — so they need
// their block behaviour handed back explicitly.
.card-top {
//
// One line carrying the badge, the title and the marks. The badge used to own a row of
// its own: ~17px plus an 8px margin out of the 122px a card has, spent before a character
// of body. `baseline` and not `center` so the badge and the glyphs sit on the title's
// first line whether it takes one line or two.
.card-head {
display: flex;
align-items: center;
align-items: baseline;
gap: 7px;
margin-bottom: 8px;
// Reserves room for the copy and ⋯ buttons floating over the top-right corner.
padding-right: 50px;
// ⚠️ And for the selection tick on the left, which floats at `left: 8px` while the card's
// own padding starts at 14px. It therefore lands on whatever begins this line — and it is
// not a hover-only concern: `checked` keeps it on with no pointer at all, which is the
// state a multiple selection leaves every card in. Unreserved, it sat squarely on the
// language badge, and on a todo list, which has no badge, it would sit on the title.
// A permanent gutter rather than one that appears with the tick: the second would shift
// the title sideways under the pointer.
padding-left: 14px;
}

.card-pin {
margin-left: auto;
font-size: 11px;
}

.card-title {
display: block;
flex: 1;
// Without it a flex item refuses to shrink below its content, and a long unbroken
// title would push the marks out past the card.
min-width: 0;
font-size: 13px;
font-weight: 600;
color: var(--text-0);
margin-bottom: 8px;
line-height: 1.3;
// ⚠️ A budget, not a nicety. The title had none: a long one took three lines and ate
// the body, so two cards side by side showed different amounts of it for no reason the
// user had chosen.
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}

.card-marks {
display: flex;
align-items: baseline;
gap: 6px;
flex-shrink: 0;
}

// `app-code-viewer` brings the colouring only; the typography stays decided here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,36 @@ describe('NoteCardComponent', () => {
expect(badge.language()).toBe('json');
});

/**
* The badge used to own a row of its own above the title — ~17px plus an 8px margin out
* of the 122px a card has, spent before a character of body. Structural rather than
* visual on purpose: jsdom lays nothing out, so what is asserted is that the three share
* one parent, which is what makes them one line.
*/
it('puts the badge, the title and the marks on a single line', async () => {
fixture.componentRef.setInput('note', createNote({ title: 'My note', pinned: true, attachmentCount: 2 }));
await fixture.whenStable();

const head = fixture.nativeElement.querySelector('.card-head');
expect(head.querySelector('app-language-badge')).not.toBeNull();
expect(head.querySelector('[data-testid="note-card-title"]')).not.toBeNull();
expect(head.querySelector('[data-testid="note-card-clip"]')).not.toBeNull();
expect(head.querySelector('[data-testid="note-card-pin"]')).not.toBeNull();
});

it('falls back to a translated placeholder for an untitled note', async () => {
fixture.componentRef.setInput('note', createNote({ title: '' }));
await fixture.whenStable();

expect(text('.card-title')).toBe('Sans titre');
});

it('shows only the first 3 lines of content as a snippet', async () => {
fixture.componentRef.setInput('note', createNote({ content: 'one\ntwo\nthree\nfour' }));
it('shows only the first 4 lines of content as a snippet', async () => {
fixture.componentRef.setInput('note', createNote({ content: 'one\ntwo\nthree\nfour\nfive' }));
await fixture.whenStable();

const lines = fixture.debugElement.queryAll(By.css('.card-snippet .line-content'));
expect(lines.map((line) => line.nativeElement.textContent)).toEqual(['one', 'two', 'three']);
expect(lines.map((line) => line.nativeElement.textContent)).toEqual(['one', 'two', 'three', 'four']);
});

it('colours the snippet according to the note language', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export interface NoteActivation {
readonly extendRange: boolean;
}

const SNIPPET_LINES = 3;
/** What fits under a head that no longer spends a row on the language badge. */
const SNIPPET_LINES = 4;
const MAX_VISIBLE_TAGS = 2;
/** What fits between the progress bar and the footer on a 150 px card. */
const MAX_VISIBLE_ITEMS = 2;
Expand Down