diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 21e9fe88d..b5157c5df 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1328,6 +1328,22 @@ images and files embedded in shared notes (`SharedEntry.extra.blobs`) as read-only rows — open the parent note, or copy the file into your own materials. +**Templates** (`SharedEntry.type === "template"`) are a separate share +target from notes: a page is written out as a `PageTemplate` (not a +`GraphiumDocument`), so a template carries only blocks, labels, and +table behavior — no lineage, chats, or shared refs +(`src/features/sharing/share-template.ts`, DATA_MODEL.md §7.1/§7.4). The +Library's **Templates** tab lists them read-only (title / description / +author, no fork action — a template is a blank starting point, not a +record to copy) and offers "New note from template", which reads the +body, converts it back into blocks +(`src/features/template/from-page-template.ts`), re-materializes any +`shared-blob:` media the same way Fork does, and opens the result with +a `templateFrom` origin field instead of `forkedFrom`. The same +conversion also powers a "Team templates" section inside the `/template` +slash-command picker (`TemplatePickerModal`), which inserts the chosen +template at the cursor instead of opening a new note. + Today the shared backend is a local folder. Other backends (cloud buckets, S3, IPFS-style) can be added by implementing the same blob interface. diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md index 1176c8b80..3631e27d3 100644 --- a/docs/DATA_MODEL.md +++ b/docs/DATA_MODEL.md @@ -105,6 +105,13 @@ type GraphiumDocument = { // ── shared storage refs (Phase 2) ─────────────────── sharedRef?: { id; type: "note" | "knowledge"; sharedAt; hash }; forkedFrom?: { sharedId; hash; authorName; authorEmail; forkedAt }; + // Origin info for a note created from a shared template (§7.1, type + // "template"). Same shape idea as `forkedFrom` (sharedId/hash) but a + // different meaning: a fork is a copy of a record, while a note created + // from a template does not inherit any facts from the template — it just + // started from its blocks/labels. Kept as a separate field so PROV can + // treat the two differently. + templateFrom?: { sharedId; hash; title; usedAt }; // ── skill metadata (only when source === "skill") ─── skillMeta?: SkillMeta; @@ -1820,6 +1827,24 @@ Key model choices: evolving vocabulary, so it is kept out of the shared format's folder structure — older builds can still list, preview, and fork an entry whose `wikiKind` they do not know. +- **`"template"` body is a `PageTemplate`, not a `GraphiumDocument`** — + sharing a page as a template writes the (previously dormant) + `PageTemplate` JSON (`src/features/template/types.ts`: `name` / + `pageTitle` / `blocks` / `labels` / `attributes` / `tableMeta` / + `mediaInlineLabels`) instead of a note. A template does not carry + lineage, chats, or shared refs — only the page's blocks and label/table + behavior. `attributes` (step checked / executor / status) lives only in + the runtime `LabelStore`, never in `GraphiumPage`, so the share dialog + passes a snapshot of it explicitly; entries whose block carries no label + are dropped. Those attributes are restored when a shared template is + **inserted** via `/template`; the "new note from template" path cannot + restore them, because `GraphiumPage` has nowhere to store them. + `extra` narrows to `{ title, description, stepCount, labelCount, + pageTitle, blobs? }`. Embedded media follow the same auto-blob path as + note sharing. Re-sharing does **not** track a prior version — every + share of a page mints a brand-new `id` (`sharedRef` on the source note + is left untouched, since a template is an independent handout, not a + copy-of-record). ### 7.2 `BlobRef` @@ -1878,6 +1903,24 @@ forkedFrom?: { The fork is treated as a separate identity from the original; PROV records the lineage between them. +A note created from a shared **template** carries `templateFrom` +instead: + +```ts +templateFrom?: { + sharedId: string; // template's SharedEntry.id + hash: string; // SharedEntry.hash at the time the template was used + title: string; // template's extra.title + usedAt: string; // ISO 8601 +}; +``` + +Same shape idea as `forkedFrom` (`sharedId` / `hash`) but a different +meaning: a fork is a copy of a record and inherits its facts, while a +note created from a template starts fresh — it only reuses the +template's blocks/labels, not any content or claims. Kept as a separate +field so PROV can distinguish the two relationships. + ### 7.5 Citation block (`sharedCitation`) A note can cite a shared entry inline through the `sharedCitation` @@ -1965,7 +2008,11 @@ steps — the same extraction the personal note index uses) and `buildProcessEntry` (the process graph). The projected `process` is the return value of `buildProcessEntry` unmodified, except `crossNoteLinks` is always cleared: those links point at the *sharer's* local note ids, -which are meaningless on the receiving side. +which are meaningless on the receiving side. Projection is independent of +the "include the shared library in ⌘K and AI chat" switch: when that +switch is off the lexical lane reads nothing, so the sync hook instead +fetches only the bodies whose `hash` differs from the cached projection +and updates the projection from those (nothing is added to the index). **Reconstructible cache.** A `version` or `logic` mismatch discards the whole file and starts empty; entries missing from a subsequent list diff --git a/manual/ja/notes-and-editor.md b/manual/ja/notes-and-editor.md index 12729084a..284f10380 100644 --- a/manual/ja/notes-and-editor.md +++ b/manual/ja/notes-and-editor.md @@ -206,7 +206,7 @@ Graphium は `⌘⇧N`(Windows/Linux は `Ctrl+Shift+N`)を「新規ノー **画像**・**動画**・**音声**・**ドキュメント** は、どれも同じピッカーを開きます。新しいものなら **ファイルからアップロード**、すでに取り込んだ素材ならそこから選びます。結果は **挿入方法** の切り替えで決まります — **埋め込み**(「中身をノート内に展開して表示」)か **リンク**(「@リンクとして挿入(中身は展開しない)」)。アップロードしたものは素材ライブラリにも入ります — [素材と引用](/ja/materials-and-citations)を参照してください。 -## テンプレート +## テンプレート {#templates} **テンプレート** を選ぶと **テンプレートを挿入** モーダルが開き、組み込みのレイアウトが 2 つあります。 @@ -219,6 +219,8 @@ Graphium は `⌘⇧N`(Windows/Linux は `Ctrl+Shift+N`)を「新規ノー ![計画テンプレートと実施テンプレートが並んだテンプレート挿入モーダル](/screenshots/template-picker.png) +チームの共有フォルダを設定していれば、チームメンバーがテンプレートとして共有したページが、組み込みテンプレートの後ろに同じ表の行として並び、提供元列に **チーム** バッジが付きます — [ストレージと同期](/ja/storage-and-sync#sharing-notes-with-your-team)を参照してください。 + ## 表を扱う {#tables} ここから先は、どの入口から作った表にも共通する話です。 diff --git a/manual/ja/storage-and-sync.md b/manual/ja/storage-and-sync.md index 392db8564..6885ddfa3 100644 --- a/manual/ja/storage-and-sync.md +++ b/manual/ja/storage-and-sync.md @@ -93,8 +93,16 @@ Fork したページが入るのは、ノートではなく自分のナレッジ ノート一覧でノートを、ナレッジの一覧でナレッジページをチェックボックスで選ぶと、一括操作のバーに件数つきの **共有** ボタンが加わります。共有は 1 件ずつ順に進み、終わると内訳が出ます。新しく共有した件数、すでに共有していたものを更新した件数、そして失敗した件数とその理由です。1 件失敗しても残りは止まりませんし、途中でキャンセルもできます。そこまでに共有できたものは、そのまま残ります。 +同じ複数選択バーは素材ギャラリーの一覧表示にもあり、画像やファイルを 1 件ずつではなくまとめて共有できます。 + このボタンが出るのは、デスクトップ版で共有フォルダと identity を設定したあとです。共有されるのは保存済みの内容なので、編集中のノートは先に保存してください。 +### ページをテンプレートとして共有する {#share-a-page-as-a-template} + +ノートの `⋯` メニューには **テンプレートとして共有** も並びます(**チームと共有** の隣)。通常の共有と違い、ページを記録としてではなく「使い回せる雛形」として渡します。名前(既定はノートの題名)と、任意で説明を入力します。ページはそのままの状態で共有されます。結果や入力済みの値を自動で消すことはしないので、渡したくないものは共有する前に自分で消してください。 + +同じページをもう一度テンプレートとして共有すると、以前のものを更新するのではなく、新しいエントリが作られます。テンプレートは元のノートに紐づくスナップショットではなく、独立した配布物だからです。 + ### 見る・fork する {#browse-and-fork} 共有フォルダを設定すると、サイドバーに **ライブラリ** セクションと **共有** の項目が現れます。チーム全員が共有したもの(ノート・ナレッジページ・文献・データファイル)が、種類ごとのタブに並びます。他人のエントリは読み取り専用です。**Fork** を押すと、自分のストレージにコピーして自由に編集できます。持っているメディアは自動でローカルのライブラリに実体化され、fork にはその出どころが記録されます。 @@ -107,7 +115,9 @@ Fork したページが入るのは、ノートではなく自分のナレッジ **素材** タブにも同じくフォルダ列があり、共有ノートに貼られている画像・ファイルが、素材として直接共有されたものと並んで一覧に出ます。ノートに貼られたものは単独で fork や検証ができるエントリではないため、**ノートを開く** か **自分の素材に取り込む** かのどちらかを行います。同じファイルが複数の共有ノートに貼られている場合は 1 行にまとめられ、何件のノートに含まれるかが件数で示されます。 -自分の左ナビゲーションと同じ構成をもう二つ、**ラベル** と **プロセス** のタブが鏡になっています。共有ノートから見つかったラベルと PROV-DM の手順を、自分のノート一覧・プロセス一覧と同じやり方で抽出して表示します。これは後述する共有検索・AI チャットを支えているバックグラウンドの読み込みに相乗りしているため、Graphium がまだ本文を読んでいない共有ノートは、これらのタブにはまだ反映されません。プロセスタブから自分のノートへ fork する操作は、自分の手順を fork するときと同じです。 +自分の左ナビゲーションと同じ構成をもう二つ、**ラベル** と **プロセス** のタブが鏡になっています。共有ノートから見つかったラベルと PROV-DM の手順を、自分のノート一覧・プロセス一覧と同じやり方で抽出して表示します。これは後述する共有検索・AI チャットを支えているバックグラウンドの読み込みに相乗りしているため、Graphium がまだ本文を読んでいない共有ノートは、これらのタブにはまだ反映されません。プロセスタブから自分のノートへ fork する操作は、自分の手順を fork するときと同じです。ラベルやプロセスだけを共有する専用の操作はありません。それらを含むノートを共有すれば十分で、両タブとも自分のノート一覧へ戻るリンク付きでその旨を説明しています。 + +**テンプレート** タブには、テンプレートとして共有されたページ(前述)が並びます。**ノート** タブのフォルダ列の代わりに **説明** 列があります。ここに **Fork** はありません — テンプレートはコピーする記録ではなく、まっさらな出発点だからです。代わりに詳細パネルに **テンプレートから新規ノート** があり、自分が共有したものも含めてどのエントリでも押せます。テンプレートを読み込み、そのブロックとラベルから新しいノートを組み立て、埋め込まれたメディアを自分のライブラリに実体化して開きます。新しいノートには、どのテンプレートから作られたかが記録されますが、fork の「派生元」とは別扱いです。テンプレートは事実を何も引き継がず、形だけを渡すものだからです。同じテンプレート一覧はエディタの `/template` ピッカーの中にも現れるので、新しいノートを作らずに、開いているノートへ挿入することもできます — [ノートとエディタ](/ja/notes-and-editor#templates)を参照してください。 ### 検索と AI チャットでの共有エントリ {#shared-entries-in-search-and-ai-chat} diff --git a/manual/notes-and-editor.md b/manual/notes-and-editor.md index 88b73db73..ffb8eec38 100644 --- a/manual/notes-and-editor.md +++ b/manual/notes-and-editor.md @@ -220,6 +220,8 @@ The modal is searchable and lists each template's **Source** (**Official** or ** ![The Insert Template modal with the Plan and Run templates](/screenshots/template-picker.png) +If your team has a shared folder set up, pages your teammates have shared as templates appear as more rows in the same table, after the built-in ones, marked with a **Team** badge in the Source column — see [Storage & sync](/storage-and-sync#sharing-notes-with-your-team). + ## Working with tables {#tables} Everything below applies to any table in a note, whatever it started as. diff --git a/manual/storage-and-sync.md b/manual/storage-and-sync.md index 7be14451f..df681f4da 100644 --- a/manual/storage-and-sync.md +++ b/manual/storage-and-sync.md @@ -93,8 +93,16 @@ Forking one brings it into your knowledge rather than your notes, so it sits wit Select notes with the checkboxes in the notes list, or knowledge pages in a Knowledge list, and a **Share** button with the count on it joins the other bulk actions. Sharing runs one item at a time and ends with a summary: how many were newly shared, how many updated entries you had shared before, and which ones failed and why. One failure does not stop the rest, and you can cancel partway — whatever was already shared stays shared. +The same multi-select bar exists in the list view of the material gallery, so you can share several images or files at once instead of one at a time from each item's menu. + The button appears in the desktop app once a shared folder and an identity are set. What travels is the saved version of each item, so save the note you are editing before you share it. +### Share a page as a template + +A note's `⋯` menu also has **Share as template**, next to **Share with team**. Unlike a regular share, this hands out the page as a reusable starting point instead of a record: give it a name (defaults to the note's title) and, optionally, a description. It shares the page exactly as it stands — Graphium does not strip results or filled-in values for you, so clear anything you do not want to hand out before sharing. + +Each share of a page as a template creates a brand-new entry rather than updating a previous one, since a template is an independent handout, not a snapshot tied back to the note it came from. + ### Browse and fork Once a shared folder is configured, a **Library** section with a **Shared** entry appears in the sidebar. It lists what everyone on the team has shared — notes, knowledge pages, references, and data files — on a tab each. Entries from others are read-only — press **Fork** to copy one into your own storage, where you can edit it freely. Any media it carries is materialized into your local library automatically, and the fork records where it came from. @@ -107,7 +115,9 @@ A shared note also carries the folder it was in when it was shared, so the **Not The **Assets** tab also has a Folder column, and lists every image or file embedded in a shared note alongside items shared directly as a material. A note's images and files are not entries you can fork or verify on their own — you can **open the parent note** or **add the file to your own materials**, and if the same file appears in several shared notes it is shown once with a count of how many notes carry it. -Two more tabs mirror your own left-hand navigation: **Labels** and **Processes**. They show labels and PROV-DM procedures found in shared notes, extracted the same way your own note list and process list are. This piggybacks on the background read that also powers shared search and AI chat (see below), so a shared note whose content Graphium has not read yet will not contribute to these tabs until it does. Forking a process into your own notes works the same way it does for your own procedures. +Two more tabs mirror your own left-hand navigation: **Labels** and **Processes**. They show labels and PROV-DM procedures found in shared notes, extracted the same way your own note list and process list are. This piggybacks on the background read that also powers shared search and AI chat (see below), so a shared note whose content Graphium has not read yet will not contribute to these tabs until it does. Forking a process into your own notes works the same way it does for your own procedures. There is no separate action to share a label or a process — sharing the note that contains them is enough, and both tabs explain this with a link back to your own note list. + +The **Templates** tab lists pages shared as templates (see above), with a **Description** column in place of the folder column you see on the Notes tab. There is no **Fork** here — a template is a blank starting point, not a record to copy — but its detail panel has **New note from template**, available on any entry including your own. It reads the template, rebuilds its blocks and labels into a new note, materializes any embedded media into your own library, and opens the result. The new note records which template it came from, separately from a fork's "derived from" — a template does not hand you any facts, only a shape to start from. The same template list also appears inside the editor's `/template` picker, so you can insert a shared template into a note you already have open instead of starting a new one — see [Notes & the editor](/notes-and-editor#templates). ### Shared entries in search and AI chat {#shared-entries-in-search-and-ai-chat} diff --git a/src/features/asset-browser/AssetGalleryView.tsx b/src/features/asset-browser/AssetGalleryView.tsx index 3cd3b7d61..bceb44827 100644 --- a/src/features/asset-browser/AssetGalleryView.tsx +++ b/src/features/asset-browser/AssetGalleryView.tsx @@ -2,7 +2,7 @@ // メディアタイプ別にサムネイル一覧を表示、ノート紐付き・削除に対応 import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; -import { Image, Video, Volume2, FileText, Table, Paperclip, Play, Link, ExternalLink, Plus, LayoutGrid, List as ListIcon, Bot, MoreHorizontal, Download, Images, Loader2, ScanText, Folder } from "lucide-react"; +import { Image, Video, Volume2, FileText, Table, Paperclip, Play, Link, ExternalLink, Plus, LayoutGrid, List as ListIcon, Bot, MoreHorizontal, Download, Images, Loader2, ScanText, Folder, Share2 } from "lucide-react"; import { UNFILED_PATH } from "../note-context/folder-tree-model"; import { aggregateNoteContexts, noteContextHue, addNoteContext, removeNoteContext } from "../note-context/context-tags"; import { ContextTagPicker } from "../note-context/ContextTagPicker"; @@ -477,6 +477,12 @@ export type AssetGalleryViewProps = { * コールバック(Phase 2b-media)。親側で saveMediaIndex 経由で永続化する。 */ onSharedRefUpdated?: (entry: MediaIndexEntry, sharedRef: import("./media-index").MediaSharedRef) => Promise | void; + /** + * 選択した素材をまとめてチームに共有する(ノート一覧の一括共有と同じ動線)。 + * デスクトップ・共有ルート・名前が揃っているときだけ親から渡される想定で、 + * 渡されなければ一括バーにボタンを出さない(単体メニュー側の無効化理由表示と役割分担)。 + */ + onBulkShare?: (fileIds: string[]) => void; /** * PDF アセットの各ページを画像化して画像アセットに登録するアクション。 * 親側で pdf-image-extractor + handleUploadMedia を組み立てて渡す。 @@ -596,6 +602,7 @@ export function AssetGalleryView({ renderNotePeek, resolveKnowledgeWikiId, onSharedRefUpdated, + onBulkShare, onExtractPdfPages, onExtractDocxImages, getKnowledgeKind, @@ -1456,6 +1463,26 @@ export function AssetGalleryView({ {t("asset.deselectAll")}
+ {/* 一括共有は「ノート一覧と同じ導線」(並び順・文言・BulkShareModal)を約束している操作。 + 見た目だけは同じバーの隣のボタン(塗りつぶし)に合わせる — 1 つだけ枠線にすると + このバーの中で浮いて見えるため。導線の一貫性は文言と挙動で担保する */} + {onBulkShare && ( + + )} {onSetMediaContexts && (