feat(entity): Chunk #4 remaining items — consistency UI, context rail, palette search, expanded seed & DATA_MODEL
- Consistency tab in EntitiesView surfacing findInconsistencies() results - ContextRail + MentionPopover wired into WritingView for entity context/hover - CommandPalette indexes entities and chapters for cross-entity search - link field type for entity references in character fields - findDeadCharacterMentions consistency check + tests - Expand seed with a location, note, and a natural Elias Vell mention so the dead-character-after-death case is actually exercised - Author docs/DATA_MODEL.md documenting core schemas, types, storage contract - Rename CharacterMention id attr -> entityId to match core backlinks/consistency
This commit is contained in:
@@ -12,6 +12,14 @@ import { ENTITY_FIELDS } from "@pubscript/core";
|
||||
import { Input } from "@pubscript/ui";
|
||||
import { Select } from "@pubscript/ui";
|
||||
|
||||
/** A short display label for an entity, regardless of kind. */
|
||||
export function entityLabel(e: Entity): string {
|
||||
if ("body" in e) {
|
||||
return e.body.trim().slice(0, 40) || "(untitled note)";
|
||||
}
|
||||
return e.name;
|
||||
}
|
||||
|
||||
/** Read a field value from an entity, regardless of where it is stored. */
|
||||
export function getFieldValue(e: Entity, key: string): string | string[] {
|
||||
if ("fields" in e) {
|
||||
@@ -40,12 +48,15 @@ export function fieldsFor(e: Entity): FieldDef[] {
|
||||
interface EntityFieldsProps {
|
||||
entity: Entity;
|
||||
onChange: (patch: Partial<Entity>) => void;
|
||||
/** All entities in the book, used to resolve `link` field references. */
|
||||
entities?: Entity[];
|
||||
}
|
||||
|
||||
function renderField(
|
||||
field: FieldDef,
|
||||
value: string | string[],
|
||||
onChange: (v: string | string[]) => void,
|
||||
entities?: Entity[],
|
||||
) {
|
||||
const label = (
|
||||
<label className="ps-field-label">
|
||||
@@ -79,9 +90,22 @@ function renderField(
|
||||
);
|
||||
case "list":
|
||||
return <TagField value={Array.isArray(value) ? value : []} onChange={onChange} />;
|
||||
case "link":
|
||||
case "link": {
|
||||
const opts = (entities ?? [])
|
||||
.filter((e) => e.id !== value)
|
||||
.map((e) => ({ value: e.id, label: entityLabel(e) }));
|
||||
return (
|
||||
<div>
|
||||
{label}
|
||||
<Select
|
||||
value={typeof value === "string" ? value : ""}
|
||||
options={[{ value: "", label: "— none —" }, ...opts]}
|
||||
onChange={(ev) => onChange(ev.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "text":
|
||||
default:
|
||||
return (
|
||||
<div className="ps-field-span-2">
|
||||
{label}
|
||||
@@ -146,7 +170,7 @@ function TagField({
|
||||
);
|
||||
}
|
||||
|
||||
export function EntityFields({ entity, onChange }: EntityFieldsProps) {
|
||||
export function EntityFields({ entity, onChange, entities }: EntityFieldsProps) {
|
||||
const fields = fieldsFor(entity);
|
||||
|
||||
return (
|
||||
@@ -157,6 +181,7 @@ export function EntityFields({ entity, onChange }: EntityFieldsProps) {
|
||||
field,
|
||||
getFieldValue(entity, field.key),
|
||||
(v) => onChange({ [field.key]: v } as Partial<Entity>),
|
||||
entities,
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user