fitle fixes
This commit is contained in:
parent
d510e96541
commit
63a862fd7d
|
|
@ -1 +1 @@
|
|||
1.1.27
|
||||
1.1.30
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Loading…
Reference in New Issue