feat: show listed on mobile (#554)

* feat: show listed on mobile

* fix: avoid endless loop when fetching listed items

* refactor: render side menu items without checking item's index

* feat: implement new blog addition

* feat: ui improvements, show appropriate message when no action available for a blog

* feat: refresh listed menu items when user executes blog-related action

* feat: UI improvements - add icons, better distinction between Listed sections

* feat: show loading indicators on async actions, near respective items

* chore: update dependencies

* fix: updates regarding last snjs changes

* fix: remove unnecessary html tags from message
This commit is contained in:
Vardan Hakobyan
2022-02-23 19:02:47 +04:00
committed by GitHub
parent f4bb8794ca
commit 804df2adf4
11 changed files with 440 additions and 91 deletions

View File

@@ -26,8 +26,8 @@
"@react-navigation/native": "^5.9.3",
"@react-navigation/stack": "^5.14.3",
"@standardnotes/sncrypto-common": "1.7.0",
"@standardnotes/snjs": "2.61.0",
"@standardnotes/stylekit": "5.5.0",
"@standardnotes/snjs": "2.63.1",
"@standardnotes/stylekit": "5.9.0",
"js-base64": "^3.5.2",
"moment": "^2.29.1",
"react": "17.0.2",
@@ -69,8 +69,8 @@
"@babel/core": "^7.11.6",
"@babel/runtime": "^7.11.2",
"@react-native-community/eslint-config": "^2.0.0",
"@standardnotes/components": "^1.7.3",
"@standardnotes/features": "^1.32.6",
"@standardnotes/components": "^1.7.5",
"@standardnotes/features": "^1.32.11",
"@types/detox": "^16.4.1",
"@types/faker": "^5.1.3",
"@types/jest": "^26.0.14",

View File

@@ -4,6 +4,8 @@ import ArchiveIcon from '@standardnotes/stylekit/dist/icons/ic-archive.svg';
import AuthenticatorIcon from '@standardnotes/stylekit/dist/icons/ic-authenticator.svg';
import CodeIcon from '@standardnotes/stylekit/dist/icons/ic-code.svg';
import MarkdownIcon from '@standardnotes/stylekit/dist/icons/ic-markdown.svg';
import NotesIcon from '@standardnotes/stylekit/dist/icons/ic-notes.svg';
import OpenInIcon from '@standardnotes/stylekit/dist/icons/ic-open-in.svg';
import PencilOffIcon from '@standardnotes/stylekit/dist/icons/ic-pencil-off.svg';
import PinFilledIcon from '@standardnotes/stylekit/dist/icons/ic-pin-filled.svg';
import SpreadsheetsIcon from '@standardnotes/stylekit/dist/icons/ic-spreadsheets.svg';
@@ -11,6 +13,7 @@ import TasksIcon from '@standardnotes/stylekit/dist/icons/ic-tasks.svg';
import PlainTextIcon from '@standardnotes/stylekit/dist/icons/ic-text-paragraph.svg';
import RichTextIcon from '@standardnotes/stylekit/dist/icons/ic-text-rich.svg';
import TrashFilledIcon from '@standardnotes/stylekit/dist/icons/ic-trash-filled.svg';
import UserAddIcon from '@standardnotes/stylekit/dist/icons/ic-user-add.svg';
import React, { useContext } from 'react';
import { ThemeContext } from 'styled-components';
@@ -26,6 +29,9 @@ const ICONS = {
'trash-filled': TrashFilledIcon,
'pin-filled': PinFilledIcon,
archive: ArchiveIcon,
'user-add': UserAddIcon,
'open-in': OpenInIcon,
notes: NotesIcon,
};
export type TEditorIcon = Extract<
@@ -42,9 +48,10 @@ export type TEditorIcon = Extract<
| 'pin-filled'
| 'archive'
>;
export type TGeneralIcon = Extract<IconType, 'user-add' | 'open-in' | 'notes'>;
type Props = {
type: TEditorIcon;
type: TEditorIcon | TGeneralIcon;
fill?: string;
styles?: Record<string, unknown>;
};

View File

@@ -1,5 +1,5 @@
import AsyncStorage from '@react-native-community/async-storage';
import { ApplicationIdentifier, DeviceInterface } from '@standardnotes/snjs';
import { ApplicationIdentifier, AbstractDevice } from '@standardnotes/snjs';
import { Alert, Linking, Platform } from 'react-native';
import DefaultPreference from 'react-native-default-preference';
import FingerprintScanner from 'react-native-fingerprint-scanner';
@@ -45,7 +45,7 @@ const showLoadFailForItemIds = (failedItemIds: string[]) => {
Alert.alert('Unable to load item(s)', text);
};
export class MobileDeviceInterface extends DeviceInterface {
export class MobileDeviceInterface extends AbstractDevice {
constructor() {
super(setTimeout, setInterval);
}

View File

@@ -0,0 +1,35 @@
import { StyleSheet } from 'react-native';
import styled from 'styled-components/native';
export const styles = StyleSheet.create({
blogItemIcon: {
marginTop: -6,
},
loadingIndicator: {
alignSelf: 'flex-start',
},
blogActionInProgressIndicator: {
marginTop: -5,
marginLeft: 6,
transform: [
{
scale: 0.8,
},
],
},
});
export const CreateBlogContainer = styled.View`
margin-bottom: 8px;
`;
export const CantLoadActionsText = styled.Text`
font-size: 12px;
margin-top: -12px;
margin-bottom: 10px;
opacity: 0.7;
color: ${({ theme }) => theme.stylekitContrastForegroundColor};
`;
export const ListedItemRow = styled.View`
display: flex;
flex-direction: row;
align-items: center;
`;

View File

@@ -0,0 +1,242 @@
import { SnIcon } from '@Components/SnIcon';
import { ApplicationContext } from '@Root/ApplicationContext';
import {
CantLoadActionsText,
CreateBlogContainer,
ListedItemRow,
styles,
} from '@Screens/SideMenu/Listed.styled';
import { SideMenuCell } from '@Screens/SideMenu/SideMenuCell';
import { SideMenuOptionIconDescriptionType } from '@Screens/SideMenu/SideMenuSection';
import { ButtonType, ListedAccount, SNNote } from '@standardnotes/snjs';
import { ListedAccountInfo } from '@standardnotes/snjs/dist/@types/services/api/responses';
import { useCustomActionSheet } from '@Style/custom_action_sheet';
import React, { FC, useCallback, useContext, useEffect, useState } from 'react';
import { ActivityIndicator, FlatList, View } from 'react-native';
type TProps = {
note: SNNote;
};
type TListedAccountItem =
| ListedAccountInfo
| Pick<ListedAccountInfo, 'display_name'>;
export const Listed: FC<TProps> = ({ note }) => {
const application = useContext(ApplicationContext);
const [isLoading, setIsLoading] = useState(false);
const [isActionInProgress, setIsActionInProgress] = useState(false);
const [isRequestingAccount, setIsRequestingAccount] = useState(false);
const [listedAccounts, setListedAccounts] = useState<ListedAccount[]>([]);
const [listedAccountDetails, setListedAccountDetails] = useState<
TListedAccountItem[]
>([]);
const [
authorUrlWithInProgressAction,
setAuthorUrlWithInProgressAction,
] = useState<string | null>(null);
const { showActionSheet } = useCustomActionSheet();
const getListedAccountsDetails = useCallback(
async (accounts: ListedAccount[]) => {
if (!application) {
return;
}
const listedAccountsArray: TListedAccountItem[] = [];
for (const listedAccountItem of accounts) {
const listedItemInfo = await application.getListedAccountInfo(
listedAccountItem,
note?.uuid
);
listedAccountsArray.push(
listedItemInfo
? listedItemInfo
: { display_name: listedAccountItem.authorId }
);
}
return listedAccountsArray;
},
[application, note?.uuid]
);
const reloadListedAccounts = useCallback(async () => {
if (!application) {
return [];
}
setIsLoading(true);
const accounts = await application.getListedAccounts();
setListedAccounts(accounts);
setListedAccountDetails((await getListedAccountsDetails(accounts)) || []);
setIsLoading(false);
}, [application, getListedAccountsDetails]);
const registerNewAccount = useCallback(() => {
if (!application || isRequestingAccount) {
return;
}
const requestAccount = async () => {
setIsRequestingAccount(true);
const account = await application.requestNewListedAccount();
if (account) {
const openSettings = await application.alertService.confirm(
'Your new Listed blog has been successfully created!' +
' You can publish a new post to your blog from Standard Notes via the' +
' Actions menu in the editor pane. Open your blog settings to begin setting it up.',
undefined,
'Open Settings',
ButtonType.Info,
'Later'
);
reloadListedAccounts();
if (openSettings) {
const info = await application.getListedAccountInfo(account);
if (info) {
application.deviceInterface.openUrl(info?.settings_url);
}
}
}
setIsRequestingAccount(false);
};
requestAccount();
}, [application, isRequestingAccount, reloadListedAccounts]);
useEffect(() => {
const loadListedData = async () => {
await reloadListedAccounts();
};
loadListedData();
}, [reloadListedAccounts]);
const doesListedItemHaveActions = (
item: TListedAccountItem
): item is ListedAccountInfo => {
return (item as ListedAccountInfo).author_url !== undefined;
};
const showActionsMenu = (item: TListedAccountItem, index: number) => {
if (!application) {
return;
}
if (!doesListedItemHaveActions(item)) {
application.alertService.alert('Unable to load actions.');
return;
}
showActionSheet(
item.display_name,
item.actions.map(action => ({
text: action.label,
callback: async () => {
setIsActionInProgress(true);
setAuthorUrlWithInProgressAction(item.author_url);
const response = await application.actionsManager.runAction(
action,
note
);
if (!response || response.error) {
setIsActionInProgress(false);
setAuthorUrlWithInProgressAction(null);
return;
}
const listedDetails = (await getListedAccountsDetails(
listedAccounts
)) as TListedAccountItem[];
setListedAccountDetails(listedDetails);
showActionsMenu(listedDetails[index], index);
setIsActionInProgress(false);
setAuthorUrlWithInProgressAction(null);
},
}))
);
};
if (!application) {
return null;
}
return (
<View>
{isLoading && <ActivityIndicator style={styles.loadingIndicator} />}
{listedAccountDetails.length > 0 && (
<FlatList
data={listedAccountDetails}
renderItem={({ item, index }) => {
if (!item) {
return null;
}
return (
<View>
<ListedItemRow>
<SideMenuCell
text={item.display_name}
onSelect={() => showActionsMenu(item, index)}
iconDesc={{
side: 'left',
type: SideMenuOptionIconDescriptionType.CustomComponent,
value: (
<SnIcon type={'notes'} styles={styles.blogItemIcon} />
),
}}
/>
{isActionInProgress &&
(item as ListedAccountInfo).author_url ===
authorUrlWithInProgressAction && (
<ActivityIndicator
style={styles.blogActionInProgressIndicator}
/>
)}
</ListedItemRow>
{!isLoading && !doesListedItemHaveActions(item) && (
<CantLoadActionsText>
Unable to load actions
</CantLoadActionsText>
)}
</View>
);
}}
/>
)}
<CreateBlogContainer>
<ListedItemRow>
<SideMenuCell
text={
isRequestingAccount ? 'Creating account...' : 'Create New Author'
}
onSelect={registerNewAccount}
iconDesc={{
side: 'left',
type: SideMenuOptionIconDescriptionType.CustomComponent,
value: <SnIcon type={'user-add'} styles={styles.blogItemIcon} />,
}}
/>
{isRequestingAccount && (
<ActivityIndicator style={styles.blogActionInProgressIndicator} />
)}
</ListedItemRow>
<ListedItemRow>
<SideMenuCell
text={'Learn more'}
onSelect={() =>
application.deviceInterface.openUrl('https://listed.to')
}
iconDesc={{
side: 'left',
type: SideMenuOptionIconDescriptionType.CustomComponent,
value: <SnIcon type={'open-in'} styles={styles.blogItemIcon} />,
}}
/>
</ListedItemRow>
</CreateBlogContainer>
</View>
);
};

View File

@@ -33,7 +33,7 @@ import {
useStyles,
} from './MainSideMenu.styled';
import { SideMenuHero } from './SideMenuHero';
import { SideMenuOption, SideMenuSection } from './SideMenuSection';
import { SideMenuOptionIconDescriptionType, SideMenuOption, SideMenuSection } from './SideMenuSection';
import { TagSelectionList } from './TagSelectionList';
type Props = {
@@ -163,7 +163,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
const iconDescriptorForTheme = (currentTheme: SNTheme | MobileTheme) => {
const desc = {
type: 'circle',
type: SideMenuOptionIconDescriptionType.Circle,
side: 'right' as 'right',
};
@@ -218,7 +218,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => {
text: 'Get More Themes',
key: 'get-theme',
iconDesc: {
type: 'icon',
type: SideMenuOptionIconDescriptionType.Icon,
name: ThemeService.nameForIcon(ICON_BRUSH),
side: 'right',
size: 17,

View File

@@ -12,6 +12,7 @@ import {
SCREEN_INPUT_MODAL_TAG,
SCREEN_NOTE_HISTORY,
} from '@Screens/screens';
import { Listed } from '@Screens/SideMenu/Listed';
import {
ButtonType,
ComponentArea,
@@ -52,7 +53,7 @@ import DrawerLayout from 'react-native-gesture-handler/DrawerLayout';
import Icon from 'react-native-vector-icons/Ionicons';
import { ThemeContext } from 'styled-components/native';
import { SafeAreaContainer, useStyles } from './NoteSideMenu.styled';
import { SideMenuOption, SideMenuSection } from './SideMenuSection';
import { SideMenuOption, SideMenuOptionIconDescriptionType, SideMenuSection } from './SideMenuSection';
import { TagSelectionList } from './TagSelectionList';
function sortAlphabetically(array: SNComponent[]): SNComponent[] {
@@ -382,7 +383,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
text: 'Get More Editors',
key: 'get-editors',
iconDesc: {
type: 'icon',
type: SideMenuOptionIconDescriptionType.Icon,
name: ThemeService.nameForIcon(ICON_MEDICAL),
side: 'right',
size: 17,
@@ -496,7 +497,7 @@ export const NoteSideMenu = React.memo((props: Props) => {
text: rawOption.text,
key: rawOption.icon,
iconDesc: {
type: 'icon',
type: SideMenuOptionIconDescriptionType.Icon,
side: 'right' as 'right',
name: ThemeService.nameForIcon(rawOption.icon),
},
@@ -585,42 +586,70 @@ export const NoteSideMenu = React.memo((props: Props) => {
return null;
}
enum MenuSections {
OptionsSection = 'options-section',
EditorsSection = 'editors-section',
ListedSection = 'listed-section',
TagsSection = 'tags-section',
}
return (
<SafeAreaContainer edges={['top', 'bottom', 'right']}>
<FlatList
style={styles.sections}
data={['options-section', 'editors-section', 'tags-section'].map(
key => ({
key,
noteOptions,
editorComponents: editors,
onTagSelect,
selectedTags,
})
)}
renderItem={({ item, index }) =>
index === 0 ? (
<SideMenuSection title="Options" options={item.noteOptions} />
) : index === 1 ? (
<SideMenuSection
title="Editors"
options={item.editorComponents}
collapsed={true}
/>
) : index === 2 ? (
<SideMenuSection title="Tags">
<TagSelectionList
hasBottomPadding={Platform.OS === 'android'}
contentType={ContentType.Tag}
onTagSelect={item.onTagSelect}
selectedTags={item.selectedTags}
emptyPlaceholder={
'Create a new tag using the tag button in the bottom right corner.'
}
data={Object.values(MenuSections).map(key => ({
key,
noteOptions,
editorComponents: editors,
onTagSelect,
selectedTags,
}))}
renderItem={({ item }) => {
const {
OptionsSection,
EditorsSection,
ListedSection,
TagsSection,
} = MenuSections;
if (item.key === OptionsSection) {
return (
<SideMenuSection title="Options" options={item.noteOptions} />
);
}
if (item.key === EditorsSection) {
return (
<SideMenuSection
title="Editors"
options={item.editorComponents}
collapsed={true}
/>
</SideMenuSection>
) : null
}
);
}
if (item.key === ListedSection) {
return (
<SideMenuSection title="Listed" collapsed={true}>
<Listed note={note} />
</SideMenuSection>
);
}
if (item.key === TagsSection) {
return (
<SideMenuSection title="Tags">
<TagSelectionList
hasBottomPadding={Platform.OS === 'android'}
contentType={ContentType.Tag}
onTagSelect={item.onTagSelect}
selectedTags={item.selectedTags}
emptyPlaceholder={
'Create a new tag using the tag button in the bottom right corner.'
}
/>
</SideMenuSection>
);
}
return null;
}}
/>
<FAB

View File

@@ -16,22 +16,24 @@ import {
TextContainer,
Touchable,
} from './SideMenuCell.styled';
import { SideMenuOption } from './SideMenuSection';
import { SideMenuOptionIconDescriptionType, SideMenuOption } from './SideMenuSection';
const renderIcon = (desc: SideMenuOption['iconDesc'], color: string) => {
if (!desc) {
return null;
}
if (desc.type === 'icon' && desc.name) {
if (desc.type === SideMenuOptionIconDescriptionType.Icon && desc.name) {
return (
<IconGraphicContainer>
<Icon name={desc.name} size={desc.size || 20} color={color} />
</IconGraphicContainer>
);
} else if (desc.type === 'ascii') {
}
if (desc.type === SideMenuOptionIconDescriptionType.Ascii) {
return <IconAscii>{desc.value}</IconAscii>;
} else if (desc.type === 'circle') {
}
if (desc.type === SideMenuOptionIconDescriptionType.Circle) {
return (
<IconCircleContainer>
<Circle
@@ -40,9 +42,11 @@ const renderIcon = (desc: SideMenuOption['iconDesc'], color: string) => {
/>
</IconCircleContainer>
);
} else {
return <RegularText>*</RegularText>;
}
if (desc.type === SideMenuOptionIconDescriptionType.CustomComponent) {
return desc.value;
}
return <RegularText>*</RegularText>;
};
export const SideMenuCell: React.FC<SideMenuOption> = props => {

View File

@@ -1,17 +1,24 @@
import React, { useMemo, useState } from 'react';
import React, { ReactElement, useMemo, useState } from 'react';
import { SideMenuCell } from './SideMenuCell';
import { CollapsedLabel, Header, Root, Title } from './SideMenuSection.styled';
export enum SideMenuOptionIconDescriptionType {
Icon = 'icon',
Ascii = 'ascii',
Circle = 'circle',
CustomComponent = 'custom-component',
}
export type SideMenuOption = {
text: string;
subtext?: string;
textClass?: 'info' | 'danger' | 'warning';
key?: string;
iconDesc?: {
type: string;
type: SideMenuOptionIconDescriptionType;
side?: 'left' | 'right';
name?: string;
value?: string;
value?: string | ReactElement;
backgroundColor?: string;
borderColor?: string;
size?: number;

View File

@@ -20,6 +20,7 @@ import React, {
import { FlatList, ListRenderItem } from 'react-native';
import { SideMenuCell } from './SideMenuCell';
import { EmptyPlaceholder } from './TagSelectionList.styled';
import {SideMenuOptionIconDescriptionType} from "@Screens/SideMenu/SideMenuSection";
type Props = {
contentType: ContentType.Tag | ContentType.SmartTag;
@@ -158,7 +159,7 @@ export const TagSelectionList = React.memo(
text={title}
iconDesc={{
side: 'left',
type: 'ascii',
type: SideMenuOptionIconDescriptionType.Ascii,
value: '#',
}}
key={item.uuid}

View File

@@ -2526,39 +2526,47 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@standardnotes/auth@^3.16.1":
version "3.16.1"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.16.1.tgz#c73b3c70dfde1905998a6a27d91241ff5c7926dc"
integrity sha512-MSYfb80AVeERrZPiN15XG9e/ECv6UrVJ0R5h9jVIEJzxPKfmpVJVr2wH2ts3B6etjD3VYtUS7UNBXC/fYI4kuw==
"@standardnotes/auth@^3.16.4":
version "3.16.4"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.16.4.tgz#6293bd67cdc4055229f1d520b6f44b39c6053a7a"
integrity sha512-2tHsDnwQgGD3pOzKuSjo4yj8hLjATb70jzFnEWoEpyCdHTuGys9qSBElfi672hU4vg+/nXaHpdVUuD5DPzLaXg==
dependencies:
"@standardnotes/common" "^1.11.0"
"@standardnotes/common" "^1.14.0"
jsonwebtoken "^8.5.1"
"@standardnotes/common@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.11.0.tgz#5ed4be817a1f448e6eeb700d141dbfd40193aabe"
integrity sha512-8TKx7bCwIazhGD3wkWTV4rmwbERsyisPbVDn6UIm1lNktWjKDF5OL1D8omalpR5wdM5qmXX5njI1zll2cxlW7A==
"@standardnotes/common@^1.14.0":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.14.0.tgz#c3b8e06fb8120524da8d135f2dc594998e5a5c3a"
integrity sha512-QA8JhWty7y/N62jdLyWuXzsVvDDeFwcQAH/DB/g5Mmaqlz9VlKcsbRmVl4wHLG3ur6n5Qj68aOhzCQd0p7f/WA==
"@standardnotes/components@^1.7.3":
version "1.7.3"
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.3.tgz#544521a5f0c5955c384fa7878003f4fdfcda860c"
integrity sha512-2AcxDdSFMK0G7h6R+EdMR2muzmFyqm0Qtl9znjQ3Bec4wGo9gpLpxlvotbihh230H+PTq4DJybX586bEs6HUKg==
"@standardnotes/components@^1.7.5":
version "1.7.5"
resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.7.5.tgz#2f199350779a8f99e9e536223d318b822bdf369f"
integrity sha512-7nyrrcgPAkCf6pjbAIO8qOM+22KQU0jMCIDX3b4GAS1jXM7DJDy5Frnk3oMHd9NeFhnbf0TQH4Nz4uEeid6/HA==
"@standardnotes/domain-events@^2.23.7":
version "2.23.7"
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.23.7.tgz#ea8f4d38929249234125475c0a59deb6eb384b72"
integrity sha512-sRht/c0KXv0NwoIalWs+2ugQz1C0JfCRIs/5FAeRCWCbn/OgHytCqPuD73/hGgUU/J323tYwB5+/7gYan4EWJg==
"@standardnotes/domain-events@^2.23.14":
version "2.23.14"
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.23.14.tgz#733e340c6d42935c90858380d7721150d1804574"
integrity sha512-DRPD0lGdVn/tbVyl97QGyyAcdUZd4qsETICCO882mG33HBN8Yc7st176U+izu3T5W3dlnTqE+MJUASj3UxVCvw==
dependencies:
"@standardnotes/auth" "^3.16.1"
"@standardnotes/features" "^1.32.6"
"@standardnotes/auth" "^3.16.4"
"@standardnotes/features" "^1.32.11"
"@standardnotes/features@^1.32.6":
version "1.32.6"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.32.6.tgz#63856e4f4af1a56aa6533a5e214a43c4b2d8b510"
integrity sha512-GQJfDzYAAlRSccPWgAAT95hmUqmo9Z/m52uhOvgOwdQgwxOkHnYh60cdM/lnYrTBk0wv9MYPTFd+3vbMXy9/YQ==
"@standardnotes/features@^1.32.11":
version "1.32.11"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.32.11.tgz#43417e541bdf0ce8a10dfd68e8073fc1338f1888"
integrity sha512-ZheQibMz4t2A6LKWcTDDGc5760AnPLFnHFwsSp0O8YydI3yz+qjm3pFF8XNeAEwgSvOX1W1nlX3E/X5tCp5LgQ==
dependencies:
"@standardnotes/auth" "^3.16.1"
"@standardnotes/common" "^1.11.0"
"@standardnotes/auth" "^3.16.4"
"@standardnotes/common" "^1.14.0"
"@standardnotes/services@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.1.0.tgz#42f743f58fb4ab85627368ae6bcdf513079ef708"
integrity sha512-r4lqUO30iHmjWodUTv+2D8xeCgpYFvJrNzR/pBIlZsAKMSjskxPyIUvBdQvHWs0o4vjf7ZedhpEwi68XwUqO3w==
dependencies:
"@standardnotes/common" "^1.14.0"
"@standardnotes/utils" "^1.1.1"
"@standardnotes/settings@^1.11.3":
version "1.11.3"
@@ -2570,28 +2578,39 @@
resolved "https://registry.yarnpkg.com/@standardnotes/sncrypto-common/-/sncrypto-common-1.7.0.tgz#6ad96afeaa031c26e45cbaf527bb511b803f998d"
integrity sha512-Dke13reJMLQFXa7y9EqZYEeZG5Ouy+32qWEsQISLjLRPrTuNwyNXee2mdPh6c9uNZxOQwrdHxVGfqzJ2iy3RpQ==
"@standardnotes/snjs@2.61.0":
version "2.61.0"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.61.0.tgz#0628fdb17c97f810fda0a989557184e394e4e555"
integrity sha512-2j/WfHPMRxwD6K857HHCWZbmK2zL4nZc7a+jY0rgfZd4yzU/wFwy3iUxV//a/IDQ9XUKAFQnyVjGnMS/oaWzsA==
"@standardnotes/snjs@2.63.1":
version "2.63.1"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.63.1.tgz#0759be39e77304fcca11ea902aa9be7bb737e756"
integrity sha512-d32CE7/yS+qEGlOfHTDc0NPzCFXPaK2zxlCi/j68R9lT/3LuV/uc1o9eNK9a5Fgcbtbk55vbW0rUYjQQVwUF8A==
dependencies:
"@standardnotes/auth" "^3.16.1"
"@standardnotes/common" "^1.11.0"
"@standardnotes/domain-events" "^2.23.7"
"@standardnotes/features" "^1.32.6"
"@standardnotes/auth" "^3.16.4"
"@standardnotes/common" "^1.14.0"
"@standardnotes/domain-events" "^2.23.14"
"@standardnotes/features" "^1.32.11"
"@standardnotes/services" "^1.1.0"
"@standardnotes/settings" "^1.11.3"
"@standardnotes/sncrypto-common" "^1.7.0"
"@standardnotes/utils" "^1.1.1"
"@standardnotes/stylekit@5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@standardnotes/stylekit/-/stylekit-5.5.0.tgz#24d2aef1529bbb09406f95acdd55ba138cf14958"
integrity sha512-feAdjOu0tBfpRJh5pH+urji40Xu72tJ2VRWxzkxXX1SYHMbgXnjBNs5R4IWko2kMIQwNGuBOvEg87S5w2/fdhw==
"@standardnotes/stylekit@5.9.0":
version "5.9.0"
resolved "https://registry.yarnpkg.com/@standardnotes/stylekit/-/stylekit-5.9.0.tgz#16d62623335091062238c850a930dedaa5ad6f1d"
integrity sha512-lIlEKwxKkQT+AttSmN40zvzWzfqpfI4VPTscZvRnWRqMQjYDEnSbcA1h0UDcT5tvnrna4RtsOiUL9o0KDx56sg==
dependencies:
"@reach/listbox" "^0.15.0"
"@reach/menu-button" "^0.15.1"
"@svgr/webpack" "^6.2.1"
prop-types "^15.7.2"
"@standardnotes/utils@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@standardnotes/utils/-/utils-1.1.1.tgz#a936edd328b4e10b43b11ffc8b1626a499fa6659"
integrity sha512-LaB1Y4arvwuABT0fybJ9At6pPEAwsDooaldYPuvqyfQAWdeqRCBMHxRDCX6yunrrwBwk7UoTie9MRw6DF1igwg==
dependencies:
"@standardnotes/common" "^1.14.0"
dompurify "^2.3.4"
lodash "^4.17.19"
"@svgr/babel-plugin-add-jsx-attribute@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18"
@@ -4459,6 +4478,11 @@ domhandler@^4.2.0, domhandler@^4.3.0:
dependencies:
domelementtype "^2.2.0"
dompurify@^2.3.4:
version "2.3.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875"
integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==
domutils@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"