mirror of
https://github.com/zadam/trilium.git
synced 2025-01-15 19:51:57 +08:00
added "inherit" relation, #3493
This commit is contained in:
parent
a863da1dce
commit
8a641e1b4f
21 changed files with 49 additions and 41 deletions
|
@ -5,8 +5,8 @@ UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
|
|||
UPDATE note_revisions SET title = 'title';
|
||||
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
|
||||
|
||||
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
|
||||
UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentId', 'documentSecret', 'encryptedDataKey',
|
||||
|
|
|
@ -14,17 +14,17 @@ class Becca {
|
|||
reset() {
|
||||
/** @type {Object.<String, BNote>} */
|
||||
this.notes = {};
|
||||
/** @type {Object.<String, Branch>} */
|
||||
/** @type {Object.<String, BBranch>} */
|
||||
this.branches = {};
|
||||
/** @type {Object.<String, Branch>} */
|
||||
/** @type {Object.<String, BBranch>} */
|
||||
this.childParentToBranch = {};
|
||||
/** @type {Object.<String, Attribute>} */
|
||||
/** @type {Object.<String, BAttribute>} */
|
||||
this.attributes = {};
|
||||
/** @type {Object.<String, Attribute[]>} Points from attribute type-name to list of attributes */
|
||||
/** @type {Object.<String, BAttribute[]>} Points from attribute type-name to list of attributes */
|
||||
this.attributeIndex = {};
|
||||
/** @type {Object.<String, Option>} */
|
||||
/** @type {Object.<String, BOption>} */
|
||||
this.options = {};
|
||||
/** @type {Object.<String, EtapiToken>} */
|
||||
/** @type {Object.<String, BEtapiToken>} */
|
||||
this.etapiTokens = {};
|
||||
|
||||
this.dirtyNoteSetCache();
|
||||
|
|
|
@ -187,7 +187,7 @@ function attributeDeleted(attributeId) {
|
|||
|
||||
if (note) {
|
||||
// first invalidate and only then remove the attribute (otherwise invalidation wouldn't be complete)
|
||||
if (attribute.isAffectingSubtree || note.isTemplate()) {
|
||||
if (attribute.isAffectingSubtree || note.isInherited()) {
|
||||
note.invalidateSubTree();
|
||||
} else {
|
||||
note.invalidateThisCache();
|
||||
|
@ -215,7 +215,7 @@ function attributeUpdated(attribute) {
|
|||
const note = becca.notes[attribute.noteId];
|
||||
|
||||
if (note) {
|
||||
if (attribute.isAffectingSubtree || note.isTemplate()) {
|
||||
if (attribute.isAffectingSubtree || note.isInherited()) {
|
||||
note.invalidateSubTree();
|
||||
} else {
|
||||
note.invalidateThisCache();
|
||||
|
|
|
@ -102,7 +102,7 @@ class BAttribute extends AbstractBeccaEntity {
|
|||
|
||||
get isAffectingSubtree() {
|
||||
return this.isInheritable
|
||||
|| (this.type === 'relation' && this.name === 'template');
|
||||
|| (this.type === 'relation' && ['template', 'inherit'].includes(this.name));
|
||||
}
|
||||
|
||||
get targetNoteId() { // alias
|
||||
|
|
|
@ -410,7 +410,7 @@ class BNote extends AbstractBeccaEntity {
|
|||
const templateAttributes = [];
|
||||
|
||||
for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates
|
||||
if (ownedAttr.type === 'relation' && ownedAttr.name === 'template') {
|
||||
if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) {
|
||||
const templateNote = this.becca.notes[ownedAttr.value];
|
||||
|
||||
if (templateNote) {
|
||||
|
@ -805,7 +805,7 @@ class BNote extends AbstractBeccaEntity {
|
|||
}
|
||||
|
||||
for (const targetRelation of this.targetRelations) {
|
||||
if (targetRelation.name === 'template') {
|
||||
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
|
||||
const note = targetRelation.note;
|
||||
|
||||
if (note) {
|
||||
|
@ -823,7 +823,7 @@ class BNote extends AbstractBeccaEntity {
|
|||
}
|
||||
|
||||
for (const targetRelation of this.targetRelations) {
|
||||
if (targetRelation.name === 'template') {
|
||||
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
|
||||
const note = targetRelation.note;
|
||||
|
||||
if (note) {
|
||||
|
@ -843,8 +843,8 @@ class BNote extends AbstractBeccaEntity {
|
|||
.filter(l => l.name.startsWith("relation:"));
|
||||
}
|
||||
|
||||
isTemplate() {
|
||||
return !!this.targetRelations.find(rel => rel.name === 'template');
|
||||
isInherited() {
|
||||
return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit');
|
||||
}
|
||||
|
||||
/** @returns {BNote[]} */
|
||||
|
@ -863,7 +863,7 @@ class BNote extends AbstractBeccaEntity {
|
|||
}
|
||||
|
||||
for (const targetRelation of note.targetRelations) {
|
||||
if (targetRelation.name === 'template') {
|
||||
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
|
||||
const targetNote = targetRelation.note;
|
||||
|
||||
if (targetNote) {
|
||||
|
@ -1067,11 +1067,11 @@ class BNote extends AbstractBeccaEntity {
|
|||
|
||||
/** @returns {BNote[]} - returns only notes which are templated, does not include their subtrees
|
||||
* in effect returns notes which are influenced by note's non-inheritable attributes */
|
||||
getTemplatedNotes() {
|
||||
getInheritingNotes() {
|
||||
const arr = [this];
|
||||
|
||||
for (const targetRelation of this.targetRelations) {
|
||||
if (targetRelation.name === 'template') {
|
||||
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
|
||||
const note = targetRelation.note;
|
||||
|
||||
if (note) {
|
||||
|
|
|
@ -259,7 +259,7 @@ class FNote {
|
|||
}
|
||||
}
|
||||
|
||||
for (const templateAttr of attrArrs.flat().filter(attr => attr.type === 'relation' && attr.name === 'template')) {
|
||||
for (const templateAttr of attrArrs.flat().filter(attr => attr.type === 'relation' && ['template', 'inherit'].includes(attr.name))) {
|
||||
const templateNote = this.froca.notes[templateAttr.value];
|
||||
|
||||
if (templateNote && templateNote.noteId !== this.noteId) {
|
||||
|
@ -651,8 +651,11 @@ class FNote {
|
|||
/**
|
||||
* @returns {FNote[]}
|
||||
*/
|
||||
getTemplateNotes() {
|
||||
const relations = this.getRelations('template');
|
||||
getNotesToInheritAttributesFrom() {
|
||||
const relations = [
|
||||
...this.getRelations('template'),
|
||||
...this.getRelations('inherit')
|
||||
];
|
||||
|
||||
return relations.map(rel => this.froca.notes[rel.value]);
|
||||
}
|
||||
|
@ -690,7 +693,7 @@ class FNote {
|
|||
|
||||
visitedNoteIds.add(this.noteId);
|
||||
|
||||
for (const templateNote of this.getTemplateNotes()) {
|
||||
for (const templateNote of this.getNotesToInheritAttributesFrom()) {
|
||||
if (templateNote.hasAncestor(ancestorNoteId, visitedNoteIds)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ const HIDDEN_ATTRIBUTES = [
|
|||
'originalFileName',
|
||||
'fileSize',
|
||||
'template',
|
||||
'inherit',
|
||||
'cssClass',
|
||||
'iconClass',
|
||||
'pageSize',
|
||||
|
|
|
@ -40,7 +40,7 @@ function isAffecting(attrRow, affectedNote) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const owningNotes = [affectedNote, ...affectedNote.getTemplateNotes()];
|
||||
const owningNotes = [affectedNote, ...affectedNote.getNotesToInheritAttributesFrom()];
|
||||
|
||||
for (const owningNote of owningNotes) {
|
||||
if (owningNote.noteId === attrNote.noteId) {
|
||||
|
|
|
@ -60,7 +60,7 @@ async function processEntityChanges(entityChanges) {
|
|||
}
|
||||
else if (entityName === 'attributes'
|
||||
&& entity.type === 'relation'
|
||||
&& entity.name === 'template'
|
||||
&& (entity.name === 'template' || entity.name === 'inherit')
|
||||
&& !(entity.value in froca.notes)) {
|
||||
|
||||
missingNoteIds.push(entity.value);
|
||||
|
|
|
@ -254,7 +254,8 @@ const ATTR_HELP = {
|
|||
"runOnBranchDeletion": "executes when a branch is deleted. Branch is a link between parent note and child note and is deleted e.g. when moving note (old branch/link is deleted).",
|
||||
"runOnAttributeCreation": "executes when new attribute is created for the note which defines this relation",
|
||||
"runOnAttributeChange": " executes when the attribute is changed of a note which defines this relation. This is triggered also when the attribute is deleted",
|
||||
"template": "attached note's attributes will be inherited even without parent-child relationship. See template for details.",
|
||||
"template": "note's attributes will be inherited even without a parent-child relationship, note's content and subtree will be added to instance notes if empty. See documentation for details.",
|
||||
"inherit": "note's attributes will be inherited even without a parent-child relationship. See template relation for a similar concept. See attribute inheritance in the documentation.",
|
||||
"renderNote": 'notes of type "render HTML note" will be rendered using a code note (HTML or script) and it is necessary to point using this relation to which note should be rendered',
|
||||
"widget": "target of this relation will be executed and rendered as a widget in the sidebar",
|
||||
"shareCss": "CSS note which will be injected into the share page. CSS note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree' and 'shareOmitDefaultCss' as well.",
|
||||
|
|
|
@ -316,7 +316,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
|||
|
||||
const relation = attrs.find(attr =>
|
||||
attr.type === 'relation'
|
||||
&& ['template', 'renderNote'].includes(attr.name)
|
||||
&& ['template', 'inherit', 'renderNote'].includes(attr.name)
|
||||
&& attributeService.isAffecting(attr, this.note));
|
||||
|
||||
if (label || relation) {
|
||||
|
|
|
@ -1117,7 +1117,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (ecAttr.type === 'relation' && ecAttr.name === 'template') {
|
||||
else if (ecAttr.type === 'relation' && (ecAttr.name === 'template' || ecAttr.name === 'inherit')) {
|
||||
// missing handling of things inherited from template
|
||||
noteIdsToReload.add(ecAttr.noteId);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ function getNeighbors(note, depth) {
|
|||
const retNoteIds = [];
|
||||
|
||||
function isIgnoredRelation(relation) {
|
||||
return ['relationMapLink', 'template', 'image', 'ancestor'].includes(relation.name);
|
||||
return ['relationMapLink', 'template', 'inherit', 'image', 'ancestor'].includes(relation.name);
|
||||
}
|
||||
|
||||
// forward links
|
||||
|
@ -126,7 +126,7 @@ function getLinkMap(req) {
|
|||
});
|
||||
|
||||
const links = Object.values(becca.attributes).filter(rel => {
|
||||
if (rel.type !== 'relation' || rel.name === 'relationMapLink' || rel.name === 'template') {
|
||||
if (rel.type !== 'relation' || rel.name === 'relationMapLink' || rel.name === 'template' || rel.name === 'inherit') {
|
||||
return false;
|
||||
}
|
||||
else if (!noteIds.has(rel.noteId) || !noteIds.has(rel.value)) {
|
||||
|
|
|
@ -32,7 +32,7 @@ function getNotesAndBranchesAndAttributes(noteIds) {
|
|||
for (const attr of note.ownedAttributes) {
|
||||
collectedAttributeIds.add(attr.attributeId);
|
||||
|
||||
if (attr.type === 'relation' && attr.name === 'template' && attr.targetNote) {
|
||||
if (attr.type === 'relation' && ['template', 'inherit'].includes(attr.name) && attr.targetNote) {
|
||||
collectEntityIds(attr.targetNote);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ module.exports = [
|
|||
{ type: 'relation', name: 'runOnAttributeCreation', isDangerous: true },
|
||||
{ type: 'relation', name: 'runOnAttributeChange', isDangerous: true },
|
||||
{ type: 'relation', name: 'template' },
|
||||
{ type: 'relation', name: 'inherit' },
|
||||
{ type: 'relation', name: 'widget', isDangerous: true },
|
||||
{ type: 'relation', name: 'renderNote', isDangerous: true },
|
||||
{ type: 'relation', name: 'shareCss' },
|
||||
|
|
|
@ -197,6 +197,8 @@ function createNewNote(params) {
|
|||
if (!note.hasOwnedRelation('template', params.templateNoteId)) {
|
||||
note.addRelation('template', params.templateNoteId);
|
||||
}
|
||||
|
||||
// no special handling for ~inherit since it doesn't matter if it's assigned with the note creation or later
|
||||
}
|
||||
|
||||
triggerNoteTitleChanged(note);
|
||||
|
|
|
@ -26,10 +26,10 @@ class AttributeExistsExp extends Expression {
|
|||
if (attr.isInheritable) {
|
||||
resultNoteSet.addAll(note.getSubtreeNotesIncludingTemplated());
|
||||
}
|
||||
else if (note.isTemplate() &&
|
||||
else if (note.isInherited() &&
|
||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
||||
!(this.attributeType === 'label' && (this.attributeName === 'template' || this.attributeName === 'workspacetemplate'))) {
|
||||
resultNoteSet.addAll(note.getTemplatedNotes());
|
||||
resultNoteSet.addAll(note.getInheritingNotes());
|
||||
}
|
||||
else {
|
||||
resultNoteSet.add(note);
|
||||
|
|
|
@ -25,8 +25,8 @@ class LabelComparisonExp extends Expression {
|
|||
if (attr.isInheritable) {
|
||||
resultNoteSet.addAll(note.getSubtreeNotesIncludingTemplated());
|
||||
}
|
||||
else if (note.isTemplate()) {
|
||||
resultNoteSet.addAll(note.getTemplatedNotes());
|
||||
else if (note.isInherited()) {
|
||||
resultNoteSet.addAll(note.getInheritingNotes());
|
||||
}
|
||||
else {
|
||||
resultNoteSet.add(note);
|
||||
|
|
|
@ -25,8 +25,8 @@ class RelationWhereExp extends Expression {
|
|||
if (subResNoteSet.hasNote(attr.targetNote)) {
|
||||
if (attr.isInheritable) {
|
||||
candidateNoteSet.addAll(note.getSubtreeNotesIncludingTemplated());
|
||||
} else if (note.isTemplate()) {
|
||||
candidateNoteSet.addAll(note.getTemplatedNotes());
|
||||
} else if (note.isInherited()) {
|
||||
candidateNoteSet.addAll(note.getInheritingNotes());
|
||||
} else {
|
||||
candidateNoteSet.add(note);
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ class SAttribute extends AbstractShacaEntity {
|
|||
/** @returns {boolean} */
|
||||
get isAffectingSubtree() {
|
||||
return this.isInheritable
|
||||
|| (this.type === 'relation' && this.name === 'template');
|
||||
|| (this.type === 'relation' && ['template', 'inherit'].includes(this.name));
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
|
|
|
@ -167,7 +167,7 @@ class SNote extends AbstractShacaEntity {
|
|||
const templateAttributes = [];
|
||||
|
||||
for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates
|
||||
if (ownedAttr.type === 'relation' && ownedAttr.name === 'template') {
|
||||
if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) {
|
||||
const templateNote = this.shaca.notes[ownedAttr.value];
|
||||
|
||||
if (templateNote) {
|
||||
|
@ -434,8 +434,8 @@ class SNote extends AbstractShacaEntity {
|
|||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isTemplate() {
|
||||
return !!this.targetRelations.find(rel => rel.name === 'template');
|
||||
isInherited() {
|
||||
return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit');
|
||||
}
|
||||
|
||||
/** @returns {SAttribute[]} */
|
||||
|
|
Loading…
Reference in a new issue