feat: add migration pref pane (#825)
* feat: add migration pane * style: clean imports * removeme: beta version * fix: do no show dropzone when tag has no parent * fix: hide the Folders toggle * fix: hide migrations option when user has no folders * fix: add delimiter on Folders mark * removeme: bump beta * fix: remove component viewer for tag folders * removeme: bump beta * chore(deps): snjs
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import { ComponentView } from '@/components/ComponentView';
|
|
||||||
import { SmartTagsSection } from '@/components/Tags/SmartTagsSection';
|
import { SmartTagsSection } from '@/components/Tags/SmartTagsSection';
|
||||||
import { TagsSection } from '@/components/Tags/TagsSection';
|
import { TagsSection } from '@/components/Tags/TagsSection';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
@@ -6,13 +5,7 @@ import { PANEL_NAME_NAVIGATION } from '@/views/constants';
|
|||||||
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs';
|
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import {
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'preact/hooks';
|
|
||||||
import { PremiumModalProvider } from './Premium';
|
import { PremiumModalProvider } from './Premium';
|
||||||
import {
|
import {
|
||||||
PanelSide,
|
PanelSide,
|
||||||
@@ -28,7 +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 componentViewer = appState.foldersComponentViewer;
|
|
||||||
const enableNativeSmartTagsFeature =
|
const enableNativeSmartTagsFeature =
|
||||||
appState.features.enableNativeSmartTagsFeature;
|
appState.features.enableNativeSmartTagsFeature;
|
||||||
const [ref, setRef] = useState<HTMLDivElement | null>();
|
const [ref, setRef] = useState<HTMLDivElement | null>();
|
||||||
@@ -72,42 +64,30 @@ export const Navigation: FunctionComponent<Props> = observer(
|
|||||||
data-aria-label="Navigation"
|
data-aria-label="Navigation"
|
||||||
ref={setRef}
|
ref={setRef}
|
||||||
>
|
>
|
||||||
{componentViewer ? (
|
<div id="navigation-content" className="content">
|
||||||
<div className="component-view-container">
|
<div className="section-title-bar">
|
||||||
<div className="component-view">
|
<div className="section-title-bar-header">
|
||||||
<ComponentView
|
<div className="sk-h3 title">
|
||||||
componentViewer={componentViewer}
|
<span className="sk-bold">Views</span>
|
||||||
application={application}
|
|
||||||
appState={appState}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div id="navigation-content" className="content">
|
|
||||||
<div className="section-title-bar">
|
|
||||||
<div className="section-title-bar-header">
|
|
||||||
<div className="sk-h3 title">
|
|
||||||
<span className="sk-bold">Views</span>
|
|
||||||
</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>
|
{!enableNativeSmartTagsFeature && (
|
||||||
<div className="scrollable">
|
<div
|
||||||
<SmartTagsSection appState={appState} />
|
className="sk-button sk-secondary-contrast wide"
|
||||||
<TagsSection appState={appState} />
|
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">
|
||||||
|
<SmartTagsSection appState={appState} />
|
||||||
|
<TagsSection appState={appState} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{ref && (
|
{ref && (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
collapsable={true}
|
collapsable={true}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
ContentType,
|
ContentType,
|
||||||
|
FeatureIdentifier,
|
||||||
SNComponent,
|
SNComponent,
|
||||||
SNTheme,
|
SNTheme,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
@@ -107,10 +108,11 @@ export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
|||||||
const reloadToggleableComponents = useCallback(() => {
|
const reloadToggleableComponents = useCallback(() => {
|
||||||
const toggleableComponents = (
|
const toggleableComponents = (
|
||||||
application.getDisplayableItems(ContentType.Component) as SNComponent[]
|
application.getDisplayableItems(ContentType.Component) as SNComponent[]
|
||||||
).filter((component) =>
|
).filter(
|
||||||
[ComponentArea.EditorStack, ComponentArea.TagsList].includes(
|
(component) =>
|
||||||
component.area
|
[ComponentArea.EditorStack, ComponentArea.TagsList].includes(
|
||||||
)
|
component.area
|
||||||
|
) && component.identifier !== FeatureIdentifier.FoldersComponent
|
||||||
);
|
);
|
||||||
setToggleableComponents(toggleableComponents);
|
setToggleableComponents(toggleableComponents);
|
||||||
}, [application]);
|
}, [application]);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Icon } from '@/components/Icon';
|
import { Icon } from '@/components/Icon';
|
||||||
import { usePremiumModal } from '@/components/Premium';
|
import { usePremiumModal } from '@/components/Premium';
|
||||||
import {
|
import { FeaturesState } from '@/ui_models/app_state/features_state';
|
||||||
FeaturesState,
|
|
||||||
TAG_FOLDERS_FEATURE_NAME,
|
|
||||||
} from '@/ui_models/app_state/features_state';
|
|
||||||
import { TagsState } from '@/ui_models/app_state/tags_state';
|
import { TagsState } from '@/ui_models/app_state/tags_state';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useDrop } from 'react-dnd';
|
import { useDrop } from 'react-dnd';
|
||||||
@@ -22,8 +19,8 @@ export const RootTagDropZone: React.FC<Props> = observer(
|
|||||||
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
|
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
|
||||||
() => ({
|
() => ({
|
||||||
accept: ItemTypes.TAG,
|
accept: ItemTypes.TAG,
|
||||||
canDrop: () => {
|
canDrop: (item) => {
|
||||||
return true;
|
return tagsState.hasParent(item.uuid);
|
||||||
},
|
},
|
||||||
drop: (item) => {
|
drop: (item) => {
|
||||||
tagsState.assignParent(item.uuid, undefined);
|
tagsState.assignParent(item.uuid, undefined);
|
||||||
|
|||||||
@@ -52,7 +52,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>
|
||||||
|
|||||||
@@ -91,22 +91,27 @@ type ListElementProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MenuItemListElement: FunctionComponent<ListElementProps> =
|
export const MenuItemListElement: FunctionComponent<ListElementProps> =
|
||||||
forwardRef(({ children, isFirstMenuItem }: ListElementProps, ref: Ref<HTMLLIElement>) => {
|
forwardRef(
|
||||||
const child = children as VNode<unknown>;
|
(
|
||||||
|
{ children, isFirstMenuItem }: ListElementProps,
|
||||||
|
ref: Ref<HTMLLIElement>
|
||||||
|
) => {
|
||||||
|
const child = children as VNode<unknown>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="list-style-none" role="none" ref={ref}>
|
<li className="list-style-none" role="none" ref={ref}>
|
||||||
{{
|
{{
|
||||||
...child,
|
...child,
|
||||||
props: {
|
props: {
|
||||||
...(child.props ? { ...child.props } : {}),
|
...(child.props ? { ...child.props } : {}),
|
||||||
...(child.type === MenuItem
|
...(child.type === MenuItem
|
||||||
? {
|
? {
|
||||||
tabIndex: isFirstMenuItem ? 0 : -1,
|
tabIndex: isFirstMenuItem ? 0 : -1,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const MenuItem: FunctionComponent<Props> = ({
|
|||||||
onClick,
|
onClick,
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
|
className={`preferences-menu-item select-none ${
|
||||||
|
selected ? 'selected' : ''
|
||||||
|
}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClick();
|
onClick();
|
||||||
|
|||||||
@@ -6,24 +6,26 @@ 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;
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
extensionsLatestVersions: ExtensionsLatestVersions,
|
extensionsLatestVersions: ExtensionsLatestVersions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const General: FunctionComponent<GeneralProps> = observer(
|
export const General: FunctionComponent<GeneralProps> = observer(
|
||||||
({
|
({ appState, application, extensionsLatestVersions }) => (
|
||||||
appState,
|
|
||||||
application,
|
|
||||||
extensionsLatestVersions
|
|
||||||
}) => (
|
|
||||||
<PreferencesPane>
|
<PreferencesPane>
|
||||||
<Tools application={application} />
|
<Tools application={application} />
|
||||||
<Defaults application={application} />
|
<Defaults application={application} />
|
||||||
<ErrorReporting appState={appState} />
|
<ErrorReporting appState={appState} />
|
||||||
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
|
<Migrations application={application} appState={appState} />
|
||||||
|
<Advanced
|
||||||
|
application={application}
|
||||||
|
appState={appState}
|
||||||
|
extensionsLatestVersions={extensionsLatestVersions}
|
||||||
|
/>
|
||||||
</PreferencesPane>
|
</PreferencesPane>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,18 +5,14 @@ import { AccountMenuState } from '@/ui_models/app_state/account_menu_state';
|
|||||||
import { isDesktopApplication } from '@/utils';
|
import { isDesktopApplication } from '@/utils';
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ComponentArea,
|
|
||||||
ContentType,
|
ContentType,
|
||||||
DeinitSource,
|
DeinitSource,
|
||||||
isPayloadSourceInternalChange,
|
NoteViewController,
|
||||||
PayloadSource,
|
PayloadSource,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
SNComponent,
|
|
||||||
SNNote,
|
SNNote,
|
||||||
SNSmartTag,
|
SNSmartTag,
|
||||||
ComponentViewer,
|
|
||||||
SNTag,
|
SNTag,
|
||||||
NoteViewController,
|
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import pull from 'lodash/pull';
|
import pull from 'lodash/pull';
|
||||||
import {
|
import {
|
||||||
@@ -91,14 +87,11 @@ export class AppState {
|
|||||||
readonly tags: TagsState;
|
readonly tags: TagsState;
|
||||||
readonly notesView: NotesViewState;
|
readonly notesView: NotesViewState;
|
||||||
|
|
||||||
public foldersComponentViewer?: ComponentViewer;
|
|
||||||
|
|
||||||
isSessionsModalVisible = false;
|
isSessionsModalVisible = false;
|
||||||
|
|
||||||
private appEventObserverRemovers: (() => void)[] = [];
|
private appEventObserverRemovers: (() => void)[] = [];
|
||||||
|
|
||||||
private readonly tagChangedDisposer: IReactionDisposer;
|
private readonly tagChangedDisposer: IReactionDisposer;
|
||||||
private readonly foldersComponentViewerDisposer: () => void;
|
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(application: WebApplication, private bridge: Bridge) {
|
constructor(application: WebApplication, private bridge: Bridge) {
|
||||||
@@ -157,8 +150,6 @@ export class AppState {
|
|||||||
this.showBetaWarning = false;
|
this.showBetaWarning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.foldersComponentViewer = undefined;
|
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
selectedTag: computed,
|
selectedTag: computed,
|
||||||
|
|
||||||
@@ -170,14 +161,9 @@ export class AppState {
|
|||||||
disableBetaWarning: action,
|
disableBetaWarning: action,
|
||||||
openSessionsModal: action,
|
openSessionsModal: action,
|
||||||
closeSessionsModal: action,
|
closeSessionsModal: action,
|
||||||
|
|
||||||
foldersComponentViewer: observable.ref,
|
|
||||||
setFoldersComponent: action,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tagChangedDisposer = this.tagChangedNotifier();
|
this.tagChangedDisposer = this.tagChangedNotifier();
|
||||||
this.foldersComponentViewerDisposer =
|
|
||||||
this.subscribeToFoldersComponentChanges();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit(source: DeinitSource): void {
|
deinit(source: DeinitSource): void {
|
||||||
@@ -197,7 +183,6 @@ export class AppState {
|
|||||||
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
||||||
this.onVisibilityChange = undefined;
|
this.onVisibilityChange = undefined;
|
||||||
this.tagChangedDisposer();
|
this.tagChangedDisposer();
|
||||||
this.foldersComponentViewerDisposer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openSessionsModal(): void {
|
openSessionsModal(): void {
|
||||||
@@ -302,51 +287,6 @@ export class AppState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFoldersComponent(component?: SNComponent) {
|
|
||||||
const foldersComponentViewer = this.foldersComponentViewer;
|
|
||||||
|
|
||||||
if (foldersComponentViewer) {
|
|
||||||
this.application.componentManager.destroyComponentViewer(
|
|
||||||
foldersComponentViewer
|
|
||||||
);
|
|
||||||
this.foldersComponentViewer = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component) {
|
|
||||||
this.foldersComponentViewer =
|
|
||||||
this.application.componentManager.createComponentViewer(
|
|
||||||
component,
|
|
||||||
undefined,
|
|
||||||
this.tags.onFoldersComponentMessage.bind(this.tags)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private subscribeToFoldersComponentChanges() {
|
|
||||||
return this.application.streamItems(
|
|
||||||
[ContentType.Component],
|
|
||||||
async (items, source) => {
|
|
||||||
if (
|
|
||||||
isPayloadSourceInternalChange(source) ||
|
|
||||||
source === PayloadSource.InitialObserverRegistrationPush
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const components = items as SNComponent[];
|
|
||||||
const hasFoldersChange = !!components.find(
|
|
||||||
(component) => component.area === ComponentArea.TagsList
|
|
||||||
);
|
|
||||||
if (hasFoldersChange) {
|
|
||||||
const componentViewer = this.application.componentManager
|
|
||||||
.componentsForArea(ComponentArea.TagsList)
|
|
||||||
.find((component) => component.active);
|
|
||||||
|
|
||||||
this.setFoldersComponent(componentViewer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get selectedTag(): SNTag | SNSmartTag | undefined {
|
public get selectedTag(): SNTag | SNSmartTag | undefined {
|
||||||
return this.tags.selected;
|
return this.tags.selected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,11 @@ export class TagsState {
|
|||||||
return this.application.isValidTagParent(parentUuid, tagUuid);
|
return this.application.isValidTagParent(parentUuid, tagUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasParent(tagUuid: UuidString): boolean {
|
||||||
|
const item = this.application.findItem(tagUuid);
|
||||||
|
return !!item && !!(item as SNTag).parentId;
|
||||||
|
}
|
||||||
|
|
||||||
public async assignParent(
|
public async assignParent(
|
||||||
tagUuid: string,
|
tagUuid: string,
|
||||||
futureParentUuid: string | undefined
|
futureParentUuid: string | undefined
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
"@reach/tooltip": "^0.16.2",
|
"@reach/tooltip": "^0.16.2",
|
||||||
"@standardnotes/components": "1.4.4",
|
"@standardnotes/components": "1.4.4",
|
||||||
"@standardnotes/features": "1.26.1",
|
"@standardnotes/features": "1.26.1",
|
||||||
"@standardnotes/snjs": "2.45.0",
|
"@standardnotes/snjs": "2.46.0",
|
||||||
"@standardnotes/settings": "^1.10.0",
|
"@standardnotes/settings": "^1.10.0",
|
||||||
"@standardnotes/sncrypto-web": "1.6.0",
|
"@standardnotes/sncrypto-web": "1.6.0",
|
||||||
"mobx": "^6.3.5",
|
"mobx": "^6.3.5",
|
||||||
|
|||||||
@@ -2655,10 +2655,10 @@
|
|||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
libsodium-wrappers "^0.7.9"
|
libsodium-wrappers "^0.7.9"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.45.0":
|
"@standardnotes/snjs@2.46.0":
|
||||||
version "2.45.0"
|
version "2.46.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.45.0.tgz#d123434b959d279af2ffe00acf2e9e6784cf497c"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.46.0.tgz#7e4242df8cd0408fde243e1048536fa19ea6c5d9"
|
||||||
integrity sha512-gTlOG3wd4zYaBeReypQiz+ASEnVCKaB8kWtKF61nkV9j3vFgYh3krsvdhOi6lMXBk+CijEefeLhrmooOtv08Xg==
|
integrity sha512-lKCW6/Q3HaDdaI6VJ31m6GyIy6TgHyGesdUQzbMFtZPEhHDjySqFCyIaQy0ZVdAMB7X/HFxREtxwAwG5wfVVvw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.15.3"
|
"@standardnotes/auth" "^3.15.3"
|
||||||
"@standardnotes/common" "^1.8.0"
|
"@standardnotes/common" "^1.8.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user