mirror of
https://github.com/zadam/trilium.git
synced 2025-12-06 07:24:25 +01:00
507 lines
11 KiB
Markdown
Vendored
507 lines
11 KiB
Markdown
Vendored
# Advanced Attributes
|
|
|
|
Master complex attribute features including inheritance, templates, computed values, and system attributes.
|
|
|
|
## Prerequisites
|
|
|
|
- Understanding of basic labels and relations
|
|
- Familiarity with note hierarchy
|
|
- Knowledge of promoted attributes
|
|
|
|
## Attribute Inheritance
|
|
|
|
### Inheritance Mechanism
|
|
|
|
Attributes marked as inheritable propagate down the note tree to all descendants.
|
|
|
|
#### Basic Inheritance
|
|
```javascript
|
|
// Parent note
|
|
#workspace #inheritable
|
|
|
|
// All child notes automatically receive
|
|
#workspace (inherited)
|
|
```
|
|
|
|
#### Inheritance Chain
|
|
```
|
|
Root Note
|
|
├── #theme=dark #inheritable
|
|
│ └── Child A (inherits #theme=dark)
|
|
│ └── Child B (inherits #theme=dark)
|
|
│ └── #theme=light (overrides inherited value)
|
|
```
|
|
|
|
### Controlling Inheritance
|
|
|
|
#### Enable Inheritance
|
|
Mark attribute as inheritable:
|
|
```javascript
|
|
note.setLabel('category', 'project', true); // true = inheritable
|
|
```
|
|
|
|
#### Block Inheritance
|
|
Stop inheritance at specific level:
|
|
```javascript
|
|
// Parent has #inheritable attribute
|
|
#workspace #inheritable
|
|
|
|
// Child blocks inheritance
|
|
#workspace #noInherit
|
|
```
|
|
|
|
#### Inheritance Templates
|
|
Define inheritable attribute sets:
|
|
```javascript
|
|
// Template note
|
|
#template
|
|
#label:status=text #inheritable
|
|
#label:priority=number #inheritable
|
|
#relation:assignee #inheritable
|
|
```
|
|
|
|
### Advanced Inheritance Patterns
|
|
|
|
#### Conditional Inheritance
|
|
```javascript
|
|
// Only inherit if condition met
|
|
if (note.hasLabel('acceptInheritance')) {
|
|
note.inheritAttributesFrom(parent);
|
|
}
|
|
```
|
|
|
|
#### Selective Inheritance
|
|
```javascript
|
|
// Inherit only specific attributes
|
|
const inheritableAttrs = parent.getAttributes()
|
|
.filter(attr => attr.isInheritable && attr.name.startsWith('project'));
|
|
|
|
inheritableAttrs.forEach(attr => {
|
|
note.setAttribute(attr.type, attr.name, attr.value);
|
|
});
|
|
```
|
|
|
|
## Computed Attributes
|
|
|
|
### Dynamic Attribute Values
|
|
|
|
Attributes whose values are calculated based on other data.
|
|
|
|
#### Formula Attributes
|
|
```javascript
|
|
// Define computed attribute
|
|
#formula=noteCount
|
|
#formulaCode=`
|
|
const children = note.getChildNotes();
|
|
return children.length;
|
|
`
|
|
```
|
|
|
|
#### Aggregation Attributes
|
|
```javascript
|
|
// Sum values from children
|
|
#aggregate=totalHours
|
|
#aggregateCode=`
|
|
const children = note.getChildNotes();
|
|
return children
|
|
.map(child => parseInt(child.getLabelValue('hours') || 0))
|
|
.reduce((sum, hours) => sum + hours, 0);
|
|
`
|
|
```
|
|
|
|
### Reactive Attributes
|
|
|
|
Update automatically when dependencies change:
|
|
|
|
```javascript
|
|
// Watch for changes
|
|
api.onAttributeChange((note, attr) => {
|
|
if (attr.name === 'status') {
|
|
// Update computed attributes
|
|
updateComputedProgress(note);
|
|
}
|
|
});
|
|
|
|
function updateComputedProgress(note) {
|
|
const children = note.getChildNotes();
|
|
const completed = children.filter(c => c.getLabelValue('status') === 'done').length;
|
|
const total = children.length;
|
|
|
|
note.setLabel('progress', `${completed}/${total}`);
|
|
note.setLabel('progressPercent', Math.round((completed/total) * 100));
|
|
}
|
|
```
|
|
|
|
## System Attributes
|
|
|
|
### Core System Attributes
|
|
|
|
#### Note Configuration
|
|
- `#disableVersioning` - Disable revision tracking
|
|
- `#readOnly` - Make note read-only
|
|
- `#hideFromTree` - Hide note in tree view
|
|
- `#excludeFromSearch` - Exclude from search results
|
|
- `#excludeFromExport` - Skip during export
|
|
|
|
#### Display Attributes
|
|
- `#iconClass` - Custom icon (e.g., `bx bx-star`)
|
|
- `#cssClass` - CSS class for styling
|
|
- `#color` - Note color in tree
|
|
- `#sorted` - Keep children sorted
|
|
|
|
#### Behavior Attributes
|
|
- `#runOnNoteCreation` - Execute script on creation
|
|
- `#runOnNoteTitleChange` - Execute on title change
|
|
- `#runOnNoteChange` - Execute on any change
|
|
- `#runOnChildNoteCreation` - Execute when child created
|
|
|
|
### Widget Attributes
|
|
|
|
Control widget behavior and display:
|
|
|
|
```javascript
|
|
// Configure widget visibility
|
|
#widget=attributeList
|
|
#widgetMode=view // view|edit|both
|
|
#widgetPosition=right // left|right|center
|
|
|
|
// Custom widget configuration
|
|
#customWidget=myWidget
|
|
#widgetOptions={"columns": 3, "sortBy": "date"}
|
|
```
|
|
|
|
### Script Attributes
|
|
|
|
Define script execution contexts:
|
|
|
|
```javascript
|
|
// Backend script
|
|
#run=backend
|
|
#runAtHour=2 // Run at 2 AM daily
|
|
|
|
// Frontend script
|
|
#run=frontend
|
|
#runOnEvent=activeNoteChanged
|
|
|
|
// API endpoint
|
|
#customRequestHandler=/api/custom
|
|
```
|
|
|
|
## Attribute Templates
|
|
|
|
### Defining Templates
|
|
|
|
Create reusable attribute definitions:
|
|
|
|
```javascript
|
|
// Attribute definition note
|
|
#label:status=text
|
|
#label:priority=single,low,medium,high,critical
|
|
#label:dueDate=date
|
|
#relation:assignee=person
|
|
#relation:project
|
|
```
|
|
|
|
### Template Application
|
|
|
|
#### Manual Application
|
|
```javascript
|
|
const template = api.getNoteWithLabel('attributeTemplate');
|
|
const definitions = template.getAttributeDefinitions();
|
|
|
|
targetNote.applyTemplate(definitions);
|
|
```
|
|
|
|
#### Automatic Application
|
|
```javascript
|
|
// On note creation
|
|
#runOnChildNoteCreation=`
|
|
const template = api.getNoteWithLabel('taskTemplate');
|
|
child.applyTemplate(template);
|
|
`
|
|
```
|
|
|
|
### Complex Templates
|
|
|
|
#### Hierarchical Templates
|
|
```javascript
|
|
// Project template
|
|
{
|
|
attributes: [
|
|
{name: 'projectStatus', type: 'label', value: 'active'},
|
|
{name: 'projectManager', type: 'relation'}
|
|
],
|
|
childTemplates: {
|
|
'tasks': 'taskTemplate',
|
|
'docs': 'documentTemplate',
|
|
'meetings': 'meetingTemplate'
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Conditional Templates
|
|
```javascript
|
|
function applyConditionalTemplate(note) {
|
|
const noteType = note.type;
|
|
const templates = {
|
|
'text': 'textNoteTemplate',
|
|
'code': 'codeNoteTemplate',
|
|
'task': 'taskNoteTemplate'
|
|
};
|
|
|
|
const templateId = templates[noteType];
|
|
if (templateId) {
|
|
note.applyTemplate(api.getNote(templateId));
|
|
}
|
|
}
|
|
```
|
|
|
|
## Bulk Attribute Operations
|
|
|
|
### Mass Attribute Updates
|
|
|
|
#### Add Attributes to Multiple Notes
|
|
```javascript
|
|
const notes = api.searchForNotes('#needsUpdate');
|
|
notes.forEach(note => {
|
|
note.setLabel('lastUpdated', new Date().toISOString());
|
|
note.setLabel('updatedBy', 'bulkOperation');
|
|
});
|
|
```
|
|
|
|
#### Remove Attributes in Bulk
|
|
```javascript
|
|
const notes = api.searchForNotes('#deprecated');
|
|
notes.forEach(note => {
|
|
note.removeLabel('deprecated');
|
|
note.removeLabel('oldVersion');
|
|
});
|
|
```
|
|
|
|
### Attribute Migration
|
|
|
|
#### Rename Attributes
|
|
```javascript
|
|
function migrateAttributes(oldName, newName) {
|
|
const notes = api.searchForNotes(`#${oldName}`);
|
|
|
|
notes.forEach(note => {
|
|
const value = note.getLabelValue(oldName);
|
|
note.removeLabel(oldName);
|
|
note.setLabel(newName, value);
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Transform Attribute Values
|
|
```javascript
|
|
function transformAttributeValues(attrName, transformer) {
|
|
const notes = api.searchForNotes(`#${attrName}`);
|
|
|
|
notes.forEach(note => {
|
|
const oldValue = note.getLabelValue(attrName);
|
|
const newValue = transformer(oldValue);
|
|
note.setLabel(attrName, newValue);
|
|
});
|
|
}
|
|
|
|
// Example: Convert dates to ISO format
|
|
transformAttributeValues('date', (value) => {
|
|
return new Date(value).toISOString();
|
|
});
|
|
```
|
|
|
|
## Advanced Attribute Patterns
|
|
|
|
### Attribute Chains
|
|
|
|
Link attributes across notes:
|
|
|
|
```javascript
|
|
// Follow relation chain
|
|
function getRelationChain(startNote, relationName, maxDepth = 10) {
|
|
const chain = [];
|
|
let current = startNote;
|
|
let depth = 0;
|
|
|
|
while (current && depth < maxDepth) {
|
|
const target = current.getRelationTarget(relationName);
|
|
if (!target) break;
|
|
|
|
chain.push(target);
|
|
current = target;
|
|
depth++;
|
|
}
|
|
|
|
return chain;
|
|
}
|
|
```
|
|
|
|
### Attribute Validation
|
|
|
|
Ensure attribute integrity:
|
|
|
|
```javascript
|
|
class AttributeValidator {
|
|
constructor(rules) {
|
|
this.rules = rules;
|
|
}
|
|
|
|
validate(note, attrName, value) {
|
|
const rule = this.rules[attrName];
|
|
if (!rule) return true;
|
|
|
|
if (rule.type === 'number') {
|
|
return !isNaN(value);
|
|
}
|
|
|
|
if (rule.pattern) {
|
|
return new RegExp(rule.pattern).test(value);
|
|
}
|
|
|
|
if (rule.enum) {
|
|
return rule.enum.includes(value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const validator = new AttributeValidator({
|
|
email: {pattern: '^[^@]+@[^@]+\\.[^@]+$'},
|
|
priority: {enum: ['low', 'medium', 'high']},
|
|
score: {type: 'number'}
|
|
});
|
|
```
|
|
|
|
### Attribute Indexing
|
|
|
|
Optimize attribute queries:
|
|
|
|
```javascript
|
|
// Build attribute index
|
|
class AttributeIndex {
|
|
constructor() {
|
|
this.index = new Map();
|
|
}
|
|
|
|
buildIndex(attributeName) {
|
|
const notes = api.searchForNotes(`#${attributeName}`);
|
|
const valueMap = new Map();
|
|
|
|
notes.forEach(note => {
|
|
const value = note.getLabelValue(attributeName);
|
|
if (!valueMap.has(value)) {
|
|
valueMap.set(value, []);
|
|
}
|
|
valueMap.get(value).push(note.noteId);
|
|
});
|
|
|
|
this.index.set(attributeName, valueMap);
|
|
}
|
|
|
|
findByAttributeValue(attributeName, value) {
|
|
const valueMap = this.index.get(attributeName);
|
|
return valueMap ? valueMap.get(value) || [] : [];
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Optimization
|
|
|
|
### Efficient Attribute Queries
|
|
|
|
```javascript
|
|
// Batch attribute operations
|
|
api.runAsTransaction(() => {
|
|
notes.forEach(note => {
|
|
// Multiple attribute changes in single transaction
|
|
note.setLabel('status', 'processed');
|
|
note.setLabel('processedDate', Date.now());
|
|
note.save();
|
|
});
|
|
});
|
|
```
|
|
|
|
### Attribute Caching
|
|
|
|
```javascript
|
|
// Cache frequently accessed attributes
|
|
const attributeCache = new Map();
|
|
|
|
function getCachedAttribute(noteId, attrName) {
|
|
const key = `${noteId}:${attrName}`;
|
|
|
|
if (!attributeCache.has(key)) {
|
|
const note = api.getNote(noteId);
|
|
const value = note.getLabelValue(attrName);
|
|
attributeCache.set(key, value);
|
|
}
|
|
|
|
return attributeCache.get(key);
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Inheritance Not Working
|
|
**Symptom:** Child notes not receiving inheritable attributes.
|
|
|
|
**Solutions:**
|
|
- Verify attribute is marked as inheritable
|
|
- Check for #noInherit blockers
|
|
- Ensure note hierarchy is correct
|
|
- Clear attribute cache
|
|
|
|
### Computed Attributes Not Updating
|
|
**Symptom:** Formula attributes showing stale values.
|
|
|
|
**Solutions:**
|
|
- Check formula syntax for errors
|
|
- Verify dependencies are tracked
|
|
- Manually trigger recalculation
|
|
- Review event handlers
|
|
|
|
### Attribute Conflicts
|
|
**Symptom:** Multiple inheritance sources causing conflicts.
|
|
|
|
**Solutions:**
|
|
- Define clear inheritance priorities
|
|
- Use explicit overrides
|
|
- Implement conflict resolution logic
|
|
- Document inheritance hierarchy
|
|
|
|
## Best Practices
|
|
|
|
1. **Design Inheritance Hierarchy**
|
|
- Plan attribute inheritance paths
|
|
- Use templates for consistency
|
|
- Document inheritance rules
|
|
|
|
2. **Optimize Attribute Access**
|
|
- Cache frequently used attributes
|
|
- Batch attribute operations
|
|
- Use indexes for large datasets
|
|
|
|
3. **Validate Attribute Data**
|
|
- Implement validation rules
|
|
- Use type checking
|
|
- Handle edge cases
|
|
|
|
4. **Monitor Performance**
|
|
- Profile attribute queries
|
|
- Track inheritance depth
|
|
- Optimize complex formulas
|
|
|
|
5. **Maintain Documentation**
|
|
- Document custom attributes
|
|
- Record validation rules
|
|
- Track attribute dependencies
|
|
|
|
## Related Topics
|
|
|
|
- [Basic Attributes](../Attributes/Labels-and-Relations.md)
|
|
- [Promoted Attributes](../Attributes/Promoted-Attributes.md)
|
|
- [Template System](../Templates.md)
|
|
- [Script API](../../Script-API/Attributes.md) |