diff --git a/app/assets/javascripts/components/AutocompleteTagInput.tsx b/app/assets/javascripts/components/AutocompleteTagInput.tsx index 35ee99b6f..c3cb2c363 100644 --- a/app/assets/javascripts/components/AutocompleteTagInput.tsx +++ b/app/assets/javascripts/components/AutocompleteTagInput.tsx @@ -22,22 +22,25 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { } = appState.noteTags; const [dropdownVisible, setDropdownVisible] = useState(false); - const [dropdownMaxHeight, setDropdownMaxHeight] = - useState('auto'); + const [dropdownMaxHeight, setDropdownMaxHeight] = useState( + 'auto' + ); const containerRef = useRef(null); const inputRef = useRef(null); - const [closeOnBlur] = useCloseOnBlur(containerRef as any, (visible: boolean) => { + const [closeOnBlur] = useCloseOnBlur(containerRef, (visible: boolean) => { setDropdownVisible(visible); appState.noteTags.clearAutocompleteSearch(); }); const showDropdown = () => { const { clientHeight } = document.documentElement; - const inputRect = inputRef.current!.getBoundingClientRect(); - setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2); - setDropdownVisible(true); + const inputRect = inputRef.current?.getBoundingClientRect(); + if (inputRect) { + setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2); + setDropdownVisible(true); + } }; const onSearchQueryChange = (event: Event) => { @@ -93,7 +96,7 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { useEffect(() => { if (autocompleteInputFocused) { - inputRef.current!.focus(); + inputRef.current?.focus(); } }, [appState.noteTags, autocompleteInputFocused]); diff --git a/app/assets/javascripts/components/Preferences/panes/general-segments/Defaults.tsx b/app/assets/javascripts/components/Preferences/panes/general-segments/Defaults.tsx index 0cab2a48b..87155c167 100644 --- a/app/assets/javascripts/components/Preferences/panes/general-segments/Defaults.tsx +++ b/app/assets/javascripts/components/Preferences/panes/general-segments/Defaults.tsx @@ -67,6 +67,10 @@ export const Defaults: FunctionComponent = ({ application }) => { application.getPreference(PrefKey.EditorSpellcheck, true) ); + const [addNoteToParentFolders, setAddNoteToParentFolders] = useState(() => + application.getPreference(PrefKey.NoteAddToParentFolders, true) + ); + const toggleSpellcheck = () => { setSpellcheck(!spellcheck); application.getAppState().toggleGlobalSpellcheck(); @@ -148,6 +152,28 @@ export const Defaults: FunctionComponent = ({ application }) => { + +
+
+ + Add all parent tags when adding a nested tag to a note + + + When enabled, adding a nested tag to a note will automatically add + all associated parent tags. + +
+ { + application.setPreference( + PrefKey.NoteAddToParentFolders, + !addNoteToParentFolders + ); + setAddNoteToParentFolders(!addNoteToParentFolders); + }} + checked={addNoteToParentFolders} + /> +
); diff --git a/app/assets/javascripts/ui_models/app_state/note_tags_state.ts b/app/assets/javascripts/ui_models/app_state/note_tags_state.ts index 25677e602..32ee27e75 100644 --- a/app/assets/javascripts/ui_models/app_state/note_tags_state.ts +++ b/app/assets/javascripts/ui_models/app_state/note_tags_state.ts @@ -1,5 +1,12 @@ import { ElementIds } from '@/element_ids'; -import { ContentType, SNNote, SNTag, UuidString } from '@standardnotes/snjs'; +import { ApplicationEvent } from '@standardnotes/snjs'; +import { + ContentType, + PrefKey, + SNNote, + SNTag, + UuidString, +} from '@standardnotes/snjs'; import { action, computed, makeObservable, observable } from 'mobx'; import { WebApplication } from '../application'; import { AppState } from './app_state'; @@ -13,6 +20,7 @@ export class NoteTagsState { focusedTagUuid: UuidString | undefined = undefined; tags: SNTag[] = []; tagsContainerMaxWidth: number | 'auto' = 0; + addNoteToParentFolders: boolean; constructor( private application: WebApplication, @@ -41,10 +49,24 @@ export class NoteTagsState { setTagsContainerMaxWidth: action, }); + this.addNoteToParentFolders = application.getPreference( + PrefKey.NoteAddToParentFolders, + true + ); + appEventListeners.push( application.streamItems(ContentType.Tag, () => { this.reloadTags(); - }) + }), + application.addSingleEventObserver( + ApplicationEvent.PreferencesChanged, + async () => { + this.addNoteToParentFolders = application.getPreference( + PrefKey.NoteAddToParentFolders, + true + ); + } + ) ); } @@ -180,7 +202,11 @@ export class NoteTagsState { const { activeNote } = this; if (activeNote) { - await this.application.addTagHierarchyToNote(activeNote, tag); + await this.application.items.addTagToNote( + activeNote, + tag, + this.addNoteToParentFolders + ); this.application.sync.sync(); this.reloadTags(); } diff --git a/package.json b/package.json index d0cff8b22..e28dc4612 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@standardnotes/filepicker": "1.10.0", "@standardnotes/settings": "1.13.1", "@standardnotes/sncrypto-web": "1.8.0", - "@standardnotes/snjs": "2.86.4", + "@standardnotes/snjs": "2.87.0", "@zip.js/zip.js": "^2.4.7", "mobx": "^6.5.0", "mobx-react-lite": "^3.3.0", diff --git a/yarn.lock b/yarn.lock index fd42fa63d..a0b219fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2491,10 +2491,10 @@ buffer "^6.0.3" libsodium-wrappers "^0.7.9" -"@standardnotes/snjs@2.86.4": - version "2.86.4" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.86.4.tgz#d0cc9d1e1e890d192bf252b19f06775902bfc6f0" - integrity sha512-Cp9nQ7a+Tjr/PVp72+qAgOaDetjyItuc9SwvnsPp4SlKku4KtCsIRQODPZfpF9ifxV/QIii+ZQbqk2ankHUQ0g== +"@standardnotes/snjs@2.87.0": + version "2.87.0" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.87.0.tgz#7bccd49a365e6b8e2fe081798cfd9b899e8d39d1" + integrity sha512-qvQhLFDtTYilG3nskdPklW1v8UTALTPX8fEjaVlH3+INlWFVR+yu9/Cwvyfkg6kqal6YTG/kG/tfDpRsLOTCxQ== dependencies: "@standardnotes/applications" "^1.2.3" "@standardnotes/auth" "^3.17.8"