mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
chore: update version to 0.2.11 and add developer notes for Logseq DB properties
This commit is contained in:
293
libs/development-notes/db_properties_notes.md
Normal file
293
libs/development-notes/db_properties_notes.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# Logseq DB Properties (Developer Notes)
|
||||
|
||||
This note explains how **properties** work in Logseq when using a **DB graph** (database mode), how that differs from a **file graph** (Markdown/Org), and how to use the **plugin SDK** APIs defined in `libs/src/LSPlugin.ts`.
|
||||
|
||||
> Scope: This is written for plugin developers. It focuses on `logseq.Editor.*` property APIs and tag/class modeling.
|
||||
|
||||
---
|
||||
|
||||
## 1) Two worlds: File graph vs DB graph
|
||||
|
||||
### File graph (Markdown / Org)
|
||||
- “Properties” are primarily **text syntax** stored in the file:
|
||||
- Markdown frontmatter (`---`)
|
||||
- Org property drawers (`:PROPERTIES:`)
|
||||
- Inline props (`key:: value`)
|
||||
- When you update a property, you’re often ultimately **editing text**.
|
||||
- SDK calls like `Editor.insertBlock`/`updateBlock` have `opts.properties`, but note this warning in the typings:
|
||||
- `IBatchBlock.properties` is **not supported for DB graph**.
|
||||
|
||||
### DB graph (Database mode)
|
||||
DB mode treats properties as **first-class DB entities**.
|
||||
|
||||
Think of it as a 2-layer model:
|
||||
|
||||
1) **Property definitions (schema)**
|
||||
- A property key (e.g. `rating`, `authors`, `zotero_key`) exists as an entity.
|
||||
- It may have a schema: type, cardinality, visibility.
|
||||
|
||||
2) **Property values (data)**
|
||||
- Blocks/pages can store values for property keys.
|
||||
- Persisted structurally in the DB (not by emitting `key:: value` text).
|
||||
|
||||
In DB graphs, prefer the dedicated APIs:
|
||||
- Property schema: `Editor.getProperty / upsertProperty / removeProperty`
|
||||
- Values on blocks: `Editor.upsertBlockProperty / removeBlockProperty / getBlockProperties`
|
||||
|
||||
---
|
||||
|
||||
## 2) Important types (from `LSPlugin.ts`)
|
||||
|
||||
### `PropertySchema`
|
||||
```ts
|
||||
export type PropertySchema = {
|
||||
type: 'default' | 'number' | 'node' | 'date' | 'checkbox' | 'url' | string,
|
||||
cardinality: 'many' | 'one',
|
||||
hide: boolean
|
||||
public: boolean
|
||||
}
|
||||
```
|
||||
|
||||
Practical meaning:
|
||||
- `type`
|
||||
- Controls editor/UI behavior and (in DB graphs) how values are interpreted.
|
||||
- Common: `default`, `number`, `date`, `checkbox`, `url`, `node`.
|
||||
- `cardinality`
|
||||
- `'one'`: a single value
|
||||
- `'many'`: multiple values (typically passed as an array)
|
||||
- `hide`
|
||||
- Hide in UI property panels.
|
||||
- `public`
|
||||
- Expose property in UI (and typically configure discoverability).
|
||||
|
||||
### Entities you’ll see
|
||||
- `BlockEntity`: blocks, but also used for some “special blocks” such as property entities.
|
||||
- `PageEntity`: pages, tags (classes), property pages (`type: 'property'`), etc.
|
||||
- `BlockIdentity`: a block uuid string OR `{ uuid }`.
|
||||
|
||||
---
|
||||
|
||||
## 3) DB-only vs graph-agnostic checks
|
||||
|
||||
Before doing DB-only work, check graph type:
|
||||
|
||||
```ts
|
||||
const isDbGraph = await logseq.App.checkCurrentIsDbGraph()
|
||||
if (!isDbGraph) {
|
||||
await logseq.UI.showMsg('This feature requires a DB graph.', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
// Also check app-level capability:
|
||||
const { supportDb } = await logseq.App.getInfo()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4) Property schema APIs (DB only)
|
||||
|
||||
### Get a property definition
|
||||
```ts
|
||||
const propEntity = await logseq.Editor.getProperty('zotero_key')
|
||||
// -> BlockEntity | null
|
||||
```
|
||||
|
||||
### Create or update a property definition (idempotent)
|
||||
Use `upsertProperty` to ensure the property exists and has the schema you expect.
|
||||
|
||||
```ts
|
||||
await logseq.Editor.upsertProperty(
|
||||
'zotero_key',
|
||||
{
|
||||
type: 'default',
|
||||
cardinality: 'one',
|
||||
hide: false,
|
||||
public: true,
|
||||
},
|
||||
{ name: 'Zotero Key' }
|
||||
)
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `key` is your stable identifier (recommend `snake_case`).
|
||||
- `opts.name` can be used as a **display name** for users.
|
||||
|
||||
### Remove a property definition
|
||||
```ts
|
||||
await logseq.Editor.removeProperty('zotero_key')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Block/page property value APIs
|
||||
|
||||
### Set (upsert) a property value on a block
|
||||
```ts
|
||||
const block = await logseq.Editor.getCurrentBlock()
|
||||
if (!block) return
|
||||
|
||||
await logseq.Editor.upsertBlockProperty(block.uuid, 'zotero_key', 'ABCD1234')
|
||||
```
|
||||
|
||||
### Set multi-value properties (`cardinality: 'many'`)
|
||||
For `many`, pass an array. Use `{ reset: true }` when you want to overwrite vs merge.
|
||||
|
||||
```ts
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
block.uuid,
|
||||
'authors',
|
||||
['Ada Lovelace', 'Alan Turing'],
|
||||
{ reset: true }
|
||||
)
|
||||
```
|
||||
|
||||
### Remove a value
|
||||
```ts
|
||||
await logseq.Editor.removeBlockProperty(block.uuid, 'zotero_key')
|
||||
```
|
||||
|
||||
### Read properties
|
||||
```ts
|
||||
const props = await logseq.Editor.getBlockProperties(block.uuid)
|
||||
// -> Record<string, any> | null
|
||||
|
||||
const pageProps = await logseq.Editor.getPageProperties('My Page')
|
||||
```
|
||||
|
||||
### Read a single property (returns a `BlockEntity | null`)
|
||||
`getBlockProperty` is useful when you want the DB entity wrapper for a key.
|
||||
|
||||
```ts
|
||||
const v = await logseq.Editor.getBlockProperty(block.uuid, 'zotero_key')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6) Tags as “classes” + attaching properties (DB modeling)
|
||||
|
||||
In DB graphs, tags can behave like **classes**.
|
||||
|
||||
Relevant APIs:
|
||||
- `Editor.createTag(tagName, opts)`
|
||||
- `Editor.addTagProperty(tagId, propertyIdOrName)` / `removeTagProperty`
|
||||
- `Editor.addTagExtends(tagId, parentTagIdOrName)` / `removeTagExtends`
|
||||
- `Editor.addBlockTag(blockId, tagId)` / `removeBlockTag`
|
||||
|
||||
### Create a tag with tagProperties
|
||||
This is the most developer-friendly way to ship a “schema bundle”:
|
||||
|
||||
```ts
|
||||
const tag = await logseq.Editor.createTag('ZoteroItem', {
|
||||
tagProperties: [
|
||||
{
|
||||
name: 'zotero_key',
|
||||
schema: { type: 'default', cardinality: 'one', public: true, hide: false },
|
||||
},
|
||||
{
|
||||
name: 'authors',
|
||||
schema: { type: 'node', cardinality: 'many', public: true, hide: false },
|
||||
},
|
||||
{
|
||||
name: 'published_at',
|
||||
schema: { type: 'date', cardinality: 'one', public: true, hide: false },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (tag) {
|
||||
const block = await logseq.Editor.getCurrentBlock()
|
||||
if (block) await logseq.Editor.addBlockTag(block.uuid, tag.uuid)
|
||||
}
|
||||
```
|
||||
|
||||
Why this pattern is popular:
|
||||
- Users see a consistent “record type” (`ZoteroItem`) with fields.
|
||||
- Plugins can be idempotent: create if missing, otherwise just reuse.
|
||||
|
||||
### Add properties to an existing tag later
|
||||
```ts
|
||||
const tag = await logseq.Editor.getTag('ZoteroItem')
|
||||
if (tag) {
|
||||
await logseq.Editor.addTagProperty(tag.uuid, 'zotero_key')
|
||||
await logseq.Editor.addTagProperty(tag.uuid, 'authors')
|
||||
}
|
||||
```
|
||||
|
||||
### Tag inheritance
|
||||
```ts
|
||||
await logseq.Editor.addTagExtends('ZoteroItem', 'Reference')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7) Recommended conventions for plugin authors
|
||||
|
||||
### Prefer DB APIs over text editing in DB graphs
|
||||
- Don’t generate `key:: value` strings as your “source of truth” in DB mode.
|
||||
- Use `upsertProperty` + `upsertBlockProperty`.
|
||||
|
||||
### Keep keys stable; use display names for UX
|
||||
- Use safe keys (`snake_case`, ASCII) for `key`.
|
||||
- Use `upsertProperty(key, schema, { name })` to show a nice label.
|
||||
|
||||
### Be explicit about cardinality
|
||||
- If you intend a list, set `cardinality: 'many'` and always write arrays.
|
||||
- Use `{ reset: true }` when you want to overwrite.
|
||||
|
||||
### Guard DB-only APIs
|
||||
- Always check `App.checkCurrentIsDbGraph()`.
|
||||
|
||||
---
|
||||
|
||||
## 8) A tiny helper wrapper (optional)
|
||||
|
||||
If you’re building a small internal “DB properties SDK” for your plugin, a minimal shape looks like:
|
||||
|
||||
```ts
|
||||
export async function ensureDbGraph() {
|
||||
if (!(await logseq.App.checkCurrentIsDbGraph())) {
|
||||
throw new Error('DB graph required')
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureProperty(
|
||||
key: string,
|
||||
schema: Partial<import('../src/LSPlugin').PropertySchema>,
|
||||
name?: string
|
||||
) {
|
||||
return logseq.Editor.upsertProperty(key, schema, name ? { name } : undefined)
|
||||
}
|
||||
|
||||
export async function setProp(
|
||||
block: string,
|
||||
key: string,
|
||||
value: any,
|
||||
reset = false
|
||||
) {
|
||||
return logseq.Editor.upsertBlockProperty(block, key, value, reset ? { reset: true } : undefined)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix: API quick reference (from `LSPlugin.ts`)
|
||||
|
||||
**Property schema (DB only)**
|
||||
- `Editor.getProperty(key)`
|
||||
- `Editor.upsertProperty(key, schema?, opts?)`
|
||||
- `Editor.removeProperty(key)`
|
||||
|
||||
**Block/page values**
|
||||
- `Editor.upsertBlockProperty(block, key, value, { reset? })`
|
||||
- `Editor.removeBlockProperty(block, key)`
|
||||
- `Editor.getBlockProperty(block, key)`
|
||||
- `Editor.getBlockProperties(block)`
|
||||
- `Editor.getPageProperties(page)`
|
||||
|
||||
**Tags / Classes**
|
||||
- `Editor.createTag(tagName, { tagProperties? })`
|
||||
- `Editor.getTag(nameOrIdent)` / `Editor.getAllTags()`
|
||||
- `Editor.addTagProperty(tagId, propertyIdOrName)` / `removeTagProperty`
|
||||
- `Editor.addTagExtends(tagId, parentTagIdOrName)` / `removeTagExtends`
|
||||
- `Editor.addBlockTag(blockId, tagId)` / `removeBlockTag`
|
||||
|
||||
406
libs/development-notes/db_properties_skill.md
Normal file
406
libs/development-notes/db_properties_skill.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# Logseq DB Properties SDK Reference
|
||||
|
||||
## Overview
|
||||
|
||||
Logseq has two storage modes:
|
||||
- **File-based**: Properties stored as `key:: value` in markdown files
|
||||
- **DB-based**: Properties stored in SQLite with structured schema, types, and relationships
|
||||
|
||||
This guide covers **DB-based properties API** for plugin development.
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Property Schema (Global Definition)
|
||||
Properties must be defined globally before use. Think of it as creating columns in a database.
|
||||
|
||||
### 2. Tags as Classes
|
||||
Tags act as classes/types. Pages/blocks can be tagged to inherit property schemas.
|
||||
|
||||
### 3. Property Values
|
||||
After defining properties, assign values to specific blocks/pages.
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Property Schema APIs
|
||||
|
||||
#### `upsertProperty(name, options)`
|
||||
Define or update a property schema globally.
|
||||
|
||||
**Parameters:**
|
||||
- `name` (string): Property name
|
||||
- `options` (object):
|
||||
- `type`: `'default'` | `'node'` | `'date'` | `'number'`
|
||||
- `cardinality`: `'one'` | `'many'` (for `node` type)
|
||||
|
||||
**Returns:** Property object with `uuid`
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// Simple text property
|
||||
await logseq.Editor.upsertProperty('author', { type: 'default' })
|
||||
|
||||
// Reference property (links to other pages)
|
||||
await logseq.Editor.upsertProperty('tags', {
|
||||
type: 'node',
|
||||
cardinality: 'many'
|
||||
})
|
||||
|
||||
// Date property
|
||||
await logseq.Editor.upsertProperty('publishedDate', { type: 'date' })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tag (Class) Management APIs
|
||||
|
||||
#### `createTag(name, options? )`
|
||||
Create a tag as a class/type definition.
|
||||
|
||||
**Parameters:**
|
||||
- `name` (string): Tag name
|
||||
- `options` (optional object): `{ uuid: string }`
|
||||
|
||||
**Returns:** Tag object with `uuid` and `id`
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const bookTag = await logseq.Editor.createTag('book')
|
||||
const articleTag = await logseq.Editor.createTag('article', {
|
||||
uuid: 'custom-uuid-123'
|
||||
})
|
||||
```
|
||||
|
||||
#### `getTag(name)`
|
||||
Retrieve an existing tag by name.
|
||||
|
||||
**Returns:** Tag object or `null`
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const tag = await logseq.Editor. getTag('book')
|
||||
if (! tag) {
|
||||
// Tag doesn't exist, create it
|
||||
}
|
||||
```
|
||||
|
||||
#### `addTagProperty(tagUUID, propertyName)`
|
||||
Add a property to a tag's schema. Pages tagged with this tag will have this property available.
|
||||
|
||||
**Parameters:**
|
||||
- `tagUUID` (string): Tag UUID
|
||||
- `propertyName` (string): Property name (must be defined via `upsertProperty` first)
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// Define properties globally
|
||||
await logseq.Editor. upsertProperty('author', { type: 'default' })
|
||||
await logseq.Editor.upsertProperty('isbn', { type: 'default' })
|
||||
|
||||
// Add properties to 'book' tag
|
||||
const bookTag = await logseq. Editor.createTag('book')
|
||||
await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
|
||||
await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Block/Page Property Value APIs
|
||||
|
||||
#### `upsertBlockProperty(blockUUID, propertyName, value)`
|
||||
Set property value for a specific block or page.
|
||||
|
||||
**Parameters:**
|
||||
- `blockUUID` (string): Block or page UUID
|
||||
- `propertyName` (string): Property name
|
||||
- `value` (any): Property value
|
||||
- For `node` type with `cardinality: 'many'`: Use array of page IDs
|
||||
- For `node` type with `cardinality: 'one'`: Use single page ID
|
||||
- For other types: Use primitive values
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const page = await logseq.Editor.getPage('my-page-uuid')
|
||||
|
||||
// Set simple value
|
||||
await logseq.Editor.upsertBlockProperty(page.uuid, 'author', 'John Doe')
|
||||
|
||||
// Set number
|
||||
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', 2024)
|
||||
|
||||
// Set single reference
|
||||
await logseq.Editor. upsertBlockProperty(page.uuid, 'category', categoryPageID)
|
||||
|
||||
// Set multiple references
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
page.uuid,
|
||||
'tags',
|
||||
[tagPageID1, tagPageID2, tagPageID3]
|
||||
)
|
||||
```
|
||||
|
||||
#### `addBlockTag(blockUUID, tagUUID)`
|
||||
Tag a block or page, making it an instance of that tag's class.
|
||||
|
||||
**Parameters:**
|
||||
- `blockUUID` (string): Block or page UUID
|
||||
- `tagUUID` (string): Tag UUID
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const page = await logseq. Editor.createPage('Moby Dick')
|
||||
const bookTag = await logseq.Editor.getTag('book')
|
||||
await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tag Inheritance
|
||||
|
||||
#### Set Tag Parent (Class Inheritance)
|
||||
Invoke API of `logseq.Editor.setTagExtends`
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// Create parent tag
|
||||
const rootTag = await logseq.Editor.createTag('Media')
|
||||
|
||||
// Create child tag
|
||||
const bookTag = await logseq.Editor.createTag('book')
|
||||
|
||||
// Set inheritance
|
||||
await logseq.Editor.addTagExtends(bookTag.uuid, rootTag.uuid)
|
||||
|
||||
// Use special property `:logseq.property.class/extends` to create tag hierarchy.
|
||||
// await logseq.Editor. upsertBlockProperty(
|
||||
// bookTag.uuid,
|
||||
// ':logseq.property.class/extends',
|
||||
// [rootTag.id] // Use . id, not .uuid
|
||||
// )
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Special Properties
|
||||
|
||||
#### Hide Empty Values
|
||||
```typescript
|
||||
const prop = await logseq.Editor. upsertProperty('optionalField', { type: 'default' })
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
prop.uuid,
|
||||
': logseq.property/hide-empty-value',
|
||||
true
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Define a Schema with Class Hierarchy
|
||||
|
||||
```typescript
|
||||
async function setupBookSchema() {
|
||||
// 1. Create root class
|
||||
const mediaTag = await logseq.Editor.createTag('Media')
|
||||
|
||||
// 2. Define common properties
|
||||
await logseq. Editor.upsertProperty('title', { type: 'default' })
|
||||
await logseq.Editor.upsertProperty('year', { type: 'number' })
|
||||
await logseq.Editor.upsertProperty('tags', { type: 'node', cardinality: 'many' })
|
||||
|
||||
// 3. Add properties to root class
|
||||
await logseq. Editor.addTagProperty(mediaTag.uuid, 'title')
|
||||
await logseq.Editor.addTagProperty(mediaTag.uuid, 'year')
|
||||
await logseq.Editor. addTagProperty(mediaTag.uuid, 'tags')
|
||||
|
||||
// 4. Create subclass
|
||||
const bookTag = await logseq.Editor.createTag('book')
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
bookTag.uuid,
|
||||
':logseq.property.class/extends',
|
||||
[mediaTag.id]
|
||||
)
|
||||
|
||||
// 5. Add book-specific properties
|
||||
await logseq.Editor.upsertProperty('author', { type: 'default' })
|
||||
await logseq.Editor.upsertProperty('isbn', { type: 'default' })
|
||||
await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
|
||||
await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Create an Instance
|
||||
|
||||
```typescript
|
||||
async function createBookInstance(bookData) {
|
||||
// 1. Create page with stable UUID
|
||||
const pageUUID = generateStableUUID(bookData.id)
|
||||
let page = await logseq.Editor.getPage(pageUUID)
|
||||
|
||||
if (!page) {
|
||||
page = await logseq.Editor.createPage(
|
||||
bookData.title,
|
||||
{},
|
||||
{ customUUID: pageUUID, redirect: false }
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Tag the page
|
||||
const bookTag = await logseq.Editor.getTag('book')
|
||||
await logseq.Editor.addBlockTag(page.uuid, bookTag. uuid)
|
||||
|
||||
// 3. Set property values
|
||||
await logseq. Editor.upsertBlockProperty(page.uuid, 'title', bookData.title)
|
||||
await logseq.Editor.upsertBlockProperty(page.uuid, 'author', bookData.author)
|
||||
await logseq.Editor.upsertBlockProperty(page.uuid, 'isbn', bookData.isbn)
|
||||
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData.year)
|
||||
|
||||
return page. uuid
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Link Related Entities
|
||||
|
||||
```typescript
|
||||
async function linkBookToCategories(bookPageUUID, categoryKeys) {
|
||||
// Get or create category pages
|
||||
const categoryIDs = await Promise.all(
|
||||
categoryKeys.map(async (key) => {
|
||||
const catUUID = generateStableUUID(key)
|
||||
let catPage = await logseq.Editor.getPage(catUUID)
|
||||
|
||||
if (!catPage) {
|
||||
catPage = await logseq.Editor.createPage(key, {}, {
|
||||
customUUID: catUUID
|
||||
})
|
||||
}
|
||||
|
||||
return catPage.id // Use . id for references
|
||||
})
|
||||
)
|
||||
|
||||
// Link book to categories
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
bookPageUUID,
|
||||
'tags', // Must be 'node' type with 'many' cardinality
|
||||
categoryIDs
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Batch Schema Setup from JSON
|
||||
|
||||
```typescript
|
||||
async function setupSchemaFromConfig(config) {
|
||||
// config = { "book": { "fields": ["author", "isbn"], "parent": "Media" } }
|
||||
|
||||
for (const [tagName, tagConfig] of Object.entries(config)) {
|
||||
let tag = await logseq.Editor.getTag(tagName)
|
||||
|
||||
if (!tag) {
|
||||
tag = await logseq.Editor.createTag(tagName)
|
||||
|
||||
// Set parent if specified
|
||||
if (tagConfig. parent) {
|
||||
const parentTag = await logseq. Editor.getTag(tagConfig. parent)
|
||||
if (parentTag) {
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
tag. uuid,
|
||||
':logseq.property.class/extends',
|
||||
[parentTag.id]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add properties
|
||||
for (const fieldName of tagConfig.fields) {
|
||||
await logseq.Editor.upsertProperty(fieldName, { type: 'default' })
|
||||
await logseq.Editor.addTagProperty(tag.uuid, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Stable UUIDs
|
||||
Generate deterministic UUIDs from stable identifiers:
|
||||
|
||||
```typescript
|
||||
import { v5 as uuidv5 } from 'uuid'
|
||||
|
||||
const NAMESPACE = 'your-plugin-namespace-uuid'
|
||||
|
||||
function generateStableUUID(id: string): string {
|
||||
return uuidv5(id, NAMESPACE)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Check Before Creating
|
||||
Always check if tags/pages exist before creating:
|
||||
|
||||
```typescript
|
||||
let tag = await logseq.Editor.getTag('book')
|
||||
if (!tag) {
|
||||
tag = await logseq.Editor.createTag('book')
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Property Definition Order
|
||||
1. Define property globally with `upsertProperty`
|
||||
2. Create tag with `createTag`
|
||||
3. Add property to tag with `addTagProperty`
|
||||
4. Create page and tag it with `addBlockTag`
|
||||
5. Set values with `upsertBlockProperty`
|
||||
|
||||
### 4. Use . id for References
|
||||
When setting `node` type properties, use `.id` not `.uuid`:
|
||||
|
||||
```typescript
|
||||
await logseq.Editor.upsertBlockProperty(
|
||||
page. uuid,
|
||||
'category',
|
||||
categoryPage.id // ✅ Correct
|
||||
// categoryPage.uuid // ❌ Wrong
|
||||
)
|
||||
```
|
||||
|
||||
### 5. Error Handling
|
||||
Wrap API calls in try-catch for robustness:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await logseq.Editor.upsertBlockProperty(uuid, 'field', value)
|
||||
} catch (error) {
|
||||
console.error(`Failed to set property: ${error}`)
|
||||
// Handle gracefully
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Real-World Example: Zotero Plugin
|
||||
|
||||
See complete implementation: [xyhp915/logseq-zotero-plugin](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers.ts#L19-L207)
|
||||
|
||||
Key techniques:
|
||||
- Schema defined from `z_item_types. json` metadata
|
||||
- Tag hierarchy: `Zotero` → `book`, `article`, etc.
|
||||
- Stable UUIDs via `v5(itemKey, namespace)`
|
||||
- Relationship properties for collections
|
||||
- Nested blocks for attachments
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
- **Type Definitions**: See `@logseq/libs` package
|
||||
- **Examples**: [logseq-zotero-plugin/handlers.ts](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers. ts)
|
||||
- **Search More**: [GitHub Code Search](https://github.com/search?type=code&q=repo:xyhp915/logseq-zotero-plugin+upsertBlockProperty)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@logseq/libs",
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"description": "Logseq SDK libraries",
|
||||
"main": "dist/lsplugin.user.js",
|
||||
"typings": "index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user