Merge branch 'release/10.16.0'

This commit is contained in:
Mo
2022-03-10 11:18:01 -06:00
55 changed files with 1752 additions and 520 deletions

View File

@@ -4,7 +4,6 @@ declare global {
interface Window {
bugsnagApiKey?: string;
dashboardUrl?: string;
defaultFilesHost: string;
defaultSyncServer: string;
devAccountEmail?: string;
devAccountPassword?: string;
@@ -30,7 +29,6 @@ import { isDev } from './utils';
const startApplication: StartApplication = async function startApplication(
defaultSyncServerHost: string,
defaultFilesHostHost: string,
bridge: Bridge,
enableUnfinishedFeatures: boolean,
webSocketUrl: string
@@ -40,7 +38,6 @@ const startApplication: StartApplication = async function startApplication(
const mainApplicationGroup = new ApplicationGroup(
defaultSyncServerHost,
defaultFilesHostHost,
bridge,
enableUnfinishedFeatures ? Runtime.Dev : Runtime.Prod,
webSocketUrl
@@ -75,7 +72,6 @@ const startApplication: StartApplication = async function startApplication(
if (IsWebPlatform) {
startApplication(
window.defaultSyncServer,
window.defaultFilesHost,
new BrowserBridge(WebAppVersion),
window.enabledUnfinishedFeatures,
window.websocketUrl

View File

@@ -24,23 +24,23 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
({ application, appState, setMenuPane, closeMenu }) => {
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false);
const [lastSyncDate, setLastSyncDate] = useState(
formatLastSyncDate(application.getLastSyncDate() as Date)
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
);
const doSynchronization = async () => {
setIsSyncingInProgress(true);
application
application.sync
.sync({
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true,
})
.then((res) => {
if (res && res.error) {
if (res && (res as any).error) {
throw new Error();
} else {
setLastSyncDate(
formatLastSyncDate(application.getLastSyncDate() as Date)
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
);
}
})

View File

@@ -8,7 +8,6 @@ import {
removeFromArray,
} from '@standardnotes/snjs';
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/constants';
import { STRING_DEFAULT_FILE_ERROR } from '@/strings';
import { alertDialog } from '@/services/alertService';
import { WebAppEvent, WebApplication } from '@/ui_models/application';
import { PureComponent } from '@/components/Abstract/PureComponent';
@@ -51,17 +50,10 @@ export class ApplicationView extends PureComponent<Props, State> {
appClass: '',
challenges: [],
};
this.onDragDrop = this.onDragDrop.bind(this);
this.onDragOver = this.onDragOver.bind(this);
this.addDragDropHandlers();
}
deinit() {
(this.application as unknown) = undefined;
window.removeEventListener('dragover', this.onDragOver, true);
window.removeEventListener('drop', this.onDragDrop, true);
(this.onDragDrop as unknown) = undefined;
(this.onDragOver as unknown) = undefined;
super.deinit();
}
@@ -145,36 +137,11 @@ export class ApplicationView extends PureComponent<Props, State> {
this.setState({ appClass });
} else if (eventName === AppStateEvent.WindowDidFocus) {
if (!(await this.application.isLocked())) {
this.application.sync();
this.application.sync.sync();
}
}
}
addDragDropHandlers() {
/**
* Disable dragging and dropping of files (but allow text) into main SN interface.
* both 'dragover' and 'drop' are required to prevent dropping of files.
* This will not prevent extensions from receiving drop events.
*/
window.addEventListener('dragover', this.onDragOver, true);
window.addEventListener('drop', this.onDragDrop, true);
}
onDragOver(event: DragEvent) {
if (event.dataTransfer?.files.length) {
event.preventDefault();
}
}
onDragDrop(event: DragEvent) {
if (event.dataTransfer?.files.length) {
event.preventDefault();
void alertDialog({
text: STRING_DEFAULT_FILE_ERROR,
});
}
}
async handleDemoSignInFromParams() {
if (
window.location.href.includes('demo') &&

View File

@@ -0,0 +1,260 @@
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { MENU_MARGIN_FROM_APP_BORDER } from '@/constants';
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@reach/disclosure';
import VisuallyHidden from '@reach/visually-hidden';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { Icon } from '../Icon';
import { useCloseOnClickOutside } from '../utils';
import { ChallengeReason, ContentType, SNFile } from '@standardnotes/snjs';
import { confirmDialog } from '@/services/alertService';
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit';
import { parseFileName } from '@standardnotes/filepicker';
import {
PopoverFileItemAction,
PopoverFileItemActionType,
} from './PopoverFileItemAction';
import { PopoverDragNDropWrapper } from './PopoverDragNDropWrapper';
type Props = {
application: WebApplication;
appState: AppState;
onClickPreprocessing?: () => Promise<void>;
};
export const AttachedFilesButton: FunctionComponent<Props> = observer(
({ application, appState, onClickPreprocessing }) => {
const note = Object.values(appState.notes.selectedNotes)[0];
const [open, setOpen] = useState(false);
const [position, setPosition] = useState({
top: 0,
right: 0,
});
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto');
const buttonRef = useRef<HTMLButtonElement>(null);
const panelRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
useCloseOnClickOutside(containerRef, () => {
setOpen(false);
});
const [attachedFilesCount, setAttachedFilesCount] = useState(
note ? application.items.getFilesForNote(note).length : 0
);
const reloadAttachedFilesCount = useCallback(() => {
setAttachedFilesCount(
note ? application.items.getFilesForNote(note).length : 0
);
}, [application.items, note]);
useEffect(() => {
const unregisterFileStream = application.streamItems(
ContentType.File,
() => {
reloadAttachedFilesCount();
}
);
return () => {
unregisterFileStream();
};
}, [application, reloadAttachedFilesCount]);
const toggleAttachedFilesMenu = async () => {
const rect = buttonRef.current?.getBoundingClientRect();
if (rect) {
const { clientHeight } = document.documentElement;
const footerElementRect = document
.getElementById('footer-bar')
?.getBoundingClientRect();
const footerHeightInPx = footerElementRect?.height;
if (footerHeightInPx) {
setMaxHeight(
clientHeight -
rect.bottom -
footerHeightInPx -
MENU_MARGIN_FROM_APP_BORDER
);
}
setPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
});
const newOpenState = !open;
if (newOpenState && onClickPreprocessing) {
await onClickPreprocessing();
}
setOpen(newOpenState);
}
};
const deleteFile = async (file: SNFile) => {
const shouldDelete = await confirmDialog({
text: `Are you sure you want to permanently delete "${file.nameWithExt}"?`,
confirmButtonStyle: 'danger',
});
if (shouldDelete) {
const deletingToastId = addToast({
type: ToastType.Loading,
message: `Deleting file "${file.nameWithExt}"...`,
});
await application.deleteItem(file);
addToast({
type: ToastType.Success,
message: `Deleted file "${file.nameWithExt}"`,
});
dismissToast(deletingToastId);
}
};
const downloadFile = async (file: SNFile) => {
appState.files.downloadFile(file);
};
const attachFileToNote = async (file: SNFile) => {
await application.items.associateFileWithNote(file, note);
};
const detachFileFromNote = async (file: SNFile) => {
await application.items.disassociateFileWithNote(file, note);
};
const toggleFileProtection = async (file: SNFile) => {
let result: SNFile | undefined;
if (file.protected) {
result = await application.protections.unprotectFile(file);
} else {
result = await application.protections.protectFile(file);
}
const isProtected = result ? result.protected : file.protected;
return isProtected;
};
const authorizeProtectedActionForFile = async (
file: SNFile,
challengeReason: ChallengeReason
) => {
const authorizedFiles =
await application.protections.authorizeProtectedActionForFiles(
[file],
challengeReason
);
const isAuthorized =
authorizedFiles.length > 0 && authorizedFiles.includes(file);
return isAuthorized;
};
const renameFile = async (file: SNFile, fileName: string) => {
const { name, ext } = parseFileName(fileName);
await application.items.renameFile(file, name, ext);
};
const handleFileAction = async (action: PopoverFileItemAction) => {
const file =
action.type !== PopoverFileItemActionType.RenameFile
? action.payload
: action.payload.file;
let isAuthorizedForAction = true;
if (
file.protected &&
action.type !== PopoverFileItemActionType.ToggleFileProtection
) {
isAuthorizedForAction = await authorizeProtectedActionForFile(
file,
ChallengeReason.AccessProtectedFile
);
}
if (!isAuthorizedForAction) {
return false;
}
switch (action.type) {
case PopoverFileItemActionType.AttachFileToNote:
await attachFileToNote(file);
break;
case PopoverFileItemActionType.DetachFileToNote:
await detachFileFromNote(file);
break;
case PopoverFileItemActionType.DeleteFile:
await deleteFile(file);
break;
case PopoverFileItemActionType.DownloadFile:
await downloadFile(file);
break;
case PopoverFileItemActionType.ToggleFileProtection: {
const isProtected = await toggleFileProtection(file);
action.callback(isProtected);
break;
}
case PopoverFileItemActionType.RenameFile:
await renameFile(file, action.payload.name);
break;
}
application.sync.sync();
return true;
};
return (
<div ref={containerRef}>
<Disclosure open={open} onChange={toggleAttachedFilesMenu}>
<DisclosureButton
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false);
}
}}
ref={buttonRef}
className={`sn-icon-button border-contrast ${
attachedFilesCount > 0 ? 'py-1 px-3' : ''
}`}
>
<VisuallyHidden>Attached files</VisuallyHidden>
<Icon type="attachment-file" className="block" />
{attachedFilesCount > 0 && (
<span className="ml-2">{attachedFilesCount}</span>
)}
</DisclosureButton>
<DisclosurePanel
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false);
buttonRef.current?.focus();
}
}}
ref={panelRef}
style={{
...position,
maxHeight,
}}
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
>
{open && (
<PopoverDragNDropWrapper
application={application}
appState={appState}
note={note}
fileActionHandler={handleFileAction}
/>
)}
</DisclosurePanel>
</Disclosure>
</div>
);
}
);

View File

