2024-01-09 18:22:06 +02:00
|
|
|
import Folder from '../../models/Folder';
|
|
|
|
import BaseModel from '../../BaseModel';
|
2024-04-25 16:31:18 +02:00
|
|
|
import { FolderEntity, TagEntity, TagsWithNoteCountEntity } from '../../services/database/types';
|
2024-03-02 16:25:27 +02:00
|
|
|
import { getDisplayParentId, getTrashFolderId } from '../../services/trash';
|
2024-03-20 13:17:46 +02:00
|
|
|
import { getCollator } from '../../models/utils/getCollator';
|
2018-05-06 13:11:59 +02:00
|
|
|
|
2024-04-25 16:31:18 +02:00
|
|
|
export type RenderFolderItem<T> = (folder: FolderEntity, hasChildren: boolean, depth: number)=> T;
|
|
|
|
export type RenderTagItem<T> = (tag: TagsWithNoteCountEntity)=> T;
|
2017-11-06 01:55:01 +02:00
|
|
|
|
2024-01-09 18:22:06 +02:00
|
|
|
function folderHasChildren_(folders: FolderEntity[], folderId: string) {
|
2024-03-02 16:25:27 +02:00
|
|
|
if (folderId === getTrashFolderId()) {
|
|
|
|
return !!folders.find(f => !!f.deleted_time);
|
|
|
|
}
|
|
|
|
|
2018-05-06 13:11:59 +02:00
|
|
|
for (let i = 0; i < folders.length; i++) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const folder = folders[i];
|
2024-03-02 16:25:27 +02:00
|
|
|
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
|
|
|
|
if (folderParentId === folderId) return true;
|
2018-05-06 13:11:59 +02:00
|
|
|
}
|
2024-03-02 16:25:27 +02:00
|
|
|
|
2018-05-06 13:11:59 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-02 16:25:27 +02:00
|
|
|
function folderIsCollapsed(folders: FolderEntity[], folderId: string, collapsedFolderIds: string[]) {
|
|
|
|
if (!collapsedFolderIds || !collapsedFolderIds.length) return false;
|
2018-05-09 11:49:31 +02:00
|
|
|
|
|
|
|
while (true) {
|
2024-03-02 16:25:27 +02:00
|
|
|
const folder: FolderEntity = BaseModel.byId(folders, folderId);
|
2019-09-19 23:51:18 +02:00
|
|
|
if (!folder) throw new Error(`No folder with id ${folder.id}`);
|
2024-03-02 16:25:27 +02:00
|
|
|
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
|
|
|
|
if (!folderParentId) return false;
|
|
|
|
if (collapsedFolderIds.indexOf(folderParentId) >= 0) return true;
|
|
|
|
folderId = folderParentId;
|
2018-05-09 11:49:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-25 16:31:18 +02:00
|
|
|
interface FolderSelectedContext {
|
|
|
|
selectedFolderId: string;
|
|
|
|
notesParentType: string;
|
|
|
|
}
|
|
|
|
export const isFolderSelected = (folder: FolderEntity, context: FolderSelectedContext) => {
|
|
|
|
return context.selectedFolderId === folder.id && context.notesParentType === 'Folder';
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type ItemsWithOrder<ItemType> = {
|
|
|
|
items: ItemType[];
|
|
|
|
order: string[];
|
|
|
|
};
|
|
|
|
|
|
|
|
interface RenderFoldersProps {
|
|
|
|
folders: FolderEntity[];
|
|
|
|
collapsedFolderIds: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderFoldersRecursive_<T>(props: RenderFoldersProps, renderItem: RenderFolderItem<T>, items: T[], parentId: string, depth: number, order: string[]): ItemsWithOrder<T> {
|
2018-05-06 13:11:59 +02:00
|
|
|
const folders = props.folders;
|
|
|
|
for (let i = 0; i < folders.length; i++) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const folder = folders[i];
|
2024-03-02 16:25:27 +02:00
|
|
|
|
|
|
|
const folderParentId = getDisplayParentId(folder, props.folders.find(f => f.id === folder.parent_id));
|
|
|
|
|
|
|
|
if (!Folder.idsEqual(folderParentId, parentId)) continue;
|
|
|
|
if (folderIsCollapsed(props.folders, folder.id, props.collapsedFolderIds)) continue;
|
2018-05-06 13:11:59 +02:00
|
|
|
const hasChildren = folderHasChildren_(folders, folder.id);
|
2019-01-26 20:04:32 +02:00
|
|
|
order.push(folder.id);
|
2024-04-25 16:31:18 +02:00
|
|
|
items.push(renderItem(folder, hasChildren, depth));
|
2019-01-26 20:04:32 +02:00
|
|
|
if (hasChildren) {
|
|
|
|
const result = renderFoldersRecursive_(props, renderItem, items, folder.id, depth + 1, order);
|
|
|
|
items = result.items;
|
|
|
|
order = result.order;
|
|
|
|
}
|
2017-11-06 01:55:01 +02:00
|
|
|
}
|
2019-01-26 20:04:32 +02:00
|
|
|
return {
|
|
|
|
items: items,
|
|
|
|
order: order,
|
|
|
|
};
|
2017-11-06 01:55:01 +02:00
|
|
|
}
|
|
|
|
|
2024-04-25 16:31:18 +02:00
|
|
|
export const renderFolders = <T> (props: RenderFoldersProps, renderItem: RenderFolderItem<T>): ItemsWithOrder<T> => {
|
2019-01-26 20:04:32 +02:00
|
|
|
return renderFoldersRecursive_(props, renderItem, [], '', 0, []);
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2018-05-06 13:11:59 +02:00
|
|
|
|
2024-04-25 16:31:18 +02:00
|
|
|
const sortTags = (tags: TagEntity[]) => {
|
|
|
|
tags = tags.slice();
|
2024-03-20 13:17:46 +02:00
|
|
|
const collator = getCollator();
|
2022-03-28 18:40:51 +02:00
|
|
|
tags.sort((a, b) => {
|
|
|
|
// It seems title can sometimes be undefined (perhaps when syncing
|
|
|
|
// and before tag has been decrypted?). It would be best to find
|
|
|
|
// the root cause but for now that will do.
|
|
|
|
//
|
|
|
|
// Fixes https://github.com/laurent22/joplin/issues/4051
|
|
|
|
if (!a || !a.title || !b || !b.title) return 0;
|
|
|
|
|
|
|
|
// Note: while newly created tags are normalized and lowercase
|
|
|
|
// imported tags might be any case, so we need to do case-insensitive
|
|
|
|
// sort.
|
2024-03-20 13:17:46 +02:00
|
|
|
return collator.compare(a.title, b.title);
|
2022-03-28 18:40:51 +02:00
|
|
|
});
|
2024-04-25 16:31:18 +02:00
|
|
|
return tags;
|
|
|
|
};
|
|
|
|
|
|
|
|
interface TagSelectedContext {
|
|
|
|
selectedTagId: string;
|
|
|
|
notesParentType: string;
|
|
|
|
}
|
|
|
|
export const isTagSelected = (tag: TagEntity, context: TagSelectedContext) => {
|
|
|
|
return context.selectedTagId === tag.id && context.notesParentType === 'Tag';
|
|
|
|
};
|
|
|
|
|
|
|
|
export const renderTags = <T> (unsortedTags: TagsWithNoteCountEntity[], renderItem: RenderTagItem<T>): ItemsWithOrder<T> => {
|
|
|
|
const tags = sortTags(unsortedTags);
|
2020-03-14 01:46:14 +02:00
|
|
|
const tagItems = [];
|
2024-03-02 16:25:27 +02:00
|
|
|
const order: string[] = [];
|
2017-11-06 01:55:01 +02:00
|
|
|
for (let i = 0; i < tags.length; i++) {
|
|
|
|
const tag = tags[i];
|
2019-01-26 20:04:32 +02:00
|
|
|
order.push(tag.id);
|
2024-04-25 16:31:18 +02:00
|
|
|
tagItems.push(renderItem(tag));
|
2017-11-06 01:55:01 +02:00
|
|
|
}
|
2019-01-26 20:04:32 +02:00
|
|
|
return {
|
|
|
|
items: tagItems,
|
|
|
|
order: order,
|
|
|
|
};
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|