feat: component viewer (#781)
* wip: component viewer * feat: get component status from component viewer * fix: remove unused property * chore(deps): snjs 2.29.0 * fix: import location
This commit is contained in:
@@ -51,7 +51,6 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ActionsMenu,
|
ActionsMenu,
|
||||||
ComponentModal,
|
|
||||||
EditorMenu,
|
EditorMenu,
|
||||||
InputModal,
|
InputModal,
|
||||||
MenuRow,
|
MenuRow,
|
||||||
@@ -166,7 +165,6 @@ const startApplication: StartApplication = async function startApplication(
|
|||||||
.directive('accountSwitcher', () => new AccountSwitcher())
|
.directive('accountSwitcher', () => new AccountSwitcher())
|
||||||
.directive('actionsMenu', () => new ActionsMenu())
|
.directive('actionsMenu', () => new ActionsMenu())
|
||||||
.directive('challengeModal', () => new ChallengeModal())
|
.directive('challengeModal', () => new ChallengeModal())
|
||||||
.directive('componentModal', () => new ComponentModal())
|
|
||||||
.directive('componentView', ComponentViewDirective)
|
.directive('componentView', ComponentViewDirective)
|
||||||
.directive('editorMenu', () => new EditorMenu())
|
.directive('editorMenu', () => new EditorMenu())
|
||||||
.directive('inputModal', () => new InputModal())
|
.directive('inputModal', () => new InputModal())
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ interface IProps {
|
|||||||
expiredDate: string;
|
expiredDate: string;
|
||||||
componentName: string;
|
componentName: string;
|
||||||
featureStatus: FeatureStatus;
|
featureStatus: FeatureStatus;
|
||||||
reloadStatus: () => void;
|
|
||||||
manageSubscription: () => void;
|
manageSubscription: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusString = (featureStatus: FeatureStatus, expiredDate: string, componentName: string) => {
|
const statusString = (
|
||||||
|
featureStatus: FeatureStatus,
|
||||||
|
expiredDate: string,
|
||||||
|
componentName: string
|
||||||
|
) => {
|
||||||
switch (featureStatus) {
|
switch (featureStatus) {
|
||||||
case FeatureStatus.InCurrentPlanButExpired:
|
case FeatureStatus.InCurrentPlanButExpired:
|
||||||
return `Your subscription expired on ${expiredDate}`;
|
return `Your subscription expired on ${expiredDate}`;
|
||||||
@@ -25,9 +28,8 @@ const statusString = (featureStatus: FeatureStatus, expiredDate: string, compone
|
|||||||
export const IsExpired: FunctionalComponent<IProps> = ({
|
export const IsExpired: FunctionalComponent<IProps> = ({
|
||||||
expiredDate,
|
expiredDate,
|
||||||
featureStatus,
|
featureStatus,
|
||||||
reloadStatus,
|
|
||||||
componentName,
|
componentName,
|
||||||
manageSubscription
|
manageSubscription,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={'sn-component'}>
|
<div className={'sn-component'}>
|
||||||
@@ -50,11 +52,13 @@ export const IsExpired: FunctionalComponent<IProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'right'}>
|
<div className={'right'}>
|
||||||
<div className={'sk-app-bar-item'} onClick={() => manageSubscription()}>
|
<div
|
||||||
<button className={'sn-button small success'}>Manage Subscription</button>
|
className={'sk-app-bar-item'}
|
||||||
</div>
|
onClick={() => manageSubscription()}
|
||||||
<div className={'sk-app-bar-item'} onClick={() => reloadStatus()}>
|
>
|
||||||
<button className={'sn-button small info'}>Reload</button>
|
<button className={'sn-button small success'}>
|
||||||
|
Manage Subscription
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { FunctionalComponent } from 'preact';
|
import { FunctionalComponent } from 'preact';
|
||||||
|
|
||||||
interface IProps {
|
export const OfflineRestricted: FunctionalComponent = () => {
|
||||||
isReloading: boolean;
|
|
||||||
reloadStatus: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OfflineRestricted: FunctionalComponent<IProps> = ({
|
|
||||||
isReloading,
|
|
||||||
reloadStatus,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={'sn-component'}>
|
<div className={'sn-component'}>
|
||||||
<div className={'sk-panel static'}>
|
<div className={'sk-panel static'}>
|
||||||
@@ -16,38 +8,29 @@ export const OfflineRestricted: FunctionalComponent<IProps> = ({
|
|||||||
<div className={'sk-panel-section stretch'}>
|
<div className={'sk-panel-section stretch'}>
|
||||||
<div className={'sk-panel-column'} />
|
<div className={'sk-panel-column'} />
|
||||||
<div className={'sk-h1 sk-bold'}>
|
<div className={'sk-h1 sk-bold'}>
|
||||||
You have restricted this component to be used offline only.
|
You have restricted this component to not use a hosted version.
|
||||||
</div>
|
</div>
|
||||||
<div className={'sk-subtitle'}>
|
<div className={'sk-subtitle'}>
|
||||||
Offline components are not available in the web application.
|
Locally-installed components are not available in the web
|
||||||
|
application.
|
||||||
</div>
|
</div>
|
||||||
<div className={'sk-panel-row'} />
|
<div className={'sk-panel-row'} />
|
||||||
<div className={'sk-panel-row'}>
|
<div className={'sk-panel-row'}>
|
||||||
<div className={'sk-panel-column'}>
|
<div className={'sk-panel-column'}>
|
||||||
<div className={'sk-p'}>You can either:</div>
|
<div className={'sk-p'}>
|
||||||
|
To continue, choose from the following options:
|
||||||
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className={'sk-p'}>
|
<li className={'sk-p'}>
|
||||||
Enable the Hosted option for this component by opening
|
Enable the Hosted option for this component by opening the
|
||||||
Preferences {'>'} General {'>'} Advanced Settings menu and{' '}
|
Preferences {'>'} General {'>'} Advanced Settings menu and{' '}
|
||||||
toggling 'Use hosted when local is unavailable' under this
|
toggling 'Use hosted when local is unavailable' under this
|
||||||
components's options. Then press Reload below.
|
component's options. Then press Reload.
|
||||||
</li>
|
</li>
|
||||||
<li className={'sk-p'}>Use the desktop application.</li>
|
<li className={'sk-p'}>Use the desktop application.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'sk-panel-row'}>
|
|
||||||
{isReloading ? (
|
|
||||||
<div className={'sk-spinner info small'} />
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
className={'sn-button small info'}
|
|
||||||
onClick={() => reloadStatus()}
|
|
||||||
>
|
|
||||||
Reload
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import {
|
|||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
SNComponent,
|
SNComponent,
|
||||||
dateToLocalizedString,
|
dateToLocalizedString,
|
||||||
ApplicationEvent,
|
ComponentViewer,
|
||||||
|
ComponentViewerEvent,
|
||||||
|
ComponentViewerError,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { FunctionalComponent } from 'preact';
|
import { FunctionalComponent } from 'preact';
|
||||||
@@ -22,9 +24,9 @@ import { openSubscriptionDashboard } from '@/hooks/manageSubscription';
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
componentUuid: string;
|
componentViewer: ComponentViewer;
|
||||||
|
requestReload?: (viewer: ComponentViewer) => void;
|
||||||
onLoad?: (component: SNComponent) => void;
|
onLoad?: (component: SNComponent) => void;
|
||||||
templateComponent?: SNComponent;
|
|
||||||
manualDealloc?: boolean;
|
manualDealloc?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,10 +36,10 @@ interface IProps {
|
|||||||
*/
|
*/
|
||||||
const MaxLoadThreshold = 4000;
|
const MaxLoadThreshold = 4000;
|
||||||
const VisibilityChangeKey = 'visibilitychange';
|
const VisibilityChangeKey = 'visibilitychange';
|
||||||
const avoidFlickerTimeout = 7;
|
const MSToWaitAfterIframeLoadToAvoidFlicker = 35;
|
||||||
|
|
||||||
export const ComponentView: FunctionalComponent<IProps> = observer(
|
export const ComponentView: FunctionalComponent<IProps> = observer(
|
||||||
({ application, onLoad, componentUuid, templateComponent }) => {
|
({ application, onLoad, componentViewer, requestReload }) => {
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
const excessiveLoadingTimeout = useRef<
|
const excessiveLoadingTimeout = useRef<
|
||||||
ReturnType<typeof setTimeout> | undefined
|
ReturnType<typeof setTimeout> | undefined
|
||||||
@@ -45,44 +47,33 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
|
|
||||||
const [hasIssueLoading, setHasIssueLoading] = useState(false);
|
const [hasIssueLoading, setHasIssueLoading] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isReloading, setIsReloading] = useState(false);
|
|
||||||
const [component] = useState<SNComponent>(
|
|
||||||
application.findItem(componentUuid) as SNComponent
|
|
||||||
);
|
|
||||||
const [featureStatus, setFeatureStatus] = useState<FeatureStatus>(
|
const [featureStatus, setFeatureStatus] = useState<FeatureStatus>(
|
||||||
application.getFeatureStatus(component.identifier)
|
componentViewer.getFeatureStatus()
|
||||||
);
|
);
|
||||||
const [isComponentValid, setIsComponentValid] = useState(true);
|
const [isComponentValid, setIsComponentValid] = useState(true);
|
||||||
const [error, setError] = useState<
|
const [error, setError] = useState<ComponentViewerError | undefined>(
|
||||||
'offline-restricted' | 'url-missing' | undefined
|
undefined
|
||||||
>(undefined);
|
);
|
||||||
const [isDeprecated, setIsDeprecated] = useState(false);
|
|
||||||
const [deprecationMessage, setDeprecationMessage] = useState<
|
const [deprecationMessage, setDeprecationMessage] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const [isDeprecationMessageDismissed, setIsDeprecationMessageDismissed] =
|
const [isDeprecationMessageDismissed, setIsDeprecationMessageDismissed] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [didAttemptReload, setDidAttemptReload] = useState(false);
|
const [didAttemptReload, setDidAttemptReload] = useState(false);
|
||||||
const [contentWindow, setContentWindow] = useState<Window | null>(null);
|
|
||||||
|
const component = componentViewer.component;
|
||||||
|
|
||||||
const manageSubscription = useCallback(() => {
|
const manageSubscription = useCallback(() => {
|
||||||
openSubscriptionDashboard(application);
|
openSubscriptionDashboard(application);
|
||||||
}, [application]);
|
}, [application]);
|
||||||
|
|
||||||
const reloadIframe = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsReloading(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsReloading(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadTimeout = setTimeout(() => {
|
const loadTimeout = setTimeout(() => {
|
||||||
handleIframeTakingTooLongToLoad();
|
handleIframeTakingTooLongToLoad();
|
||||||
}, MaxLoadThreshold);
|
}, MaxLoadThreshold);
|
||||||
|
|
||||||
excessiveLoadingTimeout.current = loadTimeout;
|
excessiveLoadingTimeout.current = loadTimeout;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
excessiveLoadingTimeout.current &&
|
excessiveLoadingTimeout.current &&
|
||||||
clearTimeout(excessiveLoadingTimeout.current);
|
clearTimeout(excessiveLoadingTimeout.current);
|
||||||
@@ -91,42 +82,25 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const reloadValidityStatus = useCallback(() => {
|
const reloadValidityStatus = useCallback(() => {
|
||||||
const offlineRestricted =
|
setFeatureStatus(componentViewer.getFeatureStatus());
|
||||||
component.offlineOnly && !isDesktopApplication();
|
if (!componentViewer.lockReadonly) {
|
||||||
const hasUrlError = (function () {
|
componentViewer.setReadonly(featureStatus !== FeatureStatus.Entitled);
|
||||||
if (isDesktopApplication()) {
|
|
||||||
return !component.local_url && !component.hasValidHostedUrl();
|
|
||||||
} else {
|
|
||||||
return !component.hasValidHostedUrl();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const readonlyState =
|
|
||||||
application.componentManager.getReadonlyStateForComponent(component);
|
|
||||||
|
|
||||||
if (!readonlyState.lockReadonly) {
|
|
||||||
application.componentManager.setReadonlyStateForComponent(
|
|
||||||
component,
|
|
||||||
featureStatus !== FeatureStatus.Entitled
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
setIsComponentValid(!offlineRestricted && !hasUrlError);
|
setIsComponentValid(componentViewer.shouldRender());
|
||||||
|
|
||||||
if (!isComponentValid) {
|
if (isLoading && !isComponentValid) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offlineRestricted) {
|
setError(componentViewer.getError());
|
||||||
setError('offline-restricted');
|
setDeprecationMessage(component.deprecationMessage);
|
||||||
} else if (hasUrlError) {
|
}, [
|
||||||
setError('url-missing');
|
componentViewer,
|
||||||
} else {
|
component.deprecationMessage,
|
||||||
setError(undefined);
|
featureStatus,
|
||||||
}
|
isComponentValid,
|
||||||
setIsDeprecated(component.isDeprecated);
|
isLoading,
|
||||||
setDeprecationMessage(component.package_info.deprecation_message);
|
]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reloadValidityStatus();
|
reloadValidityStatus();
|
||||||
@@ -141,9 +115,9 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hasIssueLoading) {
|
if (hasIssueLoading) {
|
||||||
reloadIframe();
|
requestReload?.(componentViewer);
|
||||||
}
|
}
|
||||||
}, [hasIssueLoading]);
|
}, [hasIssueLoading, componentViewer, requestReload]);
|
||||||
|
|
||||||
const handleIframeTakingTooLongToLoad = useCallback(async () => {
|
const handleIframeTakingTooLongToLoad = useCallback(async () => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -151,188 +125,133 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
|
|
||||||
if (!didAttemptReload) {
|
if (!didAttemptReload) {
|
||||||
setDidAttemptReload(true);
|
setDidAttemptReload(true);
|
||||||
reloadIframe();
|
requestReload?.(componentViewer);
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener(VisibilityChangeKey, onVisibilityChange);
|
document.addEventListener(VisibilityChangeKey, onVisibilityChange);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [componentViewer, didAttemptReload, onVisibilityChange, requestReload]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleIframeLoad = useCallback(async (iframe: HTMLIFrameElement) => {
|
|
||||||
let hasDesktopError = false;
|
|
||||||
const canAccessWindowOrigin = isDesktopApplication();
|
|
||||||
if (canAccessWindowOrigin) {
|
|
||||||
try {
|
|
||||||
const contentWindow = iframe.contentWindow as Window;
|
|
||||||
if (!contentWindow.origin || contentWindow.origin === 'null') {
|
|
||||||
hasDesktopError = true;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
excessiveLoadingTimeout.current &&
|
|
||||||
clearTimeout(excessiveLoadingTimeout.current);
|
|
||||||
setContentWindow(iframe.contentWindow);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
setHasIssueLoading(hasDesktopError);
|
|
||||||
onLoad?.(component);
|
|
||||||
}, avoidFlickerTimeout);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (contentWindow) {
|
|
||||||
application.componentManager.registerComponentWindow(
|
|
||||||
component,
|
|
||||||
contentWindow
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
application.componentManager.onComponentIframeDestroyed(component.uuid);
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [contentWindow]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!iframeRef.current) {
|
if (!iframeRef.current) {
|
||||||
setContentWindow(null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframeRef.current.onload = () => {
|
const iframe = iframeRef.current as HTMLIFrameElement;
|
||||||
const iframe = application.componentManager.iframeForComponent(
|
iframe.onload = () => {
|
||||||
component.uuid
|
const contentWindow = iframe.contentWindow as Window;
|
||||||
);
|
|
||||||
if (iframe) {
|
let hasDesktopError = false;
|
||||||
setTimeout(() => {
|
const canAccessWindowOrigin = isDesktopApplication();
|
||||||
handleIframeLoad(iframe);
|
if (canAccessWindowOrigin) {
|
||||||
});
|
try {
|
||||||
|
if (!contentWindow.origin || contentWindow.origin === 'null') {
|
||||||
|
hasDesktopError = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
excessiveLoadingTimeout.current &&
|
||||||
|
clearTimeout(excessiveLoadingTimeout.current);
|
||||||
|
|
||||||
|
componentViewer.setWindow(contentWindow);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setHasIssueLoading(hasDesktopError);
|
||||||
|
onLoad?.(component);
|
||||||
|
}, MSToWaitAfterIframeLoadToAvoidFlicker);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [onLoad, component, componentViewer]);
|
||||||
}, [iframeRef.current]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const removeFeaturesChangedObserver = application.addEventObserver(
|
const removeFeaturesChangedObserver = componentViewer.addEventObserver(
|
||||||
async () => {
|
(event) => {
|
||||||
setFeatureStatus(application.getFeatureStatus(component.identifier));
|
if (event === ComponentViewerEvent.FeatureStatusUpdated) {
|
||||||
},
|
setFeatureStatus(componentViewer.getFeatureStatus());
|
||||||
ApplicationEvent.FeaturesUpdated
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
removeFeaturesChangedObserver();
|
removeFeaturesChangedObserver();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [componentViewer]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!componentUuid) {
|
const removeActionObserver = componentViewer.addActionObserver(
|
||||||
application.componentManager.addTemporaryTemplateComponent(
|
(action, data) => {
|
||||||
templateComponent as SNComponent
|
switch (action) {
|
||||||
);
|
case ComponentAction.KeyDown:
|
||||||
}
|
application.io.handleComponentKeyDown(data.keyboardModifier);
|
||||||
|
break;
|
||||||
return () => {
|
case ComponentAction.KeyUp:
|
||||||
if (templateComponent) {
|
application.io.handleComponentKeyUp(data.keyboardModifier);
|
||||||
/** componentManager can be destroyed already via locking */
|
break;
|
||||||
application.componentManager?.removeTemporaryTemplateComponent(
|
case ComponentAction.Click:
|
||||||
templateComponent
|
application.getAppState().notes.setContextMenuOpen(false);
|
||||||
);
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
document.removeEventListener(VisibilityChangeKey, onVisibilityChange);
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unregisterComponentHandler =
|
|
||||||
application.componentManager.registerHandler({
|
|
||||||
identifier: 'component-view-' + Math.random(),
|
|
||||||
areas: [component.area],
|
|
||||||
actionHandler: (component, action, data) => {
|
|
||||||
switch (action) {
|
|
||||||
case ComponentAction.SetSize:
|
|
||||||
application.componentManager.handleSetSizeEvent(
|
|
||||||
component,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case ComponentAction.KeyDown:
|
|
||||||
application.io.handleComponentKeyDown(data.keyboardModifier);
|
|
||||||
break;
|
|
||||||
case ComponentAction.KeyUp:
|
|
||||||
application.io.handleComponentKeyUp(data.keyboardModifier);
|
|
||||||
break;
|
|
||||||
case ComponentAction.Click:
|
|
||||||
application.getAppState().notes.setContextMenuOpen(false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unregisterComponentHandler();
|
removeActionObserver();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [componentViewer, application]);
|
||||||
}, [component]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unregisterDesktopObserver = application
|
const unregisterDesktopObserver = application
|
||||||
.getDesktopService()
|
.getDesktopService()
|
||||||
.registerUpdateObserver((component: SNComponent) => {
|
.registerUpdateObserver((component: SNComponent) => {
|
||||||
if (component.uuid === component.uuid && component.active) {
|
if (component.uuid === component.uuid && component.active) {
|
||||||
reloadIframe();
|
requestReload?.(componentViewer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unregisterDesktopObserver();
|
unregisterDesktopObserver();
|
||||||
};
|
};
|
||||||
}, [application]);
|
}, [application, requestReload, componentViewer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasIssueLoading && (
|
{hasIssueLoading && (
|
||||||
<IssueOnLoading
|
<IssueOnLoading
|
||||||
componentName={component.name}
|
componentName={component.name}
|
||||||
reloadIframe={reloadIframe}
|
reloadIframe={() => {
|
||||||
|
reloadValidityStatus(), requestReload?.(componentViewer);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{featureStatus !== FeatureStatus.Entitled && (
|
{featureStatus !== FeatureStatus.Entitled && (
|
||||||
<IsExpired
|
<IsExpired
|
||||||
expiredDate={dateToLocalizedString(component.valid_until)}
|
expiredDate={dateToLocalizedString(component.valid_until)}
|
||||||
reloadStatus={reloadValidityStatus}
|
|
||||||
featureStatus={featureStatus}
|
featureStatus={featureStatus}
|
||||||
componentName={component.name}
|
componentName={component.name}
|
||||||
manageSubscription={manageSubscription}
|
manageSubscription={manageSubscription}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isDeprecated && !isDeprecationMessageDismissed && (
|
{deprecationMessage && !isDeprecationMessageDismissed && (
|
||||||
<IsDeprecated
|
<IsDeprecated
|
||||||
deprecationMessage={deprecationMessage}
|
deprecationMessage={deprecationMessage}
|
||||||
dismissDeprecationMessage={dismissDeprecationMessage}
|
dismissDeprecationMessage={dismissDeprecationMessage}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{error == 'offline-restricted' && (
|
{error === ComponentViewerError.OfflineRestricted && (
|
||||||
<OfflineRestricted
|
<OfflineRestricted />
|
||||||
isReloading={isReloading}
|
|
||||||
reloadStatus={reloadValidityStatus}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{error == 'url-missing' && (
|
{error === ComponentViewerError.MissingUrl && (
|
||||||
<UrlMissing componentName={component.name} />
|
<UrlMissing componentName={component.name} />
|
||||||
)}
|
)}
|
||||||
{component.uuid && !isReloading && isComponentValid && (
|
{component.uuid && isComponentValid && (
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
data-component-id={component.uuid}
|
data-component-viewer-id={componentViewer.identifier}
|
||||||
frameBorder={0}
|
frameBorder={0}
|
||||||
data-attr-id={`component-iframe-${component.uuid}`}
|
|
||||||
src={application.componentManager.urlForComponent(component) || ''}
|
src={application.componentManager.urlForComponent(component) || ''}
|
||||||
sandbox="allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms allow-downloads"
|
sandbox="allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms allow-downloads"
|
||||||
>
|
>
|
||||||
@@ -347,7 +266,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
|
|
||||||
export const ComponentViewDirective = toDirective<IProps>(ComponentView, {
|
export const ComponentViewDirective = toDirective<IProps>(ComponentView, {
|
||||||
onLoad: '=',
|
onLoad: '=',
|
||||||
componentUuid: '=',
|
componentViewer: '=',
|
||||||
templateComponent: '=',
|
requestReload: '=',
|
||||||
manualDealloc: '=',
|
manualDealloc: '=',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { FeatureIdentifier } from '@standardnotes/features';
|
import { FeatureStatus, FeatureIdentifier } from '@standardnotes/snjs';
|
||||||
import { FeatureStatus } from '@standardnotes/snjs';
|
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { useCallback, useState } from 'preact/hooks';
|
import { useCallback, useState } from 'preact/hooks';
|
||||||
import { JSXInternal } from 'preact/src/jsx';
|
import { JSXInternal } from 'preact/src/jsx';
|
||||||
|
|||||||
@@ -174,7 +174,11 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleComponent = (component: SNComponent) => {
|
const toggleComponent = (component: SNComponent) => {
|
||||||
application.toggleComponent(component);
|
if (component.isTheme()) {
|
||||||
|
application.toggleTheme(component);
|
||||||
|
} else {
|
||||||
|
application.toggleComponent(component);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (
|
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (
|
||||||
@@ -218,7 +222,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
|||||||
const activeTheme = themes.find(
|
const activeTheme = themes.find(
|
||||||
(theme) => theme.active && !theme.isLayerable()
|
(theme) => theme.active && !theme.isLayerable()
|
||||||
);
|
);
|
||||||
if (activeTheme) application.toggleComponent(activeTheme);
|
if (activeTheme) application.toggleTheme(activeTheme);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const ThemesMenuButton: FunctionComponent<Props> = ({
|
|||||||
const toggleTheme: JSXInternal.MouseEventHandler<HTMLButtonElement> = (e) => {
|
const toggleTheme: JSXInternal.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (theme.isLayerable() || !theme.active) {
|
if (theme.isLayerable() || !theme.active) {
|
||||||
application.toggleComponent(theme);
|
application.toggleTheme(theme);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,8 @@ type Props = {
|
|||||||
export const SearchOptions = observer(({ appState }: Props) => {
|
export const SearchOptions = observer(({ appState }: Props) => {
|
||||||
const { searchOptions } = appState;
|
const { searchOptions } = appState;
|
||||||
|
|
||||||
const {
|
const { includeProtectedContents, includeArchived, includeTrashed } =
|
||||||
includeProtectedContents,
|
searchOptions;
|
||||||
includeArchived,
|
|
||||||
includeTrashed,
|
|
||||||
} = searchOptions;
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [position, setPosition] = useState({
|
const [position, setPosition] = useState({
|
||||||
@@ -34,7 +31,10 @@ export const SearchOptions = observer(({ appState }: Props) => {
|
|||||||
const [maxWidth, setMaxWidth] = useState<number | 'auto'>('auto');
|
const [maxWidth, setMaxWidth] = useState<number | 'auto'>('auto');
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
const panelRef = useRef<HTMLDivElement>(null);
|
const panelRef = useRef<HTMLDivElement>(null);
|
||||||
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(panelRef as any, setOpen);
|
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(
|
||||||
|
panelRef as any,
|
||||||
|
setOpen
|
||||||
|
);
|
||||||
|
|
||||||
async function toggleIncludeProtectedContents() {
|
async function toggleIncludeProtectedContents() {
|
||||||
setLockCloseOnBlur(true);
|
setLockCloseOnBlur(true);
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import { WebApplication } from '@/ui_models/application';
|
|
||||||
import { SNComponent, LiveItem } from '@standardnotes/snjs';
|
|
||||||
import { WebDirective } from './../../types';
|
|
||||||
import template from '%/directives/component-modal.pug';
|
|
||||||
|
|
||||||
export type ComponentModalScope = {
|
|
||||||
componentUuid: string
|
|
||||||
onDismiss: () => void
|
|
||||||
application: WebApplication
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComponentModalCtrl implements ComponentModalScope {
|
|
||||||
$element: JQLite
|
|
||||||
componentUuid!: string
|
|
||||||
onDismiss!: () => void
|
|
||||||
application!: WebApplication
|
|
||||||
liveComponent!: LiveItem<SNComponent>
|
|
||||||
component!: SNComponent
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
constructor($element: JQLite) {
|
|
||||||
this.$element = $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
this.liveComponent = new LiveItem(
|
|
||||||
this.componentUuid,
|
|
||||||
this.application,
|
|
||||||
(component) => {
|
|
||||||
this.component = component;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.application.componentGroup.activateComponent(this.component);
|
|
||||||
}
|
|
||||||
|
|
||||||
$onDestroy() {
|
|
||||||
this.application.componentGroup.deactivateComponent(this.component);
|
|
||||||
this.liveComponent.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss() {
|
|
||||||
this.onDismiss && this.onDismiss();
|
|
||||||
const elem = this.$element;
|
|
||||||
const scope = elem.scope();
|
|
||||||
scope.$destroy();
|
|
||||||
elem.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComponentModal extends WebDirective {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.restrict = 'E';
|
|
||||||
this.template = template;
|
|
||||||
this.controller = ComponentModalCtrl;
|
|
||||||
this.controllerAs = 'ctrl';
|
|
||||||
this.bindToController = true;
|
|
||||||
this.scope = {
|
|
||||||
componentUuid: '=',
|
|
||||||
onDismiss: '&',
|
|
||||||
application: '='
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export { ActionsMenu } from './actionsMenu';
|
export { ActionsMenu } from './actionsMenu';
|
||||||
export { ComponentModal } from './componentModal';
|
|
||||||
export { EditorMenu } from './editorMenu';
|
export { EditorMenu } from './editorMenu';
|
||||||
export { InputModal } from './inputModal';
|
export { InputModal } from './inputModal';
|
||||||
export { MenuRow } from './menuRow';
|
export { MenuRow } from './menuRow';
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { WebDirective } from './../../types';
|
|||||||
import template from '%/directives/permissions-modal.pug';
|
import template from '%/directives/permissions-modal.pug';
|
||||||
|
|
||||||
class PermissionsModalCtrl {
|
class PermissionsModalCtrl {
|
||||||
|
$element: JQLite;
|
||||||
$element: JQLite
|
callback!: (success: boolean) => void;
|
||||||
callback!: (success: boolean) => void
|
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor($element: JQLite) {
|
constructor($element: JQLite) {
|
||||||
@@ -41,7 +40,7 @@ export class PermissionsModal extends WebDirective {
|
|||||||
show: '=',
|
show: '=',
|
||||||
component: '=',
|
component: '=',
|
||||||
permissionsString: '=',
|
permissionsString: '=',
|
||||||
callback: '='
|
callback: '=',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,38 @@
|
|||||||
|
import { ComponentViewer } from '@standardnotes/snjs/dist/@types';
|
||||||
import { PureViewCtrl } from './../../views/abstract/pure_view_ctrl';
|
import { PureViewCtrl } from './../../views/abstract/pure_view_ctrl';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { WebDirective } from './../../types';
|
import { WebDirective } from './../../types';
|
||||||
import {
|
import { ContentType, PayloadSource, SNNote } from '@standardnotes/snjs';
|
||||||
ContentType,
|
|
||||||
PayloadSource,
|
|
||||||
SNComponent,
|
|
||||||
SNNote,
|
|
||||||
ComponentArea
|
|
||||||
} from '@standardnotes/snjs';
|
|
||||||
import template from '%/directives/revision-preview-modal.pug';
|
import template from '%/directives/revision-preview-modal.pug';
|
||||||
import { PayloadContent } from '@standardnotes/snjs';
|
import { PayloadContent } from '@standardnotes/snjs';
|
||||||
import { confirmDialog } from '@/services/alertService';
|
import { confirmDialog } from '@/services/alertService';
|
||||||
import { STRING_RESTORE_LOCKED_ATTEMPT } from '@/strings';
|
import { STRING_RESTORE_LOCKED_ATTEMPT } from '@/strings';
|
||||||
|
|
||||||
interface RevisionPreviewScope {
|
interface RevisionPreviewScope {
|
||||||
uuid: string
|
uuid: string;
|
||||||
content: PayloadContent
|
content: PayloadContent;
|
||||||
application: WebApplication
|
application: WebApplication;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RevisionPreviewModalCtrl extends PureViewCtrl implements RevisionPreviewScope {
|
type State = {
|
||||||
|
componentViewer?: ComponentViewer;
|
||||||
|
};
|
||||||
|
|
||||||
$element: JQLite
|
class RevisionPreviewModalCtrl
|
||||||
$timeout: ng.ITimeoutService
|
extends PureViewCtrl<unknown, State>
|
||||||
uuid!: string
|
implements RevisionPreviewScope
|
||||||
content!: PayloadContent
|
{
|
||||||
title?: string
|
$element: JQLite;
|
||||||
application!: WebApplication
|
$timeout: ng.ITimeoutService;
|
||||||
unregisterComponent?: any
|
uuid!: string;
|
||||||
note!: SNNote
|
content!: PayloadContent;
|
||||||
|
title?: string;
|
||||||
|
application!: WebApplication;
|
||||||
|
note!: SNNote;
|
||||||
private originalNote!: SNNote;
|
private originalNote!: SNNote;
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor($element: JQLite, $timeout: ng.ITimeoutService) {
|
||||||
$element: JQLite,
|
|
||||||
$timeout: ng.ITimeoutService
|
|
||||||
) {
|
|
||||||
super($timeout);
|
super($timeout);
|
||||||
this.$element = $element;
|
this.$element = $element;
|
||||||
this.$timeout = $timeout;
|
this.$timeout = $timeout;
|
||||||
@@ -43,53 +40,36 @@ class RevisionPreviewModalCtrl extends PureViewCtrl implements RevisionPreviewSc
|
|||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
this.configure();
|
this.configure();
|
||||||
|
super.$onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
if (this.unregisterComponent) {
|
if (this.state.componentViewer) {
|
||||||
this.unregisterComponent();
|
this.application.componentManager.destroyComponentViewer(
|
||||||
this.unregisterComponent = undefined;
|
this.state.componentViewer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
super.$onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
get componentManager() {
|
get componentManager() {
|
||||||
return this.application.componentManager!;
|
return this.application.componentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
async configure() {
|
async configure() {
|
||||||
this.note = await this.application.createTemplateItem(
|
this.note = (await this.application.createTemplateItem(
|
||||||
ContentType.Note,
|
ContentType.Note,
|
||||||
this.content
|
this.content
|
||||||
) as SNNote;
|
)) as SNNote;
|
||||||
this.originalNote = this.application.findItem(this.uuid) as SNNote;
|
this.originalNote = this.application.findItem(this.uuid) as SNNote;
|
||||||
const editorForNote = this.componentManager.editorForNote(this.originalNote);
|
const component = this.componentManager.editorForNote(this.originalNote);
|
||||||
if (editorForNote) {
|
if (component) {
|
||||||
/**
|
const componentViewer =
|
||||||
* Create temporary copy, as a lot of componentManager is uuid based, so might
|
this.application.componentManager.createComponentViewer(component);
|
||||||
* interfere with active editor. Be sure to copy only the content, as the top level
|
componentViewer.setReadonly(true);
|
||||||
* editor object has non-copyable properties like .window, which cannot be transfered
|
componentViewer.lockReadonly = true;
|
||||||
*/
|
componentViewer.overrideContextItem = this.note;
|
||||||
const editorCopy = await this.application.createTemplateItem(
|
this.setState({ componentViewer });
|
||||||
ContentType.Component,
|
|
||||||
editorForNote.safeContent
|
|
||||||
) as SNComponent;
|
|
||||||
this.componentManager.setReadonlyStateForComponent(editorCopy, true, true);
|
|
||||||
this.unregisterComponent = this.componentManager.registerHandler({
|
|
||||||
identifier: editorCopy.uuid,
|
|
||||||
areas: [ComponentArea.Editor],
|
|
||||||
contextRequestHandler: (componentUuid) => {
|
|
||||||
if (componentUuid === this.state.editor?.uuid) {
|
|
||||||
return this.note;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentForSessionKeyHandler: (key) => {
|
|
||||||
if (key === this.componentManager.sessionKeyForComponent(this.state.editor!)) {
|
|
||||||
return this.state.editor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({editor: editorCopy});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +78,19 @@ class RevisionPreviewModalCtrl extends PureViewCtrl implements RevisionPreviewSc
|
|||||||
if (asCopy) {
|
if (asCopy) {
|
||||||
await this.application.duplicateItem(this.originalNote, {
|
await this.application.duplicateItem(this.originalNote, {
|
||||||
...this.content,
|
...this.content,
|
||||||
title: this.content.title ? this.content.title + ' (copy)' : undefined
|
title: this.content.title
|
||||||
|
? this.content.title + ' (copy)'
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.application.changeAndSaveItem(this.uuid, (mutator) => {
|
this.application.changeAndSaveItem(
|
||||||
mutator.unsafe_setCustomContent(this.content);
|
this.uuid,
|
||||||
}, true, PayloadSource.RemoteActionRetrieved);
|
(mutator) => {
|
||||||
|
mutator.unsafe_setCustomContent(this.content);
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
PayloadSource.RemoteActionRetrieved
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.dismiss();
|
this.dismiss();
|
||||||
};
|
};
|
||||||
@@ -115,7 +102,7 @@ class RevisionPreviewModalCtrl extends PureViewCtrl implements RevisionPreviewSc
|
|||||||
}
|
}
|
||||||
confirmDialog({
|
confirmDialog({
|
||||||
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
|
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
|
||||||
confirmButtonStyle: "danger"
|
confirmButtonStyle: 'danger',
|
||||||
}).then((confirmed) => {
|
}).then((confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
run();
|
run();
|
||||||
@@ -146,7 +133,7 @@ export class RevisionPreviewModal extends WebDirective {
|
|||||||
uuid: '=',
|
uuid: '=',
|
||||||
content: '=',
|
content: '=',
|
||||||
title: '=',
|
title: '=',
|
||||||
application: '='
|
application: '=',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
app/assets/javascripts/preferences/panes/CloudLink.tsx
Normal file
115
app/assets/javascripts/preferences/panes/CloudLink.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
import {
|
||||||
|
Title,
|
||||||
|
Subtitle,
|
||||||
|
Text,
|
||||||
|
LinkButton,
|
||||||
|
PreferencesGroup,
|
||||||
|
PreferencesPane,
|
||||||
|
PreferencesSegment,
|
||||||
|
} from '../components';
|
||||||
|
|
||||||
|
export const CloudLink: FunctionComponent = () => (
|
||||||
|
<PreferencesPane>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Frequently asked questions</Title>
|
||||||
|
<div className="h-2 w-full" />
|
||||||
|
<Subtitle>Who can read my private notes?</Subtitle>
|
||||||
|
<Text>
|
||||||
|
Quite simply: no one but you. Not us, not your ISP, not a hacker, and
|
||||||
|
not a government agency. As long as you keep your password safe, and
|
||||||
|
your password is reasonably strong, then you are the only person in
|
||||||
|
the world with the ability to decrypt your notes. For more on how we
|
||||||
|
handle your privacy and security, check out our easy to read{' '}
|
||||||
|
<a target="_blank" href="https://standardnotes.com/privacy">
|
||||||
|
Privacy Manifesto.
|
||||||
|
</a>
|
||||||
|
</Text>
|
||||||
|
</PreferencesSegment>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
||||||
|
<Text>
|
||||||
|
Because of our encrypted architecture, Standard Notes does not
|
||||||
|
currently provide a real-time collaboration solution. Multiple users
|
||||||
|
can share the same account however, but editing at the same time may
|
||||||
|
result in sync conflicts, which may result in the duplication of
|
||||||
|
notes.
|
||||||
|
</Text>
|
||||||
|
</PreferencesSegment>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
||||||
|
<Text>
|
||||||
|
Standard Notes can be used totally offline without an account, and
|
||||||
|
without an internet connection. You can find{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline"
|
||||||
|
>
|
||||||
|
more details here.
|
||||||
|
</a>
|
||||||
|
</Text>
|
||||||
|
</PreferencesSegment>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Subtitle>Can’t find your question here?</Subtitle>
|
||||||
|
<LinkButton
|
||||||
|
className="mt-3"
|
||||||
|
label="Open FAQ"
|
||||||
|
link="https://standardnotes.com/help"
|
||||||
|
/>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Community forum</Title>
|
||||||
|
<Text>
|
||||||
|
If you have an issue, found a bug or want to suggest a feature, you
|
||||||
|
can browse or post to the forum. It’s recommended for non-account
|
||||||
|
related issues. Please read our{' '}
|
||||||
|
<a target="_blank" href="https://standardnotes.com/longevity/">
|
||||||
|
Longevity statement
|
||||||
|
</a>{' '}
|
||||||
|
before advocating for a feature request.
|
||||||
|
</Text>
|
||||||
|
<LinkButton
|
||||||
|
className="mt-3"
|
||||||
|
label="Go to the forum"
|
||||||
|
link="https://forum.standardnotes.org/"
|
||||||
|
/>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Community groups</Title>
|
||||||
|
<Text>
|
||||||
|
Want to meet other passionate note-takers and privacy enthusiasts?
|
||||||
|
Want to share your feedback with us? Join the Standard Notes community
|
||||||
|
groups for discussions on security, themes, editors and more.
|
||||||
|
</Text>
|
||||||
|
<LinkButton
|
||||||
|
className="mt-3"
|
||||||
|
link="https://standardnotes.com/slack"
|
||||||
|
label="Join our Slack"
|
||||||
|
/>
|
||||||
|
<LinkButton
|
||||||
|
className="mt-3"
|
||||||
|
link="https://standardnotes.com/discord"
|
||||||
|
label="Join our Discord"
|
||||||
|
/>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Account related issue?</Title>
|
||||||
|
<Text>
|
||||||
|
Send an email to help@standardnotes.com and we’ll sort it out.
|
||||||
|
</Text>
|
||||||
|
<LinkButton
|
||||||
|
className="mt-3"
|
||||||
|
link="mailto: help@standardnotes.com"
|
||||||
|
label="Email us"
|
||||||
|
/>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
</PreferencesPane>
|
||||||
|
);
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { PreferencesGroup, PreferencesSegment } from "@/preferences/components";
|
import { PreferencesGroup, PreferencesSegment } from '@/preferences/components';
|
||||||
import { WebApplication } from "@/ui_models/application";
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { SNComponent } from "@standardnotes/snjs/dist/@types";
|
import { ComponentViewer, SNComponent } from '@standardnotes/snjs/dist/@types';
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from 'mobx-react-lite';
|
||||||
import { FunctionComponent } from "preact";
|
import { FunctionComponent } from 'preact';
|
||||||
import { ExtensionItem } from "./extensions-segments";
|
import { ExtensionItem } from './extensions-segments';
|
||||||
import { ComponentView } from '@/components/ComponentView';
|
import { ComponentView } from '@/components/ComponentView';
|
||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { PreferencesMenu } from '@/preferences/PreferencesMenu';
|
import { PreferencesMenu } from '@/preferences/PreferencesMenu';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
@@ -17,7 +18,17 @@ interface IProps {
|
|||||||
|
|
||||||
export const ExtensionPane: FunctionComponent<IProps> = observer(
|
export const ExtensionPane: FunctionComponent<IProps> = observer(
|
||||||
({ extension, application, appState, preferencesMenu }) => {
|
({ extension, application, appState, preferencesMenu }) => {
|
||||||
const latestVersion = preferencesMenu.extensionsLatestVersions.getVersion(extension);
|
const [componentViewer] = useState<ComponentViewer>(
|
||||||
|
application.componentManager.createComponentViewer(extension)
|
||||||
|
);
|
||||||
|
const latestVersion =
|
||||||
|
preferencesMenu.extensionsLatestVersions.getVersion(extension);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
application.componentManager.destroyComponentViewer(componentViewer);
|
||||||
|
};
|
||||||
|
}, [application, componentViewer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="preferences-extension-pane color-foreground flex-grow flex flex-row overflow-y-auto min-h-0">
|
<div className="preferences-extension-pane color-foreground flex-grow flex flex-row overflow-y-auto min-h-0">
|
||||||
@@ -28,15 +39,18 @@ export const ExtensionPane: FunctionComponent<IProps> = observer(
|
|||||||
application={application}
|
application={application}
|
||||||
extension={extension}
|
extension={extension}
|
||||||
first={false}
|
first={false}
|
||||||
uninstall={() => application.deleteItem(extension).then(() => preferencesMenu.loadExtensionsPanes())}
|
uninstall={() =>
|
||||||
toggleActivate={() => application.toggleComponent(extension).then(() => preferencesMenu.loadExtensionsPanes())}
|
application
|
||||||
|
.deleteItem(extension)
|
||||||
|
.then(() => preferencesMenu.loadExtensionsPanes())
|
||||||
|
}
|
||||||
latestVersion={latestVersion}
|
latestVersion={latestVersion}
|
||||||
/>
|
/>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<ComponentView
|
<ComponentView
|
||||||
application={application}
|
application={application}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
componentUuid={extension.uuid}
|
componentViewer={componentViewer}
|
||||||
/>
|
/>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
@@ -44,4 +58,5 @@ export const ExtensionPane: FunctionComponent<IProps> = observer(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -77,11 +77,6 @@ export const Extensions: FunctionComponent<{
|
|||||||
setExtensions(loadExtensions(application));
|
setExtensions(loadExtensions(application));
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleActivateExtension = (extension: SNComponent) => {
|
|
||||||
application.toggleComponent(extension);
|
|
||||||
setExtensions(loadExtensions(application));
|
|
||||||
};
|
|
||||||
|
|
||||||
const visibleExtensions = extensions.filter((extension) => {
|
const visibleExtensions = extensions.filter((extension) => {
|
||||||
return (
|
return (
|
||||||
extension.package_info != undefined &&
|
extension.package_info != undefined &&
|
||||||
@@ -105,7 +100,6 @@ export const Extensions: FunctionComponent<{
|
|||||||
latestVersion={extensionsLatestVersions.getVersion(extension)}
|
latestVersion={extensionsLatestVersions.getVersion(extension)}
|
||||||
first={i === 0}
|
first={i === 0}
|
||||||
uninstall={uninstallExtension}
|
uninstall={uninstallExtension}
|
||||||
toggleActivate={toggleActivateExtension}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ const DisclosureIconButton: FunctionComponent<{
|
|||||||
<DisclosureButton
|
<DisclosureButton
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${className ?? ''
|
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${
|
||||||
}`}
|
className ?? ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Icon type={icon} />
|
<Icon type={icon} />
|
||||||
</DisclosureButton>
|
</DisclosureButton>
|
||||||
|
|||||||
@@ -1,40 +1,34 @@
|
|||||||
import {
|
import {
|
||||||
SNComponent,
|
SNComponent,
|
||||||
PurePayload,
|
|
||||||
ComponentMutator,
|
ComponentMutator,
|
||||||
AppDataField,
|
AppDataField,
|
||||||
EncryptionIntent,
|
EncryptionIntent,
|
||||||
ApplicationService,
|
ApplicationService,
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
removeFromArray,
|
removeFromArray,
|
||||||
BackupFile,
|
DesktopManagerInterface,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
/* eslint-disable camelcase */
|
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
// An interface used by the Desktop app to interact with SN
|
|
||||||
import { isDesktopApplication } from '@/utils';
|
import { isDesktopApplication } from '@/utils';
|
||||||
import { Bridge } from './bridge';
|
import { Bridge } from './bridge';
|
||||||
|
|
||||||
type UpdateObserverCallback = (component: SNComponent) => void;
|
/**
|
||||||
type ComponentActivationCallback = (payload: PurePayload) => void;
|
* An interface used by the Desktop application to interact with SN
|
||||||
type ComponentActivationObserver = {
|
*/
|
||||||
id: string;
|
export class DesktopManager
|
||||||
callback: ComponentActivationCallback;
|
extends ApplicationService
|
||||||
};
|
implements DesktopManagerInterface
|
||||||
|
{
|
||||||
export class DesktopManager extends ApplicationService {
|
|
||||||
$rootScope: ng.IRootScopeService;
|
$rootScope: ng.IRootScopeService;
|
||||||
$timeout: ng.ITimeoutService;
|
$timeout: ng.ITimeoutService;
|
||||||
componentActivationObservers: ComponentActivationObserver[] = [];
|
|
||||||
updateObservers: {
|
updateObservers: {
|
||||||
callback: UpdateObserverCallback;
|
callback: (component: SNComponent) => void;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
isDesktop = isDesktopApplication();
|
isDesktop = isDesktopApplication();
|
||||||
|
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
lastSearchedText?: string;
|
lastSearchedText?: string;
|
||||||
private removeComponentObserver?: () => void;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
$rootScope: ng.IRootScopeService,
|
$rootScope: ng.IRootScopeService,
|
||||||
@@ -52,10 +46,7 @@ export class DesktopManager extends ApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.componentActivationObservers.length = 0;
|
|
||||||
this.updateObservers.length = 0;
|
this.updateObservers.length = 0;
|
||||||
this.removeComponentObserver?.();
|
|
||||||
this.removeComponentObserver = undefined;
|
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +64,9 @@ export class DesktopManager extends ApplicationService {
|
|||||||
this.bridge.onMajorDataChange();
|
this.bridge.onMajorDataChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtServerHost() {
|
getExtServerHost(): string {
|
||||||
console.assert(!!this.bridge.extensionsServerHost, 'extServerHost is null');
|
console.assert(!!this.bridge.extensionsServerHost, 'extServerHost is null');
|
||||||
return this.bridge.extensionsServerHost;
|
return this.bridge.extensionsServerHost!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,7 +74,7 @@ export class DesktopManager extends ApplicationService {
|
|||||||
* Keys are not passed into ItemParams, so the result is not encrypted
|
* Keys are not passed into ItemParams, so the result is not encrypted
|
||||||
*/
|
*/
|
||||||
convertComponentForTransmission(component: SNComponent) {
|
convertComponentForTransmission(component: SNComponent) {
|
||||||
return this.application!.protocolService!.payloadByEncryptingPayload(
|
return this.application.protocolService!.payloadByEncryptingPayload(
|
||||||
component.payloadRepresentation(),
|
component.payloadRepresentation(),
|
||||||
EncryptionIntent.FileDecrypted
|
EncryptionIntent.FileDecrypted
|
||||||
);
|
);
|
||||||
@@ -107,7 +98,7 @@ export class DesktopManager extends ApplicationService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerUpdateObserver(callback: UpdateObserverCallback) {
|
registerUpdateObserver(callback: (component: SNComponent) => void) {
|
||||||
const observer = {
|
const observer = {
|
||||||
callback: callback,
|
callback: callback,
|
||||||
};
|
};
|
||||||
@@ -143,11 +134,11 @@ export class DesktopManager extends ApplicationService {
|
|||||||
componentData: any,
|
componentData: any,
|
||||||
error: any
|
error: any
|
||||||
) {
|
) {
|
||||||
const component = this.application!.findItem(componentData.uuid);
|
const component = this.application.findItem(componentData.uuid);
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updatedComponent = await this.application!.changeAndSaveItem(
|
const updatedComponent = await this.application.changeAndSaveItem(
|
||||||
component.uuid,
|
component.uuid,
|
||||||
(m) => {
|
(m) => {
|
||||||
const mutator = m as ComponentMutator;
|
const mutator = m as ComponentMutator;
|
||||||
@@ -168,34 +159,8 @@ export class DesktopManager extends ApplicationService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
desktop_registerComponentActivationObserver(
|
|
||||||
callback: ComponentActivationCallback
|
|
||||||
) {
|
|
||||||
const observer = { id: `${Math.random}`, callback: callback };
|
|
||||||
this.componentActivationObservers.push(observer);
|
|
||||||
return observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
desktop_deregisterComponentActivationObserver(
|
|
||||||
observer: ComponentActivationObserver
|
|
||||||
) {
|
|
||||||
removeFromArray(this.componentActivationObservers, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notify observers that a component has been registered/activated */
|
|
||||||
async notifyComponentActivation(component: SNComponent) {
|
|
||||||
const serializedComponent = await this.convertComponentForTransmission(
|
|
||||||
component
|
|
||||||
);
|
|
||||||
this.$timeout(() => {
|
|
||||||
for (const observer of this.componentActivationObservers) {
|
|
||||||
observer.callback(serializedComponent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async desktop_requestBackupFile() {
|
async desktop_requestBackupFile() {
|
||||||
const data = await this.application!.createBackupFile(
|
const data = await this.application.createBackupFile(
|
||||||
this.application.hasProtectionSources()
|
this.application.hasProtectionSources()
|
||||||
? EncryptionIntent.FileEncrypted
|
? EncryptionIntent.FileEncrypted
|
||||||
: EncryptionIntent.FileDecrypted
|
: EncryptionIntent.FileDecrypted
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import {
|
|||||||
removeFromArray,
|
removeFromArray,
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ContentType,
|
ContentType,
|
||||||
|
UuidString,
|
||||||
|
FeatureStatus,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
|
|
||||||
const CACHED_THEMES_KEY = 'cachedThemes';
|
const CACHED_THEMES_KEY = 'cachedThemes';
|
||||||
|
|
||||||
export class ThemeManager extends ApplicationService {
|
export class ThemeManager extends ApplicationService {
|
||||||
private activeThemes: string[] = [];
|
private activeThemes: UuidString[] = [];
|
||||||
private unregisterDesktop!: () => void;
|
private unregisterDesktop!: () => void;
|
||||||
private unregisterStream!: () => void;
|
private unregisterStream!: () => void;
|
||||||
|
|
||||||
@@ -22,6 +24,8 @@ export class ThemeManager extends ApplicationService {
|
|||||||
this.deactivateAllThemes();
|
this.deactivateAllThemes();
|
||||||
} else if (event === ApplicationEvent.StorageReady) {
|
} else if (event === ApplicationEvent.StorageReady) {
|
||||||
await this.activateCachedThemes();
|
await this.activateCachedThemes();
|
||||||
|
} else if (event === ApplicationEvent.FeaturesUpdated) {
|
||||||
|
this.reloadThemeStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,11 +38,24 @@ export class ThemeManager extends ApplicationService {
|
|||||||
this.activeThemes.length = 0;
|
this.activeThemes.length = 0;
|
||||||
this.unregisterDesktop();
|
this.unregisterDesktop();
|
||||||
this.unregisterStream();
|
this.unregisterStream();
|
||||||
(this.unregisterDesktop as any) = undefined;
|
(this.unregisterDesktop as unknown) = undefined;
|
||||||
(this.unregisterStream as any) = undefined;
|
(this.unregisterStream as unknown) = undefined;
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadThemeStatus(): void {
|
||||||
|
for (const themeUuid of this.activeThemes) {
|
||||||
|
const theme = this.application.findItem(themeUuid) as SNTheme;
|
||||||
|
if (
|
||||||
|
!theme ||
|
||||||
|
this.application.getFeatureStatus(theme.identifier) !==
|
||||||
|
FeatureStatus.Entitled
|
||||||
|
) {
|
||||||
|
this.deactivateTheme(themeUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async onAppStart() {
|
async onAppStart() {
|
||||||
super.onAppStart();
|
super.onAppStart();
|
||||||
@@ -99,7 +116,11 @@ export class ThemeManager extends ApplicationService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.activeThemes.push(theme.uuid);
|
this.activeThemes.push(theme.uuid);
|
||||||
const url = this.application!.componentManager!.urlForComponent(theme)!;
|
const url = this.application.componentManager.urlForComponent(theme);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const link = document.createElement('link');
|
const link = document.createElement('link');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.type = 'text/css';
|
link.type = 'text/css';
|
||||||
@@ -125,19 +146,19 @@ export class ThemeManager extends ApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async cacheThemes() {
|
private async cacheThemes() {
|
||||||
const themes = this.application!.getAll(this.activeThemes) as SNTheme[];
|
const themes = this.application.getAll(this.activeThemes) as SNTheme[];
|
||||||
const mapped = await Promise.all(
|
const mapped = await Promise.all(
|
||||||
themes.map(async (theme) => {
|
themes.map(async (theme) => {
|
||||||
const payload = theme.payloadRepresentation();
|
const payload = theme.payloadRepresentation();
|
||||||
const processedPayload =
|
const processedPayload =
|
||||||
await this.application!.protocolService!.payloadByEncryptingPayload(
|
await this.application.protocolService.payloadByEncryptingPayload(
|
||||||
payload,
|
payload,
|
||||||
EncryptionIntent.LocalStorageDecrypted
|
EncryptionIntent.LocalStorageDecrypted
|
||||||
);
|
);
|
||||||
return processedPayload;
|
return processedPayload;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return this.application!.setValue(
|
return this.application.setValue(
|
||||||
CACHED_THEMES_KEY,
|
CACHED_THEMES_KEY,
|
||||||
mapped,
|
mapped,
|
||||||
StorageValueModes.Nonwrapped
|
StorageValueModes.Nonwrapped
|
||||||
@@ -154,15 +175,15 @@ export class ThemeManager extends ApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getCachedThemes() {
|
private async getCachedThemes() {
|
||||||
const cachedThemes = (await this.application!.getValue(
|
const cachedThemes = (await this.application.getValue(
|
||||||
CACHED_THEMES_KEY,
|
CACHED_THEMES_KEY,
|
||||||
StorageValueModes.Nonwrapped
|
StorageValueModes.Nonwrapped
|
||||||
)) as SNTheme[];
|
)) as SNTheme[];
|
||||||
if (cachedThemes) {
|
if (cachedThemes) {
|
||||||
const themes = [];
|
const themes = [];
|
||||||
for (const cachedTheme of cachedThemes) {
|
for (const cachedTheme of cachedThemes) {
|
||||||
const payload = this.application!.createPayloadFromObject(cachedTheme);
|
const payload = this.application.createPayloadFromObject(cachedTheme);
|
||||||
const theme = this.application!.createItemFromPayload(
|
const theme = this.application.createItemFromPayload(
|
||||||
payload
|
payload
|
||||||
) as SNTheme;
|
) as SNTheme;
|
||||||
themes.push(theme);
|
themes.push(theme);
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ export class AppState {
|
|||||||
editingTag: SNTag | undefined;
|
editingTag: SNTag | undefined;
|
||||||
_templateTag: SNTag | undefined;
|
_templateTag: SNTag | undefined;
|
||||||
|
|
||||||
|
private multiEditorSupport = false;
|
||||||
|
|
||||||
readonly quickSettingsMenu = new QuickSettingsState();
|
readonly quickSettingsMenu = new QuickSettingsState();
|
||||||
readonly accountMenu: AccountMenuState;
|
readonly accountMenu: AccountMenuState;
|
||||||
readonly actionsMenu = new ActionsMenuState();
|
readonly actionsMenu = new ActionsMenuState();
|
||||||
@@ -224,27 +226,21 @@ export class AppState {
|
|||||||
storage.set(StorageKey.ShowBetaWarning, true);
|
storage.set(StorageKey.ShowBetaWarning, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new editor if one doesn't exist. If one does, we'll replace the
|
|
||||||
* editor's note with an empty one.
|
|
||||||
*/
|
|
||||||
async createEditor(title?: string) {
|
async createEditor(title?: string) {
|
||||||
const activeEditor = this.getActiveEditor();
|
if (!this.multiEditorSupport) {
|
||||||
|
this.closeActiveEditor();
|
||||||
|
}
|
||||||
const activeTagUuid = this.selectedTag
|
const activeTagUuid = this.selectedTag
|
||||||
? this.selectedTag.isSmartTag
|
? this.selectedTag.isSmartTag
|
||||||
? undefined
|
? undefined
|
||||||
: this.selectedTag.uuid
|
: this.selectedTag.uuid
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (!activeEditor) {
|
await this.application.editorGroup.createEditor(
|
||||||
this.application.editorGroup.createEditor(
|
undefined,
|
||||||
undefined,
|
title,
|
||||||
title,
|
activeTagUuid
|
||||||
activeTagUuid
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await activeEditor.reset(title, activeTagUuid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveEditor() {
|
getActiveEditor() {
|
||||||
|
|||||||
@@ -167,12 +167,12 @@ export class NotesState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.activeEditor) {
|
if (this.activeEditor) {
|
||||||
this.application.editorGroup.createEditor(noteUuid);
|
this.application.editorGroup.closeActiveEditor();
|
||||||
} else {
|
|
||||||
this.activeEditor.setNote(note);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.application.editorGroup.createEditor(noteUuid);
|
||||||
|
|
||||||
this.appState.noteTags.reloadTags();
|
this.appState.noteTags.reloadTags();
|
||||||
await this.onActiveEditorChanged();
|
await this.onActiveEditorChanged();
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { ContentType, SNSmartTag, SNTag, UuidString } from '@standardnotes/snjs';
|
import {
|
||||||
|
ContentType,
|
||||||
|
SNSmartTag,
|
||||||
|
SNTag,
|
||||||
|
UuidString,
|
||||||
|
} from '@standardnotes/snjs';
|
||||||
import {
|
import {
|
||||||
action,
|
action,
|
||||||
computed,
|
computed,
|
||||||
makeAutoObservable,
|
makeAutoObservable,
|
||||||
makeObservable,
|
makeObservable,
|
||||||
observable,
|
observable,
|
||||||
runInAction
|
runInAction,
|
||||||
} from 'mobx';
|
} from 'mobx';
|
||||||
import { WebApplication } from '../application';
|
import { WebApplication } from '../application';
|
||||||
import { FeaturesState } from './features_state';
|
import { FeaturesState } from './features_state';
|
||||||
|
|
||||||
|
|
||||||
export class TagsState {
|
export class TagsState {
|
||||||
tags: SNTag[] = [];
|
tags: SNTag[] = [];
|
||||||
smartTags: SNSmartTag[] = [];
|
smartTags: SNSmartTag[] = [];
|
||||||
|
|||||||
@@ -18,12 +18,9 @@ import {
|
|||||||
PermissionDialog,
|
PermissionDialog,
|
||||||
Platform,
|
Platform,
|
||||||
SNApplication,
|
SNApplication,
|
||||||
SNComponent,
|
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import { ComponentModalScope } from './../directives/views/componentModal';
|
|
||||||
import { AccountSwitcherScope, PermissionsModalScope } from './../types';
|
import { AccountSwitcherScope, PermissionsModalScope } from './../types';
|
||||||
import { ComponentGroup } from './component_group';
|
|
||||||
|
|
||||||
type WebServices = {
|
type WebServices = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -40,7 +37,6 @@ export class WebApplication extends SNApplication {
|
|||||||
private webServices!: WebServices;
|
private webServices!: WebServices;
|
||||||
private currentAuthenticationElement?: angular.IRootElementService;
|
private currentAuthenticationElement?: angular.IRootElementService;
|
||||||
public editorGroup: EditorGroup;
|
public editorGroup: EditorGroup;
|
||||||
public componentGroup: ComponentGroup;
|
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
@@ -71,8 +67,6 @@ export class WebApplication extends SNApplication {
|
|||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
deviceInterface.setApplication(this);
|
deviceInterface.setApplication(this);
|
||||||
this.editorGroup = new EditorGroup(this);
|
this.editorGroup = new EditorGroup(this);
|
||||||
this.componentGroup = new ComponentGroup(this);
|
|
||||||
this.openModalComponent = this.openModalComponent.bind(this);
|
|
||||||
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
|
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,14 +79,12 @@ export class WebApplication extends SNApplication {
|
|||||||
(service as any).application = undefined;
|
(service as any).application = undefined;
|
||||||
}
|
}
|
||||||
this.webServices = {} as WebServices;
|
this.webServices = {} as WebServices;
|
||||||
(this.$compile as any) = undefined;
|
(this.$compile as unknown) = undefined;
|
||||||
this.editorGroup.deinit();
|
this.editorGroup.deinit();
|
||||||
this.componentGroup.deinit();
|
(this.scope as any).application = undefined;
|
||||||
(this.scope! as any).application = undefined;
|
|
||||||
this.scope!.$destroy();
|
this.scope!.$destroy();
|
||||||
this.scope = undefined;
|
this.scope = undefined;
|
||||||
(this.openModalComponent as any) = undefined;
|
(this.presentPermissionsDialog as unknown) = undefined;
|
||||||
(this.presentPermissionsDialog as any) = undefined;
|
|
||||||
/** Allow our Angular directives to be destroyed and any pending digest cycles
|
/** Allow our Angular directives to be destroyed and any pending digest cycles
|
||||||
* to complete before destroying the global application instance and all its services */
|
* to complete before destroying the global application instance and all its services */
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -105,8 +97,7 @@ export class WebApplication extends SNApplication {
|
|||||||
|
|
||||||
onStart(): void {
|
onStart(): void {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
this.componentManager!.openModalComponent = this.openModalComponent;
|
this.componentManager.presentPermissionsDialog =
|
||||||
this.componentManager!.presentPermissionsDialog =
|
|
||||||
this.presentPermissionsDialog;
|
this.presentPermissionsDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,24 +201,6 @@ export class WebApplication extends SNApplication {
|
|||||||
this.applicationElement.append(el);
|
this.applicationElement.append(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
async openModalComponent(component: SNComponent): Promise<void> {
|
|
||||||
switch (component.package_info?.identifier) {
|
|
||||||
case 'org.standardnotes.cloudlink':
|
|
||||||
if (!(await this.authorizeCloudLinkAccess())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const scope = this.scope!.$new(true) as Partial<ComponentModalScope>;
|
|
||||||
scope.componentUuid = component.uuid;
|
|
||||||
scope.application = this;
|
|
||||||
const el = this.$compile!(
|
|
||||||
"<component-modal application='application' component-uuid='componentUuid' " +
|
|
||||||
"class='sk-modal'></component-modal>"
|
|
||||||
)(scope as any);
|
|
||||||
this.applicationElement.append(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
presentPermissionsDialog(dialog: PermissionDialog) {
|
presentPermissionsDialog(dialog: PermissionDialog) {
|
||||||
const scope = this.scope!.$new(true) as PermissionsModalScope;
|
const scope = this.scope!.$new(true) as PermissionsModalScope;
|
||||||
scope.permissionsString = dialog.permissionsString;
|
scope.permissionsString = dialog.permissionsString;
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
import { SNComponent, ComponentArea, removeFromArray, addIfUnique , UuidString } from '@standardnotes/snjs';
|
|
||||||
import { WebApplication } from './application';
|
|
||||||
|
|
||||||
/** Areas that only allow a single component to be active */
|
|
||||||
const SingleComponentAreas = [
|
|
||||||
ComponentArea.Editor,
|
|
||||||
ComponentArea.NoteTags,
|
|
||||||
ComponentArea.TagsList
|
|
||||||
];
|
|
||||||
|
|
||||||
export class ComponentGroup {
|
|
||||||
|
|
||||||
private application: WebApplication
|
|
||||||
changeObservers: any[] = []
|
|
||||||
activeComponents: UuidString[] = []
|
|
||||||
|
|
||||||
constructor(application: WebApplication) {
|
|
||||||
this.application = application;
|
|
||||||
}
|
|
||||||
|
|
||||||
get componentManager() {
|
|
||||||
return this.application.componentManager!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public deinit() {
|
|
||||||
(this.application as any) = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async activateComponent(component: SNComponent) {
|
|
||||||
if (this.activeComponents.includes(component.uuid)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SingleComponentAreas.includes(component.area)) {
|
|
||||||
const currentActive = this.activeComponentForArea(component.area);
|
|
||||||
if (currentActive) {
|
|
||||||
await this.deactivateComponent(currentActive, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addIfUnique(this.activeComponents, component.uuid);
|
|
||||||
await this.componentManager.activateComponent(component.uuid);
|
|
||||||
this.notifyObservers();
|
|
||||||
}
|
|
||||||
|
|
||||||
async deactivateComponent(component: SNComponent, notify = true) {
|
|
||||||
if (!this.activeComponents.includes(component.uuid)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeFromArray(this.activeComponents, component.uuid);
|
|
||||||
/** If this function is called as part of global application deinit (locking),
|
|
||||||
* componentManager can be destroyed. In this case, it's harmless to not take any
|
|
||||||
* action since the componentManager will be destroyed, and the component will
|
|
||||||
* essentially be deregistered. */
|
|
||||||
if(this.componentManager) {
|
|
||||||
await this.componentManager.deactivateComponent(component.uuid);
|
|
||||||
if(notify) {
|
|
||||||
this.notifyObservers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deactivateComponentForArea(area: ComponentArea) {
|
|
||||||
const component = this.activeComponentForArea(area);
|
|
||||||
if (component) {
|
|
||||||
return this.deactivateComponent(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activeComponentForArea(area: ComponentArea) {
|
|
||||||
return this.activeComponentsForArea(area)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
activeComponentsForArea(area: ComponentArea) {
|
|
||||||
return this.allActiveComponents().filter((c) => c.area === area);
|
|
||||||
}
|
|
||||||
|
|
||||||
allComponentsForArea(area: ComponentArea) {
|
|
||||||
return this.componentManager.componentsForArea(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
private allActiveComponents() {
|
|
||||||
return this.application.getAll(this.activeComponents) as SNComponent[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies observer when the active editor has changed.
|
|
||||||
*/
|
|
||||||
public addChangeObserver(callback: () => void) {
|
|
||||||
this.changeObservers.push(callback);
|
|
||||||
callback();
|
|
||||||
return () => {
|
|
||||||
removeFromArray(this.changeObservers, callback);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private notifyObservers() {
|
|
||||||
for (const observer of this.changeObservers) {
|
|
||||||
observer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,37 +3,36 @@ import {
|
|||||||
ContentType,
|
ContentType,
|
||||||
PayloadSource,
|
PayloadSource,
|
||||||
UuidString,
|
UuidString,
|
||||||
TagMutator,
|
|
||||||
SNTag,
|
SNTag,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import { WebApplication } from './application';
|
import { WebApplication } from './application';
|
||||||
import { NoteTagsState } from './app_state/note_tags_state';
|
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
public note!: SNNote;
|
public note!: SNNote;
|
||||||
private application: WebApplication;
|
private application: WebApplication;
|
||||||
private _onNoteChange?: () => void;
|
private onNoteValueChange?: (note: SNNote, source: PayloadSource) => void;
|
||||||
private _onNoteValueChange?: (note: SNNote, source?: PayloadSource) => void;
|
|
||||||
private removeStreamObserver?: () => void;
|
private removeStreamObserver?: () => void;
|
||||||
public isTemplateNote = false;
|
public isTemplateNote = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
application: WebApplication,
|
application: WebApplication,
|
||||||
noteUuid: string | undefined,
|
noteUuid: string | undefined,
|
||||||
noteTitle: string | undefined,
|
private defaultTitle: string | undefined,
|
||||||
noteTag: UuidString | undefined
|
private defaultTag: UuidString | undefined
|
||||||
) {
|
) {
|
||||||
this.application = application;
|
this.application = application;
|
||||||
if (noteUuid) {
|
if (noteUuid) {
|
||||||
this.note = application.findItem(noteUuid) as SNNote;
|
this.note = application.findItem(noteUuid) as SNNote;
|
||||||
this.streamItems();
|
|
||||||
} else {
|
|
||||||
this.reset(noteTitle, noteTag)
|
|
||||||
.then(() => this.streamItems())
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
if (!this.note) {
|
||||||
|
await this.createTemplateNote(this.defaultTitle, this.defaultTag);
|
||||||
|
}
|
||||||
|
this.streamItems();
|
||||||
|
}
|
||||||
|
|
||||||
private streamItems() {
|
private streamItems() {
|
||||||
this.removeStreamObserver = this.application.streamItems(
|
this.removeStreamObserver = this.application.streamItems(
|
||||||
ContentType.Note,
|
ContentType.Note,
|
||||||
@@ -45,14 +44,12 @@ export class Editor {
|
|||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.removeStreamObserver?.();
|
this.removeStreamObserver?.();
|
||||||
(this.removeStreamObserver as any) = undefined;
|
(this.removeStreamObserver as unknown) = undefined;
|
||||||
this._onNoteChange = undefined;
|
(this.application as unknown) = undefined;
|
||||||
(this.application as any) = undefined;
|
this.onNoteValueChange = undefined;
|
||||||
this._onNoteChange = undefined;
|
|
||||||
this._onNoteValueChange = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNoteStream(notes: SNNote[], source?: PayloadSource) {
|
private handleNoteStream(notes: SNNote[], source: PayloadSource) {
|
||||||
/** Update our note object reference whenever it changes */
|
/** Update our note object reference whenever it changes */
|
||||||
const matchingNote = notes.find((item) => {
|
const matchingNote = notes.find((item) => {
|
||||||
return item.uuid === this.note.uuid;
|
return item.uuid === this.note.uuid;
|
||||||
@@ -60,7 +57,7 @@ export class Editor {
|
|||||||
if (matchingNote) {
|
if (matchingNote) {
|
||||||
this.isTemplateNote = false;
|
this.isTemplateNote = false;
|
||||||
this.note = matchingNote;
|
this.note = matchingNote;
|
||||||
this._onNoteValueChange && this._onNoteValueChange!(matchingNote, source);
|
this.onNoteValueChange?.(matchingNote, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,53 +70,31 @@ export class Editor {
|
|||||||
* Reverts the editor to a blank state, removing any existing note from view,
|
* Reverts the editor to a blank state, removing any existing note from view,
|
||||||
* and creating a placeholder note.
|
* and creating a placeholder note.
|
||||||
*/
|
*/
|
||||||
async reset(noteTitle = '', noteTag?: UuidString) {
|
async createTemplateNote(defaultTitle?: string, noteTag?: UuidString) {
|
||||||
const note = (await this.application.createTemplateItem(ContentType.Note, {
|
const note = (await this.application.createTemplateItem(ContentType.Note, {
|
||||||
text: '',
|
text: '',
|
||||||
title: noteTitle,
|
title: defaultTitle,
|
||||||
references: [],
|
references: [],
|
||||||
})) as SNNote;
|
})) as SNNote;
|
||||||
if (noteTag) {
|
if (noteTag) {
|
||||||
const tag = this.application.findItem(noteTag) as SNTag;
|
const tag = this.application.findItem(noteTag) as SNTag;
|
||||||
await this.application.addTagHierarchyToNote(note, tag);
|
await this.application.addTagHierarchyToNote(note, tag);
|
||||||
}
|
}
|
||||||
if (!this.isTemplateNote || this.note.title !== note.title) {
|
this.isTemplateNote = true;
|
||||||
this.setNote(note as SNNote, true);
|
this.note = note;
|
||||||
}
|
this.onNoteValueChange?.(this.note, this.note.payload.source);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register to be notified when the editor's note changes.
|
|
||||||
*/
|
|
||||||
public onNoteChange(callback: () => void) {
|
|
||||||
this._onNoteChange = callback;
|
|
||||||
if (this.note) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public clearNoteChangeListener() {
|
|
||||||
this._onNoteChange = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register to be notified when the editor's note's values change
|
* Register to be notified when the editor's note's values change
|
||||||
* (and thus a new object reference is created)
|
* (and thus a new object reference is created)
|
||||||
*/
|
*/
|
||||||
public onNoteValueChange(
|
public setOnNoteValueChange(
|
||||||
callback: (note: SNNote, source?: PayloadSource) => void
|
callback: (note: SNNote, source: PayloadSource) => void
|
||||||
) {
|
) {
|
||||||
this._onNoteValueChange = callback;
|
this.onNoteValueChange = callback;
|
||||||
}
|
if (this.note) {
|
||||||
|
this.onNoteValueChange(this.note, this.note.payload.source);
|
||||||
/**
|
|
||||||
* Sets the editor contents by setting its note.
|
|
||||||
*/
|
|
||||||
public setNote(note: SNNote, isTemplate = false) {
|
|
||||||
this.note = note;
|
|
||||||
this.isTemplateNote = isTemplate;
|
|
||||||
if (this._onNoteChange) {
|
|
||||||
this._onNoteChange();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,31 @@ import { removeFromArray, UuidString } from '@standardnotes/snjs';
|
|||||||
import { Editor } from './editor';
|
import { Editor } from './editor';
|
||||||
import { WebApplication } from './application';
|
import { WebApplication } from './application';
|
||||||
|
|
||||||
type EditorGroupChangeCallback = () => void
|
type EditorGroupChangeCallback = () => void;
|
||||||
|
|
||||||
export class EditorGroup {
|
export class EditorGroup {
|
||||||
|
public editors: Editor[] = [];
|
||||||
public editors: Editor[] = []
|
private application: WebApplication;
|
||||||
private application: WebApplication
|
changeObservers: EditorGroupChangeCallback[] = [];
|
||||||
changeObservers: EditorGroupChangeCallback[] = []
|
|
||||||
|
|
||||||
constructor(application: WebApplication) {
|
constructor(application: WebApplication) {
|
||||||
this.application = application;
|
this.application = application;
|
||||||
}
|
}
|
||||||
|
|
||||||
public deinit() {
|
public deinit() {
|
||||||
(this.application as any) = undefined;
|
(this.application as unknown) = undefined;
|
||||||
for (const editor of this.editors) {
|
for (const editor of this.editors) {
|
||||||
this.deleteEditor(editor);
|
this.deleteEditor(editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createEditor(
|
async createEditor(
|
||||||
noteUuid?: string,
|
noteUuid?: string,
|
||||||
noteTitle?: string,
|
noteTitle?: string,
|
||||||
noteTag?: UuidString
|
noteTag?: UuidString
|
||||||
) {
|
) {
|
||||||
const editor = new Editor(this.application, noteUuid, noteTitle, noteTag);
|
const editor = new Editor(this.application, noteUuid, noteTitle, noteTag);
|
||||||
|
await editor.initialize();
|
||||||
this.editors.push(editor);
|
this.editors.push(editor);
|
||||||
this.notifyObservers();
|
this.notifyObservers();
|
||||||
}
|
}
|
||||||
@@ -43,13 +43,13 @@ export class EditorGroup {
|
|||||||
|
|
||||||
closeActiveEditor() {
|
closeActiveEditor() {
|
||||||
const activeEditor = this.activeEditor;
|
const activeEditor = this.activeEditor;
|
||||||
if(activeEditor) {
|
if (activeEditor) {
|
||||||
this.deleteEditor(activeEditor);
|
this.deleteEditor(activeEditor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAllEditors() {
|
closeAllEditors() {
|
||||||
for(const editor of this.editors) {
|
for (const editor of this.editors) {
|
||||||
this.deleteEditor(editor);
|
this.deleteEditor(editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,29 +3,26 @@ import { WebApplication } from '@/ui_models/application';
|
|||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { autorun, IReactionDisposer, IReactionPublic } from 'mobx';
|
import { autorun, IReactionDisposer, IReactionPublic } from 'mobx';
|
||||||
|
|
||||||
export type CtrlState = Partial<Record<string, any>>
|
export type CtrlState = Partial<Record<string, any>>;
|
||||||
export type CtrlProps = Partial<Record<string, any>>
|
export type CtrlProps = Partial<Record<string, any>>;
|
||||||
|
|
||||||
export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
||||||
$timeout: ng.ITimeoutService
|
$timeout: ng.ITimeoutService;
|
||||||
/** Passed through templates */
|
/** Passed through templates */
|
||||||
application!: WebApplication
|
application!: WebApplication;
|
||||||
state: S = {} as any
|
state: S = {} as any;
|
||||||
private unsubApp: any
|
private unsubApp: any;
|
||||||
private unsubState: any
|
private unsubState: any;
|
||||||
private stateTimeout?: ng.IPromise<void>
|
private stateTimeout?: ng.IPromise<void>;
|
||||||
/**
|
/**
|
||||||
* Subclasses can optionally add an ng-if=ctrl.templateReady to make sure that
|
* Subclasses can optionally add an ng-if=ctrl.templateReady to make sure that
|
||||||
* no Angular handlebars/syntax render in the UI before display data is ready.
|
* no Angular handlebars/syntax render in the UI before display data is ready.
|
||||||
*/
|
*/
|
||||||
protected templateReady = false
|
protected templateReady = false;
|
||||||
private reactionDisposers: IReactionDisposer[] = [];
|
private reactionDisposers: IReactionDisposer[] = [];
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor($timeout: ng.ITimeoutService, public props: P = {} as any) {
|
||||||
$timeout: ng.ITimeoutService,
|
|
||||||
public props: P = {} as any
|
|
||||||
) {
|
|
||||||
this.$timeout = $timeout;
|
this.$timeout = $timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +88,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
|||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
afterStateChange(): void {
|
afterStateChange(): void {}
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns a promise that resolves after the UI has been updated. */
|
/** @returns a promise that resolves after the UI has been updated. */
|
||||||
flushUI(): angular.IPromise<void> {
|
flushUI(): angular.IPromise<void> {
|
||||||
@@ -129,22 +125,24 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
|||||||
if (this.application!.isLaunched()) {
|
if (this.application!.isLaunched()) {
|
||||||
this.onAppLaunch();
|
this.onAppLaunch();
|
||||||
}
|
}
|
||||||
this.unsubApp = this.application!.addEventObserver(async (eventName, data: any) => {
|
this.unsubApp = this.application!.addEventObserver(
|
||||||
this.onAppEvent(eventName, data);
|
async (eventName, data: any) => {
|
||||||
if (eventName === ApplicationEvent.Started) {
|
this.onAppEvent(eventName, data);
|
||||||
await this.onAppStart();
|
if (eventName === ApplicationEvent.Started) {
|
||||||
} else if (eventName === ApplicationEvent.Launched) {
|
await this.onAppStart();
|
||||||
await this.onAppLaunch();
|
} else if (eventName === ApplicationEvent.Launched) {
|
||||||
} else if (eventName === ApplicationEvent.CompletedIncrementalSync) {
|
await this.onAppLaunch();
|
||||||
this.onAppIncrementalSync();
|
} else if (eventName === ApplicationEvent.CompletedIncrementalSync) {
|
||||||
} else if (eventName === ApplicationEvent.CompletedFullSync) {
|
this.onAppIncrementalSync();
|
||||||
this.onAppFullSync();
|
} else if (eventName === ApplicationEvent.CompletedFullSync) {
|
||||||
} else if (eventName === ApplicationEvent.KeyStatusChanged) {
|
this.onAppFullSync();
|
||||||
this.onAppKeyChange();
|
} else if (eventName === ApplicationEvent.KeyStatusChanged) {
|
||||||
} else if (eventName === ApplicationEvent.LocalDataLoaded) {
|
this.onAppKeyChange();
|
||||||
this.onLocalDataLoaded();
|
} else if (eventName === ApplicationEvent.LocalDataLoaded) {
|
||||||
|
this.onLocalDataLoaded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppEvent(eventName: ApplicationEvent, data?: any) {
|
onAppEvent(eventName: ApplicationEvent, data?: any) {
|
||||||
@@ -175,5 +173,4 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
|||||||
onAppFullSync() {
|
onAppFullSync() {
|
||||||
/** Optional override */
|
/** Optional override */
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,14 +115,17 @@ class ApplicationViewCtrl extends PureViewCtrl<
|
|||||||
/** @override */
|
/** @override */
|
||||||
async onAppEvent(eventName: ApplicationEvent) {
|
async onAppEvent(eventName: ApplicationEvent) {
|
||||||
super.onAppEvent(eventName);
|
super.onAppEvent(eventName);
|
||||||
if (eventName === ApplicationEvent.LocalDatabaseReadError) {
|
switch (eventName) {
|
||||||
alertDialog({
|
case ApplicationEvent.LocalDatabaseReadError:
|
||||||
text: 'Unable to load local database. Please restart the app and try again.',
|
alertDialog({
|
||||||
});
|
text: 'Unable to load local database. Please restart the app and try again.',
|
||||||
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
|
});
|
||||||
alertDialog({
|
break;
|
||||||
text: 'Unable to write to local database. Please restart the app and try again.',
|
case ApplicationEvent.LocalDatabaseWriteError:
|
||||||
});
|
alertDialog({
|
||||||
|
text: 'Unable to write to local database. Please restart the app and try again.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ class ChallengeModalCtrl extends PureViewCtrl<unknown, ChallengeModalState> {
|
|||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
render(<></>, this.$element[0]);
|
render(<></>, this.$element[0]);
|
||||||
|
super.$onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private render() {
|
private render() {
|
||||||
|
|||||||
@@ -34,10 +34,8 @@
|
|||||||
)
|
)
|
||||||
.title.overflow-auto
|
.title.overflow-auto
|
||||||
input#note-title-editor.input(
|
input#note-title-editor.input(
|
||||||
ng-blur='self.onTitleBlur()',
|
|
||||||
ng-change='self.onTitleChange()',
|
ng-change='self.onTitleChange()',
|
||||||
ng-disabled='self.noteLocked',
|
ng-disabled='self.noteLocked',
|
||||||
ng-focus='self.onTitleFocus()',
|
|
||||||
ng-keyup='$event.keyCode == 13 && self.onTitleEnter($event)',
|
ng-keyup='$event.keyCode == 13 && self.onTitleEnter($event)',
|
||||||
ng-model='self.editorValues.title',
|
ng-model='self.editorValues.title',
|
||||||
select-on-focus='true',
|
select-on-focus='true',
|
||||||
@@ -76,7 +74,7 @@
|
|||||||
callback='self.editorMenuOnSelect',
|
callback='self.editorMenuOnSelect',
|
||||||
current-item='self.note',
|
current-item='self.note',
|
||||||
ng-if='self.state.showEditorMenu',
|
ng-if='self.state.showEditorMenu',
|
||||||
selected-editor-uuid='self.state.editorComponent && self.state.editorComponent.uuid',
|
selected-editor-uuid='self.state.editorComponentViewer && self.state.editorComponentViewer.component.uuid',
|
||||||
application='self.application'
|
application='self.application'
|
||||||
)
|
)
|
||||||
.sk-app-bar-item(
|
.sk-app-bar-item(
|
||||||
@@ -114,9 +112,10 @@
|
|||||||
property="'left'"
|
property="'left'"
|
||||||
)
|
)
|
||||||
component-view.component-view(
|
component-view.component-view(
|
||||||
component-uuid='self.state.editorComponent.uuid',
|
component-viewer='self.state.editorComponentViewer',
|
||||||
ng-if='self.state.editorComponent && !self.state.editorUnloading',
|
ng-if='self.state.editorComponentViewer',
|
||||||
on-load='self.onEditorLoad',
|
on-load='self.onEditorLoad',
|
||||||
|
request-reload='self.editorComponentViewerRequestsReload'
|
||||||
application='self.application'
|
application='self.application'
|
||||||
app-state='self.appState'
|
app-state='self.appState'
|
||||||
)
|
)
|
||||||
@@ -126,7 +125,7 @@
|
|||||||
ng-change='self.contentChanged()',
|
ng-change='self.contentChanged()',
|
||||||
ng-click='self.clickedTextArea()',
|
ng-click='self.clickedTextArea()',
|
||||||
ng-focus='self.onContentFocus()',
|
ng-focus='self.onContentFocus()',
|
||||||
ng-if='!self.state.editorComponent && !self.state.textareaUnloading',
|
ng-if='self.state.editorStateDidLoad && !self.state.editorComponentViewer && !self.state.textareaUnloading',
|
||||||
ng-model='self.editorValues.text',
|
ng-model='self.editorValues.text',
|
||||||
ng-model-options='{ debounce: self.state.editorDebounce}',
|
ng-model-options='{ debounce: self.state.editorDebounce}',
|
||||||
ng-readonly='self.noteLocked',
|
ng-readonly='self.noteLocked',
|
||||||
@@ -156,24 +155,23 @@
|
|||||||
| There was an error decrypting this item. Ensure you are running the
|
| There was an error decrypting this item. Ensure you are running the
|
||||||
| latest version of this app, then sign out and sign back in to try again.
|
| latest version of this app, then sign out and sign back in to try again.
|
||||||
#editor-pane-component-stack(ng-if='!self.note.errorDecrypting' ng-show='self.note')
|
#editor-pane-component-stack(ng-if='!self.note.errorDecrypting' ng-show='self.note')
|
||||||
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.stackComponents.length')
|
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.availableStackComponents.length')
|
||||||
.left
|
.left
|
||||||
.sk-app-bar-item(
|
.sk-app-bar-item(
|
||||||
ng-repeat='component in self.state.stackComponents track by component.uuid'
|
ng-repeat='component in self.state.availableStackComponents track by component.uuid'
|
||||||
ng-click='self.toggleStackComponentForCurrentItem(component)',
|
ng-click='self.toggleStackComponent(component)',
|
||||||
)
|
)
|
||||||
.sk-app-bar-item-column
|
.sk-app-bar-item-column
|
||||||
.sk-circle.small(
|
.sk-circle.small(
|
||||||
ng-class="{'info' : !self.stackComponentHidden(component) && component.active, 'neutral' : self.stackComponentHidden(component) || !component.active}"
|
ng-class="{'info' : self.stackComponentExpanded(component) && component.active, 'neutral' : !self.stackComponentExpanded(component)}"
|
||||||
)
|
)
|
||||||
.sk-app-bar-item-column
|
.sk-app-bar-item-column
|
||||||
.sk-label {{component.name}}
|
.sk-label {{component.name}}
|
||||||
.sn-component
|
.sn-component
|
||||||
component-view.component-view.component-stack-item(
|
component-view.component-view.component-stack-item(
|
||||||
ng-repeat='component in self.state.stackComponents track by component.uuid',
|
ng-repeat='viewer in self.state.stackComponentViewers track by viewer.componentUuid',
|
||||||
component-uuid='component.uuid',
|
component-viewer='viewer',
|
||||||
manual-dealloc='true',
|
manual-dealloc='true',
|
||||||
ng-show='!self.stackComponentHidden(component)',
|
|
||||||
application='self.application'
|
application='self.application'
|
||||||
app-state='self.appState'
|
app-state='self.appState'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ import {
|
|||||||
SNComponent,
|
SNComponent,
|
||||||
SNNote,
|
SNNote,
|
||||||
NoteMutator,
|
NoteMutator,
|
||||||
Uuids,
|
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
ComponentMutator,
|
ComponentMutator,
|
||||||
PayloadSource,
|
PayloadSource,
|
||||||
|
ComponentViewer,
|
||||||
|
ComponentManagerEvent,
|
||||||
|
TransactionalMutation,
|
||||||
|
ItemMutator,
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import { isDesktopApplication } from '@/utils';
|
import { isDesktopApplication } from '@/utils';
|
||||||
@@ -52,8 +55,10 @@ type NoteStatus = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type EditorState = {
|
type EditorState = {
|
||||||
stackComponents: SNComponent[];
|
availableStackComponents: SNComponent[];
|
||||||
editorComponent?: SNComponent;
|
stackComponentViewers: ComponentViewer[];
|
||||||
|
editorComponentViewer?: ComponentViewer;
|
||||||
|
editorStateDidLoad: boolean;
|
||||||
saveError?: any;
|
saveError?: any;
|
||||||
noteStatus?: NoteStatus;
|
noteStatus?: NoteStatus;
|
||||||
marginResizersEnabled?: boolean;
|
marginResizersEnabled?: boolean;
|
||||||
@@ -64,11 +69,6 @@ type EditorState = {
|
|||||||
showEditorMenu: boolean;
|
showEditorMenu: boolean;
|
||||||
showHistoryMenu: boolean;
|
showHistoryMenu: boolean;
|
||||||
spellcheck: boolean;
|
spellcheck: boolean;
|
||||||
/**
|
|
||||||
* Setting to false then true will allow the current editor component-view to be destroyed
|
|
||||||
* then re-initialized. Used when changing between component editors.
|
|
||||||
*/
|
|
||||||
editorUnloading: boolean;
|
|
||||||
/** Setting to true then false will allow the main content textarea to be destroyed
|
/** Setting to true then false will allow the main content textarea to be destroyed
|
||||||
* then re-initialized. Used when reloading spellcheck status. */
|
* then re-initialized. Used when reloading spellcheck status. */
|
||||||
textareaUnloading: boolean;
|
textareaUnloading: boolean;
|
||||||
@@ -97,7 +97,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
|
|
||||||
private leftPanelPuppet?: PanelPuppet;
|
private leftPanelPuppet?: PanelPuppet;
|
||||||
private rightPanelPuppet?: PanelPuppet;
|
private rightPanelPuppet?: PanelPuppet;
|
||||||
private unregisterComponent: any;
|
|
||||||
private saveTimeout?: ng.IPromise<void>;
|
private saveTimeout?: ng.IPromise<void>;
|
||||||
private statusTimeout?: ng.IPromise<void>;
|
private statusTimeout?: ng.IPromise<void>;
|
||||||
private lastEditorFocusEventSource?: EventSource;
|
private lastEditorFocusEventSource?: EventSource;
|
||||||
@@ -105,10 +104,11 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
onEditorLoad?: () => void;
|
onEditorLoad?: () => void;
|
||||||
|
|
||||||
private scrollPosition = 0;
|
private scrollPosition = 0;
|
||||||
private removeTrashKeyObserver?: any;
|
private removeTrashKeyObserver?: () => void;
|
||||||
private removeTabObserver?: any;
|
private removeTabObserver?: () => void;
|
||||||
|
private removeComponentStreamObserver?: () => void;
|
||||||
|
private removeComponentManagerObserver?: () => void;
|
||||||
|
|
||||||
private removeComponentsObserver!: () => void;
|
|
||||||
private protectionTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
private protectionTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
@@ -125,25 +125,26 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
|
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
|
||||||
this.setScrollPosition = this.setScrollPosition.bind(this);
|
this.setScrollPosition = this.setScrollPosition.bind(this);
|
||||||
this.resetScrollPosition = this.resetScrollPosition.bind(this);
|
this.resetScrollPosition = this.resetScrollPosition.bind(this);
|
||||||
|
this.editorComponentViewerRequestsReload =
|
||||||
|
this.editorComponentViewerRequestsReload.bind(this);
|
||||||
this.onEditorLoad = () => {
|
this.onEditorLoad = () => {
|
||||||
this.application.getDesktopService().redoSearch();
|
this.application.getDesktopService().redoSearch();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.clearNoteProtectionInactivityTimer();
|
this.removeComponentStreamObserver?.();
|
||||||
this.editor.clearNoteChangeListener();
|
(this.removeComponentStreamObserver as unknown) = undefined;
|
||||||
this.removeComponentsObserver();
|
this.removeComponentManagerObserver?.();
|
||||||
(this.removeComponentsObserver as unknown) = undefined;
|
(this.removeComponentManagerObserver as unknown) = undefined;
|
||||||
this.removeTrashKeyObserver();
|
this.removeTrashKeyObserver?.();
|
||||||
this.removeTrashKeyObserver = undefined;
|
this.removeTrashKeyObserver = undefined;
|
||||||
this.removeTabObserver && this.removeTabObserver();
|
this.clearNoteProtectionInactivityTimer();
|
||||||
|
this.removeTabObserver?.();
|
||||||
this.removeTabObserver = undefined;
|
this.removeTabObserver = undefined;
|
||||||
this.leftPanelPuppet = undefined;
|
this.leftPanelPuppet = undefined;
|
||||||
this.rightPanelPuppet = undefined;
|
this.rightPanelPuppet = undefined;
|
||||||
this.onEditorLoad = undefined;
|
this.onEditorLoad = undefined;
|
||||||
this.unregisterComponent();
|
|
||||||
this.unregisterComponent = undefined;
|
|
||||||
this.saveTimeout = undefined;
|
this.saveTimeout = undefined;
|
||||||
this.statusTimeout = undefined;
|
this.statusTimeout = undefined;
|
||||||
(this.onPanelResizeFinish as unknown) = undefined;
|
(this.onPanelResizeFinish as unknown) = undefined;
|
||||||
@@ -162,55 +163,82 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
$onInit() {
|
$onInit() {
|
||||||
super.$onInit();
|
super.$onInit();
|
||||||
this.registerKeyboardShortcuts();
|
this.registerKeyboardShortcuts();
|
||||||
this.editor.onNoteChange(() => {
|
this.editor.setOnNoteValueChange((note, source) => {
|
||||||
this.handleEditorNoteChange();
|
this.onNoteChanges(note, source);
|
||||||
});
|
|
||||||
this.editor.onNoteValueChange((note, source) => {
|
|
||||||
if (isPayloadSourceRetrieved(source!)) {
|
|
||||||
this.editorValues.title = note.title;
|
|
||||||
this.editorValues.text = note.text;
|
|
||||||
}
|
|
||||||
if (!this.editorValues.title) {
|
|
||||||
this.editorValues.title = note.title;
|
|
||||||
}
|
|
||||||
if (!this.editorValues.text) {
|
|
||||||
this.editorValues.text = note.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTemplateNoteInsertedToBeInteractableWithEditor =
|
|
||||||
source === PayloadSource.Constructor && note.dirty;
|
|
||||||
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (note.lastSyncBegan || note.dirty) {
|
|
||||||
if (note.lastSyncEnd) {
|
|
||||||
if (
|
|
||||||
note.dirty ||
|
|
||||||
note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()
|
|
||||||
) {
|
|
||||||
this.showSavingStatus();
|
|
||||||
} else if (
|
|
||||||
note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()
|
|
||||||
) {
|
|
||||||
this.showAllChangesSavedStatus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showSavingStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.autorun(() => {
|
this.autorun(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showProtectedWarning: this.appState.notes.showProtectedWarning,
|
showProtectedWarning: this.appState.notes.showProtectedWarning,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this.reloadEditorComponent();
|
||||||
|
this.reloadStackComponents();
|
||||||
|
|
||||||
|
const showProtectedWarning =
|
||||||
|
this.note.protected && !this.application.hasProtectionSources();
|
||||||
|
this.setShowProtectedOverlay(showProtectedWarning);
|
||||||
|
|
||||||
|
this.reloadPreferences();
|
||||||
|
|
||||||
|
if (this.note.dirty) {
|
||||||
|
this.showSavingStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onNoteChanges(note: SNNote, source: PayloadSource): void {
|
||||||
|
if (note.uuid !== this.note.uuid) {
|
||||||
|
throw Error('Editor received changes for non-current note');
|
||||||
|
}
|
||||||
|
if (isPayloadSourceRetrieved(source)) {
|
||||||
|
this.editorValues.title = note.title;
|
||||||
|
this.editorValues.text = note.text;
|
||||||
|
}
|
||||||
|
if (!this.editorValues.title) {
|
||||||
|
this.editorValues.title = note.title;
|
||||||
|
}
|
||||||
|
if (!this.editorValues.text) {
|
||||||
|
this.editorValues.text = note.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTemplateNoteInsertedToBeInteractableWithEditor =
|
||||||
|
source === PayloadSource.Constructor && note.dirty;
|
||||||
|
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.lastSyncBegan || note.dirty) {
|
||||||
|
if (note.lastSyncEnd) {
|
||||||
|
if (
|
||||||
|
note.dirty ||
|
||||||
|
note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()
|
||||||
|
) {
|
||||||
|
this.showSavingStatus();
|
||||||
|
} else if (
|
||||||
|
note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()
|
||||||
|
) {
|
||||||
|
this.showAllChangesSavedStatus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showSavingStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$onDestroy(): void {
|
||||||
|
if (this.state.editorComponentViewer) {
|
||||||
|
this.application.componentManager?.destroyComponentViewer(
|
||||||
|
this.state.editorComponentViewer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
super.$onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
stackComponents: [],
|
availableStackComponents: [],
|
||||||
|
stackComponentViewers: [],
|
||||||
|
editorStateDidLoad: false,
|
||||||
editorDebounce: EDITOR_DEBOUNCE,
|
editorDebounce: EDITOR_DEBOUNCE,
|
||||||
isDesktop: isDesktopApplication(),
|
isDesktop: isDesktopApplication(),
|
||||||
spellcheck: true,
|
spellcheck: true,
|
||||||
@@ -219,7 +247,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
showEditorMenu: false,
|
showEditorMenu: false,
|
||||||
showHistoryMenu: false,
|
showHistoryMenu: false,
|
||||||
noteStatus: undefined,
|
noteStatus: undefined,
|
||||||
editorUnloading: false,
|
|
||||||
textareaUnloading: false,
|
textareaUnloading: false,
|
||||||
showProtectedWarning: false,
|
showProtectedWarning: false,
|
||||||
} as EditorState;
|
} as EditorState;
|
||||||
@@ -228,7 +255,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
async onAppLaunch() {
|
async onAppLaunch() {
|
||||||
await super.onAppLaunch();
|
await super.onAppLaunch();
|
||||||
this.streamItems();
|
this.streamItems();
|
||||||
this.registerComponentHandler();
|
this.registerComponentManagerEventObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -310,34 +337,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditorNoteChange() {
|
|
||||||
this.clearNoteProtectionInactivityTimer();
|
|
||||||
this.cancelPendingSetStatus();
|
|
||||||
const note = this.editor.note;
|
|
||||||
|
|
||||||
const showProtectedWarning =
|
|
||||||
note.protected &&
|
|
||||||
(!this.application.hasProtectionSources() ||
|
|
||||||
this.application.getProtectionSessionExpiryDate().getTime() <
|
|
||||||
Date.now());
|
|
||||||
|
|
||||||
this.setShowProtectedOverlay(showProtectedWarning);
|
|
||||||
await this.setState({
|
|
||||||
showActionsMenu: false,
|
|
||||||
showEditorMenu: false,
|
|
||||||
showHistoryMenu: false,
|
|
||||||
noteStatus: undefined,
|
|
||||||
});
|
|
||||||
this.editorValues.title = note.title;
|
|
||||||
this.editorValues.text = note.text;
|
|
||||||
this.reloadEditor();
|
|
||||||
this.reloadPreferences();
|
|
||||||
this.reloadStackComponents();
|
|
||||||
if (note.dirty) {
|
|
||||||
this.showSavingStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dismissProtectedWarning() {
|
async dismissProtectedWarning() {
|
||||||
let showNoteContents = true;
|
let showNoteContents = true;
|
||||||
if (this.application.hasProtectionSources()) {
|
if (this.application.hasProtectionSources()) {
|
||||||
@@ -366,20 +365,45 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamItems() {
|
streamItems() {
|
||||||
this.removeComponentsObserver = this.application.streamItems(
|
this.removeComponentStreamObserver = this.application.streamItems(
|
||||||
ContentType.Component,
|
ContentType.Component,
|
||||||
async (_items, source) => {
|
async (_items, source) => {
|
||||||
if (isPayloadSourceInternalChange(source!)) {
|
if (
|
||||||
|
isPayloadSourceInternalChange(source) ||
|
||||||
|
source === PayloadSource.InitialObserverRegistrationPush
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.note) return;
|
if (!this.note) return;
|
||||||
this.reloadStackComponents();
|
await this.reloadStackComponents();
|
||||||
this.reloadEditor();
|
await this.reloadEditorComponent();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async reloadEditor() {
|
private createComponentViewer(component: SNComponent) {
|
||||||
|
const viewer = this.application.componentManager.createComponentViewer(
|
||||||
|
component,
|
||||||
|
this.note.uuid
|
||||||
|
);
|
||||||
|
return viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async editorComponentViewerRequestsReload(
|
||||||
|
viewer: ComponentViewer
|
||||||
|
): Promise<void> {
|
||||||
|
const component = viewer.component;
|
||||||
|
this.application.componentManager.destroyComponentViewer(viewer);
|
||||||
|
await this.setState({
|
||||||
|
editorComponentViewer: undefined,
|
||||||
|
});
|
||||||
|
await this.setState({
|
||||||
|
editorComponentViewer: this.createComponentViewer(component),
|
||||||
|
editorStateDidLoad: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reloadEditorComponent() {
|
||||||
const newEditor = this.application.componentManager.editorForNote(
|
const newEditor = this.application.componentManager.editorForNote(
|
||||||
this.note
|
this.note
|
||||||
);
|
);
|
||||||
@@ -387,22 +411,29 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
if (newEditor && this.editor.isTemplateNote) {
|
if (newEditor && this.editor.isTemplateNote) {
|
||||||
await this.editor.insertTemplatedNote();
|
await this.editor.insertTemplatedNote();
|
||||||
}
|
}
|
||||||
const currentEditor = this.state.editorComponent;
|
const currentComponentViewer = this.state.editorComponentViewer;
|
||||||
if (currentEditor?.uuid !== newEditor?.uuid) {
|
|
||||||
|
if (currentComponentViewer?.componentUuid !== newEditor?.uuid) {
|
||||||
|
if (currentComponentViewer) {
|
||||||
|
this.application.componentManager.destroyComponentViewer(
|
||||||
|
currentComponentViewer
|
||||||
|
);
|
||||||
|
}
|
||||||
await this.setState({
|
await this.setState({
|
||||||
/** Unload current component view so that we create a new one */
|
editorComponentViewer: undefined,
|
||||||
editorUnloading: true,
|
|
||||||
});
|
|
||||||
await this.setState({
|
|
||||||
/** Reload component view */
|
|
||||||
editorComponent: newEditor,
|
|
||||||
editorUnloading: false,
|
|
||||||
});
|
});
|
||||||
|
if (newEditor) {
|
||||||
|
await this.setState({
|
||||||
|
editorComponentViewer: this.createComponentViewer(newEditor),
|
||||||
|
editorStateDidLoad: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.reloadFont();
|
this.reloadFont();
|
||||||
|
} else {
|
||||||
|
await this.setState({
|
||||||
|
editorStateDidLoad: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.application.componentManager.contextItemDidChangeInArea(
|
|
||||||
ComponentArea.Editor
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setMenuState(menu: string, state: boolean) {
|
setMenuState(menu: string, state: boolean) {
|
||||||
@@ -429,6 +460,8 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async editorMenuOnSelect(component?: SNComponent) {
|
async editorMenuOnSelect(component?: SNComponent) {
|
||||||
|
const transactions: TransactionalMutation[] = [];
|
||||||
|
|
||||||
this.setMenuState('showEditorMenu', false);
|
this.setMenuState('showEditorMenu', false);
|
||||||
if (this.appState.getActiveEditor()?.isTemplateNote) {
|
if (this.appState.getActiveEditor()?.isTemplateNote) {
|
||||||
await this.appState.getActiveEditor().insertTemplatedNote();
|
await this.appState.getActiveEditor().insertTemplatedNote();
|
||||||
@@ -439,43 +472,56 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
}
|
}
|
||||||
if (!component) {
|
if (!component) {
|
||||||
if (!this.note.prefersPlainEditor) {
|
if (!this.note.prefersPlainEditor) {
|
||||||
await this.application.changeItem(this.note.uuid, (mutator) => {
|
transactions.push({
|
||||||
const noteMutator = mutator as NoteMutator;
|
itemUuid: this.note.uuid,
|
||||||
noteMutator.prefersPlainEditor = true;
|
mutate: (m: ItemMutator) => {
|
||||||
|
const noteMutator = m as NoteMutator;
|
||||||
|
noteMutator.prefersPlainEditor = true;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.reloadEditor();
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.state.editorComponent?.isExplicitlyEnabledForItem(this.note.uuid)
|
this.state.editorComponentViewer?.component.isExplicitlyEnabledForItem(
|
||||||
|
this.note.uuid
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
await this.disassociateComponentWithCurrentNote(
|
transactions.push(
|
||||||
this.state.editorComponent
|
this.transactionForDisassociateComponentWithCurrentNote(
|
||||||
|
this.state.editorComponentViewer.component
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.reloadFont();
|
this.reloadFont();
|
||||||
} else if (component.area === ComponentArea.Editor) {
|
} else if (component.area === ComponentArea.Editor) {
|
||||||
const currentEditor = this.state.editorComponent;
|
const currentEditor = this.state.editorComponentViewer?.component;
|
||||||
if (currentEditor && component.uuid !== currentEditor.uuid) {
|
if (currentEditor && component.uuid !== currentEditor.uuid) {
|
||||||
await this.disassociateComponentWithCurrentNote(currentEditor);
|
transactions.push(
|
||||||
|
this.transactionForDisassociateComponentWithCurrentNote(currentEditor)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const prefersPlain = this.note.prefersPlainEditor;
|
const prefersPlain = this.note.prefersPlainEditor;
|
||||||
if (prefersPlain) {
|
if (prefersPlain) {
|
||||||
await this.application.changeItem(this.note.uuid, (mutator) => {
|
transactions.push({
|
||||||
const noteMutator = mutator as NoteMutator;
|
itemUuid: this.note.uuid,
|
||||||
noteMutator.prefersPlainEditor = false;
|
mutate: (m: ItemMutator) => {
|
||||||
|
const noteMutator = m as NoteMutator;
|
||||||
|
noteMutator.prefersPlainEditor = false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await this.associateComponentWithCurrentNote(component);
|
transactions.push(
|
||||||
} else if (component.area === ComponentArea.EditorStack) {
|
this.transactionForAssociateComponentWithCurrentNote(component)
|
||||||
await this.toggleStackComponentForCurrentItem(component);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.application.runTransactionalMutations(transactions);
|
||||||
/** Dirtying can happen above */
|
/** Dirtying can happen above */
|
||||||
this.application.sync();
|
this.application.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAvailableExtensions() {
|
hasAvailableExtensions() {
|
||||||
return (
|
return (
|
||||||
this.application.actionsManager!.extensionsInContextOfItem(this.note)
|
this.application.actionsManager.extensionsInContextOfItem(this.note)
|
||||||
.length > 0
|
.length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -534,7 +580,9 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
const truncate = noteText.length > NOTE_PREVIEW_CHAR_LIMIT;
|
const truncate = noteText.length > NOTE_PREVIEW_CHAR_LIMIT;
|
||||||
const substring = noteText.substring(0, NOTE_PREVIEW_CHAR_LIMIT);
|
const substring = noteText.substring(0, NOTE_PREVIEW_CHAR_LIMIT);
|
||||||
const previewPlain = substring + (truncate ? STRING_ELLIPSES : '');
|
const previewPlain = substring + (truncate ? STRING_ELLIPSES : '');
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
noteMutator.preview_plain = previewPlain;
|
noteMutator.preview_plain = previewPlain;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
noteMutator.preview_html = undefined;
|
noteMutator.preview_html = undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -643,12 +691,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
this.closeAllMenus();
|
this.closeAllMenus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
onTitleFocus() {}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
onTitleBlur() {}
|
|
||||||
|
|
||||||
onContentFocus() {
|
onContentFocus() {
|
||||||
this.application
|
this.application
|
||||||
.getAppState()
|
.getAppState()
|
||||||
@@ -662,11 +704,11 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
|
|
||||||
async deleteNote(permanently: boolean) {
|
async deleteNote(permanently: boolean) {
|
||||||
if (this.editor.isTemplateNote) {
|
if (this.editor.isTemplateNote) {
|
||||||
this.application.alertService!.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT);
|
this.application.alertService.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.note.locked) {
|
if (this.note.locked) {
|
||||||
this.application.alertService!.alert(STRING_DELETE_LOCKED_ATTEMPT);
|
this.application.alertService.alert(STRING_DELETE_LOCKED_ATTEMPT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const title = this.note.safeTitle().length
|
const title = this.note.safeTitle().length
|
||||||
@@ -782,25 +824,15 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
|
|
||||||
/** @components */
|
/** @components */
|
||||||
|
|
||||||
registerComponentHandler() {
|
registerComponentManagerEventObserver() {
|
||||||
this.unregisterComponent =
|
this.removeComponentManagerObserver =
|
||||||
this.application.componentManager.registerHandler({
|
this.application.componentManager.addEventObserver((eventName, data) => {
|
||||||
identifier: 'editor',
|
if (eventName === ComponentManagerEvent.ViewerDidFocus) {
|
||||||
areas: [ComponentArea.EditorStack, ComponentArea.Editor],
|
const viewer = data?.componentViewer;
|
||||||
contextRequestHandler: (componentUuid) => {
|
if (viewer?.component.isEditor) {
|
||||||
const currentEditor = this.state.editorComponent;
|
|
||||||
if (
|
|
||||||
componentUuid === currentEditor?.uuid ||
|
|
||||||
Uuids(this.state.stackComponents).includes(componentUuid)
|
|
||||||
) {
|
|
||||||
return this.note;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
focusHandler: (component, focused) => {
|
|
||||||
if (component.isEditor() && focused) {
|
|
||||||
this.closeAllMenus();
|
this.closeAllMenus();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,58 +842,98 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
.componentsForArea(ComponentArea.EditorStack)
|
.componentsForArea(ComponentArea.EditorStack)
|
||||||
.filter((component) => component.active)
|
.filter((component) => component.active)
|
||||||
);
|
);
|
||||||
if (this.note) {
|
const enabledComponents = stackComponents.filter((component) => {
|
||||||
for (const component of stackComponents) {
|
return component.isExplicitlyEnabledForItem(this.note.uuid);
|
||||||
if (component.active) {
|
});
|
||||||
this.application.componentManager.setComponentHidden(
|
|
||||||
component,
|
const needsNewViewer = enabledComponents.filter((component) => {
|
||||||
!component.isExplicitlyEnabledForItem(this.note.uuid)
|
const hasExistingViewer = this.state.stackComponentViewers.find(
|
||||||
);
|
(viewer) => viewer.componentUuid === component.uuid
|
||||||
}
|
);
|
||||||
|
return !hasExistingViewer;
|
||||||
|
});
|
||||||
|
|
||||||
|
const needsDestroyViewer = this.state.stackComponentViewers.filter(
|
||||||
|
(viewer) => {
|
||||||
|
const viewerComponentExistsInEnabledComponents = enabledComponents.find(
|
||||||
|
(component) => {
|
||||||
|
return component.uuid === viewer.componentUuid;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return !viewerComponentExistsInEnabledComponents;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const newViewers: ComponentViewer[] = [];
|
||||||
|
for (const component of needsNewViewer) {
|
||||||
|
newViewers.push(
|
||||||
|
this.application.componentManager.createComponentViewer(
|
||||||
|
component,
|
||||||
|
this.note.uuid
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await this.setState({ stackComponents });
|
|
||||||
this.application.componentManager.contextItemDidChangeInArea(
|
for (const viewer of needsDestroyViewer) {
|
||||||
ComponentArea.EditorStack
|
this.application.componentManager.destroyComponentViewer(viewer);
|
||||||
|
}
|
||||||
|
await this.setState({
|
||||||
|
availableStackComponents: stackComponents,
|
||||||
|
stackComponentViewers: newViewers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stackComponentExpanded(component: SNComponent): boolean {
|
||||||
|
return !!this.state.stackComponentViewers.find(
|
||||||
|
(viewer) => viewer.componentUuid === component.uuid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
stackComponentHidden(component: SNComponent) {
|
async toggleStackComponent(component: SNComponent) {
|
||||||
return this.application.componentManager?.isComponentHidden(component);
|
if (!component.isExplicitlyEnabledForItem(this.note.uuid)) {
|
||||||
}
|
|
||||||
|
|
||||||
async toggleStackComponentForCurrentItem(component: SNComponent) {
|
|
||||||
const hidden =
|
|
||||||
this.application.componentManager.isComponentHidden(component);
|
|
||||||
if (hidden || !component.active) {
|
|
||||||
this.application.componentManager.setComponentHidden(component, false);
|
|
||||||
await this.associateComponentWithCurrentNote(component);
|
await this.associateComponentWithCurrentNote(component);
|
||||||
this.application.componentManager.contextItemDidChangeInArea(
|
|
||||||
ComponentArea.EditorStack
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.application.componentManager.setComponentHidden(component, true);
|
|
||||||
await this.disassociateComponentWithCurrentNote(component);
|
await this.disassociateComponentWithCurrentNote(component);
|
||||||
}
|
}
|
||||||
this.application.sync();
|
this.application.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async disassociateComponentWithCurrentNote(component: SNComponent) {
|
async disassociateComponentWithCurrentNote(component: SNComponent) {
|
||||||
|
return this.application.runTransactionalMutation(
|
||||||
|
this.transactionForDisassociateComponentWithCurrentNote(component)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionForDisassociateComponentWithCurrentNote(component: SNComponent) {
|
||||||
const note = this.note;
|
const note = this.note;
|
||||||
return this.application.changeItem(component.uuid, (m) => {
|
const transaction: TransactionalMutation = {
|
||||||
const mutator = m as ComponentMutator;
|
itemUuid: component.uuid,
|
||||||
mutator.removeAssociatedItemId(note.uuid);
|
mutate: (m: ItemMutator) => {
|
||||||
mutator.disassociateWithItem(note.uuid);
|
const mutator = m as ComponentMutator;
|
||||||
});
|
mutator.removeAssociatedItemId(note.uuid);
|
||||||
|
mutator.disassociateWithItem(note.uuid);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
async associateComponentWithCurrentNote(component: SNComponent) {
|
async associateComponentWithCurrentNote(component: SNComponent) {
|
||||||
|
return this.application.runTransactionalMutation(
|
||||||
|
this.transactionForAssociateComponentWithCurrentNote(component)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionForAssociateComponentWithCurrentNote(component: SNComponent) {
|
||||||
const note = this.note;
|
const note = this.note;
|
||||||
return this.application.changeItem(component.uuid, (m) => {
|
const transaction: TransactionalMutation = {
|
||||||
const mutator = m as ComponentMutator;
|
itemUuid: component.uuid,
|
||||||
mutator.removeDisassociatedItemId(note.uuid);
|
mutate: (m: ItemMutator) => {
|
||||||
mutator.associateWithItem(note.uuid);
|
const mutator = m as ComponentMutator;
|
||||||
});
|
mutator.removeDisassociatedItemId(note.uuid);
|
||||||
|
mutator.associateWithItem(note.uuid);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerKeyboardShortcuts() {
|
registerKeyboardShortcuts() {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ContentType,
|
ContentType,
|
||||||
SNTheme,
|
SNTheme,
|
||||||
ComponentArea,
|
|
||||||
CollectionSort,
|
CollectionSort,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import template from './footer-view.pug';
|
import template from './footer-view.pug';
|
||||||
@@ -43,7 +42,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
> {
|
> {
|
||||||
private $rootScope: ng.IRootScopeService;
|
private $rootScope: ng.IRootScopeService;
|
||||||
private showSyncResolution = false;
|
private showSyncResolution = false;
|
||||||
private unregisterComponent: any;
|
|
||||||
private rootScopeListener2: any;
|
private rootScopeListener2: any;
|
||||||
public arbitraryStatusMessage?: string;
|
public arbitraryStatusMessage?: string;
|
||||||
public user?: any;
|
public user?: any;
|
||||||
@@ -73,8 +71,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
deinit() {
|
deinit() {
|
||||||
for (const remove of this.observerRemovers) remove();
|
for (const remove of this.observerRemovers) remove();
|
||||||
this.observerRemovers.length = 0;
|
this.observerRemovers.length = 0;
|
||||||
this.unregisterComponent();
|
|
||||||
this.unregisterComponent = undefined;
|
|
||||||
this.rootScopeListener2();
|
this.rootScopeListener2();
|
||||||
this.rootScopeListener2 = undefined;
|
this.rootScopeListener2 = undefined;
|
||||||
(this.closeAccountMenu as unknown) = undefined;
|
(this.closeAccountMenu as unknown) = undefined;
|
||||||
@@ -146,7 +142,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
this.updateOfflineStatus();
|
this.updateOfflineStatus();
|
||||||
this.findErrors();
|
this.findErrors();
|
||||||
this.streamItems();
|
this.streamItems();
|
||||||
this.registerComponentHandler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadUser() {
|
reloadUser() {
|
||||||
@@ -273,25 +268,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerComponentHandler() {
|
|
||||||
this.unregisterComponent =
|
|
||||||
this.application.componentManager.registerHandler({
|
|
||||||
identifier: 'room-bar',
|
|
||||||
areas: [ComponentArea.Modal],
|
|
||||||
focusHandler: (component, focused) => {
|
|
||||||
if (component.isEditor() && focused) {
|
|
||||||
if (
|
|
||||||
component.package_info?.identifier ===
|
|
||||||
'org.standardnotes.standard-sheets'
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.closeAccountMenu();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSyncStatus() {
|
updateSyncStatus() {
|
||||||
const statusManager = this.application.getStatusManager();
|
const statusManager = this.application.getStatusManager();
|
||||||
const syncStatus = this.application.getSyncStatus();
|
const syncStatus = this.application.getSyncStatus();
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#tags-column.sn-component.section.tags(aria-label='Tags')
|
#tags-column.sn-component.section.tags(aria-label='Tags')
|
||||||
.component-view-container(ng-if='self.component')
|
.component-view-container(ng-if='self.state.componentViewer')
|
||||||
component-view.component-view(
|
component-view.component-view(
|
||||||
component-uuid='self.component.uuid',
|
component-viewer='self.state.componentViewer',
|
||||||
application='self.application'
|
application='self.application'
|
||||||
app-state='self.appState'
|
app-state='self.appState'
|
||||||
)
|
)
|
||||||
#tags-content.content(ng-if='!(self.component)')
|
#tags-content.content(ng-if='!(self.state.componentViewer)')
|
||||||
.tags-title-section.section-title-bar
|
.tags-title-section.section-title-bar
|
||||||
.section-title-bar-header
|
.section-title-bar-header
|
||||||
.sk-h3.title
|
.sk-h3.title
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import {
|
|||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ComponentAction,
|
ComponentAction,
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
|
ComponentViewer,
|
||||||
ContentType,
|
ContentType,
|
||||||
|
isPayloadSourceInternalChange,
|
||||||
|
MessageData,
|
||||||
|
PayloadSource,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
SNComponent,
|
SNComponent,
|
||||||
SNSmartTag,
|
SNSmartTag,
|
||||||
@@ -22,14 +26,14 @@ type TagState = {
|
|||||||
smartTags: SNSmartTag[];
|
smartTags: SNSmartTag[];
|
||||||
noteCounts: NoteCounts;
|
noteCounts: NoteCounts;
|
||||||
selectedTag?: SNTag;
|
selectedTag?: SNTag;
|
||||||
|
componentViewer?: ComponentViewer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||||
/** Passed through template */
|
/** Passed through template */
|
||||||
readonly application!: WebApplication;
|
readonly application!: WebApplication;
|
||||||
private readonly panelPuppet: PanelPuppet;
|
private readonly panelPuppet: PanelPuppet;
|
||||||
private unregisterComponent?: any;
|
private unregisterComponent?: () => void;
|
||||||
component?: SNComponent;
|
|
||||||
/** The original name of the edtingTag before it began editing */
|
/** The original name of the edtingTag before it began editing */
|
||||||
formData: { tagTitle?: string } = {};
|
formData: { tagTitle?: string } = {};
|
||||||
titles: Partial<Record<UuidString, string>> = {};
|
titles: Partial<Record<UuidString, string>> = {};
|
||||||
@@ -46,9 +50,9 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
|||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.removeTagsObserver?.();
|
this.removeTagsObserver?.();
|
||||||
(this.removeTagsObserver as any) = undefined;
|
(this.removeTagsObserver as unknown) = undefined;
|
||||||
(this.removeFoldersObserver as any) = undefined;
|
(this.removeFoldersObserver as unknown) = undefined;
|
||||||
this.unregisterComponent();
|
this.unregisterComponent?.();
|
||||||
this.unregisterComponent = undefined;
|
this.unregisterComponent = undefined;
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
@@ -64,15 +68,10 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
|||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onAppStart() {
|
|
||||||
super.onAppStart();
|
|
||||||
this.registerComponentHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onAppLaunch() {
|
async onAppLaunch() {
|
||||||
super.onAppLaunch();
|
super.onAppLaunch();
|
||||||
this.loadPreferences();
|
this.loadPreferences();
|
||||||
this.beginStreamingItems();
|
this.streamForFoldersComponent();
|
||||||
|
|
||||||
const smartTags = this.application.getSmartTags();
|
const smartTags = this.application.getSmartTags();
|
||||||
this.setState({ smartTags });
|
this.setState({ smartTags });
|
||||||
@@ -85,13 +84,78 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
|||||||
this.reloadNoteCounts();
|
this.reloadNoteCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
beginStreamingItems() {
|
async setFoldersComponent(component?: SNComponent) {
|
||||||
|
if (this.state.componentViewer) {
|
||||||
|
this.application.componentManager.destroyComponentViewer(
|
||||||
|
this.state.componentViewer
|
||||||
|
);
|
||||||
|
await this.setState({ componentViewer: undefined });
|
||||||
|
}
|
||||||
|
if (component) {
|
||||||
|
await this.setState({
|
||||||
|
componentViewer:
|
||||||
|
this.application.componentManager.createComponentViewer(
|
||||||
|
component,
|
||||||
|
undefined,
|
||||||
|
this.handleFoldersComponentMessage.bind(this)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFoldersComponentMessage(
|
||||||
|
action: ComponentAction,
|
||||||
|
data: MessageData
|
||||||
|
): void {
|
||||||
|
if (action === ComponentAction.SelectItem) {
|
||||||
|
const item = data.item;
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.content_type === ContentType.Tag) {
|
||||||
|
const matchingTag = this.application.findItem(item.uuid);
|
||||||
|
|
||||||
|
if (matchingTag) {
|
||||||
|
this.selectTag(matchingTag as SNTag);
|
||||||
|
}
|
||||||
|
} else if (item.content_type === ContentType.SmartTag) {
|
||||||
|
const matchingTag = this.getState().smartTags.find(
|
||||||
|
(t) => t.uuid === item.uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingTag) {
|
||||||
|
this.selectTag(matchingTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (action === ComponentAction.ClearSelection) {
|
||||||
|
this.selectTag(this.getState().smartTags[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streamForFoldersComponent() {
|
||||||
this.removeFoldersObserver = this.application.streamItems(
|
this.removeFoldersObserver = this.application.streamItems(
|
||||||
[ContentType.Component],
|
[ContentType.Component],
|
||||||
async () => {
|
async (items, source) => {
|
||||||
this.component = this.application.componentManager
|
if (
|
||||||
.componentsForArea(ComponentArea.TagsList).find((component) => component.active);
|
isPayloadSourceInternalChange(source) ||
|
||||||
});
|
source === PayloadSource.InitialObserverRegistrationPush
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const components = items as SNComponent[];
|
||||||
|
const hasFoldersChange = !!components.find(
|
||||||
|
(component) => component.area === ComponentArea.TagsList
|
||||||
|
);
|
||||||
|
if (hasFoldersChange) {
|
||||||
|
this.setFoldersComponent(
|
||||||
|
this.application.componentManager
|
||||||
|
.componentsForArea(ComponentArea.TagsList)
|
||||||
|
.find((component) => component.active)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.removeTagsObserver = this.application.streamItems(
|
this.removeTagsObserver = this.application.streamItems(
|
||||||
[ContentType.Tag, ContentType.SmartTag],
|
[ContentType.Tag, ContentType.SmartTag],
|
||||||
@@ -200,41 +264,6 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
|||||||
this.application.getAppState().panelDidResize(PANEL_NAME_TAGS, isCollapsed);
|
this.application.getAppState().panelDidResize(PANEL_NAME_TAGS, isCollapsed);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerComponentHandler() {
|
|
||||||
this.unregisterComponent =
|
|
||||||
this.application.componentManager.registerHandler({
|
|
||||||
identifier: 'tags',
|
|
||||||
areas: [ComponentArea.TagsList],
|
|
||||||
actionHandler: (_, action, data) => {
|
|
||||||
if (action === ComponentAction.SelectItem) {
|
|
||||||
const item = data.item;
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.content_type === ContentType.Tag) {
|
|
||||||
const matchingTag = this.application.findItem(item.uuid);
|
|
||||||
|
|
||||||
if (matchingTag) {
|
|
||||||
this.selectTag(matchingTag as SNTag);
|
|
||||||
}
|
|
||||||
} else if (item.content_type === ContentType.SmartTag) {
|
|
||||||
const matchingTag = this.getState().smartTags.find(
|
|
||||||
(t) => t.uuid === item.uuid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchingTag) {
|
|
||||||
this.selectTag(matchingTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (action === ComponentAction.ClearSelection) {
|
|
||||||
this.selectTag(this.getState().smartTags[0]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectTag(tag: SNTag) {
|
async selectTag(tag: SNTag) {
|
||||||
if (tag.conflictOf) {
|
if (tag.conflictOf) {
|
||||||
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
|
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
.sk-modal-background(ng-click="ctrl.dismiss()")
|
|
||||||
.sk-modal-content(
|
|
||||||
ng-attr-id="component-content-outer-{{ctrl.component.uuid}}"
|
|
||||||
)
|
|
||||||
.sn-component
|
|
||||||
.sk-panel(
|
|
||||||
ng-attr-id="component-content-inner-{{ctrl.component.uuid}}"
|
|
||||||
)
|
|
||||||
.sk-panel-header
|
|
||||||
.sk-panel-header-title
|
|
||||||
| {{ctrl.component.name}}
|
|
||||||
a.sk-a.info.close-button(ng-click="ctrl.dismiss()") Close
|
|
||||||
component-view.component-view(
|
|
||||||
ng-if='ctrl.component.active'
|
|
||||||
component-uuid="ctrl.component.uuid",
|
|
||||||
application='ctrl.application'
|
|
||||||
app-state='self.appState'
|
|
||||||
)
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
.sn-component
|
.sn-component
|
||||||
.sk-panel
|
.sk-panel
|
||||||
.sk-panel-header
|
.sk-panel-header
|
||||||
.sk-panel-header-title Activate Extension
|
.sk-panel-header-title Activate Component
|
||||||
a.sk-a.info.close-button(ng-click='ctrl.deny()') Cancel
|
a.sk-a.info.close-button(ng-click='ctrl.deny()') Cancel
|
||||||
.sk-panel-content
|
.sk-panel-content
|
||||||
.sk-panel-section
|
.sk-panel-section
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
| {{ctrl.permissionsString}}
|
| {{ctrl.permissionsString}}
|
||||||
.sk-panel-row
|
.sk-panel-row
|
||||||
p.sk-p
|
p.sk-p
|
||||||
| Extensions use an offline messaging system to communicate. Learn more at
|
| Components use an offline messaging system to communicate. Learn more at
|
||||||
|
|
|
|
||||||
a.sk-a.info(
|
a.sk-a.info(
|
||||||
href='https://standardnotes.com/permissions',
|
href='https://standardnotes.com/permissions',
|
||||||
|
|||||||
@@ -20,18 +20,18 @@
|
|||||||
a.sk-a.info.close-button(
|
a.sk-a.info.close-button(
|
||||||
ng-click="ctrl.dismiss(); $event.stopPropagation()"
|
ng-click="ctrl.dismiss(); $event.stopPropagation()"
|
||||||
) Close
|
) Close
|
||||||
.sk-panel-content.selectable(ng-if="!ctrl.state.editor")
|
.sk-panel-content.selectable(ng-if="!ctrl.state.componentViewer")
|
||||||
.sk-h2 {{ctrl.content.title}}
|
.sk-h2 {{ctrl.content.title}}
|
||||||
p.normal.sk-p(
|
p.normal.sk-p(
|
||||||
style="white-space: pre-wrap; font-size: 16px;"
|
style="white-space: pre-wrap; font-size: 16px;"
|
||||||
) {{ctrl.content.text}}
|
) {{ctrl.content.text}}
|
||||||
.sk-panel-content.sk-h2(
|
.sk-panel-content.sk-h2(
|
||||||
ng-if="ctrl.state.editor"
|
ng-if="ctrl.state.componentViewer"
|
||||||
style="height: auto; flex-grow: 0"
|
style="height: auto; flex-grow: 0"
|
||||||
) {{ctrl.content.title}}
|
) {{ctrl.content.title}}
|
||||||
component-view.component-view(
|
component-view.component-view(
|
||||||
ng-if="ctrl.state.editor",
|
ng-if="ctrl.state.componentViewer",
|
||||||
template-component="ctrl.state.editor",
|
component-viewer="ctrl.state.componentViewer",
|
||||||
application='ctrl.application'
|
application='ctrl.application'
|
||||||
app-state='self.appState'
|
app-state='self.appState'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
"@standardnotes/features": "1.10.2",
|
"@standardnotes/features": "1.10.2",
|
||||||
"@reach/tooltip": "^0.16.2",
|
"@reach/tooltip": "^0.16.2",
|
||||||
"@standardnotes/sncrypto-web": "1.5.3",
|
"@standardnotes/sncrypto-web": "1.5.3",
|
||||||
"@standardnotes/snjs": "2.25.0",
|
"@standardnotes/snjs": "2.29.0",
|
||||||
"mobx": "^6.3.5",
|
"mobx": "^6.3.5",
|
||||||
"mobx-react-lite": "^3.2.2",
|
"mobx-react-lite": "^3.2.2",
|
||||||
"preact": "^10.5.15",
|
"preact": "^10.5.15",
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"identifier": "org.standardnotes.markdown-basic-local",
|
||||||
|
"name": "Markdown Basic - Local",
|
||||||
|
"content_type": "SN|Component",
|
||||||
|
"area": "editor-editor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Write Markdown in private",
|
||||||
|
"url": "http://localhost:8004/dist/index.html",
|
||||||
|
"thumbnail_url": "http://localhost:8004/dist/favicon.png"
|
||||||
|
}
|
||||||
55
yarn.lock
55
yarn.lock
@@ -2597,6 +2597,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@sinonjs/commons" "^1.7.0"
|
"@sinonjs/commons" "^1.7.0"
|
||||||
|
|
||||||
|
"@standardnotes/auth@3.8.1":
|
||||||
|
version "3.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.8.1.tgz#4197fb2f7e223c6bd13a870a3feac3c73294fb3c"
|
||||||
|
integrity sha512-Q2/81dgFGIGuYlQ4VnSjGRsDB0Qw0tQP/qsiuV+DQj+wdp5Wy5WX3Q4g+p2PNvoyEAYgbuduEHZfWuTLAaIdyw==
|
||||||
|
dependencies:
|
||||||
|
"@standardnotes/common" "^1.2.1"
|
||||||
|
|
||||||
"@standardnotes/auth@3.8.3", "@standardnotes/auth@^3.8.1":
|
"@standardnotes/auth@3.8.3", "@standardnotes/auth@^3.8.1":
|
||||||
version "3.8.3"
|
version "3.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.8.3.tgz#6e627c1a1a9ebf91d97f52950d099bf7704382e3"
|
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.8.3.tgz#6e627c1a1a9ebf91d97f52950d099bf7704382e3"
|
||||||
@@ -2604,15 +2611,15 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/common" "^1.2.1"
|
"@standardnotes/common" "^1.2.1"
|
||||||
|
|
||||||
"@standardnotes/common@^1.2.1":
|
"@standardnotes/common@1.2.1", "@standardnotes/common@^1.2.1":
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.2.1.tgz#9db212db86ccbf08b347da02549b3dbe4bedbb02"
|
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.2.1.tgz#9db212db86ccbf08b347da02549b3dbe4bedbb02"
|
||||||
integrity sha512-HilBxS50CBlC6TJvy1mrnhGVDzOH63M/Jf+hyMxQ0Vt1nYzpd0iyxVEUrgMh7ZiyO1b9CLnCDED99Jy9rnZWVQ==
|
integrity sha512-HilBxS50CBlC6TJvy1mrnhGVDzOH63M/Jf+hyMxQ0Vt1nYzpd0iyxVEUrgMh7ZiyO1b9CLnCDED99Jy9rnZWVQ==
|
||||||
|
|
||||||
"@standardnotes/domain-events@^2.5.1":
|
"@standardnotes/domain-events@2.5.1":
|
||||||
version "2.10.0"
|
version "2.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.10.0.tgz#719c430d1736daffcb4233aa3381b58280564dc0"
|
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.5.1.tgz#e6433e940ae616683d1c24f76133c70755504c44"
|
||||||
integrity sha512-8jvkhNjoYrXN81RA8Q4vGEKH9R002Y/aEK29GyxmQmijT5+JwlA4f0ySycz5sJxWGULohL1k96RueYPs97hV3g==
|
integrity sha512-p0VB4Al/ZcVqcj9ztU7TNqzc3jjjG6/U7x9lBW/QURHxpB+PnwJq3kFU5V5JA9QpCOYlXLT71CMERMf/O5QX6g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.8.1"
|
"@standardnotes/auth" "^3.8.1"
|
||||||
|
|
||||||
@@ -2624,20 +2631,20 @@
|
|||||||
"@standardnotes/auth" "3.8.3"
|
"@standardnotes/auth" "3.8.3"
|
||||||
"@standardnotes/common" "^1.2.1"
|
"@standardnotes/common" "^1.2.1"
|
||||||
|
|
||||||
"@standardnotes/features@^1.10.3":
|
"@standardnotes/features@1.11.0":
|
||||||
version "1.10.3"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.10.3.tgz#f5824342446e69f006ea8ac8916203d1d3992f21"
|
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.11.0.tgz#66e960a20358c5f58b6be4e19226b34df6f4efbf"
|
||||||
integrity sha512-PU4KthoDr6NL1bOfKnYV1WXYqRu1/IcdkZkJa2LHcYMPduUjDUKO6qRK73dF0+EEI1U+YXY/9rHyfadGwd0Ymg==
|
integrity sha512-KMP60C1lf5C141s5VVOs7mISS1IUCioJfYsbsxtPycx9Q1mgbDB3xJv/GuCK/avsQOiGrB7QN06CQJ7fw4XV3Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "3.8.3"
|
"@standardnotes/auth" "3.8.3"
|
||||||
"@standardnotes/common" "^1.2.1"
|
"@standardnotes/common" "^1.2.1"
|
||||||
|
|
||||||
"@standardnotes/settings@^1.2.1":
|
"@standardnotes/settings@^1.8.0":
|
||||||
version "1.4.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.4.0.tgz#b8f43383fa1b469d609f5ac6c2ce571efb8bfe71"
|
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.8.0.tgz#d7bd1f35c3b500d12ba73f5f385b1019baae3efc"
|
||||||
integrity sha512-mGptrIaM/3UWOkc9xmzuRRM2A75caX6vyqCeKhyqPdM3ZR/YpYH7I6qYDsO6wpkoF3soD2nRJ6pLV7HBjGdGag==
|
integrity sha512-gszghenDHFHoEeIGW7fHtIOJw35WpuaOdTD6UNMrH71xEduAr0JtzaLwrFXJ7XIp62zY7CSY1V6Npxo6HTGn+w==
|
||||||
|
|
||||||
"@standardnotes/sncrypto-common@^1.5.2":
|
"@standardnotes/sncrypto-common@1.5.2", "@standardnotes/sncrypto-common@^1.5.2":
|
||||||
version "1.5.2"
|
version "1.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/sncrypto-common/-/sncrypto-common-1.5.2.tgz#be9404689d94f953c68302609a4f76751eaa82cd"
|
resolved "https://registry.yarnpkg.com/@standardnotes/sncrypto-common/-/sncrypto-common-1.5.2.tgz#be9404689d94f953c68302609a4f76751eaa82cd"
|
||||||
integrity sha512-+OQ6gajTcVSHruw33T52MHyBDKL1vRCfQBXQn4tt4+bCfBAe+PFLkEQMHp35bg5twCfg9+wUf2KhmNNSNyBBZw==
|
integrity sha512-+OQ6gajTcVSHruw33T52MHyBDKL1vRCfQBXQn4tt4+bCfBAe+PFLkEQMHp35bg5twCfg9+wUf2KhmNNSNyBBZw==
|
||||||
@@ -2651,17 +2658,17 @@
|
|||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
libsodium-wrappers "^0.7.9"
|
libsodium-wrappers "^0.7.9"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.25.0":
|
"@standardnotes/snjs@2.29.0":
|
||||||
version "2.25.0"
|
version "2.29.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.25.0.tgz#742ee451547c1f36d29bb3e58678068d6a455520"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.29.0.tgz#6c7c6ccd983df4a1a5e2063647eb731304002fd9"
|
||||||
integrity sha512-Sb2sZItuxWAFepbNyqGvH8CIC436VirEjAqc0NK9+1CK0wqPLfpCiDBEkTzsQa2UovnoKu/p4tpTrMnx3FvK2A==
|
integrity sha512-Y+GpNiFyJtVr2W3nVbC2zljtXpBlqe3cB4+R1REE0V4hnQBaq/HE6PaUd80TnFj99Kl8lowyH/o4bNV3+CjGgg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.8.1"
|
"@standardnotes/auth" "3.8.1"
|
||||||
"@standardnotes/common" "^1.2.1"
|
"@standardnotes/common" "1.2.1"
|
||||||
"@standardnotes/domain-events" "^2.5.1"
|
"@standardnotes/domain-events" "2.5.1"
|
||||||
"@standardnotes/features" "^1.10.3"
|
"@standardnotes/features" "1.11.0"
|
||||||
"@standardnotes/settings" "^1.2.1"
|
"@standardnotes/settings" "^1.8.0"
|
||||||
"@standardnotes/sncrypto-common" "^1.5.2"
|
"@standardnotes/sncrypto-common" "1.5.2"
|
||||||
|
|
||||||
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
||||||
version "5.4.0"
|
version "5.4.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user