@@ -0,0 +1,195 @@
import { ContentType, SNFile } from '@standardnotes/snjs';
import { FilesIllustration } from '@standardnotes/stylekit';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { StateUpdater, useCallback, useEffect, useState } from 'preact/hooks';
import { Button } from '../Button';
import { Icon } from '../Icon';
import { PopoverTabs, PopoverWrapperProps } from './PopoverDragNDropWrapper';
import { PopoverFileItem } from './PopoverFileItem';
import { PopoverFileItemActionType } from './PopoverFileItemAction';
type Props = PopoverWrapperProps & {
currentTab: PopoverTabs;
setCurrentTab: StateUpdater<PopoverTabs>;
};
export const AttachedFilesPopover: FunctionComponent<Props> = observer(
({
application,
appState,
note,
fileActionHandler,
currentTab,
setCurrentTab,
}) => {
const [attachedFiles, setAttachedFiles] = useState<SNFile[]>([]);
const [allFiles, setAllFiles] = useState<SNFile[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const filesList =
currentTab === PopoverTabs.AttachedFiles ? attachedFiles : allFiles;
const filteredList =
searchQuery.length > 0
? filesList.filter(
(file) => file.nameWithExt.toLowerCase().indexOf(searchQuery) !== -1
)
: filesList;
const reloadAttachedFiles = useCallback(() => {
setAttachedFiles(
application.items
.getFilesForNote(note)
.sort((a, b) => (a.created_at < b.created_at ? 1 : -1))
);
}, [application.items, note]);
const reloadAllFiles = useCallback(() => {
setAllFiles(
application
.getItems(ContentType.File)
.sort((a, b) => (a.created_at < b.created_at ? 1 : -1)) as SNFile[]
);
}, [application]);
useEffect(() => {
const unregisterFileStream = application.streamItems(
ContentType.File,
() => {
reloadAttachedFiles();
reloadAllFiles();
}
);
return () => {
unregisterFileStream();
};
}, [application, reloadAllFiles, reloadAttachedFiles]);
const handleAttachFilesClick = async () => {
const uploadedFiles = await appState.files.uploadNewFile();
if (!uploadedFiles) {
return;
}
if (currentTab === PopoverTabs.AttachedFiles) {
uploadedFiles.forEach((file) => {
fileActionHandler({
type: PopoverFileItemActionType.AttachFileToNote,
payload: file,
});
});
}
};
return (
<div className="flex flex-col">
<div className="flex border-0 border-b-1 border-solid border-main">
<button
className={`bg-default border-0 cursor-pointer px-3 py-2.5 relative focus:bg-info-backdrop focus:shadow-bottom ${
currentTab === PopoverTabs.AttachedFiles
? 'color-info font-medium shadow-bottom'
: 'color-text'
}`}
onClick={() => {
setCurrentTab(PopoverTabs.AttachedFiles);
}}
>
Attached
</button>
<button
className={`bg-default border-0 cursor-pointer px-3 py-2.5 relative focus:bg-info-backdrop focus:shadow-bottom ${
currentTab === PopoverTabs.AllFiles
? 'color-info font-medium shadow-bottom'
: 'color-text'
}`}
onClick={() => {
setCurrentTab(PopoverTabs.AllFiles);
}}
>
All files
</button>
</div>
<div className="min-h-0 max-h-110 overflow-y-auto">
{filteredList.length > 0 || searchQuery.length > 0 ? (
<div className="sticky top-0 left-0 p-3 bg-default border-0 border-b-1 border-solid border-main">
<div className="relative">
<input
type="text"
className="w-full rounded py-1.5 px-3 text-input bg-default border-solid border-1 border-main"
placeholder="Search files..."
value={searchQuery}
onInput={(e) => {
setSearchQuery((e.target as HTMLInputElement).value);
}}
/>
{searchQuery.length > 0 && (
<button
className="flex absolute right-2 p-0 bg-transparent border-0 top-1/2 -translate-y-1/2 cursor-pointer"
onClick={() => {
setSearchQuery('');
}}
>
<Icon
type="clear-circle-filled"
className="color-neutral"
/>
</button>
)}
</div>
</div>
) : null}
{filteredList.length > 0 ? (
filteredList.map((file: SNFile) => {
return (
<PopoverFileItem
key={file.uuid}
file={file}
isAttachedToNote={attachedFiles.includes(file)}
handleFileAction={fileActionHandler}
/>
);
})
) : (
<div className="flex flex-col items-center justify-center w-full py-8">
<div className="w-18 h-18 mb-2">
<FilesIllustration
style={{
transform: 'scale(0.6)',
transformOrigin: 'top left',
}}
/>
</div>
<div className="text-sm font-medium mb-3">
{searchQuery.length > 0
? 'No result found'
: currentTab === PopoverTabs.AttachedFiles
? 'No files attached to this note'
: 'No files found in this account'}
</div>
<Button type="normal" onClick={handleAttachFilesClick}>
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'}{' '}
files
</Button>
<div className="text-xs color-grey-0 mt-3">
Or drop your files here
</div>
</div>
)}
</div>
{filteredList.length > 0 && (
<button
className="sn-dropdown-item py-3 border-0 border-t-1px border-solid border-main focus:bg-info-backdrop"
onClick={handleAttachFilesClick}
>
<Icon type="add" className="mr-2 color-neutral" />
{currentTab === PopoverTabs.AttachedFiles
? 'Attach'
: 'Upload'}{' '}
files
</button>
)}
</div>
);
}
);

View File

@@ -0,0 +1,143 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/constants';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { StreamingFileReader } from '@standardnotes/filepicker';
import { SNNote } from '@standardnotes/snjs';
import { FunctionComponent } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { AttachedFilesPopover } from './AttachedFilesPopover';
import {
PopoverFileItemAction,
PopoverFileItemActionType,
} from './PopoverFileItemAction';
export enum PopoverTabs {
AttachedFiles,
AllFiles,
}
export type PopoverWrapperProps = {
application: WebApplication;
appState: AppState;
note: SNNote;
fileActionHandler: (action: PopoverFileItemAction) => Promise<boolean>;
};
export const PopoverDragNDropWrapper: FunctionComponent<
PopoverWrapperProps
> = ({ fileActionHandler, appState, application, note }) => {
const dropzoneRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [currentTab, setCurrentTab] = useState(PopoverTabs.AttachedFiles);
const dragCounter = useRef(0);
const handleDrag = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
};
const handleDragIn = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
dragCounter.current = dragCounter.current + 1;
if (event.dataTransfer?.items.length) {
setIsDragging(true);
}
};
const handleDragOut = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
dragCounter.current = dragCounter.current - 1;
if (dragCounter.current > 0) {
return;
}
setIsDragging(false);
};
const handleDrop = useCallback(
(event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
setIsDragging(false);
if (event.dataTransfer?.items.length) {
Array.from(event.dataTransfer.items).forEach(async (item) => {
let fileOrHandle;
if (StreamingFileReader.available()) {
fileOrHandle =
(await item.getAsFileSystemHandle()) as FileSystemFileHandle;
} else {
fileOrHandle = item.getAsFile();
}
if (fileOrHandle) {
const uploadedFiles = await appState.files.uploadNewFile(
fileOrHandle
);
if (!uploadedFiles) {
return;
}
if (currentTab === PopoverTabs.AttachedFiles) {
uploadedFiles.forEach((file) => {
fileActionHandler({
type: PopoverFileItemActionType.AttachFileToNote,
payload: file,
});
});
}
}
});
event.dataTransfer.clearData();
dragCounter.current = 0;
}
},
[appState.files, currentTab, fileActionHandler]
);
useEffect(() => {
const dropzoneElement = dropzoneRef.current;
if (dropzoneElement) {
dropzoneElement.addEventListener('dragenter', handleDragIn);
dropzoneElement.addEventListener('dragleave', handleDragOut);
dropzoneElement.addEventListener('dragover', handleDrag);
dropzoneElement.addEventListener('drop', handleDrop);
}
return () => {
dropzoneElement?.removeEventListener('dragenter', handleDragIn);
dropzoneElement?.removeEventListener('dragleave', handleDragOut);
dropzoneElement?.removeEventListener('dragover', handleDrag);
dropzoneElement?.removeEventListener('drop', handleDrop);
};
}, [handleDrop]);
return (
<div
ref={dropzoneRef}
className="focus:shadow-none"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
style={{
border: isDragging ? '2px dashed var(--sn-stylekit-info-color)' : '',
}}
>
<AttachedFilesPopover
application={application}
appState={appState}
note={note}
fileActionHandler={fileActionHandler}
currentTab={currentTab}
setCurrentTab={setCurrentTab}
/>
</div>
);
};

View File

@@ -0,0 +1,129 @@
import { KeyboardKey } from '@/services/ioService';
import { formatSizeToReadableString } from '@standardnotes/filepicker';
import { SNFile } from '@standardnotes/snjs';
import { FunctionComponent } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks';
import { ICONS } from '../Icon';
import {
PopoverFileItemAction,
PopoverFileItemActionType,
} from './PopoverFileItemAction';
import { PopoverFileSubmenu } from './PopoverFileSubmenu';
const getIconForFileType = (fileType: string) => {
let iconType = 'file-other';
if (fileType === 'pdf') {
iconType = 'file-pdf';
}
if (/^(docx?|odt)/.test(fileType)) {
iconType = 'file-doc';
}
if (/^pptx?/.test(fileType)) {
iconType = 'file-ppt';
}
if (/^(xlsx?|ods)/.test(fileType)) {
iconType = 'file-xls';
}
if (/^(jpe?g|a?png|webp|gif)/.test(fileType)) {
iconType = 'file-image';
}
if (/^(mov|mp4|mkv)/.test(fileType)) {
iconType = 'file-mov';
}
if (/^(wav|mp3|flac|ogg)/.test(fileType)) {
iconType = 'file-music';
}
if (/^(zip|rar|7z)/.test(fileType)) {
iconType = 'file-zip';
}
const IconComponent = ICONS[iconType as keyof typeof ICONS];
return <IconComponent className="flex-shrink-0" />;
};
export type PopoverFileItemProps = {
file: SNFile;
isAttachedToNote: boolean;
handleFileAction: (action: PopoverFileItemAction) => Promise<boolean>;
};
export const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
file,
isAttachedToNote,
handleFileAction,
}) => {
const [fileName, setFileName] = useState(file.nameWithExt);
const [isRenamingFile, setIsRenamingFile] = useState(false);
const fileNameInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (isRenamingFile) {
fileNameInputRef.current?.focus();
}
}, [isRenamingFile]);
const renameFile = async (file: SNFile, name: string) => {
const didRename = await handleFileAction({
type: PopoverFileItemActionType.RenameFile,
payload: {
file,
name,
},
});
if (didRename) {
setIsRenamingFile(false);
}
};
const handleFileNameInput = (event: Event) => {
setFileName((event.target as HTMLInputElement).value);
};
const handleFileNameInputKeyDown = (event: KeyboardEvent) => {
if (event.key === KeyboardKey.Enter) {
renameFile(file, fileName);
return;
}
};
return (
<div className="flex items-center justify-between p-3">
<div className="flex items-center">
{getIconForFileType(file.ext ?? '')}
<div className="flex flex-col mx-4">
{isRenamingFile ? (
<input
type="text"
className="text-input px-1.5 py-1 mb-1 border-1 border-solid border-main bg-transparent color-foreground"
value={fileName}
ref={fileNameInputRef}
onInput={handleFileNameInput}
onKeyDown={handleFileNameInputKeyDown}
/>
) : (
<div className="text-sm mb-1">{file.nameWithExt}</div>
)}
<div className="text-xs color-grey-0">
{file.created_at.toLocaleString()} ·{' '}
{formatSizeToReadableString(file.size)}
</div>
</div>
</div>
<PopoverFileSubmenu
file={file}
isAttachedToNote={isAttachedToNote}
handleFileAction={handleFileAction}
setIsRenamingFile={setIsRenamingFile}
/>
</div>
);
};

View File

@@ -0,0 +1,32 @@
import { SNFile } from '@standardnotes/snjs';
export enum PopoverFileItemActionType {
AttachFileToNote,
DetachFileToNote,
DeleteFile,
DownloadFile,
RenameFile,
ToggleFileProtection,
}
export type PopoverFileItemAction =
| {
type: Exclude<
PopoverFileItemActionType,
| PopoverFileItemActionType.RenameFile
| PopoverFileItemActionType.ToggleFileProtection
>;
payload: SNFile;
}
| {
type: PopoverFileItemActionType.ToggleFileProtection;
payload: SNFile;
callback: (isProtected: boolean) => void;
}
| {
type: PopoverFileItemActionType.RenameFile;
payload: {
file: SNFile;
name: string;
};
};

View File

