refactor: extract shared logic to active note state
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { SNTag } from '@standardnotes/snjs';
|
import { SNTag } from '@standardnotes/snjs';
|
||||||
import { FunctionalComponent, RefObject } from 'preact';
|
import { FunctionalComponent } from 'preact';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { Disclosure, DisclosurePanel } from '@reach/disclosure';
|
import { Disclosure, DisclosurePanel } from '@reach/disclosure';
|
||||||
@@ -10,15 +10,13 @@ import { AppState } from '@/ui_models/app_state';
|
|||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
tagsRef: RefObject<HTMLButtonElement[]>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AutocompleteTagInput: FunctionalComponent<Props> = ({
|
export const AutocompleteTagInput: FunctionalComponent<Props> = ({
|
||||||
application,
|
application,
|
||||||
appState,
|
appState,
|
||||||
tagsRef,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { tags, tagsContainerMaxWidth, tagsOverflowed } = appState.activeNote;
|
const { tagElements, tags, tagsContainerMaxWidth, tagsOverflowed } = appState.activeNote;
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [dropdownVisible, setDropdownVisible] = useState(false);
|
const [dropdownVisible, setDropdownVisible] = useState(false);
|
||||||
@@ -85,13 +83,9 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reloadInputOverflowed = useCallback(() => {
|
const reloadInputOverflowed = useCallback(() => {
|
||||||
let overflowed = false;
|
const overflowed = !tagsOverflowed && appState.activeNote.isElementOverflowed(inputRef.current);
|
||||||
if (!tagsOverflowed && tagsRef.current && tagsRef.current.length > 0) {
|
|
||||||
const firstTagTop = tagsRef.current[0].offsetTop;
|
|
||||||
overflowed = inputRef.current.offsetTop > firstTagTop;
|
|
||||||
}
|
|
||||||
appState.activeNote.setInputOverflowed(overflowed);
|
appState.activeNote.setInputOverflowed(overflowed);
|
||||||
}, [appState.activeNote, tagsOverflowed, tagsRef]);
|
}, [appState.activeNote, tagsOverflowed]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reloadInputOverflowed();
|
reloadInputOverflowed();
|
||||||
@@ -124,10 +118,9 @@ export const AutocompleteTagInput: FunctionalComponent<Props> = ({
|
|||||||
if (
|
if (
|
||||||
event.key === 'Backspace' &&
|
event.key === 'Backspace' &&
|
||||||
searchQuery === '' &&
|
searchQuery === '' &&
|
||||||
tagsRef.current &&
|
tagElements.length > 0
|
||||||
tagsRef.current.length > 1
|
|
||||||
) {
|
) {
|
||||||
tagsRef.current[tagsRef.current.length - 1].focus();
|
tagElements[tagElements.length - 1]?.focus();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { FunctionalComponent, RefObject } from 'preact';
|
import { FunctionalComponent, RefObject } from 'preact';
|
||||||
import { useRef, useState } from 'preact/hooks';
|
import { useCallback, useRef, useState } from 'preact/hooks';
|
||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { SNTag } from '@standardnotes/snjs/dist/@types';
|
import { SNTag } from '@standardnotes/snjs/dist/@types';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
index: number;
|
|
||||||
tagsRef: RefObject<HTMLButtonElement[]>;
|
|
||||||
tag: SNTag;
|
tag: SNTag;
|
||||||
overflowed: boolean;
|
overflowButtonRef: RefObject<HTMLButtonElement>;
|
||||||
maxWidth: number | 'auto';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoteTag: FunctionalComponent<Props> = ({
|
export const NoteTag: FunctionalComponent<Props> = ({ appState, tag, overflowButtonRef }) => {
|
||||||
appState,
|
const {
|
||||||
index,
|
tags,
|
||||||
tagsRef,
|
tagsContainerMaxWidth,
|
||||||
tag,
|
} = appState.activeNote;
|
||||||
overflowed,
|
|
||||||
maxWidth,
|
const [overflowed, setOverflowed] = useState(false);
|
||||||
}) => {
|
|
||||||
const [showDeleteButton, setShowDeleteButton] = useState(false);
|
const [showDeleteButton, setShowDeleteButton] = useState(false);
|
||||||
|
|
||||||
const deleteTagRef = useRef<HTMLButtonElement>();
|
const deleteTagRef = useRef<HTMLButtonElement>();
|
||||||
|
|
||||||
const deleteTag = async () => {
|
const deleteTag = async () => {
|
||||||
await appState.activeNote.removeTagFromActiveNote(tag);
|
await appState.activeNote.removeTagFromActiveNote(tag);
|
||||||
|
const previousTag = appState.activeNote.getPreviousTag(tag);
|
||||||
|
|
||||||
if (index > 0 && tagsRef.current) {
|
if (previousTag) {
|
||||||
tagsRef.current[index - 1].focus();
|
const previousTagElement = appState.activeNote.getTagElement(previousTag);
|
||||||
|
previousTagElement?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,21 +42,33 @@ export const NoteTag: FunctionalComponent<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onBlur = (event: FocusEvent) => {
|
const onBlur = (event: FocusEvent) => {
|
||||||
appState.activeNote.setTagFocused(false);
|
const relatedTarget = event.relatedTarget as Node;
|
||||||
if ((event.relatedTarget as Node) !== deleteTagRef.current) {
|
if (relatedTarget === overflowButtonRef.current) {
|
||||||
|
(event.target as HTMLButtonElement).focus();
|
||||||
|
} else if (relatedTarget !== deleteTagRef.current) {
|
||||||
|
appState.activeNote.setTagFocused(false);
|
||||||
setShowDeleteButton(false);
|
setShowDeleteButton(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reloadOverflowed = useCallback(() => {
|
||||||
|
const overflowed = appState.activeNote.isTagOverflowed(tag);
|
||||||
|
setOverflowed(overflowed);
|
||||||
|
}, [appState.activeNote, tag]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reloadOverflowed();
|
||||||
|
}, [reloadOverflowed, tags, tagsContainerMaxWidth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
ref={(element) => {
|
ref={(element) => {
|
||||||
if (element && tagsRef.current) {
|
if (element) {
|
||||||
tagsRef.current[index] = element;
|
appState.activeNote.setTagElement(tag, element);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="sn-tag pl-1 pr-2 mr-2"
|
className="sn-tag pl-1 pr-2 mr-2"
|
||||||
style={{ maxWidth }}
|
style={{ maxWidth: tagsContainerMaxWidth }}
|
||||||
onClick={onTagClick}
|
onClick={onTagClick}
|
||||||
onKeyUp={(event) => {
|
onKeyUp={(event) => {
|
||||||
if (event.key === 'Backspace') {
|
if (event.key === 'Backspace') {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { toDirective, useCloseOnClickOutside } from './utils';
|
|||||||
import { AutocompleteTagInput } from './AutocompleteTagInput';
|
import { AutocompleteTagInput } from './AutocompleteTagInput';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { SNTag } from '@standardnotes/snjs';
|
|
||||||
import { NoteTag } from './NoteTag';
|
import { NoteTag } from './NoteTag';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -15,7 +14,9 @@ type Props = {
|
|||||||
const NoteTagsContainer = observer(({ application, appState }: Props) => {
|
const NoteTagsContainer = observer(({ application, appState }: Props) => {
|
||||||
const {
|
const {
|
||||||
inputOverflowed,
|
inputOverflowed,
|
||||||
|
overflowCountPosition,
|
||||||
overflowedTagsCount,
|
overflowedTagsCount,
|
||||||
|
tagElements,
|
||||||
tags,
|
tags,
|
||||||
tagsContainerMaxWidth,
|
tagsContainerMaxWidth,
|
||||||
tagsContainerExpanded,
|
tagsContainerExpanded,
|
||||||
@@ -23,15 +24,9 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
|
|||||||
} = appState.activeNote;
|
} = appState.activeNote;
|
||||||
|
|
||||||
const [expandedContainerHeight, setExpandedContainerHeight] = useState(0);
|
const [expandedContainerHeight, setExpandedContainerHeight] = useState(0);
|
||||||
const [lastVisibleTagIndex, setLastVisibleTagIndex] =
|
|
||||||
useState<number | null>(null);
|
|
||||||
const [overflowCountPosition, setOverflowCountPosition] = useState(0);
|
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
|
||||||
const tagsContainerRef = useRef<HTMLDivElement>();
|
const tagsContainerRef = useRef<HTMLDivElement>();
|
||||||
const tagsRef = useRef<HTMLButtonElement[]>([]);
|
const overflowButtonRef = useRef<HTMLButtonElement>();
|
||||||
|
|
||||||
tagsRef.current = [];
|
|
||||||
|
|
||||||
useCloseOnClickOutside(tagsContainerRef, (expanded: boolean) => {
|
useCloseOnClickOutside(tagsContainerRef, (expanded: boolean) => {
|
||||||
if (tagsContainerExpanded) {
|
if (tagsContainerExpanded) {
|
||||||
@@ -39,88 +34,29 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isTagOverflowed = useCallback(
|
const reloadExpandedContainerHeight = useCallback(() => {
|
||||||
(tagElement?: HTMLButtonElement): boolean | undefined => {
|
|
||||||
if (!tagElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tagsContainerExpanded) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const firstTagTop = tagsRef.current[0].offsetTop;
|
|
||||||
return tagElement.offsetTop > firstTagTop;
|
|
||||||
},
|
|
||||||
[tagsContainerExpanded]
|
|
||||||
);
|
|
||||||
|
|
||||||
const reloadLastVisibleTagIndex = useCallback(() => {
|
|
||||||
if (tagsContainerExpanded) {
|
|
||||||
return tags.length - 1;
|
|
||||||
}
|
|
||||||
const firstOverflowedTagIndex = tagsRef.current.findIndex((tagElement) =>
|
|
||||||
isTagOverflowed(tagElement)
|
|
||||||
);
|
|
||||||
if (firstOverflowedTagIndex > -1) {
|
|
||||||
setLastVisibleTagIndex(firstOverflowedTagIndex - 1);
|
|
||||||
} else {
|
|
||||||
setLastVisibleTagIndex(null);
|
|
||||||
}
|
|
||||||
}, [isTagOverflowed, tags, tagsContainerExpanded]);
|
|
||||||
|
|
||||||
const reloadExpandedContainersHeight = useCallback(() => {
|
|
||||||
setExpandedContainerHeight(tagsContainerRef.current.scrollHeight);
|
setExpandedContainerHeight(tagsContainerRef.current.scrollHeight);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const reloadOverflowCount = useCallback(() => {
|
useEffect(() => {
|
||||||
const count = tagsRef.current.filter((tagElement) =>
|
|
||||||
isTagOverflowed(tagElement)
|
|
||||||
).length;
|
|
||||||
appState.activeNote.setOverflowedTagsCount(count);
|
|
||||||
}, [appState.activeNote, isTagOverflowed]);
|
|
||||||
|
|
||||||
const reloadOverflowCountPosition = useCallback(() => {
|
|
||||||
if (tagsContainerExpanded || !lastVisibleTagIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tagsRef.current[lastVisibleTagIndex]) {
|
|
||||||
const {
|
|
||||||
offsetLeft: lastVisibleTagLeft,
|
|
||||||
clientWidth: lastVisibleTagWidth,
|
|
||||||
} = tagsRef.current[lastVisibleTagIndex];
|
|
||||||
setOverflowCountPosition(lastVisibleTagLeft + lastVisibleTagWidth);
|
|
||||||
}
|
|
||||||
}, [lastVisibleTagIndex, tagsContainerExpanded]);
|
|
||||||
|
|
||||||
const expandTags = () => {
|
|
||||||
appState.activeNote.setTagsContainerExpanded(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const reloadTagsContainerLayout = useCallback(() => {
|
|
||||||
appState.activeNote.reloadTagsContainerLayout();
|
appState.activeNote.reloadTagsContainerLayout();
|
||||||
reloadLastVisibleTagIndex();
|
reloadExpandedContainerHeight();
|
||||||
reloadExpandedContainersHeight();
|
|
||||||
reloadOverflowCount();
|
|
||||||
reloadOverflowCountPosition();
|
|
||||||
}, [
|
}, [
|
||||||
appState.activeNote,
|
appState.activeNote,
|
||||||
reloadLastVisibleTagIndex,
|
reloadExpandedContainerHeight,
|
||||||
reloadExpandedContainersHeight,
|
tags,
|
||||||
reloadOverflowCount,
|
tagsContainerMaxWidth,
|
||||||
reloadOverflowCountPosition,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
reloadTagsContainerLayout();
|
|
||||||
}, [reloadTagsContainerLayout, tags, tagsContainerMaxWidth]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let tagResizeObserver: ResizeObserver;
|
let tagResizeObserver: ResizeObserver;
|
||||||
if (ResizeObserver) {
|
if (ResizeObserver) {
|
||||||
tagResizeObserver = new ResizeObserver(() => {
|
tagResizeObserver = new ResizeObserver(() => {
|
||||||
reloadTagsContainerLayout();
|
appState.activeNote.reloadTagsContainerLayout();
|
||||||
|
reloadExpandedContainerHeight();
|
||||||
});
|
});
|
||||||
tagsRef.current.forEach((tagElement) =>
|
tagElements.forEach(
|
||||||
tagResizeObserver.observe(tagElement)
|
(tagElement) => tagElement && tagResizeObserver.observe(tagElement)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,14 +65,13 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
|
|||||||
tagResizeObserver.disconnect();
|
tagResizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [reloadTagsContainerLayout]);
|
}, [appState.activeNote, reloadExpandedContainerHeight, tagElements]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex transition-height duration-150 relative ${
|
className={`flex transition-height duration-150 relative ${
|
||||||
inputOverflowed ? 'h-18' : 'h-9'
|
inputOverflowed ? 'h-18' : 'h-9'
|
||||||
}`}
|
}`}
|
||||||
ref={containerRef}
|
|
||||||
style={tagsContainerExpanded ? { height: expandedContainerHeight } : {}}
|
style={tagsContainerExpanded ? { height: expandedContainerHeight } : {}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -148,31 +83,22 @@ const NoteTagsContainer = observer(({ application, appState }: Props) => {
|
|||||||
maxWidth: tagsContainerMaxWidth,
|
maxWidth: tagsContainerMaxWidth,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tags.map((tag: SNTag, index: number) => (
|
{tags.map((tag) => (
|
||||||
<NoteTag
|
<NoteTag
|
||||||
|
key={tag.uuid}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
tagsRef={tagsRef}
|
|
||||||
index={index}
|
|
||||||
tag={tag}
|
tag={tag}
|
||||||
maxWidth={tagsContainerMaxWidth}
|
overflowButtonRef={overflowButtonRef}
|
||||||
overflowed={
|
|
||||||
!tagsContainerExpanded &&
|
|
||||||
!!lastVisibleTagIndex &&
|
|
||||||
index > lastVisibleTagIndex
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<AutocompleteTagInput
|
<AutocompleteTagInput application={application} appState={appState} />
|
||||||
application={application}
|
|
||||||
appState={appState}
|
|
||||||
tagsRef={tagsRef}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{tagsOverflowed && (
|
{tagsOverflowed && (
|
||||||
<button
|
<button
|
||||||
|
ref={overflowButtonRef}
|
||||||
type="button"
|
type="button"
|
||||||
className="sn-tag ml-1 px-2 absolute"
|
className="sn-tag ml-1 absolute"
|
||||||
onClick={expandTags}
|
onClick={() => appState.activeNote.setTagsContainerExpanded(true)}
|
||||||
style={{ left: overflowCountPosition }}
|
style={{ left: overflowCountPosition }}
|
||||||
>
|
>
|
||||||
+{overflowedTagsCount}
|
+{overflowedTagsCount}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class PanelResizerCtrl implements PanelResizerScope {
|
|||||||
index!: number
|
index!: number
|
||||||
minWidth!: number
|
minWidth!: number
|
||||||
onResizeFinish!: () => ResizeFinishCallback
|
onResizeFinish!: () => ResizeFinishCallback
|
||||||
onWidthEvent?: () => () => void
|
onMouseMoveEvent?: () => () => void
|
||||||
panelId!: string
|
panelId!: string
|
||||||
property!: PanelSide
|
property!: PanelSide
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ class PanelResizerCtrl implements PanelResizerScope {
|
|||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
(this.onResizeFinish as any) = undefined;
|
(this.onResizeFinish as any) = undefined;
|
||||||
(this.onWidthEvent as any) = undefined;
|
(this.onMouseMoveEvent as any) = undefined;
|
||||||
(this.control as any) = undefined;
|
(this.control as any) = undefined;
|
||||||
window.removeEventListener(WINDOW_EVENT_RESIZE, this.handleResize);
|
window.removeEventListener(WINDOW_EVENT_RESIZE, this.handleResize);
|
||||||
document.removeEventListener(MouseEventType.Move, this.onMouseMove);
|
document.removeEventListener(MouseEventType.Move, this.onMouseMove);
|
||||||
@@ -245,13 +245,13 @@ class PanelResizerCtrl implements PanelResizerScope {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
if (this.onMouseMoveEvent) {
|
||||||
|
this.onMouseMoveEvent()();
|
||||||
|
}
|
||||||
if (this.property && this.property === PanelSide.Left) {
|
if (this.property && this.property === PanelSide.Left) {
|
||||||
this.handleLeftEvent(event);
|
this.handleLeftEvent(event);
|
||||||
} else {
|
} else {
|
||||||
this.handleWidthEvent(event);
|
this.handleWidthEvent(event);
|
||||||
if (this.onWidthEvent) {
|
|
||||||
this.onWidthEvent()();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +393,7 @@ export class PanelResizer extends WebDirective {
|
|||||||
index: '=',
|
index: '=',
|
||||||
minWidth: '=',
|
minWidth: '=',
|
||||||
onResizeFinish: '&',
|
onResizeFinish: '&',
|
||||||
onWidthEvent: '&',
|
onMouseMoveEvent: '&',
|
||||||
panelId: '=',
|
panelId: '=',
|
||||||
property: '='
|
property: '='
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import { AppState } from './app_state';
|
|||||||
|
|
||||||
export class ActiveNoteState {
|
export class ActiveNoteState {
|
||||||
inputOverflowed = false;
|
inputOverflowed = false;
|
||||||
|
overflowCountPosition = 0;
|
||||||
overflowedTagsCount = 0;
|
overflowedTagsCount = 0;
|
||||||
|
tagElements: (HTMLButtonElement | undefined)[] = [];
|
||||||
|
tagFocused = false;
|
||||||
tags: SNTag[] = [];
|
tags: SNTag[] = [];
|
||||||
tagsContainerMaxWidth: number | 'auto' = 0;
|
tagsContainerMaxWidth: number | 'auto' = 0;
|
||||||
tagFocused = false;
|
|
||||||
tagsContainerExpanded = false;
|
tagsContainerExpanded = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -27,29 +29,31 @@ export class ActiveNoteState {
|
|||||||
) {
|
) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
inputOverflowed: observable,
|
inputOverflowed: observable,
|
||||||
|
overflowCountPosition: observable,
|
||||||
overflowedTagsCount: observable,
|
overflowedTagsCount: observable,
|
||||||
tags: observable,
|
tagElements: observable,
|
||||||
tagFocused: observable,
|
tagFocused: observable,
|
||||||
|
tags: observable,
|
||||||
tagsContainerExpanded: observable,
|
tagsContainerExpanded: observable,
|
||||||
tagsContainerMaxWidth: observable,
|
tagsContainerMaxWidth: observable,
|
||||||
|
|
||||||
tagsOverflowed: computed,
|
tagsOverflowed: computed,
|
||||||
|
|
||||||
setInputOverflowed: action,
|
setInputOverflowed: action,
|
||||||
|
setOverflowCountPosition: action,
|
||||||
setOverflowedTagsCount: action,
|
setOverflowedTagsCount: action,
|
||||||
|
setTagElement: action,
|
||||||
setTagFocused: action,
|
setTagFocused: action,
|
||||||
|
setTags: action,
|
||||||
setTagsContainerExpanded: action,
|
setTagsContainerExpanded: action,
|
||||||
setTagsContainerMaxWidth: action,
|
setTagsContainerMaxWidth: action,
|
||||||
reloadTags: action,
|
reloadTags: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
appEventListeners.push(
|
appEventListeners.push(
|
||||||
application.streamItems(
|
application.streamItems(ContentType.Tag, () => {
|
||||||
ContentType.Tag,
|
this.reloadTags();
|
||||||
() => {
|
})
|
||||||
this.reloadTags();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +69,33 @@ export class ActiveNoteState {
|
|||||||
this.inputOverflowed = overflowed;
|
this.inputOverflowed = overflowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOverflowCountPosition(position: number): void {
|
||||||
|
this.overflowCountPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
setOverflowedTagsCount(count: number): void {
|
setOverflowedTagsCount(count: number): void {
|
||||||
this.overflowedTagsCount = count;
|
this.overflowedTagsCount = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTagElement(tag: SNTag, element: HTMLButtonElement): void {
|
||||||
|
const tagIndex = this.getTagIndex(tag);
|
||||||
|
if (tagIndex > -1) {
|
||||||
|
this.tagElements.splice(tagIndex, 1, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTagFocused(focused: boolean): void {
|
setTagFocused(focused: boolean): void {
|
||||||
this.tagFocused = focused;
|
this.tagFocused = focused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTagElements(elements: (HTMLButtonElement | undefined)[]): void {
|
||||||
|
this.tagElements = elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTags(tags: SNTag[]): void {
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
setTagsContainerExpanded(expanded: boolean): void {
|
setTagsContainerExpanded(expanded: boolean): void {
|
||||||
this.tagsContainerExpanded = expanded;
|
this.tagsContainerExpanded = expanded;
|
||||||
}
|
}
|
||||||
@@ -81,28 +104,86 @@ export class ActiveNoteState {
|
|||||||
this.tagsContainerMaxWidth = width;
|
this.tagsContainerMaxWidth = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTagElement(tag: SNTag): HTMLButtonElement | undefined {
|
||||||
|
const tagIndex = this.getTagIndex(tag);
|
||||||
|
if (tagIndex > -1 && this.tagElements.length > tagIndex) {
|
||||||
|
return this.tagElements[tagIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagIndex(tag: SNTag): number {
|
||||||
|
return this.tags.findIndex(t => t.uuid === tag.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreviousTag(tag: SNTag): SNTag | undefined {
|
||||||
|
const previousTagIndex = this.getTagIndex(tag) - 1;
|
||||||
|
if (previousTagIndex > -1 && this.tags.length > previousTagIndex) {
|
||||||
|
return this.tags[previousTagIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isElementOverflowed(element: HTMLElement): boolean {
|
||||||
|
if (
|
||||||
|
this.tagElements.length === 0 ||
|
||||||
|
!this.tagElements[0]
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const firstTagTop = this.tagElements[0].offsetTop;
|
||||||
|
return element.offsetTop > firstTagTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTagOverflowed(tag: SNTag): boolean {
|
||||||
|
const tagElement = this.getTagElement(tag);
|
||||||
|
return tagElement ? this.isElementOverflowed(tagElement) : false;
|
||||||
|
}
|
||||||
|
|
||||||
reloadTags(): void {
|
reloadTags(): void {
|
||||||
const { activeNote } = this;
|
const { activeNote } = this;
|
||||||
if (activeNote) {
|
if (activeNote) {
|
||||||
this.tags = this.application.getSortedTagsForNote(activeNote);
|
const tags = this.application.getSortedTagsForNote(activeNote);
|
||||||
}
|
const tagElements: (HTMLButtonElement | undefined)[] = [];
|
||||||
|
this.setTags(tags);
|
||||||
|
this.setTagElements(tagElements.fill(undefined, tags.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadOverflowCountPosition(): void {
|
||||||
|
const lastVisibleTagIndex = this.tagElements.findIndex(tagElement => tagElement && this.isElementOverflowed(tagElement)) - 1;
|
||||||
|
if (lastVisibleTagIndex > -1 && this.tagElements.length > lastVisibleTagIndex) {
|
||||||
|
const lastVisibleTagElement = this.tagElements[lastVisibleTagIndex];
|
||||||
|
if (lastVisibleTagElement) {
|
||||||
|
const { offsetLeft, offsetWidth } = lastVisibleTagElement;
|
||||||
|
this.setOverflowCountPosition(offsetLeft + offsetWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadOverflowedTagsCount(): void {
|
||||||
|
const count = this.tagElements.filter((tagElement) =>
|
||||||
|
tagElement && this.isElementOverflowed(tagElement)
|
||||||
|
).length;
|
||||||
|
this.setOverflowedTagsCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadTagsContainerMaxWidth(): void {
|
||||||
|
const EDITOR_ELEMENT_ID = 'editor-column';
|
||||||
|
const defaultFontSize = parseFloat(window.getComputedStyle(
|
||||||
|
document.documentElement
|
||||||
|
).fontSize);
|
||||||
|
const overflowMargin = defaultFontSize * 5;
|
||||||
|
const editorWidth = document.getElementById(EDITOR_ELEMENT_ID)?.clientWidth;
|
||||||
|
if (editorWidth) {
|
||||||
|
this.appState.activeNote.setTagsContainerMaxWidth(
|
||||||
|
editorWidth - overflowMargin
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadTagsContainerLayout(): void {
|
reloadTagsContainerLayout(): void {
|
||||||
const EDITOR_ELEMENT_ID = 'editor-column';
|
this.reloadTagsContainerMaxWidth();
|
||||||
const defaultFontSize = window.getComputedStyle(
|
this.reloadOverflowedTagsCount();
|
||||||
document.documentElement
|
this.reloadOverflowCountPosition();
|
||||||
).fontSize;
|
|
||||||
const containerMargins = parseFloat(defaultFontSize) * 6;
|
|
||||||
const deleteButtonMargin = this.tagFocused ? parseFloat(defaultFontSize) * 1.25 : 0;
|
|
||||||
const editorWidth =
|
|
||||||
document.getElementById(EDITOR_ELEMENT_ID)?.clientWidth;
|
|
||||||
|
|
||||||
if (editorWidth) {
|
|
||||||
this.appState.activeNote.setTagsContainerMaxWidth(
|
|
||||||
editorWidth - containerMargins + deleteButtonMargin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addTagToActiveNote(tag: SNTag): Promise<void> {
|
async addTagToActiveNote(tag: SNTag): Promise<void> {
|
||||||
|
|||||||
@@ -169,6 +169,6 @@
|
|||||||
default-width="300"
|
default-width="300"
|
||||||
hoverable="true"
|
hoverable="true"
|
||||||
on-resize-finish="self.onPanelResize"
|
on-resize-finish="self.onPanelResize"
|
||||||
on-width-event="self.onPanelWidthEvent"
|
on-mouse-move-event="self.onPanelMouseMoveEvent"
|
||||||
panel-id="'notes-column'"
|
panel-id="'notes-column'"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
|
|||||||
};
|
};
|
||||||
this.onWindowResize = this.onWindowResize.bind(this);
|
this.onWindowResize = this.onWindowResize.bind(this);
|
||||||
this.onPanelResize = this.onPanelResize.bind(this);
|
this.onPanelResize = this.onPanelResize.bind(this);
|
||||||
this.onPanelWidthEvent = this.onPanelWidthEvent.bind(this);
|
this.onPanelMouseMoveEvent = this.onPanelMouseMoveEvent.bind(this);
|
||||||
window.addEventListener('resize', this.onWindowResize, true);
|
window.addEventListener('resize', this.onWindowResize, true);
|
||||||
this.registerKeyboardShortcuts();
|
this.registerKeyboardShortcuts();
|
||||||
this.autorun(async () => {
|
this.autorun(async () => {
|
||||||
@@ -134,7 +134,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
|
|||||||
window.removeEventListener('resize', this.onWindowResize, true);
|
window.removeEventListener('resize', this.onWindowResize, true);
|
||||||
(this.onWindowResize as any) = undefined;
|
(this.onWindowResize as any) = undefined;
|
||||||
(this.onPanelResize as any) = undefined;
|
(this.onPanelResize as any) = undefined;
|
||||||
(this.onPanelWidthEvent as any) = undefined;
|
(this.onPanelMouseMoveEvent as any) = undefined;
|
||||||
this.newNoteKeyObserver();
|
this.newNoteKeyObserver();
|
||||||
this.nextNoteKeyObserver();
|
this.nextNoteKeyObserver();
|
||||||
this.previousNoteKeyObserver();
|
this.previousNoteKeyObserver();
|
||||||
@@ -659,8 +659,8 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPanelWidthEvent(): void {
|
onPanelMouseMoveEvent(): void {
|
||||||
this.appState.activeNote.reloadTagsContainerLayout();
|
this.appState.activeNote.reloadTagsContainerMaxWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
paginate() {
|
paginate() {
|
||||||
|
|||||||
@@ -162,6 +162,10 @@
|
|||||||
line-height: 2.25rem;
|
line-height: 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-0 {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.w-3\.5 {
|
.w-3\.5 {
|
||||||
width: 0.875rem;
|
width: 0.875rem;
|
||||||
}
|
}
|
||||||
@@ -190,6 +194,10 @@
|
|||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-0 {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.h-3\.5 {
|
.h-3\.5 {
|
||||||
height: 0.875rem;
|
height: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user