Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04c7a52a5a | ||
|
|
52df528994 | ||
|
|
2f00246d6f | ||
|
|
acec77a20c | ||
|
|
59413a8b64 | ||
|
|
cf69cac410 | ||
|
|
b0998fe524 |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 331 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 40 KiB |
@@ -485,10 +485,10 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me
|
||||
.evaluate((el) => el.getBoundingClientRect().left + parseFloat(getComputedStyle(el).paddingLeft));
|
||||
expect(headLeft, 'gruppehovedet er rykket ind').toBeLessThanOrEqual(looseLeft);
|
||||
|
||||
// A project inside a group starts further in than a row outside one. Measured
|
||||
// rather than asserted on a class, because indentation is a fact about where
|
||||
// the row's contents land — and measured at the *content* edge, not the box:
|
||||
// the indent is padding, so the element's own left edge does not move.
|
||||
// A project sits at the same left margin whether or not it is in a group — one
|
||||
// straight edge down the sidebar, with the bold heading doing the grouping the
|
||||
// indent used to. Measured at the *content* edge, not the box, because what the
|
||||
// eye follows is where the text lands.
|
||||
await sidebar.getByRole('link', { name: 'Regnskab' }).dragTo(heading);
|
||||
const contentLeft = (el) =>
|
||||
el.getBoundingClientRect().left + parseFloat(getComputedStyle(el).paddingLeft);
|
||||
@@ -496,7 +496,10 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me
|
||||
sidebar.locator('.folder > a').first().evaluate(contentLeft),
|
||||
sidebar.locator('.views a').first().evaluate(contentLeft)
|
||||
]);
|
||||
expect(inside, 'et projekt i en gruppe er ikke rykket ind').toBeGreaterThan(outside);
|
||||
expect(
|
||||
Math.abs(inside - outside),
|
||||
'et projekt i en gruppe står ikke i flugt med de faste visninger'
|
||||
).toBeLessThan(2);
|
||||
|
||||
await sidebar
|
||||
.getByRole('link', { name: 'Regnskab' })
|
||||
@@ -1522,9 +1525,18 @@ test('en gruppe er en side med sine projekter, en beskrivelse og filer', async (
|
||||
// Into the group, then onto its page — which is what the heading is a link to
|
||||
// now. The chevron beside it still folds; they are two intentions and the old
|
||||
// single target could only serve the smaller one.
|
||||
await sidebar
|
||||
.getByRole('link', { name: 'Drejebænk' })
|
||||
.dragTo(sidebar.getByRole('link', { name: 'Værksted' }));
|
||||
//
|
||||
// The drop is retried until the project actually nests under the group: a native
|
||||
// HTML5 drag occasionally does not register the first time under CI load, and a
|
||||
// single dragTo then leaves the group empty and the page assertion failing for a
|
||||
// reason that has nothing to do with what the test is about.
|
||||
const nested = sidebar.locator('.folder > a').filter({ hasText: 'Drejebænk' });
|
||||
await expect(async () => {
|
||||
await sidebar
|
||||
.getByRole('link', { name: 'Drejebænk' })
|
||||
.dragTo(sidebar.getByRole('link', { name: 'Værksted' }));
|
||||
await expect(nested).toHaveCount(1, { timeout: 1000 });
|
||||
}).toPass({ timeout: 10000 });
|
||||
await sidebar.getByRole('link', { name: 'Værksted' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Værksted', level: 1 })).toBeVisible();
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
it, which is the second time that drop has earned its test. -->
|
||||
<a
|
||||
href={item.href}
|
||||
data-view={item.key}
|
||||
class:active={current === item.href}
|
||||
class:receiving={item.key === 'today' && overToday}
|
||||
onclick={onnavigate}
|
||||
@@ -529,6 +530,52 @@
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
{:else if item.key === 'today'}
|
||||
<!-- A star, the way Things marks Today: the one view you open first. -->
|
||||
<svg class="mark" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path
|
||||
d="M8 2l1.7 3.6 3.9.5-2.9 2.7.7 3.9L8 10.9 4.6 13.4l.7-3.9L2.4 6.1l3.9-.5z"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.3"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{:else if item.key === 'upcoming'}
|
||||
<!-- A calendar with a day marked — the Calendar view carries the plain
|
||||
grid, so the marked day keeps the two apart. -->
|
||||
<svg class="mark" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<rect
|
||||
x="2.2"
|
||||
y="3.2"
|
||||
width="11.6"
|
||||
height="10.6"
|
||||
rx="1.4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
<path
|
||||
d="M2.2 6.4h11.6M5.6 2.2v2M10.4 2.2v2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<rect x="8.8" y="8.6" width="2.6" height="2.6" rx="0.5" fill="currentColor" />
|
||||
</svg>
|
||||
{:else if item.key === 'delegated'}
|
||||
<!-- A person: the tasks you are waiting on someone else for. -->
|
||||
<svg class="mark" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<circle cx="8" cy="5.4" r="2.5" fill="none" stroke="currentColor" stroke-width="1.4" />
|
||||
<path
|
||||
d="M3.4 13.4c0-2.6 2.1-4.2 4.6-4.2s4.6 1.6 4.6 4.2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<span class="dot" class:today={item.key === 'today'} aria-hidden="true"></span>
|
||||
{/if}
|
||||
@@ -570,6 +617,7 @@
|
||||
{#snippet projectRow(project, sortable)}
|
||||
<a
|
||||
href="/projekt/{project.id}"
|
||||
data-view={project.is_inbox ? 'inbox' : undefined}
|
||||
class:sortable
|
||||
class:active={current === `/projekt/${project.id}`}
|
||||
class:dragging={draggingId === project.id}
|
||||
@@ -593,7 +641,29 @@
|
||||
? onTaskDrop(e, project)
|
||||
: sortable && onDrop(e, project)}
|
||||
>
|
||||
<span class="dot" style="background: {colorVar(project.color)}" aria-hidden="true"></span>
|
||||
{#if project.is_inbox}
|
||||
<!-- A tray, not a coloured dot: the Inbox is a fixed view that happens to
|
||||
be a project, and it reads as one of the views above, not as the first
|
||||
of the projects below. -->
|
||||
<svg class="mark" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path
|
||||
d="M2.3 9.2L4 3.4a1.1 1.1 0 011-.8h6a1.1 1.1 0 011 .8l1.7 5.8"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.3"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2.3 9.2v2.9A1.3 1.3 0 003.6 13.4h8.8a1.3 1.3 0 001.3-1.3V9.2h-3.3l-1 1.7H6.6l-1-1.7z"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.3"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<span class="dot" style="background: {colorVar(project.color)}" aria-hidden="true"></span>
|
||||
{/if}
|
||||
{projectName(project)}
|
||||
<!-- Open tasks, not people. The same grey number meant three things in this
|
||||
one column — people at a project, projects at a group, tasks at a label
|
||||
@@ -656,7 +726,7 @@
|
||||
and with every project filed there would be no loose row left to aim at. -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="group-head"
|
||||
class="group-head sticky-head"
|
||||
class:over={overGroup === ''}
|
||||
ondragover={(e) => {
|
||||
if (!carries(e, PROJECT)) return;
|
||||
@@ -1133,8 +1203,26 @@
|
||||
flex: none;
|
||||
}
|
||||
|
||||
a.active .mark {
|
||||
color: var(--accent);
|
||||
/* The fixed views carry a colour of their own, the way Things does — the star
|
||||
amber, the Inbox blue — and keep it whether or not they are the active row.
|
||||
So no active-state override here; the colour is the icon's identity. */
|
||||
[data-view='inbox'] .mark {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
[data-view='today'] .mark {
|
||||
color: var(--color-amber);
|
||||
}
|
||||
[data-view='upcoming'] .mark {
|
||||
color: var(--color-tomato);
|
||||
}
|
||||
[data-view='delegated'] .mark {
|
||||
color: var(--color-violet);
|
||||
}
|
||||
[data-view='notes'] .mark {
|
||||
color: var(--color-teal);
|
||||
}
|
||||
[data-view='calendar'] .mark {
|
||||
color: var(--color-indigo);
|
||||
}
|
||||
|
||||
.group-head {
|
||||
@@ -1144,6 +1232,17 @@
|
||||
padding: 0 var(--s2) var(--s2);
|
||||
}
|
||||
|
||||
/* The Projekter heading holds the only way to add a project or a group, so it
|
||||
must not scroll away with a long list. Pinned to the top of the scroller, on
|
||||
the sidebar's own ground so the rows pass under it. */
|
||||
.sticky-head {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
padding-top: var(--s2);
|
||||
background: var(--surface-sunken);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 560;
|
||||
@@ -1370,21 +1469,31 @@
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* Projects inside a group are indented to the group's name, not to its dot:
|
||||
the eye follows the text, and lining up under the dot leaves the names
|
||||
looking a step short of where the heading starts. */
|
||||
/* Projects sit at the same left margin whether or not they are in an area —
|
||||
one straight edge down the whole sidebar reads calmer than a staircase, and
|
||||
the bold area heading above already does the grouping the indent used to. */
|
||||
.folder > a {
|
||||
padding-left: calc(var(--s2) + var(--s4));
|
||||
padding-left: var(--s2);
|
||||
}
|
||||
|
||||
/* The line is indented with them, so a drop between two projects in a group
|
||||
reads as landing in the group rather than across the whole sidebar. */
|
||||
.folder > a.sortable::before {
|
||||
left: calc(var(--s2) + var(--s4));
|
||||
left: var(--s2);
|
||||
}
|
||||
|
||||
.folder > .empty {
|
||||
padding-left: calc(var(--s2) + var(--s4));
|
||||
padding-left: var(--s2);
|
||||
}
|
||||
|
||||
/* An area is the sidebar's own heading — Things treats it as bold and plainly
|
||||
cased, a thing the eye lands on, not another faint uppercase label like the
|
||||
system sections. So an area's name is set apart while "Projekter", "Filtre"
|
||||
and "Etiketter" stay quiet. */
|
||||
.folder-head h2 {
|
||||
font-size: var(--menu-size);
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* Inside the rename form now, so it needs no padding of its own — the form
|
||||
@@ -1455,8 +1564,12 @@
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* An accent-tinted selection, not a half-step of surface: on the sunken dark
|
||||
sidebar --surface-raised was all but invisible, so the active row read as no
|
||||
different from the rest. --accent-sunken carries the accent hue and stands out
|
||||
in both themes. */
|
||||
a.active {
|
||||
background: var(--surface-raised);
|
||||
background: var(--accent-sunken);
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -1081,6 +1081,11 @@
|
||||
min-width: 0;
|
||||
border-right: 1px solid var(--line);
|
||||
padding-right: var(--s3);
|
||||
/* White in light, a nuance lighter than the ground in dark — the note list
|
||||
is a surface you read down, so it lifts off the page like the editor does.
|
||||
--surface-raised is #fff in the light themes and one step up in the dark. */
|
||||
background: var(--surface-raised);
|
||||
padding-left: var(--s3);
|
||||
}
|
||||
|
||||
.head {
|
||||
@@ -1546,7 +1551,9 @@
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
background: var(--surface);
|
||||
/* A nuance lighter than the ground in dark, still white in light — matches
|
||||
the note list beside it so the two read as one writing surface. */
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
/* Luft nok til at teksten ikke rører kanten.
|
||||
|
||||