feat: allows duplicate names in tags folder & smart tags (#792)
* feat: tag rendering & validation uses hierarchy * feat: add prefix to autocomplete
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { SNNote, ContentType, SNTag, UuidString } from '@standardnotes/snjs';
|
||||
import { ContentType, SNNote, SNTag, UuidString } from '@standardnotes/snjs';
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
import { WebApplication } from '../application';
|
||||
import { AppState } from './app_state';
|
||||
@@ -194,4 +194,41 @@ export class NoteTagsState {
|
||||
this.reloadTags();
|
||||
}
|
||||
}
|
||||
|
||||
getSortedTagsForNote(note: SNNote): SNTag[] {
|
||||
const tags = this.application.getSortedTagsForNote(note);
|
||||
|
||||
const sortFunction = (tagA: SNTag, tagB: SNTag): number => {
|
||||
const a = this.getLongTitle(tagA);
|
||||
const b = this.getLongTitle(tagB);
|
||||
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (b > a) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
return tags.sort(sortFunction);
|
||||
}
|
||||
|
||||
getPrefixTitle(tag: SNTag): string | undefined {
|
||||
const hierarchy = this.application.getTagParentChain(tag);
|
||||
|
||||
if (hierarchy.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const prefixTitle = hierarchy.map((tag) => tag.title).join('/');
|
||||
return `${prefixTitle}/`;
|
||||
}
|
||||
|
||||
getLongTitle(tag: SNTag): string {
|
||||
const hierarchy = this.application.getTagParentChain(tag);
|
||||
const tags = [...hierarchy, tag];
|
||||
const longTitle = tags.map((tag) => tag.title).join('/');
|
||||
return longTitle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ComponentAction,
|
||||
ContentType,
|
||||
MessageData,
|
||||
SNApplication,
|
||||
SNSmartTag,
|
||||
SNTag,
|
||||
TagMutator,
|
||||
@@ -22,6 +23,48 @@ import { FeaturesState, SMART_TAGS_FEATURE_NAME } from './features_state';
|
||||
|
||||
type AnyTag = SNTag | SNSmartTag;
|
||||
|
||||
const rootTags = (application: SNApplication): SNTag[] => {
|
||||
const hasNoParent = (tag: SNTag) => !application.getTagParent(tag);
|
||||
|
||||
const allTags = application.getDisplayableItems(ContentType.Tag) as SNTag[];
|
||||
const rootTags = allTags.filter(hasNoParent);
|
||||
|
||||
return rootTags;
|
||||
};
|
||||
|
||||
const tagSiblings = (application: SNApplication, tag: SNTag): SNTag[] => {
|
||||
const withoutCurrentTag = (tags: SNTag[]) =>
|
||||
tags.filter((other) => other.uuid !== tag.uuid);
|
||||
|
||||
const isTemplateTag = application.isTemplateItem(tag);
|
||||
const parentTag = !isTemplateTag && application.getTagParent(tag);
|
||||
|
||||
if (parentTag) {
|
||||
const siblingsAndTag = application.getTagChildren(parentTag);
|
||||
return withoutCurrentTag(siblingsAndTag);
|
||||
}
|
||||
|
||||
return withoutCurrentTag(rootTags(application));
|
||||
};
|
||||
|
||||
const isValidFutureSiblings = (
|
||||
application: SNApplication,
|
||||
futureSiblings: SNTag[],
|
||||
tag: SNTag
|
||||
): boolean => {
|
||||
const siblingWithSameName = futureSiblings.find(
|
||||
(otherTag) => otherTag.title === tag.title
|
||||
);
|
||||
|
||||
if (siblingWithSameName) {
|
||||
application.alertService?.alert(
|
||||
`A tag with the name ${tag.title} already exists at this destination. Please rename this tag before moving and try again.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export class TagsState {
|
||||
tags: SNTag[] = [];
|
||||
smartTags: SNSmartTag[] = [];
|
||||
@@ -144,12 +187,27 @@ export class TagsState {
|
||||
): Promise<void> {
|
||||
const tag = this.application.findItem(tagUuid) as SNTag;
|
||||
|
||||
const currentParent = this.application.getTagParent(tag);
|
||||
const currentParentUuid = currentParent?.parentId;
|
||||
|
||||
if (currentParentUuid === parentUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent =
|
||||
parentUuid && (this.application.findItem(parentUuid) as SNTag);
|
||||
|
||||
if (!parent) {
|
||||
const futureSiblings = rootTags(this.application);
|
||||
if (!isValidFutureSiblings(this.application, futureSiblings, tag)) {
|
||||
return;
|
||||
}
|
||||
await this.application.unsetTagParent(tag);
|
||||
} else {
|
||||
const futureSiblings = this.application.getTagChildren(parent);
|
||||
if (!isValidFutureSiblings(this.application, futureSiblings, tag)) {
|
||||
return;
|
||||
}
|
||||
await this.application.setTagParent(parent, tag);
|
||||
}
|
||||
|
||||
@@ -249,7 +307,11 @@ export class TagsState {
|
||||
const hasEmptyTitle = newTitle.length === 0;
|
||||
const hasNotChangedTitle = newTitle === tag.title;
|
||||
const isTemplateChange = this.application.isTemplateItem(tag);
|
||||
const hasDuplicatedTitle = !!this.application.findTagByTitle(newTitle);
|
||||
|
||||
const siblings = tagSiblings(this.application, tag);
|
||||
const hasDuplicatedTitle = siblings.some(
|
||||
(other) => other.title.toLowerCase() === newTitle.toLowerCase()
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
this.editing_ = undefined;
|
||||
|
||||
Reference in New Issue
Block a user