fitle fixes

This commit is contained in:
beo3000 2026-02-23 17:19:26 +01:00
parent d510e96541
commit 63a862fd7d
9 changed files with 83 additions and 15 deletions

View File

@ -1 +1 @@
1.1.27
1.1.30

View File

@ -10,7 +10,9 @@
import ConfirmDialog from './ConfirmDialog.svelte';
import WiedervorlageSection from './WiedervorlageSection.svelte';
import LinkTitle from './LinkTitle.svelte';
import { get } from 'svelte/store';
import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
import { normalizeTitleAndBody } from '$lib/utils/titleUtils';
import { useUnsavedGuard } from '$lib/utils/unsavedGuard.svelte';
interface Props {
@ -96,12 +98,14 @@
const isPrivate = journalScope === 'private';
if (selectedLinkedContextId) {
const topic = await createTopic(selectedLinkedContextId, title);
const topic = await createTopic(selectedLinkedContextId, title, isPrivate);
if (body) {
await createHistoryEntry(topic.id, selectedDate, body, null, false, isPrivate);
}
} else {
const text = body ? `${title}\n${body}` : title;
const { maxTitleLength } = get(scopeSettings);
const { title: shortTitle, body: fullBody } = normalizeTitleAndBody(title, body, maxTitleLength);
const text = fullBody ? `${shortTitle}\n\n${fullBody}` : shortTitle;
await getOrCreateJournalTopic();
await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text, null, wiedervorlageChecked, isPrivate);
}
@ -129,7 +133,7 @@
function startEdit(entry: { id: string; text: string; isPrivate?: boolean }) {
const lines = entry.text.split('\n');
editingId = entry.id;
editTitle = lines[0];
editTitle = lines[0].replace(/^#{1,6}\s+/, '');
editBody = lines.slice(1).join('\n').trim();
editIsPrivate = !!entry.isPrivate;
}
@ -343,7 +347,7 @@
</div>
{:else}
{@const lines = entry.text.split('\n')}
{@const title = lines[0]}
{@const title = lines[0].replace(/^#{1,6}\s+/, '')}
{@const body = lines.slice(1).join('\n').trim()}
<div class="group mb-3 flex items-start gap-2 rounded bg-card-bg p-2.5">
<span class="mt-0.5 text-xs text-muted whitespace-nowrap">{formatTime(entry.updatedAt)}</span>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import type { Topic, HistoryEntry } from "@ka-note/shared";
import { get } from "svelte/store";
import { liveQuery } from "dexie";
import { db } from "$lib/db/schema";
import {
@ -11,6 +12,8 @@
} from "$lib/db/repositories";
import { today } from "$lib/db/helpers";
import { processedTopicIds, collapsedTopicIds } from "$lib/stores/agenda";
import { scopeSettings } from "$lib/stores/scopeContext";
import { normalizeTitleAndBody } from "$lib/utils/titleUtils";
import HistoryItem from "./HistoryItem.svelte";
import LinkTitle from "./LinkTitle.svelte";
import MeetingControls from "./MeetingControls.svelte";
@ -109,9 +112,15 @@
editingTitle = true;
}
function saveTitleEdit() {
if (titleInput.trim()) {
updateTopic(topic.id, { title: titleInput.trim() });
async function saveTitleEdit() {
const raw = titleInput.trim();
if (raw) {
const { maxTitleLength } = get(scopeSettings);
const { title, body } = normalizeTitleAndBody(raw, '', maxTitleLength);
await updateTopic(topic.id, { title });
if (body) {
await createHistoryEntry(topic.id, today(), body);
}
}
editingTitle = false;
}
@ -178,7 +187,7 @@
{:else}
<LinkTitle text={topic.title} />
<button
class="opacity-0 group-hover:opacity-100 ml-2.5 bg-transparent border-none text-[#aaa] cursor-pointer text-xs hover:text-white"
class="opacity-40 group-hover:opacity-100 ml-2.5 bg-transparent border-none text-[#aaa] cursor-pointer text-xs hover:text-white"
onclick={(e) => {
e.stopPropagation();
startTitleEdit();

View File

@ -1,6 +1,9 @@
import { get } from 'svelte/store';
import { db } from './schema';
import { newId, now, today } from './helpers';
import { getAccessToken } from '$lib/auth/authStore';
import { normalizeTitleAndBody } from '$lib/utils/titleUtils';
import { scopeSettings } from '$lib/stores/scopeContext';
import type { AgendaContext, Topic, HistoryEntry, Rating, ContextType, TopicStatus, ProjectMeta, PersonMeta, CompanyMeta, Page, Notebook, PageNotebook } from '@ka-note/shared';
// --- Contexts ---
@ -104,8 +107,10 @@ export async function getTopic(id: string): Promise<Topic | undefined> {
return db.topics.get(id);
}
export async function createTopic(contextId: string, title: string): Promise<Topic> {
export async function createTopic(contextId: string, rawTitle: string, isPrivate = false): Promise<Topic> {
const existing = await getTopicsByContext(contextId);
const { maxTitleLength } = get(scopeSettings);
const { title, body } = normalizeTitleAndBody(rawTitle, '', maxTitleLength);
const topic: Topic = {
id: newId(),
contextId,
@ -123,6 +128,9 @@ export async function createTopic(contextId: string, title: string): Promise<Top
await db.topics.update(t.id, { sortOrder: t.sortOrder + 1 });
}
await db.topics.put(topic);
if (body) {
await createHistoryEntry(topic.id, today(), body, null, false, isPrivate);
}
return topic;
}

View File

@ -13,6 +13,7 @@ export interface ScopeSettings {
unknownColor: string;
businessIcon: string;
privateIcon: string;
maxTitleLength: number;
}
const SETTINGS_KEY = 'ka-scope-settings';
@ -23,6 +24,7 @@ const defaults: ScopeSettings = {
unknownColor: '#555566',
businessIcon: '🏭',
privateIcon: '🏠',
maxTitleLength: 22,
};
function loadSettings(): ScopeSettings {

View File

@ -0,0 +1,15 @@
const DEFAULT_MAX_TITLE_LENGTH = 22;
/**
* If title exceeds maxLen, truncates it and prepends the full title to the body.
*/
export function normalizeTitleAndBody(
title: string,
body = '',
maxLen = DEFAULT_MAX_TITLE_LENGTH
): { title: string; body: string } {
if (title.length <= maxLen) return { title, body };
const short = title.slice(0, maxLen) + '…';
const newBody = body.trim() ? `${title}\n\n${body.trim()}` : title;
return { title: short, body: newBody };
}

View File

@ -92,6 +92,7 @@
unknownColor: '#555566',
businessIcon: '🏭',
privateIcon: '🏠',
maxTitleLength: 22,
});
settings = { ...$scopeSettings };
}
@ -168,6 +169,25 @@
</button>
</section>
<!-- Title truncation -->
<section class="space-y-4">
<h2 class="text-sm font-semibold uppercase text-muted">Titel</h2>
<p class="text-xs text-muted">Titel, die diese Zeichenlänge überschreiten, werden automatisch gekürzt. Der vollständige Text wird als Notiz gespeichert.</p>
<div class="rounded border border-border bg-card-bg p-4 flex items-center gap-4">
<label class="text-xs text-muted w-40" for="maxTitleLength">Max. Titellänge</label>
<input
id="maxTitleLength"
type="number"
min="10"
max="100"
bind:value={settings.maxTitleLength}
oninput={save}
class="w-20 rounded border border-border bg-bg px-2 py-1 text-sm text-center focus:outline-none focus:ring-1 focus:ring-accent"
/>
<span class="text-xs text-muted">Zeichen</span>
</div>
</section>
<!-- API Keys -->
<section class="space-y-4">
<h2 class="text-sm font-semibold uppercase text-muted">API Keys</h2>

View File

@ -8,11 +8,22 @@ import type { AuthEnv } from '../middleware/auth.js';
import { randomUUID } from 'crypto';
const JOURNAL_TOPIC_ID = 'daily-log-journal';
const MAX_TITLE_LENGTH = 22;
function now(): string {
return new Date().toISOString();
}
function normalizeTitle(title: string, body: string): { title: string; text: string } {
if (title.length <= MAX_TITLE_LENGTH) {
const text = body.trim() ? `${title}\n\n${body}` : title;
return { title, text };
}
const short = title.slice(0, MAX_TITLE_LENGTH) + '…';
const fullBody = body.trim() ? `${title}\n\n${body}` : title;
return { title: short, text: `${short}\n\n${fullBody}` };
}
async function ensureJournalTopic(userId: string): Promise<void> {
const existing = await db.select({ id: topics.id }).from(topics)
.where(and(eq(topics.id, JOURNAL_TOPIC_ID), eq(topics.userId, userId)))
@ -86,18 +97,18 @@ push.openapi(pushDailyLogRoute, async (c) => {
await ensureDailyLog(userId);
await ensureJournalTopic(userId);
const { date, scope, title, body } = c.req.valid('json');
const { date, scope, title: rawTitle, body } = c.req.valid('json');
const isPrivate = scope === 'private';
const titleMarker = `## ${title}`;
const { title, text } = normalizeTitle(rawTitle, body);
// Duplicate check: same date + title prefix in existing entries
// Duplicate check: same date + title as first line (plain or legacy ## prefix)
const existing = await db.select({ id: historyEntries.id })
.from(historyEntries)
.where(and(
eq(historyEntries.userId, userId),
eq(historyEntries.topicId, JOURNAL_TOPIC_ID),
eq(historyEntries.date, date),
sql`${historyEntries.text} LIKE ${titleMarker + '%'}`,
sql`(${historyEntries.text} LIKE ${title + '%'} OR ${historyEntries.text} LIKE ${'## ' + title + '%'})`,
))
.get();
@ -112,7 +123,6 @@ push.openapi(pushDailyLogRoute, async (c) => {
const sortOrder = (maxSortOrder?.val ?? 0) + 1;
const id = randomUUID();
const text = body.trim() ? `${titleMarker}\n\n${body}` : titleMarker;
await db.insert(historyEntries).values({
id,

BIN
vorlagen/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB