Export template source from 122c862dccc4a1825f19cc8d4eb1095e5661f1ea:examples/local-first/packages/language-reader-presentation
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import type { ExactReference } from "@quixos/camino-react";
|
||||
import { Icon } from "@quixos/camino-react/ui";
|
||||
import { copyText } from "./canvas/selection.js";
|
||||
import type { ReaderExtension } from "./extensions.js";
|
||||
import { VocabularyLabel, VocabularySettings } from "./vocabulary.js";
|
||||
|
||||
/** Register reader-wide configuration alongside the extension's contributions.
|
||||
* The host retains this section in shared presentation navigation. */
|
||||
export const readerExtensionSettings: readonly Pick<ReaderExtension, "id" | "title" | "Settings">[] = [
|
||||
{ id: "vocabulary", title: "Vocabulary", Settings: VocabularySettings },
|
||||
];
|
||||
|
||||
/** Install application extensions here. A sidebar and a context menu share the
|
||||
* same source-aware actions; neither needs to pretend to be an annotator. */
|
||||
export function createReaderExtensions(vocabulary: {
|
||||
vocabulary: ExactReference | null;
|
||||
name: string;
|
||||
known: ReadonlySet<string>;
|
||||
busy: string | null;
|
||||
ready: boolean;
|
||||
remember(word: string): Promise<void>;
|
||||
report(error: unknown): void;
|
||||
}): ReaderExtension[] {
|
||||
return [
|
||||
{
|
||||
id: "source",
|
||||
title: "Text",
|
||||
actions: (context) => {
|
||||
const text = context.target ? context.text.slice(context.target.start, context.target.end) : "";
|
||||
return text ? [{ id: "copy", label: "Copy original text", run: () => copyText(text) }] : [];
|
||||
},
|
||||
},
|
||||
{
|
||||
...readerExtensionSettings[0]!,
|
||||
sidebarVisible: (context) => !!context.target,
|
||||
actions: (context) => {
|
||||
const word = context.target ? context.text.slice(context.target.start, context.target.end).trim() : "";
|
||||
if (!word || word.length > 100 || !vocabulary.vocabulary) return [];
|
||||
const saved = vocabulary.known.has(word);
|
||||
return [
|
||||
{
|
||||
id: "vocabulary.save",
|
||||
label: saved ? "Saved to vocabulary" : vocabulary.busy === word ? "Saving…" : "Add to vocabulary",
|
||||
disabled: saved || !!vocabulary.busy || !vocabulary.ready,
|
||||
run: () => vocabulary.remember(word),
|
||||
},
|
||||
];
|
||||
},
|
||||
Sidebar: ({ context, actions }) =>
|
||||
!context.target ? null : (
|
||||
<section className="lr-word-actions" aria-label="Word actions">
|
||||
<div className="lr-word-action-buttons">
|
||||
{actions.map((action) => (
|
||||
<button
|
||||
type="button"
|
||||
key={action.id}
|
||||
disabled={action.disabled}
|
||||
onClick={() => void Promise.resolve().then(action.run).catch(vocabulary.report)}
|
||||
>
|
||||
{action.id === "vocabulary.save" && <Icon name="plus" size={14} />} {action.content ?? action.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{vocabulary.vocabulary && (
|
||||
<small>
|
||||
Saved in <VocabularyLabel reference={vocabulary.vocabulary} />
|
||||
</small>
|
||||
)}
|
||||
</section>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user