refactor: repo (#1070)
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
|
||||
type Props = {
|
||||
onMouseLeave: () => void
|
||||
onMouseOver: () => void
|
||||
onClick: () => void
|
||||
showLockedIcon: boolean
|
||||
lockText: string
|
||||
}
|
||||
|
||||
const EditingDisabledBanner: FunctionComponent<Props> = ({
|
||||
onMouseLeave,
|
||||
onMouseOver,
|
||||
onClick,
|
||||
showLockedIcon,
|
||||
lockText,
|
||||
}) => {
|
||||
const background = showLockedIcon ? 'bg-warning-faded' : 'bg-info-faded'
|
||||
const iconColor = showLockedIcon ? 'color-accessory-tint-3' : 'color-accessory-tint-1'
|
||||
const textColor = showLockedIcon ? 'color-warning' : 'color-accessory-tint-1'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center relative ${background} px-3.5 py-2 cursor-pointer`}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOver={onMouseOver}
|
||||
onClick={onClick}
|
||||
>
|
||||
{showLockedIcon ? (
|
||||
<Icon type="pencil-off" className={`${iconColor} flex fill-current mr-3`} />
|
||||
) : (
|
||||
<Icon type="pencil" className={`${iconColor} flex fill-current mr-3`} />
|
||||
)}
|
||||
<span className={textColor}>{lockText}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditingDisabledBanner
|
||||
@@ -0,0 +1,9 @@
|
||||
export const reloadFont = (monospaceFont?: boolean) => {
|
||||
const root = document.querySelector(':root') as HTMLElement
|
||||
const propertyName = '--sn-stylekit-editor-font-family'
|
||||
if (monospaceFont) {
|
||||
root.style.setProperty(propertyName, 'var(--sn-stylekit-monospace-font)')
|
||||
} else {
|
||||
root.style.setProperty(propertyName, 'var(--sn-stylekit-sans-serif-font)')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||
NoteViewController,
|
||||
SNNote,
|
||||
} from '@standardnotes/snjs'
|
||||
|
||||
import NoteView from './NoteView'
|
||||
|
||||
describe('NoteView', () => {
|
||||
let noteViewController: NoteViewController
|
||||
let application: WebApplication
|
||||
let viewControllerManager: ViewControllerManager
|
||||
let notesState: NotesController
|
||||
|
||||
const createNoteView = () =>
|
||||
new NoteView({
|
||||
controller: noteViewController,
|
||||
application,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
noteViewController = {} as jest.Mocked<NoteViewController>
|
||||
|
||||
notesState = {} as jest.Mocked<NotesController>
|
||||
notesState.setShowProtectedWarning = jest.fn()
|
||||
|
||||
viewControllerManager = {
|
||||
notesController: notesState,
|
||||
} as jest.Mocked<ViewControllerManager>
|
||||
|
||||
application = {} as jest.Mocked<WebApplication>
|
||||
application.getViewControllerManager = jest.fn().mockReturnValue(viewControllerManager)
|
||||
application.hasProtectionSources = jest.fn().mockReturnValue(true)
|
||||
application.authorizeNoteAccess = jest.fn()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
describe('note is protected', () => {
|
||||
it("should hide the note if at the time of the session expiration the note wasn't edited for longer than the allowed idle time", async () => {
|
||||
const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5
|
||||
|
||||
noteViewController.note = {
|
||||
protected: true,
|
||||
userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||
|
||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('should postpone the note hiding by correct time if the time passed after its last modification is less than the allowed idle time', async () => {
|
||||
const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3
|
||||
|
||||
noteViewController.note = {
|
||||
protected: true,
|
||||
userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||
|
||||
const secondsAfterWhichTheNoteShouldHide =
|
||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastEdit
|
||||
|
||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||
|
||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||
|
||||
jest.advanceTimersByTime(1 * 1000)
|
||||
|
||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('should postpone the note hiding by correct time if the user continued editing it even after the protection session has expired', async () => {
|
||||
const secondsElapsedSinceLastModification = 3
|
||||
|
||||
noteViewController.note = {
|
||||
protected: true,
|
||||
userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastModification * 1000),
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||
|
||||
let secondsAfterWhichTheNoteShouldHide =
|
||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastModification
|
||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||
|
||||
noteViewController.note = {
|
||||
protected: true,
|
||||
userModifiedDate: new Date(),
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
secondsAfterWhichTheNoteShouldHide = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
|
||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||
|
||||
jest.advanceTimersByTime(1 * 1000)
|
||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('note is unprotected', () => {
|
||||
it('should not call any hiding logic', async () => {
|
||||
noteViewController.note = {
|
||||
protected: false,
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
await createNoteView().onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||
|
||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dismissProtectedWarning', () => {
|
||||
beforeEach(() => {
|
||||
noteViewController.note = {
|
||||
protected: false,
|
||||
} as jest.Mocked<SNNote>
|
||||
})
|
||||
|
||||
describe('the note has protection sources', () => {
|
||||
it('should reveal note contents if the authorization has been passed', async () => {
|
||||
application.authorizeNoteAccess = jest.fn().mockReturnValue(true)
|
||||
|
||||
const noteView = new NoteView({
|
||||
controller: noteViewController,
|
||||
application,
|
||||
})
|
||||
|
||||
await noteView.dismissProtectedWarning()
|
||||
|
||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(false)
|
||||
})
|
||||
|
||||
it('should not reveal note contents if the authorization has not been passed', async () => {
|
||||
application.authorizeNoteAccess = jest.fn().mockReturnValue(false)
|
||||
|
||||
const noteView = new NoteView({
|
||||
controller: noteViewController,
|
||||
application,
|
||||
})
|
||||
|
||||
await noteView.dismissProtectedWarning()
|
||||
|
||||
expect(notesState.setShowProtectedWarning).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('the note does not have protection sources', () => {
|
||||
it('should reveal note contents', async () => {
|
||||
application.hasProtectionSources = jest.fn().mockReturnValue(false)
|
||||
|
||||
const noteView = new NoteView({
|
||||
controller: noteViewController,
|
||||
application,
|
||||
})
|
||||
|
||||
await noteView.dismissProtectedWarning()
|
||||
|
||||
expect(notesState.setShowProtectedWarning).toHaveBeenCalledWith(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
1093
packages/web/src/javascripts/Components/NoteView/NoteView.tsx
Normal file
1093
packages/web/src/javascripts/Components/NoteView/NoteView.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
import { NoteViewController } from '@standardnotes/snjs'
|
||||
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
|
||||
export interface NoteViewProps {
|
||||
application: WebApplication
|
||||
controller: NoteViewController
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { SNComponent, SNNote, ComponentMutator, TransactionalMutation, ItemMutator } from '@standardnotes/snjs'
|
||||
|
||||
export const transactionForAssociateComponentWithCurrentNote = (component: SNComponent, note: SNNote) => {
|
||||
const transaction: TransactionalMutation = {
|
||||
itemUuid: component.uuid,
|
||||
mutate: (m: ItemMutator) => {
|
||||
const mutator = m as ComponentMutator
|
||||
mutator.removeDisassociatedItemId(note.uuid)
|
||||
mutator.associateWithItem(note.uuid)
|
||||
},
|
||||
}
|
||||
return transaction
|
||||
}
|
||||
|
||||
export const transactionForDisassociateComponentWithCurrentNote = (component: SNComponent, note: SNNote) => {
|
||||
const transaction: TransactionalMutation = {
|
||||
itemUuid: component.uuid,
|
||||
mutate: (m: ItemMutator) => {
|
||||
const mutator = m as ComponentMutator
|
||||
mutator.removeAssociatedItemId(note.uuid)
|
||||
mutator.disassociateWithItem(note.uuid)
|
||||
},
|
||||
}
|
||||
return transaction
|
||||
}
|
||||
Reference in New Issue
Block a user