@notis_ai/cli 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/scaffolds/notis-database/app/page.tsx +38 -41
- package/dist/scaffolds/notis-database/lib/types.ts +5 -7
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
- package/dist/scaffolds/notis-database/notis.config.ts +48 -1
- package/dist/scaffolds/notis-database/package-lock.json +3935 -0
- package/dist/scaffolds/notis-database/package.json +2 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
- package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
- package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
- package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
- package/dist/scaffolds/notis-journal/package.json +2 -2
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
- package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
- package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
- package/dist/scaffolds/notis-notes/package.json +7 -3
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
- package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
- package/dist/scaffolds/notis-random/app/page.tsx +141 -74
- package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
- package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
- package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
- package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
- package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
- package/dist/scaffolds/notis-random/notis.config.ts +47 -3
- package/dist/scaffolds/notis-random/package-lock.json +4513 -0
- package/dist/scaffolds/notis-random/package.json +6 -2
- package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
- package/dist/scaffolds.json +8 -17
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +449 -144
- package/skills/notis-apps/cli.md +15 -0
- package/skills/notis-cli/SKILL.md +8 -4
- package/skills/notis-onboarding/BRIEF.md +93 -0
- package/src/cli.js +50 -12
- package/src/command-specs/apps.js +154 -21
- package/src/command-specs/index.js +2 -0
- package/src/command-specs/onboarding.js +216 -0
- package/src/command-specs/tools.js +8 -4
- package/src/runtime/app-platform.js +65 -4
- package/src/runtime/desktop-auth.js +66 -17
- package/src/runtime/errors.js +1 -0
- package/src/runtime/output.js +9 -2
- package/src/runtime/profiles.js +179 -17
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +26 -1
- package/template/.harness/index.html.tmpl +30 -2
- package/template/packages/sdk/src/config.ts +38 -1
- package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
- package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
- package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
- package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
- package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
- package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
- package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
- package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
- package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
- package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
- package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
- package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
- package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
- package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
- package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
- package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
- package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
- package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notis-apps/notis-database",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
+
"notisAppVersion": "0.1.0",
|
|
4
5
|
"private": true,
|
|
5
6
|
"type": "module",
|
|
6
7
|
"scripts": {
|
|
@@ -8,7 +9,7 @@
|
|
|
8
9
|
"build": "vite build"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@notis/sdk": "file
|
|
12
|
+
"@notis/sdk": "file:./packages/sdk",
|
|
12
13
|
"@phosphor-icons/react": "^2.1.10",
|
|
13
14
|
"@radix-ui/react-slot": "^1.1.0",
|
|
14
15
|
"class-variance-authority": "^0.7.0",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { useDocument } from '../hooks/useDocument';
|
|
6
|
+
import { Markdown } from './Markdown';
|
|
7
|
+
import type { NotisDocumentEditorProps } from '../runtime';
|
|
8
|
+
|
|
9
|
+
const fallbackNoticeStyle: CSSProperties = {
|
|
10
|
+
marginTop: '0.75rem',
|
|
11
|
+
fontSize: '12px',
|
|
12
|
+
color: 'hsl(var(--muted-foreground))',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const fallbackFrameStyle: CSSProperties = {
|
|
16
|
+
borderRadius: '0.5rem',
|
|
17
|
+
border: '1px dashed hsl(var(--border))',
|
|
18
|
+
padding: '1rem',
|
|
19
|
+
fontSize: '13px',
|
|
20
|
+
color: 'hsl(var(--muted-foreground))',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const fallbackTitleStyle: CSSProperties = {
|
|
24
|
+
margin: '0 0 0.75rem',
|
|
25
|
+
fontSize: '1.5rem',
|
|
26
|
+
fontWeight: 700,
|
|
27
|
+
letterSpacing: '-0.01em',
|
|
28
|
+
color: 'hsl(var(--foreground))',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read-only stand-in used when no host editor is available (standalone dev
|
|
33
|
+
* harness, snapshots). Markdown content renders via `Markdown`; other content
|
|
34
|
+
* types get a typed placeholder.
|
|
35
|
+
*/
|
|
36
|
+
function DocumentEditorFallback({ documentId, variant = 'full', className }: NotisDocumentEditorProps) {
|
|
37
|
+
const runtime = useNotisRuntime();
|
|
38
|
+
const { document, loading, error } = useDocument(documentId);
|
|
39
|
+
// Listing screenshots should show the app as it looks in the portal, so the
|
|
40
|
+
// harness-only editing notice stays out of scenario captures.
|
|
41
|
+
const isScreenshot = Boolean(runtime?.context?.screenshotScenario);
|
|
42
|
+
|
|
43
|
+
if (loading) {
|
|
44
|
+
return <div className={className} style={fallbackFrameStyle}>Loading document…</div>;
|
|
45
|
+
}
|
|
46
|
+
if (error || !document) {
|
|
47
|
+
return (
|
|
48
|
+
<div className={className} style={fallbackFrameStyle}>
|
|
49
|
+
{error?.message ?? 'Document not found.'}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const isMarkdown = document.contentType !== 'file';
|
|
55
|
+
return (
|
|
56
|
+
<div className={className}>
|
|
57
|
+
{variant === 'full' ? <h1 style={fallbackTitleStyle}>{document.title}</h1> : null}
|
|
58
|
+
{isMarkdown ? (
|
|
59
|
+
<>
|
|
60
|
+
<Markdown value={document.contentMarkdown ?? ''} />
|
|
61
|
+
{isScreenshot ? null : (
|
|
62
|
+
<p style={fallbackNoticeStyle}>Read-only preview — editing is available inside the Notis portal.</p>
|
|
63
|
+
)}
|
|
64
|
+
</>
|
|
65
|
+
) : (
|
|
66
|
+
<div style={fallbackFrameStyle}>
|
|
67
|
+
{document.fileType ? `${document.fileType.toUpperCase()} document` : 'File document'} — open it in
|
|
68
|
+
the Notis portal to view.
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Embeds the document editor for a document anywhere in an app view. The host
|
|
77
|
+
* (portal) provides the implementation through `runtime.ui.DocumentEditor` and
|
|
78
|
+
* dispatches on the document's content type (markdown -> rich text editor,
|
|
79
|
+
* files -> matching viewer). Outside the portal this falls back to a read-only
|
|
80
|
+
* preview.
|
|
81
|
+
*
|
|
82
|
+
* ```tsx
|
|
83
|
+
* <DocumentEditor documentId={entry.id} variant="body" className="min-h-[320px]" />
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function DocumentEditor(props: NotisDocumentEditorProps): ReactElement {
|
|
87
|
+
const runtime = useNotisRuntime();
|
|
88
|
+
const HostEditor = runtime?.ui?.DocumentEditor;
|
|
89
|
+
if (HostEditor) {
|
|
90
|
+
return <HostEditor {...props} />;
|
|
91
|
+
}
|
|
92
|
+
return <DocumentEditorFallback {...props} />;
|
|
93
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { AnchorHTMLAttributes, ReactElement } from 'react';
|
|
4
|
+
import ReactMarkdown, { type Components } from 'react-markdown';
|
|
5
|
+
import remarkGfm from 'remark-gfm';
|
|
6
|
+
|
|
7
|
+
export interface MarkdownProps {
|
|
8
|
+
/** Markdown source to render. */
|
|
9
|
+
value: string;
|
|
10
|
+
/** Typography scale. 'sm' suits dense surfaces (cards, list rows). */
|
|
11
|
+
size?: 'sm' | 'base';
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Per-element overrides, passed through to react-markdown. */
|
|
14
|
+
components?: Components;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function MarkdownLink({
|
|
18
|
+
href,
|
|
19
|
+
children,
|
|
20
|
+
node: _node,
|
|
21
|
+
...rest
|
|
22
|
+
}: AnchorHTMLAttributes<HTMLAnchorElement> & { node?: unknown }) {
|
|
23
|
+
const isExternal = typeof href === 'string' && /^https?:\/\//i.test(href);
|
|
24
|
+
return (
|
|
25
|
+
<a
|
|
26
|
+
href={href}
|
|
27
|
+
{...(isExternal ? { target: '_blank', rel: 'noreferrer noopener' } : {})}
|
|
28
|
+
{...rest}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</a>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultComponents: Components = {
|
|
36
|
+
a: MarkdownLink,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Renders a markdown value with Notis typography. Markdown is a value type:
|
|
41
|
+
* use this anywhere markdown appears — document content, database fields,
|
|
42
|
+
* generated text. Styling comes from `.notis-markdown` rules in the SDK
|
|
43
|
+
* stylesheet and follows the portal theme variables.
|
|
44
|
+
*/
|
|
45
|
+
export function Markdown({ value, size = 'base', className, components }: MarkdownProps): ReactElement {
|
|
46
|
+
const classes = ['notis-markdown', size === 'sm' ? 'notis-markdown--sm' : null, className]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(' ');
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={classes}>
|
|
52
|
+
<ReactMarkdown
|
|
53
|
+
remarkPlugins={[remarkGfm]}
|
|
54
|
+
components={components ? { ...defaultComponents, ...components } : defaultComponents}
|
|
55
|
+
>
|
|
56
|
+
{value}
|
|
57
|
+
</ReactMarkdown>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -73,7 +73,12 @@ function shortcutMatches(shortcut: ParsedShortcut, event: KeyboardEvent): boolea
|
|
|
73
73
|
return true;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function
|
|
76
|
+
function isEditableEvent(event: Event): boolean {
|
|
77
|
+
// Notis apps mount inside a Shadow DOM, so a document-level listener sees
|
|
78
|
+
// `event.target` retargeted to the shadow host. `composedPath()[0]` pierces
|
|
79
|
+
// the shadow boundary to the real focused element, so action-bar shortcuts
|
|
80
|
+
// don't fire while the user is typing in an app input.
|
|
81
|
+
const target = event.composedPath?.()[0] ?? event.target;
|
|
77
82
|
if (!(target instanceof HTMLElement)) return false;
|
|
78
83
|
if (target.isContentEditable) return true;
|
|
79
84
|
const tag = target.tagName;
|
|
@@ -193,7 +198,7 @@ export function MultiSelectActionBar({
|
|
|
193
198
|
if (selectedCount === 0 || parsedShortcuts.length === 0) return;
|
|
194
199
|
|
|
195
200
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
196
|
-
if (
|
|
201
|
+
if (isEditableEvent(event)) return;
|
|
197
202
|
for (const { action, parsed } of parsedShortcuts) {
|
|
198
203
|
if (shortcutMatches(parsed, event)) {
|
|
199
204
|
event.preventDefault();
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration utilities for notis.config.ts.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```ts
|
|
6
|
+
* // notis.config.ts
|
|
7
|
+
* import { defineNotisApp } from '@notis/sdk/config';
|
|
8
|
+
*
|
|
9
|
+
* export default defineNotisApp({
|
|
10
|
+
* name: 'My App',
|
|
11
|
+
* description: 'Does things',
|
|
12
|
+
* icon: 'phosphor:squares-four',
|
|
13
|
+
* databases: ['tasks'],
|
|
14
|
+
* routes: [
|
|
15
|
+
* {
|
|
16
|
+
* path: '/',
|
|
17
|
+
* slug: 'notes',
|
|
18
|
+
* name: 'Notes',
|
|
19
|
+
* default: true,
|
|
20
|
+
* collection: {
|
|
21
|
+
* database: 'notes',
|
|
22
|
+
* titleProperty: 'Name',
|
|
23
|
+
* parentProperty: 'Parent',
|
|
24
|
+
* sidebar: {
|
|
25
|
+
* mode: 'tree',
|
|
26
|
+
* allowCreate: true,
|
|
27
|
+
* },
|
|
28
|
+
* },
|
|
29
|
+
* },
|
|
30
|
+
* ],
|
|
31
|
+
* tools: [...],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export interface NotisRouteConfig {
|
|
37
|
+
path: string;
|
|
38
|
+
slug: string;
|
|
39
|
+
name: string;
|
|
40
|
+
icon?: string;
|
|
41
|
+
parentSlug?: string | null;
|
|
42
|
+
default?: boolean;
|
|
43
|
+
exportName?: string;
|
|
44
|
+
collection?: {
|
|
45
|
+
database: string;
|
|
46
|
+
titleProperty: string;
|
|
47
|
+
parentProperty?: string | null;
|
|
48
|
+
sidebar?: {
|
|
49
|
+
mode: 'flat-list' | 'tree';
|
|
50
|
+
allowCreate: boolean;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const NOTIS_APP_CATEGORIES = [
|
|
56
|
+
'Productivity',
|
|
57
|
+
'Sales & Marketing',
|
|
58
|
+
'Operations',
|
|
59
|
+
'Product & Engineering',
|
|
60
|
+
'Personal',
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export type NotisAppCategory = typeof NOTIS_APP_CATEGORIES[number];
|
|
64
|
+
|
|
65
|
+
export interface NotisAppDatabaseConfig {
|
|
66
|
+
/** Database slug, as declared by the app. */
|
|
67
|
+
slug: string;
|
|
68
|
+
/**
|
|
69
|
+
* Ship this database's rows to everyone who installs the app.
|
|
70
|
+
*
|
|
71
|
+
* Off by default: declaring a database publishes its STRUCTURE, never its
|
|
72
|
+
* content. Turn it on only for starter content that every installer should
|
|
73
|
+
* receive - a default folder tree, a set of templates - and never for a
|
|
74
|
+
* database that accumulates the author's own data.
|
|
75
|
+
*/
|
|
76
|
+
seedDocuments?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface NotisAppAuthor {
|
|
80
|
+
name: string;
|
|
81
|
+
handle?: string;
|
|
82
|
+
url?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface NotisAppSkillConfig {
|
|
86
|
+
/** Stable source-owned key used by other app declarations. */
|
|
87
|
+
key: string;
|
|
88
|
+
/** Path to the skill entrypoint, relative to notis.config.ts. */
|
|
89
|
+
path: string;
|
|
90
|
+
/** User-facing name used for the installed skill. */
|
|
91
|
+
name: string;
|
|
92
|
+
description?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface NotisAppOnboardingConfig {
|
|
96
|
+
/** Key of a skill declared in `skills`. */
|
|
97
|
+
skill: string;
|
|
98
|
+
/** Editable message placed in Notis when onboarding is opened. */
|
|
99
|
+
prompt: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface NotisAppScreenshotConfig {
|
|
103
|
+
/** Conventional metadata/screenshot-N.png source path. */
|
|
104
|
+
path: string;
|
|
105
|
+
/** Meaningful description used by the Store gallery and assistive technology. */
|
|
106
|
+
alt: string;
|
|
107
|
+
/** Route slug captured by `notis apps screenshot`. */
|
|
108
|
+
route?: string;
|
|
109
|
+
/** Optional fixture scenario from metadata/screenshot-fixtures.json. */
|
|
110
|
+
scenario?: string;
|
|
111
|
+
/** Optional CSS selector captured as the truthful focal region for this Store image. */
|
|
112
|
+
focus?: string;
|
|
113
|
+
/** Portal color scheme used while rendering this screenshot. Defaults to light. */
|
|
114
|
+
theme?: 'light' | 'dark';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Named accent tokens for an app's avatar. Keep in sync with the portal
|
|
119
|
+
* `ACCENT_NAMES` and the server `ACCENT_TOKENS`.
|
|
120
|
+
*/
|
|
121
|
+
export const NOTIS_APP_ACCENTS = [
|
|
122
|
+
'blue',
|
|
123
|
+
'violet',
|
|
124
|
+
'emerald',
|
|
125
|
+
'amber',
|
|
126
|
+
'rose',
|
|
127
|
+
'sky',
|
|
128
|
+
'fuchsia',
|
|
129
|
+
'teal',
|
|
130
|
+
] as const;
|
|
131
|
+
|
|
132
|
+
export type NotisAppAccent = typeof NOTIS_APP_ACCENTS[number];
|
|
133
|
+
|
|
134
|
+
export interface NotisAppCapabilities {
|
|
135
|
+
/**
|
|
136
|
+
* Read every database in the workspace, not only the ones this app declares
|
|
137
|
+
* in `databases` or created itself.
|
|
138
|
+
*
|
|
139
|
+
* An app runtime is otherwise sandboxed to its own databases, so a catalog or
|
|
140
|
+
* explorer app sees an empty list without this. `'read'` is the only accepted
|
|
141
|
+
* value and it never grants writes: `LOCAL_NOTIS_DATABASE_UPSERT_*` stays
|
|
142
|
+
* bound to the app's own databases.
|
|
143
|
+
*/
|
|
144
|
+
workspaceDatabases?: 'read';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface NotisAppConfig {
|
|
148
|
+
/** URL-safe app slug. Existing apps may still use a display name here. */
|
|
149
|
+
name: string;
|
|
150
|
+
/** Human display title, Raycast-style. Falls back to `name`. */
|
|
151
|
+
title?: string;
|
|
152
|
+
description?: string;
|
|
153
|
+
/**
|
|
154
|
+
* App icon. A `phosphor:<name>` value (e.g. `phosphor:dice-five`) or
|
|
155
|
+
* `metadata/icon.png`. When unset, the app shows its two-letter initials.
|
|
156
|
+
*/
|
|
157
|
+
icon?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Optional accent color for the app avatar. One of {@link NOTIS_APP_ACCENTS}.
|
|
160
|
+
* When unset, a stable accent is derived automatically from the app id.
|
|
161
|
+
*/
|
|
162
|
+
accent?: NotisAppAccent;
|
|
163
|
+
author?: NotisAppAuthor;
|
|
164
|
+
categories?: NotisAppCategory[];
|
|
165
|
+
tagline?: string;
|
|
166
|
+
/** @deprecated Add release entries to the root CHANGELOG.md instead. */
|
|
167
|
+
versionNotes?: string;
|
|
168
|
+
/** Editorial screenshot order and capture scenarios for the Store listing. */
|
|
169
|
+
screenshots?: NotisAppScreenshotConfig[];
|
|
170
|
+
/**
|
|
171
|
+
* Databases this app owns. A bare string publishes structure only; use the
|
|
172
|
+
* object form to opt a database into shipping its rows to installers.
|
|
173
|
+
*/
|
|
174
|
+
databases?: (string | NotisAppDatabaseConfig)[];
|
|
175
|
+
/**
|
|
176
|
+
* Extra permissions the app asks for at install time. Everything here widens
|
|
177
|
+
* what the app can reach beyond its own data, so each one is surfaced to the
|
|
178
|
+
* user before they install.
|
|
179
|
+
*/
|
|
180
|
+
capabilities?: NotisAppCapabilities;
|
|
181
|
+
routes?: NotisRouteConfig[];
|
|
182
|
+
tools?: string[];
|
|
183
|
+
/** Skills shipped from this app's source tree. */
|
|
184
|
+
skills?: NotisAppSkillConfig[];
|
|
185
|
+
/** Optional onboarding entrypoint exposed from the app sidebar. */
|
|
186
|
+
onboarding?: NotisAppOnboardingConfig;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Identity function that provides type checking and autocomplete for the
|
|
191
|
+
* Notis app configuration. The returned object is read at build time by
|
|
192
|
+
* `notis apps build` to generate the manifest.
|
|
193
|
+
*/
|
|
194
|
+
export function defineNotisApp(config: NotisAppConfig): NotisAppConfig {
|
|
195
|
+
return config;
|
|
196
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document data helpers: normalize raw tool results (Notion-shaped property
|
|
3
|
+
* values, snake_case fields) into the SDK's `DocumentRecord`/`DatabaseProperty`
|
|
4
|
+
* shapes, and derive plain-text projections from typed content.
|
|
5
|
+
*
|
|
6
|
+
* These are pure functions — safe in any context (portal, dev harness, tests).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
DatabaseProperty,
|
|
11
|
+
DatabasePropertyOption,
|
|
12
|
+
DatabasePropertyType,
|
|
13
|
+
DocumentContentType,
|
|
14
|
+
DocumentRecord,
|
|
15
|
+
} from './runtime';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Value primitives
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export function isPresentString(value: unknown): value is string {
|
|
22
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
26
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
27
|
+
? (value as Record<string, unknown>)
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function optionalString(value: unknown): string | null {
|
|
32
|
+
return isPresentString(value) ? value.trim() : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Flattens a Notion-shaped rich_text array (or plain string) to text. */
|
|
36
|
+
export function extractRichText(value: unknown): string {
|
|
37
|
+
if (typeof value === 'string') return value;
|
|
38
|
+
if (!Array.isArray(value)) return '';
|
|
39
|
+
return value
|
|
40
|
+
.map((item) => {
|
|
41
|
+
const record = asRecord(item);
|
|
42
|
+
const text = asRecord(record?.text);
|
|
43
|
+
return optionalString(text?.content) ?? optionalString(record?.plain_text) ?? '';
|
|
44
|
+
})
|
|
45
|
+
.join('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Extracts the ids of a normalized relation property value. */
|
|
49
|
+
export function getRelationIds(value: unknown): string[] {
|
|
50
|
+
if (!Array.isArray(value)) return [];
|
|
51
|
+
return value.filter((item): item is string => isPresentString(item));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Property + document normalization
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collapses a Notion-shaped property value ({type: 'select', select: {...}})
|
|
60
|
+
* into a plain JS value (string, string[], number, boolean, ...). Values that
|
|
61
|
+
* are already plain pass through unchanged.
|
|
62
|
+
*/
|
|
63
|
+
export function normalizePropertyValue(value: unknown): unknown {
|
|
64
|
+
const record = asRecord(value);
|
|
65
|
+
if (!record) return value;
|
|
66
|
+
|
|
67
|
+
const type = optionalString(record.type);
|
|
68
|
+
if (!type) return value;
|
|
69
|
+
|
|
70
|
+
if (type === 'title') return extractRichText(record.title);
|
|
71
|
+
if (type === 'rich_text') return extractRichText(record.rich_text);
|
|
72
|
+
if (type === 'select' || type === 'status') {
|
|
73
|
+
return optionalString(asRecord(record[type])?.name) ?? record[type] ?? null;
|
|
74
|
+
}
|
|
75
|
+
if (type === 'multi_select') {
|
|
76
|
+
const items = Array.isArray(record.multi_select) ? record.multi_select : [];
|
|
77
|
+
return items.map((item) => optionalString(asRecord(item)?.name) ?? item).filter(Boolean);
|
|
78
|
+
}
|
|
79
|
+
if (type === 'relation') {
|
|
80
|
+
const items = Array.isArray(record.relation) ? record.relation : [];
|
|
81
|
+
return items.map((item) => optionalString(asRecord(item)?.id) ?? item).filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
if (type === 'date') return optionalString(asRecord(record.date)?.start) ?? record.date ?? null;
|
|
84
|
+
if (type in record) return record[type];
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeContentType(value: unknown): DocumentContentType | null {
|
|
89
|
+
return value === 'markdown' || value === 'file' ? value : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalizes a raw document from a Notis database tool result
|
|
94
|
+
* (LOCAL_NOTIS_DATABASE_QUERY / GET_DOCUMENT / UPSERT_*) into a
|
|
95
|
+
* `DocumentRecord`: camelCases fields and collapses property values.
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeDocumentRecord(value: unknown): DocumentRecord {
|
|
98
|
+
const record = asRecord(value) ?? {};
|
|
99
|
+
const rawProperties = asRecord(record.properties) ?? {};
|
|
100
|
+
const properties: Record<string, unknown> = {};
|
|
101
|
+
for (const [key, propertyValue] of Object.entries(rawProperties)) {
|
|
102
|
+
properties[key] = normalizePropertyValue(propertyValue);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
id: optionalString(record.id) ?? '',
|
|
107
|
+
title: optionalString(record.title) ?? 'Untitled',
|
|
108
|
+
url: optionalString(record.url),
|
|
109
|
+
properties,
|
|
110
|
+
icon: optionalString(record.icon),
|
|
111
|
+
cover: optionalString(record.cover),
|
|
112
|
+
databaseSlug: optionalString(record.databaseSlug) ?? optionalString(record.database_slug) ?? undefined,
|
|
113
|
+
contentType: normalizeContentType(record.contentType ?? record.content_type),
|
|
114
|
+
fileType: optionalString(record.fileType) ?? optionalString(record.file_type),
|
|
115
|
+
contentBlocknote: Array.isArray(record.contentBlocknote)
|
|
116
|
+
? (record.contentBlocknote as Array<Record<string, unknown>>)
|
|
117
|
+
: Array.isArray(record.content_blocknote)
|
|
118
|
+
? (record.content_blocknote as Array<Record<string, unknown>>)
|
|
119
|
+
: null,
|
|
120
|
+
contentMarkdown: optionalString(record.contentMarkdown) ?? optionalString(record.content_markdown),
|
|
121
|
+
plainText: optionalString(record.plainText) ?? optionalString(record.plain_text),
|
|
122
|
+
createdAt:
|
|
123
|
+
optionalString(record.createdAt)
|
|
124
|
+
?? optionalString(record.created_at)
|
|
125
|
+
?? optionalString(record.created_time),
|
|
126
|
+
lastEditedTime:
|
|
127
|
+
optionalString(record.lastEditedTime)
|
|
128
|
+
?? optionalString(record.last_edited_time)
|
|
129
|
+
?? optionalString(record.updated_at),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Normalizes a raw schema property from LOCAL_NOTIS_DATABASE_GET_DATABASE. */
|
|
134
|
+
export function normalizeDatabaseProperty(value: unknown): DatabaseProperty | null {
|
|
135
|
+
const record = asRecord(value);
|
|
136
|
+
const name = optionalString(record?.name);
|
|
137
|
+
if (!record || !name) return null;
|
|
138
|
+
|
|
139
|
+
const rawOptions = Array.isArray(record.options) ? record.options : [];
|
|
140
|
+
const options = rawOptions.flatMap((option): DatabasePropertyOption[] => {
|
|
141
|
+
const optionRecord = asRecord(option);
|
|
142
|
+
const optionName = optionalString(optionRecord?.name);
|
|
143
|
+
if (!optionRecord || !optionName) return [];
|
|
144
|
+
return [{
|
|
145
|
+
id: optionalString(optionRecord.id),
|
|
146
|
+
name: optionName,
|
|
147
|
+
color: optionalString(optionRecord.color),
|
|
148
|
+
order: typeof optionRecord.order === 'number' ? optionRecord.order : undefined,
|
|
149
|
+
}];
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
id: optionalString(record.id),
|
|
154
|
+
name,
|
|
155
|
+
type: (optionalString(record.type) ?? 'rich_text') as DatabasePropertyType,
|
|
156
|
+
description: optionalString(record.description),
|
|
157
|
+
options,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Plain-text projections
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
/** Strips markdown syntax to readable plain text (previews, search). */
|
|
166
|
+
export function markdownToPlainText(markdown: string): string {
|
|
167
|
+
return markdown
|
|
168
|
+
// fenced code blocks: keep the code, drop the fences
|
|
169
|
+
.replace(/```[^\n]*\n?/g, '')
|
|
170
|
+
// images: keep alt text
|
|
171
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
172
|
+
// links: keep link text
|
|
173
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
174
|
+
// html tags
|
|
175
|
+
.replace(/<[^>]+>/g, ' ')
|
|
176
|
+
// headings, blockquotes, list markers, task boxes
|
|
177
|
+
.replace(/^\s{0,3}(#{1,6}\s+|>\s?|[-*+]\s+(\[[ xX]\]\s+)?|\d+\.\s+)/gm, '')
|
|
178
|
+
// emphasis / strikethrough / inline code
|
|
179
|
+
.replace(/(\*\*|__|[*_~`])/g, '')
|
|
180
|
+
// table separators and pipes
|
|
181
|
+
.replace(/^\s*\|?[-:| ]+\|?\s*$/gm, '')
|
|
182
|
+
.replace(/\|/g, ' ')
|
|
183
|
+
// horizontal rules
|
|
184
|
+
.replace(/^\s*([-*_]\s*){3,}$/gm, '')
|
|
185
|
+
.replace(/\s+/g, ' ')
|
|
186
|
+
.trim();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Flattens BlockNote block JSON to plain text. */
|
|
190
|
+
export function blockNoteToPlainText(blocks: unknown): string {
|
|
191
|
+
const segments: string[] = [];
|
|
192
|
+
|
|
193
|
+
function visit(node: unknown): void {
|
|
194
|
+
if (typeof node === 'string') {
|
|
195
|
+
if (node.trim()) segments.push(node.trim());
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (Array.isArray(node)) {
|
|
199
|
+
node.forEach(visit);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const record = asRecord(node);
|
|
203
|
+
if (!record) return;
|
|
204
|
+
if (typeof record.text === 'string' && record.text.trim()) {
|
|
205
|
+
segments.push(record.text.trim());
|
|
206
|
+
}
|
|
207
|
+
visit(record.content);
|
|
208
|
+
visit(record.children);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
visit(blocks);
|
|
212
|
+
return segments.join(' ').replace(/\s+/g, ' ').trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Best-available plain-text preview for a document:
|
|
217
|
+
* plainText -> contentMarkdown -> contentBlocknote -> title.
|
|
218
|
+
*/
|
|
219
|
+
export function getDocumentPreview(document: DocumentRecord): string {
|
|
220
|
+
if (isPresentString(document.plainText)) {
|
|
221
|
+
return document.plainText.replace(/\s+/g, ' ').trim();
|
|
222
|
+
}
|
|
223
|
+
if (isPresentString(document.contentMarkdown)) {
|
|
224
|
+
return markdownToPlainText(document.contentMarkdown);
|
|
225
|
+
}
|
|
226
|
+
const fromBlocks = blockNoteToPlainText(document.contentBlocknote);
|
|
227
|
+
if (fromBlocks) return fromBlocks;
|
|
228
|
+
return isPresentString(document.title) ? document.title : 'Untitled';
|
|
229
|
+
}
|