diff --git a/web/src/components/CardRow.vue b/web/src/components/CardRow.vue index 24dcc39..a276292 100644 --- a/web/src/components/CardRow.vue +++ b/web/src/components/CardRow.vue @@ -1,46 +1,18 @@ - - - - {{ card.status?.name ?? 'No status' }} - + + {{ card.text }} + + {{ card.status?.name ?? 'No status' }} + + ✕ diff --git a/web/src/components/KanbanCard.vue b/web/src/components/KanbanCard.vue index 27e7394..f2f8d97 100644 --- a/web/src/components/KanbanCard.vue +++ b/web/src/components/KanbanCard.vue @@ -5,5 +5,5 @@ defineProps<{ card: Card }>() - {{ card.text }} + {{ card.text }} diff --git a/web/src/router/index.ts b/web/src/router/index.ts index 9608dcc..de1b3f5 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -23,6 +23,12 @@ const router = createRouter({ component: () => import('../views/ProjectConfigureView.vue'), meta: { requiresAuth: true, wide: true }, }, + { + path: '/cards/:id(\\d+)', + name: 'card', + component: () => import('../views/CardView.vue'), + meta: { requiresAuth: true, wide: true }, + }, { path: '/profile', name: 'profile', diff --git a/web/src/stores/cards.ts b/web/src/stores/cards.ts index 2c433be..b632894 100644 --- a/web/src/stores/cards.ts +++ b/web/src/stores/cards.ts @@ -53,7 +53,6 @@ export const useCardsStore = defineStore('cards', () => { } const setComplete = (card: Card, complete: boolean) => patch(card, { complete }) - const setText = (card: Card, text: string) => patch(card, { text }) async function remove(card: Card): Promise { await apiRequest(`/cards/${card.id}`, { method: 'DELETE', auth: true }) @@ -75,7 +74,6 @@ export const useCardsStore = defineStore('cards', () => { load, add, setComplete, - setText, remove, reset, } diff --git a/web/src/style.css b/web/src/style.css index 63a2f33..26a6a6e 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -48,9 +48,7 @@ } /* No browser focus ring on form controls -- the border colour change is - highlight enough (components with more elaborate focus styles, e.g. - .card-row__text, override this with their own higher-specificity rule). - Specificity is a single pseudo-class + element, (0,0,1,1) -- .field below + highlight enough. Specificity is a single pseudo-class + element, (0,0,1,1) -- .field below is deliberately a single class with no element in the selector, (0,0,1,0), so it can never win a specificity tie against this regardless of source order (a wrapper-class + descendant-element rule, e.g. the old ".form @@ -550,7 +548,34 @@ h1 { background: var(--surface); } -.card-row__status { +/* Wraps the text + status badge -- the whole row is a link to the card's own + view, except the delete button (a sibling, so it isn't nested inside it). */ +.card-row__link { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.3rem 0.4rem; + border-radius: var(--radius-sm); + color: inherit; + text-decoration: none; +} + +.card-row__link:hover { + background: var(--bg); +} + +.card-row__text { + flex: 1; + min-width: 0; + font-size: 1rem; + word-break: break-word; +} + +/* A small pill naming a card's status -- the card list row and the card's + own view both show one. */ +.status-badge { flex: none; padding: 0.15rem 0.55rem; border-radius: var(--radius-pill); @@ -562,32 +587,11 @@ h1 { color: var(--muted); } -.card-row__status--none { +.status-badge--none { font-weight: 400; font-style: italic; } -.card-row__text { - flex: 1; - min-width: 0; - border: 1px solid transparent; - border-radius: var(--radius-sm); - background: transparent; - color: var(--text); - font-size: 1rem; - padding: 0.3rem 0.4rem; -} - -.card-row__text:hover { - border-color: var(--border); -} - -.card-row__text:focus { - outline: none; - border-color: var(--accent); - background: var(--bg); -} - .card-row__delete { flex: none; border: none; @@ -649,6 +653,19 @@ h1 { text-decoration: none; } +/* The card view's back link sits inside its title, right before the card's + text, rather than up in .project-head__actions like the other views. */ +.card-view__back { + display: inline-flex; + margin-right: 0.4rem; + color: var(--muted); + text-decoration: none; +} + +.card-view__back:hover { + color: var(--accent); +} + .menu { position: relative; flex: none; @@ -889,14 +906,21 @@ h1 { } .kanban-card { + display: block; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 0.55rem 0.65rem; font-size: 0.9rem; + color: inherit; + text-decoration: none; cursor: grab; } +.kanban-card:hover { + border-color: var(--accent); +} + .kanban-card--ghost { opacity: var(--opacity-ghost); } diff --git a/web/src/views/CardView.vue b/web/src/views/CardView.vue new file mode 100644 index 0000000..5502c7d --- /dev/null +++ b/web/src/views/CardView.vue @@ -0,0 +1,146 @@ + + + + + {{ loadError }} + + + + + ← + {{ card.text }} + + + + + {{ deleting ? 'Deleting…' : 'Delete card' }} + + + + + + + {{ card.status?.name ?? 'No status' }} + + + {{ deleteError }} + + + Edit text + + + + {{ saving ? 'Saving…' : 'Save' }} + + + {{ saveError.message }} + + + + diff --git a/web/src/views/ProjectView.vue b/web/src/views/ProjectView.vue index 8680bdc..f2a19de 100644 --- a/web/src/views/ProjectView.vue +++ b/web/src/views/ProjectView.vue @@ -181,7 +181,6 @@ async function onCreate() { v-for="card in sortedCards" :key="card.id" :card="card" - @save-text="(v) => run(cards.setText(card, v))" @delete="run(cards.remove(card))" />
{{ loadError }}
+ + {{ card.status?.name ?? 'No status' }} + +
{{ deleteError }}
{{ saveError.message }}