added "inherit" relation, #3493

This commit is contained in:
zadam 2023-01-06 20:31:55 +01:00
parent a863da1dce
commit 8a641e1b4f
21 changed files with 49 additions and 41 deletions

View file

@ -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',

View file

@ -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();

View file

@ -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();

View file

@ -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

View file

@ -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) {

View file

@ -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;
}

View file

@ -83,6 +83,7 @@ const HIDDEN_ATTRIBUTES = [
'originalFileName',
'fileSize',
'template',
'inherit',
'cssClass',
'iconClass',
'pageSize',

View file

@ -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) {

View file

@ -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);

View file

@ -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.",

View file

@ -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) {

View file

@ -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);
}

View file

@ -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)) {

View file

@ -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);
}
}

View file

@ -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' },

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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} */

View file

@ -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[]} */