1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-03-23 21:09:30 +02:00

Desktop, Mobile: Added support for notebook icons

This commit is contained in:
Laurent Cozic 2021-11-15 17:19:51 +00:00
parent 79d97f2ba7
commit e97bb78ce4
25 changed files with 715 additions and 62 deletions

View File

@ -216,6 +216,12 @@ packages/app-desktop/gui/DialogTitle.js.map
packages/app-desktop/gui/DropboxLoginScreen.d.ts
packages/app-desktop/gui/DropboxLoginScreen.js
packages/app-desktop/gui/DropboxLoginScreen.js.map
packages/app-desktop/gui/EditFolderDialog/Dialog.d.ts
packages/app-desktop/gui/EditFolderDialog/Dialog.js
packages/app-desktop/gui/EditFolderDialog/Dialog.js.map
packages/app-desktop/gui/EditFolderDialog/IconSelector.d.ts
packages/app-desktop/gui/EditFolderDialog/IconSelector.js
packages/app-desktop/gui/EditFolderDialog/IconSelector.js.map
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.d.ts
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
@ -282,6 +288,9 @@ packages/app-desktop/gui/MainScreen/commands/newTodo.js.map
packages/app-desktop/gui/MainScreen/commands/openFolder.d.ts
packages/app-desktop/gui/MainScreen/commands/openFolder.js
packages/app-desktop/gui/MainScreen/commands/openFolder.js.map
packages/app-desktop/gui/MainScreen/commands/openFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js.map
packages/app-desktop/gui/MainScreen/commands/openNote.d.ts
packages/app-desktop/gui/MainScreen/commands/openNote.js
packages/app-desktop/gui/MainScreen/commands/openNote.js.map
@ -699,6 +708,9 @@ packages/app-desktop/gui/utils/SyncScrollMap.js.map
packages/app-desktop/gui/utils/convertToScreenCoordinates.d.ts
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map
packages/app-desktop/gui/utils/loadScript.d.ts
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gui/utils/loadScript.js.map
packages/app-desktop/plugins/GotoAnything.d.ts
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/plugins/GotoAnything.js.map

12
.gitignore vendored
View File

@ -199,6 +199,12 @@ packages/app-desktop/gui/DialogTitle.js.map
packages/app-desktop/gui/DropboxLoginScreen.d.ts
packages/app-desktop/gui/DropboxLoginScreen.js
packages/app-desktop/gui/DropboxLoginScreen.js.map
packages/app-desktop/gui/EditFolderDialog/Dialog.d.ts
packages/app-desktop/gui/EditFolderDialog/Dialog.js
packages/app-desktop/gui/EditFolderDialog/Dialog.js.map
packages/app-desktop/gui/EditFolderDialog/IconSelector.d.ts
packages/app-desktop/gui/EditFolderDialog/IconSelector.js
packages/app-desktop/gui/EditFolderDialog/IconSelector.js.map
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.d.ts
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
@ -265,6 +271,9 @@ packages/app-desktop/gui/MainScreen/commands/newTodo.js.map
packages/app-desktop/gui/MainScreen/commands/openFolder.d.ts
packages/app-desktop/gui/MainScreen/commands/openFolder.js
packages/app-desktop/gui/MainScreen/commands/openFolder.js.map
packages/app-desktop/gui/MainScreen/commands/openFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js.map
packages/app-desktop/gui/MainScreen/commands/openNote.d.ts
packages/app-desktop/gui/MainScreen/commands/openNote.js
packages/app-desktop/gui/MainScreen/commands/openNote.js.map
@ -682,6 +691,9 @@ packages/app-desktop/gui/utils/SyncScrollMap.js.map
packages/app-desktop/gui/utils/convertToScreenCoordinates.d.ts
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map
packages/app-desktop/gui/utils/loadScript.d.ts
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gui/utils/loadScript.js.map
packages/app-desktop/plugins/GotoAnything.d.ts
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/plugins/GotoAnything.js.map

View File

