feat: per-tag display preferences (#1868)

This commit is contained in:
Mo
2022-10-25 07:27:26 -05:00
committed by GitHub
parent 9248d0ff16
commit ee7f11c933
32 changed files with 783 additions and 413 deletions

View File

@@ -1,58 +1,17 @@
import Dropdown from '@/Components/Dropdown/Dropdown'
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
import {
FeatureIdentifier,
PrefKey,
ComponentArea,
ComponentMutator,
SNComponent,
NewNoteTitleFormat,
Platform,
} from '@standardnotes/snjs'
import { PrefKey, Platform } from '@standardnotes/snjs'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/Application/Application'
import { FunctionComponent, useEffect, useMemo, useState } from 'react'
import { FunctionComponent, useState } from 'react'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch'
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import CustomNoteTitleFormat from './Defaults/CustomNoteTitleFormat'
import { PrefDefaults } from '@/Constants/PrefDefaults'
type Props = {
application: WebApplication
}
type EditorOption = DropdownItem & {
value: FeatureIdentifier | 'plain-editor'
}
const makeEditorDefault = (application: WebApplication, component: SNComponent, currentDefault: SNComponent) => {
if (currentDefault) {
removeEditorDefault(application, currentDefault)
}
application.mutator
.changeAndSaveItem(component, (m) => {
const mutator = m as ComponentMutator
mutator.defaultEditor = true
})
.catch(console.error)
}
const removeEditorDefault = (application: WebApplication, component: SNComponent) => {
application.mutator
.changeAndSaveItem(component, (m) => {
const mutator = m as ComponentMutator
mutator.defaultEditor = false
})
.catch(console.error)
}
const getDefaultEditor = (application: WebApplication) => {
return application.componentManager.componentsForArea(ComponentArea.Editor).filter((e) => e.isDefaultEditor())[0]
}
export const AndroidConfirmBeforeExitKey = 'ConfirmBeforeExit'
const Defaults: FunctionComponent<Props> = ({ application }) => {
@@ -60,23 +19,10 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
() => (application.getValue(AndroidConfirmBeforeExitKey) as boolean) ?? true,
)
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
const [defaultEditorValue, setDefaultEditorValue] = useState(
() => getDefaultEditor(application)?.package_info?.identifier || 'plain-editor',
)
const [spellcheck, setSpellcheck] = useState(() =>
application.getPreference(PrefKey.EditorSpellcheck, PrefDefaults[PrefKey.EditorSpellcheck]),
)
const [newNoteTitleFormat, setNewNoteTitleFormat] = useState(() =>
application.getPreference(PrefKey.NewNoteTitleFormat, PrefDefaults[PrefKey.NewNoteTitleFormat]),
)
const handleNewNoteTitleFormatChange = (value: string) => {
setNewNoteTitleFormat(value as NewNoteTitleFormat)
application.setPreference(PrefKey.NewNoteTitleFormat, value as NewNoteTitleFormat)
}
const [addNoteToParentFolders, setAddNoteToParentFolders] = useState(() =>
application.getPreference(PrefKey.NoteAddToParentFolders, PrefDefaults[PrefKey.NoteAddToParentFolders]),
)
@@ -86,70 +32,6 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
application.toggleGlobalSpellcheck().catch(console.error)
}
useEffect(() => {
const editors = application.componentManager
.componentsForArea(ComponentArea.Editor)
.map((editor): EditorOption => {
const identifier = editor.package_info.identifier
const [iconType, tint] = application.iconsController.getIconAndTintForNoteType(editor.package_info.note_type)
return {
label: editor.displayName,
value: identifier,
...(iconType ? { icon: iconType } : null),
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
}
})
.concat([
{
icon: 'plain-text',
iconClassName: 'text-accessory-tint-1',
label: PLAIN_EDITOR_NAME,
value: 'plain-editor',
},
])
.sort((a, b) => {
return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1
})
setEditorItems(editors)
}, [application])
const setDefaultEditor = (value: string) => {
setDefaultEditorValue(value as FeatureIdentifier)
const editors = application.componentManager.componentsForArea(ComponentArea.Editor)
const currentDefault = getDefaultEditor(application)
if (value !== 'plain-editor') {
const editorComponent = editors.filter((e) => e.package_info.identifier === value)[0]
makeEditorDefault(application, editorComponent, currentDefault)
} else {
removeEditorDefault(application, currentDefault)
}
}
const noteTitleFormatOptions = useMemo(
() => [
{
label: 'Current date and time',
value: NewNoteTitleFormat.CurrentDateAndTime,
},
{
label: 'Current note count',
value: NewNoteTitleFormat.CurrentNoteCount,
},
{
label: 'Custom format',
value: NewNoteTitleFormat.CustomFormat,
},
{
label: 'Empty',
value: NewNoteTitleFormat.Empty,
},
],
[],
)
const toggleAndroidConfirmBeforeExit = () => {
const newValue = !androidConfirmBeforeExit
setAndroidConfirmBeforeExit(newValue)
@@ -161,44 +43,17 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
<PreferencesSegment>
<Title>Defaults</Title>
{application.platform === Platform.Android && (
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Always ask before closing app</Subtitle>
<Text>Whether a confirmation dialog should be shown before closing the app.</Text>
<>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Always ask before closing app (Android)</Subtitle>
<Text>Whether a confirmation dialog should be shown before closing the app.</Text>
</div>
<Switch onChange={toggleAndroidConfirmBeforeExit} checked={androidConfirmBeforeExit} />
</div>
<Switch onChange={toggleAndroidConfirmBeforeExit} checked={androidConfirmBeforeExit} />
</div>
<HorizontalSeparator classes="my-4" />
</>
)}
<HorizontalSeparator classes="my-4" />
<div>
<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 note type"
items={editorItems}
value={defaultEditorValue}
onChange={setDefaultEditor}
/>
</div>
</div>
<HorizontalSeparator classes="my-4" />
<div>
<Subtitle>Default Note Title Format</Subtitle>
<Text>New notes will be created with a title in this format</Text>
<div className="mt-2">
<Dropdown
id="def-new-note-title-format"
label="Select the default note type"
items={noteTitleFormatOptions}
value={newNoteTitleFormat}
onChange={handleNewNoteTitleFormatChange}
/>
</div>
</div>
{newNoteTitleFormat === NewNoteTitleFormat.CustomFormat && <CustomNoteTitleFormat application={application} />}
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Spellcheck</Subtitle>

View File

@@ -1,80 +0,0 @@
import { WebApplication } from '@/Application/Application'
import { Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { PrefKey } from '@standardnotes/snjs'
import { ChangeEventHandler, useRef, useState } from 'react'
import dayjs from 'dayjs'
type Props = {
application: WebApplication
}
const PrefChangeDebounceTimeInMs = 25
const HelpPageUrl = 'https://day.js.org/docs/en/display/format#list-of-all-available-formats'
const CustomNoteTitleFormat = ({ application }: Props) => {
const [customNoteTitleFormat, setCustomNoteTitleFormat] = useState(() =>
application.getPreference(PrefKey.CustomNoteTitleFormat, PrefDefaults[PrefKey.CustomNoteTitleFormat]),
)
const setCustomNoteTitleFormatPreference = () => {
application.setPreference(PrefKey.CustomNoteTitleFormat, customNoteTitleFormat)
}
const debounceTimeoutRef = useRef<number>()
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (event) => {
setCustomNoteTitleFormat(event.currentTarget.value)
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current)
}
debounceTimeoutRef.current = window.setTimeout(async () => {
setCustomNoteTitleFormatPreference()
}, PrefChangeDebounceTimeInMs)
}
return (
<>
<HorizontalSeparator classes="my-4" />
<div>
<Subtitle>Custom Note Title Format</Subtitle>
<Text>
All available date-time formatting options can be found{' '}
<a
className="underline"
href={HelpPageUrl}
target="_blank"
onClick={(event) => {
if (application.isNativeMobileWeb()) {
event.preventDefault()
application.mobileDevice().openUrl(HelpPageUrl)
}
}}
>
here
</a>
. Use square brackets (<code>[]</code>) to escape date-time formatting.
</Text>
<div className="mt-2">
<input
className="min-w-55 rounded border border-solid border-passive-3 bg-default px-2 py-1.5 text-sm focus-within:ring-2 focus-within:ring-info"
placeholder="e.g. YYYY-MM-DD"
value={customNoteTitleFormat}
onChange={handleInputChange}
onBlur={setCustomNoteTitleFormatPreference}
spellCheck={false}
/>
</div>
<div className="mt-2">
<span className="font-bold">Preview:</span> {dayjs().format(customNoteTitleFormat)}
</div>
</div>
</>
)
}
export default CustomNoteTitleFormat