|
|
@@ -3,8 +3,8 @@
|
|
|
## 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
|
|
|
+- **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.
|
|
|
|
|
|
@@ -27,16 +27,20 @@ After defining properties, assign values to specific blocks/pages.
|
|
|
|
|
|
### Property Schema APIs
|
|
|
|
|
|
-#### `upsertProperty(name, options)`
|
|
|
+#### `upsertProperty(name, schema?, opts?)`
|
|
|
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)
|
|
|
+- `schema` (optional object):
|
|
|
+ - `type`: `'default'` | `'node'` | `'date'` | `'number'` | `'checkbox'` | `'url'`
|
|
|
+ - `cardinality`: `'one'` | `'many'` (for `node` type)
|
|
|
+ - `hide`: boolean - hide property in UI
|
|
|
+ - `public`: boolean - make property public
|
|
|
+- `opts` (optional object):
|
|
|
+ - `name`: string - display name override
|
|
|
|
|
|
-**Returns:** Property object with `uuid`
|
|
|
+**Returns:** `IEntityID` object with `id`
|
|
|
|
|
|
**Example:**
|
|
|
```typescript
|
|
|
@@ -49,77 +53,216 @@ await logseq.Editor.upsertProperty('tags', {
|
|
|
cardinality: 'many'
|
|
|
})
|
|
|
|
|
|
-// Date property
|
|
|
-await logseq.Editor.upsertProperty('publishedDate', { type: 'date' })
|
|
|
+// Hidden property with custom display name
|
|
|
+await logseq.Editor. upsertProperty('internalKey',
|
|
|
+ { type: 'default', hide: true },
|
|
|
+ { name: 'Internal Key' }
|
|
|
+)
|
|
|
+```
|
|
|
+
|
|
|
+#### `getProperty(key)`
|
|
|
+Retrieve an existing property by key.
|
|
|
+
|
|
|
+**Returns:** `BlockEntity` or `null`
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+const prop = await logseq.Editor. getProperty('author')
|
|
|
+if (!prop) {
|
|
|
+ await logseq.Editor.upsertProperty('author', { type: 'default' })
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### `removeProperty(key)`
|
|
|
+Remove a property definition globally.
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+await logseq.Editor.removeProperty('deprecatedField')
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### Tag (Class) Management APIs
|
|
|
|
|
|
-#### `createTag(name, options? )`
|
|
|
-Create a tag as a class/type definition.
|
|
|
+#### `createTag(name, opts?)`
|
|
|
+Create a tag as a class/type definition with properties in one call.
|
|
|
|
|
|
**Parameters:**
|
|
|
- `name` (string): Tag name
|
|
|
-- `options` (optional object): `{ uuid: string }`
|
|
|
+- `opts` (optional object):
|
|
|
+ - `uuid`: string - custom UUID
|
|
|
+ - `tagProperties`: Array of property definitions
|
|
|
+ - `name`: string - property name
|
|
|
+ - `schema`: Partial<PropertySchema> - property schema
|
|
|
+ - `properties`: object - additional properties
|
|
|
|
|
|
-**Returns:** Tag object with `uuid` and `id`
|
|
|
+**Returns:** `PageEntity` or `null`
|
|
|
|
|
|
**Example:**
|
|
|
```typescript
|
|
|
+// Simple tag
|
|
|
const bookTag = await logseq.Editor.createTag('book')
|
|
|
+
|
|
|
+// Tag with custom UUID
|
|
|
const articleTag = await logseq.Editor.createTag('article', {
|
|
|
uuid: 'custom-uuid-123'
|
|
|
})
|
|
|
+
|
|
|
+// Tag with properties defined in one call
|
|
|
+const bookTag = await logseq.Editor. createTag('book', {
|
|
|
+ uuid: generateStableUUID('book'),
|
|
|
+ tagProperties: [
|
|
|
+ {
|
|
|
+ name: 'author',
|
|
|
+ schema: { type: 'default' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'isbn',
|
|
|
+ schema: { type: 'default' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'tags',
|
|
|
+ schema: { type: 'node', cardinality: 'many' }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+})
|
|
|
```
|
|
|
|
|
|
-#### `getTag(name)`
|
|
|
-Retrieve an existing tag by name.
|
|
|
+#### `getTag(nameOrIdent)`
|
|
|
+Retrieve an existing tag by name or entity ID.
|
|
|
|
|
|
-**Returns:** Tag object or `null`
|
|
|
+**Returns:** `PageEntity` or `null`
|
|
|
|
|
|
**Example:**
|
|
|
```typescript
|
|
|
-const tag = await logseq.Editor. getTag('book')
|
|
|
-if (! tag) {
|
|
|
+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.
|
|
|
+#### `getTagsByName(tagName)`
|
|
|
+Retrieve all tags matching a name (useful for namespaced tags).
|
|
|
+
|
|
|
+**Returns:** `Array<PageEntity>` or `null`
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+const tags = await logseq.Editor.getTagsByName('book')
|
|
|
+```
|
|
|
+
|
|
|
+#### `getAllTags()`
|
|
|
+Get all tags in the current graph.
|
|
|
+
|
|
|
+**Returns:** `Array<PageEntity>` or `null`
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+const allTags = await logseq. Editor.getAllTags()
|
|
|
+```
|
|
|
+
|
|
|
+#### `addTagProperty(tagId, propertyIdOrName)`
|
|
|
+Add a property to a tag's schema. Property must be defined via `upsertProperty` first.
|
|
|
|
|
|
**Parameters:**
|
|
|
-- `tagUUID` (string): Tag UUID
|
|
|
-- `propertyName` (string): Property name (must be defined via `upsertProperty` first)
|
|
|
+- `tagId` (BlockIdentity): Tag UUID
|
|
|
+- `propertyIdOrName` (BlockIdentity): Property UUID or name
|
|
|
|
|
|
**Example:**
|
|
|
```typescript
|
|
|
-// Define properties globally
|
|
|
+// Define property globally
|
|
|
await logseq.Editor. upsertProperty('author', { type: 'default' })
|
|
|
-await logseq.Editor.upsertProperty('isbn', { type: 'default' })
|
|
|
|
|
|
-// Add properties to 'book' tag
|
|
|
+// Add to tag
|
|
|
+const bookTag = await logseq. Editor.createTag('book')
|
|
|
+await logseq.Editor. addTagProperty(bookTag.uuid, 'author')
|
|
|
+```
|
|
|
+
|
|
|
+#### `removeTagProperty(tagId, propertyIdOrName)`
|
|
|
+Remove a property from a tag's schema.
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+await logseq.Editor.removeTagProperty(bookTag.uuid, 'deprecatedField')
|
|
|
+```
|
|
|
+
|
|
|
+#### `addTagExtends(tagId, parentTagIdOrName)`
|
|
|
+Set tag inheritance (class extends).
|
|
|
+
|
|
|
+**Parameters:**
|
|
|
+- `tagId` (BlockIdentity): Child tag UUID
|
|
|
+- `parentTagIdOrName` (BlockIdentity): Parent tag UUID or name
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+const mediaTag = await logseq.Editor.createTag('Media')
|
|
|
const bookTag = await logseq. Editor.createTag('book')
|
|
|
-await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
|
|
|
-await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
|
|
|
+
|
|
|
+// book extends Media
|
|
|
+await logseq.Editor. addTagExtends(bookTag. uuid, mediaTag.uuid)
|
|
|
+```
|
|
|
+
|
|
|
+#### `removeTagExtends(tagId, parentTagIdOrName)`
|
|
|
+Remove tag inheritance.
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+await logseq.Editor.removeTagExtends(bookTag.uuid, mediaTag.uuid)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Block/Page Tagging APIs
|
|
|
+
|
|
|
+#### `addBlockTag(blockId, tagId)`
|
|
|
+Tag a block or page, making it an instance of that tag's class.
|
|
|
+
|
|
|
+**Parameters:**
|
|
|
+- `blockId` (BlockIdentity): Block or page UUID
|
|
|
+- `tagId` (BlockIdentity): 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)
|
|
|
+```
|
|
|
+
|
|
|
+#### `removeBlockTag(blockId, tagId)`
|
|
|
+Remove a tag from a block or page.
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+await logseq.Editor.removeBlockTag(page.uuid, bookTag.uuid)
|
|
|
+```
|
|
|
+
|
|
|
+#### `getTagObjects(nameOrIdent)`
|
|
|
+Get all blocks/pages tagged with a specific tag.
|
|
|
+
|
|
|
+**Returns:** `Array<BlockEntity>` or `null`
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+const allBooks = await logseq.Editor.getTagObjects('book')
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### Block/Page Property Value APIs
|
|
|
|
|
|
-#### `upsertBlockProperty(blockUUID, propertyName, value)`
|
|
|
+#### `upsertBlockProperty(blockUUID, key, value, options?)`
|
|
|
Set property value for a specific block or page.
|
|
|
|
|
|
**Parameters:**
|
|
|
-- `blockUUID` (string): Block or page UUID
|
|
|
-- `propertyName` (string): Property name
|
|
|
+- `blockUUID` (BlockIdentity | EntityID): Block or page UUID or entity ID
|
|
|
+- `key` (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
|
|
|
+ - For `node` type with `cardinality: 'many'`: Use array of page IDs (`.id` not `.uuid`)
|
|
|
+ - For `node` type with `cardinality: 'one'`: Use single page ID
|
|
|
+ - For other types: Use primitive values
|
|
|
+- `options` (optional object):
|
|
|
+ - `reset`: boolean - replace instead of merge
|
|
|
|
|
|
**Example:**
|
|
|
```typescript
|
|
|
@@ -131,109 +274,147 @@ 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 single reference (use . id not .uuid)
|
|
|
+await logseq.Editor.upsertBlockProperty(page.uuid, 'category', categoryPage.id)
|
|
|
|
|
|
-// Set multiple references
|
|
|
+// Set multiple references (use . id not .uuid)
|
|
|
await logseq.Editor.upsertBlockProperty(
|
|
|
page.uuid,
|
|
|
'tags',
|
|
|
- [tagPageID1, tagPageID2, tagPageID3]
|
|
|
+ [tagPage1.id, tagPage2.id, tagPage3.id]
|
|
|
+)
|
|
|
+
|
|
|
+// Reset property value completely
|
|
|
+await logseq.Editor. upsertBlockProperty(
|
|
|
+ page.uuid,
|
|
|
+ 'tags',
|
|
|
+ [newTag1.id],
|
|
|
+ { reset: true }
|
|
|
)
|
|
|
```
|
|
|
|
|
|
-#### `addBlockTag(blockUUID, tagUUID)`
|
|
|
-Tag a block or page, making it an instance of that tag's class.
|
|
|
+#### `getBlockProperty(blockId, key)`
|
|
|
+Get a single property value from a block or page.
|
|
|
|
|
|
-**Parameters:**
|
|
|
-- `blockUUID` (string): Block or page UUID
|
|
|
-- `tagUUID` (string): Tag UUID
|
|
|
+**Returns:** `BlockEntity` or `null`
|
|
|
|
|
|
**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)
|
|
|
+const author = await logseq.Editor. getBlockProperty(page.uuid, 'author')
|
|
|
```
|
|
|
|
|
|
----
|
|
|
+#### `getBlockProperties(blockId)`
|
|
|
+Get all properties from a block.
|
|
|
|
|
|
-### Tag Inheritance
|
|
|
-
|
|
|
-#### Set Tag Parent (Class Inheritance)
|
|
|
-Invoke API of `logseq.Editor.setTagExtends`
|
|
|
+**Returns:** `Record<string, any>` or `null`
|
|
|
|
|
|
**Example:**
|
|
|
```typescript
|
|
|
-// Create parent tag
|
|
|
-const rootTag = await logseq.Editor.createTag('Media')
|
|
|
+const props = await logseq.Editor. getBlockProperties(page.uuid)
|
|
|
+console.log(props. author, props.isbn)
|
|
|
+```
|
|
|
|
|
|
-// Create child tag
|
|
|
-const bookTag = await logseq.Editor.createTag('book')
|
|
|
+#### `getPageProperties(pageId)`
|
|
|
+Get all properties from a page.
|
|
|
+
|
|
|
+**Returns:** `Record<string, any>` or `null`
|
|
|
+
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+const props = await logseq.Editor.getPageProperties(page.uuid)
|
|
|
+```
|
|
|
|
|
|
-// Set inheritance
|
|
|
-await logseq.Editor.addTagExtends(bookTag.uuid, rootTag.uuid)
|
|
|
+#### `removeBlockProperty(blockId, key)`
|
|
|
+Remove a property from a block or page.
|
|
|
|
|
|
-// 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
|
|
|
-// )
|
|
|
+**Example:**
|
|
|
+```typescript
|
|
|
+await logseq.Editor.removeBlockProperty(page.uuid, 'deprecatedField')
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
-### Special Properties
|
|
|
+### Special Properties & Utilities
|
|
|
|
|
|
#### Hide Empty Values
|
|
|
+Use special property key `:logseq. property/hide-empty-value`:
|
|
|
+
|
|
|
```typescript
|
|
|
-const prop = await logseq.Editor. upsertProperty('optionalField', { type: 'default' })
|
|
|
+const prop = await logseq.Editor.upsertProperty('optionalField', { type: 'default' })
|
|
|
await logseq.Editor.upsertBlockProperty(
|
|
|
- prop.uuid,
|
|
|
+ prop.id, // Use property's entity ID
|
|
|
': logseq.property/hide-empty-value',
|
|
|
true
|
|
|
)
|
|
|
```
|
|
|
|
|
|
+#### Set Block Icon
|
|
|
+```typescript
|
|
|
+// Tabler icon
|
|
|
+await logseq.Editor.setBlockIcon(blockId, 'tabler-icon', 'book')
|
|
|
+
|
|
|
+// Emoji (from emoji-mart)
|
|
|
+await logseq.Editor.setBlockIcon(blockId, 'emoji', 'books')
|
|
|
+```
|
|
|
+
|
|
|
+#### Remove Block Icon
|
|
|
+```typescript
|
|
|
+await logseq.Editor.removeBlockIcon(blockId)
|
|
|
+```
|
|
|
+
|
|
|
---
|
|
|
|
|
|
## Common Patterns
|
|
|
|
|
|
-### Pattern 1: Define a Schema with Class Hierarchy
|
|
|
+### Pattern 1: Define Schema with Tag and Properties (Simple)
|
|
|
|
|
|
```typescript
|
|
|
async function setupBookSchema() {
|
|
|
- // 1. Create root class
|
|
|
- const mediaTag = await logseq.Editor.createTag('Media')
|
|
|
+ // Create tag with all properties in one call
|
|
|
+ const bookTag = await logseq.Editor.createTag('book', {
|
|
|
+ uuid: generateStableUUID('book'),
|
|
|
+ tagProperties: [
|
|
|
+ { name: 'title', schema: { type: 'default' } },
|
|
|
+ { name: 'author', schema: { type: 'default' } },
|
|
|
+ { name: 'isbn', schema: { type: 'default' } },
|
|
|
+ { name: 'year', schema: { type: 'number' } },
|
|
|
+ { name: 'tags', schema: { type: 'node', cardinality: 'many' } }
|
|
|
+ ]
|
|
|
+ })
|
|
|
|
|
|
- // 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' })
|
|
|
+ return bookTag
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Pattern 2: Define Schema with Inheritance
|
|
|
+
|
|
|
+```typescript
|
|
|
+async function setupSchemaWithInheritance() {
|
|
|
+ // 1. Create parent tag
|
|
|
+ const mediaTag = await logseq.Editor.createTag('Media', {
|
|
|
+ tagProperties: [
|
|
|
+ { name: 'title', schema: { type: 'default' } },
|
|
|
+ { name: 'year', schema: { type: 'number' } },
|
|
|
+ { name: 'tags', schema: { 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')
|
|
|
+ // 2. Create child tag with specific properties
|
|
|
+ const bookTag = await logseq.Editor.createTag('book', {
|
|
|
+ tagProperties: [
|
|
|
+ { name: 'author', schema: { type: 'default' } },
|
|
|
+ { name: 'isbn', schema: { type: 'default' } }
|
|
|
+ ]
|
|
|
+ })
|
|
|
|
|
|
- // 4. Create subclass
|
|
|
- const bookTag = await logseq.Editor.createTag('book')
|
|
|
- await logseq.Editor.upsertBlockProperty(
|
|
|
- bookTag.uuid,
|
|
|
- ':logseq.property.class/extends',
|
|
|
- [mediaTag.id]
|
|
|
- )
|
|
|
+ // 3. Set inheritance
|
|
|
+ await logseq. Editor.addTagExtends(bookTag.uuid, mediaTag.uuid)
|
|
|
|
|
|
- // 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')
|
|
|
+ return { mediaTag, bookTag }
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### Pattern 2: Create an Instance
|
|
|
+### Pattern 3: Create Instance with Properties
|
|
|
|
|
|
```typescript
|
|
|
async function createBookInstance(bookData) {
|
|
|
@@ -242,7 +423,7 @@ async function createBookInstance(bookData) {
|
|
|
let page = await logseq.Editor.getPage(pageUUID)
|
|
|
|
|
|
if (!page) {
|
|
|
- page = await logseq.Editor.createPage(
|
|
|
+ page = await logseq.Editor. createPage(
|
|
|
bookData.title,
|
|
|
{},
|
|
|
{ customUUID: pageUUID, redirect: false }
|
|
|
@@ -250,26 +431,26 @@ async function createBookInstance(bookData) {
|
|
|
}
|
|
|
|
|
|
// 2. Tag the page
|
|
|
- const bookTag = await logseq.Editor.getTag('book')
|
|
|
+ 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, 'author', bookData. author)
|
|
|
await logseq.Editor.upsertBlockProperty(page.uuid, 'isbn', bookData.isbn)
|
|
|
- await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData.year)
|
|
|
+ await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData. year)
|
|
|
|
|
|
return page. uuid
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### Pattern 3: Link Related Entities
|
|
|
+### Pattern 4: Link Related Entities
|
|
|
|
|
|
```typescript
|
|
|
async function linkBookToCategories(bookPageUUID, categoryKeys) {
|
|
|
// Get or create category pages
|
|
|
const categoryIDs = await Promise.all(
|
|
|
- categoryKeys.map(async (key) => {
|
|
|
+ categoryKeys. map(async (key) => {
|
|
|
const catUUID = generateStableUUID(key)
|
|
|
let catPage = await logseq.Editor.getPage(catUUID)
|
|
|
|
|
|
@@ -279,7 +460,7 @@ async function linkBookToCategories(bookPageUUID, categoryKeys) {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- return catPage.id // Use . id for references
|
|
|
+ return catPage.id // ⚠️ Use .id not .uuid for references
|
|
|
})
|
|
|
)
|
|
|
|
|
|
@@ -292,35 +473,78 @@ async function linkBookToCategories(bookPageUUID, categoryKeys) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### Pattern 4: Batch Schema Setup from JSON
|
|
|
+### Pattern 5: Batch Schema Setup from Configuration
|
|
|
|
|
|
```typescript
|
|
|
async function setupSchemaFromConfig(config) {
|
|
|
- // config = { "book": { "fields": ["author", "isbn"], "parent": "Media" } }
|
|
|
+ // config example:
|
|
|
+ // {
|
|
|
+ // "Media": {
|
|
|
+ // "properties": ["title", "year"],
|
|
|
+ // "parent": null
|
|
|
+ // },
|
|
|
+ // "book": {
|
|
|
+ // "properties": ["author", "isbn"],
|
|
|
+ // "parent": "Media"
|
|
|
+ // }
|
|
|
+ // }
|
|
|
|
|
|
- for (const [tagName, tagConfig] of Object.entries(config)) {
|
|
|
- let tag = await logseq.Editor.getTag(tagName)
|
|
|
+ for (const [tagName, tagConfig] of Object. entries(config)) {
|
|
|
+ let tag = await logseq.Editor. getTag(tagName)
|
|
|
|
|
|
if (!tag) {
|
|
|
- tag = await logseq.Editor.createTag(tagName)
|
|
|
+ // Create tag with properties
|
|
|
+ tag = await logseq.Editor.createTag(tagName, {
|
|
|
+ tagProperties: tagConfig.properties. map(name => ({
|
|
|
+ name,
|
|
|
+ schema: { type: 'default' }
|
|
|
+ }))
|
|
|
+ })
|
|
|
|
|
|
// Set parent if specified
|
|
|
- if (tagConfig. parent) {
|
|
|
+ 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]
|
|
|
- )
|
|
|
+ await logseq.Editor.addTagExtends(tag.uuid, parentTag.uuid)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Pattern 6: Migrate Existing Pages to New Schema
|
|
|
+
|
|
|
+```typescript
|
|
|
+async function migrateToBookSchema() {
|
|
|
+ // 1. Setup schema
|
|
|
+ const bookTag = await logseq.Editor.createTag('book', {
|
|
|
+ tagProperties: [
|
|
|
+ { name: 'author', schema: { type: 'default' } },
|
|
|
+ { name: 'year', schema: { type: 'number' } }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+
|
|
|
+ // 2. Find all pages with old structure
|
|
|
+ const allPages = await logseq.Editor.getAllPages()
|
|
|
+
|
|
|
+ // 3. Migrate each page
|
|
|
+ for (const page of allPages) {
|
|
|
+ const props = await logseq.Editor. getPageProperties(page.uuid)
|
|
|
|
|
|
- // Add properties
|
|
|
- for (const fieldName of tagConfig.fields) {
|
|
|
- await logseq.Editor.upsertProperty(fieldName, { type: 'default' })
|
|
|
- await logseq.Editor.addTagProperty(tag.uuid, fieldName)
|
|
|
+ // Check if page has book-like properties
|
|
|
+ if (props?. author || props?.isbn) {
|
|
|
+ // Add book tag
|
|
|
+ await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)
|
|
|
+
|
|
|
+ // Migrate properties if needed
|
|
|
+ if (props. author) {
|
|
|
+ await logseq.Editor.upsertBlockProperty(
|
|
|
+ page.uuid,
|
|
|
+ 'author',
|
|
|
+ props. author
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -344,31 +568,44 @@ function generateStableUUID(id: string): string {
|
|
|
```
|
|
|
|
|
|
### 2. Check Before Creating
|
|
|
-Always check if tags/pages exist before creating:
|
|
|
+Always check if tags/pages/properties exist before creating:
|
|
|
|
|
|
```typescript
|
|
|
let tag = await logseq.Editor.getTag('book')
|
|
|
if (!tag) {
|
|
|
- tag = await logseq.Editor.createTag('book')
|
|
|
+ tag = await logseq.Editor.createTag('book', {
|
|
|
+ tagProperties: [/* ... */]
|
|
|
+ })
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### 3. Property Definition Order
|
|
|
+### 3. Property Definition Order (Manual Approach)
|
|
|
+If not using `createTag` with `tagProperties`:
|
|
|
+
|
|
|
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
|
|
|
+### 4. Use . id for References ⚠️
|
|
|
When setting `node` type properties, use `.id` not `.uuid`:
|
|
|
|
|
|
```typescript
|
|
|
+const categoryPage = await logseq.Editor.getPage('category-uuid')
|
|
|
+
|
|
|
+// ✅ Correct
|
|
|
await logseq.Editor.upsertBlockProperty(
|
|
|
- page. uuid,
|
|
|
+ page.uuid,
|
|
|
'category',
|
|
|
- categoryPage.id // ✅ Correct
|
|
|
- // categoryPage.uuid // ❌ Wrong
|
|
|
+ categoryPage.id
|
|
|
+)
|
|
|
+
|
|
|
+// ❌ Wrong
|
|
|
+await logseq.Editor.upsertBlockProperty(
|
|
|
+ page.uuid,
|
|
|
+ 'category',
|
|
|
+ categoryPage.uuid
|
|
|
)
|
|
|
```
|
|
|
|
|
|
@@ -380,15 +617,35 @@ try {
|
|
|
await logseq.Editor.upsertBlockProperty(uuid, 'field', value)
|
|
|
} catch (error) {
|
|
|
console.error(`Failed to set property: ${error}`)
|
|
|
- // Handle gracefully
|
|
|
+ await logseq.UI.showMsg(`Error: ${error. message}`, 'error')
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+### 6. Use createTag with tagProperties
|
|
|
+Prefer the streamlined approach for better readability:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// ✅ Better: One call
|
|
|
+const tag = await logseq.Editor. createTag('book', {
|
|
|
+ tagProperties: [
|
|
|
+ { name: 'author', schema: { type: 'default' } },
|
|
|
+ { name: 'isbn', schema: { type: 'default' } }
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+// ❌ Verbose: Multiple calls
|
|
|
+await logseq. Editor.upsertProperty('author', { type: 'default' })
|
|
|
+await logseq.Editor.upsertProperty('isbn', { type: 'default' })
|
|
|
+const tag = await logseq.Editor. createTag('book')
|
|
|
+await logseq.Editor. addTagProperty(tag.uuid, 'author')
|
|
|
+await logseq.Editor.addTagProperty(tag.uuid, 'isbn')
|
|
|
+```
|
|
|
+
|
|
|
---
|
|
|
|
|
|
-## Real-World Example: Zotero Plugin
|
|
|
+## 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)
|
|
|
+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
|
|
|
@@ -399,8 +656,25 @@ Key techniques:
|
|
|
|
|
|
---
|
|
|
|
|
|
+## Type Definitions
|
|
|
+
|
|
|
+```typescript
|
|
|
+type PropertySchema = {
|
|
|
+ type: 'default' | 'number' | 'node' | 'date' | 'checkbox' | 'url' | string
|
|
|
+ cardinality: 'many' | 'one'
|
|
|
+ hide: boolean
|
|
|
+ public: boolean
|
|
|
+}
|
|
|
+
|
|
|
+type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
|
|
|
+type PageIdentity = BlockPageName | BlockIdentity
|
|
|
+type EntityID = number
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
## 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)
|
|
|
+- **Type Definitions**: `@logseq/libs` - [LSPlugin. ts](https://github.com/logseq/logseq/blob/master/libs/src/LSPlugin.ts)
|
|
|
+- **Real Example**: [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)
|