@@ -0,0 +1,188 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/constants';
import {
calculateSubmenuStyle,
SubmenuStyle,
} from '@/utils/calculateSubmenuStyle';
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@reach/disclosure';
import { FunctionComponent } from 'preact';
import {
StateUpdater,
useCallback,
useEffect,
useRef,
useState,
} from 'preact/hooks';
import { Icon } from '../Icon';
import { Switch } from '../Switch';
import { useCloseOnBlur } from '../utils';
import { PopoverFileItemProps } from './PopoverFileItem';
import { PopoverFileItemActionType } from './PopoverFileItemAction';
type Props = Omit<PopoverFileItemProps, 'renameFile'> & {
setIsRenamingFile: StateUpdater<boolean>;
};
export const PopoverFileSubmenu: FunctionComponent<Props> = ({
file,
isAttachedToNote,
handleFileAction,
setIsRenamingFile,
}) => {
const menuContainerRef = useRef<HTMLDivElement>(null);
const menuButtonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isFileProtected, setIsFileProtected] = useState(file.protected);
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>({
right: 0,
bottom: 0,
maxHeight: 'auto',
});
const [closeOnBlur] = useCloseOnBlur(menuContainerRef, setIsMenuOpen);
const closeMenu = () => {
setIsMenuOpen(false);
};
const toggleMenu = () => {
if (!isMenuOpen) {
const menuPosition = calculateSubmenuStyle(menuButtonRef.current);
if (menuPosition) {
setMenuStyle(menuPosition);
}
}
setIsMenuOpen(!isMenuOpen);
};
const recalculateMenuStyle = useCallback(() => {
const newMenuPosition = calculateSubmenuStyle(
menuButtonRef.current,
menuRef.current
);
if (newMenuPosition) {
setMenuStyle(newMenuPosition);
}
}, []);
useEffect(() => {
if (isMenuOpen) {
setTimeout(() => {
recalculateMenuStyle();
});
}
}, [isMenuOpen, recalculateMenuStyle]);
return (
<div ref={menuContainerRef}>
<Disclosure open={isMenuOpen} onChange={toggleMenu}>
<DisclosureButton
ref={menuButtonRef}
onBlur={closeOnBlur}
className="w-7 h-7 p-1 rounded-full border-0 bg-transparent hover:bg-contrast cursor-pointer"
>
<Icon type="more" className="color-neutral" />
</DisclosureButton>
<DisclosurePanel
ref={menuRef}
style={{
...menuStyle,
position: 'fixed',
}}
className="sn-dropdown flex flex-col max-h-120 min-w-60 py-1 fixed overflow-y-auto"
>
{isMenuOpen && (
<>
{isAttachedToNote ? (
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.DetachFileToNote,
payload: file,
});
closeMenu();
}}
>
<Icon type="link-off" className="mr-2 color-neutral" />
Detach from note
</button>
) : (
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.AttachFileToNote,
payload: file,
});
closeMenu();
}}
>
<Icon type="link" className="mr-2 color-neutral" />
Attach to note
</button>
)}
<div className="min-h-1px my-1 bg-border"></div>
<button
className="sn-dropdown-item justify-between focus:bg-info-backdrop"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.ToggleFileProtection,
payload: file,
callback: (isProtected: boolean) => {
setIsFileProtected(isProtected);
},
});
}}
onBlur={closeOnBlur}
>
<span className="flex items-center">
<Icon type="password" className="mr-2 color-neutral" />
Password protection
</span>
<Switch
className="px-0 pointer-events-none"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
checked={isFileProtected}
/>
</button>
<div className="min-h-1px my-1 bg-border"></div>
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
handleFileAction({
type: PopoverFileItemActionType.DownloadFile,
payload: file,
});
closeMenu();
}}
>
<Icon type="download" className="mr-2 color-neutral" />
Download
</button>
<button
onBlur={closeOnBlur}
className="sn-dropdown-item focus:bg-info-backdrop"
onClick={() => {
setIsRenamingFile(true);
}}
>
<Icon type="pencil" className="mr-2 color-neutral" />
Rename
</button>
</>
)}
</DisclosurePanel>
</Disclosure>
</div>
);
};

View File

@@ -0,0 +1,25 @@
interface BubbleProperties {
label: string;
selected: boolean;
onSelect: () => void;
}
const styles = {
base: 'px-2 py-1.5 text-center rounded-full cursor-pointer transition border-1 border-solid active:border-info active:bg-info active:color-neutral-contrast',
unselected: 'color-neutral border-secondary',
selected: 'border-info bg-info color-neutral-contrast',
};
const Bubble = ({ label, selected, onSelect }: BubbleProperties) => (
<span
role="tab"
className={`bubble ${styles.base} ${
selected ? styles.selected : styles.unselected
}`}
onClick={onSelect}
>
{label}
</span>
);
export default Bubble;

View File

