1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-06-15 23:00:36 +02:00

All: Added support for hierarchical/nested tags (#2572)

The implementation uses / symbol as a nesting separator. I.e. tag/subtag is a nested tag, where tag is the parent tag and subtag is its child. Creating a tag named tag/subtag/subsubtag creates three tags, one for each level. The tags are associated using parent_id field.

In the app, viewing notes with a tag will also show all notes that are associated with any of the tag's descendant tags (same for the note count). Deleting a tag will also delete all its descendant tags.

In the desktop app the tags are shown nested just like the notebooks.
This commit is contained in:
Vaidotas Šimkus
2020-07-12 18:09:07 +01:00
committed by GitHub
parent 073bd80f89
commit e11e57f1d8
31 changed files with 991 additions and 191 deletions

View File

@ -1,39 +1,55 @@
const Folder = require('lib/models/Folder');
const BaseItem = require('lib/models/BaseItem');
const BaseModel = require('lib/BaseModel');
const shared = {};
function folderHasChildren_(folders, folderId) {
for (let i = 0; i < folders.length; i++) {
const folder = folders[i];
if (folder.parent_id === folderId) return true;
function itemHasChildren_(items, itemId) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.parent_id === itemId) return true;
}
return false;
}
function folderIsVisible(folders, folderId, collapsedFolderIds) {
if (!collapsedFolderIds || !collapsedFolderIds.length) return true;
function itemIsVisible(items, itemId, collapsedItemIds) {
if (!collapsedItemIds || !collapsedItemIds.length) return true;
while (true) {
const folder = BaseModel.byId(folders, folderId);
if (!folder) throw new Error(`No folder with id ${folder.id}`);
if (!folder.parent_id) return true;
if (collapsedFolderIds.indexOf(folder.parent_id) >= 0) return false;
folderId = folder.parent_id;
const item = BaseModel.byId(items, itemId);
if (!item) throw new Error(`No item with id ${itemId}`);
if (!item.parent_id) return true;
if (collapsedItemIds.indexOf(item.parent_id) >= 0) return false;
itemId = item.parent_id;
}
}
function renderFoldersRecursive_(props, renderItem, items, parentId, depth, order) {
const folders = props.folders;
for (let i = 0; i < folders.length; i++) {
const folder = folders[i];
if (!Folder.idsEqual(folder.parent_id, parentId)) continue;
if (!folderIsVisible(props.folders, folder.id, props.collapsedFolderIds)) continue;
const hasChildren = folderHasChildren_(folders, folder.id);
order.push(folder.id);
items.push(renderItem(folder, props.selectedFolderId == folder.id && props.notesParentType == 'Folder', hasChildren, depth));
function renderItemsRecursive_(props, renderItem, items, parentId, depth, order, itemType) {
let itemsKey = '';
let notesParentType = '';
let collapsedItemsKey = '';
let selectedItemKey = '';
if (itemType === BaseModel.TYPE_FOLDER) {
itemsKey = 'folders';
notesParentType = 'Folder';
collapsedItemsKey = 'collapsedFolderIds';
selectedItemKey = 'selectedFolderId';
} else if (itemType === BaseModel.TYPE_TAG) {
itemsKey = 'tags';
notesParentType = 'Tag';
collapsedItemsKey = 'collapsedTagIds';
selectedItemKey = 'selectedTagId';
}
const propItems = props[itemsKey];
for (let i = 0; i < propItems.length; i++) {
const item = propItems[i];
if (!BaseItem.getClassByItemType(itemType).idsEqual(item.parent_id, parentId)) continue;
if (!itemIsVisible(props[itemsKey], item.id, props[collapsedItemsKey])) continue;
const hasChildren = itemHasChildren_(propItems, item.id);
order.push(item.id);
items.push(renderItem(item, props[selectedItemKey] == item.id && props.notesParentType == notesParentType, hasChildren, depth));
if (hasChildren) {
const result = renderFoldersRecursive_(props, renderItem, items, folder.id, depth + 1, order);
const result = renderItemsRecursive_(props, renderItem, items, item.id, depth + 1, order, itemType);
items = result.items;
order = result.order;
}
@ -45,25 +61,11 @@ function renderFoldersRecursive_(props, renderItem, items, parentId, depth, orde
}
shared.renderFolders = function(props, renderItem) {
return renderFoldersRecursive_(props, renderItem, [], '', 0, []);
return renderItemsRecursive_(props, renderItem, [], '', 0, [], BaseModel.TYPE_FOLDER);
};
shared.renderTags = function(props, renderItem) {
const tags = props.tags.slice();
tags.sort((a, b) => {
return a.title < b.title ? -1 : +1;
});
const tagItems = [];
const order = [];
for (let i = 0; i < tags.length; i++) {
const tag = tags[i];
order.push(tag.id);
tagItems.push(renderItem(tag, props.selectedTagId == tag.id && props.notesParentType == 'Tag'));
}
return {
items: tagItems,
order: order,
};
return renderItemsRecursive_(props, renderItem, [], '', 0, [], BaseModel.TYPE_TAG);
};
// shared.renderSearches = function(props, renderItem) {