Merge branch 'master' into feat/alias-redesign

This commit is contained in:
megayu
2026-05-15 18:34:02 +08:00
committed by GitHub
9 changed files with 258 additions and 134 deletions

View File

@@ -1,6 +1,6 @@
---
name: logseq-task-on-lambda
description: Use when Codex needs to execute a task described by a Logseq block in the Lambda RTC graph. The request must provide a block UUID directly or as a double-bracket UUID reference. This skill validates sync state, fetches the target block tree with the Logseq CLI, and then completes the task described by that block.
description: Use when Codex needs to execute or continue a task described by a Logseq block in the Lambda RTC graph. The request must provide a block UUID directly or as a double-bracket UUID reference. This skill validates sync state, fetches the target block tree with the Logseq CLI, handles in-review tasks with TODO #agent-steer guidance blocks, and then completes the task described by the active block tree.
---
# Logseq Task On Lambda
@@ -39,14 +39,23 @@ Run `.agents/skills/logseq-task-on-lambda/scripts/fetch-task-block.sh UUID_OR_DO
- Do not fetch block content before this script succeeds.
- Treat the script stdout root block and children as the complete task description.
3. Mark the task as DOING.
3. Choose the active task brief.
- If the fetched root task is not `in-review`, treat the fetched root block and its children as the active task brief.
- If the fetched root task is already `in-review`, continue the previous work from its review guidance instead of redoing the whole fetched root task.
- For an `in-review` root task, primarily focus on child block-trees whose root block has both the `#agent-steer` tag and `TODO` status.
- Treat each matching `#agent-steer` TODO block-tree as the next guidance prompt for continuing the previous work.
- Preserve the UUID of every matching `#agent-steer` TODO block so it can be marked `done` after the guidance has been handled.
- If the fetched text does not expose the needed `#agent-steer` block UUIDs or task statuses, re-run the sync gate and use the smallest structured `logseq` read needed to identify only those blocks.
- Stop if an `in-review` task has no actionable `#agent-steer` TODO block-tree, or if the matching blocks cannot be identified unambiguously.
4. Mark the root task as DOING.
- Immediately after the fetch script succeeds and before doing the described work, update the fetched root block status to `doing`.
- Use the normalized UUID from the fetch script stderr: `logseq upsert task --graph "Lambda RTC" --uuid "$normalized_uuid" --status doing`.
- Follow `logseq-cli` write rules and re-run the sync gate immediately before writing to `Lambda RTC`.
- Stop if the root block cannot be updated as a task.
4. Record reproducibility for bug and regression tasks.
- If the fetched task block or its children clearly identify the task as a bug or regression, update the fetched root block's `Reproducible?` property with one exact choice: `Not sure`, `Yes`, or `No`.
5. Record reproducibility for bug and regression tasks.
- If the active task brief clearly identifies the task as a bug or regression, update the fetched root block's `Reproducible?` property with one exact choice: `Not sure`, `Yes`, or `No`.
- Use `Yes` if the issue was reproduced, `No` if reproduction was actively attempted and failed, and `Not sure` if reproduction was not attempted or the evidence is insufficient.
- If the value is not known before doing the described work, set `Reproducible?` to `Not sure` first; if later evidence changes the value, update it before adding the completion summary.
- Use the exact property name string key and a string choice value: `logseq upsert block --graph "Lambda RTC" --uuid "$normalized_uuid" --update-properties "{\"Reproducible?\" \"$reproducible_value\"}"`.
@@ -54,16 +63,24 @@ Run `.agents/skills/logseq-task-on-lambda/scripts/fetch-task-block.sh UUID_OR_DO
- Do not update `Reproducible?` for idea or enhancement tasks.
- Stop if a clear bug or regression task cannot be updated with one of the exact `Reproducible?` choices.
5. Complete the described task.
- Follow the fetched block tree, not assumptions from the UUID or graph name.
- If the block tree is ambiguous or not actionable, stop with a concise error instead of guessing.
6. Complete the described task.
- Follow the active task brief, not assumptions from the UUID or graph name.
- If the active task brief is ambiguous or not actionable, stop with a concise error instead of guessing.
- If the task requires code edits, follow repo `AGENTS.md` files and load any matching repo-local skills before editing.
- If the task requires Logseq graph writes, follow `logseq-cli` write rules and re-run the sync gate immediately before writes to `Lambda RTC`.
6. Optionally create a pull request.
7. Generate the PR title and git branch name before optional PR creation.
- Do this only when the active task brief or the user's current request explicitly asks for a pull request.
- Generate both values after completing the described task and before staging, committing, pushing, or opening a PR.
- Follow the repo `AGENTS.md` PR title format: `feat|enhance|fix(<module>): <short description>`.
- Choose the PR type from the task outcome: use `fix` for bug or regression tasks, `enhance` for improvements to existing behavior, and `feat` for new behavior.
- Generate a concise, lowercase, hyphenated git branch name with the `codex/` prefix unless the user explicitly asks for a different branch prefix.
- Reuse the generated PR title and branch name in the GitHub publishing workflow.
8. Optionally create a pull request.
- Default behavior is to not create a PR.
- Create a PR only when the fetched task block or the user's current request explicitly asks for one.
- For bug or regression tasks with an existing GitHub issue URL in the fetched task block, its properties, or its children, preserve that issue URL before overwriting any task property with the PR URL.
- Create a PR only when the active task brief or the user's current request explicitly asks for one.
- For bug or regression tasks with an existing GitHub issue URL in the active task brief, the fetched root block properties, or their children, preserve that issue URL before overwriting any task property with the PR URL.
- For those bug or regression PRs, make the commit message mention the linked issue with `fix $github_issue_url`, for example `fix https://github.com/logseq/db-test/issues/1`.
- Use the linked GitHub issue URL, not the newly created PR URL, in the `fix ...` line.
- When creating a PR, follow the loaded GitHub publishing workflow for branch, staging, commit, push, and PR creation.
@@ -75,30 +92,36 @@ Run `.agents/skills/logseq-task-on-lambda/scripts/fetch-task-block.sh UUID_OR_DO
- Include the PR URL in the final report.
- Stop if PR creation is explicitly requested but cannot be completed safely.
7. Add a completion summary under the task block.
- When the described work is finished, create one concise child block under the fetched root block before changing the final task status.
- Use the normalized UUID as the parent target: `logseq upsert block --graph "Lambda RTC" --target-uuid "$normalized_uuid" --content "Summary: ..."`.
- Include the outcome and verification performed. Keep the summary factual and scoped to this task.
- For tasks completed with `.agents/skills/logseq-answer-machine/SKILL.md`, write a specific answer summary as a block tree instead of a single flat block:
- Create a `Summary:` child block under the fetched root block.
- Add nested child blocks under `Summary:` for the useful sections from the answer, such as `Short Answer`, `Evidence`, `How It Works`, `Runtime Verification`, `Edge Cases and Open Questions`, and `Practical Takeaways`.
- Keep the block tree proportional to the question, but include concrete file paths, namespaces, command names, runtime surfaces, observed behavior, and verification results when they support the answer.
- Do not write vague summaries such as only "Answered the question" or "Investigated the codebase".
- Format code and technical terms with Markdown where appropriate, including backticks for code symbols, namespaces, file paths, commands, properties, keywords, and literal values.
- Use additional `logseq upsert block --graph "Lambda RTC" --target-uuid "$summary_or_section_uuid" --content "$child_content"` calls to create the nested summary blocks.
9. Mark handled `#agent-steer` guidance blocks as done.
- Do this only for an originally `in-review` root task with matching `#agent-steer` TODO block-trees selected in step 3.
- After completing the guidance and before adding the completion summary, update every handled `#agent-steer` block's status to `done`.
- Use each preserved `#agent-steer` block UUID: `logseq upsert task --graph "Lambda RTC" --uuid "$agent_steer_uuid" --status done`.
- Follow `logseq-cli` write rules and re-run the sync gate immediately before writing to `Lambda RTC`.
- Stop if any handled `#agent-steer` block cannot be updated as a task.
10. Add a completion summary under the task block.
- When the described work is finished, create a `Summary:` child block under the fetched root block before changing the final task status.
- Use the normalized UUID as the parent target for the top-level summary block: `logseq upsert block --graph "Lambda RTC" --target-uuid "$normalized_uuid" --content "Summary:"`.
- Write the summary as an outline-style block tree with nested child blocks, not as a long single block.
- Add concise nested sections under `Summary:` for the useful parts of the outcome, such as `Outcome`, `Changes`, `Verification`, `PR`, `Evidence`, `How It Works`, `Edge Cases and Open Questions`, or `Practical Takeaways`.
- Keep the block tree proportional to the task; include concrete file paths, namespaces, command names, runtime surfaces, observed behavior, and verification results when they support the summary.
- Do not write vague summaries such as only "Completed the task", "Answered the question", or "Investigated the codebase".
- Format code and technical terms with Markdown where appropriate, including backticks for code symbols, namespaces, file paths, commands, properties, keywords, and literal values.
- Use additional `logseq upsert block --graph "Lambda RTC" --target-uuid "$summary_or_section_uuid" --content "$child_content"` calls to create the nested summary blocks.
- Do not include the PR URL in the summary child block.
- Stop if the summary block cannot be created.
8. Mark the task as in review.
11. Mark the root task as in review.
- After the summary child block is created, update the fetched root block status to `in-review`.
- Use the normalized UUID from the fetch script stderr: `logseq upsert task --graph "Lambda RTC" --uuid "$normalized_uuid" --status in-review`.
- Follow `logseq-cli` write rules and re-run the sync gate immediately before writing to `Lambda RTC`.
- Stop if the root block cannot be updated as a task.
9. Report the result.
12. Report the result.
- Mention the normalized UUID.
- State that the sync gate passed, including `ws-state`, `pending-local`, and `pending-server`.
- State that the task status was moved to `doing`, a completion summary was added, and the task status was moved to `in-review`.
- If the task started in `in-review`, state which `#agent-steer` TODO block UUIDs were used as guidance and moved to `done`.
- For `logseq-answer-machine` tasks, state that the completion summary was written as a specific Markdown block tree.
- State which `Reproducible?` choice was recorded for a bug or regression task, or that it was skipped because the task was not a bug or regression.
- If a PR was explicitly requested and created, include the PR URL and state that the `GitHub Url` property was updated. For bug or regression PRs with a linked GitHub issue, state that the commit message mentioned `fix $github_issue_url`. If no PR was requested, do not create one.
@@ -110,7 +133,9 @@ Run `.agents/skills/logseq-task-on-lambda/scripts/fetch-task-block.sh UUID_OR_DO
- Never fetch block content before the fetch script reports a passed sync gate.
- Never silently substitute another graph, block, page, db id, or query result.
- Never mask invalid sync state with defaults.
- Never create a pull request unless the fetched task block or the user's current request explicitly asks for one.
- Never redo a whole `in-review` root task when actionable `#agent-steer` TODO guidance exists; continue from the `#agent-steer` block-tree.
- Never complete an `in-review` continuation without marking every handled `#agent-steer` TODO block as `done`.
- Never create a pull request unless the active task brief or the user's current request explicitly asks for one.
- Never leave a created PR unrecorded on the fetched root block's `GitHub Url` property.
- Never overwrite a bug or regression task's linked GitHub issue URL before preserving it for the commit message.
- Never create a bug or regression PR for a task with a linked GitHub issue URL unless the commit message mentions `fix $github_issue_url`.
@@ -118,5 +143,5 @@ Run `.agents/skills/logseq-task-on-lambda/scripts/fetch-task-block.sh UUID_OR_DO
- Never use boolean values for `Reproducible?`; it is a default property with exact choices `Not sure`, `Yes`, and `No`.
- Never skip recording one exact `Reproducible?` choice for a fetched task that is clearly a bug or regression.
- Never skip the `doing` status write, completion summary child block, or final `in-review` status write when the described task completes successfully.
- Never flatten a `logseq-answer-machine` completion summary into a vague single block; write the specific answer summary as a Markdown block tree.
- Never flatten the completion summary into a vague single block; write the specific summary as a Markdown outline block tree.
- Stop on the first command error other than a sync status showing unopened sync, invalid JSON result, missing block, sync timeout, non-idle sync state, or non-actionable task brief.

View File

@@ -562,7 +562,13 @@
{:on-click (fn [e]
(util/stop e)
(let [repo-dir (config/get-repo-dir repo)
file-fpath (path/path-join repo-dir (str "assets/" (:block/uuid asset-block) "." (name ext)))]
ext-url (:logseq.property.asset/external-url asset-block)
local-ext-url? (and (not (string/blank? ext-url))
(common-config/local-relative-asset? ext-url))
file-fpath (if local-ext-url?
;; Plugin-sourced asset stored under assets/storages/<plugin-id>/...
(path/path-join repo-dir (string/replace ext-url #"^[./]+" ""))
(path/path-join repo-dir (str "assets/" (:block/uuid asset-block) "." (name ext))))]
(js/window.apis.openPath file-fpath)))}
file-name])
@@ -1113,13 +1119,19 @@
img-placeholder (when image?
[:div.img-placeholder.asset-container
{:style img-metadata}])
;; When external-url is set, use it as the render path so
;; plugin-sandboxed assets (./assets/storages/<plugin-id>/...)
;; resolve correctly; <make-asset-url handles both remote URLs
;; and graph-root-relative paths.
href (or (:logseq.property.asset/external-url block)
(path/path-join (str "../" common-config/local-assets-dir) file))
content (cond
file-ready?
(asset-link (assoc config
:asset-block block
:image-placeholder img-placeholder)
(:block/title block)
(path/path-join (str "../" common-config/local-assets-dir) file)
href
img-metadata
nil)
image?

View File

@@ -715,8 +715,11 @@
(defn- node-matches-scoped-classes?
[class-ids node]
(let [node (or (:value node) node)]
(some #(contains? class-ids (if (integer? %) % (:db/id %))) (:block/tags node))))
(let [node-value (or (:value node) node)
node' (if (and (:db/id node-value) (nil? (:block/tags node-value)))
(or (db/entity (:db/id node-value)) node-value)
node-value)]
(some #(contains? class-ids (if (integer? %) % (:db/id %))) (:block/tags node'))))
(defn- scoped-class-nodes
[repo property classes result]
@@ -731,6 +734,15 @@
classes)
distinct))))
(defn- <load-initial-node-choices
[repo property non-root-classes]
(if (seq non-root-classes)
(if (broad-scoped-node-property? property non-root-classes)
(db-async/<get-property-values (:db/ident property))
(p/let [result (p/all (map (fn [class] (db-async/<get-tag-objects repo (:db/id class))) non-root-classes))]
(distinct (apply concat result))))
(db-async/<get-property-values (:db/ident property))))
(rum/defc ^:large-vars/cleanup-todo select-node < rum/static
[property
{:keys [block multiple-choices? dropdown? input-opts on-input add-new-choice! target] :as opts}
@@ -927,8 +939,14 @@
(rum/defc property-value-select-node < rum/static
[block property opts
{:keys [*show-new-property-config?]}]
(let [[initial-choices set-initial-choices!] (hooks/use-state nil)
(let [[initial-choices set-initial-choices-state!] (hooks/use-state nil)
[result set-result!] (hooks/use-state nil)
*initial-choices (rum/use-ref nil)
current-initial-choices (fn []
(or (rum/deref *initial-choices) initial-choices))
set-initial-choices! (fn [value]
(rum/set-ref! *initial-choices value)
(set-initial-choices-state! value))
set-result-and-initial-choices! (fn [value]
(set-initial-choices! value)
(set-result! value))
@@ -947,13 +965,13 @@
:input-opts input-opts
:on-input (fn [v]
(if (string/blank? v)
(set-result! initial-choices)
(set-result! (current-initial-choices))
;; TODO rank initial choices higher
(p/let [result (search/block-search (state/get-current-repo) v {:enable-snippet? false
:built-in? false})]
(set-result! result))))
:add-new-choice! (fn [new-choice]
(set-initial-choices! (add-initial-node-choice initial-choices new-choice))))
(set-initial-choices! (add-initial-node-choice (current-initial-choices) new-choice))))
repo (state/get-current-repo)
classes (:logseq.property/classes property)
class? (= :class (:logseq.property/type property))
@@ -969,15 +987,8 @@
extends-property?
nil
(seq non-root-classes)
(if (broad-scoped-node-property? property non-root-classes)
(set-result-and-initial-choices! [])
(p/let [result (p/all (map (fn [class] (db-async/<get-tag-objects repo (:db/id class))) non-root-classes))
result' (distinct (apply concat result))]
(set-result-and-initial-choices! result')))
:else
(p/let [result (db-async/<get-property-values (:db/ident property))]
(p/let [result (<load-initial-node-choices repo property non-root-classes)]
(set-result-and-initial-choices! result))))
[])

View File

@@ -528,108 +528,109 @@
[:div.extensions__pdf-header
[:div.extensions__pdf-toolbar
[:div.inner
;; appearance
[:a.button
{:title (t :pdf/more-settings)
:on-click #(set-settings-visible! (not settings-visible?))}
(svg/adjustments 18)]
[:div.r.flex.buttons
;; appearance
[:a.button
{:title (t :pdf/more-settings)
:on-click #(set-settings-visible! (not settings-visible?))}
(svg/adjustments 18)]
;; selection
[:a.button
{:title (t :pdf/area-highlight-shortcut (if util/mac? "⌘" "Shift"))
:class (when area-mode? "is-active")
:on-click #(set-area-mode! (not area-mode?))}
(svg/icon-area 18)]
;; selection
[:a.button
{:title (t :pdf/area-highlight-shortcut (if util/mac? "⌘" "Shift"))
:class (when area-mode? "is-active")
:on-click #(set-area-mode! (not area-mode?))}
(svg/icon-area 18)]
[:a.button
{:title (t :pdf/highlight-mode)
:class (when highlight-mode? "is-active")
:on-click #(set-highlight-mode! (not highlight-mode?))}
(svg/highlighter 16)]
[:a.button
{:title (t :pdf/highlight-mode)
:class (when highlight-mode? "is-active")
:on-click #(set-highlight-mode! (not highlight-mode?))}
(svg/highlighter 16)]
;; zoom
[:a.button
{:title (t :pdf/zoom-out)
:on-click (fn []
(pdf-utils/zoom-out-viewer viewer)
(dispatch-extra-state!))}
(svg/zoom-out 18)]
;; zoom
[:a.button
{:title (t :pdf/zoom-out)
:on-click (fn []
(pdf-utils/zoom-out-viewer viewer)
(dispatch-extra-state!))}
(svg/zoom-out 18)]
[:a.button
{:title (t :pdf/zoom-in)
:on-click (fn []
(pdf-utils/zoom-in-viewer viewer)
(dispatch-extra-state!))}
(svg/zoom-in 18)]
[:a.button
{:title (t :pdf/zoom-in)
:on-click (fn []
(pdf-utils/zoom-in-viewer viewer)
(dispatch-extra-state!))}
(svg/zoom-in 18)]
[:a.button
{:title (t :pdf/auto-fit)
:on-click (fn []
(pdf-utils/reset-viewer-auto! viewer)
(dispatch-extra-state!))}
(svg/auto-fit 18)]
[:a.button
{:title (t :pdf/auto-fit)
:on-click (fn []
(pdf-utils/reset-viewer-auto! viewer)
(dispatch-extra-state!))}
(svg/auto-fit 18)]
[:a.button
{:title (t :pdf/outline)
:on-click #(set-outline-visible! (not outline-visible?))}
(svg/view-list 16)]
[:a.button
{:title (t :pdf/outline)
:on-click #(set-outline-visible! (not outline-visible?))}
(svg/view-list 16)]
;; search
[:a.button
{:title (t :pdf/search)
:on-click #(set-finder-visible! (not finder-visible?))}
(svg/search2 19)]
;; search
[:a.button
{:title (t :pdf/search)
:on-click #(set-finder-visible! (not finder-visible?))}
(svg/search2 19)]
;; annotations
[:a.button
{:title (t :pdf/annotations-page)
:on-click (fn []
(if asset-block
(pdf-assets/goto-annotations-page! (:pdf/current @state/state))
(state/pub-event! [:asset/dialog-edit-external-url nil pdf-current])))}
(svg/annotations 16)]
;; annotations
[:a.button
{:title (t :pdf/annotations-page)
:on-click (fn []
(if asset-block
(pdf-assets/goto-annotations-page! (:pdf/current @state/state))
(state/pub-event! [:asset/dialog-edit-external-url nil pdf-current])))}
(svg/annotations 16)]
;; system window
[:a.button
{:title (if in-system-window?
(t :pdf/open-in-app-window)
(t :pdf/open-in-external-window))
:on-click #(if in-system-window?
(pdf-windows/exit-pdf-in-system-window! true)
(on-external-window!))}
(ui/icon (if in-system-window?
"window-minimize"
"window-maximize"))]
;; system window
[:a.button
{:title (if in-system-window?
(t :pdf/open-in-app-window)
(t :pdf/open-in-external-window))
:on-click #(if in-system-window?
(pdf-windows/exit-pdf-in-system-window! true)
(on-external-window!))}
(ui/icon (if in-system-window?
"window-minimize"
"window-maximize"))]
;; pager
[:div.pager.flex.items-center.ml-1
;; pager
[:div.pager.flex.items-center.ml-1
[:span.nu.flex.items-center.opacity-70
[:input {:ref *page-ref
:type "number"
:min 1
:max total-page-num
:class (util/classnames [{:is-long (> (util/safe-parse-int current-page-num) 999)}])
:default-value current-page-num
:on-mouse-enter #(.select ^js (.-target %))
:on-key-up (fn [^js e]
(let [^js input (.-target e)
value (util/safe-parse-int (.-value input))]
(set-current-page-num! value)
(when (and (= (.-keyCode e) 13) value (> value 0))
(->> (if (> value total-page-num) total-page-num value)
(set! (. viewer -currentPageNumber))))))}]
[:small "/ " total-page-num]]
[:span.nu.flex.items-center.opacity-70
[:input {:ref *page-ref
:type "number"
:min 1
:max total-page-num
:class (util/classnames [{:is-long (> (util/safe-parse-int current-page-num) 999)}])
:default-value current-page-num
:on-mouse-enter #(.select ^js (.-target %))
:on-key-up (fn [^js e]
(let [^js input (.-target e)
value (util/safe-parse-int (.-value input))]
(set-current-page-num! value)
(when (and (= (.-keyCode e) 13) value (> value 0))
(->> (if (> value total-page-num) total-page-num value)
(set! (. viewer -currentPageNumber))))))}]
[:small "/ " total-page-num]]
[:span.ct.flex.items-center
[:a.button {:on-click #(. viewer previousPage)} (svg/up-narrow)]
[:a.button {:on-click #(. viewer nextPage)} (svg/down-narrow)]]]
[:span.ct.flex.items-center
[:a.button {:on-click #(. viewer previousPage)} (svg/up-narrow)]
[:a.button {:on-click #(. viewer nextPage)} (svg/down-narrow)]]]
[:a.button
{:on-click #(if in-system-window?
(pdf-windows/exit-pdf-in-system-window! false)
(state/set-current-pdf! nil))}
(t :ui/close)]]]
[:a.button
{:on-click #(if in-system-window?
(pdf-windows/exit-pdf-in-system-window! false)
(state/set-current-pdf! nil))}
(t :ui/close)]]]]
;; contents outline
(pdf-outline-&-highlights viewer outline-visible? set-outline-visible!)

View File

@@ -356,7 +356,10 @@
video? (contains? config/video-formats asset-type)
pdf? (= :pdf asset-type)
file-name (str (:block/uuid asset) "." asset-type-str)
rel-path (path/path-join (str "../" common-config/local-assets-dir) file-name)]
;; Prefer external-url so plugin-sandboxed assets resolve to their
;; real on-disk path; mirrors the asset-cp render-side fix.
rel-path (or (:logseq.property.asset/external-url asset)
(path/path-join (str "../" common-config/local-assets-dir) file-name))]
(cond
image?
(p/let [url (assets-handler/<make-asset-url rel-path)]

View File

@@ -1099,7 +1099,8 @@
:notification
(do
(log/error ::apply-outliner-ops-failed e)
(shared-service/broadcast-to-clients! :notification [(:message payload) (:type payload) (:clear? payload) (:uid payload) (:timeout payload)])
(shared-service/broadcast-to-clients! :notification [(:message payload) (:type payload) (:clear? payload) (:uid payload) (:timeout payload)
(select-keys payload [:i18n-key :i18n-args])])
;; re-throw as CLI needs to see notification
(throw e))
(throw e)))))))

View File

@@ -122,7 +122,7 @@
(defn- schema-type-check!
[type]
(let [valid-types #{:default :number :date :datetime :checkbox :url :node :json :string}]
(let [valid-types #{:default :number :date :datetime :checkbox :url :node :asset :json :string}]
(when-not (contains? valid-types type)
(throw (ex-info (str "Invalid type, type should be one of: " valid-types) {:type type})))))
@@ -151,7 +151,7 @@
(defn upsert-property
"schema:
{:type :default | :number | :date | :datetime | :checkbox | :url | :node | :json | :string
{:type :default | :number | :date | :datetime | :checkbox | :url | :node | :asset | :json | :string
:cardinality :many | :one
:hide? true
:view-context :page

View File

@@ -6,6 +6,7 @@
[electron.ipc :as ipc]
[frontend.config :as config]
[frontend.fs :as fs]
[frontend.handler.assets :as assets-handler]
[frontend.handler.command-palette :as palette-handler]
[frontend.handler.common.plugin :as plugin-common-handler]
[frontend.handler.plugin :as plugin-handler]
@@ -88,6 +89,18 @@
[plugin-id]
(util/node-path.join "storages" (util/node-path.basename plugin-id)))
(defn- binary-content?
"Detect payload shapes that can't be transit-serialized through ipc/ipc.
Mirrors assets-handler/->uint8 — anything it accepts as binary, we route
through writeFileBytes instead of write-plain-text-file!."
[content]
(or (instance? js/ArrayBuffer content)
(instance? js/Uint8Array content)
(and (exists? js/ArrayBuffer) (.isView js/ArrayBuffer content))
(and (object? content)
(= "Buffer" (aget content "type"))
(array? (aget content "data")))))
(defn- write_rootdir_file
[file content sub-root root-dir]
(p/let [repo ""
@@ -98,7 +111,13 @@
user-path-root (util/node-path.dirname user-path)
exist? (fs/file-exists? user-path-root "")
_ (when-not exist? (fs/mkdir-recur! user-path-root))
_ (fs/write-plain-text-file! repo nil user-path content {:skip-compare? true})]
;; Binary content (ArrayBuffer/Uint8Array/...) can't survive
;; transit serialization through ipc/ipc inside write-plain-text-file!,
;; so on Electron we bypass to window.apis.writeFileBytes — same path
;; the native fs/write-asset-file! uses for the same reason.
_ (if (and (binary-content? content) (util/electron?))
(js/window.apis.writeFileBytes user-path (assets-handler/->uint8 content))
(fs/write-plain-text-file! repo nil user-path content {:skip-compare? true}))]
user-path))
(defn write_dotdir_file

View File

@@ -1,6 +1,8 @@
(ns frontend.components.property.value-test
(:require [cljs.test :refer [async deftest is]]
[frontend.components.property.value :as property-value]
[frontend.db :as db]
[frontend.db.async :as db-async]
[frontend.db.model :as model]
[promesa.core :as p]))
@@ -214,3 +216,53 @@
(is (= choices
(#'property-value/scoped-class-nodes
"repo" property [tag-class] nil))))))
(deftest scoped-class-nodes-keeps-hydrated-broad-scope-initial-choices-test
(let [property {:logseq.property/type :node}
page-class {:db/id 1
:db/ident :logseq.class/Page}
matching-choice {:value {:db/id 100
:block/uuid #uuid "11111111-1111-1111-1111-111111111111"}
:label "Existing page"}
unrelated-choice {:value {:db/id 101
:block/uuid #uuid "22222222-2222-2222-2222-222222222222"}
:label "Unrelated block"}]
(with-redefs [db/entity (fn [id]
(case id
100 {:db/id 100
:block/title "Existing page"
:block/tags [1]}
101 {:db/id 101
:block/title "Unrelated block"}
nil))
model/get-structured-children (fn [_repo _class-id] [])]
(is (= [matching-choice]
(#'property-value/scoped-class-nodes
"repo" property [page-class] [matching-choice unrelated-choice]))))))
(deftest load-initial-node-choices-loads-existing-values-for-broad-page-scope-test
(async done
(let [property {:db/ident :user.property/p1
:logseq.property/type :node
:logseq.property/classes [{:db/id 1
:db/ident :logseq.class/Page}]}
existing-values [{:value {:db/id 100
:block/uuid #uuid "11111111-1111-1111-1111-111111111111"}
:label "page 1"}
{:value {:db/id 101
:block/uuid #uuid "22222222-2222-2222-2222-222222222222"}
:label "page 2"}]
queried-properties* (atom [])]
(with-redefs [db-async/<get-property-values (fn [property-ident]
(swap! queried-properties* conj property-ident)
(p/resolved existing-values))
db-async/<get-tag-objects (fn [_repo _class-id]
(p/resolved []))]
(-> (#'property-value/<load-initial-node-choices "repo" property (:logseq.property/classes property))
(p/then (fn [result]
(is (= [:user.property/p1] @queried-properties*))
(is (= existing-values result))
(done)))
(p/catch (fn [error]
(is false (str error))
(done))))))))