Some checks failed
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
The sidebar now presents pages as a collapsible tree built from parentId (folder view) or grouped under the hierarchical label tree (label view, read-only; multi-label pages appear under each label, untagged ones in an 'unlabeled' group). The pond owner sets the default via a new sidebarView pond setting (PATCH-merged like the other keys); every user can override it locally (ui.sidebar.view.<pondId>), and the toggle sits above the page list. Collapse state persists per pond. New pages created while a page is open become its children — the inline form says so and sends parentId. Reordering (buttons and drag-between) now operates within one sibling group; the label filter stays a folder-view feature and falls back to the flat list while active, so the filtered order is never mistaken for a partial tree. SidebarContent is keyed by pond id so the per-pond localStorage hooks mount with the right key. e2e hooks (.sidebar__pages, .sidebar__page, reorder buttons) kept; reorder/labels/content packs green locally, plus a live smoke of nesting, collapse persistence, and both views. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { CreatePageInput, PageView, createPageInputSchema } from '@dorfteich/shared';
|
|
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
import { Field, FormError } from '../components/forms';
|
|
import { apiPost } from '../lib/api';
|
|
|
|
interface NewPageFormProps {
|
|
pondId: string;
|
|
pondSlug: string;
|
|
/** Parent for the new page (issue #108): the currently open page, so new
|
|
* pages nest under where the user is; `null` creates at the root. */
|
|
parentId?: string | null;
|
|
parentTitle?: string;
|
|
onCreated: () => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
/** Inline "new page" prompt: title → create → editor opens (issue #26). */
|
|
export function NewPageForm({
|
|
pondId,
|
|
pondSlug,
|
|
parentId = null,
|
|
parentTitle,
|
|
onCreated,
|
|
onCancel,
|
|
}: NewPageFormProps): React.JSX.Element {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const [error, setError] = useState<unknown>(null);
|
|
const form = useForm<CreatePageInput>({ resolver: zodResolver(createPageInputSchema) });
|
|
|
|
const onSubmit = form.handleSubmit(async (input) => {
|
|
setError(null);
|
|
try {
|
|
const page = await apiPost<PageView>(`/ponds/${pondId}/pages`, { ...input, parentId });
|
|
onCreated();
|
|
navigate(`/p/${pondSlug}/${page.slug}`);
|
|
} catch (err) {
|
|
setError(err);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<form className="sidebar__new-page-form" onSubmit={(event) => void onSubmit(event)} noValidate>
|
|
<FormError error={error} />
|
|
<Field label={t('layout.sidebar.newPageTitle')} error={form.formState.errors.title?.message}>
|
|
<input
|
|
type="text"
|
|
autoFocus
|
|
{...form.register('title')}
|
|
onKeyDown={(event) => {
|
|
if (event.key === 'Escape') onCancel();
|
|
}}
|
|
/>
|
|
</Field>
|
|
{parentId && parentTitle && (
|
|
<p className="sidebar__new-page-parent">
|
|
{t('layout.sidebar.newPageUnder', { title: parentTitle })}
|
|
</p>
|
|
)}
|
|
<div className="sidebar__new-page-actions">
|
|
<button type="submit" className="button" disabled={form.formState.isSubmitting}>
|
|
{t('layout.sidebar.create')}
|
|
</button>
|
|
<button type="button" className="linklike" onClick={onCancel}>
|
|
{t('layout.sidebar.cancel')}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|