feat: enable folders by default and remove from experimental features
This commit is contained in:
@@ -21,8 +21,6 @@ type Props = {
|
|||||||
export const Navigation: FunctionComponent<Props> = observer(
|
export const Navigation: FunctionComponent<Props> = observer(
|
||||||
({ application }) => {
|
({ application }) => {
|
||||||
const appState = useMemo(() => application.getAppState(), [application]);
|
const appState = useMemo(() => application.getAppState(), [application]);
|
||||||
const enableNativeSmartTagsFeature =
|
|
||||||
appState.features.enableNativeSmartTagsFeature;
|
|
||||||
const [ref, setRef] = useState<HTMLDivElement | null>();
|
const [ref, setRef] = useState<HTMLDivElement | null>();
|
||||||
const [panelWidth, setPanelWidth] = useState<number>(0);
|
const [panelWidth, setPanelWidth] = useState<number>(0);
|
||||||
|
|
||||||
@@ -39,10 +37,6 @@ export const Navigation: FunctionComponent<Props> = observer(
|
|||||||
};
|
};
|
||||||
}, [application]);
|
}, [application]);
|
||||||
|
|
||||||
const onCreateNewTag = useCallback(() => {
|
|
||||||
appState.tags.createNewTemplate();
|
|
||||||
}, [appState]);
|
|
||||||
|
|
||||||
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
||||||
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
||||||
application.setPreference(PrefKey.TagsPanelWidth, width);
|
application.setPreference(PrefKey.TagsPanelWidth, width);
|
||||||
@@ -70,17 +64,6 @@ export const Navigation: FunctionComponent<Props> = observer(
|
|||||||
<div className="sk-h3 title">
|
<div className="sk-h3 title">
|
||||||
<span className="sk-bold">Views</span>
|
<span className="sk-bold">Views</span>
|
||||||
</div>
|
</div>
|
||||||
{!enableNativeSmartTagsFeature && (
|
|
||||||
<div
|
|
||||||
className="sk-button sk-secondary-contrast wide"
|
|
||||||
onClick={onCreateNewTag}
|
|
||||||
title="Create a new tag"
|
|
||||||
>
|
|
||||||
<div className="sk-label">
|
|
||||||
<i className="icon ion-plus add-button" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="scrollable">
|
<div className="scrollable">
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export const NotesView: FunctionComponent<Props> = observer(
|
|||||||
>
|
>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div id="notes-title-bar" className="section-title-bar">
|
<div id="notes-title-bar" className="section-title-bar">
|
||||||
<div className="p-4">
|
<div id="notes-title-bar-container">
|
||||||
<div className="section-title-bar-header">
|
<div className="section-title-bar-header">
|
||||||
<div className="sk-h2 font-semibold title">{panelTitle}</div>
|
<div className="sk-h2 font-semibold title">{panelTitle}</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const PremiumFeaturesModal: FunctionalComponent<Props> = ({
|
|||||||
<PremiumIllustration className="mb-2" />
|
<PremiumIllustration className="mb-2" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-lg text-center font-bold mb-1">
|
<div className="text-lg text-center font-bold mb-1">
|
||||||
Enable premium features
|
Enable Premium Features
|
||||||
</div>
|
</div>
|
||||||
</AlertDialogLabel>
|
</AlertDialogLabel>
|
||||||
<AlertDialogDescription className="text-sm text-center color-grey-1 px-4.5 mb-2">
|
<AlertDialogDescription className="text-sm text-center color-grey-1 px-4.5 mb-2">
|
||||||
@@ -65,7 +65,7 @@ export const PremiumFeaturesModal: FunctionalComponent<Props> = ({
|
|||||||
className="w-full rounded no-border py-2 font-bold bg-info color-info-contrast hover:brightness-130 focus:brightness-130 cursor-pointer"
|
className="w-full rounded no-border py-2 font-bold bg-info color-info-contrast hover:brightness-130 focus:brightness-130 cursor-pointer"
|
||||||
ref={plansButtonRef}
|
ref={plansButtonRef}
|
||||||
>
|
>
|
||||||
See our plans
|
See Plans
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,45 +11,38 @@ type Props = {
|
|||||||
featuresState: FeaturesState;
|
featuresState: FeaturesState;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RootTagDropZone: React.FC<Props> = observer(
|
export const RootTagDropZone: React.FC<Props> = observer(({ tagsState }) => {
|
||||||
({ tagsState, featuresState }) => {
|
const premiumModal = usePremiumModal();
|
||||||
const premiumModal = usePremiumModal();
|
|
||||||
const isNativeFoldersEnabled = featuresState.enableNativeFoldersFeature;
|
|
||||||
|
|
||||||
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
|
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
|
||||||
() => ({
|
() => ({
|
||||||
accept: ItemTypes.TAG,
|
accept: ItemTypes.TAG,
|
||||||
canDrop: (item) => {
|
canDrop: (item) => {
|
||||||
return tagsState.hasParent(item.uuid);
|
return tagsState.hasParent(item.uuid);
|
||||||
},
|
},
|
||||||
drop: (item) => {
|
drop: (item) => {
|
||||||
tagsState.assignParent(item.uuid, undefined);
|
tagsState.assignParent(item.uuid, undefined);
|
||||||
},
|
},
|
||||||
collect: (monitor) => ({
|
collect: (monitor) => ({
|
||||||
isOver: !!monitor.isOver(),
|
isOver: !!monitor.isOver(),
|
||||||
canDrop: !!monitor.canDrop(),
|
canDrop: !!monitor.canDrop(),
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
[tagsState, premiumModal]
|
}),
|
||||||
);
|
[tagsState, premiumModal]
|
||||||
|
);
|
||||||
|
|
||||||
if (!isNativeFoldersEnabled) {
|
return (
|
||||||
return null;
|
<div
|
||||||
}
|
ref={dropRef}
|
||||||
|
className={`root-drop ${canDrop ? 'active' : ''} ${
|
||||||
return (
|
isOver ? 'is-drag-over' : ''
|
||||||
<div
|
}`}
|
||||||
ref={dropRef}
|
>
|
||||||
className={`root-drop ${canDrop ? 'active' : ''} ${
|
<Icon className="color-neutral" type="link-off" />
|
||||||
isOver ? 'is-drag-over' : ''
|
<p className="content">
|
||||||
}`}
|
Move the tag here to <br />
|
||||||
>
|
remove it from its folder.
|
||||||
<Icon className="color-neutral" type="link-off" />
|
</p>
|
||||||
<p className="content">
|
</div>
|
||||||
Move the tag here to <br />
|
);
|
||||||
remove it from its folder.
|
});
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const SmartTagsListItem: FunctionComponent<Props> = observer(
|
|||||||
const level = 0;
|
const level = 0;
|
||||||
const isSelected = tagsState.selected === tag;
|
const isSelected = tagsState.selected === tag;
|
||||||
const isEditing = tagsState.editingTag === tag;
|
const isEditing = tagsState.editingTag === tag;
|
||||||
const isSmartTagsEnabled = features.enableNativeSmartTagsFeature;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(tag.title || '');
|
setTitle(tag.title || '');
|
||||||
@@ -88,7 +87,7 @@ export const SmartTagsListItem: FunctionComponent<Props> = observer(
|
|||||||
tagsState.remove(tag);
|
tagsState.remove(tag);
|
||||||
}, [tagsState, tag]);
|
}, [tagsState, tag]);
|
||||||
|
|
||||||
const isFaded = !isSmartTagsEnabled && !tag.isAllTag;
|
const isFaded = !tag.isAllTag;
|
||||||
const iconType = smartTagIconType(tag);
|
const iconType = smartTagIconType(tag);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -104,14 +103,12 @@ export const SmartTagsListItem: FunctionComponent<Props> = observer(
|
|||||||
>
|
>
|
||||||
{!tag.errorDecrypting ? (
|
{!tag.errorDecrypting ? (
|
||||||
<div className="tag-info">
|
<div className="tag-info">
|
||||||
{isSmartTagsEnabled && (
|
<div className={`tag-icon mr-1`}>
|
||||||
<div className={`tag-icon mr-1`}>
|
<Icon
|
||||||
<Icon
|
type={iconType}
|
||||||
type={iconType}
|
className={`${isSelected ? 'color-info' : 'color-neutral'}`}
|
||||||
className={`${isSelected ? 'color-info' : 'color-neutral'}`}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<input
|
<input
|
||||||
className={`title ${isEditing ? 'editing' : ''}`}
|
className={`title ${isEditing ? 'editing' : ''}`}
|
||||||
id={`react-tag-${tag.uuid}`}
|
id={`react-tag-${tag.uuid}`}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const TagsList: FunctionComponent<Props> = observer(({ appState }) => {
|
|||||||
<DndProvider backend={backend}>
|
<DndProvider backend={backend}>
|
||||||
{allTags.length === 0 ? (
|
{allTags.length === 0 ? (
|
||||||
<div className="no-tags-placeholder">
|
<div className="no-tags-placeholder">
|
||||||
No tags. Create one using the add button above.
|
No tags or folders. Create one using the add button above.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const TagsListItem: FunctionComponent<Props> = observer(
|
|||||||
const hasChildren = childrenTags.length > 0;
|
const hasChildren = childrenTags.length > 0;
|
||||||
|
|
||||||
const hasFolders = features.hasFolders;
|
const hasFolders = features.hasFolders;
|
||||||
const isNativeFoldersEnabled = features.enableNativeFoldersFeature;
|
|
||||||
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder;
|
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder;
|
||||||
|
|
||||||
const premiumModal = usePremiumModal();
|
const premiumModal = usePremiumModal();
|
||||||
@@ -114,13 +113,13 @@ export const TagsListItem: FunctionComponent<Props> = observer(
|
|||||||
type: ItemTypes.TAG,
|
type: ItemTypes.TAG,
|
||||||
item: { uuid: tag.uuid },
|
item: { uuid: tag.uuid },
|
||||||
canDrag: () => {
|
canDrag: () => {
|
||||||
return isNativeFoldersEnabled;
|
return true;
|
||||||
},
|
},
|
||||||
collect: (monitor) => ({
|
collect: (monitor) => ({
|
||||||
isDragging: !!monitor.isDragging(),
|
isDragging: !!monitor.isDragging(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[tag, isNativeFoldersEnabled]
|
[tag]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
|
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
|
||||||
@@ -160,7 +159,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
|
|||||||
>
|
>
|
||||||
{!tag.errorDecrypting ? (
|
{!tag.errorDecrypting ? (
|
||||||
<div className="tag-info" title={title} ref={dropRef}>
|
<div className="tag-info" title={title} ref={dropRef}>
|
||||||
{isNativeFoldersEnabled && hasAtLeastOneFolder && (
|
{hasAtLeastOneFolder && (
|
||||||
<div
|
<div
|
||||||
className={`tag-fold ${showChildren ? 'opened' : 'closed'}`}
|
className={`tag-fold ${showChildren ? 'opened' : 'closed'}`}
|
||||||
onClick={hasChildren ? toggleChildren : undefined}
|
onClick={hasChildren ? toggleChildren : undefined}
|
||||||
@@ -173,12 +172,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div className={`tag-icon ${'draggable'} mr-1`} ref={dragRef}>
|
||||||
className={`tag-icon ${
|
|
||||||
isNativeFoldersEnabled ? 'draggable' : ''
|
|
||||||
} mr-1`}
|
|
||||||
ref={dragRef}
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
type="hashtag"
|
type="hashtag"
|
||||||
className={`${isSelected ? 'color-info' : 'color-neutral'}`}
|
className={`${isSelected ? 'color-info' : 'color-neutral'}`}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { TagsList } from '@/components/Tags/TagsList';
|
import { TagsList } from '@/components/Tags/TagsList';
|
||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
import { ApplicationEvent } from '@/__mocks__/@standardnotes/snjs';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { useCallback, useEffect, useState } from 'preact/hooks';
|
||||||
import { TagsSectionAddButton } from './TagsSectionAddButton';
|
import { TagsSectionAddButton } from './TagsSectionAddButton';
|
||||||
import { TagsSectionTitle } from './TagsSectionTitle';
|
import { TagsSectionTitle } from './TagsSectionTitle';
|
||||||
|
|
||||||
@@ -11,11 +13,51 @@ type Props = {
|
|||||||
|
|
||||||
export const TagsSection: FunctionComponent<Props> = observer(
|
export const TagsSection: FunctionComponent<Props> = observer(
|
||||||
({ appState }) => {
|
({ appState }) => {
|
||||||
|
const [hasMigration, setHasMigration] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const checkIfMigrationNeeded = useCallback(() => {
|
||||||
|
setHasMigration(appState.application.hasTagsNeedingFoldersMigration());
|
||||||
|
}, [appState.application]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
appState.application.addEventObserver(async (event) => {
|
||||||
|
const events = [
|
||||||
|
ApplicationEvent.CompletedInitialSync,
|
||||||
|
ApplicationEvent.SignedIn,
|
||||||
|
];
|
||||||
|
if (events.includes(event)) {
|
||||||
|
checkIfMigrationNeeded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [appState.application, checkIfMigrationNeeded]);
|
||||||
|
|
||||||
|
const runMigration = useCallback(async () => {
|
||||||
|
if (
|
||||||
|
await appState.application.alertService.confirm(
|
||||||
|
'<i>Introducing native, built-in nested tags without requiring the legacy Folders component.</i><br/></br> ' +
|
||||||
|
" To get started, we'll need to migrate any tags containing a dot character to the new system.<br/></br> " +
|
||||||
|
' This migration will convert any tags with dots appearing in their name into a natural' +
|
||||||
|
' hierarchy that is compatible with the new nested tags feature.' +
|
||||||
|
' Running this migration will remove any "." characters appearing in tag names.',
|
||||||
|
'New: Folders to Nested Tags',
|
||||||
|
'Run Migration'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
appState.application.migrateTagsToFolders().then(() => {
|
||||||
|
checkIfMigrationNeeded();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [appState.application, checkIfMigrationNeeded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<div className="section-title-bar">
|
<div className="section-title-bar">
|
||||||
<div className="section-title-bar-header">
|
<div className="section-title-bar-header">
|
||||||
<TagsSectionTitle features={appState.features} />
|
<TagsSectionTitle
|
||||||
|
features={appState.features}
|
||||||
|
hasMigration={hasMigration}
|
||||||
|
onClickMigration={runMigration}
|
||||||
|
/>
|
||||||
<TagsSectionAddButton
|
<TagsSectionAddButton
|
||||||
tags={appState.tags}
|
tags={appState.tags}
|
||||||
features={appState.features}
|
features={appState.features}
|
||||||
|
|||||||
@@ -10,13 +10,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TagsSectionAddButton: FunctionComponent<Props> = observer(
|
export const TagsSectionAddButton: FunctionComponent<Props> = observer(
|
||||||
({ tags, features }) => {
|
({ tags }) => {
|
||||||
const isNativeFoldersEnabled = features.enableNativeFoldersFeature;
|
|
||||||
|
|
||||||
if (!isNativeFoldersEnabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
focusable={true}
|
focusable={true}
|
||||||
|
|||||||
@@ -11,33 +11,32 @@ import { useCallback } from 'preact/hooks';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
features: FeaturesState;
|
features: FeaturesState;
|
||||||
|
hasMigration: boolean;
|
||||||
|
onClickMigration: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TagsSectionTitle: FunctionComponent<Props> = observer(
|
export const TagsSectionTitle: FunctionComponent<Props> = observer(
|
||||||
({ features }) => {
|
({ features, hasMigration, onClickMigration }) => {
|
||||||
const isNativeFoldersEnabled = features.enableNativeFoldersFeature;
|
const entitledToFolders = features.hasFolders;
|
||||||
const hasFolders = features.hasFolders;
|
|
||||||
const modal = usePremiumModal();
|
const modal = usePremiumModal();
|
||||||
|
|
||||||
const showPremiumAlert = useCallback(() => {
|
const showPremiumAlert = useCallback(() => {
|
||||||
modal.activate(TAG_FOLDERS_FEATURE_NAME);
|
modal.activate(TAG_FOLDERS_FEATURE_NAME);
|
||||||
}, [modal]);
|
}, [modal]);
|
||||||
|
|
||||||
if (!isNativeFoldersEnabled) {
|
if (entitledToFolders) {
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="sk-h3 title">
|
|
||||||
<span className="sk-bold">Tags</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasFolders) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="sk-h3 title">
|
<div className="sk-h3 title">
|
||||||
<span className="sk-bold">Folders</span>
|
<span className="sk-bold">Folders</span>
|
||||||
|
{hasMigration && (
|
||||||
|
<label
|
||||||
|
className="ml-1 sk-bold color-info cursor-pointer"
|
||||||
|
onClick={onClickMigration}
|
||||||
|
>
|
||||||
|
Migration Available
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -52,7 +51,7 @@ export const TagsSectionTitle: FunctionComponent<Props> = observer(
|
|||||||
className="ml-1 sk-bold color-grey-2 cursor-pointer"
|
className="ml-1 sk-bold color-grey-2 cursor-pointer"
|
||||||
onClick={showPremiumAlert}
|
onClick={showPremiumAlert}
|
||||||
>
|
>
|
||||||
· Folders
|
Folders
|
||||||
</label>
|
</label>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ErrorReporting, Tools, Defaults } from './general-segments';
|
|||||||
import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
|
import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
|
||||||
import { Advanced } from '@/preferences/panes/account';
|
import { Advanced } from '@/preferences/panes/account';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { Migrations } from './general-segments/Migrations';
|
|
||||||
|
|
||||||
interface GeneralProps {
|
interface GeneralProps {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -20,7 +19,6 @@ export const General: FunctionComponent<GeneralProps> = observer(
|
|||||||
<Tools application={application} />
|
<Tools application={application} />
|
||||||
<Defaults application={application} />
|
<Defaults application={application} />
|
||||||
<ErrorReporting appState={appState} />
|
<ErrorReporting appState={appState} />
|
||||||
<Migrations application={application} appState={appState} />
|
|
||||||
<Advanced
|
<Advanced
|
||||||
application={application}
|
application={application}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
import { Button } from '@/components/Button';
|
|
||||||
import { Icon } from '@/components/Icon';
|
|
||||||
import { WebApplication } from '@/ui_models/application';
|
|
||||||
import { AppState } from '@/ui_models/app_state';
|
|
||||||
import { FunctionComponent } from 'preact';
|
|
||||||
import { useCallback, useState } from 'preact/hooks';
|
|
||||||
import {
|
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
Subtitle,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
} from '../../components';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
application: WebApplication;
|
|
||||||
appState: AppState;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CheckIcon: React.FC = () => {
|
|
||||||
return <Icon className="success min-w-4 min-h-4" type="check-bold" />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Migration3dot0dot0: FunctionComponent<Props> = ({ application }) => {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [complete, setComplete] = useState(false);
|
|
||||||
const [error, setError] = useState<unknown | null>(null);
|
|
||||||
|
|
||||||
const trigger = useCallback(() => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
setComplete(false);
|
|
||||||
application
|
|
||||||
.migrateTagDotsToHierarchy()
|
|
||||||
.then(() => {
|
|
||||||
setLoading(false);
|
|
||||||
setError(null);
|
|
||||||
setComplete(true);
|
|
||||||
})
|
|
||||||
.catch((error: unknown) => {
|
|
||||||
setLoading(false);
|
|
||||||
setError(error);
|
|
||||||
setComplete(false);
|
|
||||||
});
|
|
||||||
}, [application, setLoading, setError]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Subtitle>(3.0.0) Folders Component to Native Folders</Subtitle>
|
|
||||||
<Text>
|
|
||||||
This migration transform tags with "." in their title into a hierarchy
|
|
||||||
of parents. This lets your transform tag hierarchies created with the
|
|
||||||
folder component into native tag folders.
|
|
||||||
</Text>
|
|
||||||
<div className="flex flex-row items-center mt-3">
|
|
||||||
<Button
|
|
||||||
type="normal"
|
|
||||||
onClick={trigger}
|
|
||||||
className="m-2"
|
|
||||||
disabled={loading}
|
|
||||||
label="Run Now"
|
|
||||||
/>
|
|
||||||
{complete && (
|
|
||||||
<div className="ml-3">
|
|
||||||
<Text>Migration successful.</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{error && (
|
|
||||||
<div className="ml-3">
|
|
||||||
<Text>Something wrong happened. Please contact support.</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Migrations: FunctionComponent<Props> = ({
|
|
||||||
application,
|
|
||||||
appState,
|
|
||||||
}) => {
|
|
||||||
const hasNativeFoldersEnabled = appState.features.enableNativeFoldersFeature;
|
|
||||||
|
|
||||||
if (!hasNativeFoldersEnabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasFoldersFeature = appState.features.hasFolders;
|
|
||||||
|
|
||||||
if (!hasFoldersFeature) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PreferencesGroup>
|
|
||||||
<PreferencesSegment>
|
|
||||||
<Title>Migrations</Title>
|
|
||||||
<div className="h-2 w-full" />
|
|
||||||
<Migration3dot0dot0 application={application} appState={appState} />
|
|
||||||
</PreferencesSegment>
|
|
||||||
</PreferencesGroup>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -42,8 +42,6 @@ export class FeaturesState {
|
|||||||
_hasFolders: observable,
|
_hasFolders: observable,
|
||||||
_hasSmartTags: observable,
|
_hasSmartTags: observable,
|
||||||
hasFolders: computed,
|
hasFolders: computed,
|
||||||
enableNativeFoldersFeature: computed,
|
|
||||||
enableNativeSmartTagsFeature: computed,
|
|
||||||
_premiumAlertFeatureName: observable,
|
_premiumAlertFeatureName: observable,
|
||||||
showPremiumAlert: action,
|
showPremiumAlert: action,
|
||||||
closePremiumAlert: action,
|
closePremiumAlert: action,
|
||||||
@@ -71,14 +69,6 @@ export class FeaturesState {
|
|||||||
this.unsub();
|
this.unsub();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get enableNativeFoldersFeature(): boolean {
|
|
||||||
return this.enableUnfinishedFeatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get enableNativeSmartTagsFeature(): boolean {
|
|
||||||
return this.enableUnfinishedFeatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get hasFolders(): boolean {
|
public get hasFolders(): boolean {
|
||||||
return this._hasFolders;
|
return this._hasFolders;
|
||||||
}
|
}
|
||||||
@@ -97,10 +87,6 @@ export class FeaturesState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private hasNativeFolders(): boolean {
|
private hasNativeFolders(): boolean {
|
||||||
if (!this.enableNativeFoldersFeature) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = this.application.getFeatureStatus(
|
const status = this.application.getFeatureStatus(
|
||||||
FeatureIdentifier.TagNesting
|
FeatureIdentifier.TagNesting
|
||||||
);
|
);
|
||||||
@@ -109,10 +95,6 @@ export class FeaturesState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private hasNativeSmartTags(): boolean {
|
private hasNativeSmartTags(): boolean {
|
||||||
if (!this.enableNativeSmartTagsFeature) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = this.application.getFeatureStatus(
|
const status = this.application.getFeatureStatus(
|
||||||
FeatureIdentifier.SmartFilters
|
FeatureIdentifier.SmartFilters
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -172,10 +172,6 @@ export class TagsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getChildren(tag: SNTag): SNTag[] {
|
getChildren(tag: SNTag): SNTag[] {
|
||||||
if (!this.features.enableNativeFoldersFeature) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.application.isTemplateItem(tag)) {
|
if (this.application.isTemplateItem(tag)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -235,10 +231,6 @@ export class TagsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get rootTags(): SNTag[] {
|
get rootTags(): SNTag[] {
|
||||||
if (!this.features.enableNativeFoldersFeature) {
|
|
||||||
return this.tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tags.filter((tag) => !this.application.getTagParent(tag));
|
return this.tags.filter((tag) => !this.application.getTagParent(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,35 +347,20 @@ export class TagsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isTemplateChange) {
|
if (isTemplateChange) {
|
||||||
if (this.features.enableNativeSmartTagsFeature) {
|
const isSmartTagTitle = this.application.isSmartTagTitle(newTitle);
|
||||||
const isSmartTagTitle = this.application.isSmartTagTitle(newTitle);
|
|
||||||
|
|
||||||
if (isSmartTagTitle) {
|
if (isSmartTagTitle) {
|
||||||
if (!this.features.hasSmartTags) {
|
if (!this.features.hasSmartTags) {
|
||||||
await this.features.showPremiumAlert(SMART_TAGS_FEATURE_NAME);
|
await this.features.showPremiumAlert(SMART_TAGS_FEATURE_NAME);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertedTag = await this.application.createTagOrSmartTag(
|
|
||||||
newTitle
|
|
||||||
);
|
|
||||||
runInAction(() => {
|
|
||||||
this.selected = insertedTag as SNTag;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Legacy code, remove me after we enableNativeSmartTagsFeature for everyone.
|
|
||||||
// See https://app.asana.com/0/0/1201612665552831/f
|
|
||||||
const insertedTag = await this.application.insertItem(tag);
|
|
||||||
const changedTag = await this.application.changeItem<TagMutator>(
|
|
||||||
insertedTag.uuid,
|
|
||||||
(m) => {
|
|
||||||
m.title = newTitle;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.selected = changedTag as SNTag;
|
|
||||||
await this.application.saveItem(insertedTag.uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const insertedTag = await this.application.createTagOrSmartTag(newTitle);
|
||||||
|
this.application.sync();
|
||||||
|
runInAction(() => {
|
||||||
|
this.selected = insertedTag as SNTag;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.application.changeAndSaveItem<TagMutator>(
|
await this.application.changeAndSaveItem<TagMutator>(
|
||||||
tag.uuid,
|
tag.uuid,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$content-horizontal-padding: 16px;
|
||||||
|
|
||||||
#navigation {
|
#navigation {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
@@ -20,15 +22,15 @@
|
|||||||
|
|
||||||
.section-title-bar {
|
.section-title-bar {
|
||||||
color: var(--sn-stylekit-secondary-foreground-color);
|
color: var(--sn-stylekit-secondary-foreground-color);
|
||||||
padding-top: 15px;
|
padding-top: 0.8125rem;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
padding-left: 14px;
|
padding-left: $content-horizontal-padding;
|
||||||
padding-right: 14px;
|
padding-right: $content-horizontal-padding;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-tags-placeholder {
|
.no-tags-placeholder {
|
||||||
padding: 0px 14px;
|
padding: 0px $content-horizontal-padding;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
margin-top: -5px;
|
margin-top: -5px;
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#notes-title-bar-container {
|
||||||
|
padding: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
#notes-title-bar {
|
#notes-title-bar {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
@@ -149,7 +153,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flag-icons {
|
.flag-icons {
|
||||||
padding: .135rem 0;
|
padding: 0.135rem 0;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
& > * {
|
& > * {
|
||||||
|
|||||||
Reference in New Issue
Block a user