feat: note types
This commit is contained in:
@@ -72,7 +72,7 @@ export const ChangeEditorButton: FunctionComponent<Props> = observer(
|
||||
ref={buttonRef}
|
||||
className="sn-icon-button border-contrast"
|
||||
>
|
||||
<VisuallyHidden>Change editor</VisuallyHidden>
|
||||
<VisuallyHidden>Change note type</VisuallyHidden>
|
||||
<Icon type="dashboard" className="block" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
import { Fragment, FunctionComponent } from 'preact'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { EditorMenuItem, EditorMenuGroup } from '@/Components/NotesOptions/ChangeEditorOption'
|
||||
import { createEditorMenuGroups, PLAIN_EDITOR_NAME } from './createEditorMenuGroups'
|
||||
import { createEditorMenuGroups } from './createEditorMenuGroups'
|
||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||
|
||||
type ChangeEditorMenuProps = {
|
||||
application: WebApplication
|
||||
@@ -169,7 +170,7 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu className="pt-0.5 pb-1" a11yLabel="Change editor menu" isOpen={isVisible}>
|
||||
<Menu className="pt-0.5 pb-1" a11yLabel="Change note type menu" isOpen={isVisible}>
|
||||
{groups
|
||||
.filter((group) => group.items && group.items.length)
|
||||
.map((group, index) => {
|
||||
@@ -194,9 +195,7 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
<MenuItem
|
||||
type={MenuItemType.RadioButton}
|
||||
onClick={onClickEditorItem}
|
||||
className={
|
||||
'sn-dropdown-item py-2 text-input focus:bg-info-backdrop focus:shadow-none'
|
||||
}
|
||||
className={'sn-dropdown-item py-2 text-input focus:bg-info-backdrop focus:shadow-none'}
|
||||
onBlur={closeOnBlur}
|
||||
checked={isSelectedEditor(item)}
|
||||
>
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
NoteType,
|
||||
} from '@standardnotes/snjs'
|
||||
import { EditorMenuItem, EditorMenuGroup } from '@/Components/NotesOptions/ChangeEditorOption'
|
||||
|
||||
export const PLAIN_EDITOR_NAME = 'Plain Editor'
|
||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||
|
||||
type EditorGroup = NoteType | 'plain' | 'others'
|
||||
|
||||
@@ -50,10 +49,7 @@ export const createEditorMenuGroups = (application: WebApplication, editors: SNC
|
||||
}
|
||||
|
||||
GetFeatures()
|
||||
.filter(
|
||||
(feature) =>
|
||||
feature.content_type === ContentType.Component && feature.area === ComponentArea.Editor,
|
||||
)
|
||||
.filter((feature) => feature.content_type === ContentType.Component && feature.area === ComponentArea.Editor)
|
||||
.forEach((editorFeature) => {
|
||||
const notInstalled = !editors.find((editor) => editor.identifier === editorFeature.identifier)
|
||||
const isExperimental = application.features.isExperimentalFeature(editorFeature.identifier)
|
||||
@@ -69,8 +65,7 @@ export const createEditorMenuGroups = (application: WebApplication, editors: SNC
|
||||
const editorItem: EditorMenuItem = {
|
||||
name: editor.name,
|
||||
component: editor,
|
||||
isEntitled:
|
||||
application.features.getFeatureStatus(editor.identifier) === FeatureStatus.Entitled,
|
||||
isEntitled: application.features.getFeatureStatus(editor.identifier) === FeatureStatus.Entitled,
|
||||
}
|
||||
|
||||
editorItems[getEditorGroup(editor.package_info)].push(editorItem)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import {
|
||||
CollectionSort,
|
||||
CollectionSortProperty,
|
||||
sanitizeHtmlString,
|
||||
SNNote,
|
||||
} from '@standardnotes/snjs'
|
||||
import { CollectionSort, CollectionSortProperty, sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Icon } from '@/Components/Icon'
|
||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -55,10 +51,8 @@ export const NotesListItem: FunctionComponent<Props> = ({
|
||||
const flags = flagsForNote(note)
|
||||
const showModifiedDate = sortedBy === CollectionSort.UpdatedAt
|
||||
const editorForNote = application.componentManager.editorForNote(note)
|
||||
const editorName = editorForNote?.name ?? 'Plain editor'
|
||||
const [icon, tint] = application.iconsController.getIconAndTintForEditor(
|
||||
editorForNote?.identifier,
|
||||
)
|
||||
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
|
||||
const [icon, tint] = application.iconsController.getIconAndTintForEditor(editorForNote?.identifier)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -69,17 +63,11 @@ export const NotesListItem: FunctionComponent<Props> = ({
|
||||
>
|
||||
{!hideEditorIcon && (
|
||||
<div className="icon">
|
||||
<Icon
|
||||
ariaLabel={`Icon for ${editorName}`}
|
||||
type={icon}
|
||||
className={`color-accessory-tint-${tint}`}
|
||||
/>
|
||||
<Icon ariaLabel={`Icon for ${editorName}`} type={icon} className={`color-accessory-tint-${tint}`} />
|
||||
</div>
|
||||
)}
|
||||
<div className={`meta ${hideEditorIcon ? 'icon-hidden' : ''}`}>
|
||||
<div className="name-container">
|
||||
{note.title.length ? <div className="name">{note.title}</div> : null}
|
||||
</div>
|
||||
<div className="name-container">{note.title.length ? <div className="name">{note.title}</div> : null}</div>
|
||||
{!hidePreview && !note.hidePreview && !note.protected && (
|
||||
<div className="note-preview">
|
||||
{note.preview_html && (
|
||||
@@ -90,9 +78,7 @@ export const NotesListItem: FunctionComponent<Props> = ({
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{!note.preview_html && note.preview_plain && (
|
||||
<div className="plain-preview">{note.preview_plain}</div>
|
||||
)}
|
||||
{!note.preview_html && note.preview_plain && <div className="plain-preview">{note.preview_plain}</div>}
|
||||
{!note.preview_html && !note.preview_plain && note.text && (
|
||||
<div className="default-preview">{note.text}</div>
|
||||
)}
|
||||
@@ -128,11 +114,7 @@ export const NotesListItem: FunctionComponent<Props> = ({
|
||||
<div className="flag-icons">
|
||||
{note.locked && (
|
||||
<span title="Editing Disabled">
|
||||
<Icon
|
||||
ariaLabel="Editing Disabled"
|
||||
type="pencil-off"
|
||||
className="sn-icon--small color-info"
|
||||
/>
|
||||
<Icon ariaLabel="Editing Disabled" type="pencil-off" className="sn-icon--small color-info" />
|
||||
</span>
|
||||
)}
|
||||
{note.trashed && (
|
||||
@@ -142,11 +124,7 @@ export const NotesListItem: FunctionComponent<Props> = ({
|
||||
)}
|
||||
{note.archived && (
|
||||
<span title="Archived">
|
||||
<Icon
|
||||
ariaLabel="Archived"
|
||||
type="archive"
|
||||
className="sn-icon--mid color-accessory-tint-3"
|
||||
/>
|
||||
<Icon ariaLabel="Archived" type="archive" className="sn-icon--mid color-accessory-tint-3" />
|
||||
</span>
|
||||
)}
|
||||
{note.pinned && (
|
||||
|
||||
@@ -31,10 +31,7 @@ export type EditorMenuItem = {
|
||||
|
||||
export type EditorMenuGroup = AccordionMenuGroup<EditorMenuItem>
|
||||
|
||||
export const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({
|
||||
application,
|
||||
note,
|
||||
}) => {
|
||||
export const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ application, note }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>({
|
||||
@@ -90,7 +87,7 @@ export const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="dashboard" className="color-neutral mr-2" />
|
||||
Change editor
|
||||
Change note type
|
||||
</div>
|
||||
<Icon type="chevron-right" className="color-neutral" />
|
||||
</DisclosureButton>
|
||||
|
||||
@@ -32,6 +32,8 @@ const DeletePermanentlyButton = ({ closeOnBlur, onClick }: DeletePermanentlyButt
|
||||
)
|
||||
|
||||
const iconClass = 'color-neutral mr-2'
|
||||
const iconClassDanger = 'color-danger mr-2'
|
||||
const iconClassWarning = 'color-warning mr-2'
|
||||
|
||||
const getWordCount = (text: string) => {
|
||||
if (text.trim().length === 0) {
|
||||
@@ -88,15 +90,9 @@ const NoteAttributes: FunctionComponent<{
|
||||
application: SNApplication
|
||||
note: SNNote
|
||||
}> = ({ application, note }) => {
|
||||
const { words, characters, paragraphs } = useMemo(
|
||||
() => countNoteAttributes(note.text),
|
||||
[note.text],
|
||||
)
|
||||
const { words, characters, paragraphs } = useMemo(() => countNoteAttributes(note.text), [note.text])
|
||||
|
||||
const readTime = useMemo(
|
||||
() => (typeof words === 'number' ? calculateReadTime(words) : 'N/A'),
|
||||
[words],
|
||||
)
|
||||
const readTime = useMemo(() => (typeof words === 'number' ? calculateReadTime(words) : 'N/A'), [words])
|
||||
|
||||
const dateLastModified = useMemo(() => formatDate(note.userModifiedDate), [note.userModifiedDate])
|
||||
|
||||
@@ -168,283 +164,276 @@ const NOTE_SIZE_WARNING_THRESHOLD = 0.5 * BYTES_IN_ONE_MEGABYTE
|
||||
|
||||
const NoteSizeWarning: FunctionComponent<{
|
||||
note: SNNote
|
||||
}> = ({ note }) =>
|
||||
(new Blob([note.text]).size > NOTE_SIZE_WARNING_THRESHOLD ? (
|
||||
}> = ({ note }) => {
|
||||
return new Blob([note.text]).size > NOTE_SIZE_WARNING_THRESHOLD ? (
|
||||
<div className="flex items-center px-3 py-3.5 relative bg-note-size-warning">
|
||||
<Icon type="warning" className="color-accessory-tint-3 flex-shrink-0 mr-3" />
|
||||
<div className="color-grey-0 select-none leading-140% max-w-80%">
|
||||
This note may have trouble syncing to the mobile application due to its size.
|
||||
</div>
|
||||
</div>
|
||||
) : null)
|
||||
) : null
|
||||
}
|
||||
|
||||
export const NotesOptions = observer(
|
||||
({ application, appState, closeOnBlur }: NotesOptionsProps) => {
|
||||
const [altKeyDown, setAltKeyDown] = useState(false)
|
||||
export const NotesOptions = observer(({ application, appState, closeOnBlur }: NotesOptionsProps) => {
|
||||
const [altKeyDown, setAltKeyDown] = useState(false)
|
||||
|
||||
const toggleOn = (condition: (note: SNNote) => boolean) => {
|
||||
const notesMatchingAttribute = notes.filter(condition)
|
||||
const notesNotMatchingAttribute = notes.filter((note) => !condition(note))
|
||||
return notesMatchingAttribute.length > notesNotMatchingAttribute.length
|
||||
const toggleOn = (condition: (note: SNNote) => boolean) => {
|
||||
const notesMatchingAttribute = notes.filter(condition)
|
||||
const notesNotMatchingAttribute = notes.filter((note) => !condition(note))
|
||||
return notesMatchingAttribute.length > notesNotMatchingAttribute.length
|
||||
}
|
||||
|
||||
const notes = Object.values(appState.notes.selectedNotes)
|
||||
const hidePreviews = toggleOn((note) => note.hidePreview)
|
||||
const locked = toggleOn((note) => note.locked)
|
||||
const protect = toggleOn((note) => note.protected)
|
||||
const archived = notes.some((note) => note.archived)
|
||||
const unarchived = notes.some((note) => !note.archived)
|
||||
const trashed = notes.some((note) => note.trashed)
|
||||
const notTrashed = notes.some((note) => !note.trashed)
|
||||
const pinned = notes.some((note) => note.pinned)
|
||||
const unpinned = notes.some((note) => !note.pinned)
|
||||
|
||||
useEffect(() => {
|
||||
const removeAltKeyObserver = application.io.addKeyObserver({
|
||||
modifiers: [KeyboardModifier.Alt],
|
||||
onKeyDown: () => {
|
||||
setAltKeyDown(true)
|
||||
},
|
||||
onKeyUp: () => {
|
||||
setAltKeyDown(false)
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
removeAltKeyObserver()
|
||||
}
|
||||
}, [application])
|
||||
|
||||
const getNoteFileName = (note: SNNote): string => {
|
||||
const editor = application.componentManager.editorForNote(note)
|
||||
const format = editor?.package_info?.file_type || 'txt'
|
||||
return `${note.title}.${format}`
|
||||
}
|
||||
|
||||
const downloadSelectedItems = async () => {
|
||||
if (notes.length === 1) {
|
||||
application.getArchiveService().downloadData(new Blob([notes[0].text]), getNoteFileName(notes[0]))
|
||||
return
|
||||
}
|
||||
|
||||
const notes = Object.values(appState.notes.selectedNotes)
|
||||
const hidePreviews = toggleOn((note) => note.hidePreview)
|
||||
const locked = toggleOn((note) => note.locked)
|
||||
const protect = toggleOn((note) => note.protected)
|
||||
const archived = notes.some((note) => note.archived)
|
||||
const unarchived = notes.some((note) => !note.archived)
|
||||
const trashed = notes.some((note) => note.trashed)
|
||||
const notTrashed = notes.some((note) => !note.trashed)
|
||||
const pinned = notes.some((note) => note.pinned)
|
||||
const unpinned = notes.some((note) => !note.pinned)
|
||||
|
||||
useEffect(() => {
|
||||
const removeAltKeyObserver = application.io.addKeyObserver({
|
||||
modifiers: [KeyboardModifier.Alt],
|
||||
onKeyDown: () => {
|
||||
setAltKeyDown(true)
|
||||
},
|
||||
onKeyUp: () => {
|
||||
setAltKeyDown(false)
|
||||
},
|
||||
if (notes.length > 1) {
|
||||
const loadingToastId = addToast({
|
||||
type: ToastType.Loading,
|
||||
message: `Exporting ${notes.length} notes...`,
|
||||
})
|
||||
|
||||
return () => {
|
||||
removeAltKeyObserver()
|
||||
}
|
||||
}, [application])
|
||||
|
||||
const getNoteFileName = (note: SNNote): string => {
|
||||
const editor = application.componentManager.editorForNote(note)
|
||||
const format = editor?.package_info?.file_type || 'txt'
|
||||
return `${note.title}.${format}`
|
||||
}
|
||||
|
||||
const downloadSelectedItems = async () => {
|
||||
if (notes.length === 1) {
|
||||
application
|
||||
.getArchiveService()
|
||||
.downloadData(new Blob([notes[0].text]), getNoteFileName(notes[0]))
|
||||
return
|
||||
}
|
||||
|
||||
if (notes.length > 1) {
|
||||
const loadingToastId = addToast({
|
||||
type: ToastType.Loading,
|
||||
message: `Exporting ${notes.length} notes...`,
|
||||
})
|
||||
await application.getArchiveService().downloadDataAsZip(
|
||||
notes.map((note) => {
|
||||
return {
|
||||
filename: getNoteFileName(note),
|
||||
content: new Blob([note.text]),
|
||||
}
|
||||
}),
|
||||
)
|
||||
dismissToast(loadingToastId)
|
||||
addToast({
|
||||
type: ToastType.Success,
|
||||
message: `Exported ${notes.length} notes`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateSelectedItems = () => {
|
||||
notes.forEach((note) => {
|
||||
application.mutator.duplicateItem(note).catch(console.error)
|
||||
await application.getArchiveService().downloadDataAsZip(
|
||||
notes.map((note) => {
|
||||
return {
|
||||
filename: getNoteFileName(note),
|
||||
content: new Blob([note.text]),
|
||||
}
|
||||
}),
|
||||
)
|
||||
dismissToast(loadingToastId)
|
||||
addToast({
|
||||
type: ToastType.Success,
|
||||
message: `Exported ${notes.length} notes`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const openRevisionHistoryModal = () => {
|
||||
appState.notes.setShowRevisionHistoryModal(true)
|
||||
}
|
||||
const duplicateSelectedItems = () => {
|
||||
notes.forEach((note) => {
|
||||
application.mutator.duplicateItem(note).catch(console.error)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={openRevisionHistoryModal}
|
||||
>
|
||||
<Icon type="history" className={iconClass} />
|
||||
Note history
|
||||
</button>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
</>
|
||||
)}
|
||||
const openRevisionHistoryModal = () => {
|
||||
appState.notes.setShowRevisionHistoryModal(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={openRevisionHistoryModal}>
|
||||
<Icon type="history" className={iconClass} />
|
||||
Note history
|
||||
</button>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
onClick={() => {
|
||||
appState.notes.setLockSelectedNotes(!locked)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="pencil-off" className={iconClass} />
|
||||
Prevent editing
|
||||
</span>
|
||||
<Switch className="px-0" checked={locked} />
|
||||
</button>
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
onClick={() => {
|
||||
appState.notes.setHideSelectedNotePreviews(!hidePreviews)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="rich-text" className={iconClass} />
|
||||
Show preview
|
||||
</span>
|
||||
<Switch className="px-0" checked={!hidePreviews} />
|
||||
</button>
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
onClick={() => {
|
||||
appState.notes.setProtectSelectedNotes(!protect).catch(console.error)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className={iconClass} />
|
||||
Password protect
|
||||
</span>
|
||||
<Switch className="px-0" checked={protect} />
|
||||
</button>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<ChangeEditorOption appState={appState} application={application} note={notes[0]} />
|
||||
</>
|
||||
)}
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
{appState.tags.tagsCount > 0 && <AddTagOption appState={appState} />}
|
||||
{unpinned && (
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
onClick={() => {
|
||||
appState.notes.setLockSelectedNotes(!locked)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setPinSelectedNotes(true)
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="pencil-off" className={iconClass} />
|
||||
Prevent editing
|
||||
</span>
|
||||
<Switch className="px-0" checked={locked} />
|
||||
<Icon type="pin" className={iconClass} />
|
||||
Pin to top
|
||||
</button>
|
||||
)}
|
||||
{pinned && (
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
onClick={() => {
|
||||
appState.notes.setHideSelectedNotePreviews(!hidePreviews)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setPinSelectedNotes(false)
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="rich-text" className={iconClass} />
|
||||
Show preview
|
||||
</span>
|
||||
<Switch className="px-0" checked={!hidePreviews} />
|
||||
<Icon type="unpin" className={iconClass} />
|
||||
Unpin
|
||||
</button>
|
||||
)}
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={downloadSelectedItems}>
|
||||
<Icon type="download" className={iconClass} />
|
||||
Export
|
||||
</button>
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={duplicateSelectedItems}>
|
||||
<Icon type="copy" className={iconClass} />
|
||||
Duplicate
|
||||
</button>
|
||||
{unarchived && (
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
onClick={() => {
|
||||
appState.notes.setProtectSelectedNotes(!protect).catch(console.error)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setArchiveSelectedNotes(true).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className={iconClass} />
|
||||
Protect
|
||||
</span>
|
||||
<Switch className="px-0" checked={protect} />
|
||||
<Icon type="archive" className={iconClassWarning} />
|
||||
<span className="color-warning">Archive</span>
|
||||
</button>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<ChangeEditorOption appState={appState} application={application} note={notes[0]} />
|
||||
</>
|
||||
)}
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
{appState.tags.tagsCount > 0 && <AddTagOption appState={appState} />}
|
||||
{unpinned && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setPinSelectedNotes(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="pin" className={iconClass} />
|
||||
Pin to top
|
||||
</button>
|
||||
)}
|
||||
{pinned && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setPinSelectedNotes(false)
|
||||
}}
|
||||
>
|
||||
<Icon type="unpin" className={iconClass} />
|
||||
Unpin
|
||||
</button>
|
||||
)}
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={downloadSelectedItems}>
|
||||
<Icon type="download" className={iconClass} />
|
||||
Export
|
||||
)}
|
||||
{archived && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setArchiveSelectedNotes(false).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<Icon type="unarchive" className={iconClassWarning} />
|
||||
<span className="color-warning">Unarchive</span>
|
||||
</button>
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={duplicateSelectedItems}>
|
||||
<Icon type="copy" className={iconClass} />
|
||||
Duplicate
|
||||
</button>
|
||||
{unarchived && (
|
||||
)}
|
||||
{notTrashed &&
|
||||
(altKeyDown ? (
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
onClick={async () => {
|
||||
await appState.notes.deleteNotesPermanently()
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setArchiveSelectedNotes(true).catch(console.error)
|
||||
onClick={async () => {
|
||||
await appState.notes.setTrashSelectedNotes(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="archive" className={iconClass} />
|
||||
Archive
|
||||
<Icon type="trash" className={iconClassDanger} />
|
||||
<span className="color-danger">Move to trash</span>
|
||||
</button>
|
||||
)}
|
||||
{archived && (
|
||||
))}
|
||||
{trashed && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => {
|
||||
appState.notes.setArchiveSelectedNotes(false).catch(console.error)
|
||||
onClick={async () => {
|
||||
await appState.notes.setTrashSelectedNotes(false)
|
||||
}}
|
||||
>
|
||||
<Icon type="unarchive" className={iconClass} />
|
||||
Unarchive
|
||||
<Icon type="restore" className={iconClass} />
|
||||
Restore
|
||||
</button>
|
||||
)}
|
||||
{notTrashed &&
|
||||
(altKeyDown ? (
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
onClick={async () => {
|
||||
await appState.notes.deleteNotesPermanently()
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={async () => {
|
||||
await appState.notes.setTrashSelectedNotes(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className={iconClass} />
|
||||
Move to trash
|
||||
</button>
|
||||
))}
|
||||
{trashed && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={async () => {
|
||||
await appState.notes.setTrashSelectedNotes(false)
|
||||
}}
|
||||
>
|
||||
<Icon type="restore" className={iconClass} />
|
||||
Restore
|
||||
</button>
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
onClick={async () => {
|
||||
await appState.notes.deleteNotesPermanently()
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={async () => {
|
||||
await appState.notes.emptyTrash()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start">
|
||||
<Icon type="trash-sweep" className="color-danger mr-2" />
|
||||
<div className="flex-row">
|
||||
<div className="color-danger">Empty Trash</div>
|
||||
<div className="text-xs">{appState.notes.trashedNotesCount} notes in Trash</div>
|
||||
</div>
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
onClick={async () => {
|
||||
await appState.notes.deleteNotesPermanently()
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
onClick={async () => {
|
||||
await appState.notes.emptyTrash()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start">
|
||||
<Icon type="trash-sweep" className="color-danger mr-2" />
|
||||
<div className="flex-row">
|
||||
<div className="color-danger">Empty Trash</div>
|
||||
<div className="text-xs">{appState.notes.trashedNotesCount} notes in Trash</div>
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{notes.length === 1 ? (
|
||||
<>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<ListedActionsOption application={application} note={notes[0]} />
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<SpellcheckOptions appState={appState} note={notes[0]} />
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<NoteAttributes application={application} note={notes[0]} />
|
||||
<NoteSizeWarning note={notes[0]} />
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
},
|
||||
)
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{notes.length === 1 ? (
|
||||
<>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<ListedActionsOption application={application} note={notes[0]} />
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<SpellcheckOptions appState={appState} note={notes[0]} />
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<NoteAttributes application={application} note={notes[0]} />
|
||||
<NoteSizeWarning note={notes[0]} />
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown'
|
||||
import {
|
||||
FeatureIdentifier,
|
||||
PrefKey,
|
||||
ComponentArea,
|
||||
ComponentMutator,
|
||||
SNComponent,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FeatureIdentifier, PrefKey, ComponentArea, ComponentMutator, SNComponent } from '@standardnotes/snjs'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
@@ -18,6 +12,7 @@ import { FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Switch } from '@/Components/Switch'
|
||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -27,11 +22,7 @@ type EditorOption = DropdownItem & {
|
||||
value: FeatureIdentifier | 'plain-editor'
|
||||
}
|
||||
|
||||
const makeEditorDefault = (
|
||||
application: WebApplication,
|
||||
component: SNComponent,
|
||||
currentDefault: SNComponent,
|
||||
) => {
|
||||
const makeEditorDefault = (application: WebApplication, component: SNComponent, currentDefault: SNComponent) => {
|
||||
if (currentDefault) {
|
||||
removeEditorDefault(application, currentDefault)
|
||||
}
|
||||
@@ -53,9 +44,7 @@ const removeEditorDefault = (application: WebApplication, component: SNComponent
|
||||
}
|
||||
|
||||
const getDefaultEditor = (application: WebApplication) => {
|
||||
return application.componentManager
|
||||
.componentsForArea(ComponentArea.Editor)
|
||||
.filter((e) => e.isDefaultEditor())[0]
|
||||
return application.componentManager.componentsForArea(ComponentArea.Editor).filter((e) => e.isDefaultEditor())[0]
|
||||
}
|
||||
|
||||
export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
@@ -64,9 +53,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
() => getDefaultEditor(application)?.package_info?.identifier || 'plain-editor',
|
||||
)
|
||||
|
||||
const [spellcheck, setSpellcheck] = useState(() =>
|
||||
application.getPreference(PrefKey.EditorSpellcheck, true),
|
||||
)
|
||||
const [spellcheck, setSpellcheck] = useState(() => application.getPreference(PrefKey.EditorSpellcheck, true))
|
||||
|
||||
const [addNoteToParentFolders, setAddNoteToParentFolders] = useState(() =>
|
||||
application.getPreference(PrefKey.NoteAddToParentFolders, true),
|
||||
@@ -95,7 +82,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
{
|
||||
icon: 'plain-text',
|
||||
iconClassName: 'color-accessory-tint-1',
|
||||
label: 'Plain Editor',
|
||||
label: PLAIN_EDITOR_NAME,
|
||||
value: 'plain-editor',
|
||||
},
|
||||
])
|
||||
@@ -124,12 +111,12 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
<PreferencesSegment>
|
||||
<Title>Defaults</Title>
|
||||
<div>
|
||||
<Subtitle>Default Editor</Subtitle>
|
||||
<Text>New notes will be created using this editor.</Text>
|
||||
<Subtitle>Default Note Type</Subtitle>
|
||||
<Text>New notes will be created using this type.</Text>
|
||||
<div className="mt-2">
|
||||
<Dropdown
|
||||
id="def-editor-dropdown"
|
||||
label="Select the default editor"
|
||||
label="Select the default note type"
|
||||
items={editorItems}
|
||||
value={defaultEditorValue}
|
||||
onChange={setDefaultEditor}
|
||||
@@ -141,9 +128,8 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Spellcheck</Subtitle>
|
||||
<Text>
|
||||
The default spellcheck value for new notes. Spellcheck can be configured per note from
|
||||
the note context menu. Spellcheck may degrade overall typing performance with long
|
||||
notes.
|
||||
The default spellcheck value for new notes. Spellcheck can be configured per note from the note context
|
||||
menu. Spellcheck may degrade overall typing performance with long notes.
|
||||
</Text>
|
||||
</div>
|
||||
<Switch onChange={toggleSpellcheck} checked={spellcheck} />
|
||||
@@ -152,16 +138,11 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Add all parent tags when adding a nested tag to a note</Subtitle>
|
||||
<Text>
|
||||
When enabled, adding a nested tag to a note will automatically add all associated
|
||||
parent tags.
|
||||
</Text>
|
||||
<Text>When enabled, adding a nested tag to a note will automatically add all associated parent tags.</Text>
|
||||
</div>
|
||||
<Switch
|
||||
onChange={() => {
|
||||
application
|
||||
.setPreference(PrefKey.NoteAddToParentFolders, !addNoteToParentFolders)
|
||||
.catch(console.error)
|
||||
application.setPreference(PrefKey.NoteAddToParentFolders, !addNoteToParentFolders).catch(console.error)
|
||||
setAddNoteToParentFolders(!addNoteToParentFolders)
|
||||
}}
|
||||
checked={addNoteToParentFolders}
|
||||
|
||||
@@ -20,3 +20,5 @@ export const BYTES_IN_ONE_MEGABYTE = 1_000_000
|
||||
export const TAG_FOLDERS_FEATURE_NAME = 'Tag folders'
|
||||
export const TAG_FOLDERS_FEATURE_TOOLTIP = 'A Plus or Pro plan is required to enable Tag folders.'
|
||||
export const SMART_TAGS_FEATURE_NAME = 'Smart Tags'
|
||||
|
||||
export const PLAIN_EDITOR_NAME = 'Plain Text'
|
||||
|
||||
Reference in New Issue
Block a user