@@ -256,7 +256,7 @@ export class Footer extends PureComponent<Props, State> {
updateSyncStatus() {
const statusManager = this.application.getStatusManager();
const syncStatus = this.application.getSyncStatus();
const syncStatus = this.application.sync.getSyncStatus();
const stats = syncStatus.getStats();
if (syncStatus.hasError()) {
statusManager.setMessage('Unable to Sync');
@@ -290,7 +290,7 @@ export class Footer extends PureComponent<Props, State> {
updateLocalDataStatus() {
const statusManager = this.application.getStatusManager();
const syncStatus = this.application.getSyncStatus();
const syncStatus = this.application.sync.getSyncStatus();
const stats = syncStatus.getStats();
const encryption = this.application.isEncryptionAvailable();
if (stats.localDataDone) {
@@ -312,7 +312,7 @@ export class Footer extends PureComponent<Props, State> {
findErrors() {
this.setState({
hasError: this.application.getSyncStatus().hasError(),
hasError: this.application.sync.getSyncStatus().hasError(),
});
}

View File

@@ -9,6 +9,7 @@ import {
ArrowLeftIcon,
ArrowsSortDownIcon,
ArrowsSortUpIcon,
AttachmentFileIcon,
AuthenticatorIcon,
CheckBoldIcon,
CheckCircleIcon,
@@ -17,6 +18,7 @@ import {
ChevronRightIcon,
CloseIcon,
CloudOffIcon,
ClearCircleFilledIcon,
CodeIcon,
CopyIcon,
DashboardIcon,
@@ -25,12 +27,22 @@ import {
EmailIcon,
EyeIcon,
EyeOffIcon,
FileDocIcon,
FileImageIcon,
FileMovIcon,
FileMusicIcon,
FileOtherIcon,
FilePdfIcon,
FilePptIcon,
FileXlsIcon,
FileZipIcon,
HashtagIcon,
HashtagOffIcon,
HelpIcon,
HistoryIcon,
InfoIcon,
KeyboardIcon,
LinkIcon,
LinkOffIcon,
ListBulleted,
ListedIcon,
@@ -44,9 +56,9 @@ import {
MoreIcon,
NotesIcon,
PasswordIcon,
PencilOffIcon,
PencilFilledIcon,
PencilIcon,
PencilOffIcon,
PinFilledIcon,
PinIcon,
PlainTextIcon,
@@ -75,17 +87,28 @@ import {
WindowIcon,
} from '@standardnotes/stylekit';
const ICONS = {
export const ICONS = {
'account-circle': AccountCircleIcon,
'arrow-left': ArrowLeftIcon,
'arrows-sort-down': ArrowsSortDownIcon,
'arrows-sort-up': ArrowsSortUpIcon,
'attachment-file': AttachmentFileIcon,
'check-bold': CheckBoldIcon,
'check-circle': CheckCircleIcon,
'chevron-down': ChevronDownIcon,
'chevron-right': ChevronRightIcon,
'cloud-off': CloudOffIcon,
'clear-circle-filled': ClearCircleFilledIcon,
'eye-off': EyeOffIcon,
'file-doc': FileDocIcon,
'file-image': FileImageIcon,
'file-mov': FileMovIcon,
'file-music': FileMusicIcon,
'file-other': FileOtherIcon,
'file-pdf': FilePdfIcon,
'file-ppt': FilePptIcon,
'file-xls': FileXlsIcon,
'file-zip': FileZipIcon,
'hashtag-off': HashtagOffIcon,
'link-off': LinkOffIcon,
'list-bulleted': ListBulleted,
@@ -121,6 +144,7 @@ const ICONS = {
history: HistoryIcon,
info: InfoIcon,
keyboard: KeyboardIcon,
link: LinkIcon,
listed: ListedIcon,
lock: LockIcon,
markdown: MarkdownIcon,

View File

@@ -17,6 +17,8 @@ import {
ItemMutator,
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
NoteViewController,
FeatureIdentifier,
FeatureStatus,
} from '@standardnotes/snjs';
import { debounce, isDesktopApplication } from '@/utils';
import { KeyboardModifier, KeyboardKey } from '@/services/ioService';
@@ -37,6 +39,7 @@ import { ComponentView } from '../ComponentView';
import { PanelSide, PanelResizer, PanelResizeType } from '../PanelResizer';
import { ElementIds } from '@/element_ids';
import { ChangeEditorButton } from '../ChangeEditorButton';
import { AttachedFilesButton } from '../AttachedFilesPopover/AttachedFilesButton';
const MINIMUM_STATUS_DURATION = 400;
const TEXTAREA_DEBOUNCE = 100;
@@ -100,6 +103,7 @@ type State = {
editorTitle: string;
editorText: string;
isDesktop?: boolean;
isEntitledToFiles: boolean;
lockText: string;
marginResizersEnabled?: boolean;
monospaceFont?: boolean;
@@ -168,6 +172,9 @@ export class NoteView extends PureComponent<Props, State> {
editorText: '',
editorTitle: '',
isDesktop: isDesktopApplication(),
isEntitledToFiles:
this.application.features.getFeatureStatus(FeatureIdentifier.Files) ===
FeatureStatus.Entitled,
lockText: 'Note Editing Disabled',
noteStatus: undefined,
noteLocked: this.controller.note.locked,
@@ -321,6 +328,15 @@ export class NoteView extends PureComponent<Props, State> {
/** @override */
async onAppEvent(eventName: ApplicationEvent) {
switch (eventName) {
case ApplicationEvent.FeaturesUpdated:
case ApplicationEvent.UserRolesChanged:
this.setState({
isEntitledToFiles:
this.application.features.getFeatureStatus(
FeatureIdentifier.Files
) === FeatureStatus.Entitled,
});
break;
case ApplicationEvent.PreferencesChanged:
this.reloadPreferences();
break;
@@ -681,7 +697,7 @@ export class NoteView extends PureComponent<Props, State> {
if (left !== undefined && left !== null) {
await this.application.setPreference(PrefKey.EditorLeft, left);
}
this.application.sync();
this.application.sync.sync();
};
async reloadSpellcheck() {
@@ -797,7 +813,7 @@ export class NoteView extends PureComponent<Props, State> {
} else {
await this.disassociateComponentWithCurrentNote(component);
}
this.application.sync();
this.application.sync.sync();
};
async disassociateComponentWithCurrentNote(component: SNComponent) {
@@ -1027,6 +1043,18 @@ export class NoteView extends PureComponent<Props, State> {
)}
</div>
</div>
{this.state.isEntitledToFiles &&
window.enabledUnfinishedFeatures && (
<div className="mr-3">
<AttachedFilesButton
application={this.application}
appState={this.appState}
onClickPreprocessing={
this.ensureNoteIsInsertedBeforeUIAction
}
/>
</div>
)}
<div className="mr-3">
<ChangeEditorButton
application={this.application}

View File

@@ -38,7 +38,6 @@ export const AddTagOption: FunctionComponent<Props> = observer(
const menuPosition = calculateSubmenuStyle(menuButtonRef.current);
if (menuPosition) {
setMenuStyle(menuPosition);
console.log(menuPosition);
}
}
@@ -53,7 +52,6 @@ export const AddTagOption: FunctionComponent<Props> = observer(
if (newMenuPosition) {
setMenuStyle(newMenuPosition);
console.log(newMenuPosition);
}
}, []);

View File

@@ -153,7 +153,7 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
await application.runTransactionalMutations(transactions);
/** Dirtying can happen above */
application.sync();
application.sync.sync();
setCurrentEditor(application.componentManager.editorForNote(note));
};

View File

@@ -59,11 +59,13 @@ export const createEditorMenuGroups = (
feature.area === ComponentArea.Editor
)
.forEach((editorFeature) => {
if (
!editors.find(
(editor) => editor.identifier === editorFeature.identifier
)
) {
const notInstalled = !editors.find(
(editor) => editor.identifier === editorFeature.identifier
);
const isExperimental = application.features.isExperimentalFeature(
editorFeature.identifier
);
if (notInstalled && !isExperimental) {
editorItems[getEditorGroup(editorFeature)].push({
name: editorFeature.name as string,
isEntitled: false,

View File

@@ -48,13 +48,13 @@ export const NotesView: FunctionComponent<Props> = observer(
selectPreviousNote,
onFilterEnter,
handleFilterTextChanged,
onSearchInputBlur,
clearFilterText,
paginate,
panelWidth,
} = appState.notesView;
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false);
const [focusedSearch, setFocusedSearch] = useState(false);
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(
displayOptionsMenuRef,
@@ -130,6 +130,9 @@ export const NotesView: FunctionComponent<Props> = observer(
setNoteFilterText((e.target as HTMLInputElement).value);
};
const onSearchFocused = () => setFocusedSearch(true);
const onSearchBlurred = () => setFocusedSearch(false);
const onNoteFilterKeyUp = (e: KeyboardEvent) => {
if (e.key === KeyboardKey.Enter) {
onFilterEnter();
@@ -179,32 +182,38 @@ export const NotesView: FunctionComponent<Props> = observer(
</button>
</div>
<div className="filter-section" role="search">
<input
type="text"
id="search-bar"
className="filter-bar"
placeholder="Search"
title="Searches notes in the currently selected tag"
value={noteFilterText}
onChange={onNoteFilterTextChange}
onKeyUp={onNoteFilterKeyUp}
onBlur={() => onSearchInputBlur()}
/>
{noteFilterText ? (
<button
onClick={clearFilterText}
aria-role="button"
id="search-clear-button"
>
</button>
) : null}
<div className="ml-2">
<SearchOptions
application={application}
appState={appState}
<div>
<input
type="text"
id="search-bar"
className="filter-bar"
placeholder="Search"
title="Searches notes in the currently selected tag"
value={noteFilterText}
onChange={onNoteFilterTextChange}
onKeyUp={onNoteFilterKeyUp}
onFocus={onSearchFocused}
onBlur={onSearchBlurred}
/>
{noteFilterText && (
<button
onClick={clearFilterText}
aria-role="button"
id="search-clear-button"
>
</button>
)}
</div>
{(focusedSearch || noteFilterText) && (
<div className="animate-fade-from-top">
<SearchOptions
application={application}
appState={appState}
/>
</div>
)}
</div>
<NoAccountWarning appState={appState} />
</div>

View File

@@ -1,16 +1,7 @@
import { AppState } from '@/ui_models/app_state';
import { Icon } from './Icon';
import { useCloseOnBlur } from './utils';
import { useEffect, useRef, useState } from 'preact/hooks';
import { WebApplication } from '@/ui_models/application';
import VisuallyHidden from '@reach/visually-hidden';
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@reach/disclosure';
import { Switch } from './Switch';
import { observer } from 'mobx-react-lite';
import Bubble from './Bubble';
type Props = {
appState: AppState;
@@ -23,94 +14,33 @@ export const SearchOptions = observer(({ appState }: Props) => {
const { includeProtectedContents, includeArchived, includeTrashed } =
searchOptions;
const [open, setOpen] = useState(false);
const [position, setPosition] = useState({
top: 0,
right: 0,
});
const [maxWidth, setMaxWidth] = useState<number | 'auto'>('auto');
const buttonRef = useRef<HTMLButtonElement>(null);
const panelRef = useRef<HTMLDivElement>(null);
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(
panelRef as any,
setOpen
);
async function toggleIncludeProtectedContents() {
setLockCloseOnBlur(true);
try {
await searchOptions.toggleIncludeProtectedContents();
} finally {
setLockCloseOnBlur(false);
}
await searchOptions.toggleIncludeProtectedContents();
}
const updateWidthAndPosition = () => {
const rect = buttonRef.current!.getBoundingClientRect();
setMaxWidth(rect.right - 16);
setPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
});
};
useEffect(() => {
window.addEventListener('resize', updateWidthAndPosition);
return () => {
window.removeEventListener('resize', updateWidthAndPosition);
};
}, []);
return (
<Disclosure
open={open}
onChange={() => {
updateWidthAndPosition();
setOpen(!open);
}}
<div
role="tablist"
className="search-options justify-center"
onMouseDown={(e) => e.preventDefault()}
>
<DisclosureButton
ref={buttonRef}
onBlur={closeOnBlur}
className="border-0 p-0 bg-transparent cursor-pointer color-neutral hover:color-info"
>
<VisuallyHidden>Search options</VisuallyHidden>
<Icon type="tune" className="block" />
</DisclosureButton>
<DisclosurePanel
ref={panelRef}
style={{
...position,
maxWidth,
}}
className="sn-dropdown sn-dropdown--animated w-80 fixed grid gap-2 py-2"
onBlur={closeOnBlur}
>
<Switch
className="h-10"
checked={includeProtectedContents}
onChange={toggleIncludeProtectedContents}
onBlur={closeOnBlur}
>
<p className="capitalize">Include protected contents</p>
</Switch>
<Switch
className="h-10"
checked={includeArchived}
onChange={searchOptions.toggleIncludeArchived}
onBlur={closeOnBlur}
>
<p className="capitalize">Include archived notes</p>
</Switch>
<Switch
className="h-10"
checked={includeTrashed}
onChange={searchOptions.toggleIncludeTrashed}
onBlur={closeOnBlur}
>
<p className="capitalize">Include trashed notes</p>
</Switch>
</DisclosurePanel>
</Disclosure>
<Bubble
label="Protected Contents"
selected={includeProtectedContents}
onSelect={toggleIncludeProtectedContents}
/>
<Bubble
label="Archived"
selected={includeArchived}
onSelect={searchOptions.toggleIncludeArchived}
/>
<Bubble
label="Trashed"
selected={includeTrashed}
onSelect={searchOptions.toggleIncludeTrashed}
/>
</div>
);
});

View File

@@ -1,6 +1,5 @@
import { WebApplication } from '@/ui_models/application';
import { PureComponent } from './Abstract/PureComponent';
import { Fragment } from 'preact';
type Props = {
application: WebApplication;
@@ -8,43 +7,13 @@ type Props = {
};
export class SyncResolutionMenu extends PureComponent<Props> {
private status: Partial<{
backupFinished: boolean;
resolving: boolean;
attemptedResolution: boolean;
success: boolean;
fail: boolean;
}> = {};
constructor(props: Props) {
super(props, props.application);
}
downloadBackup(encrypted: boolean) {
this.props.application.getArchiveService().downloadBackup(encrypted);
this.status.backupFinished = true;
}
skipBackup() {
this.status.backupFinished = true;
}
async performSyncResolution() {
this.status.resolving = true;
await this.props.application.resolveOutOfSync();
this.status.resolving = false;
this.status.attemptedResolution = true;
if (this.props.application.isOutOfSync()) {
this.status.fail = true;
} else {
this.status.success = true;
}
}
close() {
close = () => {
this.props.close();
}
};
render() {
return (
@@ -59,13 +28,15 @@ export class SyncResolutionMenu extends PureComponent<Props> {
<div className="sk-panel-content">
<div className="sk-panel-section">
<div className="sk-panel-row sk-p">
We've detected that the data on the server may not match the
data in the current application session.
We've detected that the data in the current application session
may not match the data on the server. An attempt was made to
auto-resolve the issue, but it was unable to reconcile the
differences.
</div>
<div className="sk-p sk-panel-row">
<div className="sk-panel-column">
<strong className="sk-panel-row">
Option 1 Restart App:
Option 1 Restart Application:
</strong>
<div className="sk-p">
Quit the application and re-open it. Sometimes, this may
@@ -76,108 +47,15 @@ export class SyncResolutionMenu extends PureComponent<Props> {
<div className="sk-p sk-panel-row">
<div className="sk-panel-column">
<strong className="sk-panel-row">
Option 2 (recommended) Sign Out:
Option 2 Sign Out and Back In:
</strong>
<div className="sk-p">
Sign out of your account, then sign back in. This will
ensure your data is consistent with the server.
</div>
Be sure to download a backup of your data before doing so.
</div>
</div>
<div className="sk-p sk-panel-row">
<div className="sk-panel-column">
<strong className="sk-panel-row">
Option 3 Sync Resolution:
</strong>
<div className="sk-p">
We can attempt to reconcile changes by downloading all data
from the server. No existing data will be overwritten. If
the local contents of an item differ from what the server
has, a conflicted copy will be created.
ensure your data is consistent with the server. Be sure to
download a backup of your data before doing so.
</div>
</div>
</div>
{!this.status.backupFinished && (
<Fragment>
<div className="sk-p sk-panel-row">
Please download a backup before we attempt to perform a full
account sync resolution.
</div>
<div className="sk-panel-row">
<div className="flex gap-2">
<button
onClick={() => this.downloadBackup(true)}
className="sn-button small info"
>
Encrypted
</button>
<button
onClick={() => this.downloadBackup(false)}
className="sn-button small info"
>
Decrypted
</button>
<button
onClick={this.skipBackup}
className="sn-button small danger"
>
Skip
</button>
</div>
</div>
</Fragment>
)}
{this.status.backupFinished && (
<div>
{!this.status.resolving && !this.status.attemptedResolution && (
<div className="sk-panel-row">
<button
onClick={this.performSyncResolution}
className="sn-button small info"
>
Perform Sync Resolution
</button>
</div>
)}
{this.status.resolving && (
<div className="sk-panel-row justify-left">
<div className="sk-horizontal-group">
<div className="sk-spinner small info" />
<div className="sk-label">
Attempting sync resolution...
</div>
</div>
</div>
)}
{this.status.fail && (
<div className="sk-panel-column">
<div className="sk-panel-row sk-label danger">
Sync Resolution Failed
</div>
<div className="sk-p sk-panel-row">
We attempted to reconcile local content and server
content, but were unable to do so. At this point, we
recommend signing out of your account and signing back
in. You may wish to download a data backup before doing
so.
</div>
</div>
)}
{this.status.success && (
<div className="sk-panel-column">
<div className="sk-panel-row sk-label success">
Sync Resolution Success
</div>
<div className="sk-p sk-panel-row">
Your local data is now in sync with the server. You may
close this window.
</div>
</div>
)}
</div>
)}
</div>
</div>
</div>

View File

@@ -46,8 +46,13 @@ export function useCloseOnClickOutside(
if (!container.current) {
return;
}
const isDescendant = container.current.contains(event.target as Node);
if (!isDescendant) {
const isDescendantOfContainer = container.current.contains(
event.target as Node
);
const isDescendantOfDialog = (event.target as HTMLElement).closest(
'[role="dialog"]'
);
if (!isDescendantOfContainer && !isDescendantOfDialog) {
callback();
}
},

View File

@@ -13,4 +13,6 @@ export const NOTES_LIST_SCROLL_THRESHOLD = 200;
export const MILLISECONDS_IN_A_DAY = 1000 * 60 * 60 * 24;
export const DAYS_IN_A_WEEK = 7;
export const DAYS_IN_A_YEAR = 365;
export const BYTES_IN_ONE_MEGABYTE = 1000000;
export const BYTES_IN_ONE_KILOBYTE = 1_000;
export const BYTES_IN_ONE_MEGABYTE = 1_000_000;

View File

@@ -74,7 +74,7 @@ export const Extensions: FunctionComponent<{
const confirmExtension = async () => {
await application.insertItem(confirmableExtension as SNComponent);
application.sync();
application.sync.sync();
setExtensions(loadExtensions(application));
};

View File

@@ -2,7 +2,7 @@ import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { FunctionComponent } from 'preact';
import { PreferencesPane } from '../components';
import { ErrorReporting, Tools, Defaults } from './general-segments';
import { ErrorReporting, Tools, Defaults, LabsPane } from './general-segments';
import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
import { Advanced } from '@/preferences/panes/account';
import { observer } from 'mobx-react-lite';
@@ -19,6 +19,7 @@ export const General: FunctionComponent<GeneralProps> = observer(
<Tools application={application} />
<Defaults application={application} />
<ErrorReporting appState={appState} />
<LabsPane application={application} />
<Advanced
application={application}
appState={appState}

View File

@@ -24,22 +24,22 @@ export const Sync: FunctionComponent<Props> = observer(
({ application }: Props) => {
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false);
const [lastSyncDate, setLastSyncDate] = useState(
formatLastSyncDate(application.getLastSyncDate() as Date)
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
);
const doSynchronization = async () => {
setIsSyncingInProgress(true);
const response = await application.sync({
const response = await application.sync.sync({
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true,
});
setIsSyncingInProgress(false);
if (response && response.error) {
application.alertService!.alert(STRING_GENERIC_SYNC_ERROR);
if (response && (response as any).error) {
application.alertService.alert(STRING_GENERIC_SYNC_ERROR);
} else {
setLastSyncDate(
formatLastSyncDate(application.getLastSyncDate() as Date)
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
);
}
};

View File

@@ -0,0 +1,112 @@
import { FindNativeFeature } from '@standardnotes/features';
import { Switch } from '@/components/Switch';
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/preferences/components';
import { WebApplication } from '@/ui_models/application';
import { FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs';
import { FunctionComponent } from 'preact';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { usePremiumModal } from '@/components/Premium';
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
type ExperimentalFeatureItem = {
identifier: FeatureIdentifier;
name: string;
description: string;
isEnabled: boolean;
isEntitled: boolean;
};
type Props = {
application: WebApplication;
};
export const LabsPane: FunctionComponent<Props> = ({ application }) => {
const [experimentalFeatures, setExperimentalFeatures] = useState<
ExperimentalFeatureItem[]
>([]);
const reloadExperimentalFeatures = useCallback(() => {
const experimentalFeatures = application.features
.getExperimentalFeatures()
.map((featureIdentifier) => {
const feature = FindNativeFeature(featureIdentifier);
return {
identifier: featureIdentifier,
name: feature?.name ?? featureIdentifier,
description: feature?.description ?? '',
isEnabled:
application.features.isExperimentalFeatureEnabled(
featureIdentifier
),
isEntitled:
application.features.getFeatureStatus(featureIdentifier) ===
FeatureStatus.Entitled,
};
});
setExperimentalFeatures(experimentalFeatures);
}, [application.features]);
useEffect(() => {
reloadExperimentalFeatures();
}, [reloadExperimentalFeatures]);
const premiumModal = usePremiumModal();
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Labs</Title>
<div>
{experimentalFeatures.map(
(
{ identifier, name, description, isEnabled, isEntitled },
index: number
) => {
const toggleFeature = () => {
if (!isEntitled) {
premiumModal.activate(name);
return;
}
application.features.toggleExperimentalFeature(identifier);
reloadExperimentalFeatures();
};
const showHorizontalSeparator =
experimentalFeatures.length > 1 &&
index !== experimentalFeatures.length - 1;
return (
<>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>{name}</Subtitle>
<Text>{description}</Text>
</div>
<Switch onChange={toggleFeature} checked={isEnabled} />
</div>
{showHorizontalSeparator && (
<HorizontalSeparator classes="mt-5 mb-3" />
)}
</>
);
}
)}
{experimentalFeatures.length === 0 && (
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Text>No experimental features available.</Text>
</div>
</div>
)}
</div>
</PreferencesSegment>
</PreferencesGroup>
);
};

View File

@@ -1,3 +1,4 @@
export * from './ErrorReporting';
export * from './Tools';
export * from './Defaults';
export * from './Labs';

View File

@@ -2,7 +2,6 @@ import { Bridge } from './services/bridge';
export type StartApplication = (
defaultSyncServerHost: string,
defaultFilesHostHost: string,
bridge: Bridge,
enableUnfinishedFeatures: boolean,
webSocketUrl: string

View File

@@ -26,6 +26,7 @@ import {
} from 'mobx';
import { ActionsMenuState } from './actions_menu_state';
import { FeaturesState } from './features_state';
import { FilesState } from './files_state';
import { NotesState } from './notes_state';
import { NotesViewState } from './notes_view_state';
import { NoteTagsState } from './note_tags_state';
@@ -89,6 +90,7 @@ export class AppState {
readonly tags: TagsState;
readonly notesView: NotesViewState;
readonly subscription: SubscriptionState;
readonly files: FilesState;
isSessionsModalVisible = false;
@@ -139,6 +141,7 @@ export class AppState {
this,
this.appEventObserverRemovers
);
this.files = new FilesState(application);
this.addAppEventObserver();
this.streamNotesAndTags();
this.onVisibilityChange = () => {
@@ -381,7 +384,7 @@ export class AppState {
}
break;
case ApplicationEvent.SyncStatusChanged:
this.sync.update(this.application.getSyncStatus());
this.sync.update(this.application.sync.getSyncStatus());
break;
}
});

View File

@@ -0,0 +1,142 @@
import {
ClassicFileReader,
StreamingFileReader,
StreamingFileSaver,
ClassicFileSaver,
} from '@standardnotes/filepicker';
import { SNFile } from '@standardnotes/snjs';
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit';
import { WebApplication } from '../application';
export class FilesState {
constructor(private application: WebApplication) {}
public async downloadFile(file: SNFile): Promise<void> {
let downloadingToastId = '';
try {
const saver = StreamingFileSaver.available()
? new StreamingFileSaver(file.nameWithExt)
: new ClassicFileSaver();
const isUsingStreamingSaver = saver instanceof StreamingFileSaver;
if (isUsingStreamingSaver) {
await saver.selectFileToSaveTo();
}
downloadingToastId = addToast({
type: ToastType.Loading,
message: `Downloading file...`,
});
await this.application.files.downloadFile(
file,
async (decryptedBytes: Uint8Array) => {
if (isUsingStreamingSaver) {
await saver.pushBytes(decryptedBytes);
} else {
saver.saveFile(file.nameWithExt, decryptedBytes);
}
}
);
if (isUsingStreamingSaver) {
await saver.finish();
}
addToast({
type: ToastType.Success,
message: 'Successfully downloaded file',
});
} catch (error) {
console.error(error);
addToast({
type: ToastType.Error,
message: 'There was an error while downloading the file',
});
}
if (downloadingToastId.length > 0) {
dismissToast(downloadingToastId);
}
}
public async uploadNewFile(fileOrHandle?: File | FileSystemFileHandle) {
let toastId = '';
try {
const minimumChunkSize = this.application.files.minimumChunkSize();
const picker = StreamingFileReader.available()
? StreamingFileReader
: ClassicFileReader;
const selectedFiles =
fileOrHandle instanceof File
? [fileOrHandle]
: StreamingFileReader.available() &&
fileOrHandle instanceof FileSystemFileHandle
? await StreamingFileReader.getFilesFromHandles([fileOrHandle])
: await picker.selectFiles();
const uploadedFiles: SNFile[] = [];
for (const file of selectedFiles) {
const operation = await this.application.files.beginNewFileUpload();
const onChunk = async (
chunk: Uint8Array,
index: number,
isLast: boolean
) => {
await this.application.files.pushBytesForUpload(
operation,
chunk,
index,
isLast
);
};
toastId = addToast({
type: ToastType.Loading,
message: `Uploading file "${file.name}"...`,
});
const fileResult = await picker.readFile(
file,
minimumChunkSize,
onChunk
);
const uploadedFile = await this.application.files.finishUpload(
operation,
fileResult.name,
fileResult.ext
);
uploadedFiles.push(uploadedFile);
dismissToast(toastId);
addToast({
type: ToastType.Success,
message: `Uploaded file "${uploadedFile.nameWithExt}"`,
});
}
return uploadedFiles;
} catch (error) {
console.error(error);
if (toastId.length > 0) {
dismissToast(toastId);
}
addToast({
type: ToastType.Error,
message: 'There was an error while uploading the file',
});
}
}
}

View File

@@ -181,7 +181,7 @@ export class NoteTagsState {
if (activeNote) {
await this.application.addTagHierarchyToNote(activeNote, tag);
this.application.sync();
this.application.sync.sync();
this.reloadTags();
}
}
@@ -192,7 +192,7 @@ export class NoteTagsState {
await this.application.changeItem(tag.uuid, (mutator) => {
mutator.removeItemAsRelationship(activeNote);
});
this.application.sync();
this.application.sync.sync();
this.reloadTags();
}
}

View File

@@ -265,7 +265,7 @@ export class NotesState {
mutate,
false
);
this.application.sync();
this.application.sync.sync();
}
setHideSelectedNotePreviews(hide: boolean): void {
@@ -403,7 +403,7 @@ export class NotesState {
},
false
);
this.application.sync();
this.application.sync.sync();
}
async addTagToSelectedNotes(tag: SNTag): Promise<void> {
@@ -419,7 +419,7 @@ export class NotesState {
});
})
);
this.application.sync();
this.application.sync.sync();
}
async removeTagFromSelectedNotes(tag: SNTag): Promise<void> {
@@ -429,7 +429,7 @@ export class NotesState {
mutator.removeItemAsRelationship(note);
}
});
this.application.sync();
this.application.sync.sync();
}
isTagInSelectedNotes(tag: SNTag): boolean {
@@ -453,7 +453,7 @@ export class NotesState {
})
) {
this.application.emptyTrash();
this.application.sync();
this.application.sync.sync();
}
}

View File

@@ -559,10 +559,6 @@ export class NotesViewState {
this.reloadNotes();
};
onSearchInputBlur = () => {
this.appState.searchOptions.refreshIncludeProtectedContents();
};
clearFilterText = () => {
this.setNoteFilterText('');
this.onFilterEnter();

View File

@@ -208,7 +208,7 @@ export class TagsState {
this.assignParent(createdTag.uuid, parent.uuid);
this.application.sync();
this.application.sync.sync();
runInAction(() => {
this.selected = createdTag as SNTag;
@@ -364,7 +364,7 @@ export class TagsState {
await this.application.setTagParent(futureParent, tag);
}
await this.application.sync();
await this.application.sync.sync();
}
get rootTags(): SNTag[] {
@@ -507,7 +507,7 @@ export class TagsState {
}
const insertedTag = await this.application.createTagOrSmartView(newTitle);
this.application.sync();
this.application.sync.sync();
runInAction(() => {
this.selected = insertedTag as SNTag;
});

View File

@@ -48,7 +48,6 @@ export class WebApplication extends SNApplication {
platform: Platform,
identifier: string,
defaultSyncServerHost: string,
defaultFilesHostHost: string,
public bridge: Bridge,
webSocketUrl: string,
runtime: Runtime
@@ -61,7 +60,6 @@ export class WebApplication extends SNApplication {
alertService: new AlertService(),
identifier,
defaultHost: defaultSyncServerHost,
defaultFilesHost: defaultFilesHostHost,
appVersion: bridge.appVersion,
webSocketUrl: webSocketUrl,
runtime,

View File

@@ -21,7 +21,6 @@ import { InternalEventBus } from '@standardnotes/services';
export class ApplicationGroup extends SNApplicationGroup {
constructor(
private defaultSyncServerHost: string,
private defaultFilesHostHost: string,
private bridge: Bridge,
private runtime: Runtime,
private webSocketUrl: string
@@ -52,7 +51,6 @@ export class ApplicationGroup extends SNApplicationGroup {
platform,
descriptor.identifier,
this.defaultSyncServerHost,
this.defaultFilesHostHost,
this.bridge,
this.webSocketUrl,
this.runtime

View File

@@ -1,6 +1,10 @@
import { Platform, platformFromString } from '@standardnotes/snjs';
import { IsDesktopPlatform, IsWebPlatform } from '@/version';
import { EMAIL_REGEX } from '../constants';
import {
BYTES_IN_ONE_KILOBYTE,
BYTES_IN_ONE_MEGABYTE,
EMAIL_REGEX,
} from '../constants';
export { isMobile } from './isMobile';
declare const process: {

View File

@@ -52,11 +52,11 @@
.filter-section {
clear: left;
height: 28px;
margin-top: 14px;
max-height: 80px;
margin-top: 10px;
position: relative;
display: flex;
align-items: center;
flex-direction: column;
.filter-bar {
background-color: var(--sn-stylekit-contrast-background-color);
@@ -71,6 +71,20 @@
border-color: transparent;
width: 100%;
position: relative;
height: 28px;
}
.search-options {
margin-top: 10px;
display: grid;
grid-template-columns: repeat( 3, 1fr );
gap: .5rem;
font-size: var(--sn-stylekit-font-size-p);
white-space: nowrap;
overflow-x: auto;
}
#search-clear-button {
@@ -86,9 +100,9 @@
line-height: 17px;
text-align: center;
position: absolute;
top: 50%;
top: 20%;
transform: translateY(-50%);
right: 36px;
right: 10px;
cursor: pointer;
transition: background-color 0.15s linear;

View File

@@ -258,6 +258,11 @@
margin-right: 3rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.my-0\.5 {
margin-top: 0.125rem;
margin-bottom: 0.125rem;
@@ -328,6 +333,10 @@
width: 0.75rem;
}
.w-18 {
width: 4.5rem;
}
.w-26 {
width: 6.5rem;
}
@@ -428,6 +437,10 @@
max-height: 1.25rem;
}
.max-h-110 {
max-height: 27.5rem;
}
.border-danger {
border-color: var(--sn-stylekit-danger-color);
}
@@ -436,6 +449,10 @@
border-color: var(--sn-stylekit-neutral-contrast-color);
}
.border-secondary {
border-color: var(--sn-stylekit-secondary-border-color);
}
.sn-component .border-r-1px {
border-right-width: 1px;
}
@@ -513,6 +530,11 @@
padding-right: 0;
}
.px-1\.5 {
padding-left: 0.375rem;
padding-right: 0.375rem;
}
.px-2\.5 {
padding-left: 0.625rem;
padding-right: 0.625rem;
@@ -572,6 +594,10 @@
font-weight: 500;
}
.sticky {
position: sticky;
}
.top-30\% {
top: 30%;
}
@@ -636,6 +662,10 @@
right: 0;
}
.right-2 {
right: 0.5rem;
}
.-right-2 {
right: -0.5rem;
}
@@ -902,6 +932,10 @@
var(--sn-stylekit-info-color) -1px -1px 0px 0px inset;
}
.focus\:shadow-bottom:focus {
box-shadow: currentcolor 0px -1px 0px 0px inset, currentcolor 0px 1px 0px 0px;
}
.bg-note-size-warning {
background-color: rgba(235, 173, 0, 0.08);
}
@@ -938,3 +972,44 @@
.invisible {
visibility: hidden;
}
.color-neutral-contrast {
color: var(--sn-stylekit-neutral-contrast-color);
}
.active\:bg-info:active {
background-color: var(--sn-stylekit-info-color);
}
.active\:border-info:active {
border-color: var(--sn-stylekit-info-color);
}
.active\:color-neutral-contrast:active {
color: var(--sn-stylekit-neutral-contrast-color);
}
.transition {
transition-property: color, background-color, border-color,
text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter,
backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 100ms;
}
.animate-fade-from-top {
animation: fade-from-top 0.2s ease-out;
}
@keyframes fade-from-top {
0% {
opacity: 0;
transform: translateY(-20%);
}
75% {
opacity: 1;
}
100% {
transform: translateY(0%);
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "standard-notes-web",
"version": "3.12.1",
"version": "3.13.0",
"license": "AGPL-3.0-or-later",
"repository": {
"type": "git",
@@ -27,21 +27,22 @@
"@babel/preset-typescript": "^7.16.7",
"@reach/disclosure": "^0.16.2",
"@reach/visually-hidden": "^0.16.0",
"@standardnotes/responses": "^1.1.7",
"@standardnotes/services": "^1.4.0",
"@standardnotes/stylekit": "5.14.0",
"@standardnotes/responses": "1.3.7",
"@standardnotes/services": "1.5.9",
"@standardnotes/stylekit": "5.15.0",
"@svgr/webpack": "^6.2.1",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.179",
"@types/react": "^17.0.39",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"@types/react": "^17.0.40",
"@types/wicg-file-system-access": "^2020.9.5",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"apply-loader": "^2.0.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.3",
"connect": "^3.7.0",
"copy-webpack-plugin": "^10.2.4",
"css-loader": "^6.7.0",
"css-loader": "^6.7.1",
"dotenv": "^16.0.0",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
@@ -71,22 +72,23 @@
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@bugsnag/js": "^7.16.1",
"@bugsnag/js": "^7.16.2",
"@reach/alert": "^0.16.0",
"@reach/alert-dialog": "^0.16.2",
"@reach/checkbox": "^0.16.0",
"@reach/dialog": "^0.16.2",
"@reach/listbox": "^0.16.2",
"@reach/tooltip": "^0.16.2",
"@standardnotes/components": "1.7.9",
"@standardnotes/features": "1.34.1",
"@standardnotes/settings": "^1.11.5",
"@standardnotes/components": "1.7.11",
"@standardnotes/features": "1.34.8",
"@standardnotes/filepicker": "1.8.0",
"@standardnotes/settings": "1.12.0",
"@standardnotes/sncrypto-web": "1.7.3",
"@standardnotes/snjs": "2.73.2",
"@standardnotes/snjs": "2.79.5",
"mobx": "^6.4.2",
"mobx-react-lite": "^3.3.0",
"preact": "^10.6.6",
"qrcode.react": "^1.0.1",
"qrcode.react": "^2.0.0",
"react-dnd": "^15.1.1",
"react-dnd-html5-backend": "^15.1.2",
"react-dnd-touch-backend": "^15.1.1"

View File

@@ -30,9 +30,9 @@
"binary": "21df7e16f57f6aede482b1281ff1c6d2ad9747fc9c3059e483aea7a03a3a8224"
},
"org.standardnotes.theme-dynamic": {
"version": "1.0.3",
"base64": "81d1465772d0233e3a9648ed0e0ea289f51d1dcf12fef8b29b25488268256521",
"binary": "42d28f3598669b801821879f0b7e26389300f151bfbc566b11c214e6091719ed"
"version": "1.0.4",
"base64": "92038664655c6cb1d6b422b9546da1a98da58b0251394cf2a485aed9b1774c07",
"binary": "d05185aca67768f35227565dacce030fdff233563f119781baaf4e95f9198115"
},
"org.standardnotes.code-editor": {
"version": "1.3.12",
@@ -95,8 +95,8 @@
"binary": "88d4b9a6ff94b1f6cba884787e051cda3cd8073022984b63f7d3b13157aa6e2a"
},
"org.standardnotes.markdown-visual-editor": {
"version": "1.0.1",
"base64": "b0984c4a35b45eb4035a2dcdc0ba71b7b68b5dffcdeea53d0c5fbcbc0ff85894",
"binary": "d8ac1028cf50d60f7a80010dd1c6047f57e01cd066ea163f169d514d1e7d2e71"
"version": "1.0.2",
"base64": "40e25db3e2b2d3ce18088ff80b5039a9f32e9a2151d08af7a29a5beb520edc3f",
"binary": "9327b7f312974c754f979493dfd6f3652f6b98c2be44d6b82b9d3e27d5a901c3"
}
}

View File

@@ -1,26 +1,25 @@
{
"files": {
"main.css": "./static/css/main.f7535a78.chunk.css",
"main.js": "./static/js/main.7192e457.chunk.js",
"main.js.map": "./static/js/main.7192e457.chunk.js.map",
"runtime-main.js": "./static/js/runtime-main.54ae45a0.js",
"runtime-main.js.map": "./static/js/runtime-main.54ae45a0.js.map",
"main.js": "./static/js/main.18531fb0.chunk.js",
"main.js.map": "./static/js/main.18531fb0.chunk.js.map",
"runtime-main.js": "./static/js/runtime-main.0f6dab54.js",
"runtime-main.js.map": "./static/js/runtime-main.0f6dab54.js.map",
"static/css/2.4ff8d936.chunk.css": "./static/css/2.4ff8d936.chunk.css",
"static/js/2.2cfce575.chunk.js": "./static/js/2.2cfce575.chunk.js",
"static/js/2.2cfce575.chunk.js.map": "./static/js/2.2cfce575.chunk.js.map",
"static/js/3.bf2ee733.chunk.js": "./static/js/3.bf2ee733.chunk.js",
"static/js/3.bf2ee733.chunk.js.map": "./static/js/3.bf2ee733.chunk.js.map",
"static/js/2.6a3180fe.chunk.js": "./static/js/2.6a3180fe.chunk.js",
"static/js/2.6a3180fe.chunk.js.map": "./static/js/2.6a3180fe.chunk.js.map",
"index.html": "./index.html",
"static/css/2.4ff8d936.chunk.css.map": "./static/css/2.4ff8d936.chunk.css.map",
"static/css/main.f7535a78.chunk.css.map": "./static/css/main.f7535a78.chunk.css.map",
"static/js/2.2cfce575.chunk.js.LICENSE.txt": "./static/js/2.2cfce575.chunk.js.LICENSE.txt",
"static/js/2.6a3180fe.chunk.js.LICENSE.txt": "./static/js/2.6a3180fe.chunk.js.LICENSE.txt",
"static/js/main.18531fb0.chunk.js.LICENSE.txt": "./static/js/main.18531fb0.chunk.js.LICENSE.txt",
"static/media/main.scss": "./static/media/roboto-vietnamese-400-normal.c0bec65d.woff2"
},
"entrypoints": [
"static/js/runtime-main.54ae45a0.js",
"static/js/runtime-main.0f6dab54.js",
"static/css/2.4ff8d936.chunk.css",
"static/js/2.2cfce575.chunk.js",
"static/js/2.6a3180fe.chunk.js",
"static/css/main.f7535a78.chunk.css",
"static/js/main.7192e457.chunk.js"
"static/js/main.18531fb0.chunk.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="A lightweight WYSIWYG markdown editor, derivated from Milkdown editor"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Markdown Visual</title><link href="./static/css/2.4ff8d936.chunk.css" rel="stylesheet"><link href="./static/css/main.f7535a78.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],f=0,p=[];f<i.length;f++)a=i[f],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&p.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(s&&s(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var c=t[i];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+"static/js/"+({}[e]||e)+"."+{3:"bf2ee733"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="./",a.oe=function(e){throw console.error(e),e};var i=this["webpackJsonp@standardnotes/markdown-visual"]=this["webpackJsonp@standardnotes/markdown-visual"]||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var s=c;t()}([])</script><script src="./static/js/2.2cfce575.chunk.js"></script><script src="./static/js/main.7192e457.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="A lightweight WYSIWYG markdown editor, derivated from Milkdown editor"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Markdown Visual</title><link href="./static/css/2.4ff8d936.chunk.css" rel="stylesheet"><link href="./static/css/main.f7535a78.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="./";var l=this["webpackJsonp@standardnotes/markdown-visual"]=this["webpackJsonp@standardnotes/markdown-visual"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="./static/js/2.6a3180fe.chunk.js"></script><script src="./static/js/main.18531fb0.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
* Determine if an object is a Buffer
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/**
* Prism: Lightweight, robust, elegant syntax highlighting
*
* @license MIT <https://opensource.org/licenses/MIT>
* @author Lea Verou <https://lea.verou.me>
* @namespace
* @public
*/
/** @license React v0.20.2
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
/**
* @license
* Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
* Build: `lodash include="includes,merge,filter,map,remove,find,omit,pull,cloneDeep,pick,uniq,sortedIndexBy,mergeWith"`
*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="./";var l=this["webpackJsonp@standardnotes/markdown-visual"]=this["webpackJsonp@standardnotes/markdown-visual"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([]);
//# sourceMappingURL=runtime-main.0f6dab54.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,8 @@
{
"name": "@standardnotes/markdown-visual",
"version": "1.0.1",
"version": "1.0.2",
"author": "Johnny Almonte <johnny@standardnotes.com>",
"description": "A lightweight WYSIWYG markdown editor, derivated from Milkdown editor",
"description": "A lightweight WYSIWYG markdown editor for Standard Notes, derived from Milkdown.",
"keywords": [
"Standard Notes",
"Standard Notes Extensions"
@@ -39,7 +39,6 @@
"@milkdown/plugin-clipboard": "^5.5.0",
"@milkdown/plugin-cursor": "^5.5.0",
"@milkdown/plugin-diagram": "^5.5.0",
"@milkdown/plugin-emoji": "^5.5.0",
"@milkdown/plugin-history": "^5.5.0",
"@milkdown/plugin-indent": "^5.5.0",
"@milkdown/plugin-listener": "^5.5.0",

View File

@@ -2,7 +2,7 @@
.section.tags,
navigation {
flex: none !important;
width: 120px !important;
width: 190px !important;
transition: width 0.25s;
}
@@ -10,21 +10,21 @@ navigation {
.section.tags:hover,
navigation:hover {
flex: initial;
width: 180px !important;
width: 250px !important;
transition: width 0.25s;
}
.section.notes,
notes-view {
flex: none !important;
width: 200px !important;
width: 270px !important;
transition: width 0.25s;
}
.section.notes:hover,
notes-view:hover {
flex: initial;
width: 350px !important;
width: 420px !important;
transition: width 0.25s;
}

View File

@@ -1,7 +1 @@
{
"version": 3,
"mappings": "AAAA;;UAEW;EACT,IAAI,EAAE,eAAe;EACrB,KAAK,EAAE,gBAAgB;EACvB,UAAU,EAAE,WAAW;;;AAGzB;;gBAEiB;EACf,IAAI,EAAE,OAAO;EACb,KAAK,EAAE,gBAAgB;EACvB,UAAU,EAAE,WAAW;;;AAGzB;UACW;EACT,IAAI,EAAE,eAAe;EACrB,KAAK,EAAE,gBAAgB;EACvB,UAAU,EAAE,WAAW;;;AAGzB;gBACiB;EACf,IAAI,EAAE,OAAO;EACb,KAAK,EAAE,gBAAgB;EACvB,UAAU,EAAE,WAAW",
"sources": ["../src/main.scss"],
"names": [],
"file": "dist.css"
}
{"version":3,"sourceRoot":"","sources":["../src/main.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;EAGE;EACA;EACA;;;AAGF;AAAA;AAAA;EAGE;EACA;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;;;AAGF;AAAA;EAEE;EACA;EACA","file":"dist.css"}

View File

@@ -1,6 +1,6 @@
{
"name": "sn-theme-dynamic",
"version": "1.0.3",
"name": "@standardnotes/dynamic-theme",
"version": "1.0.4",
"main": "dist/dist.css",
"devDependencies": {
"grunt": "^1.0.1",

265
yarn.lock
View File

@@ -1751,10 +1751,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@bugsnag/browser@^7.16.1":
version "7.16.1"
resolved "https://registry.yarnpkg.com/@bugsnag/browser/-/browser-7.16.1.tgz#652aa3ed64e51ba0015878d252a08917429bba03"
integrity sha512-Tq9fWpwmqdOsbedYL67GzsTKrG5MERIKtnKCi5FyvFjTj143b6as0pwj7LWQ+Eh8grWlR7S11+VvJmb8xnY8Tg==
"@bugsnag/browser@^7.16.2":
version "7.16.2"
resolved "https://registry.yarnpkg.com/@bugsnag/browser/-/browser-7.16.2.tgz#b6fc7ebaeae4800d195b660abc770caf33670e03"
integrity sha512-iBbAmjTDe0I6WPTHi3wIcmKu3ykydtT6fc8atJA65rzgDLMlTM1Wnwz4Ny1cn0bVouLGa48BRiOJ27Rwy7QRYA==
dependencies:
"@bugsnag/core" "^7.16.1"
@@ -1774,18 +1774,18 @@
resolved "https://registry.yarnpkg.com/@bugsnag/cuid/-/cuid-3.0.0.tgz#2ee7642a30aee6dc86f5e7f824653741e42e5c35"
integrity sha512-LOt8aaBI+KvOQGneBtpuCz3YqzyEAehd1f3nC5yr9TIYW1+IzYKa2xWS4EiMz5pPOnRPHkyyS5t/wmSmN51Gjg==
"@bugsnag/js@^7.16.1":
version "7.16.1"
resolved "https://registry.yarnpkg.com/@bugsnag/js/-/js-7.16.1.tgz#4a4ec2c7f3e047333e7d15eb53cb11f165b7067f"
integrity sha512-yb83OmsbIMDJhX3hHhbHl5StN72feqdr/Ctq7gqsdcfOHNb2121Edf2EbegPJKZhFqSik66vWwiVbGJ6CdS/UQ==
"@bugsnag/js@^7.16.2":
version "7.16.2"
resolved "https://registry.yarnpkg.com/@bugsnag/js/-/js-7.16.2.tgz#fb15ec9cc5980f0b210aecc7b740274e50400a91"
integrity sha512-AzV0PtG3SZt+HnA2JmRJeI60aDNZsIJbEEAZIWZeATvWBt5RdVdsWKllM1SkTvURfxfdAVd4Xry3BgVrh8nEbg==
dependencies:
"@bugsnag/browser" "^7.16.1"
"@bugsnag/node" "^7.16.1"
"@bugsnag/browser" "^7.16.2"
"@bugsnag/node" "^7.16.2"
"@bugsnag/node@^7.16.1":
version "7.16.1"
resolved "https://registry.yarnpkg.com/@bugsnag/node/-/node-7.16.1.tgz#473bb6eeb346b418295b49e4c4576e0004af4901"
integrity sha512-9zBA1IfDTbLKMoDltdhELpTd1e+b5+vUW4j40zGA+4SYIe64XNZKShfqRdvij7embvC1iHQ9UpuPRSk60P6Dng==
"@bugsnag/node@^7.16.2":
version "7.16.2"
resolved "https://registry.yarnpkg.com/@bugsnag/node/-/node-7.16.2.tgz#8ac1b41786306d8917fb9fe222ada74fe0c4c6d5"
integrity sha512-V5pND701cIYGzjjTwt0tuvAU1YyPB9h7vo5F/DzrDHRPmCINA/oVbc0Twco87knc2VPe8ntGFqTicTY65iOWzg==
dependencies:
"@bugsnag/core" "^7.16.1"
byline "^5.0.0"
@@ -2313,10 +2313,10 @@
dependencies:
"@standardnotes/common" "^1.15.3"
"@standardnotes/auth@^3.17.3":
version "3.17.3"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.17.3.tgz#a00f10faa0fb2a7dd76509d3b678f85818aad63c"
integrity sha512-tb5ylXuDBPhgeZZynNsMk83N74NMMV9z6M9hyrwuK5HbKWM5r5L9U8lwFawG8flqTKpYzPeWxmaRFZT/5qR22Q==
"@standardnotes/auth@^3.17.4":
version "3.17.4"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.17.4.tgz#ab2449a280ee6ec794fe397c9d8387e105c6c644"
integrity sha512-0710hUiYoRFjABfUFPlyOIyCMx0gC0rlJtFdPYK7WHXf0bfxO0JiSXeWbNSvV0QVGqHIkcUjGmdyE6cJEKTh9g==
dependencies:
"@standardnotes/common" "^1.15.3"
jsonwebtoken "^8.5.1"
@@ -2326,60 +2326,66 @@
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.15.3.tgz#0b8ce48b81b260abe2d405431fb04aacb44b5a01"
integrity sha512-9oh/W3sFQYyA5Vabcbu6BUkLVkFq/25Q5EK9KCd4aT9QnDJ9JQlTtzDmTk1jYuM6rnccsJ6SW2pcWjbi9FVniw==
"@standardnotes/components@1.7.9":
version "1.7.9"
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.9.tgz#41e5fdbcee250b9b3c18045dad8998c6f668307b"
integrity sha512-/+Paw6ry/IS9ldYUM/lgC4O6qwl1fukWvNw65IMKyB9LMY3+xTh/I2BfnWynP117pVPxtu3/2+FBEnx04KvQwg==
"@standardnotes/components@1.7.11":
version "1.7.11"
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.11.tgz#791c1e1bef5bc223f34c423e4ddd443fe8a699d3"
integrity sha512-V8gtuLMbn0ldRQLZj0iKrm5PRufHdRGbSQ32/u0w1M6dTE42wKUYuMNhPwkn9cNEaYzhhcHRD01L/lYEepBZBQ==
"@standardnotes/domain-events@^2.23.21":
version "2.23.21"
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.23.21.tgz#bbf752ee7a0fd08b9fb675e81b46d3c10bbf89e9"
integrity sha512-YPpwy+QrDziBOpjt5cOIZwY47fOddN3038+NTRSqxi4h/D8hU+U5O8dGl2XktENEq9DqVJ78OVmWBjkA2FlsEQ==
"@standardnotes/domain-events@^2.24.5":
version "2.24.5"
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.24.5.tgz#f4699b2241e0e51829d88ec8afe1b8d00d5fe37d"
integrity sha512-NrbeaQ0Yl+56cMlXOLSISHCpkiRTAcmmRtIEPAqn0V7RBeRXqKfy6Fo5OUPSuGtYinQfbKBLi5rxIC/rqhMZFg==
dependencies:
"@standardnotes/auth" "^3.17.3"
"@standardnotes/features" "^1.34.1"
"@standardnotes/auth" "^3.17.4"
"@standardnotes/features" "^1.34.8"
"@standardnotes/features@1.34.1", "@standardnotes/features@^1.34.1":
version "1.34.1"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.34.1.tgz#da2e37a437ba274dfbdb9e0f5ab9de3ee95689ee"
integrity sha512-ILc4mSLoAE6S24GquNAS5TNLqM+RBKx8zyfFVgKEUB2sF00341HDi+/l5MuCCds2VnhTvIuVcIsCrPlj9NVmFg==
"@standardnotes/features@1.34.8", "@standardnotes/features@^1.34.8":
version "1.34.8"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.34.8.tgz#a82b7648706bdf62355eff6df1f4ccadf0b83afa"
integrity sha512-0ttEnBjod7zANER0sU6TzCz0RHSt+p1lQ9xzg9a3z6Azjz8WAJkQAMeIBJNgym8EPVI8QUjKFyz/Rdo0PNAyng==
dependencies:
"@standardnotes/auth" "^3.17.3"
"@standardnotes/auth" "^3.17.4"
"@standardnotes/common" "^1.15.3"
"@standardnotes/payloads@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@standardnotes/payloads/-/payloads-1.3.2.tgz#3255abf6e2a2385c73e7998705066b828e5a74e0"
integrity sha512-SnDqdQXyEWett/Y33crvNnDGwv4BfCkoHId99fLd+UL5uTgXpuFgkd/AVHg+mOT5xC3+KAR8zdUCmQSXTRuysg==
"@standardnotes/filepicker@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@standardnotes/filepicker/-/filepicker-1.8.0.tgz#f8d85350c4b4022235e3017b0b2c7841882eef4f"
integrity sha512-xgFoD+aHFCKV5pAbhKNCyyhUL18G9l2Aep6eiQ5gxB55l8CcNHlLBi5qw5i1we07NdCwIJ3yP3aVKI+7qe22yQ==
"@standardnotes/payloads@^1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@standardnotes/payloads/-/payloads-1.4.7.tgz#e722a117a1bb02b76555cc6b164eefdb765bea9f"
integrity sha512-0y7fDqu1OBMhvIrQDkJbsYEzpPqodSM9X8c5s6hVCs4GtriQcw1JAjsekRyV2/9iKd8GiOldNNWBDDYre+78gQ==
dependencies:
"@standardnotes/applications" "^1.1.3"
"@standardnotes/common" "^1.15.3"
"@standardnotes/features" "^1.34.1"
"@standardnotes/features" "^1.34.8"
"@standardnotes/utils" "^1.2.3"
"@standardnotes/responses@^1.1.7":
version "1.1.7"
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.1.7.tgz#ab982f94693f2e1f967809bdb5e3863182770b14"
integrity sha512-1q8+eGjvttleesciHAOTe6u478W3UpEy+0StT8ZL3miFWzIyCLXJmnxNbYfYCm6oZ+t2rwhW+AzmnMRhG2cOUA==
"@standardnotes/responses@1.3.7", "@standardnotes/responses@^1.3.7":
version "1.3.7"
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.3.7.tgz#6304d55613df77342c21336b8c4000a65d3bb42f"
integrity sha512-RYIWNB6RinZmOSre6lfLmFAdpN0cSWb5PCjmYAThedkor21iK//9yhOG5xKChqIfB+Y+kaKFfnm8+BtONcMD0A==
dependencies:
"@standardnotes/auth" "^3.17.3"
"@standardnotes/auth" "^3.17.4"
"@standardnotes/common" "^1.15.3"
"@standardnotes/features" "^1.34.1"
"@standardnotes/payloads" "^1.3.2"
"@standardnotes/features" "^1.34.8"
"@standardnotes/payloads" "^1.4.7"
"@standardnotes/services@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.4.0.tgz#776ee5d022e4512844af1a284a2e90f599217218"
integrity sha512-wO0LQ+qMG0bfH0HNPulsO8nZ2Z1Y84NLP0fZdMdtqiuaCi1GrM/PUlcL/fpXCJKNeTKoYa8Dh4PfF8DOjalKXg==
"@standardnotes/services@1.5.9", "@standardnotes/services@^1.5.9":
version "1.5.9"
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.5.9.tgz#4356c739659145553fafe87d59f98db6c9dc4ad8"
integrity sha512-EiiAbFGwsqwgb2cFLDKt1Jb9LpuKynrVwYtDUoxZlM9FiRvcmQdK9w+0upS/mthcreaiKHjdkSuQMwJkCaU3rw==
dependencies:
"@standardnotes/applications" "^1.1.3"
"@standardnotes/common" "^1.15.3"
"@standardnotes/responses" "^1.3.7"
"@standardnotes/utils" "^1.2.3"
"@standardnotes/settings@^1.11.5":
version "1.11.5"
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.11.5.tgz#792bf3e0505065486f521b2f19c2bf1081b8fa5e"
integrity sha512-n6StAS3nBgs7Lia5mOt3+H4Xd6hatCcHFx83paFq9kdI1cKbqn7oFF4g/rUbWPy4nsx+96zBehB6EhsJ2MGzyQ==
"@standardnotes/settings@1.12.0", "@standardnotes/settings@^1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.12.0.tgz#43f3dd7f015f726b1ed88a48fcc3737899116cd5"
integrity sha512-w6S5TT7KRpvUb+JsXZ7ucWPjlWRtpKQdsyT7cLs66ynKRXxUn40hf4kA8T9FhuLAKbG+wIYDrAZl3FRk+HvDWQ==
"@standardnotes/sncrypto-common@^1.7.3":
version "1.7.3"
@@ -2395,27 +2401,27 @@
buffer "^6.0.3"
libsodium-wrappers "^0.7.9"
"@standardnotes/snjs@2.73.2":
version "2.73.2"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.73.2.tgz#5361d73c861b990d06821d5a822dcb0893830a43"
integrity sha512-oGPZmzX+tNWQFxmvrQRtsqlofbW25YUOapyEPN8oVgfwV98r8EVzpUGXHWkog3ZW6oO3aO22Rhtk0D92d0CqtQ==
"@standardnotes/snjs@2.79.5":
version "2.79.5"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.79.5.tgz#2df9ef88d0c0ffead2e0044249c5a833921d94dc"
integrity sha512-IEZtHg6Ga4/R2QUMbJhD4MEOvkNuBtiNYrokpqrf5kzGGOvL3b/RwcSWUW8zjBS1WxDSGmIo3WBKze3FXhfdEA==
dependencies:
"@standardnotes/applications" "^1.1.3"
"@standardnotes/auth" "^3.17.3"
"@standardnotes/auth" "^3.17.4"
"@standardnotes/common" "^1.15.3"
"@standardnotes/domain-events" "^2.23.21"
"@standardnotes/features" "^1.34.1"
"@standardnotes/payloads" "^1.3.2"
"@standardnotes/responses" "^1.1.7"
"@standardnotes/services" "^1.4.0"
"@standardnotes/settings" "^1.11.5"
"@standardnotes/domain-events" "^2.24.5"
"@standardnotes/features" "^1.34.8"
"@standardnotes/payloads" "^1.4.7"
"@standardnotes/responses" "^1.3.7"
"@standardnotes/services" "^1.5.9"
"@standardnotes/settings" "^1.12.0"
"@standardnotes/sncrypto-common" "^1.7.3"
"@standardnotes/utils" "^1.2.3"
"@standardnotes/stylekit@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@standardnotes/stylekit/-/stylekit-5.14.0.tgz#806b7d896fb94de8be72e762dd205beefff05fd7"
integrity sha512-zCB8QFPUTe+063RsLNCudkP6FY6ujKC3iE2pD6ak4htpXtW6DndwYbhEffuiWIjbRzDGAy0YyQ7y8V19Jw7ElQ==
"@standardnotes/stylekit@5.15.0":
version "5.15.0"
resolved "https://registry.yarnpkg.com/@standardnotes/stylekit/-/stylekit-5.15.0.tgz#14c0ff5c4e40d4afa9ea6fec0431934e1184a18f"
integrity sha512-BR78DIdXo8fxzNPruiugiQJfgT7usUFWxJ0CPnjhk3hshYM2/kleHdcrahYdi2Rs5MR7chhbsvUFUWHxykOaRg==
dependencies:
"@nanostores/preact" "^0.1.3"
"@reach/listbox" "^0.16.2"
@@ -2767,10 +2773,10 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react@^17.0.39":
version "17.0.39"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce"
integrity sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==
"@types/react@^17.0.40":
version "17.0.40"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.40.tgz#dc010cee6254d5239a138083f3799a16638e6bad"
integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
@@ -2813,6 +2819,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
"@types/wicg-file-system-access@^2020.9.5":
version "2020.9.5"
resolved "https://registry.yarnpkg.com/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz#4a0c8f3d1ed101525f329e86c978f7735404474f"
integrity sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==
"@types/ws@^8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21"
@@ -2832,14 +2843,14 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.13.0.tgz#2809052b85911ced9c54a60dac10e515e9114497"
integrity sha512-vLktb2Uec81fxm/cfz2Hd6QaWOs8qdmVAZXLdOBX6JFJDhf6oDZpMzZ4/LZ6SFM/5DgDcxIMIvy3F+O9yZBuiQ==
"@typescript-eslint/eslint-plugin@^5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz#5119b67152356231a0e24b998035288a9cd21335"
integrity sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w==
dependencies:
"@typescript-eslint/scope-manager" "5.13.0"
"@typescript-eslint/type-utils" "5.13.0"
"@typescript-eslint/utils" "5.13.0"
"@typescript-eslint/scope-manager" "5.14.0"
"@typescript-eslint/type-utils" "5.14.0"
"@typescript-eslint/utils" "5.14.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
@@ -2847,69 +2858,69 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.13.0.tgz#0394ed8f2f849273c0bf4b811994d177112ced5c"
integrity sha512-GdrU4GvBE29tm2RqWOM0P5QfCtgCyN4hXICj/X9ibKED16136l9ZpoJvCL5pSKtmJzA+NRDzQ312wWMejCVVfg==
"@typescript-eslint/parser@^5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.14.0.tgz#7c79f898aa3cff0ceee6f1d34eeed0f034fb9ef3"
integrity sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw==
dependencies:
"@typescript-eslint/scope-manager" "5.13.0"
"@typescript-eslint/types" "5.13.0"
"@typescript-eslint/typescript-estree" "5.13.0"
"@typescript-eslint/scope-manager" "5.14.0"
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/typescript-estree" "5.14.0"
debug "^4.3.2"
"@typescript-eslint/scope-manager@5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.13.0.tgz#cf6aff61ca497cb19f0397eea8444a58f46156b6"
integrity sha512-T4N8UvKYDSfVYdmJq7g2IPJYCRzwtp74KyDZytkR4OL3NRupvswvmJQJ4CX5tDSurW2cvCc1Ia1qM7d0jpa7IA==
"@typescript-eslint/scope-manager@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz#ea518962b42db8ed0a55152ea959c218cb53ca7b"
integrity sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw==
dependencies:
"@typescript-eslint/types" "5.13.0"
"@typescript-eslint/visitor-keys" "5.13.0"
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/visitor-keys" "5.14.0"
"@typescript-eslint/type-utils@5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.13.0.tgz#b0efd45c85b7bab1125c97b752cab3a86c7b615d"
integrity sha512-/nz7qFizaBM1SuqAKb7GLkcNn2buRdDgZraXlkhz+vUGiN1NZ9LzkA595tHHeduAiS2MsHqMNhE2zNzGdw43Yg==
"@typescript-eslint/type-utils@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz#711f08105860b12988454e91df433567205a8f0b"
integrity sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw==
dependencies:
"@typescript-eslint/utils" "5.13.0"
"@typescript-eslint/utils" "5.14.0"
debug "^4.3.2"
tsutils "^3.21.0"
"@typescript-eslint/types@5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.13.0.tgz#da1de4ae905b1b9ff682cab0bed6b2e3be9c04e5"
integrity sha512-LmE/KO6DUy0nFY/OoQU0XelnmDt+V8lPQhh8MOVa7Y5k2gGRd6U9Kp3wAjhB4OHg57tUO0nOnwYQhRRyEAyOyg==
"@typescript-eslint/types@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11"
integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw==
"@typescript-eslint/typescript-estree@5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.13.0.tgz#b37c07b748ff030a3e93d87c842714e020b78141"
integrity sha512-Q9cQow0DeLjnp5DuEDjLZ6JIkwGx3oYZe+BfcNuw/POhtpcxMTy18Icl6BJqTSd+3ftsrfuVb7mNHRZf7xiaNA==
"@typescript-eslint/typescript-estree@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314"
integrity sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA==
dependencies:
"@typescript-eslint/types" "5.13.0"
"@typescript-eslint/visitor-keys" "5.13.0"
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/visitor-keys" "5.14.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.13.0.tgz#2328feca700eb02837298339a2e49c46b41bd0af"
integrity sha512-+9oHlPWYNl6AwwoEt5TQryEHwiKRVjz7Vk6kaBeD3/kwHE5YqTGHtm/JZY8Bo9ITOeKutFaXnBlMgSATMJALUQ==
"@typescript-eslint/utils@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.14.0.tgz#6c8bc4f384298cbbb32b3629ba7415f9f80dc8c4"
integrity sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.13.0"
"@typescript-eslint/types" "5.13.0"
"@typescript-eslint/typescript-estree" "5.13.0"
"@typescript-eslint/scope-manager" "5.14.0"
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/typescript-estree" "5.14.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.13.0.tgz#f45ff55bcce16403b221ac9240fbeeae4764f0fd"
integrity sha512-HLKEAS/qA1V7d9EzcpLFykTePmOQqOFim8oCvhY3pZgQ8Hi38hYpHd9e5GN6nQBFQNecNhws5wkS9Y5XIO0s/g==
"@typescript-eslint/visitor-keys@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986"
integrity sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw==
dependencies:
"@typescript-eslint/types" "5.13.0"
"@typescript-eslint/types" "5.14.0"
eslint-visitor-keys "^3.0.0"
"@webassemblyjs/ast@1.11.1":
@@ -4115,10 +4126,10 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-loader@^6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.0.tgz#c1200da1dfffe6643b18bda20fdd84cad3e36d39"
integrity sha512-S7HCfCiDHLA+VXKqdZwyRZgoO0R9BnKDnVIoHMq5grl3N86zAu7MB+FBWHr5xOJC8SmvpTLha/2NpfFkFEN/ig==
css-loader@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==
dependencies:
icss-utils "^5.1.0"
postcss "^8.4.7"
@@ -8002,10 +8013,10 @@ qr.js@0.0.0:
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
qrcode.react@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-1.0.1.tgz#2834bb50e5e275ffe5af6906eff15391fe9e38a5"
integrity sha512-8d3Tackk8IRLXTo67Y+c1rpaiXjoz/Dd2HpcMdW//62/x8J1Nbho14Kh8x974t9prsLHN6XqVgcnRiBGFptQmg==
qrcode.react@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-2.0.0.tgz#20b738eddcfc6958673bbbd1a0c5568ee5c399f5"
integrity sha512-1CCzwC4KHYCzOLb7M+lKYqHzDMVsppKJBhrU1ZDCuJRHKiD99LeDHOkuvUKCRvnJgDzp9SnB6/WNA7qp6I7ozA==
dependencies:
loose-envify "^1.4.0"
prop-types "^15.6.0"