feat: generic items list (#1035)
This commit is contained in:
@@ -118,7 +118,7 @@ export const AccountMenu: FunctionComponent<Props> = observer(
|
|||||||
return (
|
return (
|
||||||
<div ref={ref} id="account-menu" className="sn-component">
|
<div ref={ref} id="account-menu" className="sn-component">
|
||||||
<div
|
<div
|
||||||
className={`sn-menu-border sn-account-menu sn-dropdown ${
|
className={`sn-account-menu sn-dropdown ${
|
||||||
shouldAnimateCloseMenu ? 'slide-up-animation' : 'sn-dropdown--animated'
|
shouldAnimateCloseMenu ? 'slide-up-animation' : 'sn-dropdown--animated'
|
||||||
} min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto absolute`}
|
} min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto absolute`}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { WebApplication } from '@/UIModels/Application'
|
|||||||
import { Component } from 'preact'
|
import { Component } from 'preact'
|
||||||
import { ApplicationView } from '@/Components/ApplicationView/ApplicationView'
|
import { ApplicationView } from '@/Components/ApplicationView/ApplicationView'
|
||||||
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
||||||
import { ApplicationGroupEvent, Runtime, ApplicationGroupEventData, DeinitSource } from '@standardnotes/snjs'
|
import { ApplicationGroupEvent, ApplicationGroupEventData, DeinitSource } from '@standardnotes/snjs'
|
||||||
import { unmountComponentAtNode, findDOMNode } from 'preact/compat'
|
import { unmountComponentAtNode, findDOMNode } from 'preact/compat'
|
||||||
import { DialogContent, DialogOverlay } from '@reach/dialog'
|
import { DialogContent, DialogOverlay } from '@reach/dialog'
|
||||||
import { isDesktopApplication } from '@/Utils'
|
import { isDesktopApplication } from '@/Utils'
|
||||||
@@ -39,12 +39,7 @@ export class ApplicationGroupView extends Component<Props, State> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.group = new ApplicationGroup(
|
this.group = new ApplicationGroup(props.server, props.device, props.websocketUrl)
|
||||||
props.server,
|
|
||||||
props.device,
|
|
||||||
props.enableUnfinished ? Runtime.Dev : Runtime.Prod,
|
|
||||||
props.websocketUrl,
|
|
||||||
)
|
|
||||||
|
|
||||||
window.mainApplicationGroup = this.group
|
window.mainApplicationGroup = this.group
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants'
|
|||||||
import { alertDialog } from '@/Services/AlertService'
|
import { alertDialog } from '@/Services/AlertService'
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { Navigation } from '@/Components/Navigation/Navigation'
|
import { Navigation } from '@/Components/Navigation/Navigation'
|
||||||
import { NotesView } from '@/Components/NotesView/NotesView'
|
|
||||||
import { NoteGroupView } from '@/Components/NoteGroupView/NoteGroupView'
|
import { NoteGroupView } from '@/Components/NoteGroupView/NoteGroupView'
|
||||||
import { Footer } from '@/Components/Footer/Footer'
|
import { Footer } from '@/Components/Footer/Footer'
|
||||||
import { SessionsModal } from '@/Components/SessionsModal'
|
import { SessionsModal } from '@/Components/SessionsModal/SessionsModal'
|
||||||
import { PreferencesViewWrapper } from '@/Components/Preferences/PreferencesViewWrapper'
|
import { PreferencesViewWrapper } from '@/Components/Preferences/PreferencesViewWrapper'
|
||||||
import { ChallengeModal } from '@/Components/ChallengeModal/ChallengeModal'
|
import { ChallengeModal } from '@/Components/ChallengeModal/ChallengeModal'
|
||||||
import { NotesContextMenu } from '@/Components/NotesContextMenu/NotesContextMenu'
|
import { NotesContextMenu } from '@/Components/NotesContextMenu/NotesContextMenu'
|
||||||
@@ -21,9 +20,11 @@ import { PremiumModalProvider } from '@/Hooks/usePremiumModal'
|
|||||||
import { ConfirmSignoutContainer } from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
|
import { ConfirmSignoutContainer } from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
|
||||||
import { TagsContextMenu } from '@/Components/Tags/TagContextMenu'
|
import { TagsContextMenu } from '@/Components/Tags/TagContextMenu'
|
||||||
import { ToastContainer } from '@standardnotes/stylekit'
|
import { ToastContainer } from '@standardnotes/stylekit'
|
||||||
import { FilePreviewModal } from '../Files/FilePreviewModal'
|
import { FilePreviewModal } from '@/Components/Files/FilePreviewModal'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
import { isStateDealloced } from '@/UIModels/AppState/AbstractState'
|
import { isStateDealloced } from '@/UIModels/AppState/AbstractState'
|
||||||
|
import { ContentListView } from '@/Components/ContentListView/ContentListView'
|
||||||
|
import { FileContextMenu } from '@/Components/FileContextMenu/FileContextMenu'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -211,7 +212,7 @@ export const ApplicationView: FunctionComponent<Props> = ({ application, mainApp
|
|||||||
<div className={platformString + ' main-ui-view sn-component'}>
|
<div className={platformString + ' main-ui-view sn-component'}>
|
||||||
<div id="app" className={appClass + ' app app-column-container'}>
|
<div id="app" className={appClass + ' app app-column-container'}>
|
||||||
<Navigation application={application} />
|
<Navigation application={application} />
|
||||||
<NotesView application={application} appState={appState} />
|
<ContentListView application={application} appState={appState} />
|
||||||
<NoteGroupView application={application} />
|
<NoteGroupView application={application} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -227,6 +228,7 @@ export const ApplicationView: FunctionComponent<Props> = ({ application, mainApp
|
|||||||
<>
|
<>
|
||||||
<NotesContextMenu application={application} appState={appState} />
|
<NotesContextMenu application={application} appState={appState} />
|
||||||
<TagsContextMenu appState={appState} />
|
<TagsContextMenu appState={appState} />
|
||||||
|
<FileContextMenu appState={appState} />
|
||||||
<PurchaseFlowWrapper application={application} appState={appState} />
|
<PurchaseFlowWrapper application={application} appState={appState} />
|
||||||
<ConfirmSignoutContainer
|
<ConfirmSignoutContainer
|
||||||
applicationGroup={mainApplicationGroup}
|
applicationGroup={mainApplicationGroup}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { FunctionComponent } from 'preact'
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||||
import { ChallengeReason, CollectionSort, ContentType, FileItem, SNNote } from '@standardnotes/snjs'
|
import { ChallengeReason, ContentType, FileItem, SNNote } from '@standardnotes/snjs'
|
||||||
import { confirmDialog } from '@/Services/AlertService'
|
import { confirmDialog } from '@/Services/AlertService'
|
||||||
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit'
|
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit'
|
||||||
import { StreamingFileReader } from '@standardnotes/filepicker'
|
import { StreamingFileReader } from '@standardnotes/filepicker'
|
||||||
@@ -32,7 +32,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
const note: SNNote | undefined = Object.values(appState.notes.selectedNotes)[0]
|
const note: SNNote | undefined = appState.notes.firstSelectedNote
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [position, setPosition] = useState({
|
const [position, setPosition] = useState({
|
||||||
@@ -59,10 +59,8 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
const attachedFilesCount = attachedFiles.length
|
const attachedFilesCount = attachedFiles.length
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
application.items.setDisplayOptions(ContentType.File, CollectionSort.Title, 'dsc')
|
|
||||||
|
|
||||||
const unregisterFileStream = application.streamItems(ContentType.File, () => {
|
const unregisterFileStream = application.streamItems(ContentType.File, () => {
|
||||||
setAllFiles(application.items.getDisplayableItems<FileItem>(ContentType.File))
|
setAllFiles(application.items.getDisplayableFiles())
|
||||||
if (note) {
|
if (note) {
|
||||||
setAttachedFiles(application.items.getFilesForNote(note))
|
setAttachedFiles(application.items.getFilesForNote(note))
|
||||||
}
|
}
|
||||||
@@ -174,7 +172,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const authorizeProtectedActionForFile = async (file: FileItem, challengeReason: ChallengeReason) => {
|
const authorizeProtectedActionForFile = async (file: FileItem, challengeReason: ChallengeReason) => {
|
||||||
const authorizedFiles = await application.protections.authorizeProtectedActionForFiles([file], challengeReason)
|
const authorizedFiles = await application.protections.authorizeProtectedActionForItems([file], challengeReason)
|
||||||
const isAuthorized = authorizedFiles.length > 0 && authorizedFiles.includes(file)
|
const isAuthorized = authorizedFiles.length > 0 && authorizedFiles.includes(file)
|
||||||
return isAuthorized
|
return isAuthorized
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure
|
|||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { StateUpdater, useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
import { StateUpdater, useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||||
import { PopoverFileItemProps } from './PopoverFileItem'
|
import { PopoverFileItemProps } from './PopoverFileItem'
|
||||||
import { PopoverFileItemActionType } from './PopoverFileItemAction'
|
import { PopoverFileItemActionType } from './PopoverFileItemAction'
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const ChangeEditorButton: FunctionComponent<Props> = observer(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = Object.values(appState.notes.selectedNotes)[0]
|
const note = appState.notes.firstSelectedNote
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
const [position, setPosition] = useState({
|
const [position, setPosition] = useState({
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type ChangeEditorMenuProps = {
|
|||||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||||
closeMenu: () => void
|
closeMenu: () => void
|
||||||
isVisible: boolean
|
isVisible: boolean
|
||||||
note: SNNote
|
note: SNNote | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGroupId = (group: EditorMenuGroup) => group.title.toLowerCase().replace(/\s/, '-')
|
const getGroupId = (group: EditorMenuGroup) => group.title.toLowerCase().replace(/\s/, '-')
|
||||||
@@ -75,7 +75,8 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
[currentEditor],
|
[currentEditor],
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectComponent = async (component: SNComponent | null, note: SNNote) => {
|
const selectComponent = useCallback(
|
||||||
|
async (component: SNComponent | null, note: SNNote) => {
|
||||||
if (component) {
|
if (component) {
|
||||||
if (component.conflictOf) {
|
if (component.conflictOf) {
|
||||||
application.mutator
|
application.mutator
|
||||||
@@ -88,7 +89,7 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
|
|
||||||
const transactions: TransactionalMutation[] = []
|
const transactions: TransactionalMutation[] = []
|
||||||
|
|
||||||
await application.getAppState().notesView.insertCurrentIfTemplate()
|
await application.getAppState().contentListView.insertCurrentIfTemplate()
|
||||||
|
|
||||||
if (note.locked) {
|
if (note.locked) {
|
||||||
application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT).catch(console.error)
|
application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT).catch(console.error)
|
||||||
@@ -133,9 +134,12 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
application.sync.sync().catch(console.error)
|
application.sync.sync().catch(console.error)
|
||||||
|
|
||||||
setCurrentEditor(application.componentManager.editorForNote(note))
|
setCurrentEditor(application.componentManager.editorForNote(note))
|
||||||
}
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
const selectEditor = async (itemToBeSelected: EditorMenuItem) => {
|
const selectEditor = useCallback(
|
||||||
|
async (itemToBeSelected: EditorMenuItem) => {
|
||||||
if (!itemToBeSelected.isEntitled) {
|
if (!itemToBeSelected.isEntitled) {
|
||||||
premiumModal.activate(itemToBeSelected.name)
|
premiumModal.activate(itemToBeSelected.name)
|
||||||
return
|
return
|
||||||
@@ -160,12 +164,14 @@ export const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSelectEditor) {
|
if (shouldSelectEditor && note) {
|
||||||
selectComponent(itemToBeSelected.component ?? null, note).catch(console.error)
|
selectComponent(itemToBeSelected.component ?? null, note).catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
closeMenu()
|
closeMenu()
|
||||||
}
|
},
|
||||||
|
[application.componentManager, closeMenu, currentEditor, note, premiumModal, selectComponent],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu className="pt-0.5 pb-1" a11yLabel="Change note type menu" isOpen={isVisible}>
|
<Menu className="pt-0.5 pb-1" a11yLabel="Change note type menu" isOpen={isVisible}>
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
|
import { KeyboardKey } from '@/Services/IOService'
|
||||||
|
import { AppState } from '@/UIModels/AppState'
|
||||||
|
import { UuidString } from '@standardnotes/snjs'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants'
|
||||||
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
|
import { ContentListItem } from './ContentListItem'
|
||||||
|
import { useCallback } from 'preact/hooks'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
application: WebApplication
|
||||||
|
appState: AppState
|
||||||
|
items: ListableContentItem[]
|
||||||
|
selectedItems: Record<UuidString, ListableContentItem>
|
||||||
|
paginate: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContentList: FunctionComponent<Props> = observer(
|
||||||
|
({ application, appState, items, selectedItems, paginate }) => {
|
||||||
|
const { selectPreviousItem, selectNextItem } = appState.contentListView
|
||||||
|
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = appState.contentListView.webDisplayOptions
|
||||||
|
const { sortBy } = appState.contentListView.displayOptions
|
||||||
|
|
||||||
|
const onScroll = useCallback(
|
||||||
|
(e: Event) => {
|
||||||
|
const offset = NOTES_LIST_SCROLL_THRESHOLD
|
||||||
|
const element = e.target as HTMLElement
|
||||||
|
if (element.scrollTop + element.offsetHeight >= element.scrollHeight - offset) {
|
||||||
|
paginate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[paginate],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
if (e.key === KeyboardKey.Up) {
|
||||||
|
e.preventDefault()
|
||||||
|
selectPreviousItem()
|
||||||
|
} else if (e.key === KeyboardKey.Down) {
|
||||||
|
e.preventDefault()
|
||||||
|
selectNextItem()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectNextItem, selectPreviousItem],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="infinite-scroll focus:shadow-none focus:outline-none"
|
||||||
|
id="notes-scrollable"
|
||||||
|
onScroll={onScroll}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
|
>
|
||||||
|
{items.map((item) => (
|
||||||
|
<ContentListItem
|
||||||
|
key={item.uuid}
|
||||||
|
application={application}
|
||||||
|
appState={appState}
|
||||||
|
item={item}
|
||||||
|
selected={!!selectedItems[item.uuid]}
|
||||||
|
hideDate={hideDate}
|
||||||
|
hidePreview={hideNotePreview}
|
||||||
|
hideTags={hideTags}
|
||||||
|
hideIcon={hideEditorIcon}
|
||||||
|
sortBy={sortBy}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ContentType, SNTag } from '@standardnotes/snjs'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { FileListItem } from './FileListItem'
|
||||||
|
import { NoteListItem } from './NoteListItem'
|
||||||
|
import { AbstractListItemProps } from './Types/AbstractListItemProps'
|
||||||
|
|
||||||
|
export const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
|
||||||
|
const getTags = () => {
|
||||||
|
if (props.hideTags) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedTag = props.appState.tags.selected
|
||||||
|
if (!selectedTag) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = props.appState.getItemTags(props.item)
|
||||||
|
|
||||||
|
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
|
||||||
|
if (isNavigatingOnlyTag) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags.map((tag) => tag.title).sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props.item.content_type) {
|
||||||
|
case ContentType.Note:
|
||||||
|
return <NoteListItem tags={getTags()} {...props} />
|
||||||
|
case ContentType.File:
|
||||||
|
return <FileListItem tags={getTags()} {...props} />
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ import { PrefKey } from '@standardnotes/snjs'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||||
|
import { ContentList } from '@/Components/ContentListView/ContentList'
|
||||||
|
import { NotesListOptionsMenu } from '@/Components/ContentListView/NotesListOptionsMenu'
|
||||||
import { NoAccountWarning } from '@/Components/NoAccountWarning/NoAccountWarning'
|
import { NoAccountWarning } from '@/Components/NoAccountWarning/NoAccountWarning'
|
||||||
import { NotesList } from '@/Components/NotesList/NotesList'
|
|
||||||
import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu'
|
|
||||||
import { SearchOptions } from '@/Components/SearchOptions/SearchOptions'
|
import { SearchOptions } from '@/Components/SearchOptions/SearchOptions'
|
||||||
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||||
@@ -20,34 +20,32 @@ type Props = {
|
|||||||
appState: AppState
|
appState: AppState
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotesView: FunctionComponent<Props> = observer(({ application, appState }: Props) => {
|
export const ContentListView: FunctionComponent<Props> = observer(({ application, appState }) => {
|
||||||
if (isStateDealloced(appState)) {
|
if (isStateDealloced(appState)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const notesViewPanelRef = useRef<HTMLDivElement>(null)
|
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
|
||||||
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
|
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
completedFullSync,
|
completedFullSync,
|
||||||
displayOptions,
|
|
||||||
noteFilterText,
|
noteFilterText,
|
||||||
optionsSubtitle,
|
optionsSubtitle,
|
||||||
panelTitle,
|
panelTitle,
|
||||||
renderedNotes,
|
renderedItems,
|
||||||
|
setNoteFilterText,
|
||||||
searchBarElement,
|
searchBarElement,
|
||||||
|
selectNextItem,
|
||||||
|
selectPreviousItem,
|
||||||
|
onFilterEnter,
|
||||||
|
clearFilterText,
|
||||||
paginate,
|
paginate,
|
||||||
panelWidth,
|
panelWidth,
|
||||||
} = appState.notesView
|
createNewNote,
|
||||||
|
} = appState.contentListView
|
||||||
|
|
||||||
const { selectedNotes } = appState.notes
|
const { selectedItems } = appState.selectedItems
|
||||||
|
|
||||||
const createNewNote = useCallback(() => appState.notesView.createNewNote(), [appState])
|
|
||||||
const onFilterEnter = useCallback(() => appState.notesView.onFilterEnter(), [appState])
|
|
||||||
const clearFilterText = useCallback(() => appState.notesView.clearFilterText(), [appState])
|
|
||||||
const setNoteFilterText = useCallback((text: string) => appState.notesView.setNoteFilterText(text), [appState])
|
|
||||||
const selectNextNote = useCallback(() => appState.notesView.selectNextNote(), [appState])
|
|
||||||
const selectPreviousNote = useCallback(() => appState.notesView.selectPreviousNote(), [appState])
|
|
||||||
|
|
||||||
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
||||||
const [focusedSearch, setFocusedSearch] = useState(false)
|
const [focusedSearch, setFocusedSearch] = useState(false)
|
||||||
@@ -76,7 +74,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
if (searchBarElement === document.activeElement) {
|
if (searchBarElement === document.activeElement) {
|
||||||
searchBarElement?.blur()
|
searchBarElement?.blur()
|
||||||
}
|
}
|
||||||
selectNextNote()
|
selectNextItem()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -84,7 +82,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
key: KeyboardKey.Up,
|
key: KeyboardKey.Up,
|
||||||
element: document.body,
|
element: document.body,
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
selectPreviousNote()
|
selectPreviousItem()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
previousNoteKeyObserver()
|
previousNoteKeyObserver()
|
||||||
searchKeyObserver()
|
searchKeyObserver()
|
||||||
}
|
}
|
||||||
}, [application, createNewNote, selectPreviousNote, searchBarElement, selectNextNote])
|
}, [application.io, createNewNote, searchBarElement, selectNextItem, selectPreviousItem])
|
||||||
|
|
||||||
const onNoteFilterTextChange = useCallback(
|
const onNoteFilterTextChange = useCallback(
|
||||||
(e: Event) => {
|
(e: Event) => {
|
||||||
@@ -144,14 +142,14 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="notes-column"
|
id="items-column"
|
||||||
className="sn-component section notes app-column app-column-second"
|
className="sn-component section app-column app-column-second"
|
||||||
aria-label="Notes"
|
aria-label={'Notes & Files'}
|
||||||
ref={notesViewPanelRef}
|
ref={itemsViewPanelRef}
|
||||||
>
|
>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div id="notes-title-bar" className="section-title-bar">
|
<div id="items-title-bar" className="section-title-bar">
|
||||||
<div id="notes-title-bar-container">
|
<div id="items-title-bar-container">
|
||||||
<div className="section-title-bar-header">
|
<div className="section-title-bar-header">
|
||||||
<div className="sk-h2 font-semibold title">{panelTitle}</div>
|
<div className="sk-h2 font-semibold title">{panelTitle}</div>
|
||||||
<button
|
<button
|
||||||
@@ -172,7 +170,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
id="search-bar"
|
id="search-bar"
|
||||||
className="filter-bar"
|
className="filter-bar"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
title="Searches notes in the currently selected tag"
|
title="Searches notes and files in the currently selected tag"
|
||||||
value={noteFilterText}
|
value={noteFilterText}
|
||||||
onChange={onNoteFilterTextChange}
|
onChange={onNoteFilterTextChange}
|
||||||
onKeyUp={onNoteFilterKeyUp}
|
onKeyUp={onNoteFilterKeyUp}
|
||||||
@@ -195,7 +193,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
</div>
|
</div>
|
||||||
<NoAccountWarning appState={appState} />
|
<NoAccountWarning appState={appState} />
|
||||||
</div>
|
</div>
|
||||||
<div id="notes-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
|
<div id="items-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
|
||||||
<div className="sk-app-bar no-edges">
|
<div className="sk-app-bar no-edges">
|
||||||
<div className="left">
|
<div className="left">
|
||||||
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
|
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
|
||||||
@@ -227,27 +225,24 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{completedFullSync && !renderedNotes.length ? <p className="empty-notes-list faded">No notes.</p> : null}
|
{completedFullSync && !renderedItems.length ? <p className="empty-items-list faded">No items.</p> : null}
|
||||||
{!completedFullSync && !renderedNotes.length ? (
|
{!completedFullSync && !renderedItems.length ? <p className="empty-items-list faded">Loading...</p> : null}
|
||||||
<p className="empty-notes-list faded">Loading notes...</p>
|
{renderedItems.length ? (
|
||||||
) : null}
|
<ContentList
|
||||||
{renderedNotes.length ? (
|
items={renderedItems}
|
||||||
<NotesList
|
selectedItems={selectedItems}
|
||||||
notes={renderedNotes}
|
|
||||||
selectedNotes={selectedNotes}
|
|
||||||
application={application}
|
application={application}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
displayOptions={displayOptions}
|
|
||||||
paginate={paginate}
|
paginate={paginate}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{notesViewPanelRef.current && (
|
{itemsViewPanelRef.current && (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
collapsable={true}
|
collapsable={true}
|
||||||
hoverable={true}
|
hoverable={true}
|
||||||
defaultWidth={300}
|
defaultWidth={300}
|
||||||
panel={notesViewPanelRef.current}
|
panel={itemsViewPanelRef.current}
|
||||||
side={PanelSide.Right}
|
side={PanelSide.Right}
|
||||||
type={PanelResizeType.WidthOnly}
|
type={PanelResizeType.WidthOnly}
|
||||||
resizeFinishCallback={panelResizeFinishCallback}
|
resizeFinishCallback={panelResizeFinishCallback}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { FileItem } from '@standardnotes/snjs'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { useCallback } from 'preact/hooks'
|
||||||
|
import { getFileIconComponent } from '../AttachedFilesPopover/PopoverFileItem'
|
||||||
|
import { ListItemConflictIndicator } from './ListItemConflictIndicator'
|
||||||
|
import { ListItemFlagIcons } from './ListItemFlagIcons'
|
||||||
|
import { ListItemTags } from './ListItemTags'
|
||||||
|
import { ListItemMetadata } from './ListItemMetadata'
|
||||||
|
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
||||||
|
|
||||||
|
export const FileListItem: FunctionComponent<DisplayableListItemProps> = observer(
|
||||||
|
({ application, appState, hideDate, hideIcon, hideTags, item, selected, sortBy, tags }) => {
|
||||||
|
const openFileContextMenu = useCallback(
|
||||||
|
(posX: number, posY: number) => {
|
||||||
|
appState.files.setFileContextMenuLocation({
|
||||||
|
x: posX,
|
||||||
|
y: posY,
|
||||||
|
})
|
||||||
|
appState.files.setShowFileContextMenu(true)
|
||||||
|
},
|
||||||
|
[appState.files],
|
||||||
|
)
|
||||||
|
|
||||||
|
const openContextMenu = useCallback(
|
||||||
|
(posX: number, posY: number) => {
|
||||||
|
void appState.contentListView.selectItemWithScrollHandling(item, {
|
||||||
|
userTriggered: true,
|
||||||
|
scrollIntoView: false,
|
||||||
|
})
|
||||||
|
openFileContextMenu(posX, posY)
|
||||||
|
},
|
||||||
|
[appState.contentListView, item, openFileContextMenu],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
void appState.selectedItems.selectItem(item.uuid, true).then(({ didSelect }) => {
|
||||||
|
if (didSelect && appState.selectedItems.selectedItemsCount < 2) {
|
||||||
|
appState.filePreviewModal.activate(item as FileItem, appState.files.allFiles)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [appState.filePreviewModal, appState.files.allFiles, appState.selectedItems, item])
|
||||||
|
|
||||||
|
const IconComponent = () =>
|
||||||
|
getFileIconComponent(
|
||||||
|
application.iconsController.getIconForFileType((item as FileItem).mimeType),
|
||||||
|
'w-5 h-5 flex-shrink-0',
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`content-list-item flex items-stretch w-full cursor-pointer ${
|
||||||
|
selected && 'selected border-0 border-l-2px border-solid border-info'
|
||||||
|
}`}
|
||||||
|
id={item.uuid}
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
openContextMenu(event.clientX, event.clientY)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!hideIcon ? (
|
||||||
|
<div className="flex flex-col items-center justify-between p-4 pr-3 mr-0">
|
||||||
|
<IconComponent />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="pr-4" />
|
||||||
|
)}
|
||||||
|
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
|
||||||
|
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
|
||||||
|
<div className="break-word mr-2">{item.title}</div>
|
||||||
|
</div>
|
||||||
|
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
|
||||||
|
<ListItemTags hideTags={hideTags} tags={tags} />
|
||||||
|
<ListItemConflictIndicator item={item} />
|
||||||
|
</div>
|
||||||
|
<ListItemFlagIcons item={item} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
|
|
||||||
|
export const ListItemConflictIndicator: FunctionComponent<{
|
||||||
|
item: {
|
||||||
|
conflictOf?: ListableContentItem['conflictOf']
|
||||||
|
}
|
||||||
|
}> = ({ item }) => {
|
||||||
|
return item.conflictOf ? (
|
||||||
|
<div className="flex flex-wrap items-center mt-0.5">
|
||||||
|
<div className={'py-1 px-1.5 rounded mr-1 mt-2 bg-danger color-danger-contrast'}>
|
||||||
|
<div className="text-xs font-bold text-center">Conflicted Copy</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: {
|
||||||
|
locked: ListableContentItem['locked']
|
||||||
|
trashed: ListableContentItem['trashed']
|
||||||
|
archived: ListableContentItem['archived']
|
||||||
|
pinned: ListableContentItem['pinned']
|
||||||
|
}
|
||||||
|
hasFiles?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-start p-4 pl-0 border-0 border-b-1 border-solid border-main">
|
||||||
|
{item.locked && (
|
||||||
|
<span className="flex items-center" title="Editing Disabled">
|
||||||
|
<Icon ariaLabel="Editing Disabled" type="pencil-off" className="sn-icon--small color-info" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{item.trashed && (
|
||||||
|
<span className="flex items-center ml-1.5" title="Trashed">
|
||||||
|
<Icon ariaLabel="Trashed" type="trash-filled" className="sn-icon--small color-danger" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{item.archived && (
|
||||||
|
<span className="flex items-center ml-1.5" title="Archived">
|
||||||
|
<Icon ariaLabel="Archived" type="archive" className="sn-icon--mid color-accessory-tint-3" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{item.pinned && (
|
||||||
|
<span className="flex items-center ml-1.5" title="Pinned">
|
||||||
|
<Icon ariaLabel="Pinned" type="pin-filled" className="sn-icon--small color-info" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{hasFiles && (
|
||||||
|
<span className="flex items-center ml-1.5" title="Files">
|
||||||
|
<Icon ariaLabel="Files" type="attachment-file" className="sn-icon--small color-info" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { CollectionSort, SortableItem } from '@standardnotes/snjs'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: {
|
||||||
|
protected: ListableContentItem['protected']
|
||||||
|
updatedAtString?: ListableContentItem['updatedAtString']
|
||||||
|
createdAtString?: ListableContentItem['createdAtString']
|
||||||
|
}
|
||||||
|
hideDate: boolean
|
||||||
|
sortBy: keyof SortableItem | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sortBy }) => {
|
||||||
|
const showModifiedDate = sortBy === CollectionSort.UpdatedAt
|
||||||
|
|
||||||
|
if (hideDate && !item.protected) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-xs leading-1.4 mt-1 faded">
|
||||||
|
{item.protected && <span>Protected {hideDate ? '' : ' • '}</span>}
|
||||||
|
{!hideDate && showModifiedDate && <span>Modified {item.updatedAtString || 'Now'}</span>}
|
||||||
|
{!hideDate && !showModifiedDate && <span>{item.createdAtString || 'Now'}</span>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
|
|
||||||
|
export const ListItemTags: FunctionComponent<{
|
||||||
|
hideTags: boolean
|
||||||
|
tags: string[]
|
||||||
|
}> = ({ hideTags, tags }) => {
|
||||||
|
if (hideTags || !tags.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap mt-1.5 text-xs gap-2">
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<span className="inline-flex items-center py-1 px-1.5 bg-grey-4-opacity-variant color-foreground rounded-0.5">
|
||||||
|
<Icon type="hashtag" className="sn-icon--small color-grey-1 mr-1" />
|
||||||
|
<span>{tag}</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||||
|
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
|
import { ListItemConflictIndicator } from './ListItemConflictIndicator'
|
||||||
|
import { ListItemFlagIcons } from './ListItemFlagIcons'
|
||||||
|
import { ListItemTags } from './ListItemTags'
|
||||||
|
import { ListItemMetadata } from './ListItemMetadata'
|
||||||
|
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
||||||
|
|
||||||
|
export const NoteListItem: FunctionComponent<DisplayableListItemProps> = observer(
|
||||||
|
({ application, appState, hideDate, hideIcon, hideTags, hidePreview, item, selected, sortBy, tags }) => {
|
||||||
|
const editorForNote = application.componentManager.editorForNote(item as SNNote)
|
||||||
|
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
|
||||||
|
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
|
||||||
|
const hasFiles = application.items.getFilesForNote(item as SNNote).length > 0
|
||||||
|
|
||||||
|
const openNoteContextMenu = (posX: number, posY: number) => {
|
||||||
|
appState.notes.setContextMenuClickLocation({
|
||||||
|
x: posX,
|
||||||
|
y: posY,
|
||||||
|
})
|
||||||
|
appState.notes.reloadContextMenuLayout()
|
||||||
|
appState.notes.setContextMenuOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openContextMenu = (posX: number, posY: number) => {
|
||||||
|
void appState.selectedItems.selectItem(item.uuid, true)
|
||||||
|
openNoteContextMenu(posX, posY)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`content-list-item flex items-stretch w-full cursor-pointer ${
|
||||||
|
selected && 'selected border-0 border-l-2px border-solid border-info'
|
||||||
|
}`}
|
||||||
|
id={item.uuid}
|
||||||
|
onClick={() => {
|
||||||
|
void appState.selectedItems.selectItem(item.uuid, true)
|
||||||
|
}}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
openContextMenu(event.clientX, event.clientY)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!hideIcon ? (
|
||||||
|
<div className="flex flex-col items-center justify-between p-4 pr-3 mr-0">
|
||||||
|
<Icon ariaLabel={`Icon for ${editorName}`} type={icon} className={`color-accessory-tint-${tint}`} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="pr-4" />
|
||||||
|
)}
|
||||||
|
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
|
||||||
|
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
|
||||||
|
<div className="break-word mr-2">{item.title}</div>
|
||||||
|
</div>
|
||||||
|
{!hidePreview && !item.hidePreview && !item.protected && (
|
||||||
|
<div className="overflow-hidden overflow-ellipsis text-sm">
|
||||||
|
{item.preview_html && (
|
||||||
|
<div
|
||||||
|
className="my-1"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: sanitizeHtmlString(item.preview_html),
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
{!item.preview_html && item.preview_plain && (
|
||||||
|
<div className="leading-1.3 overflow-hidden line-clamp-1 mt-1">{item.preview_plain}</div>
|
||||||
|
)}
|
||||||
|
{!item.preview_html && !item.preview_plain && item.text && (
|
||||||
|
<div className="leading-1.3 overflow-hidden line-clamp-1 mt-1">{item.text}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
|
||||||
|
<ListItemTags hideTags={hideTags} tags={tags} />
|
||||||
|
<ListItemConflictIndicator item={item} />
|
||||||
|
</div>
|
||||||
|
<ListItemFlagIcons item={item} hasFiles={hasFiles} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
|
import { AppState } from '@/UIModels/AppState'
|
||||||
|
import { SortableItem } from '@standardnotes/snjs'
|
||||||
|
import { ListableContentItem } from './ListableContentItem'
|
||||||
|
|
||||||
|
export type AbstractListItemProps = {
|
||||||
|
application: WebApplication
|
||||||
|
appState: AppState
|
||||||
|
hideDate: boolean
|
||||||
|
hideIcon: boolean
|
||||||
|
hideTags: boolean
|
||||||
|
hidePreview: boolean
|
||||||
|
item: ListableContentItem
|
||||||
|
selected: boolean
|
||||||
|
sortBy: keyof SortableItem | undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { AbstractListItemProps } from './AbstractListItemProps'
|
||||||
|
|
||||||
|
export type DisplayableListItemProps = AbstractListItemProps & {
|
||||||
|
tags: string[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ContentType, DecryptedItem, ItemContent } from '@standardnotes/snjs'
|
||||||
|
|
||||||
|
export type ListableContentItem = DecryptedItem<ItemContent> & {
|
||||||
|
title: string
|
||||||
|
protected: boolean
|
||||||
|
uuid: string
|
||||||
|
content_type: ContentType
|
||||||
|
updatedAtString?: string
|
||||||
|
createdAtString?: string
|
||||||
|
hidePreview?: boolean
|
||||||
|
preview_html?: string
|
||||||
|
preview_plain?: string
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER } from '@/Constants'
|
||||||
|
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||||
|
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
|
||||||
|
import { AppState } from '@/UIModels/AppState'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||||
|
import React from 'react'
|
||||||
|
import { PopoverFileItemAction } from '../AttachedFilesPopover/PopoverFileItemAction'
|
||||||
|
import { PopoverTabs } from '../AttachedFilesPopover/PopoverTabs'
|
||||||
|
import { FileMenuOptions } from './FileMenuOptions'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appState: AppState
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileContextMenu: FunctionComponent<Props> = observer(({ appState }) => {
|
||||||
|
const { selectedFiles, showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = appState.files
|
||||||
|
|
||||||
|
const [contextMenuStyle, setContextMenuStyle] = useState<React.CSSProperties>({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
visibility: 'hidden',
|
||||||
|
})
|
||||||
|
const [contextMenuMaxHeight, setContextMenuMaxHeight] = useState<number | 'auto'>('auto')
|
||||||
|
const contextMenuRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => setShowFileContextMenu(open))
|
||||||
|
useCloseOnClickOutside(contextMenuRef, () => appState.files.setShowFileContextMenu(false))
|
||||||
|
|
||||||
|
const selectedFile = Object.values(selectedFiles)[0]
|
||||||
|
if (!showFileContextMenu || !selectedFile) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadContextMenuLayout = useCallback(() => {
|
||||||
|
const { clientHeight } = document.documentElement
|
||||||
|
const defaultFontSize = window.getComputedStyle(document.documentElement).fontSize
|
||||||
|
const maxContextMenuHeight = parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER
|
||||||
|
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||||
|
const footerHeightInPx = footerElementRect?.height
|
||||||
|
|
||||||
|
let openUpBottom = true
|
||||||
|
|
||||||
|
if (footerHeightInPx) {
|
||||||
|
const bottomSpace = clientHeight - footerHeightInPx - fileContextMenuLocation.y
|
||||||
|
const upSpace = fileContextMenuLocation.y
|
||||||
|
|
||||||
|
if (maxContextMenuHeight > bottomSpace) {
|
||||||
|
if (upSpace > maxContextMenuHeight) {
|
||||||
|
openUpBottom = false
|
||||||
|
setContextMenuMaxHeight('auto')
|
||||||
|
} else {
|
||||||
|
if (upSpace > bottomSpace) {
|
||||||
|
setContextMenuMaxHeight(upSpace - MENU_MARGIN_FROM_APP_BORDER)
|
||||||
|
openUpBottom = false
|
||||||
|
} else {
|
||||||
|
setContextMenuMaxHeight(bottomSpace - MENU_MARGIN_FROM_APP_BORDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setContextMenuMaxHeight('auto')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openUpBottom) {
|
||||||
|
setContextMenuStyle({
|
||||||
|
top: fileContextMenuLocation.y,
|
||||||
|
left: fileContextMenuLocation.x,
|
||||||
|
visibility: 'visible',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setContextMenuStyle({
|
||||||
|
bottom: clientHeight - fileContextMenuLocation.y,
|
||||||
|
left: fileContextMenuLocation.x,
|
||||||
|
visibility: 'visible',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [fileContextMenuLocation.x, fileContextMenuLocation.y])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showFileContextMenu) {
|
||||||
|
reloadContextMenuLayout()
|
||||||
|
}
|
||||||
|
}, [reloadContextMenuLayout, showFileContextMenu])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('resize', reloadContextMenuLayout)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', reloadContextMenuLayout)
|
||||||
|
}
|
||||||
|
}, [reloadContextMenuLayout])
|
||||||
|
|
||||||
|
const handleFileAction = useCallback(
|
||||||
|
async (action: PopoverFileItemAction) => {
|
||||||
|
const { didHandleAction } = await appState.files.handleFileAction(action, PopoverTabs.AllFiles)
|
||||||
|
return didHandleAction
|
||||||
|
},
|
||||||
|
[appState.files],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={contextMenuRef}
|
||||||
|
className="sn-dropdown min-w-60 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
|
||||||
|
style={{
|
||||||
|
...contextMenuStyle,
|
||||||
|
maxHeight: contextMenuMaxHeight,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileMenuOptions
|
||||||
|
file={selectedFile}
|
||||||
|
handleFileAction={handleFileAction}
|
||||||
|
closeOnBlur={closeOnBlur}
|
||||||
|
closeMenu={() => setShowFileContextMenu(false)}
|
||||||
|
shouldShowRenameOption={false}
|
||||||
|
shouldShowAttachOption={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants'
|
||||||
|
import { FileItem } from '@standardnotes/snjs'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
import { PopoverFileItemAction, PopoverFileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
|
||||||
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
closeMenu: () => void
|
||||||
|
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||||
|
file: FileItem
|
||||||
|
fileProtectionToggleCallback?: (isProtected: boolean) => void
|
||||||
|
handleFileAction: (action: PopoverFileItemAction) => Promise<boolean>
|
||||||
|
isFileAttachedToNote?: boolean
|
||||||
|
renameToggleCallback?: (isRenamingFile: boolean) => void
|
||||||
|
shouldShowRenameOption: boolean
|
||||||
|
shouldShowAttachOption: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileMenuOptions: FunctionComponent<Props> = ({
|
||||||
|
closeMenu,
|
||||||
|
closeOnBlur,
|
||||||
|
file,
|
||||||
|
fileProtectionToggleCallback,
|
||||||
|
handleFileAction,
|
||||||
|
isFileAttachedToNote,
|
||||||
|
renameToggleCallback,
|
||||||
|
shouldShowRenameOption,
|
||||||
|
shouldShowAttachOption,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.PreviewFile,
|
||||||
|
payload: file,
|
||||||
|
}).catch(console.error)
|
||||||
|
closeMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="file" className="mr-2 color-neutral" />
|
||||||
|
Preview file
|
||||||
|
</button>
|
||||||
|
{isFileAttachedToNote ? (
|
||||||
|
<button
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.DetachFileToNote,
|
||||||
|
payload: file,
|
||||||
|
}).catch(console.error)
|
||||||
|
closeMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="link-off" className="mr-2 color-neutral" />
|
||||||
|
Detach from note
|
||||||
|
</button>
|
||||||
|
) : shouldShowAttachOption ? (
|
||||||
|
<button
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.AttachFileToNote,
|
||||||
|
payload: file,
|
||||||
|
}).catch(console.error)
|
||||||
|
closeMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="link" className="mr-2 color-neutral" />
|
||||||
|
Attach to note
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<div className="min-h-1px my-1 bg-border"></div>
|
||||||
|
<button
|
||||||
|
className="sn-dropdown-item justify-between focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.ToggleFileProtection,
|
||||||
|
payload: file,
|
||||||
|
callback: (isProtected: boolean) => {
|
||||||
|
fileProtectionToggleCallback?.(isProtected)
|
||||||
|
},
|
||||||
|
}).catch(console.error)
|
||||||
|
}}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
>
|
||||||
|
<span className="flex items-center">
|
||||||
|
<Icon type="password" className="mr-2 color-neutral" />
|
||||||
|
Password protection
|
||||||
|
</span>
|
||||||
|
<Switch className="px-0 pointer-events-none" tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} checked={file.protected} />
|
||||||
|
</button>
|
||||||
|
<div className="min-h-1px my-1 bg-border"></div>
|
||||||
|
<button
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.DownloadFile,
|
||||||
|
payload: file,
|
||||||
|
}).catch(console.error)
|
||||||
|
closeMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="download" className="mr-2 color-neutral" />
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
{shouldShowRenameOption && (
|
||||||
|
<button
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
renameToggleCallback?.(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="pencil" className="mr-2 color-neutral" />
|
||||||
|
Rename
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.DeleteFile,
|
||||||
|
payload: file,
|
||||||
|
}).catch(console.error)
|
||||||
|
closeMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="trash" className="mr-2 color-danger" />
|
||||||
|
<span className="color-danger">Delete permanently</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ export const FilePreviewModal: FunctionComponent<Props> = observer(({ applicatio
|
|||||||
width: '90%',
|
width: '90%',
|
||||||
maxWidth: '90%',
|
maxWidth: '90%',
|
||||||
minHeight: '90%',
|
minHeight: '90%',
|
||||||
background: 'var(--sn-stylekit-background-color)',
|
background: 'var(--modal-background-color)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { WebAppEvent, WebApplication } from '@/UIModels/Application'
|
|||||||
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
|
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
|
||||||
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
||||||
import { destroyAllObjectProperties, preventRefreshing } from '@/Utils'
|
import { destroyAllObjectProperties, preventRefreshing } from '@/Utils'
|
||||||
import { ApplicationEvent, ContentType, CollectionSort, ApplicationDescriptor } from '@standardnotes/snjs'
|
import { ApplicationEvent, ApplicationDescriptor } from '@standardnotes/snjs'
|
||||||
import {
|
import {
|
||||||
STRING_NEW_UPDATE_READY,
|
STRING_NEW_UPDATE_READY,
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_UPGRADE,
|
STRING_CONFIRM_APP_QUIT_DURING_UPGRADE,
|
||||||
@@ -15,7 +15,7 @@ import { AccountMenu, AccountMenuPane } from '@/Components/AccountMenu/AccountMe
|
|||||||
import { AppStateEvent, EventSource } from '@/UIModels/AppState'
|
import { AppStateEvent, EventSource } from '@/UIModels/AppState'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { QuickSettingsMenu } from '@/Components/QuickSettingsMenu/QuickSettingsMenu'
|
import { QuickSettingsMenu } from '@/Components/QuickSettingsMenu/QuickSettingsMenu'
|
||||||
import { SyncResolutionMenu } from '@/Components/SyncResolutionMenu'
|
import { SyncResolutionMenu } from '@/Components/SyncResolutionMenu/SyncResolutionMenu'
|
||||||
import { Fragment } from 'preact'
|
import { Fragment } from 'preact'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -118,7 +118,6 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
this.reloadUpgradeStatus()
|
this.reloadUpgradeStatus()
|
||||||
this.updateOfflineStatus()
|
this.updateOfflineStatus()
|
||||||
this.findErrors()
|
this.findErrors()
|
||||||
this.streamItems()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadUser() {
|
reloadUser() {
|
||||||
@@ -217,10 +216,6 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
streamItems() {
|
|
||||||
this.application.items.setDisplayOptions(ContentType.Theme, CollectionSort.Title, 'asc')
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSyncStatus() {
|
updateSyncStatus() {
|
||||||
const statusManager = this.application.status
|
const statusManager = this.application.status
|
||||||
const syncStatus = this.application.sync.getSyncStatus()
|
const syncStatus = this.application.sync.getSyncStatus()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ComponentChildren, FunctionComponent, VNode } from 'preact'
|
|||||||
import { forwardRef, Ref } from 'preact/compat'
|
import { forwardRef, Ref } from 'preact/compat'
|
||||||
import { JSXInternal } from 'preact/src/jsx'
|
import { JSXInternal } from 'preact/src/jsx'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { Switch, SwitchProps } from '@/Components/Switch'
|
import { Switch, SwitchProps } from '@/Components/Switch/Switch'
|
||||||
import { IconType } from '@standardnotes/snjs'
|
import { IconType } from '@standardnotes/snjs'
|
||||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants'
|
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants'
|
||||||
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
import { WebApplication } from '@/UIModels/Application'
|
|
||||||
import { KeyboardKey } from '@/Services/IOService'
|
|
||||||
import { AppState } from '@/UIModels/AppState'
|
|
||||||
import { DisplayOptions } from '@/UIModels/AppState/NotesViewState'
|
|
||||||
import { SNNote, SNTag } from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
|
||||||
import { FunctionComponent } from 'preact'
|
|
||||||
import { NotesListItem } from './NotesListItem'
|
|
||||||
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants'
|
|
||||||
import { useCallback } from 'preact/hooks'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
application: WebApplication
|
|
||||||
appState: AppState
|
|
||||||
notes: SNNote[]
|
|
||||||
selectedNotes: Record<string, SNNote>
|
|
||||||
displayOptions: DisplayOptions
|
|
||||||
paginate: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NotesList: FunctionComponent<Props> = observer(
|
|
||||||
({ application, appState, notes, selectedNotes, displayOptions, paginate }) => {
|
|
||||||
const selectNextNote = useCallback(() => appState.notesView.selectNextNote(), [appState])
|
|
||||||
const selectPreviousNote = useCallback(() => appState.notesView.selectPreviousNote(), [appState])
|
|
||||||
|
|
||||||
const { hideTags, hideDate, hideNotePreview, hideEditorIcon, sortBy } = displayOptions
|
|
||||||
|
|
||||||
const tagsForNote = useCallback(
|
|
||||||
(note: SNNote): string[] => {
|
|
||||||
if (hideTags) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const selectedTag = appState.tags.selected
|
|
||||||
if (!selectedTag) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const tags = appState.getNoteTags(note)
|
|
||||||
if (selectedTag instanceof SNTag && tags.length === 1) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return tags.map((tag) => tag.title).sort()
|
|
||||||
},
|
|
||||||
[appState, hideTags],
|
|
||||||
)
|
|
||||||
|
|
||||||
const openNoteContextMenu = useCallback(
|
|
||||||
(posX: number, posY: number) => {
|
|
||||||
appState.notes.setContextMenuClickLocation({
|
|
||||||
x: posX,
|
|
||||||
y: posY,
|
|
||||||
})
|
|
||||||
appState.notes.reloadContextMenuLayout()
|
|
||||||
appState.notes.setContextMenuOpen(true)
|
|
||||||
},
|
|
||||||
[appState],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onContextMenu = useCallback(
|
|
||||||
(note: SNNote, posX: number, posY: number) => {
|
|
||||||
appState.notes.selectNote(note.uuid, true).catch(console.error)
|
|
||||||
openNoteContextMenu(posX, posY)
|
|
||||||
},
|
|
||||||
[appState, openNoteContextMenu],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onScroll = useCallback(
|
|
||||||
(e: Event) => {
|
|
||||||
const offset = NOTES_LIST_SCROLL_THRESHOLD
|
|
||||||
const element = e.target as HTMLElement
|
|
||||||
if (element.scrollTop + element.offsetHeight >= element.scrollHeight - offset) {
|
|
||||||
paginate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[paginate],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onKeyDown = useCallback(
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
if (e.key === KeyboardKey.Up) {
|
|
||||||
e.preventDefault()
|
|
||||||
selectPreviousNote()
|
|
||||||
} else if (e.key === KeyboardKey.Down) {
|
|
||||||
e.preventDefault()
|
|
||||||
selectNextNote()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[selectNextNote, selectPreviousNote],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="infinite-scroll focus:shadow-none focus:outline-none"
|
|
||||||
id="notes-scrollable"
|
|
||||||
onScroll={onScroll}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
|
||||||
>
|
|
||||||
{notes.map((note) => (
|
|
||||||
<NotesListItem
|
|
||||||
application={application}
|
|
||||||
key={note.uuid}
|
|
||||||
note={note}
|
|
||||||
tags={tagsForNote(note)}
|
|
||||||
selected={!!selectedNotes[note.uuid]}
|
|
||||||
hideDate={hideDate}
|
|
||||||
hidePreview={hideNotePreview}
|
|
||||||
hideTags={hideTags}
|
|
||||||
hideEditorIcon={hideEditorIcon}
|
|
||||||
sortedBy={sortBy}
|
|
||||||
onClick={() => {
|
|
||||||
appState.notes.selectNote(note.uuid, true).catch(console.error)
|
|
||||||
}}
|
|
||||||
onContextMenu={(e: MouseEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
onContextMenu(note, e.clientX, e.clientY)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import { WebApplication } from '@/UIModels/Application'
|
|
||||||
import { CollectionSort, CollectionSortProperty, sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
|
|
||||||
import { FunctionComponent } from 'preact'
|
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
|
||||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
application: WebApplication
|
|
||||||
note: SNNote
|
|
||||||
tags: string[]
|
|
||||||
hideDate: boolean
|
|
||||||
hidePreview: boolean
|
|
||||||
hideTags: boolean
|
|
||||||
hideEditorIcon: boolean
|
|
||||||
onClick: () => void
|
|
||||||
onContextMenu: (e: MouseEvent) => void
|
|
||||||
selected: boolean
|
|
||||||
sortedBy?: CollectionSortProperty
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoteFlag = {
|
|
||||||
text: string
|
|
||||||
class: 'info' | 'neutral' | 'warning' | 'success' | 'danger'
|
|
||||||
}
|
|
||||||
|
|
||||||
const flagsForNote = (note: SNNote) => {
|
|
||||||
const flags = [] as NoteFlag[]
|
|
||||||
if (note.conflictOf) {
|
|
||||||
flags.push({
|
|
||||||
text: 'Conflicted Copy',
|
|
||||||
class: 'danger',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NotesListItem: FunctionComponent<Props> = ({
|
|
||||||
application,
|
|
||||||
hideDate,
|
|
||||||
hidePreview,
|
|
||||||
hideTags,
|
|
||||||
hideEditorIcon,
|
|
||||||
note,
|
|
||||||
onClick,
|
|
||||||
onContextMenu,
|
|
||||||
selected,
|
|
||||||
sortedBy,
|
|
||||||
tags,
|
|
||||||
}) => {
|
|
||||||
const flags = flagsForNote(note)
|
|
||||||
const hasFiles = application.items.getFilesForNote(note).length > 0
|
|
||||||
const showModifiedDate = sortedBy === CollectionSort.UpdatedAt
|
|
||||||
const editorForNote = application.componentManager.editorForNote(note)
|
|
||||||
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
|
|
||||||
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`note ${selected ? 'selected' : ''}`}
|
|
||||||
id={`note-${note.uuid}`}
|
|
||||||
onClick={onClick}
|
|
||||||
onContextMenu={onContextMenu}
|
|
||||||
>
|
|
||||||
{!hideEditorIcon && (
|
|
||||||
<div className="icon">
|
|
||||||
<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>
|
|
||||||
{!hidePreview && !note.hidePreview && !note.protected && (
|
|
||||||
<div className="note-preview">
|
|
||||||
{note.preview_html && (
|
|
||||||
<div
|
|
||||||
className="html-preview"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: sanitizeHtmlString(note.preview_html),
|
|
||||||
}}
|
|
||||||
></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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!hideDate || note.protected ? (
|
|
||||||
<div className="bottom-info faded">
|
|
||||||
{note.protected && <span>Protected {hideDate ? '' : ' • '}</span>}
|
|
||||||
{!hideDate && showModifiedDate && <span>Modified {note.updatedAtString || 'Now'}</span>}
|
|
||||||
{!hideDate && !showModifiedDate && <span>{note.createdAtString || 'Now'}</span>}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{!hideTags && tags.length ? (
|
|
||||||
<div className="tags-string">
|
|
||||||
{tags.map((tag) => (
|
|
||||||
<span className="tag color-foreground">
|
|
||||||
<Icon type="hashtag" className="sn-icon--small color-grey-1 mr-1" />
|
|
||||||
<span>{tag}</span>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{flags.length ? (
|
|
||||||
<div className="note-flags flex flex-wrap">
|
|
||||||
{flags.map((flag) => (
|
|
||||||
<div className={`flag ${flag.class}`}>
|
|
||||||
<div className="label">{flag.text}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className="flag-icons">
|
|
||||||
{note.locked && (
|
|
||||||
<span title="Editing Disabled">
|
|
||||||
<Icon ariaLabel="Editing Disabled" type="pencil-off" className="sn-icon--small color-info" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{note.trashed && (
|
|
||||||
<span title="Trashed">
|
|
||||||
<Icon ariaLabel="Trashed" type="trash-filled" className="sn-icon--small color-danger" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{note.archived && (
|
|
||||||
<span title="Archived">
|
|
||||||
<Icon ariaLabel="Archived" type="archive" className="sn-icon--mid color-accessory-tint-3" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{note.pinned && (
|
|
||||||
<span title="Pinned">
|
|
||||||
<Icon ariaLabel="Pinned" type="pin-filled" className="sn-icon--small color-info" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{hasFiles && (
|
|
||||||
<span title="Files">
|
|
||||||
<Icon ariaLabel="Files" type="attachment-file" className="sn-icon--small color-info" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useState, useEffect, useMemo, useCallback } from 'preact/hooks'
|
import { useState, useEffect, useMemo, useCallback } from 'preact/hooks'
|
||||||
import { SNApplication, SNNote } from '@standardnotes/snjs'
|
import { SNApplication, SNNote } from '@standardnotes/snjs'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
|
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
@@ -43,8 +43,9 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const themesAsItems: DropdownItem[] = application.items
|
const themesAsItems: DropdownItem[] = application.items
|
||||||
.getDisplayableItems<SNTheme>(ContentType.Theme)
|
.getDisplayableComponents()
|
||||||
.filter((theme) => !theme.isLayerable())
|
.filter((component) => component.isTheme())
|
||||||
|
.filter((component) => !(component as SNTheme).isLayerable())
|
||||||
.sort(sortThemes)
|
.sort(sortThemes)
|
||||||
.map((theme) => {
|
.map((theme) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
|
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { convertStringifiedBooleanToBoolean } from '@/Utils'
|
import { convertStringifiedBooleanToBoolean } from '@/Utils'
|
||||||
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
} from '@/Components/Preferences/PreferencesComponents'
|
||||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import {
|
import {
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@/Components/Preferences/PreferencesComponents'
|
} from '@/Components/Preferences/PreferencesComponents'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import { EncryptionStatusItem } from '../../Security/Encryption'
|
import { EncryptionStatusItem } from '../../Security/Encryption'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { SNComponent } from '@standardnotes/snjs'
|
import { SNComponent } from '@standardnotes/snjs'
|
||||||
import { PreferencesSegment, SubtitleLight } from '@/Components/Preferences/PreferencesComponents'
|
import { PreferencesSegment, SubtitleLight } from '@/Components/Preferences/PreferencesComponents'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { useState } from 'preact/hooks'
|
import { useState } from 'preact/hooks'
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { WebApplication } from '@/UIModels/Application'
|
|||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
import { PLAIN_EDITOR_NAME } from '@/Constants'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import {
|
import {
|
||||||
PreferencesGroup,
|
PreferencesGroup,
|
||||||
PreferencesSegment,
|
PreferencesSegment,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import {
|
import {
|
||||||
PreferencesGroup,
|
PreferencesGroup,
|
||||||
PreferencesSegment,
|
PreferencesSegment,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import {
|
import {
|
||||||
PreferencesGroup,
|
PreferencesGroup,
|
||||||
PreferencesSegment,
|
PreferencesSegment,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
|
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
|
||||||
import { TwoFactorActivationView } from './TwoFactorActivationView'
|
import { TwoFactorActivationView } from './TwoFactorActivationView'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useCallback } from 'preact/hooks'
|
|||||||
import { JSXInternal } from 'preact/src/jsx'
|
import { JSXInternal } from 'preact/src/jsx'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||||
import { ComponentArea, ContentType, FeatureIdentifier, GetFeatures, SNComponent, SNTheme } from '@standardnotes/snjs'
|
import { ComponentArea, ContentType, FeatureIdentifier, GetFeatures, SNComponent } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||||
import { JSXInternal } from 'preact/src/jsx'
|
import { JSXInternal } from 'preact/src/jsx'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||||
import { quickSettingsKeyDownHandler, themesMenuKeyDownHandler } from './EventHandlers'
|
import { quickSettingsKeyDownHandler, themesMenuKeyDownHandler } from './EventHandlers'
|
||||||
import { FocusModeSwitch } from './FocusModeSwitch'
|
import { FocusModeSwitch } from './FocusModeSwitch'
|
||||||
@@ -18,7 +18,7 @@ import { sortThemes } from '@/Utils/SortThemes'
|
|||||||
|
|
||||||
const focusModeAnimationDuration = 1255
|
const focusModeAnimationDuration = 1255
|
||||||
|
|
||||||
const MENU_CLASSNAME = 'sn-menu-border sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto'
|
const MENU_CLASSNAME = 'sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto'
|
||||||
|
|
||||||
type MenuProps = {
|
type MenuProps = {
|
||||||
appState: AppState
|
appState: AppState
|
||||||
@@ -65,7 +65,10 @@ export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(({ appli
|
|||||||
}, [focusModeEnabled])
|
}, [focusModeEnabled])
|
||||||
|
|
||||||
const reloadThemes = useCallback(() => {
|
const reloadThemes = useCallback(() => {
|
||||||
const themes = application.items.getDisplayableItems<SNTheme>(ContentType.Theme).map((item) => {
|
const themes = application.items
|
||||||
|
.getDisplayableComponents()
|
||||||
|
.filter((component) => component.isTheme())
|
||||||
|
.map((item) => {
|
||||||
return {
|
return {
|
||||||
name: item.displayName,
|
name: item.displayName,
|
||||||
identifier: item.identifier,
|
identifier: item.identifier,
|
||||||
@@ -91,9 +94,10 @@ export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(({ appli
|
|||||||
|
|
||||||
const reloadToggleableComponents = useCallback(() => {
|
const reloadToggleableComponents = useCallback(() => {
|
||||||
const toggleableComponents = application.items
|
const toggleableComponents = application.items
|
||||||
.getDisplayableItems<SNComponent>(ContentType.Component)
|
.getDisplayableComponents()
|
||||||
.filter(
|
.filter(
|
||||||
(component) =>
|
(component) =>
|
||||||
|
!component.isTheme() &&
|
||||||
[ComponentArea.EditorStack].includes(component.area) &&
|
[ComponentArea.EditorStack].includes(component.area) &&
|
||||||
component.identifier !== FeatureIdentifier.DeprecatedFoldersComponent,
|
component.identifier !== FeatureIdentifier.DeprecatedFoldersComponent,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useCallback, useMemo } from 'preact/hooks'
|
|||||||
import { JSXInternal } from 'preact/src/jsx'
|
import { JSXInternal } from 'preact/src/jsx'
|
||||||
import { Icon } from '@/Components/Icon/Icon'
|
import { Icon } from '@/Components/Icon/Icon'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch/Switch'
|
||||||
import { ThemeItem } from './ThemeItem'
|
import { ThemeItem } from './ThemeItem'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -57,13 +57,17 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
({ application, appState }) => {
|
({ application, appState }) => {
|
||||||
const closeButtonRef = useRef<HTMLButtonElement>(null)
|
const closeButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
const dismissModal = () => {
|
const dismissModal = useCallback(() => {
|
||||||
appState.notes.setShowRevisionHistoryModal(false)
|
appState.notes.setShowRevisionHistoryModal(false)
|
||||||
}
|
}, [appState.notes])
|
||||||
|
|
||||||
const note = Object.values(appState.notes.selectedNotes)[0]
|
const note = appState.notes.firstSelectedNote
|
||||||
const editorForCurrentNote = useMemo(() => {
|
const editorForCurrentNote = useMemo(() => {
|
||||||
|
if (note) {
|
||||||
return application.componentManager.editorForNote(note)
|
return application.componentManager.editorForNote(note)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}, [application, note])
|
}, [application, note])
|
||||||
|
|
||||||
const [isFetchingSelectedRevision, setIsFetchingSelectedRevision] = useState(false)
|
const [isFetchingSelectedRevision, setIsFetchingSelectedRevision] = useState(false)
|
||||||
@@ -100,7 +104,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
}
|
}
|
||||||
}, [fetchRemoteHistory, remoteHistory?.length])
|
}, [fetchRemoteHistory, remoteHistory?.length])
|
||||||
|
|
||||||
const restore = () => {
|
const restore = useCallback(() => {
|
||||||
if (selectedRevision) {
|
if (selectedRevision) {
|
||||||
const originalNote = application.items.findItem(selectedRevision.payload.uuid) as SNNote
|
const originalNote = application.items.findItem(selectedRevision.payload.uuid) as SNNote
|
||||||
|
|
||||||
@@ -130,9 +134,9 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
})
|
})
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
}
|
}, [application.alertService, application.items, application.mutator, dismissModal, selectedRevision])
|
||||||
|
|
||||||
const restoreAsCopy = async () => {
|
const restoreAsCopy = useCallback(async () => {
|
||||||
if (selectedRevision) {
|
if (selectedRevision) {
|
||||||
const originalNote = application.items.findSureItem<SNNote>(selectedRevision.payload.uuid)
|
const originalNote = application.items.findSureItem<SNNote>(selectedRevision.payload.uuid)
|
||||||
|
|
||||||
@@ -143,11 +147,11 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
appState.notes.selectNote(duplicatedItem.uuid).catch(console.error)
|
appState.selectedItems.selectItem(duplicatedItem.uuid).catch(console.error)
|
||||||
|
|
||||||
dismissModal()
|
dismissModal()
|
||||||
}
|
}
|
||||||
}
|
}, [appState.selectedItems, application.items, application.mutator, dismissModal, selectedRevision])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTemplateNote = async () => {
|
const fetchTemplateNote = async () => {
|
||||||
@@ -164,7 +168,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
fetchTemplateNote().catch(console.error)
|
fetchTemplateNote().catch(console.error)
|
||||||
}, [application, selectedRevision])
|
}, [application, selectedRevision])
|
||||||
|
|
||||||
const deleteSelectedRevision = () => {
|
const deleteSelectedRevision = useCallback(() => {
|
||||||
if (!selectedRemoteEntry) {
|
if (!selectedRemoteEntry) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -178,7 +182,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
'Cancel',
|
'Cancel',
|
||||||
)
|
)
|
||||||
.then((shouldDelete) => {
|
.then((shouldDelete) => {
|
||||||
if (shouldDelete) {
|
if (shouldDelete && note) {
|
||||||
setIsDeletingRevision(true)
|
setIsDeletingRevision(true)
|
||||||
|
|
||||||
application.historyManager
|
application.historyManager
|
||||||
@@ -195,7 +199,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}, [application.alertService, application.historyManager, fetchRemoteHistory, note, selectedRemoteEntry])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogOverlay
|
<DialogOverlay
|
||||||
@@ -210,7 +214,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
width: '90%',
|
width: '90%',
|
||||||
maxWidth: '90%',
|
maxWidth: '90%',
|
||||||
minHeight: '90%',
|
minHeight: '90%',
|
||||||
background: 'var(--sn-stylekit-background-color)',
|
background: 'var(--modal-background-color)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -219,6 +223,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-grow min-h-0">
|
<div className="flex flex-grow min-h-0">
|
||||||
|
{note && (
|
||||||
<HistoryListContainer
|
<HistoryListContainer
|
||||||
application={application}
|
application={application}
|
||||||
note={note}
|
note={note}
|
||||||
@@ -229,6 +234,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
setShowContentLockedScreen={setShowContentLockedScreen}
|
setShowContentLockedScreen={setShowContentLockedScreen}
|
||||||
setIsFetchingSelectedRevision={setIsFetchingSelectedRevision}
|
setIsFetchingSelectedRevision={setIsFetchingSelectedRevision}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<div className={'flex flex-col flex-grow relative'}>
|
<div className={'flex flex-col flex-grow relative'}>
|
||||||
<RevisionContentPlaceholder
|
<RevisionContentPlaceholder
|
||||||
selectedRevision={selectedRevision}
|
selectedRevision={selectedRevision}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ const smartViewIconType = (view: SmartView): IconType => {
|
|||||||
if (view.uuid === SystemViewId.AllNotes) {
|
if (view.uuid === SystemViewId.AllNotes) {
|
||||||
return 'notes'
|
return 'notes'
|
||||||
}
|
}
|
||||||
|
if (view.uuid === SystemViewId.Files) {
|
||||||
|
return 'file'
|
||||||
|
}
|
||||||
if (view.uuid === SystemViewId.ArchivedNotes) {
|
if (view.uuid === SystemViewId.ArchivedNotes) {
|
||||||
return 'archive'
|
return 'archive'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,9 @@ export class ThemeManager extends ApplicationService {
|
|||||||
private setThemeAsPerColorScheme(prefersDarkColorScheme: boolean) {
|
private setThemeAsPerColorScheme(prefersDarkColorScheme: boolean) {
|
||||||
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
||||||
|
|
||||||
const themes = this.application.items.getDisplayableItems(ContentType.Theme) as SNTheme[]
|
const themes = this.application.items
|
||||||
|
.getDisplayableComponents()
|
||||||
|
.filter((component) => component.isTheme()) as SNTheme[]
|
||||||
|
|
||||||
const activeTheme = themes.find((theme) => theme.active && !theme.isLayerable())
|
const activeTheme = themes.find((theme) => theme.active && !theme.isLayerable())
|
||||||
const activeThemeIdentifier = activeTheme ? activeTheme.identifier : DefaultThemeIdentifier
|
const activeThemeIdentifier = activeTheme ? activeTheme.identifier : DefaultThemeIdentifier
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ContentType,
|
ContentType,
|
||||||
DeinitSource,
|
DeinitSource,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
SNNote,
|
|
||||||
SNTag,
|
SNTag,
|
||||||
removeFromArray,
|
removeFromArray,
|
||||||
WebOrDesktopDeviceInterface,
|
WebOrDesktopDeviceInterface,
|
||||||
@@ -17,7 +16,7 @@ import { ActionsMenuState } from './ActionsMenuState'
|
|||||||
import { FeaturesState } from './FeaturesState'
|
import { FeaturesState } from './FeaturesState'
|
||||||
import { FilesState } from './FilesState'
|
import { FilesState } from './FilesState'
|
||||||
import { NotesState } from './NotesState'
|
import { NotesState } from './NotesState'
|
||||||
import { NotesViewState } from './NotesViewState'
|
import { ContentListViewState } from './ContentListViewState'
|
||||||
import { NoteTagsState } from './NoteTagsState'
|
import { NoteTagsState } from './NoteTagsState'
|
||||||
import { NoAccountWarningState } from './NoAccountWarningState'
|
import { NoAccountWarningState } from './NoAccountWarningState'
|
||||||
import { PreferencesState } from './PreferencesState'
|
import { PreferencesState } from './PreferencesState'
|
||||||
@@ -29,6 +28,8 @@ import { SyncState } from './SyncState'
|
|||||||
import { TagsState } from './TagsState'
|
import { TagsState } from './TagsState'
|
||||||
import { FilePreviewModalState } from './FilePreviewModalState'
|
import { FilePreviewModalState } from './FilePreviewModalState'
|
||||||
import { AbstractState } from './AbstractState'
|
import { AbstractState } from './AbstractState'
|
||||||
|
import { SelectedItemsState } from './SelectedItemsState'
|
||||||
|
import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem'
|
||||||
|
|
||||||
export enum AppStateEvent {
|
export enum AppStateEvent {
|
||||||
TagChanged,
|
TagChanged,
|
||||||
@@ -70,7 +71,7 @@ export class AppState extends AbstractState {
|
|||||||
readonly files: FilesState
|
readonly files: FilesState
|
||||||
readonly noAccountWarning: NoAccountWarningState
|
readonly noAccountWarning: NoAccountWarningState
|
||||||
readonly notes: NotesState
|
readonly notes: NotesState
|
||||||
readonly notesView: NotesViewState
|
readonly contentListView: ContentListViewState
|
||||||
readonly noteTags: NoteTagsState
|
readonly noteTags: NoteTagsState
|
||||||
readonly preferences = new PreferencesState()
|
readonly preferences = new PreferencesState()
|
||||||
readonly purchaseFlow: PurchaseFlowState
|
readonly purchaseFlow: PurchaseFlowState
|
||||||
@@ -79,6 +80,7 @@ export class AppState extends AbstractState {
|
|||||||
readonly subscription: SubscriptionState
|
readonly subscription: SubscriptionState
|
||||||
readonly sync = new SyncState()
|
readonly sync = new SyncState()
|
||||||
readonly tags: TagsState
|
readonly tags: TagsState
|
||||||
|
readonly selectedItems: SelectedItemsState
|
||||||
|
|
||||||
isSessionsModalVisible = false
|
isSessionsModalVisible = false
|
||||||
|
|
||||||
@@ -89,6 +91,7 @@ export class AppState extends AbstractState {
|
|||||||
constructor(application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
constructor(application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
||||||
super(application)
|
super(application)
|
||||||
|
|
||||||
|
this.selectedItems = new SelectedItemsState(application, this, this.appEventObserverRemovers)
|
||||||
this.notes = new NotesState(
|
this.notes = new NotesState(
|
||||||
application,
|
application,
|
||||||
this,
|
this,
|
||||||
@@ -106,8 +109,8 @@ export class AppState extends AbstractState {
|
|||||||
this.searchOptions = new SearchOptionsState(application, this.appEventObserverRemovers)
|
this.searchOptions = new SearchOptionsState(application, this.appEventObserverRemovers)
|
||||||
this.subscription = new SubscriptionState(application, this.appEventObserverRemovers)
|
this.subscription = new SubscriptionState(application, this.appEventObserverRemovers)
|
||||||
this.purchaseFlow = new PurchaseFlowState(application)
|
this.purchaseFlow = new PurchaseFlowState(application)
|
||||||
this.notesView = new NotesViewState(application, this, this.appEventObserverRemovers)
|
this.contentListView = new ContentListViewState(application, this, this.appEventObserverRemovers)
|
||||||
this.files = new FilesState(application)
|
this.files = new FilesState(application, this, this.appEventObserverRemovers)
|
||||||
this.addAppEventObserver()
|
this.addAppEventObserver()
|
||||||
this.onVisibilityChange = () => {
|
this.onVisibilityChange = () => {
|
||||||
const visible = document.visibilityState === 'visible'
|
const visible = document.visibilityState === 'visible'
|
||||||
@@ -177,8 +180,8 @@ export class AppState extends AbstractState {
|
|||||||
this.notes.deinit(source)
|
this.notes.deinit(source)
|
||||||
;(this.notes as unknown) = undefined
|
;(this.notes as unknown) = undefined
|
||||||
|
|
||||||
this.notesView.deinit(source)
|
this.contentListView.deinit(source)
|
||||||
;(this.notesView as unknown) = undefined
|
;(this.contentListView as unknown) = undefined
|
||||||
|
|
||||||
this.noteTags.deinit(source)
|
this.noteTags.deinit(source)
|
||||||
;(this.noteTags as unknown) = undefined
|
;(this.noteTags as unknown) = undefined
|
||||||
@@ -321,8 +324,8 @@ export class AppState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the tags that are referncing this note */
|
/** Returns the tags that are referncing this note */
|
||||||
public getNoteTags(note: SNNote) {
|
public getItemTags(item: ListableContentItem) {
|
||||||
return this.application.items.itemsReferencingItem(note).filter((ref) => {
|
return this.application.items.itemsReferencingItem(item).filter((ref) => {
|
||||||
return ref.content_type === ContentType.Tag
|
return ref.content_type === ContentType.Tag
|
||||||
}) as SNTag[]
|
}) as SNTag[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,64 @@
|
|||||||
|
import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem'
|
||||||
import { destroyAllObjectProperties } from '@/Utils'
|
import { destroyAllObjectProperties } from '@/Utils'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
CollectionSort,
|
CollectionSort,
|
||||||
CollectionSortProperty,
|
|
||||||
ContentType,
|
ContentType,
|
||||||
DeinitSource,
|
DeinitSource,
|
||||||
findInArray,
|
findInArray,
|
||||||
NotesDisplayCriteria,
|
|
||||||
NoteViewController,
|
NoteViewController,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
SmartView,
|
SmartView,
|
||||||
SNNote,
|
SNNote,
|
||||||
SNTag,
|
SNTag,
|
||||||
SystemViewId,
|
SystemViewId,
|
||||||
|
DisplayOptions,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
||||||
import { AppState, AppStateEvent } from '.'
|
import { AppState, AppStateEvent } from '.'
|
||||||
import { WebApplication } from '../Application'
|
import { WebApplication } from '../Application'
|
||||||
import { AbstractState } from './AbstractState'
|
import { AbstractState } from './AbstractState'
|
||||||
|
import { WebDisplayOptions } from './WebDisplayOptions'
|
||||||
|
|
||||||
const MIN_NOTE_CELL_HEIGHT = 51.0
|
const MinNoteCellHeight = 51.0
|
||||||
const DEFAULT_LIST_NUM_NOTES = 20
|
const DefaultListNumNotes = 20
|
||||||
const ELEMENT_ID_SEARCH_BAR = 'search-bar'
|
const ElementIdSearchBar = 'search-bar'
|
||||||
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable'
|
const ElementIdScrollContainer = 'notes-scrollable'
|
||||||
|
const SupportsFileSelectionState = false
|
||||||
|
|
||||||
export type DisplayOptions = {
|
export class ContentListViewState extends AbstractState {
|
||||||
sortBy: CollectionSortProperty
|
|
||||||
sortReverse: boolean
|
|
||||||
hidePinned: boolean
|
|
||||||
showArchived: boolean
|
|
||||||
showTrashed: boolean
|
|
||||||
hideProtected: boolean
|
|
||||||
hideTags: boolean
|
|
||||||
hideNotePreview: boolean
|
|
||||||
hideDate: boolean
|
|
||||||
hideEditorIcon: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotesViewState extends AbstractState {
|
|
||||||
completedFullSync = false
|
completedFullSync = false
|
||||||
noteFilterText = ''
|
noteFilterText = ''
|
||||||
notes: SNNote[] = []
|
notes: SNNote[] = []
|
||||||
|
items: ListableContentItem[] = []
|
||||||
notesToDisplay = 0
|
notesToDisplay = 0
|
||||||
pageSize = 0
|
pageSize = 0
|
||||||
panelTitle = 'All Notes'
|
panelTitle = 'All Notes'
|
||||||
panelWidth = 0
|
panelWidth = 0
|
||||||
renderedNotes: SNNote[] = []
|
renderedItems: ListableContentItem[] = []
|
||||||
searchSubmitted = false
|
searchSubmitted = false
|
||||||
showDisplayOptionsMenu = false
|
showDisplayOptionsMenu = false
|
||||||
displayOptions = {
|
displayOptions: DisplayOptions = {
|
||||||
sortBy: CollectionSort.CreatedAt,
|
sortBy: CollectionSort.CreatedAt,
|
||||||
sortReverse: false,
|
sortDirection: 'dsc',
|
||||||
hidePinned: false,
|
includePinned: true,
|
||||||
showArchived: false,
|
includeArchived: false,
|
||||||
showTrashed: false,
|
includeTrashed: false,
|
||||||
hideProtected: false,
|
includeProtected: true,
|
||||||
|
}
|
||||||
|
webDisplayOptions: WebDisplayOptions = {
|
||||||
hideTags: true,
|
hideTags: true,
|
||||||
hideDate: false,
|
hideDate: false,
|
||||||
hideNotePreview: false,
|
hideNotePreview: false,
|
||||||
hideEditorIcon: false,
|
hideEditorIcon: false,
|
||||||
}
|
}
|
||||||
private reloadNotesPromise?: Promise<unknown>
|
private reloadItemsPromise?: Promise<unknown>
|
||||||
|
|
||||||
override deinit(source: DeinitSource) {
|
override deinit(source: DeinitSource) {
|
||||||
super.deinit(source)
|
super.deinit(source)
|
||||||
;(this.noteFilterText as unknown) = undefined
|
;(this.noteFilterText as unknown) = undefined
|
||||||
;(this.notes as unknown) = undefined
|
;(this.notes as unknown) = undefined
|
||||||
;(this.renderedNotes as unknown) = undefined
|
;(this.renderedItems as unknown) = undefined
|
||||||
;(window.onresize as unknown) = undefined
|
;(window.onresize as unknown) = undefined
|
||||||
|
|
||||||
destroyAllObjectProperties(this)
|
destroyAllObjectProperties(this)
|
||||||
@@ -79,7 +71,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
|
|
||||||
appObservers.push(
|
appObservers.push(
|
||||||
application.streamItems<SNNote>(ContentType.Note, () => {
|
application.streamItems<SNNote>(ContentType.Note, () => {
|
||||||
void this.reloadNotes()
|
void this.reloadItems()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
application.streamItems<SNTag>([ContentType.Tag], async ({ changed, inserted }) => {
|
application.streamItems<SNTag>([ContentType.Tag], async ({ changed, inserted }) => {
|
||||||
@@ -88,7 +80,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
/** A tag could have changed its relationships, so we need to reload the filter */
|
/** A tag could have changed its relationships, so we need to reload the filter */
|
||||||
this.reloadNotesDisplayOptions()
|
this.reloadNotesDisplayOptions()
|
||||||
|
|
||||||
void this.reloadNotes()
|
void this.reloadItems()
|
||||||
|
|
||||||
if (appState.tags.selected && findInArray(tags, 'uuid', appState.tags.selected.uuid)) {
|
if (appState.tags.selected && findInArray(tags, 'uuid', appState.tags.selected.uuid)) {
|
||||||
/** Tag title could have changed */
|
/** Tag title could have changed */
|
||||||
@@ -100,11 +92,11 @@ export class NotesViewState extends AbstractState {
|
|||||||
}, ApplicationEvent.PreferencesChanged),
|
}, ApplicationEvent.PreferencesChanged),
|
||||||
application.addEventObserver(async () => {
|
application.addEventObserver(async () => {
|
||||||
this.application.noteControllerGroup.closeAllNoteControllers()
|
this.application.noteControllerGroup.closeAllNoteControllers()
|
||||||
void this.selectFirstNote()
|
void this.selectFirstItem()
|
||||||
this.setCompletedFullSync(false)
|
this.setCompletedFullSync(false)
|
||||||
}, ApplicationEvent.SignedIn),
|
}, ApplicationEvent.SignedIn),
|
||||||
application.addEventObserver(async () => {
|
application.addEventObserver(async () => {
|
||||||
void this.reloadNotes().then(() => {
|
void this.reloadItems().then(() => {
|
||||||
if (
|
if (
|
||||||
this.notes.length === 0 &&
|
this.notes.length === 0 &&
|
||||||
appState.tags.selected instanceof SmartView &&
|
appState.tags.selected instanceof SmartView &&
|
||||||
@@ -126,7 +118,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
this.reloadNotesDisplayOptions()
|
this.reloadNotesDisplayOptions()
|
||||||
void this.reloadNotes()
|
void this.reloadItems()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -144,14 +136,15 @@ export class NotesViewState extends AbstractState {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
completedFullSync: observable,
|
completedFullSync: observable,
|
||||||
displayOptions: observable.struct,
|
displayOptions: observable.struct,
|
||||||
|
webDisplayOptions: observable.struct,
|
||||||
noteFilterText: observable,
|
noteFilterText: observable,
|
||||||
notes: observable,
|
notes: observable,
|
||||||
notesToDisplay: observable,
|
notesToDisplay: observable,
|
||||||
panelTitle: observable,
|
panelTitle: observable,
|
||||||
renderedNotes: observable,
|
renderedItems: observable,
|
||||||
showDisplayOptionsMenu: observable,
|
showDisplayOptionsMenu: observable,
|
||||||
|
|
||||||
reloadNotes: action,
|
reloadItems: action,
|
||||||
reloadPanelTitle: action,
|
reloadPanelTitle: action,
|
||||||
reloadPreferences: action,
|
reloadPreferences: action,
|
||||||
resetPagination: action,
|
resetPagination: action,
|
||||||
@@ -186,7 +179,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get searchBarElement() {
|
get searchBarElement() {
|
||||||
return document.getElementById(ELEMENT_ID_SEARCH_BAR)
|
return document.getElementById(ElementIdSearchBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFiltering(): boolean {
|
get isFiltering(): boolean {
|
||||||
@@ -206,17 +199,17 @@ export class NotesViewState extends AbstractState {
|
|||||||
this.panelTitle = title
|
this.panelTitle = title
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadNotes = async (): Promise<void> => {
|
reloadItems = async (): Promise<void> => {
|
||||||
if (this.reloadNotesPromise) {
|
if (this.reloadItemsPromise) {
|
||||||
await this.reloadNotesPromise
|
await this.reloadItemsPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reloadNotesPromise = this.performReloadNotes()
|
this.reloadItemsPromise = this.performReloadItems()
|
||||||
|
|
||||||
await this.reloadNotesPromise
|
await this.reloadItemsPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
private async performReloadNotes() {
|
private async performReloadItems() {
|
||||||
const tag = this.appState.tags.selected
|
const tag = this.appState.tags.selected
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
return
|
return
|
||||||
@@ -224,32 +217,43 @@ export class NotesViewState extends AbstractState {
|
|||||||
|
|
||||||
const notes = this.application.items.getDisplayableNotes()
|
const notes = this.application.items.getDisplayableNotes()
|
||||||
|
|
||||||
const renderedNotes = notes.slice(0, this.notesToDisplay)
|
const items = this.application.items.getDisplayableNotesAndFiles()
|
||||||
|
|
||||||
this.notes = notes
|
const renderedItems = items.slice(0, this.notesToDisplay)
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.renderedNotes = renderedNotes
|
this.notes = notes
|
||||||
|
this.items = items
|
||||||
|
this.renderedItems = renderedItems
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.recomputeSelectionAfterNotesReload()
|
await this.recomputeSelectionAfterItemsReload()
|
||||||
|
|
||||||
this.reloadPanelTitle()
|
this.reloadPanelTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async recomputeSelectionAfterNotesReload() {
|
private async recomputeSelectionAfterItemsReload() {
|
||||||
const appState = this.appState
|
const appState = this.appState
|
||||||
const activeController = this.getActiveNoteController()
|
const activeController = this.getActiveNoteController()
|
||||||
const activeNote = activeController?.note
|
const activeNote = activeController?.note
|
||||||
const isSearching = this.noteFilterText.length > 0
|
const isSearching = this.noteFilterText.length > 0
|
||||||
const hasMultipleNotesSelected = appState.notes.selectedNotesCount >= 2
|
const hasMultipleItemsSelected = appState.selectedItems.selectedItemsCount >= 2
|
||||||
|
|
||||||
if (hasMultipleNotesSelected) {
|
if (hasMultipleItemsSelected) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedItem = Object.values(appState.selectedItems.selectedItems)[0]
|
||||||
|
|
||||||
|
const isSelectedItemFile =
|
||||||
|
this.items.includes(selectedItem) && selectedItem && selectedItem.content_type === ContentType.File
|
||||||
|
|
||||||
|
if (isSelectedItemFile && !SupportsFileSelectionState) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeNote) {
|
if (!activeNote) {
|
||||||
await this.selectFirstNote()
|
await this.selectFirstItem()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,7 +266,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
if (!noteExistsInUpdatedResults && !isSearching) {
|
if (!noteExistsInUpdatedResults && !isSearching) {
|
||||||
this.closeNoteController(activeController)
|
this.closeNoteController(activeController)
|
||||||
|
|
||||||
this.selectNextNote()
|
this.selectNextItem()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -277,9 +281,9 @@ export class NotesViewState extends AbstractState {
|
|||||||
this.application.getPreference(PrefKey.NotesShowArchived, false)
|
this.application.getPreference(PrefKey.NotesShowArchived, false)
|
||||||
|
|
||||||
if ((activeNote.trashed && !showTrashedNotes) || (activeNote.archived && !showArchivedNotes)) {
|
if ((activeNote.trashed && !showTrashedNotes) || (activeNote.archived && !showArchivedNotes)) {
|
||||||
await this.selectNextOrCreateNew()
|
await this.selectNextItemOrCreateNewNote()
|
||||||
} else if (!this.appState.notes.selectedNotes[activeNote.uuid]) {
|
} else if (!this.appState.selectedItems.selectedItems[activeNote.uuid]) {
|
||||||
await this.selectNoteWithScrollHandling(activeNote).catch(console.error)
|
await this.appState.selectedItems.selectItem(activeNote.uuid).catch(console.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,30 +299,32 @@ export class NotesViewState extends AbstractState {
|
|||||||
includeArchived = this.appState.searchOptions.includeArchived
|
includeArchived = this.appState.searchOptions.includeArchived
|
||||||
includeTrashed = this.appState.searchOptions.includeTrashed
|
includeTrashed = this.appState.searchOptions.includeTrashed
|
||||||
} else {
|
} else {
|
||||||
includeArchived = this.displayOptions.showArchived ?? false
|
includeArchived = this.displayOptions.includeArchived ?? false
|
||||||
includeTrashed = this.displayOptions.showTrashed ?? false
|
includeTrashed = this.displayOptions.includeTrashed ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
const criteria = NotesDisplayCriteria.Create({
|
const criteria: DisplayOptions = {
|
||||||
sortProperty: this.displayOptions.sortBy,
|
sortBy: this.displayOptions.sortBy,
|
||||||
sortDirection: this.displayOptions.sortReverse ? 'asc' : 'dsc',
|
sortDirection: this.displayOptions.sortDirection,
|
||||||
tags: tag instanceof SNTag ? [tag] : [],
|
tags: tag instanceof SNTag ? [tag] : [],
|
||||||
views: tag instanceof SmartView ? [tag] : [],
|
views: tag instanceof SmartView ? [tag] : [],
|
||||||
includeArchived,
|
includeArchived,
|
||||||
includeTrashed,
|
includeTrashed,
|
||||||
includePinned: !this.displayOptions.hidePinned,
|
includePinned: this.displayOptions.includePinned,
|
||||||
includeProtected: !this.displayOptions.hideProtected,
|
includeProtected: this.displayOptions.includeProtected,
|
||||||
searchQuery: {
|
searchQuery: {
|
||||||
query: searchText,
|
query: searchText,
|
||||||
includeProtectedNoteText: this.appState.searchOptions.includeProtectedContents,
|
includeProtectedNoteText: this.appState.searchOptions.includeProtectedContents,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
this.application.items.setNotesDisplayCriteria(criteria)
|
this.application.items.setPrimaryItemDisplayOptions(criteria)
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadPreferences = async () => {
|
reloadPreferences = async () => {
|
||||||
const freshDisplayOptions = {} as DisplayOptions
|
const newDisplayOptions = {} as DisplayOptions
|
||||||
|
const newWebDisplayOptions = {} as WebDisplayOptions
|
||||||
|
|
||||||
const currentSortBy = this.displayOptions.sortBy
|
const currentSortBy = this.displayOptions.sortBy
|
||||||
|
|
||||||
let sortBy = this.application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt)
|
let sortBy = this.application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt)
|
||||||
@@ -326,42 +332,47 @@ export class NotesViewState extends AbstractState {
|
|||||||
sortBy = CollectionSort.UpdatedAt
|
sortBy = CollectionSort.UpdatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
freshDisplayOptions.sortBy = sortBy
|
newDisplayOptions.sortBy = sortBy
|
||||||
freshDisplayOptions.sortReverse = this.application.getPreference(PrefKey.SortNotesReverse, false)
|
newDisplayOptions.sortDirection =
|
||||||
freshDisplayOptions.showArchived = this.application.getPreference(PrefKey.NotesShowArchived, false)
|
this.application.getPreference(PrefKey.SortNotesReverse, false) === false ? 'dsc' : 'asc'
|
||||||
freshDisplayOptions.showTrashed = this.application.getPreference(PrefKey.NotesShowTrashed, false) as boolean
|
newDisplayOptions.includeArchived = this.application.getPreference(PrefKey.NotesShowArchived, false)
|
||||||
freshDisplayOptions.hidePinned = this.application.getPreference(PrefKey.NotesHidePinned, false)
|
newDisplayOptions.includeTrashed = this.application.getPreference(PrefKey.NotesShowTrashed, false) as boolean
|
||||||
freshDisplayOptions.hideProtected = this.application.getPreference(PrefKey.NotesHideProtected, false)
|
newDisplayOptions.includePinned = !this.application.getPreference(PrefKey.NotesHidePinned, false)
|
||||||
freshDisplayOptions.hideNotePreview = this.application.getPreference(PrefKey.NotesHideNotePreview, false)
|
newDisplayOptions.includeProtected = !this.application.getPreference(PrefKey.NotesHideProtected, false)
|
||||||
freshDisplayOptions.hideDate = this.application.getPreference(PrefKey.NotesHideDate, false)
|
|
||||||
freshDisplayOptions.hideTags = this.application.getPreference(PrefKey.NotesHideTags, true)
|
newWebDisplayOptions.hideNotePreview = this.application.getPreference(PrefKey.NotesHideNotePreview, false)
|
||||||
freshDisplayOptions.hideEditorIcon = this.application.getPreference(PrefKey.NotesHideEditorIcon, false)
|
newWebDisplayOptions.hideDate = this.application.getPreference(PrefKey.NotesHideDate, false)
|
||||||
|
newWebDisplayOptions.hideTags = this.application.getPreference(PrefKey.NotesHideTags, true)
|
||||||
|
newWebDisplayOptions.hideEditorIcon = this.application.getPreference(PrefKey.NotesHideEditorIcon, false)
|
||||||
|
|
||||||
const displayOptionsChanged =
|
const displayOptionsChanged =
|
||||||
freshDisplayOptions.sortBy !== this.displayOptions.sortBy ||
|
newDisplayOptions.sortBy !== this.displayOptions.sortBy ||
|
||||||
freshDisplayOptions.sortReverse !== this.displayOptions.sortReverse ||
|
newDisplayOptions.sortDirection !== this.displayOptions.sortDirection ||
|
||||||
freshDisplayOptions.hidePinned !== this.displayOptions.hidePinned ||
|
newDisplayOptions.includePinned !== this.displayOptions.includePinned ||
|
||||||
freshDisplayOptions.showArchived !== this.displayOptions.showArchived ||
|
newDisplayOptions.includeArchived !== this.displayOptions.includeArchived ||
|
||||||
freshDisplayOptions.showTrashed !== this.displayOptions.showTrashed ||
|
newDisplayOptions.includeTrashed !== this.displayOptions.includeTrashed ||
|
||||||
freshDisplayOptions.hideProtected !== this.displayOptions.hideProtected ||
|
newDisplayOptions.includeProtected !== this.displayOptions.includeProtected ||
|
||||||
freshDisplayOptions.hideEditorIcon !== this.displayOptions.hideEditorIcon ||
|
newWebDisplayOptions.hideNotePreview !== this.webDisplayOptions.hideNotePreview ||
|
||||||
freshDisplayOptions.hideTags !== this.displayOptions.hideTags
|
newWebDisplayOptions.hideDate !== this.webDisplayOptions.hideDate ||
|
||||||
|
newWebDisplayOptions.hideEditorIcon !== this.webDisplayOptions.hideEditorIcon ||
|
||||||
|
newWebDisplayOptions.hideTags !== this.webDisplayOptions.hideTags
|
||||||
|
|
||||||
this.displayOptions = freshDisplayOptions
|
this.displayOptions = newDisplayOptions
|
||||||
|
this.webDisplayOptions = newWebDisplayOptions
|
||||||
|
|
||||||
if (displayOptionsChanged) {
|
if (displayOptionsChanged) {
|
||||||
this.reloadNotesDisplayOptions()
|
this.reloadNotesDisplayOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.reloadNotes()
|
await this.reloadItems()
|
||||||
|
|
||||||
const width = this.application.getPreference(PrefKey.NotesPanelWidth)
|
const width = this.application.getPreference(PrefKey.NotesPanelWidth)
|
||||||
if (width) {
|
if (width) {
|
||||||
this.panelWidth = width
|
this.panelWidth = width
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freshDisplayOptions.sortBy !== currentSortBy) {
|
if (newDisplayOptions.sortBy !== currentSortBy) {
|
||||||
await this.selectFirstNote()
|
await this.selectFirstItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +400,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
|
|
||||||
get optionsSubtitle(): string {
|
get optionsSubtitle(): string {
|
||||||
let base = ''
|
let base = ''
|
||||||
|
|
||||||
if (this.displayOptions.sortBy === CollectionSort.CreatedAt) {
|
if (this.displayOptions.sortBy === CollectionSort.CreatedAt) {
|
||||||
base += ' Date Added'
|
base += ' Date Added'
|
||||||
} else if (this.displayOptions.sortBy === CollectionSort.UpdatedAt) {
|
} else if (this.displayOptions.sortBy === CollectionSort.UpdatedAt) {
|
||||||
@@ -396,28 +408,34 @@ export class NotesViewState extends AbstractState {
|
|||||||
} else if (this.displayOptions.sortBy === CollectionSort.Title) {
|
} else if (this.displayOptions.sortBy === CollectionSort.Title) {
|
||||||
base += ' Title'
|
base += ' Title'
|
||||||
}
|
}
|
||||||
if (this.displayOptions.showArchived) {
|
|
||||||
|
if (this.displayOptions.includeArchived) {
|
||||||
base += ' | + Archived'
|
base += ' | + Archived'
|
||||||
}
|
}
|
||||||
if (this.displayOptions.showTrashed) {
|
|
||||||
|
if (this.displayOptions.includeTrashed) {
|
||||||
base += ' | + Trashed'
|
base += ' | + Trashed'
|
||||||
}
|
}
|
||||||
if (this.displayOptions.hidePinned) {
|
|
||||||
|
if (!this.displayOptions.includePinned) {
|
||||||
base += ' | – Pinned'
|
base += ' | – Pinned'
|
||||||
}
|
}
|
||||||
if (this.displayOptions.hideProtected) {
|
|
||||||
|
if (!this.displayOptions.includeProtected) {
|
||||||
base += ' | – Protected'
|
base += ' | – Protected'
|
||||||
}
|
}
|
||||||
if (this.displayOptions.sortReverse) {
|
|
||||||
|
if (this.displayOptions.sortDirection === 'asc') {
|
||||||
base += ' | Reversed'
|
base += ' | Reversed'
|
||||||
}
|
}
|
||||||
|
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
paginate = () => {
|
paginate = () => {
|
||||||
this.notesToDisplay += this.pageSize
|
this.notesToDisplay += this.pageSize
|
||||||
|
|
||||||
void this.reloadNotes()
|
void this.reloadItems()
|
||||||
|
|
||||||
if (this.searchSubmitted) {
|
if (this.searchSubmitted) {
|
||||||
this.application.getDesktopService()?.searchText(this.noteFilterText)
|
this.application.getDesktopService()?.searchText(this.noteFilterText)
|
||||||
@@ -426,9 +444,9 @@ export class NotesViewState extends AbstractState {
|
|||||||
|
|
||||||
resetPagination = (keepCurrentIfLarger = false) => {
|
resetPagination = (keepCurrentIfLarger = false) => {
|
||||||
const clientHeight = document.documentElement.clientHeight
|
const clientHeight = document.documentElement.clientHeight
|
||||||
this.pageSize = Math.ceil(clientHeight / MIN_NOTE_CELL_HEIGHT)
|
this.pageSize = Math.ceil(clientHeight / MinNoteCellHeight)
|
||||||
if (this.pageSize === 0) {
|
if (this.pageSize === 0) {
|
||||||
this.pageSize = DEFAULT_LIST_NUM_NOTES
|
this.pageSize = DefaultListNumNotes
|
||||||
}
|
}
|
||||||
if (keepCurrentIfLarger && this.notesToDisplay > this.pageSize) {
|
if (keepCurrentIfLarger && this.notesToDisplay > this.pageSize) {
|
||||||
return
|
return
|
||||||
@@ -436,60 +454,64 @@ export class NotesViewState extends AbstractState {
|
|||||||
this.notesToDisplay = this.pageSize
|
this.notesToDisplay = this.pageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstNonProtectedNote = () => {
|
getFirstNonProtectedItem = () => {
|
||||||
return this.notes.find((note) => !note.protected)
|
return this.items.find((item) => !item.protected)
|
||||||
}
|
}
|
||||||
|
|
||||||
get notesListScrollContainer() {
|
get notesListScrollContainer() {
|
||||||
return document.getElementById(ELEMENT_ID_SCROLL_CONTAINER)
|
return document.getElementById(ElementIdScrollContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNoteWithScrollHandling = async (
|
selectItemWithScrollHandling = async (
|
||||||
note: SNNote,
|
item: {
|
||||||
userTriggered?: boolean,
|
uuid: ListableContentItem['uuid']
|
||||||
scrollIntoView = true,
|
},
|
||||||
|
{ userTriggered = false, scrollIntoView = true },
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await this.appState.notes.selectNote(note.uuid, userTriggered)
|
await this.appState.selectedItems.selectItem(item.uuid, userTriggered)
|
||||||
|
|
||||||
if (scrollIntoView) {
|
if (scrollIntoView) {
|
||||||
const noteElement = document.getElementById(`note-${note.uuid}`)
|
const itemElement = document.getElementById(item.uuid)
|
||||||
noteElement?.scrollIntoView({
|
itemElement?.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectFirstNote = async () => {
|
selectFirstItem = async () => {
|
||||||
const note = this.getFirstNonProtectedNote()
|
const item = this.getFirstNonProtectedItem()
|
||||||
|
|
||||||
if (note) {
|
if (item) {
|
||||||
await this.selectNoteWithScrollHandling(note, false, false)
|
await this.selectItemWithScrollHandling(item, {
|
||||||
|
userTriggered: false,
|
||||||
|
scrollIntoView: false,
|
||||||
|
})
|
||||||
|
|
||||||
this.resetScrollPosition()
|
this.resetScrollPosition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNextNote = () => {
|
selectNextItem = () => {
|
||||||
const displayableNotes = this.notes
|
const displayableItems = this.items
|
||||||
|
|
||||||
const currentIndex = displayableNotes.findIndex((candidate) => {
|
const currentIndex = displayableItems.findIndex((candidate) => {
|
||||||
return candidate.uuid === this.activeControllerNote?.uuid
|
return candidate.uuid === this.appState.selectedItems.lastSelectedItem?.uuid
|
||||||
})
|
})
|
||||||
|
|
||||||
let nextIndex = currentIndex + 1
|
let nextIndex = currentIndex + 1
|
||||||
|
|
||||||
while (nextIndex < displayableNotes.length) {
|
while (nextIndex < displayableItems.length) {
|
||||||
const nextNote = displayableNotes[nextIndex]
|
const nextItem = displayableItems[nextIndex]
|
||||||
|
|
||||||
nextIndex++
|
nextIndex++
|
||||||
|
|
||||||
if (nextNote.protected) {
|
if (nextItem.protected) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectNoteWithScrollHandling(nextNote).catch(console.error)
|
this.selectItemWithScrollHandling(nextItem, { userTriggered: true }).catch(console.error)
|
||||||
|
|
||||||
const nextNoteElement = document.getElementById(`note-${nextNote.uuid}`)
|
const nextNoteElement = document.getElementById(nextItem.uuid)
|
||||||
|
|
||||||
nextNoteElement?.focus()
|
nextNoteElement?.focus()
|
||||||
|
|
||||||
@@ -497,39 +519,42 @@ export class NotesViewState extends AbstractState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNextOrCreateNew = async () => {
|
selectNextItemOrCreateNewNote = async () => {
|
||||||
const note = this.getFirstNonProtectedNote()
|
const item = this.getFirstNonProtectedItem()
|
||||||
|
|
||||||
if (note) {
|
if (item) {
|
||||||
await this.selectNoteWithScrollHandling(note, false, false).catch(console.error)
|
await this.selectItemWithScrollHandling(item, {
|
||||||
|
userTriggered: false,
|
||||||
|
scrollIntoView: false,
|
||||||
|
}).catch(console.error)
|
||||||
} else {
|
} else {
|
||||||
await this.createNewNote()
|
await this.createNewNote()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPreviousNote = () => {
|
selectPreviousItem = () => {
|
||||||
const displayableNotes = this.notes
|
const displayableItems = this.items
|
||||||
|
|
||||||
if (!this.activeControllerNote) {
|
if (!this.appState.selectedItems.lastSelectedItem) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentIndex = displayableNotes.indexOf(this.activeControllerNote)
|
const currentIndex = displayableItems.indexOf(this.appState.selectedItems.lastSelectedItem)
|
||||||
|
|
||||||
let previousIndex = currentIndex - 1
|
let previousIndex = currentIndex - 1
|
||||||
|
|
||||||
while (previousIndex >= 0) {
|
while (previousIndex >= 0) {
|
||||||
const previousNote = displayableNotes[previousIndex]
|
const previousItem = displayableItems[previousIndex]
|
||||||
|
|
||||||
previousIndex--
|
previousIndex--
|
||||||
|
|
||||||
if (previousNote.protected) {
|
if (previousItem.protected) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectNoteWithScrollHandling(previousNote).catch(console.error)
|
this.selectItemWithScrollHandling(previousItem, { userTriggered: true }).catch(console.error)
|
||||||
|
|
||||||
const previousNoteElement = document.getElementById(`note-${previousNote.uuid}`)
|
const previousNoteElement = document.getElementById(previousItem.uuid)
|
||||||
|
|
||||||
previousNoteElement?.focus()
|
previousNoteElement?.focus()
|
||||||
|
|
||||||
@@ -591,7 +616,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
|
|
||||||
this.reloadNotesDisplayOptions()
|
this.reloadNotesDisplayOptions()
|
||||||
|
|
||||||
void this.reloadNotes()
|
void this.reloadItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterEnter = () => {
|
onFilterEnter = () => {
|
||||||
@@ -624,7 +649,7 @@ export class NotesViewState extends AbstractState {
|
|||||||
|
|
||||||
this.reloadNotesDisplayOptions()
|
this.reloadNotesDisplayOptions()
|
||||||
|
|
||||||
void this.reloadNotes()
|
void this.reloadItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
clearFilterText = () => {
|
clearFilterText = () => {
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
|
import {
|
||||||
|
PopoverFileItemAction,
|
||||||
|
PopoverFileItemActionType,
|
||||||
|
} from '@/Components/AttachedFilesPopover/PopoverFileItemAction'
|
||||||
|
import { PopoverTabs } from '@/Components/AttachedFilesPopover/PopoverTabs'
|
||||||
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants'
|
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants'
|
||||||
|
import { confirmDialog } from '@/Services/AlertService'
|
||||||
import { concatenateUint8Arrays } from '@/Utils/ConcatenateUint8Arrays'
|
import { concatenateUint8Arrays } from '@/Utils/ConcatenateUint8Arrays'
|
||||||
import {
|
import {
|
||||||
ClassicFileReader,
|
ClassicFileReader,
|
||||||
@@ -7,11 +13,202 @@ import {
|
|||||||
ClassicFileSaver,
|
ClassicFileSaver,
|
||||||
parseFileName,
|
parseFileName,
|
||||||
} from '@standardnotes/filepicker'
|
} from '@standardnotes/filepicker'
|
||||||
import { ClientDisplayableError, FileItem } from '@standardnotes/snjs'
|
import { ChallengeReason, ClientDisplayableError, ContentType, FileItem } from '@standardnotes/snjs'
|
||||||
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/stylekit'
|
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/stylekit'
|
||||||
|
import { action, computed, makeObservable, observable, reaction } from 'mobx'
|
||||||
|
import { WebApplication } from '../Application'
|
||||||
import { AbstractState } from './AbstractState'
|
import { AbstractState } from './AbstractState'
|
||||||
|
import { AppState } from './AppState'
|
||||||
|
|
||||||
|
const UnprotectedFileActions = [PopoverFileItemActionType.ToggleFileProtection]
|
||||||
|
const NonMutatingFileActions = [PopoverFileItemActionType.DownloadFile, PopoverFileItemActionType.PreviewFile]
|
||||||
|
|
||||||
|
type FileContextMenuLocation = { x: number; y: number }
|
||||||
|
|
||||||
export class FilesState extends AbstractState {
|
export class FilesState extends AbstractState {
|
||||||
|
allFiles: FileItem[] = []
|
||||||
|
attachedFiles: FileItem[] = []
|
||||||
|
showFileContextMenu = false
|
||||||
|
fileContextMenuLocation: FileContextMenuLocation = { x: 0, y: 0 }
|
||||||
|
|
||||||
|
constructor(application: WebApplication, override appState: AppState, appObservers: (() => void)[]) {
|
||||||
|
super(application, appState)
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
allFiles: observable,
|
||||||
|
attachedFiles: observable,
|
||||||
|
showFileContextMenu: observable,
|
||||||
|
fileContextMenuLocation: observable,
|
||||||
|
|
||||||
|
selectedFiles: computed,
|
||||||
|
|
||||||
|
reloadAllFiles: action,
|
||||||
|
reloadAttachedFiles: action,
|
||||||
|
setShowFileContextMenu: action,
|
||||||
|
setFileContextMenuLocation: action,
|
||||||
|
})
|
||||||
|
|
||||||
|
appObservers.push(
|
||||||
|
application.streamItems(ContentType.File, () => {
|
||||||
|
this.reloadAllFiles()
|
||||||
|
this.reloadAttachedFiles()
|
||||||
|
}),
|
||||||
|
reaction(
|
||||||
|
() => appState.notes.selectedNotes,
|
||||||
|
() => {
|
||||||
|
this.reloadAttachedFiles()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedFiles() {
|
||||||
|
return this.appState.selectedItems.getSelectedItems<FileItem>(ContentType.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowFileContextMenu = (enabled: boolean) => {
|
||||||
|
this.showFileContextMenu = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
setFileContextMenuLocation = (location: FileContextMenuLocation) => {
|
||||||
|
this.fileContextMenuLocation = location
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadAllFiles = () => {
|
||||||
|
this.allFiles = this.application.items.getDisplayableFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadAttachedFiles = () => {
|
||||||
|
const note = this.appState.notes.firstSelectedNote
|
||||||
|
if (note) {
|
||||||
|
this.attachedFiles = this.application.items.getFilesForNote(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFile = async (file: FileItem) => {
|
||||||
|
const shouldDelete = await confirmDialog({
|
||||||
|
text: `Are you sure you want to permanently delete "${file.name}"?`,
|
||||||
|
confirmButtonStyle: 'danger',
|
||||||
|
})
|
||||||
|
if (shouldDelete) {
|
||||||
|
const deletingToastId = addToast({
|
||||||
|
type: ToastType.Loading,
|
||||||
|
message: `Deleting file "${file.name}"...`,
|
||||||
|
})
|
||||||
|
await this.application.files.deleteFile(file)
|
||||||
|
addToast({
|
||||||
|
type: ToastType.Success,
|
||||||
|
message: `Deleted file "${file.name}"`,
|
||||||
|
})
|
||||||
|
dismissToast(deletingToastId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachFileToNote = async (file: FileItem) => {
|
||||||
|
const note = this.appState.notes.firstSelectedNote
|
||||||
|
if (!note) {
|
||||||
|
addToast({
|
||||||
|
type: ToastType.Error,
|
||||||
|
message: 'Could not attach file because selected note was deleted',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.application.items.associateFileWithNote(file, note)
|
||||||
|
}
|
||||||
|
|
||||||
|
detachFileFromNote = async (file: FileItem) => {
|
||||||
|
const note = this.appState.notes.firstSelectedNote
|
||||||
|
if (!note) {
|
||||||
|
addToast({
|
||||||
|
type: ToastType.Error,
|
||||||
|
message: 'Could not attach file because selected note was deleted',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.application.items.disassociateFileWithNote(file, note)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFileProtection = async (file: FileItem) => {
|
||||||
|
let result: FileItem | undefined
|
||||||
|
if (file.protected) {
|
||||||
|
result = await this.application.mutator.unprotectFile(file)
|
||||||
|
} else {
|
||||||
|
result = await this.application.mutator.protectFile(file)
|
||||||
|
}
|
||||||
|
const isProtected = result ? result.protected : file.protected
|
||||||
|
return isProtected
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizeProtectedActionForFile = async (file: FileItem, challengeReason: ChallengeReason) => {
|
||||||
|
const authorizedFiles = await this.application.protections.authorizeProtectedActionForItems([file], challengeReason)
|
||||||
|
const isAuthorized = authorizedFiles.length > 0 && authorizedFiles.includes(file)
|
||||||
|
return isAuthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
renameFile = async (file: FileItem, fileName: string) => {
|
||||||
|
await this.application.items.renameFile(file, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileAction = async (
|
||||||
|
action: PopoverFileItemAction,
|
||||||
|
currentTab: PopoverTabs,
|
||||||
|
): Promise<{
|
||||||
|
didHandleAction: boolean
|
||||||
|
}> => {
|
||||||
|
const file = action.type !== PopoverFileItemActionType.RenameFile ? action.payload : action.payload.file
|
||||||
|
let isAuthorizedForAction = true
|
||||||
|
|
||||||
|
const requiresAuthorization = file.protected && !UnprotectedFileActions.includes(action.type)
|
||||||
|
|
||||||
|
if (requiresAuthorization) {
|
||||||
|
isAuthorizedForAction = await this.authorizeProtectedActionForFile(file, ChallengeReason.AccessProtectedFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAuthorizedForAction) {
|
||||||
|
return {
|
||||||
|
didHandleAction: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case PopoverFileItemActionType.AttachFileToNote:
|
||||||
|
await this.attachFileToNote(file)
|
||||||
|
break
|
||||||
|
case PopoverFileItemActionType.DetachFileToNote:
|
||||||
|
await this.detachFileFromNote(file)
|
||||||
|
break
|
||||||
|
case PopoverFileItemActionType.DeleteFile:
|
||||||
|
await this.deleteFile(file)
|
||||||
|
break
|
||||||
|
case PopoverFileItemActionType.DownloadFile:
|
||||||
|
await this.downloadFile(file)
|
||||||
|
break
|
||||||
|
case PopoverFileItemActionType.ToggleFileProtection: {
|
||||||
|
const isProtected = await this.toggleFileProtection(file)
|
||||||
|
action.callback(isProtected)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case PopoverFileItemActionType.RenameFile:
|
||||||
|
await this.renameFile(file, action.payload.name)
|
||||||
|
break
|
||||||
|
case PopoverFileItemActionType.PreviewFile:
|
||||||
|
this.appState.filePreviewModal.activate(
|
||||||
|
file,
|
||||||
|
currentTab === PopoverTabs.AllFiles ? this.allFiles : this.attachedFiles,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NonMutatingFileActions.includes(action.type)) {
|
||||||
|
this.application.sync.sync().catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
didHandleAction: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async downloadFile(file: FileItem): Promise<void> {
|
public async downloadFile(file: FileItem): Promise<void> {
|
||||||
let downloadingToastId = ''
|
let downloadingToastId = ''
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export class NoteTagsState extends AbstractState {
|
|||||||
searchActiveNoteAutocompleteTags(): void {
|
searchActiveNoteAutocompleteTags(): void {
|
||||||
const newResults = this.application.items.searchTags(
|
const newResults = this.application.items.searchTags(
|
||||||
this.autocompleteSearchQuery,
|
this.autocompleteSearchQuery,
|
||||||
this.appState.notesView.activeControllerNote,
|
this.appState.contentListView.activeControllerNote,
|
||||||
)
|
)
|
||||||
this.setAutocompleteTagResults(newResults)
|
this.setAutocompleteTagResults(newResults)
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ export class NoteTagsState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadTags(): void {
|
reloadTags(): void {
|
||||||
const activeNote = this.appState.notesView.activeControllerNote
|
const activeNote = this.appState.contentListView.activeControllerNote
|
||||||
|
|
||||||
if (activeNote) {
|
if (activeNote) {
|
||||||
const tags = this.application.items.getSortedTagsForNote(activeNote)
|
const tags = this.application.items.getSortedTagsForNote(activeNote)
|
||||||
@@ -173,7 +173,7 @@ export class NoteTagsState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addTagToActiveNote(tag: SNTag): Promise<void> {
|
async addTagToActiveNote(tag: SNTag): Promise<void> {
|
||||||
const activeNote = this.appState.notesView.activeControllerNote
|
const activeNote = this.appState.contentListView.activeControllerNote
|
||||||
|
|
||||||
if (activeNote) {
|
if (activeNote) {
|
||||||
await this.application.items.addTagToNote(activeNote, tag, this.addNoteToParentFolders)
|
await this.application.items.addTagToNote(activeNote, tag, this.addNoteToParentFolders)
|
||||||
@@ -183,7 +183,7 @@ export class NoteTagsState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeTagFromActiveNote(tag: SNTag): Promise<void> {
|
async removeTagFromActiveNote(tag: SNTag): Promise<void> {
|
||||||
const activeNote = this.appState.notesView.activeControllerNote
|
const activeNote = this.appState.contentListView.activeControllerNote
|
||||||
|
|
||||||
if (activeNote) {
|
if (activeNote) {
|
||||||
await this.application.mutator.changeItem(tag, (mutator) => {
|
await this.application.mutator.changeItem(tag, (mutator) => {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { destroyAllObjectProperties } from '@/Utils'
|
import { destroyAllObjectProperties } from '@/Utils'
|
||||||
import { confirmDialog } from '@/Services/AlertService'
|
import { confirmDialog } from '@/Services/AlertService'
|
||||||
import { KeyboardModifier } from '@/Services/IOService'
|
|
||||||
import { StringEmptyTrash, Strings, StringUtils } from '@/Strings'
|
import { StringEmptyTrash, Strings, StringUtils } from '@/Strings'
|
||||||
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants'
|
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants'
|
||||||
import { UuidString, SNNote, NoteMutator, ContentType, SNTag, ChallengeReason, DeinitSource } from '@standardnotes/snjs'
|
import { SNNote, NoteMutator, ContentType, SNTag, DeinitSource, TagMutator } from '@standardnotes/snjs'
|
||||||
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
||||||
import { WebApplication } from '../Application'
|
import { WebApplication } from '../Application'
|
||||||
import { AppState } from './AppState'
|
import { AppState } from './AppState'
|
||||||
@@ -11,7 +10,6 @@ import { AbstractState } from './AbstractState'
|
|||||||
|
|
||||||
export class NotesState extends AbstractState {
|
export class NotesState extends AbstractState {
|
||||||
lastSelectedNote: SNNote | undefined
|
lastSelectedNote: SNNote | undefined
|
||||||
selectedNotes: Record<UuidString, SNNote> = {}
|
|
||||||
contextMenuOpen = false
|
contextMenuOpen = false
|
||||||
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
|
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -25,7 +23,6 @@ export class NotesState extends AbstractState {
|
|||||||
override deinit(source: DeinitSource) {
|
override deinit(source: DeinitSource) {
|
||||||
super.deinit(source)
|
super.deinit(source)
|
||||||
;(this.lastSelectedNote as unknown) = undefined
|
;(this.lastSelectedNote as unknown) = undefined
|
||||||
;(this.selectedNotes as unknown) = undefined
|
|
||||||
;(this.onActiveEditorChanged as unknown) = undefined
|
;(this.onActiveEditorChanged as unknown) = undefined
|
||||||
|
|
||||||
destroyAllObjectProperties(this)
|
destroyAllObjectProperties(this)
|
||||||
@@ -40,12 +37,13 @@ export class NotesState extends AbstractState {
|
|||||||
super(application, appState)
|
super(application, appState)
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
selectedNotes: observable,
|
|
||||||
contextMenuOpen: observable,
|
contextMenuOpen: observable,
|
||||||
contextMenuPosition: observable,
|
contextMenuPosition: observable,
|
||||||
showProtectedWarning: observable,
|
showProtectedWarning: observable,
|
||||||
showRevisionHistoryModal: observable,
|
showRevisionHistoryModal: observable,
|
||||||
|
|
||||||
|
selectedNotes: computed,
|
||||||
|
firstSelectedNote: computed,
|
||||||
selectedNotesCount: computed,
|
selectedNotesCount: computed,
|
||||||
trashedNotesCount: computed,
|
trashedNotesCount: computed,
|
||||||
|
|
||||||
@@ -89,6 +87,14 @@ export class NotesState extends AbstractState {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get selectedNotes() {
|
||||||
|
return this.appState.selectedItems.getSelectedItems<SNNote>(ContentType.Note)
|
||||||
|
}
|
||||||
|
|
||||||
|
get firstSelectedNote(): SNNote | undefined {
|
||||||
|
return Object.values(this.selectedNotes)[0]
|
||||||
|
}
|
||||||
|
|
||||||
get selectedNotesCount(): number {
|
get selectedNotesCount(): number {
|
||||||
if (this.dealloced) {
|
if (this.dealloced) {
|
||||||
return 0
|
return 0
|
||||||
@@ -101,79 +107,8 @@ export class NotesState extends AbstractState {
|
|||||||
return this.application.items.trashedItems.length
|
return this.application.items.trashedItems.length
|
||||||
}
|
}
|
||||||
|
|
||||||
private async selectNotesRange(selectedNote: SNNote): Promise<void> {
|
async openNote(noteUuid: string): Promise<void> {
|
||||||
const notes = this.application.items.getDisplayableNotes()
|
if (this.appState.contentListView.activeControllerNote?.uuid === noteUuid) {
|
||||||
|
|
||||||
const lastSelectedNoteIndex = notes.findIndex((note) => note.uuid == this.lastSelectedNote?.uuid)
|
|
||||||
const selectedNoteIndex = notes.findIndex((note) => note.uuid == selectedNote.uuid)
|
|
||||||
|
|
||||||
let notesToSelect = []
|
|
||||||
if (selectedNoteIndex > lastSelectedNoteIndex) {
|
|
||||||
notesToSelect = notes.slice(lastSelectedNoteIndex, selectedNoteIndex + 1)
|
|
||||||
} else {
|
|
||||||
notesToSelect = notes.slice(selectedNoteIndex, lastSelectedNoteIndex + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorizedNotes = await this.application.authorizeProtectedActionForNotes(
|
|
||||||
notesToSelect,
|
|
||||||
ChallengeReason.SelectProtectedNote,
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const note of authorizedNotes) {
|
|
||||||
runInAction(() => {
|
|
||||||
this.selectedNotes[note.uuid] = note
|
|
||||||
this.lastSelectedNote = note
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectNote(uuid: UuidString, userTriggered?: boolean): Promise<void> {
|
|
||||||
const note = this.application.items.findItem(uuid) as SNNote
|
|
||||||
if (!note) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedNotes[uuid]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasMeta = this.io.activeModifiers.has(KeyboardModifier.Meta)
|
|
||||||
const hasCtrl = this.io.activeModifiers.has(KeyboardModifier.Ctrl)
|
|
||||||
const hasShift = this.io.activeModifiers.has(KeyboardModifier.Shift)
|
|
||||||
|
|
||||||
const isMultipleSelectSingle = userTriggered && (hasMeta || hasCtrl)
|
|
||||||
const isMultipleSelectRange = userTriggered && hasShift
|
|
||||||
|
|
||||||
if (isMultipleSelectSingle) {
|
|
||||||
if (this.selectedNotes[uuid]) {
|
|
||||||
delete this.selectedNotes[uuid]
|
|
||||||
} else if (await this.application.authorizeNoteAccess(note)) {
|
|
||||||
runInAction(() => {
|
|
||||||
this.selectedNotes[uuid] = note
|
|
||||||
this.lastSelectedNote = note
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (isMultipleSelectRange) {
|
|
||||||
await this.selectNotesRange(note)
|
|
||||||
} else {
|
|
||||||
const shouldSelectNote = this.selectedNotesCount > 1 || !this.selectedNotes[uuid]
|
|
||||||
if (shouldSelectNote && (await this.application.authorizeNoteAccess(note))) {
|
|
||||||
runInAction(() => {
|
|
||||||
this.selectedNotes = {
|
|
||||||
[note.uuid]: note,
|
|
||||||
}
|
|
||||||
this.lastSelectedNote = note
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedNotesCount === 1) {
|
|
||||||
await this.openNote(Object.keys(this.selectedNotes)[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async openNote(noteUuid: string): Promise<void> {
|
|
||||||
if (this.appState.notesView.activeControllerNote?.uuid === noteUuid) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +294,7 @@ export class NotesState extends AbstractState {
|
|||||||
})
|
})
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.selectedNotes = {}
|
this.appState.selectedItems.setSelectedItems({})
|
||||||
this.contextMenuOpen = false
|
this.contextMenuOpen = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -376,7 +311,7 @@ export class NotesState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unselectNotes(): void {
|
unselectNotes(): void {
|
||||||
this.selectedNotes = {}
|
this.appState.selectedItems.setSelectedItems({})
|
||||||
}
|
}
|
||||||
|
|
||||||
getSpellcheckStateForNote(note: SNNote) {
|
getSpellcheckStateForNote(note: SNNote) {
|
||||||
@@ -400,9 +335,9 @@ export class NotesState extends AbstractState {
|
|||||||
const tagsToAdd = [...parentChainTags, tag]
|
const tagsToAdd = [...parentChainTags, tag]
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tagsToAdd.map(async (tag) => {
|
tagsToAdd.map(async (tag) => {
|
||||||
await this.application.mutator.changeItem(tag, (mutator) => {
|
await this.application.mutator.changeItem<TagMutator>(tag, (mutator) => {
|
||||||
for (const note of selectedNotes) {
|
for (const note of selectedNotes) {
|
||||||
mutator.addItemAsRelationship(note)
|
mutator.addNote(note)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
@@ -422,7 +357,7 @@ export class NotesState extends AbstractState {
|
|||||||
|
|
||||||
isTagInSelectedNotes(tag: SNTag): boolean {
|
isTagInSelectedNotes(tag: SNTag): boolean {
|
||||||
const selectedNotes = this.getSelectedNotesList()
|
const selectedNotes = this.getSelectedNotesList()
|
||||||
return selectedNotes.every((note) => this.appState.getNoteTags(note).find((noteTag) => noteTag.uuid === tag.uuid))
|
return selectedNotes.every((note) => this.appState.getItemTags(note).find((noteTag) => noteTag.uuid === tag.uuid))
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowProtectedWarning(show: boolean): void {
|
setShowProtectedWarning(show: boolean): void {
|
||||||
@@ -445,10 +380,6 @@ export class NotesState extends AbstractState {
|
|||||||
return Object.values(this.selectedNotes)
|
return Object.values(this.selectedNotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get io() {
|
|
||||||
return this.application.io
|
|
||||||
}
|
|
||||||
|
|
||||||
setShowRevisionHistoryModal(show: boolean): void {
|
setShowRevisionHistoryModal(show: boolean): void {
|
||||||
this.showRevisionHistoryModal = show
|
this.showRevisionHistoryModal = show
|
||||||
}
|
}
|
||||||
|
|||||||
140
app/assets/javascripts/UIModels/AppState/SelectedItemsState.ts
Normal file
140
app/assets/javascripts/UIModels/AppState/SelectedItemsState.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem'
|
||||||
|
import { ChallengeReason, ContentType, KeyboardModifier, FileItem, SNNote, UuidString } from '@standardnotes/snjs'
|
||||||
|
import { action, computed, makeObservable, observable, runInAction } from 'mobx'
|
||||||
|
import { WebApplication } from '../Application'
|
||||||
|
import { AbstractState } from './AbstractState'
|
||||||
|
import { AppState } from './AppState'
|
||||||
|
|
||||||
|
type SelectedItems = Record<UuidString, ListableContentItem>
|
||||||
|
|
||||||
|
export class SelectedItemsState extends AbstractState {
|
||||||
|
lastSelectedItem: ListableContentItem | undefined
|
||||||
|
selectedItems: SelectedItems = {}
|
||||||
|
|
||||||
|
constructor(application: WebApplication, override appState: AppState, appObservers: (() => void)[]) {
|
||||||
|
super(application)
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
selectedItems: observable,
|
||||||
|
|
||||||
|
selectedItemsCount: computed,
|
||||||
|
|
||||||
|
selectItem: action,
|
||||||
|
setSelectedItems: action,
|
||||||
|
})
|
||||||
|
|
||||||
|
appObservers.push(
|
||||||
|
application.streamItems<SNNote | FileItem>(
|
||||||
|
[ContentType.Note, ContentType.File],
|
||||||
|
({ changed, inserted, removed }) => {
|
||||||
|
runInAction(() => {
|
||||||
|
for (const removedNote of removed) {
|
||||||
|
delete this.selectedItems[removedNote.uuid]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of [...changed, ...inserted]) {
|
||||||
|
if (this.selectedItems[item.uuid]) {
|
||||||
|
this.selectedItems[item.uuid] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get io() {
|
||||||
|
return this.application.io
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedItemsCount(): number {
|
||||||
|
return Object.keys(this.selectedItems).length
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedItems = <T extends ListableContentItem>(contentType: ContentType) => {
|
||||||
|
const filteredEntries = Object.entries(this.appState.selectedItems.selectedItems).filter(
|
||||||
|
([_, item]) => item.content_type === contentType,
|
||||||
|
) as [UuidString, T][]
|
||||||
|
return Object.fromEntries<T>(filteredEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedItems = (selectedItems: SelectedItems) => {
|
||||||
|
this.selectedItems = selectedItems
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectItemsRange = async (selectedItem: ListableContentItem): Promise<void> => {
|
||||||
|
const items = this.appState.contentListView.renderedItems
|
||||||
|
|
||||||
|
const lastSelectedItemIndex = items.findIndex((item) => item.uuid == this.lastSelectedItem?.uuid)
|
||||||
|
const selectedItemIndex = items.findIndex((item) => item.uuid == selectedItem.uuid)
|
||||||
|
|
||||||
|
let itemsToSelect = []
|
||||||
|
if (selectedItemIndex > lastSelectedItemIndex) {
|
||||||
|
itemsToSelect = items.slice(lastSelectedItemIndex, selectedItemIndex + 1)
|
||||||
|
} else {
|
||||||
|
itemsToSelect = items.slice(selectedItemIndex, lastSelectedItemIndex + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorizedItems = await this.application.protections.authorizeProtectedActionForItems(
|
||||||
|
itemsToSelect,
|
||||||
|
ChallengeReason.SelectProtectedNote,
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const item of authorizedItems) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.selectedItems[item.uuid] = item
|
||||||
|
this.lastSelectedItem = item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectItem = async (
|
||||||
|
uuid: UuidString,
|
||||||
|
userTriggered?: boolean,
|
||||||
|
): Promise<{
|
||||||
|
didSelect: boolean
|
||||||
|
}> => {
|
||||||
|
const item = this.application.items.findItem<ListableContentItem>(uuid)
|
||||||
|
if (!item) {
|
||||||
|
return {
|
||||||
|
didSelect: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasMeta = this.io.activeModifiers.has(KeyboardModifier.Meta)
|
||||||
|
const hasCtrl = this.io.activeModifiers.has(KeyboardModifier.Ctrl)
|
||||||
|
const hasShift = this.io.activeModifiers.has(KeyboardModifier.Shift)
|
||||||
|
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
||||||
|
const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item)
|
||||||
|
|
||||||
|
if (userTriggered && (hasMeta || hasCtrl)) {
|
||||||
|
if (this.selectedItems[uuid] && hasMoreThanOneSelected) {
|
||||||
|
delete this.selectedItems[uuid]
|
||||||
|
} else if (isAuthorizedForAccess) {
|
||||||
|
this.selectedItems[uuid] = item
|
||||||
|
this.lastSelectedItem = item
|
||||||
|
}
|
||||||
|
} else if (userTriggered && hasShift) {
|
||||||
|
await this.selectItemsRange(item)
|
||||||
|
} else {
|
||||||
|
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedItems[uuid]
|
||||||
|
if (shouldSelectNote && isAuthorizedForAccess) {
|
||||||
|
this.setSelectedItems({
|
||||||
|
[item.uuid]: item,
|
||||||
|
})
|
||||||
|
this.lastSelectedItem = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedItemsCount === 1) {
|
||||||
|
const item = Object.values(this.selectedItems)[0]
|
||||||
|
if (item.content_type === ContentType.Note) {
|
||||||
|
await this.appState.notes.openNote(item.uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
didSelect: this.selectedItems[uuid] != undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ type AnyTag = SNTag | SmartView
|
|||||||
const rootTags = (application: SNApplication): SNTag[] => {
|
const rootTags = (application: SNApplication): SNTag[] => {
|
||||||
const hasNoParent = (tag: SNTag) => !application.items.getTagParent(tag)
|
const hasNoParent = (tag: SNTag) => !application.items.getTagParent(tag)
|
||||||
|
|
||||||
const allTags = application.items.getDisplayableItems<SNTag>(ContentType.Tag)
|
const allTags = application.items.getDisplayableTags()
|
||||||
const rootTags = allTags.filter(hasNoParent)
|
const rootTags = allTags.filter(hasNoParent)
|
||||||
|
|
||||||
return rootTags
|
return rootTags
|
||||||
@@ -132,7 +132,7 @@ export class TagsState extends AbstractState {
|
|||||||
appEventListeners.push(
|
appEventListeners.push(
|
||||||
this.application.streamItems([ContentType.Tag, ContentType.SmartView], ({ changed, removed }) => {
|
this.application.streamItems([ContentType.Tag, ContentType.SmartView], ({ changed, removed }) => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.tags = this.application.items.getDisplayableItems<SNTag>(ContentType.Tag)
|
this.tags = this.application.items.getDisplayableTags()
|
||||||
|
|
||||||
this.smartViews = this.application.items.getSmartViews()
|
this.smartViews = this.application.items.getSmartViews()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export type WebDisplayOptions = {
|
||||||
|
hideTags: boolean
|
||||||
|
hideDate: boolean
|
||||||
|
hideNotePreview: boolean
|
||||||
|
hideEditorIcon: boolean
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
NoteGroupController,
|
NoteGroupController,
|
||||||
removeFromArray,
|
removeFromArray,
|
||||||
IconsController,
|
IconsController,
|
||||||
Runtime,
|
|
||||||
DesktopDeviceInterface,
|
DesktopDeviceInterface,
|
||||||
isDesktopDevice,
|
isDesktopDevice,
|
||||||
DeinitMode,
|
DeinitMode,
|
||||||
@@ -49,7 +48,6 @@ export class WebApplication extends SNApplication {
|
|||||||
identifier: string,
|
identifier: string,
|
||||||
defaultSyncServerHost: string,
|
defaultSyncServerHost: string,
|
||||||
webSocketUrl: string,
|
webSocketUrl: string,
|
||||||
runtime: Runtime,
|
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
environment: deviceInterface.environment,
|
environment: deviceInterface.environment,
|
||||||
@@ -61,7 +59,7 @@ export class WebApplication extends SNApplication {
|
|||||||
defaultHost: defaultSyncServerHost,
|
defaultHost: defaultSyncServerHost,
|
||||||
appVersion: deviceInterface.appVersion,
|
appVersion: deviceInterface.appVersion,
|
||||||
webSocketUrl: webSocketUrl,
|
webSocketUrl: webSocketUrl,
|
||||||
runtime,
|
supportsFileNavigation: window.enabledUnfinishedFeatures,
|
||||||
})
|
})
|
||||||
|
|
||||||
deviceInterface.setApplication(this)
|
deviceInterface.setApplication(this)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
ApplicationDescriptor,
|
ApplicationDescriptor,
|
||||||
SNApplicationGroup,
|
SNApplicationGroup,
|
||||||
Platform,
|
Platform,
|
||||||
Runtime,
|
|
||||||
InternalEventBus,
|
InternalEventBus,
|
||||||
isDesktopDevice,
|
isDesktopDevice,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
@@ -21,7 +20,6 @@ const createApplication = (
|
|||||||
deviceInterface: WebOrDesktopDevice,
|
deviceInterface: WebOrDesktopDevice,
|
||||||
defaultSyncServerHost: string,
|
defaultSyncServerHost: string,
|
||||||
device: WebOrDesktopDevice,
|
device: WebOrDesktopDevice,
|
||||||
runtime: Runtime,
|
|
||||||
webSocketUrl: string,
|
webSocketUrl: string,
|
||||||
) => {
|
) => {
|
||||||
const platform = getPlatform()
|
const platform = getPlatform()
|
||||||
@@ -32,7 +30,6 @@ const createApplication = (
|
|||||||
descriptor.identifier,
|
descriptor.identifier,
|
||||||
defaultSyncServerHost,
|
defaultSyncServerHost,
|
||||||
webSocketUrl,
|
webSocketUrl,
|
||||||
runtime,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const appState = new AppState(application, device)
|
const appState = new AppState(application, device)
|
||||||
@@ -54,23 +51,17 @@ const createApplication = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
||||||
constructor(
|
constructor(private defaultSyncServerHost: string, device: WebOrDesktopDevice, private webSocketUrl: string) {
|
||||||
private defaultSyncServerHost: string,
|
|
||||||
device: WebOrDesktopDevice,
|
|
||||||
private runtime: Runtime,
|
|
||||||
private webSocketUrl: string,
|
|
||||||
) {
|
|
||||||
super(device)
|
super(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
override async initialize(): Promise<void> {
|
override async initialize(): Promise<void> {
|
||||||
const defaultSyncServerHost = this.defaultSyncServerHost
|
const defaultSyncServerHost = this.defaultSyncServerHost
|
||||||
const runtime = this.runtime
|
|
||||||
const webSocketUrl = this.webSocketUrl
|
const webSocketUrl = this.webSocketUrl
|
||||||
|
|
||||||
await super.initialize({
|
await super.initialize({
|
||||||
applicationCreator: async (descriptor, device) => {
|
applicationCreator: async (descriptor, device) => {
|
||||||
return createApplication(descriptor, device, defaultSyncServerHost, device, runtime, webSocketUrl)
|
return createApplication(descriptor, device, defaultSyncServerHost, device, webSocketUrl)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ $heading-height: 75px;
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
background-color: var(--sn-stylekit-editor-background-color);
|
background-color: var(--editor-background-color);
|
||||||
color: var(--sn-stylekit-editor-foreground-color);
|
color: var(--editor-foreground-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#error-decrypting-container {
|
#error-decrypting-container {
|
||||||
@@ -34,7 +34,7 @@ $heading-height: 75px;
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
padding-right: 14px;
|
padding-right: 14px;
|
||||||
|
|
||||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
border-bottom: 1px solid var(--editor-title-bar-border-bottom-color);
|
||||||
z-index: $z-index-editor-title-bar;
|
z-index: $z-index-editor-title-bar;
|
||||||
|
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -53,10 +53,10 @@ $heading-height: 75px;
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--sn-stylekit-editor-foreground-color);
|
color: var(--editor-title-input-color);
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: var(--sn-stylekit-editor-foreground-color);
|
color: var(--editor-title-input-color);
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -100,7 +100,7 @@ $heading-height: 75px;
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
tab-size: 2;
|
tab-size: 2;
|
||||||
background-color: var(--sn-stylekit-background-color);
|
background-color: var(--editor-pane-background-color);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -112,8 +112,8 @@ $heading-height: 75px;
|
|||||||
.editable {
|
.editable {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--sn-stylekit-editor-background-color);
|
background-color: var(--editor-pane-editor-background-color);
|
||||||
color: var(--sn-stylekit-editor-foreground-color);
|
color: var(--editor-pane-editor-foreground-color);
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -143,7 +143,7 @@ $heading-height: 75px;
|
|||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--sn-stylekit-background-color);
|
background-color: var(--editor-pane-component-stack-item-background-color);
|
||||||
// we moved the border top from the .component-stack-item to the .iframe, as on parent,
|
// we moved the border top from the .component-stack-item to the .iframe, as on parent,
|
||||||
// it increases its height and caused unneccessary scrollbars on windows.
|
// it increases its height and caused unneccessary scrollbars on windows.
|
||||||
border-top: 1px solid var(--sn-stylekit-border-color);
|
border-top: 1px solid var(--sn-stylekit-border-color);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#navigation,
|
#navigation,
|
||||||
#notes-column {
|
#items-column {
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
animation: fade-out 1.25s forwards;
|
animation: fade-out 1.25s forwards;
|
||||||
transition: width 1.25s;
|
transition: width 1.25s;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#notes-column:hover {
|
#items-column:hover {
|
||||||
flex: initial;
|
flex: initial;
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
.disable-focus-mode {
|
.disable-focus-mode {
|
||||||
#navigation,
|
#navigation,
|
||||||
#notes-column {
|
#items-column {
|
||||||
transition: width 1.25s;
|
transition: width 1.25s;
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
animation: fade-in 1.25s forwards;
|
animation: fade-in 1.25s forwards;
|
||||||
|
|||||||
@@ -26,22 +26,6 @@
|
|||||||
left: inherit;
|
left: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dock-shortcut {
|
|
||||||
width: 12px;
|
|
||||||
|
|
||||||
&:hover .sk-app-bar-item-column {
|
|
||||||
border-bottom: 2px solid var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-app-bar-item-column {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
* {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#account-panel,
|
#account-panel,
|
||||||
|
|||||||
167
app/assets/stylesheets/_items-column.scss
Normal file
167
app/assets/stylesheets/_items-column.scss
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
@import './scrollbar';
|
||||||
|
|
||||||
|
#items-column {
|
||||||
|
background-color: var(--items-column-background-color);
|
||||||
|
border-left: 1px solid var(--items-column-border-left-color);
|
||||||
|
border-right: 1px solid var(--items-column-border-right-color);
|
||||||
|
font-size: var(--sn-stylekit-font-size-h2);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
-moz-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
|
||||||
|
.empty-items-list {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: var(--sn-stylekit-font-size-h3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#items-title-bar-container {
|
||||||
|
padding: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#items-title-bar {
|
||||||
|
font-weight: normal;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.section-title-bar-header .title {
|
||||||
|
width: calc(90% - 45px);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: var(--sn-stylekit-font-size-p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#items-menu-bar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notes-options-menu {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
clear: left;
|
||||||
|
max-height: 80px;
|
||||||
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
background-color: var(--items-column-search-background-color);
|
||||||
|
border-radius: var(--sn-stylekit-general-border-radius);
|
||||||
|
height: 100%;
|
||||||
|
color: #909090;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: var(--sn-stylekit-font-size-h3);
|
||||||
|
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-options {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
font-size: var(--sn-stylekit-font-size-p);
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-clear-button {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
cursor: default;
|
||||||
|
background-color: var(--sn-stylekit-neutral-color);
|
||||||
|
color: var(--sn-stylekit-neutral-contrast-color);
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 17px;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 20%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
transition: background-color 0.15s linear;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--sn-stylekit-info-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.infinite-scroll {
|
||||||
|
@include minimal_scrollbar();
|
||||||
|
height: inherit;
|
||||||
|
background-color: var(--items-column-items-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-list-item {
|
||||||
|
&.selected, &:hover {
|
||||||
|
background-color: var(--item-cell-selected-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
background-color: var(--note-preview-progress-background-color);
|
||||||
|
color: var(--note-preview-progress-color);
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&::-webkit-progress-bar {
|
||||||
|
background-color: var(--note-preview-progress-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
background-color: var(--note-preview-progress-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-progress-bar {
|
||||||
|
background-color: var(--note-preview-progress-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: var(--item-cell-selected-background-color);
|
||||||
|
border-left: 2px solid var(--item-cell-selected-border-left-color);
|
||||||
|
|
||||||
|
progress {
|
||||||
|
background-color: var(--note-preview-selected-progress-background-color);
|
||||||
|
color: var(--note-preview-selected-progress-color);
|
||||||
|
|
||||||
|
&::-webkit-progress-bar {
|
||||||
|
background-color: var(--note-preview-selected-progress-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
background-color: var(--note-preview-progress-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-progress-bar {
|
||||||
|
background-color: var(--note-preview-progress-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#lock-screen {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
width: 100vw;
|
|
||||||
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
z-index: $z-index-lock-screen;
|
|
||||||
background-color: var(--sn-stylekit-background-color);
|
|
||||||
color: var(--sn-stylekit-foreground-color);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.sk-panel {
|
|
||||||
width: 315px;
|
|
||||||
flex-grow: 0;
|
|
||||||
// border-radius: 0;
|
|
||||||
|
|
||||||
.sk-panel-header {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#passcode-reset {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,6 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--sn-stylekit-foreground-color);
|
|
||||||
background-color: var(--sn-stylekit-background-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -50,14 +48,6 @@ body {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tinted {
|
|
||||||
color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tinted-selected {
|
|
||||||
color: var(--sn-stylekit-info-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: var(--sn-stylekit-font-size-h1);
|
font-size: var(--sn-stylekit-font-size-h1);
|
||||||
}
|
}
|
||||||
@@ -96,13 +86,13 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--sn-stylekit-info-color) !important; /* WebKit/Blink Browsers */
|
background: var(--text-selection-background-color) !important;
|
||||||
color: var(--sn-stylekit-info-contrast-color);
|
color: var(--text-selection-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-moz-selection {
|
::-moz-selection {
|
||||||
background: var(--sn-stylekit-info-color) !important;
|
background: var(--text-selection-background-color) !important;
|
||||||
color: var(--sn-stylekit-info-contrast-color);
|
color: var(--text-selection-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -115,7 +105,7 @@ p {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-color: var(--sn-stylekit-background-color);
|
background-color: var(--editor-header-bar-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
$footer-height: 2rem;
|
$footer-height: 2rem;
|
||||||
@@ -145,8 +135,7 @@ $footer-height: 2rem;
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
// needs to be a color that works on main bg and contrast bg
|
background-color: var(--panel-resizer-background-color);
|
||||||
background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
@@ -176,8 +165,6 @@ $footer-height: 2rem;
|
|||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
// so it blends in with editor a bit more
|
|
||||||
// background-color: var(--sn-stylekit-editor-background-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dragging {
|
&.dragging {
|
||||||
@@ -207,7 +194,6 @@ $footer-height: 2rem;
|
|||||||
> .content {
|
> .content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
background-color: var(--sn-stylekit-background-color);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,21 +231,3 @@ $footer-height: 2rem;
|
|||||||
.z-index-purchase-flow {
|
.z-index-purchase-flow {
|
||||||
z-index: $z-index-purchase-flow;
|
z-index: $z-index-purchase-flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
|
||||||
&.non-interactive {
|
|
||||||
user-select: text !important;
|
|
||||||
resize: none;
|
|
||||||
background-color: transparent;
|
|
||||||
border-color: var(--sn-stylekit-border-color);
|
|
||||||
font-family: monospace;
|
|
||||||
outline: 0;
|
|
||||||
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
-o-user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,10 +25,6 @@
|
|||||||
@extend .cursor-auto;
|
@extend .cursor-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sn-menu-border {
|
|
||||||
border: var(--sn-stylekit-menu-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sn-account-menu-headline {
|
.sn-account-menu-headline {
|
||||||
@extend .sk-h2;
|
@extend .sk-h2;
|
||||||
@extend .sk-bold;
|
@extend .sk-bold;
|
||||||
|
|||||||
@@ -64,7 +64,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--sn-stylekit-contrast-foreground-color);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ $content-horizontal-padding: 16px;
|
|||||||
#navigation-content {
|
#navigation-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: var(--sn-stylekit-secondary-background-color);
|
background-color: var(--navigation-column-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title-bar {
|
.section-title-bar {
|
||||||
color: var(--sn-stylekit-secondary-foreground-color);
|
color: var(--navigation-section-title-color);
|
||||||
padding-top: 0.8125rem;
|
padding-top: 0.8125rem;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
padding-left: $content-horizontal-padding;
|
padding-left: $content-horizontal-padding;
|
||||||
@@ -64,10 +64,6 @@ $content-horizontal-padding: 16px;
|
|||||||
.tag {
|
.tag {
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag,
|
.tag,
|
||||||
@@ -123,8 +119,8 @@ $content-horizontal-padding: 16px;
|
|||||||
width: 80%;
|
width: 80%;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--sn-stylekit-secondary-foreground-color);
|
color: var(--navigation-item-text-color);
|
||||||
-webkit-text-fill-color: var(--sn-stylekit-secondary-foreground-color);
|
-webkit-text-fill-color: var(--navigation-item-text-color);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -158,7 +154,7 @@ $content-horizontal-padding: 16px;
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--sn-stylekit-neutral-color);
|
color: var(--navigation-item-count-color);
|
||||||
min-width: 15px;
|
min-width: 15px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -201,12 +197,7 @@ $content-horizontal-padding: 16px;
|
|||||||
&:hover:not(.selected),
|
&:hover:not(.selected),
|
||||||
&.selected,
|
&.selected,
|
||||||
&.is-drag-over {
|
&.is-drag-over {
|
||||||
background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
background-color: var(--navigation-item-selected-background-color);
|
||||||
color: var(--sn-stylekit-secondary-contrast-foreground-color);
|
|
||||||
|
|
||||||
> .title {
|
|
||||||
color: var(--sn-stylekit-secondary-contrast-foreground-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,324 +0,0 @@
|
|||||||
@import './scrollbar';
|
|
||||||
|
|
||||||
#notes-column,
|
|
||||||
.notes {
|
|
||||||
border-left: 1px solid var(--sn-stylekit-border-color);
|
|
||||||
border-right: 1px solid var(--sn-stylekit-border-color);
|
|
||||||
font-size: var(--sn-stylekit-font-size-h2);
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
-moz-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
|
|
||||||
.empty-notes-list {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: var(--sn-stylekit-font-size-h3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notes-title-bar-container {
|
|
||||||
padding: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notes-title-bar {
|
|
||||||
font-weight: normal;
|
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
.section-title-bar-header .title {
|
|
||||||
width: calc(90% - 45px);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--sn-stylekit-font-size-p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#notes-menu-bar {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notes-options-menu {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-section {
|
|
||||||
clear: left;
|
|
||||||
max-height: 80px;
|
|
||||||
margin-top: 10px;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.filter-bar {
|
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
|
||||||
border-radius: var(--sn-stylekit-general-border-radius);
|
|
||||||
height: 100%;
|
|
||||||
color: #909090;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: var(--sn-stylekit-font-size-h3);
|
|
||||||
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-options {
|
|
||||||
margin-top: 10px;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
font-size: var(--sn-stylekit-font-size-p);
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-clear-button {
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 17px;
|
|
||||||
height: 17px;
|
|
||||||
cursor: default;
|
|
||||||
background-color: var(--sn-stylekit-neutral-color);
|
|
||||||
color: var(--sn-stylekit-neutral-contrast-color);
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 17px;
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 20%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
right: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
transition: background-color 0.15s linear;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.infinite-scroll {
|
|
||||||
@include minimal_scrollbar();
|
|
||||||
height: inherit;
|
|
||||||
background-color: var(--sn-stylekit-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 1rem;
|
|
||||||
padding-right: 0.75rem;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta {
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 1rem 0;
|
|
||||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
|
||||||
|
|
||||||
&.icon-hidden {
|
|
||||||
padding-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
word-break: break-word;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-info {
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.flag-icons {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 1rem;
|
|
||||||
padding-left: 0;
|
|
||||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > * + * {
|
|
||||||
margin-left: 0.375rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags-string {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.345rem;
|
|
||||||
font-size: 0.725rem;
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.25rem 0.375rem 0.25rem 0.325rem;
|
|
||||||
background-color: var(--sn-stylekit-grey-4-opacity-variant);
|
|
||||||
border-radius: 0.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview {
|
|
||||||
font-size: var(--sn-stylekit-font-size-h3);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
margin-top: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.default-preview,
|
|
||||||
.plain-preview {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 1; /* number of lines to show */
|
|
||||||
line-height: 1.3;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.html-preview {
|
|
||||||
margin-top: 4px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-flag {
|
|
||||||
color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-flags {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 0.125rem;
|
|
||||||
|
|
||||||
.flag {
|
|
||||||
padding: 4px;
|
|
||||||
padding-left: 6px;
|
|
||||||
padding-right: 6px;
|
|
||||||
border-radius: var(--sn-stylekit-general-border-radius);
|
|
||||||
margin-right: 4px;
|
|
||||||
margin-top: 4px;
|
|
||||||
|
|
||||||
&.info {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
color: var(--sn-stylekit-info-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.success {
|
|
||||||
background-color: var(--sn-stylekit-success-color);
|
|
||||||
color: var(--sn-stylekit-success-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warning {
|
|
||||||
background-color: var(--sn-stylekit-warning-color);
|
|
||||||
color: var(--sn-stylekit-warning-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.neutral {
|
|
||||||
background-color: var(--sn-stylekit-neutral-color);
|
|
||||||
color: var(--sn-stylekit-neutral-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.danger {
|
|
||||||
background-color: var(--sn-stylekit-danger-color);
|
|
||||||
color: var(--sn-stylekit-danger-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progress {
|
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
|
||||||
color: var(--sn-stylekit-info-color);
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&::-webkit-progress-bar {
|
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-progress-value {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-progress-bar {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
|
||||||
border-left: 2px solid var(--sn-stylekit-info-color);
|
|
||||||
|
|
||||||
progress {
|
|
||||||
background-color: var(--sn-stylekit-secondary-foreground-color);
|
|
||||||
color: var(--sn-stylekit-secondary-background-color);
|
|
||||||
|
|
||||||
&::-webkit-progress-bar {
|
|
||||||
background-color: var(--sn-stylekit-secondary-foreground-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-progress-value {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-progress-bar {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
@extend .border-1;
|
@extend .border-1;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: var(--sn-stylekit-neutral-color);
|
color: var(--preferences-navigation-icon-color);
|
||||||
@extend .text-base;
|
@extend .text-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
@extend .color-info;
|
@extend .color-info;
|
||||||
@extend .font-bold;
|
@extend .font-bold;
|
||||||
|
|
||||||
background-color: var(--sn-stylekit-info-backdrop-color);
|
background-color: var(--preferences-navigation-selected-background-color);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@extend .color-info;
|
@extend .color-info;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-reach-dialog-overlay]::before {
|
[data-reach-dialog-overlay]::before {
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
background-color: var(--sn-stylekit-contrast-background-color);
|
||||||
content: '';
|
content: '';
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.challenge-modal-overlay::before {
|
.challenge-modal-overlay::before {
|
||||||
background-color: var(--sn-stylekit-grey-5);
|
background-color: var(--sn-stylekit-grey-5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,10 @@
|
|||||||
margin-left: 0.125rem;
|
margin-left: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sn-component .ml-1\.5 {
|
||||||
|
margin-left: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-3 {
|
.ml-3 {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -299,6 +303,10 @@
|
|||||||
margin-top: 0.125rem;
|
margin-top: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-1\.5 {
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-2\.5 {
|
.mt-2\.5 {
|
||||||
margin-top: 0.625rem;
|
margin-top: 0.625rem;
|
||||||
}
|
}
|
||||||
@@ -538,6 +546,10 @@
|
|||||||
padding: 0.375rem;
|
padding: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.p-6 {
|
.p-6 {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
@@ -590,6 +602,14 @@
|
|||||||
padding-bottom: 0.625rem;
|
padding-bottom: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sn-component .pr-4 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sn-component .px-0 {
|
.sn-component .px-0 {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
@@ -656,6 +676,10 @@
|
|||||||
padding-bottom: 3rem;
|
padding-bottom: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pl-0 {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-2 {
|
.pl-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -858,7 +882,7 @@
|
|||||||
@extend .border-solid;
|
@extend .border-solid;
|
||||||
@extend .rounded-full;
|
@extend .rounded-full;
|
||||||
@extend .relative;
|
@extend .relative;
|
||||||
border-color: var(--sn-stylekit-grey-1);
|
border-color: var(--dropdown-menu-radio-button-inactive-color);
|
||||||
|
|
||||||
&--checked {
|
&--checked {
|
||||||
@extend .border-info;
|
@extend .border-info;
|
||||||
@@ -939,10 +963,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-info-contrast {
|
|
||||||
border-color: var(--sn-stylekit-info-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sn-icon-button {
|
.sn-icon-button {
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
@@ -995,6 +1015,14 @@
|
|||||||
line-height: 140%;
|
line-height: 140%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leading-1\.3 {
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leading-1\.4 {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
.dimmed {
|
.dimmed {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
@@ -1034,6 +1062,10 @@
|
|||||||
border-top-width: 0;
|
border-top-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sn-component .border-l-2px {
|
||||||
|
border-left-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.text-editor {
|
.text-editor {
|
||||||
font-size: var(--sn-stylekit-font-size-editor);
|
font-size: var(--sn-stylekit-font-size-editor);
|
||||||
}
|
}
|
||||||
@@ -1135,6 +1167,10 @@
|
|||||||
background-color: var(--sn-stylekit-background-color);
|
background-color: var(--sn-stylekit-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sn-component .hover\:bg-grey-5:hover {
|
||||||
|
background-color: var(--sn-stylekit-grey-5);
|
||||||
|
}
|
||||||
|
|
||||||
.sn-component .progress-bar {
|
.sn-component .progress-bar {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
background-color: var(--sn-stylekit-contrast-background-color);
|
||||||
@@ -1156,6 +1192,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.line-clamp-1 {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.sn-component .flex-row-reverse {
|
.sn-component .flex-row-reverse {
|
||||||
flex-flow: row-reverse;
|
flex-flow: row-reverse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sn-component {
|
.sn-component {
|
||||||
.sk-notification {
|
|
||||||
&.unpadded {
|
|
||||||
padding: 0;
|
|
||||||
padding-bottom: 0 !important;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.padded-row {
|
|
||||||
padding: 10px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bordered-row {
|
|
||||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
|
||||||
border-top: 1px solid var(--sn-stylekit-border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-app-bar {
|
.sk-app-bar {
|
||||||
&.dynamic-height {
|
&.dynamic-height {
|
||||||
min-height: 1.625rem !important;
|
min-height: 1.625rem !important;
|
||||||
@@ -86,7 +69,7 @@ a,
|
|||||||
.sk-a {
|
.sk-a {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--sn-stylekit-info-color);
|
color: var(--link-element-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.sk-a {
|
button.sk-a {
|
||||||
|
|||||||
44
app/assets/stylesheets/_theme.scss
Normal file
44
app/assets/stylesheets/_theme.scss
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
:root {
|
||||||
|
--modal-background-color: var(--sn-stylekit-background-color);
|
||||||
|
|
||||||
|
--editor-header-bar-background-color: var(--sn-stylekit-background-color);
|
||||||
|
--editor-background-color: var(--sn-stylekit-editor-background-color);
|
||||||
|
--editor-foreground-color: var(--sn-stylekit-editor-foreground-color);
|
||||||
|
--editor-title-bar-border-bottom-color: var(--sn-stylekit-border-color);
|
||||||
|
--editor-title-input-color: var(--sn-stylekit-editor-foreground-color);
|
||||||
|
--editor-pane-background-color: var(--sn-stylekit-background-color);
|
||||||
|
--editor-pane-editor-background-color: var(--sn-stylekit-editor-background-color);
|
||||||
|
--editor-pane-editor-foreground-color: var(--sn-stylekit-editor-foreground-color);
|
||||||
|
--editor-pane-component-stack-item-background-color: var(--sn-stylekit-background-color);
|
||||||
|
|
||||||
|
--text-selection-color: var(--sn-stylekit-info-contrast-color);
|
||||||
|
--text-selection-background-color: var(--sn-stylekit-info-color);
|
||||||
|
|
||||||
|
--note-preview-progress-color: var(--sn-stylekit-info-color);
|
||||||
|
--note-preview-progress-background-color: var(--sn-stylekit-contrast-background-color);
|
||||||
|
|
||||||
|
--note-preview-selected-progress-color: var(--sn-stylekit-secondary-background-color);
|
||||||
|
--note-preview-selected-progress-background-color: var(--sn-stylekit-secondary-foreground-color);
|
||||||
|
|
||||||
|
--items-column-background-color: var(--sn-stylekit-background-color);
|
||||||
|
--items-column-items-background-color: var(--sn-stylekit-background-color);
|
||||||
|
--items-column-border-left-color: var(--sn-stylekit-border-color);
|
||||||
|
--items-column-border-right-color: var(--sn-stylekit-border-color);
|
||||||
|
--items-column-search-background-color: var(--sn-stylekit-contrast-background-color);
|
||||||
|
--item-cell-selected-background-color: var(--sn-stylekit-grey-5);
|
||||||
|
--item-cell-selected-border-left-color: var(--sn-stylekit-info-color);
|
||||||
|
|
||||||
|
--navigation-column-background-color: var(--sn-stylekit-secondary-background-color);
|
||||||
|
--navigation-section-title-color: var(--sn-stylekit-secondary-foreground-color);
|
||||||
|
--navigation-item-text-color: var(--sn-stylekit-secondary-foreground-color);
|
||||||
|
--navigation-item-count-color: var(--sn-stylekit-neutral-color);
|
||||||
|
--navigation-item-selected-background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||||
|
|
||||||
|
--preferences-navigation-icon-color: var(--sn-stylekit-neutral-color);
|
||||||
|
--preferences-navigation-selected-background-color: var(--sn-stylekit-info-backdrop-color);
|
||||||
|
|
||||||
|
--dropdown-menu-radio-button-inactive-color: var(--sn-stylekit-grey-1);
|
||||||
|
|
||||||
|
--panel-resizer-background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||||
|
--link-element-color: var(--sn-stylekit-info-color);
|
||||||
|
}
|
||||||
@@ -164,9 +164,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
|||||||
border-radius: 0.3046875rem;
|
border-radius: 0.3046875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-main {
|
|
||||||
background-color: var(--sn-stylekit-info-color);
|
|
||||||
}
|
|
||||||
.bg-transparent {
|
.bg-transparent {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -243,9 +240,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
|||||||
.text-sm {
|
.text-sm {
|
||||||
font-size: var(--sn-stylekit-font-size-h5);
|
font-size: var(--sn-stylekit-font-size-h5);
|
||||||
}
|
}
|
||||||
.text-info-contrast {
|
|
||||||
color: var(--sn-stylekit-info-contrast-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
@import '@standardnotes/stylekit/dist/stylekit';
|
@import '@standardnotes/stylekit/dist/stylekit';
|
||||||
|
@import 'theme';
|
||||||
@import 'main';
|
@import 'main';
|
||||||
@import 'ui';
|
@import 'ui';
|
||||||
@import 'footer';
|
@import 'footer';
|
||||||
@import 'navigation';
|
@import 'navigation';
|
||||||
@import 'notes';
|
@import 'items-column';
|
||||||
@import 'editor';
|
@import 'editor';
|
||||||
@import 'menus';
|
@import 'menus';
|
||||||
@import 'modals';
|
@import 'modals';
|
||||||
@import 'lock-screen';
|
|
||||||
@import 'stylekit-sub';
|
@import 'stylekit-sub';
|
||||||
@import 'ionicons';
|
@import 'ionicons';
|
||||||
@import 'reach-sub';
|
@import 'reach-sub';
|
||||||
|
|||||||
@@ -70,11 +70,11 @@
|
|||||||
"@reach/tooltip": "^0.16.2",
|
"@reach/tooltip": "^0.16.2",
|
||||||
"@reach/visually-hidden": "^0.16.0",
|
"@reach/visually-hidden": "^0.16.0",
|
||||||
"@standardnotes/components": "1.8.1",
|
"@standardnotes/components": "1.8.1",
|
||||||
"@standardnotes/filepicker": "1.14.12",
|
"@standardnotes/filepicker": "1.15.0",
|
||||||
"@standardnotes/icons": "^1.1.7",
|
"@standardnotes/icons": "^1.1.7",
|
||||||
"@standardnotes/services": "^1.12.2",
|
"@standardnotes/services": "^1.13.1",
|
||||||
"@standardnotes/sncrypto-web": "1.10.1",
|
"@standardnotes/sncrypto-web": "1.10.1",
|
||||||
"@standardnotes/snjs": "2.110.3",
|
"@standardnotes/snjs": "2.113.0",
|
||||||
"@standardnotes/stylekit": "5.27.1",
|
"@standardnotes/stylekit": "5.27.1",
|
||||||
"@zip.js/zip.js": "^2.4.10",
|
"@zip.js/zip.js": "^2.4.10",
|
||||||
"mobx": "^6.5.0",
|
"mobx": "^6.5.0",
|
||||||
|
|||||||
174
yarn.lock
174
yarn.lock
@@ -2274,18 +2274,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@sinonjs/commons" "^1.7.0"
|
"@sinonjs/commons" "^1.7.0"
|
||||||
|
|
||||||
"@standardnotes/auth@^3.18.16":
|
"@standardnotes/auth@^3.18.17":
|
||||||
version "3.18.16"
|
version "3.18.17"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.18.16.tgz#56af6d347f7d66a9c07bfee31396d3e3486f2aa0"
|
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.18.17.tgz#1adff2f4e9e42a9843cbe235cf8dd35c158e894a"
|
||||||
integrity sha512-wrkBRrHs4oMMyZ1yB1fFCiulVPf+vrB/x5+fZnlhOFxFS6oRaq2xop0sa+XIDZD6qat54JxT2ati5LyJglZ1nw==
|
integrity sha512-RWA1z6xu9GD3/zdBXCmgMicSDGMG4vn/Ta0+LnEveQJTaDlNus+080lepiEwP20MyncUMqfvie/HSfqUMoM0iQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
|
|
||||||
"@standardnotes/common@^1.21.0":
|
"@standardnotes/common@^1.22.0":
|
||||||
version "1.21.0"
|
version "1.22.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.21.0.tgz#57cd8ddd65ca827966d65801c5270f17aa16189d"
|
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.22.0.tgz#397604fb4b92901bac276940a2647509b70a7ad2"
|
||||||
integrity sha512-TZKH/l2colOc68mn8FRTCoILpRHw5ZaJjpt/LPtPoRDy0ZPYbyKzRRzYVdoqZeEvJwDmGG24o3QB4xQpf3K0dA==
|
integrity sha512-xg2SU/Pq36O2ksSotuJm94ZEaVdFzpR6rVNwBAaZzjNtW41K4e107LOVfpu1Nv6Qykv79AKBbGzZ600f5QJ3jg==
|
||||||
|
|
||||||
"@standardnotes/components@1.8.1":
|
"@standardnotes/components@1.8.1":
|
||||||
version "1.8.1"
|
version "1.8.1"
|
||||||
@@ -2303,84 +2303,84 @@
|
|||||||
eslint-plugin-prettier "^4.0.0"
|
eslint-plugin-prettier "^4.0.0"
|
||||||
prettier "^2.6.2"
|
prettier "^2.6.2"
|
||||||
|
|
||||||
"@standardnotes/domain-events@^2.28.8":
|
"@standardnotes/domain-events@^2.28.9":
|
||||||
version "2.28.8"
|
version "2.28.9"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.28.8.tgz#7df9ea426b6723c0517c23e94853358b437a8cf9"
|
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.28.9.tgz#f2ad87ba29e45eb0d1991a3a91d917c49f5e3d99"
|
||||||
integrity sha512-OQuEWoVWyVBMdXAJtokGzQbhaRKVAs4/zShcSQLVhHHujoUIGJE9hJVu7uH4LqgPnLZ/tlaZGRylXWmK+7wSXQ==
|
integrity sha512-LsmvWW5v9sMFViNcuKL/CMgz0Dww6Qi5a6jJ8CzmmbgeNLuPj8FjiQGUV5u4iF70UR633EKMiHXdmh0Gub8gSA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.16"
|
"@standardnotes/auth" "^3.18.17"
|
||||||
"@standardnotes/features" "^1.44.1"
|
"@standardnotes/features" "^1.44.2"
|
||||||
|
|
||||||
"@standardnotes/encryption@^1.7.12":
|
"@standardnotes/encryption@^1.8.1":
|
||||||
version "1.7.12"
|
version "1.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/encryption/-/encryption-1.7.12.tgz#01edca2e84949ce64a0d3c05e438f89bc40b8e38"
|
resolved "https://registry.yarnpkg.com/@standardnotes/encryption/-/encryption-1.8.1.tgz#b3b0ba644d6cae0395687cd1eecea452d9109fb5"
|
||||||
integrity sha512-QOtYzdPOZhldBQ2t4C6bwBUvxsycyEfBzd/ii9UGknl1TovRR01a+B2A1DqeN8oQxBecvUC5B9Uxhxfsiq2efw==
|
integrity sha512-FEHH88xd4zvQ5vPm8GusjM50j/G/C/zDg1997z+IvPsWubqA7ZHBXJ+9Rul87QbMZDRpP/TzBaTViWpAc8DkaQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/models" "^1.8.8"
|
"@standardnotes/models" "^1.10.0"
|
||||||
"@standardnotes/responses" "^1.6.24"
|
"@standardnotes/responses" "^1.6.25"
|
||||||
"@standardnotes/services" "^1.12.2"
|
"@standardnotes/services" "^1.13.1"
|
||||||
|
|
||||||
"@standardnotes/features@^1.44.1":
|
"@standardnotes/features@^1.44.2":
|
||||||
version "1.44.1"
|
version "1.44.2"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.44.1.tgz#49ae92babbf29b2bc5608ccac5a1e2aa026d5b0b"
|
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.44.2.tgz#26a8240bf3fd88034d3f9202ffd55586098590b5"
|
||||||
integrity sha512-++2moW5tY6bv6ahin9rP9ay3UEezhktJR5LOILx6ZsuM4KUvx2trBapxpzzTysPVvW2zuiWWeJq82M4d4ChOEw==
|
integrity sha512-GEIFu4Fj42XTbnjUcipZHEVc3BtG2UtyGNB5IS1qXazluL5VLvY6Z42aOkWKnUX2bnJPPCpkJ7xLLijkjQtAew==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.16"
|
"@standardnotes/auth" "^3.18.17"
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
|
|
||||||
"@standardnotes/filepicker@1.14.12", "@standardnotes/filepicker@^1.14.12":
|
"@standardnotes/filepicker@1.15.0", "@standardnotes/filepicker@^1.15.0":
|
||||||
version "1.14.12"
|
version "1.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/filepicker/-/filepicker-1.14.12.tgz#ded6526f11ad7cb3309a1dc23955235350d35bef"
|
resolved "https://registry.yarnpkg.com/@standardnotes/filepicker/-/filepicker-1.15.0.tgz#d9a5e93d31c74c7ec0f8aedc16903e678fffaeb6"
|
||||||
integrity sha512-GYIc8f0eVDJfvWV2bagRq4oJ2Uj0XJ5lemf4EReZzPK0MTpqmqvCJrlYd8WzVc8OYBaWNNUwP6qs8Esop2wuRg==
|
integrity sha512-44cWnUNhSubsu3fWxlVEVnw25GwCn4N9c8J59a0m9VzAtYe0aeGc0/DGRlh7vB8SVOwaOtnIL3IvDMpn6rg71A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
"@standardnotes/services" "^1.12.2"
|
"@standardnotes/services" "^1.13.1"
|
||||||
"@standardnotes/utils" "^1.6.9"
|
"@standardnotes/utils" "^1.6.10"
|
||||||
|
|
||||||
"@standardnotes/files@^1.1.13":
|
"@standardnotes/files@^1.2.1":
|
||||||
version "1.1.13"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/files/-/files-1.1.13.tgz#0bc4a67c6a43538cf5fbbd2e8c34a72c5cdef61e"
|
resolved "https://registry.yarnpkg.com/@standardnotes/files/-/files-1.2.1.tgz#006b8fe9cd38b498984f6c6068c5c65c2160abb8"
|
||||||
integrity sha512-jEsz5+MbM3dKY3xPfjQ+FIz+0bzA2/e80hnmsqX0dzaMuYXtpEFuTG4gpkaSoGCLRm9vqJPUmgXf736s7+bs7g==
|
integrity sha512-Wf+p+dwxjvs1rkqwM8N9WTIjUhCsDivE/u+8hHy9yza/UHntfzcr4mqsE7wilNSZannMgp1SBlvW4+LT+FOX/w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/encryption" "^1.7.12"
|
"@standardnotes/encryption" "^1.8.1"
|
||||||
"@standardnotes/models" "^1.8.8"
|
"@standardnotes/models" "^1.10.0"
|
||||||
"@standardnotes/responses" "^1.6.24"
|
"@standardnotes/responses" "^1.6.25"
|
||||||
"@standardnotes/services" "^1.12.2"
|
"@standardnotes/services" "^1.13.1"
|
||||||
"@standardnotes/utils" "^1.6.9"
|
"@standardnotes/utils" "^1.6.10"
|
||||||
|
|
||||||
"@standardnotes/icons@^1.1.7":
|
"@standardnotes/icons@^1.1.7":
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/icons/-/icons-1.1.7.tgz#4523d8e2e3aa34d0e921df802d54c0babe054279"
|
resolved "https://registry.yarnpkg.com/@standardnotes/icons/-/icons-1.1.7.tgz#4523d8e2e3aa34d0e921df802d54c0babe054279"
|
||||||
integrity sha512-yHB/KInj7qSmtnUqtTo6CPttu+2+oAwdANfjE3a69sgS7nVBxUuwSHHxb9xUOKpJBmipFjJi5F0YVF9kLmHiwg==
|
integrity sha512-yHB/KInj7qSmtnUqtTo6CPttu+2+oAwdANfjE3a69sgS7nVBxUuwSHHxb9xUOKpJBmipFjJi5F0YVF9kLmHiwg==
|
||||||
|
|
||||||
"@standardnotes/models@^1.8.8":
|
"@standardnotes/models@^1.10.0":
|
||||||
version "1.8.8"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/models/-/models-1.8.8.tgz#a086b597a4da9169add163fa4770f1afb40d8598"
|
resolved "https://registry.yarnpkg.com/@standardnotes/models/-/models-1.10.0.tgz#7b8c27b8d5d7f8d60552ea0fafbae2b3061f4847"
|
||||||
integrity sha512-H/7BGU8wdkFNcQLhemdKUDmqoC5CacfVOU0uctTjXdOwW7PKMhtV2g7Fvj8lWWtnOYW86bKB7VV9uK7rgQcieQ==
|
integrity sha512-Pvi0zKDhVSvjs54ewXyrIjh9XdjTvG1dK/Z7MfPDjEPWUz8/XnaVnFuDo/0riCMTR77op3H5q402qfx3jS2dLA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/features" "^1.44.1"
|
"@standardnotes/features" "^1.44.2"
|
||||||
"@standardnotes/responses" "^1.6.24"
|
"@standardnotes/responses" "^1.6.25"
|
||||||
"@standardnotes/utils" "^1.6.9"
|
"@standardnotes/utils" "^1.6.10"
|
||||||
|
|
||||||
"@standardnotes/responses@^1.6.24":
|
"@standardnotes/responses@^1.6.25":
|
||||||
version "1.6.24"
|
version "1.6.25"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.6.24.tgz#4ba71a17b70b0ba70445cdec5d1fee66ee455888"
|
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.6.25.tgz#96867fd1154bbf6004b87d4fb87d4c37c13f0534"
|
||||||
integrity sha512-XYZ0ndkLSoPAmsTig1ER6DdN+aFWge6DS4NRCw+bYXcKu73Km/r8F5Q44nOKIqj6Hvn0jDceD276UcUpesahqw==
|
integrity sha512-sEWx7izTaeaO5gWPMeEkX0oNOVWkBEelK1na6cy5oIf81nAsVQ3sQbr8qRXXs7nnGnJR+PeUTEwJA0aHyr156g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.16"
|
"@standardnotes/auth" "^3.18.17"
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
"@standardnotes/features" "^1.44.1"
|
"@standardnotes/features" "^1.44.2"
|
||||||
|
|
||||||
"@standardnotes/services@^1.12.2":
|
"@standardnotes/services@^1.13.1":
|
||||||
version "1.12.2"
|
version "1.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.12.2.tgz#efc52192ac63668b2de5bcc7c3a5d082b40d942c"
|
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.13.1.tgz#e727c35f2eecbc5d6a4e17470f13d90a84e7203f"
|
||||||
integrity sha512-n+taTbHxnA63bFgioJGCoAqASbk6CFlLzYQdaoV/vAyyVntN4vyuy9hb06ku6kl2vBQQbTJse7CPHe9W1istWQ==
|
integrity sha512-0cu3Glla4Ir7k5pgfD1ztNxQ37/xOE/0+MvLnU98bzAdI2oqeS9POOpPeu2EKxYU1hKjacPZTZEaHl2iR7PP0w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.16"
|
"@standardnotes/auth" "^3.18.17"
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
"@standardnotes/models" "^1.8.8"
|
"@standardnotes/models" "^1.10.0"
|
||||||
"@standardnotes/responses" "^1.6.24"
|
"@standardnotes/responses" "^1.6.25"
|
||||||
"@standardnotes/utils" "^1.6.9"
|
"@standardnotes/utils" "^1.6.10"
|
||||||
|
|
||||||
"@standardnotes/settings@^1.14.3":
|
"@standardnotes/settings@^1.14.3":
|
||||||
version "1.14.3"
|
version "1.14.3"
|
||||||
@@ -2401,24 +2401,24 @@
|
|||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
libsodium-wrappers "^0.7.9"
|
libsodium-wrappers "^0.7.9"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.110.3":
|
"@standardnotes/snjs@2.113.0":
|
||||||
version "2.110.3"
|
version "2.113.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.110.3.tgz#ea9fc61598a8ddebac342e74f2cfb7fce45ac4f4"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.113.0.tgz#4dd65cafc633da1c0a7e04a23ee7f0fed9ba80cf"
|
||||||
integrity sha512-oXj0cSs+NxCHahqo3hYr/nGlOP0u+jPmsDDCUfG1XAOi/mftMTXTPeIWzNceNYVZNLoF/HmDME3zlcWn7WTXXw==
|
integrity sha512-fZHZbFDfZWHkpQsz8qOBy4Q0kbdmHNtW28cbbzwGKglVbmWeCEb07DG3/vkCMSJj+4UiHUw6lWv8D7qm08k7rw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.16"
|
"@standardnotes/auth" "^3.18.17"
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
"@standardnotes/domain-events" "^2.28.8"
|
"@standardnotes/domain-events" "^2.28.9"
|
||||||
"@standardnotes/encryption" "^1.7.12"
|
"@standardnotes/encryption" "^1.8.1"
|
||||||
"@standardnotes/features" "^1.44.1"
|
"@standardnotes/features" "^1.44.2"
|
||||||
"@standardnotes/filepicker" "^1.14.12"
|
"@standardnotes/filepicker" "^1.15.0"
|
||||||
"@standardnotes/files" "^1.1.13"
|
"@standardnotes/files" "^1.2.1"
|
||||||
"@standardnotes/models" "^1.8.8"
|
"@standardnotes/models" "^1.10.0"
|
||||||
"@standardnotes/responses" "^1.6.24"
|
"@standardnotes/responses" "^1.6.25"
|
||||||
"@standardnotes/services" "^1.12.2"
|
"@standardnotes/services" "^1.13.1"
|
||||||
"@standardnotes/settings" "^1.14.3"
|
"@standardnotes/settings" "^1.14.3"
|
||||||
"@standardnotes/sncrypto-common" "^1.9.0"
|
"@standardnotes/sncrypto-common" "^1.9.0"
|
||||||
"@standardnotes/utils" "^1.6.9"
|
"@standardnotes/utils" "^1.6.10"
|
||||||
|
|
||||||
"@standardnotes/stylekit@5.27.1":
|
"@standardnotes/stylekit@5.27.1":
|
||||||
version "5.27.1"
|
version "5.27.1"
|
||||||
@@ -2433,12 +2433,12 @@
|
|||||||
nanostores "^0.5.10"
|
nanostores "^0.5.10"
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
"@standardnotes/utils@^1.6.9":
|
"@standardnotes/utils@^1.6.10":
|
||||||
version "1.6.9"
|
version "1.6.10"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/utils/-/utils-1.6.9.tgz#dcfbcd1a16455b327dd714fa299f4092542ce061"
|
resolved "https://registry.yarnpkg.com/@standardnotes/utils/-/utils-1.6.10.tgz#25816fd072ebe4b0e83585237bfd7ba528b4faef"
|
||||||
integrity sha512-Umog99gJgvVx/EDDsaF63+KXg7jkWL8qAgLT/oxLI8/kRGQfe+16nwlIPR/QkaTHJa415mvChz2aIhf4now0/w==
|
integrity sha512-XXWAx67CMMRZBFcLYbOxeAIBhlPAziU+hBcwHfHMiZ1qZ+2/tNEVZHsXIcBPwZAQ0bvGWE7TIgCSaTk45qdKUg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/common" "^1.21.0"
|
"@standardnotes/common" "^1.22.0"
|
||||||
dompurify "^2.3.6"
|
dompurify "^2.3.6"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user