@ -18,6 +18,7 @@ export enum AppStateDialogName {
export interface AppStateDialog {
name: AppStateDialogName;
props: Record<string, any>;
}
export interface AppState extends State {
@ -287,6 +288,7 @@ export default function(state: AppState, action: any) {
newDialogs.push({
name: action.name,
props: action.props || {},
});
newState.dialogs = newDialogs;

View File

@ -564,7 +564,8 @@ class Application extends BaseApplication {
// setTimeout(() => {
// this.dispatch({
// type: 'DIALOG_OPEN',
// name: 'masterPassword',
// name: 'editFolder',
// props: { folderId: '3d90f7da26b947dc9c8c6c65e86cd231' },
// });
// }, 2000);

View File

@ -27,6 +27,9 @@ interface Props {
disabled?: boolean;
style?: any;
size?: ButtonSize;
isSquare?: boolean;
iconOnly?: boolean;
fontSize?: number;
}
const StyledTitle = styled.span`
@ -41,6 +44,10 @@ export const buttonSizePx = (props: Props) => {
throw new Error(`Unknown size: ${props.size}`);
};
const isSquare = (props: Props) => {
return props.iconOnly || props.isSquare;
};
const StyledButtonBase = styled.button`
display: flex;
align-items: center;
@ -48,19 +55,19 @@ const StyledButtonBase = styled.button`
height: ${(props: Props) => buttonSizePx(props)}px;
min-height: ${(props: Props) => buttonSizePx(props)}px;
max-height: ${(props: Props) => buttonSizePx(props)}px;
width: ${(props: any) => props.iconOnly ? `${buttonSizePx}px` : 'auto'};
${(props: any) => props.iconOnly ? `min-width: ${buttonSizePx}px;` : ''}
${(props: any) => !props.iconOnly ? 'min-width: 100px;' : ''}
${(props: any) => props.iconOnly ? `max-width: ${buttonSizePx}px;` : ''}
width: ${(props: Props) => isSquare(props) ? `${buttonSizePx(props)}px` : 'auto'};
${(props: Props) => isSquare(props) ? `min-width: ${buttonSizePx(props)}px;` : ''}
${(props: Props) => !isSquare(props) ? 'min-width: 100px;' : ''}
${(props: Props) => isSquare(props) ? `max-width: ${buttonSizePx(props)}px;` : ''}
box-sizing: border-box;
border-radius: 3px;
border-style: solid;
border-width: 1px;
/*font-size: ${(props: any) => props.theme.fontSize}px; */
padding: 0 ${(props: any) => props.iconOnly ? 4 : 14}px;
padding: 0 ${(props: Props) => isSquare(props) ? 4 : 14}px;
justify-content: center;
opacity: ${(props: any) => props.disabled ? 0.5 : 1};
opacity: ${(props: Props) => props.disabled ? 0.5 : 1};
user-select: none;
${(props: Props) => props.fontSize ? `font-size: ${props.fontSize}px;` : ''}
`;
const StyledIcon = styled(styled.span(space))`
@ -200,7 +207,7 @@ function buttonClass(level: ButtonLevel) {
return StyledButtonSecondary;
}
function Button(props: Props) {
const Button = React.forwardRef((props: Props, ref: any) => {
const iconOnly = props.iconName && !props.title;
const StyledButton = buttonClass(props.level);
@ -221,11 +228,11 @@ function Button(props: Props) {
}
return (
<StyledButton size={props.size} style={props.style} disabled={props.disabled} title={props.tooltip} className={props.className} iconOnly={iconOnly} onClick={onClick}>
<StyledButton ref={ref} fontSize={props.fontSize} isSquare={props.isSquare} size={props.size} style={props.style} disabled={props.disabled} title={props.tooltip} className={props.className} iconOnly={iconOnly} onClick={onClick}>
{renderIcon()}
{renderTitle()}
</StyledButton>
);
}
});
export default styled(Button)`${space}`;

View File

@ -0,0 +1,115 @@
import * as React from 'react';
import { useCallback, useState } from 'react';
import { _ } from '@joplin/lib/locale';
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
import Dialog from '../Dialog';
import DialogTitle from '../DialogTitle';
import StyledInput from '../style/StyledInput';
import { IconSelector, ChangeEvent } from './IconSelector';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import Folder from '@joplin/lib/models/Folder';
import { FolderIcon } from '@joplin/lib/services/database/types';
import Button from '../Button/Button';
interface Props {
themeId: number;
dispatch: Function;
folderId: string;
}
export default function(props: Props) {
const [folderTitle, setFolderTitle] = useState('');
const [folderIcon, setFolderIcon] = useState<FolderIcon>();
useAsyncEffect(async (event: AsyncEffectEvent) => {
const folder = await Folder.load(props.folderId);
if (event.cancelled) return;
setFolderTitle(folder.title);
setFolderIcon(Folder.unserializeIcon(folder.icon));
}, [props.folderId]);
const onClose = useCallback(() => {
props.dispatch({
type: 'DIALOG_CLOSE',
name: 'editFolder',
});
}, [props.dispatch]);
const onButtonRowClick = useCallback(async (event: ClickEvent) => {
if (event.buttonName === 'cancel') {
onClose();
return;
}
if (event.buttonName === 'ok') {
await Folder.save({
id: props.folderId,
title: folderTitle,
icon: Folder.serializeIcon(folderIcon),
});
onClose();
return;
}
}, [onClose, folderTitle, folderIcon, props.folderId]);
const onFolderTitleChange = useCallback((event: any) => {
setFolderTitle(event.target.value);
}, []);
const onFolderIconChange = useCallback((event: ChangeEvent) => {
setFolderIcon(event.value);
}, []);
const onClearClick = useCallback(() => {
setFolderIcon(null);
}, []);
function renderForm() {
return (
<div>
<div className="form">
<div className="form-input-group">
<label>{_('Title')}</label>
<StyledInput type="text" value={folderTitle} onChange={onFolderTitleChange}/>
</div>
<div className="form-input-group">
<label>{_('Icon')}</label>
<div className="icon-selector-row">
<IconSelector
icon={folderIcon}
onChange={onFolderIconChange}
/>
<Button ml={1} title={_('Clear')} onClick={onClearClick}/>
</div>
</div>
</div>
</div>
);
}
function renderContent() {
return (
<div className="dialog-content">
{renderForm()}
</div>
);
}
function renderDialogWrapper() {
return (
<div className="dialog-root">
<DialogTitle title={_('Edit notebook')}/>
{renderContent()}
<DialogButtonRow
themeId={props.themeId}
onClick={onButtonRowClick}
/>
</div>
);
}
return (
<Dialog onClose={onClose} className="master-password-dialog" renderContent={renderDialogWrapper}/>
);
}

View File

@ -0,0 +1,92 @@
import { EmojiButton } from '@joeattardi/emoji-button';
import { useEffect, useState, useCallback, useRef } from 'react';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import { loadScript } from '../utils/loadScript';
import Button from '../Button/Button';
import { FolderIcon } from '@joplin/lib/services/database/types';
export interface ChangeEvent {
value: FolderIcon;
}
type ChangeHandler = (event: ChangeEvent)=> void;
interface Props {
onChange: ChangeHandler;
icon: FolderIcon | null;
}
export const IconSelector = (props: Props) => {
const [emojiButtonClassReady, setEmojiButtonClassReady] = useState<boolean>(false);
const [picker, setPicker] = useState<EmojiButton>();
const buttonRef = useRef(null);
useAsyncEffect(async (event: AsyncEffectEvent) => {
const loadScripts = async () => {
// The emoji-button lib is annoying to load as it only comes as an
// ES module. So we first need to load the lib, then load a loader
// script, which will copy the class to the window object.
await loadScript({
id: 'emoji-button-lib',
src: 'node_modules/@joeattardi/emoji-button/dist/index.js',
attrs: {
type: 'module',
},
});
if (event.cancelled) return;
await loadScript({
id: 'emoji-button-lib-loader',
src: 'gui/EditFolderDialog/loadEmojiLib.js',
attrs: {
type: 'module',
},
});
if (event.cancelled) return;
setEmojiButtonClassReady(true);
};
void loadScripts();
}, []);
useEffect(() => {
if (!emojiButtonClassReady) return () => {};
const p: EmojiButton = new (window as any).EmojiButton({
zIndex: 10000,
});
const onEmoji = (selection: FolderIcon) => {
props.onChange({ value: selection });
};
p.on('emoji', onEmoji);
setPicker(p);
return () => {
p.off('emoji', onEmoji);
};
}, [emojiButtonClassReady, props.onChange]);
const onClick = useCallback(() => {
picker.togglePicker(buttonRef.current);
}, [picker]);
const buttonText = props.icon ? props.icon.emoji : '...';
return (
<Button
disabled={!picker}
ref={buttonRef}
onClick={onClick}
title={buttonText}
isSquare={true}
fontSize={20}
/>
);
};

View File

@ -0,0 +1,2 @@
import { EmojiButton } from '../../node_modules/@joeattardi/emoji-button/dist/index.js';
window.EmojiButton = EmojiButton;

View File

@ -0,0 +1,4 @@
.icon-selector-row {
display: flex;
flex-direction: row;
}

View File

@ -11,6 +11,7 @@ import * as newNote from './newNote';
import * as newSubFolder from './newSubFolder';
import * as newTodo from './newTodo';
import * as openFolder from './openFolder';
import * as openFolderDialog from './openFolderDialog';
import * as openNote from './openNote';
import * as openTag from './openTag';
import * as print from './print';
@ -47,6 +48,7 @@ const index:any[] = [
newSubFolder,
newTodo,
openFolder,
openFolderDialog,
openNote,
openTag,
print,

View File

@ -0,0 +1,22 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
export const declaration: CommandDeclaration = {
name: 'openFolderDialog',
label: () => _('Edit'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, folderId: string) => {
context.dispatch({
type: 'DIALOG_OPEN',
name: 'editFolder',
isOpen: true,
props: {
folderId,
},
});
},
};
};

View File

@ -22,6 +22,7 @@ import { plainTextToHtml } from '@joplin/lib/htmlUtils';
import openEditDialog from './utils/openEditDialog';
import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
import { themeStyle } from '@joplin/lib/theme';
import { loadScript } from '../../../utils/loadScript';
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
@ -286,32 +287,32 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
// module would not load these extra files.
// -----------------------------------------------------------------------------------------
const loadScript = async (script: any) => {
return new Promise((resolve) => {
let element: any = document.createElement('script');
if (script.src.indexOf('.css') >= 0) {
element = document.createElement('link');
element.rel = 'stylesheet';
element.href = script.src;
} else {
element.src = script.src;
// const loadScript = async (script: any) => {
// return new Promise((resolve) => {
// let element: any = document.createElement('script');
// if (script.src.indexOf('.css') >= 0) {
// element = document.createElement('link');
// element.rel = 'stylesheet';
// element.href = script.src;
// } else {
// element.src = script.src;
if (script.attrs) {
for (const attr in script.attrs) {
element[attr] = script.attrs[attr];
}
}
}
// if (script.attrs) {
// for (const attr in script.attrs) {
// element[attr] = script.attrs[attr];
// }
// }
// }
element.id = script.id;
// element.id = script.id;
element.onload = () => {
resolve(null);
};
// element.onload = () => {
// resolve(null);
// };
document.getElementsByTagName('head')[0].appendChild(element);
});
};
// document.getElementsByTagName('head')[0].appendChild(element);
// });
// };
useEffect(() => {
let cancelled = false;

View File

@ -21,6 +21,7 @@ import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './Di
import Dialog from './Dialog';
import SyncWizardDialog from './SyncWizard/Dialog';
import MasterPasswordDialog from './MasterPasswordDialog/Dialog';
import EditFolderDialog from './EditFolderDialog/Dialog';
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
const { ImportScreen } = require('./ImportScreen.min.js');
const { ResourceScreen } = require('./ResourceScreen.js');
@ -36,7 +37,7 @@ interface Props {
size: Size;
zoomFactor: number;
needApiAuth: boolean;
dialogs: AppStateDialog;
dialogs: AppStateDialog[];
}
interface ModalDialogProps {
@ -53,19 +54,25 @@ interface RegisteredDialogProps {
}
interface RegisteredDialog {
render: (props: RegisteredDialogProps)=> any;
render: (props: RegisteredDialogProps, customProps: any)=> any;
}
const registeredDialogs: Record<string, RegisteredDialog> = {
syncWizard: {
render: (props: RegisteredDialogProps) => {
return <SyncWizardDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId}/>;
render: (props: RegisteredDialogProps, customProps: any) => {
return <SyncWizardDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
},
},
masterPassword: {
render: (props: RegisteredDialogProps) => {
return <MasterPasswordDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId}/>;
render: (props: RegisteredDialogProps, customProps: any) => {
return <MasterPasswordDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
},
},
editFolder: {
render: (props: RegisteredDialogProps, customProps: any) => {
return <EditFolderDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
},
},
};
@ -180,17 +187,19 @@ class RootComponent extends React.Component<Props, any> {
}
private renderDialogs() {
if (!this.props.dialogs.length) return null;
const props: Props = this.props;
if (!props.dialogs.length) return null;
const output: any[] = [];
for (const dialog of this.props.dialogs) {
for (const dialog of props.dialogs) {
const md = registeredDialogs[dialog.name];
if (!md) throw new Error(`Unknown dialog: ${dialog.name}`);
output.push(md.render({
key: dialog.name,
themeId: this.props.themeId,
dispatch: this.props.dispatch,
}));
themeId: props.themeId,
dispatch: props.dispatch,
}, dialog.props));
}
return output;
}

View File

@ -78,12 +78,14 @@ function ExpandLink(props: any) {
}
function FolderItem(props: any) {
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, folderIcon, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
const noteCountComp = noteCount ? <StyledNoteCount className="note-count-label">{noteCount}</StyledNoteCount> : null;
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
const icon = folderIcon ? <span style={{ fontSize: 20, marginRight: 5 }}>{folderIcon.emoji}</span> : null;
return (
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth} ${selected ? 'selected' : ''}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
@ -103,7 +105,7 @@ function FolderItem(props: any) {
}}
onDoubleClick={onFolderToggleClick_}
>
<span className="title">{folderTitle}</span>
{icon}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
{shareIcon} {noteCountComp}
</StyledListItemAnchor>
</StyledListItem>
@ -292,7 +294,7 @@ class SidebarComponent extends React.Component<Props, State> {
);
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('renameFolder', itemId)));
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('openFolderDialog', itemId)));
menu.append(new MenuItem({ type: 'separator' }));
@ -464,6 +466,7 @@ class SidebarComponent extends React.Component<Props, State> {
key={folder.id}
folderId={folder.id}
folderTitle={Folder.displayTitle(folder)}
folderIcon={Folder.unserializeIcon(folder.icon)}
themeId={this.props.themeId}
depth={depth}
selected={selected}

View File

@ -46,7 +46,7 @@ export const StyledHeaderLabel = styled.span`
export const StyledListItem = styled.div`
box-sizing: border-box;
height: 25px;
height: 30px;
display: flex;
flex-direction: row;
align-items: center;

View File

@ -0,0 +1,48 @@
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('loadScript');
export interface Script {
id: string;
src: string;
attrs?: Record<string, any>;
}
export const loadScript = async (script: Script) => {
return new Promise((resolve) => {
let element: any = document.getElementById(script.id);
if (element) {
if (element.href === script.src || element.src === script.src) {
logger.info(`Trying to load a script that has already been loaded: ${JSON.stringify(script)} - skipping it`);
resolve(null);
} else {
logger.info(`Source of script has changed - reloading it: ${JSON.stringify(script)}`);
element.parentNode.removeChild(element);
element = null;
}
}
if (script.src.indexOf('.css') >= 0) {
element = document.createElement('link');
element.rel = 'stylesheet';
element.href = script.src;
} else {
element = document.createElement('script');
element.src = script.src;
if (script.attrs) {
for (const attr in script.attrs) {
element[attr] = script.attrs[attr];
}
}
}
element.id = script.id;
element.onload = () => {
resolve(null);
};
document.getElementsByTagName('head')[0].appendChild(element);
});
};

View File

@ -11,6 +11,7 @@
"dependencies": {
"@electron/remote": "^2.0.1",
"@fortawesome/fontawesome-free": "^5.13.0",
"@joeattardi/emoji-button": "^4.6.0",
"async-mutex": "^0.1.3",
"codemirror": "^5.56.0",
"color": "^3.1.2",
@ -1530,6 +1531,15 @@
"version": "0.8.2",
"integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "5.13.0",
"integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg==",
@ -1537,6 +1547,42 @@
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "1.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@gar/promisify": {
"version": "1.1.2",
"integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==",
@ -2250,6 +2296,23 @@
"node": ">=8"
}
},
"node_modules/@joeattardi/emoji-button": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@joeattardi/emoji-button/-/emoji-button-4.6.0.tgz",
"integrity": "sha512-KwOE1j+YxX47JmT0pXNCa+9Ai4Wf2fmABtvuxy6JBJ5QV0HdoThRKjL6CxAreVwwLbNQ/PDoR36xpc5QJjLXPA==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-regular-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@popperjs/core": "^2.4.0",
"@types/twemoji": "^12.1.1",
"focus-trap": "^5.1.0",
"fuzzysort": "^1.1.4",
"tiny-emitter": "^2.1.0",
"tslib": "^2.0.0",
"twemoji": "^13.0.0"
}
},
"node_modules/@malept/cross-spawn-promise": {
"version": "1.1.1",
"integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==",
@ -2439,6 +2502,15 @@
"node": ">=10"
}
},
"node_modules/@popperjs/core": {
"version": "2.10.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.2.tgz",
"integrity": "sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@sindresorhus/is": {
"version": "0.14.0",
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
@ -2896,6 +2968,11 @@
"@types/react-test-renderer": "*"
}
},
"node_modules/@types/twemoji": {
"version": "12.1.2",
"resolved": "https://registry.npmjs.org/@types/twemoji/-/twemoji-12.1.2.tgz",
"integrity": "sha512-3eMyKenMi0R1CeKzBYtk/Z2JIHsTMQrIrTah0q54o45pHTpWVNofU2oHx0jS8tqsDRhis2TbB6238WP9oh2l2w=="
},
"node_modules/@types/verror": {
"version": "1.10.5",
"integrity": "sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==",
@ -7916,6 +7993,15 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/focus-trap": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.1.0.tgz",
"integrity": "sha512-CkB/nrO55069QAUjWFBpX6oc+9V90Qhgpe6fBWApzruMq5gnlh90Oo7iSSDK7pKiV5ugG6OY2AXM5mxcmL3lwQ==",
"dependencies": {
"tabbable": "^4.0.0",
"xtend": "^4.0.1"
}
},
"node_modules/for-in": {
"version": "1.0.2",
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
@ -8060,6 +8146,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/fuzzysort": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz",
"integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ=="
},
"node_modules/gauge": {
"version": "2.7.4",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
@ -16602,6 +16693,11 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
"node_modules/tabbable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
"integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
},
"node_modules/taboverride": {
"version": "4.0.3",
"integrity": "sha1-M5JAEqLzr17mCcXzDhvSanX75qk="
@ -16829,6 +16925,11 @@
"node": ">=0.10.0"
}
},
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"node_modules/tinymce": {
"version": "5.8.0",
"integrity": "sha512-1bOI3k+1D76rVjAJC3XkHezXJVghurnKBDREF1STHBLTQUY17XTbaDNJUxNgJqJHa2xg1udd5I1bzdfSd77DGw=="
@ -17008,6 +17109,11 @@
"utf8-byte-length": "^1.0.1"
}
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"node_modules/tunnel": {
"version": "0.0.6",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
@ -17032,6 +17138,54 @@
"dev": true,
"optional": true
},
"node_modules/twemoji": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.1.0.tgz",
"integrity": "sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==",
"dependencies": {
"fs-extra": "^8.0.1",
"jsonfile": "^5.0.0",
"twemoji-parser": "13.1.0",
"universalify": "^0.1.2"
}
},
"node_modules/twemoji-parser": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.1.0.tgz",
"integrity": "sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg=="
},
"node_modules/twemoji/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/twemoji/node_modules/fs-extra/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/twemoji/node_modules/jsonfile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
"integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==",
"dependencies": {
"universalify": "^0.1.2"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/type": {
"version": "1.2.0",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
@ -17174,8 +17328,12 @@
}
},
"node_modules/universalify": {
"version": "0.1.1",
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/unset-value": {
"version": "1.0.0",
@ -17847,7 +18005,6 @@
"node_modules/xtend": {
"version": "4.0.2",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true,
"engines": {
"node": ">=0.4"
}
@ -18995,10 +19152,39 @@
"version": "0.8.2",
"integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
},
"@fortawesome/fontawesome-common-types": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
},
"@fortawesome/fontawesome-free": {
"version": "5.13.0",
"integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "1.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
}
},
"@gar/promisify": {
"version": "1.1.2",
"integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==",
@ -19532,6 +19718,23 @@
}
}
},
"@joeattardi/emoji-button": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@joeattardi/emoji-button/-/emoji-button-4.6.0.tgz",
"integrity": "sha512-KwOE1j+YxX47JmT0pXNCa+9Ai4Wf2fmABtvuxy6JBJ5QV0HdoThRKjL6CxAreVwwLbNQ/PDoR36xpc5QJjLXPA==",
"requires": {
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-regular-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@popperjs/core": "^2.4.0",
"@types/twemoji": "^12.1.1",
"focus-trap": "^5.1.0",
"fuzzysort": "^1.1.4",
"tiny-emitter": "^2.1.0",
"tslib": "^2.0.0",
"twemoji": "^13.0.0"
}
},
"@malept/cross-spawn-promise": {
"version": "1.1.1",
"integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==",
@ -19664,6 +19867,11 @@
}
}
},
"@popperjs/core": {
"version": "2.10.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.2.tgz",
"integrity": "sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ=="
},
"@sindresorhus/is": {
"version": "0.14.0",
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
@ -20112,6 +20320,11 @@
"@types/react-test-renderer": "*"
}
},
"@types/twemoji": {
"version": "12.1.2",
"resolved": "https://registry.npmjs.org/@types/twemoji/-/twemoji-12.1.2.tgz",
"integrity": "sha512-3eMyKenMi0R1CeKzBYtk/Z2JIHsTMQrIrTah0q54o45pHTpWVNofU2oHx0jS8tqsDRhis2TbB6238WP9oh2l2w=="
},
"@types/verror": {
"version": "1.10.5",
"integrity": "sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==",
@ -24029,6 +24242,15 @@
}
}
},
"focus-trap": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.1.0.tgz",
"integrity": "sha512-CkB/nrO55069QAUjWFBpX6oc+9V90Qhgpe6fBWApzruMq5gnlh90Oo7iSSDK7pKiV5ugG6OY2AXM5mxcmL3lwQ==",
"requires": {
"tabbable": "^4.0.0",
"xtend": "^4.0.1"
}
},
"for-in": {
"version": "1.0.2",
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
@ -24141,6 +24363,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"fuzzysort": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz",
"integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ=="
},
"gauge": {
"version": "2.7.4",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
@ -30633,6 +30860,11 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
"tabbable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
"integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
},
"taboverride": {
"version": "4.0.3",
"integrity": "sha1-M5JAEqLzr17mCcXzDhvSanX75qk="
@ -30816,6 +31048,11 @@
"integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
"dev": true
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"tinymce": {
"version": "5.8.0",
"integrity": "sha512-1bOI3k+1D76rVjAJC3XkHezXJVghurnKBDREF1STHBLTQUY17XTbaDNJUxNgJqJHa2xg1udd5I1bzdfSd77DGw=="
@ -30951,6 +31188,11 @@
"utf8-byte-length": "^1.0.1"
}
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"tunnel": {
"version": "0.0.6",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
@ -30969,6 +31211,53 @@
"dev": true,
"optional": true
},
"twemoji": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.1.0.tgz",
"integrity": "sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==",
"requires": {
"fs-extra": "^8.0.1",
"jsonfile": "^5.0.0",
"twemoji-parser": "13.1.0",
"universalify": "^0.1.2"
},
"dependencies": {
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"dependencies": {
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
}
}
},
"jsonfile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
"integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==",
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^0.1.2"
}
}
}
},
"twemoji-parser": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.1.0.tgz",
"integrity": "sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg=="
},
"type": {
"version": "1.2.0",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
@ -31080,8 +31369,9 @@
}
},
"universalify": {
"version": "0.1.1",
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"unset-value": {
"version": "1.0.0",
@ -31599,8 +31889,7 @@
},
"xtend": {
"version": "4.0.2",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "4.0.3",

View File

@ -136,6 +136,7 @@
"dependencies": {
"@electron/remote": "^2.0.1",
"@fortawesome/fontawesome-free": "^5.13.0",
"@joeattardi/emoji-button": "^4.6.0",
"@joplin/lib": "~2.6",
"@joplin/renderer": "~2.6",
"async-mutex": "^0.1.3",

View File

@ -1,4 +1,5 @@
@use 'main.scss' as main;
@use 'gui/ConfigScreen/style.scss' as config-screen;
@use 'gui/EditFolderDialog/style.scss' as edit-folder-dialog;
@use 'gui/EncryptionConfigScreen/style.scss' as encryption-config-screen;
@use 'gui/PasswordInput/style.scss' as password-input;
@use 'gui/ConfigScreen/style.scss' as config-screen;
@use 'main.scss' as main;

View File

@ -385,7 +385,9 @@ class ScreenHeaderComponent extends React.PureComponent {
for (let i = 0; i < folders.length; i++) {
const f = folders[i];
pickerItems.push({ label: `${' '.repeat(indent)} ${Folder.displayTitle(f)}`, value: f.id });
const icon = Folder.unserializeIcon(f.icon);
const iconString = icon ? `${icon.emoji} ` : '';
pickerItems.push({ label: `${' '.repeat(indent)} ${iconString + Folder.displayTitle(f)}`, value: f.id });
pickerItems = addFolderChildren(f.children, pickerItems, indent + 1);
}

View File

@ -227,6 +227,9 @@ class NotesScreenComponent extends BaseScreenComponent {
);
}
const icon = Folder.unserializeIcon(parent.icon);
const iconString = icon ? `${icon.emoji} ` : '';
let buttonFolderId = this.props.selectedFolderId != Folder.conflictFolderId() ? this.props.selectedFolderId : null;
if (!buttonFolderId) buttonFolderId = this.props.activeFolderId;
@ -236,7 +239,7 @@ class NotesScreenComponent extends BaseScreenComponent {
return (
<View style={rootStyle}>
<ScreenHeader title={title} showBackButton={false} parentComponent={thisComp} sortButton_press={this.sortButton_press} folderPickerOptions={this.folderPickerOptions()} showSearchButton={true} showSideMenuButton={true} />
<ScreenHeader title={iconString + title} showBackButton={false} parentComponent={thisComp} sortButton_press={this.sortButton_press} folderPickerOptions={this.folderPickerOptions()} showSearchButton={true} showSideMenuButton={true} />
<NoteList style={this.styles().noteList} />
{actionButtonComp}
<DialogBox

View File

@ -252,6 +252,9 @@ class SideMenuContentComponent extends Component {
</TouchableOpacity>
);
const folderIcon = Folder.unserializeIcon(folder.icon);
const icon = folderIcon ? `${folderIcon.emoji} ` : '';
return (
<View key={folder.id} style={{ flex: 1, flexDirection: 'row' }}>
<TouchableOpacity
@ -265,7 +268,7 @@ class SideMenuContentComponent extends Component {
>
<View style={folderButtonStyle}>
<Text numberOfLines={1} style={this.styles().folderButtonText}>
{Folder.displayTitle(folder)}
{icon + Folder.displayTitle(folder)}
</Text>
</View>
</TouchableOpacity>

View File

@ -351,7 +351,7 @@ export default class JoplinDatabase extends Database {
// must be set in the synchronizer too.
// Note: v16 and v17 don't do anything. They were used to debug an issue.
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40];
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41];
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
@ -906,6 +906,10 @@ export default class JoplinDatabase extends Database {
queries.push('ALTER TABLE `resources` ADD COLUMN master_key_id TEXT NOT NULL DEFAULT ""');
}
if (targetVersion == 41) {
queries.push('ALTER TABLE `folders` ADD COLUMN icon TEXT NOT NULL DEFAULT ""');
}
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
queries.push(updateVersionQuery);

View File

@ -1,4 +1,4 @@
import { FolderEntity } from '../services/database/types';
import { FolderEntity, FolderIcon } from '../services/database/types';
import BaseModel, { DeleteOptions } from '../BaseModel';
import time from '../time';
import { _ } from '../locale';
@ -666,4 +666,13 @@ export default class Folder extends BaseItem {
return folder;
});
}
public static serializeIcon(icon: FolderIcon): string {
return icon ? JSON.stringify(icon) : '';
}
public static unserializeIcon(icon: string): FolderIcon {
return icon ? JSON.parse(icon) : null;
}
}

View File

@ -15,6 +15,14 @@ export interface BaseItemEntity {
created_time?: number;
}
export interface FolderIcon {
emoji: string;
name: string;
}
@ -60,6 +68,7 @@ export interface FolderEntity {
"is_shared"?: number
"share_id"?: string
"master_key_id"?: string
"icon"?: string
"type_"?: number
}
export interface ItemChangeEntity {