Fjern mottak-konseptet: alle referanser peker til arbeidsflaten
- «Mottak» → «Arbeidsflaten» i alle tilbake-lenker
- goto('/workspace') → goto('/') i ContextHeader
- Slettet NodeEditor.svelte og NewChatDialog.svelte (kun brukt av mottak)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c46c9e364e
commit
f092afd2ba
10 changed files with 9 additions and 404 deletions
|
|
@ -168,7 +168,7 @@
|
||||||
<div class="context-header-inner">
|
<div class="context-header-inner">
|
||||||
<!-- Left: Back + Context selector -->
|
<!-- Left: Back + Context selector -->
|
||||||
<div class="context-header-left">
|
<div class="context-header-left">
|
||||||
<a href="/" class="context-back" title="Tilbake til mottak">←</a>
|
<a href="/" class="context-back" title="Tilbake til arbeidsflaten">←</a>
|
||||||
|
|
||||||
<div class="context-selector">
|
<div class="context-selector">
|
||||||
<button
|
<button
|
||||||
|
|
@ -198,7 +198,7 @@
|
||||||
{#if !searchQuery.trim()}
|
{#if !searchQuery.trim()}
|
||||||
<button
|
<button
|
||||||
class="context-selector-item"
|
class="context-selector-item"
|
||||||
onclick={() => { selectorOpen = false; goto('/workspace'); }}
|
onclick={() => { selectorOpen = false; goto('/'); }}
|
||||||
>
|
>
|
||||||
<span class="context-selector-item-title">Min arbeidsflate</span>
|
<span class="context-selector-item-title">Min arbeidsflate</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { nodeStore, edgeStore } from '$lib/realtime';
|
|
||||||
import type { Node } from '$lib/realtime';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
currentUserId: string;
|
|
||||||
onselect: (personId: string) => void;
|
|
||||||
onclose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { currentUserId, onselect, onclose }: Props = $props();
|
|
||||||
|
|
||||||
let search = $state('');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Available people to chat with: all person nodes except current user.
|
|
||||||
* For 1:1 chat we show person nodes the user can see.
|
|
||||||
*/
|
|
||||||
const people = $derived.by(() => {
|
|
||||||
const persons: Node[] = [];
|
|
||||||
for (const node of nodeStore.byKind('person')) {
|
|
||||||
if (node.id !== currentUserId) {
|
|
||||||
persons.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort alphabetically by title
|
|
||||||
persons.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
|
|
||||||
return persons;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Filter by search term */
|
|
||||||
const filtered = $derived.by(() => {
|
|
||||||
if (!search.trim()) return people;
|
|
||||||
const q = search.trim().toLowerCase();
|
|
||||||
return people.filter(p => (p.title || '').toLowerCase().includes(q));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there's already a 1:1 communication with this person.
|
|
||||||
* Returns the communication node ID if found, undefined otherwise.
|
|
||||||
*/
|
|
||||||
function existingChatWith(personId: string): string | undefined {
|
|
||||||
// Find communication nodes where both current user and person are participants
|
|
||||||
const userComms = new Set<string>();
|
|
||||||
for (const edge of edgeStore.bySource(currentUserId)) {
|
|
||||||
if (edge.edgeType === 'owner' || edge.edgeType === 'member_of') {
|
|
||||||
const target = nodeStore.get(edge.targetId);
|
|
||||||
if (target?.nodeKind === 'communication') {
|
|
||||||
userComms.add(edge.targetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const edge of edgeStore.bySource(personId)) {
|
|
||||||
if (edge.edgeType === 'owner' || edge.edgeType === 'member_of') {
|
|
||||||
if (userComms.has(edge.targetId)) {
|
|
||||||
return edge.targetId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSelect(personId: string) {
|
|
||||||
onselect(personId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBackdropClick(e: MouseEvent) {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
onclose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeydown(e: KeyboardEvent) {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
onclose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window onkeydown={handleKeydown} />
|
|
||||||
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
||||||
<div
|
|
||||||
onclick={handleBackdropClick}
|
|
||||||
class="fixed inset-0 z-50 flex items-start justify-center bg-black/40 pt-20"
|
|
||||||
>
|
|
||||||
<div class="w-full max-w-md rounded-xl bg-white shadow-xl">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex items-center justify-between border-b border-gray-200 px-4 py-3">
|
|
||||||
<h2 class="text-lg font-semibold text-gray-900">Ny samtale</h2>
|
|
||||||
<button
|
|
||||||
onclick={onclose}
|
|
||||||
class="rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
|
|
||||||
aria-label="Lukk"
|
|
||||||
>
|
|
||||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search -->
|
|
||||||
<div class="border-b border-gray-100 px-4 py-2">
|
|
||||||
<input
|
|
||||||
bind:value={search}
|
|
||||||
type="text"
|
|
||||||
placeholder="Søk etter person…"
|
|
||||||
class="w-full rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 text-sm placeholder:text-gray-400 focus:border-blue-300 focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- People list -->
|
|
||||||
<div class="max-h-80 overflow-y-auto">
|
|
||||||
{#if filtered.length === 0}
|
|
||||||
<p class="px-4 py-6 text-center text-sm text-gray-400">
|
|
||||||
{#if people.length === 0}
|
|
||||||
Ingen andre brukere funnet
|
|
||||||
{:else}
|
|
||||||
Ingen treff for «{search}»
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
{:else}
|
|
||||||
<ul>
|
|
||||||
{#each filtered as person (person.id)}
|
|
||||||
{@const existing = existingChatWith(person.id)}
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
onclick={() => handleSelect(person.id)}
|
|
||||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-blue-100 text-sm font-medium text-blue-700">
|
|
||||||
{(person.title || '?')[0].toUpperCase()}
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<p class="font-medium text-gray-900">{person.title || 'Ukjent'}</p>
|
|
||||||
{#if existing}
|
|
||||||
<p class="text-xs text-gray-400">Har eksisterende samtale</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<svg class="h-4 w-4 shrink-0 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,243 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount, onDestroy } from 'svelte';
|
|
||||||
import { Editor } from '@tiptap/core';
|
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
|
||||||
import Link from '@tiptap/extension-link';
|
|
||||||
import Image from '@tiptap/extension-image';
|
|
||||||
import Placeholder from '@tiptap/extension-placeholder';
|
|
||||||
import { uploadMedia, casUrl } from '$lib/api';
|
|
||||||
import VoiceRecorder from './VoiceRecorder.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onsubmit: (data: { title: string; content: string; html: string }) => Promise<void>;
|
|
||||||
disabled?: boolean;
|
|
||||||
accessToken?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { onsubmit, disabled = false, accessToken }: Props = $props();
|
|
||||||
|
|
||||||
let editorElement: HTMLDivElement | undefined = $state();
|
|
||||||
let editor: Editor | undefined = $state();
|
|
||||||
let title = $state('');
|
|
||||||
let submitting = $state(false);
|
|
||||||
let uploading = $state(0);
|
|
||||||
let error = $state('');
|
|
||||||
let voiceMemoInfo = $state('');
|
|
||||||
|
|
||||||
async function handleImageUpload(file: File): Promise<void> {
|
|
||||||
if (!accessToken) {
|
|
||||||
error = 'Ikke innlogget — kan ikke laste opp bilder';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!file.type.startsWith('image/')) return;
|
|
||||||
|
|
||||||
uploading++;
|
|
||||||
try {
|
|
||||||
const result = await uploadMedia(accessToken, {
|
|
||||||
file,
|
|
||||||
title: file.name,
|
|
||||||
visibility: 'hidden'
|
|
||||||
});
|
|
||||||
const src = casUrl(result.cas_hash);
|
|
||||||
editor?.chain().focus().setImage({
|
|
||||||
src,
|
|
||||||
alt: file.name,
|
|
||||||
title: file.name
|
|
||||||
}).run();
|
|
||||||
// Add data-node-id to the just-inserted image
|
|
||||||
// TipTap Image extension stores src/alt/title as attributes.
|
|
||||||
// We extend the node attribute below to include data-node-id.
|
|
||||||
} catch (e) {
|
|
||||||
error = e instanceof Error ? e.message : 'Feil ved bildeopplasting';
|
|
||||||
} finally {
|
|
||||||
uploading--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handle files from drop or paste events */
|
|
||||||
function handleFiles(files: FileList | File[]) {
|
|
||||||
for (const file of files) {
|
|
||||||
if (file.type.startsWith('image/')) {
|
|
||||||
handleImageUpload(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend the Image extension to include data-node-id attribute
|
|
||||||
const CasImage = Image.extend({
|
|
||||||
addAttributes() {
|
|
||||||
return {
|
|
||||||
...this.parent?.(),
|
|
||||||
'data-node-id': {
|
|
||||||
default: null,
|
|
||||||
parseHTML: (element: HTMLElement) => element.getAttribute('data-node-id'),
|
|
||||||
renderHTML: (attributes: Record<string, unknown>) => {
|
|
||||||
if (!attributes['data-node-id']) return {};
|
|
||||||
return { 'data-node-id': attributes['data-node-id'] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
editor = new Editor({
|
|
||||||
element: editorElement!,
|
|
||||||
extensions: [
|
|
||||||
StarterKit.configure({
|
|
||||||
heading: { levels: [2, 3] },
|
|
||||||
codeBlock: false,
|
|
||||||
horizontalRule: false
|
|
||||||
}),
|
|
||||||
Link.configure({
|
|
||||||
openOnClick: false,
|
|
||||||
HTMLAttributes: { class: 'text-blue-600 underline' }
|
|
||||||
}),
|
|
||||||
CasImage.configure({
|
|
||||||
inline: false,
|
|
||||||
allowBase64: false,
|
|
||||||
HTMLAttributes: {
|
|
||||||
class: 'max-w-full h-auto rounded'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Placeholder.configure({
|
|
||||||
placeholder: 'Skriv noe…'
|
|
||||||
})
|
|
||||||
],
|
|
||||||
editorProps: {
|
|
||||||
attributes: {
|
|
||||||
class: 'prose prose-sm max-w-none focus:outline-none min-h-[80px] px-3 py-2'
|
|
||||||
},
|
|
||||||
handleDrop: (_view, event, _slice, moved) => {
|
|
||||||
if (moved || !event.dataTransfer?.files?.length) return false;
|
|
||||||
const imageFiles = Array.from(event.dataTransfer.files).filter(f =>
|
|
||||||
f.type.startsWith('image/')
|
|
||||||
);
|
|
||||||
if (imageFiles.length === 0) return false;
|
|
||||||
event.preventDefault();
|
|
||||||
handleFiles(imageFiles);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
handlePaste: (_view, event) => {
|
|
||||||
const files = event.clipboardData?.files;
|
|
||||||
if (!files?.length) return false;
|
|
||||||
const imageFiles = Array.from(files).filter(f =>
|
|
||||||
f.type.startsWith('image/')
|
|
||||||
);
|
|
||||||
if (imageFiles.length === 0) return false;
|
|
||||||
event.preventDefault();
|
|
||||||
handleFiles(imageFiles);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTransaction: () => {
|
|
||||||
// Force Svelte reactivity
|
|
||||||
editor = editor;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
editor?.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
const isEmpty = $derived(!title.trim() && (!editor || editor.isEmpty));
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
if (isEmpty || submitting || disabled || uploading > 0) return;
|
|
||||||
|
|
||||||
submitting = true;
|
|
||||||
error = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = editor?.getText() ?? '';
|
|
||||||
const html = editor?.getHTML() ?? '';
|
|
||||||
await onsubmit({ title: title.trim(), content, html });
|
|
||||||
// Clear editor on success
|
|
||||||
title = '';
|
|
||||||
editor?.commands.clearContent();
|
|
||||||
} catch (e) {
|
|
||||||
error = e instanceof Error ? e.message : 'Noe gikk galt';
|
|
||||||
} finally {
|
|
||||||
submitting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeydown(e: KeyboardEvent) {
|
|
||||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleSubmit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
||||||
<div class="rounded-lg border border-gray-200 bg-white shadow-sm" onkeydown={handleKeydown}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
bind:value={title}
|
|
||||||
placeholder="Tittel (valgfritt)"
|
|
||||||
disabled={disabled || submitting}
|
|
||||||
class="w-full border-b border-gray-100 bg-transparent px-3 py-2 text-sm font-medium text-gray-900 placeholder:text-gray-400 focus:outline-none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div bind:this={editorElement}></div>
|
|
||||||
|
|
||||||
{#if uploading > 0}
|
|
||||||
<p class="px-3 py-1 text-xs text-blue-600">Laster opp {uploading} bilde{uploading > 1 ? 'r' : ''}…</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if error}
|
|
||||||
<p class="px-3 py-1 text-xs text-red-600">{error}</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if voiceMemoInfo}
|
|
||||||
<p class="px-3 py-1 text-xs text-green-600">{voiceMemoInfo}</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between border-t border-gray-100 px-3 py-2">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-xs text-gray-400">
|
|
||||||
{#if editor}
|
|
||||||
Markdown · Ctrl+B/I · Dra/lim bilder · Ctrl+Enter for å sende
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<VoiceRecorder
|
|
||||||
{accessToken}
|
|
||||||
disabled={disabled || submitting}
|
|
||||||
onerror={(msg) => { error = msg; }}
|
|
||||||
onrecorded={() => { voiceMemoInfo = 'Talenotat lastet opp — transkriberes i bakgrunnen'; setTimeout(() => { voiceMemoInfo = ''; }, 5000); }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onclick={handleSubmit}
|
|
||||||
disabled={isEmpty || submitting || disabled || uploading > 0}
|
|
||||||
class="rounded bg-blue-600 px-3 py-1 text-xs font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-40"
|
|
||||||
>
|
|
||||||
{#if uploading > 0}
|
|
||||||
Laster opp…
|
|
||||||
{:else if submitting}
|
|
||||||
Sender…
|
|
||||||
{:else}
|
|
||||||
Opprett node
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:global(.tiptap p.is-editor-empty:first-child::before) {
|
|
||||||
color: #9ca3af;
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
float: left;
|
|
||||||
height: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.tiptap img) {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -249,7 +249,7 @@
|
||||||
<header class="border-b border-gray-200 bg-white">
|
<header class="border-b border-gray-200 bg-white">
|
||||||
<div class="flex items-center justify-between px-4 py-3">
|
<div class="flex items-center justify-between px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Mottak</a>
|
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Arbeidsflaten</a>
|
||||||
<h1 class="text-lg font-semibold text-gray-900">
|
<h1 class="text-lg font-semibold text-gray-900">
|
||||||
{boardNode?.title || 'Kanban-brett'}
|
{boardNode?.title || 'Kanban-brett'}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,7 @@
|
||||||
<header class="border-b border-gray-200 bg-white">
|
<header class="border-b border-gray-200 bg-white">
|
||||||
<div class="flex items-center justify-between px-4 py-3">
|
<div class="flex items-center justify-between px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Mottak</a>
|
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Arbeidsflaten</a>
|
||||||
<h1 class="text-lg font-semibold text-gray-900">Kalender</h1>
|
<h1 class="text-lg font-semibold text-gray-900">Kalender</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@
|
||||||
<div class="rounded-lg border border-yellow-200 bg-yellow-50 p-4 text-sm text-yellow-800">
|
<div class="rounded-lg border border-yellow-200 bg-yellow-50 p-4 text-sm text-yellow-800">
|
||||||
<p class="font-medium">Samtale ikke funnet</p>
|
<p class="font-medium">Samtale ikke funnet</p>
|
||||||
<p class="mt-1">Kommunikasjonsnoden med ID {communicationId} finnes ikke eller er ikke tilgjengelig.</p>
|
<p class="mt-1">Kommunikasjonsnoden med ID {communicationId} finnes ikke eller er ikke tilgjengelig.</p>
|
||||||
<a href="/" class="mt-2 inline-block text-blue-600 hover:underline">Tilbake til mottak</a>
|
<a href="/" class="mt-2 inline-block text-blue-600 hover:underline">Tilbake til arbeidsflaten</a>
|
||||||
</div>
|
</div>
|
||||||
{:else if messages.length === 0}
|
{:else if messages.length === 0}
|
||||||
<p class="text-center text-sm text-gray-400">
|
<p class="text-center text-sm text-gray-400">
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@
|
||||||
<div class="workspace-message workspace-message-warn">
|
<div class="workspace-message workspace-message-warn">
|
||||||
<p class="workspace-message-title">Samling ikke funnet</p>
|
<p class="workspace-message-title">Samling ikke funnet</p>
|
||||||
<p>Samlingsnoden med ID {collectionId} finnes ikke eller er ikke tilgjengelig.</p>
|
<p>Samlingsnoden med ID {collectionId} finnes ikke eller er ikke tilgjengelig.</p>
|
||||||
<a href="/" class="workspace-link">Tilbake til mottak</a>
|
<a href="/" class="workspace-link">Tilbake til arbeidsflaten</a>
|
||||||
</div>
|
</div>
|
||||||
{:else if traitNames.length === 0}
|
{:else if traitNames.length === 0}
|
||||||
<div class="workspace-message">
|
<div class="workspace-message">
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
<header class="border-b border-gray-200 bg-white">
|
<header class="border-b border-gray-200 bg-white">
|
||||||
<div class="mx-auto flex max-w-4xl items-center justify-between px-4 py-3">
|
<div class="mx-auto flex max-w-4xl items-center justify-between px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Mottak</a>
|
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Arbeidsflaten</a>
|
||||||
<h1 class="text-lg font-semibold text-gray-900">Ny samling</h1>
|
<h1 class="text-lg font-semibold text-gray-900">Ny samling</h1>
|
||||||
</div>
|
</div>
|
||||||
{#if connected}
|
{#if connected}
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@
|
||||||
<header class="border-b border-gray-200 bg-white">
|
<header class="border-b border-gray-200 bg-white">
|
||||||
<div class="mx-auto flex max-w-3xl items-center justify-between px-4 py-3">
|
<div class="mx-auto flex max-w-3xl items-center justify-between px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Mottak</a>
|
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Arbeidsflaten</a>
|
||||||
<h1 class="text-lg font-semibold text-gray-900">Dagbok</h1>
|
<h1 class="text-lg font-semibold text-gray-900">Dagbok</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|
|
||||||
|
|
@ -404,7 +404,7 @@
|
||||||
<header class="border-b border-gray-200 bg-white shrink-0">
|
<header class="border-b border-gray-200 bg-white shrink-0">
|
||||||
<div class="flex items-center justify-between px-4 py-3">
|
<div class="flex items-center justify-between px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Mottak</a>
|
<a href="/" class="text-sm text-gray-400 hover:text-gray-600">← Arbeidsflaten</a>
|
||||||
<h1 class="text-lg font-semibold text-gray-900">Kunnskapsgraf</h1>
|
<h1 class="text-lg font-semibold text-gray-900">Kunnskapsgraf</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue