feat: Add note attributes to notes options menu (#681)
This commit is contained in:
@@ -2,7 +2,7 @@ import { AppState } from '@/ui_models/app_state';
|
|||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { Switch } from './Switch';
|
import { Switch } from './Switch';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useRef, useState, useEffect } from 'preact/hooks';
|
import { useRef, useState, useEffect, useMemo } from 'preact/hooks';
|
||||||
import {
|
import {
|
||||||
Disclosure,
|
Disclosure,
|
||||||
DisclosureButton,
|
DisclosureButton,
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { SNNote } from '@standardnotes/snjs/dist/@types';
|
import { SNNote } from '@standardnotes/snjs/dist/@types';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { KeyboardModifier } from '@/services/ioService';
|
import { KeyboardModifier } from '@/services/ioService';
|
||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
@@ -20,9 +21,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type DeletePermanentlyButtonProps = {
|
type DeletePermanentlyButtonProps = {
|
||||||
closeOnBlur: Props["closeOnBlur"];
|
closeOnBlur: Props['closeOnBlur'];
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
const DeletePermanentlyButton = ({
|
const DeletePermanentlyButton = ({
|
||||||
closeOnBlur,
|
closeOnBlur,
|
||||||
@@ -34,6 +35,87 @@ const DeletePermanentlyButton = ({
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const countNoteAttributes = (text: string) => {
|
||||||
|
try {
|
||||||
|
JSON.parse(text);
|
||||||
|
return {
|
||||||
|
characters: 'N/A',
|
||||||
|
words: 'N/A',
|
||||||
|
paragraphs: 'N/A',
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
const characters = text.length;
|
||||||
|
const words = text.match(/[\w’'-]+\b/g)?.length;
|
||||||
|
const paragraphs = text.replace(/\n$/gm, '').split(/\n/).length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
characters,
|
||||||
|
words,
|
||||||
|
paragraphs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateReadTime = (words: number) => {
|
||||||
|
const timeToRead = Math.round(words / 200);
|
||||||
|
if (timeToRead === 0) {
|
||||||
|
return '< 1 minute';
|
||||||
|
} else {
|
||||||
|
return `${timeToRead} ${timeToRead > 1 ? 'minutes' : 'minute'}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (date: Date | undefined) => {
|
||||||
|
if (!date) return;
|
||||||
|
return `${date.toDateString()} ${date.toLocaleTimeString()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoteAttributes: FunctionComponent<{ note: SNNote }> = ({ note }) => {
|
||||||
|
const { words, characters, paragraphs } = useMemo(
|
||||||
|
() => countNoteAttributes(note.text),
|
||||||
|
[note.text]
|
||||||
|
);
|
||||||
|
|
||||||
|
const readTime = useMemo(
|
||||||
|
() => (typeof words === 'number' ? calculateReadTime(words) : 'N/A'),
|
||||||
|
[words]
|
||||||
|
);
|
||||||
|
|
||||||
|
const dateLastModified = useMemo(
|
||||||
|
() => formatDate(note.serverUpdatedAt),
|
||||||
|
[note.serverUpdatedAt]
|
||||||
|
);
|
||||||
|
|
||||||
|
const dateCreated = useMemo(
|
||||||
|
() => formatDate(note.created_at),
|
||||||
|
[note.created_at]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-3 pt-1.5 pb-1 text-xs color-neutral font-medium">
|
||||||
|
{typeof words === 'number' ? (
|
||||||
|
<>
|
||||||
|
<div className="mb-1">
|
||||||
|
{words} words · {characters} characters · {paragraphs} paragraphs
|
||||||
|
</div>
|
||||||
|
<div className="mb-1">
|
||||||
|
<span className="font-semibold">Read time:</span> {readTime}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<div className="mb-1">
|
||||||
|
<span className="font-semibold">Last modified:</span> {dateLastModified}
|
||||||
|
</div>
|
||||||
|
<div className="mb-1">
|
||||||
|
<span className="font-semibold">Created:</span> {dateCreated}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-semibold">Note ID:</span> {note.uuid}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const NotesOptions = observer(
|
export const NotesOptions = observer(
|
||||||
({ application, appState, closeOnBlur, onSubmenuChange }: Props) => {
|
({ application, appState, closeOnBlur, onSubmenuChange }: Props) => {
|
||||||
const [tagsMenuOpen, setTagsMenuOpen] = useState(false);
|
const [tagsMenuOpen, setTagsMenuOpen] = useState(false);
|
||||||
@@ -45,8 +127,9 @@ export const NotesOptions = observer(
|
|||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
});
|
});
|
||||||
const [tagsMenuMaxHeight, setTagsMenuMaxHeight] =
|
const [tagsMenuMaxHeight, setTagsMenuMaxHeight] = useState<number | 'auto'>(
|
||||||
useState<number | 'auto'>('auto');
|
'auto'
|
||||||
|
);
|
||||||
const [altKeyDown, setAltKeyDown] = useState(false);
|
const [altKeyDown, setAltKeyDown] = useState(false);
|
||||||
|
|
||||||
const toggleOn = (condition: (note: SNNote) => boolean) => {
|
const toggleOn = (condition: (note: SNNote) => boolean) => {
|
||||||
@@ -86,7 +169,7 @@ export const NotesOptions = observer(
|
|||||||
},
|
},
|
||||||
onKeyUp: () => {
|
onKeyUp: () => {
|
||||||
setAltKeyDown(false);
|
setAltKeyDown(false);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -163,7 +246,7 @@ export const NotesOptions = observer(
|
|||||||
Protect
|
Protect
|
||||||
</span>
|
</span>
|
||||||
</Switch>
|
</Switch>
|
||||||
<div className="h-1px my-2 bg-border"></div>
|
<div className="min-h-1px my-2 bg-border"></div>
|
||||||
{appState.tags.tagsCount > 0 && (
|
{appState.tags.tagsCount > 0 && (
|
||||||
<Disclosure open={tagsMenuOpen} onChange={openTagsMenu}>
|
<Disclosure open={tagsMenuOpen} onChange={openTagsMenu}>
|
||||||
<DisclosureButton
|
<DisclosureButton
|
||||||
@@ -327,6 +410,12 @@ export const NotesOptions = observer(
|
|||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{notes.length === 1 ? (
|
||||||
|
<>
|
||||||
|
<div className="min-h-1px my-2 bg-border"></div>
|
||||||
|
<NoteAttributes note={notes[0]} />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,10 @@
|
|||||||
min-width: 3.75rem;
|
min-width: 3.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.min-h-1px {
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.min-h-1 {
|
.min-h-1 {
|
||||||
min-height: 0.25rem;
|
min-height: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user