From bbf9d6c01b821d17227e342768ff2c791a2cb10f Mon Sep 17 00:00:00 2001 From: mgmeyers Date: Thu, 18 Apr 2024 09:10:40 -0700 Subject: [PATCH] Integrate tag search into new preview renderer --- src/Settings.ts | 6 +- src/StateManager.ts | 1 + src/components/Editor/dateWidget.ts | 1 - src/components/Item/Item.tsx | 4 +- src/components/Item/ItemContent.tsx | 10 ++- src/components/Kanban.tsx | 14 ++-- src/components/Lane/Lane.tsx | 6 +- .../MarkdownRenderer/MarkdownRenderer.tsx | 39 ++++++++--- src/components/Table/Table.tsx | 2 +- src/components/context.ts | 1 + src/components/helpers.ts | 67 +++++++++++++------ src/lang/locale/en.ts | 2 +- src/parsers/formats/list.ts | 14 +++- src/styles.less | 8 +-- 14 files changed, 124 insertions(+), 51 deletions(-) diff --git a/src/Settings.ts b/src/Settings.ts index 346b6e84..dda54fe5 100644 --- a/src/Settings.ts +++ b/src/Settings.ts @@ -538,9 +538,11 @@ export class SettingsManager { }); new Setting(contentEl) - .setName(t('Tag action')) + .setName(t('Tag click action')) .setDesc( - t('This setting controls whether clicking the tags displayed below the card title opens the Obsidian search or the Kanban board search.') + t( + 'This setting controls whether clicking the tags displayed below the card title opens the Obsidian search or the Kanban board search.' + ) ) .addDropdown((dropdown) => { dropdown.addOption('kanban', t('Search Kanban Board')); diff --git a/src/StateManager.ts b/src/StateManager.ts index de66a029..2698e1bf 100644 --- a/src/StateManager.ts +++ b/src/StateManager.ts @@ -262,6 +262,7 @@ export class StateManager { 'show-set-view': this.getSettingRaw('show-set-view', suppliedSettings) ?? true, 'tag-colors': this.getSettingRaw('tag-colors', suppliedSettings) ?? [], 'date-colors': this.getSettingRaw('date-colors', suppliedSettings) ?? [], + 'tag-action': this.getSettingRaw('tag-action', suppliedSettings) ?? 'obsidian', }; } diff --git a/src/components/Editor/dateWidget.ts b/src/components/Editor/dateWidget.ts index ac91665c..2ef0ff2e 100644 --- a/src/components/Editor/dateWidget.ts +++ b/src/components/Editor/dateWidget.ts @@ -166,7 +166,6 @@ export function usePreprocessedStr( str = str.replace( new RegExp(`${dateTrigger}\\[\\[([^\\]]+)\\]\\]`, 'g'), (match, content) => { - console.log(match, content); const parsed = moment(content, dateFormat); if (!parsed.isValid()) return match; const linkPath = app.metadataCache.getFirstLinkpathDest(content, stateManager.file.path); diff --git a/src/components/Item/Item.tsx b/src/components/Item/Item.tsx index 1cd76b30..dcc91ec8 100644 --- a/src/components/Item/Item.tsx +++ b/src/components/Item/Item.tsx @@ -149,7 +149,7 @@ export const DraggableItem = memo(function DraggableItem(props: DraggableItemPro useDragHandle(measureRef, measureRef); - const isMatch = search ? innerProps.item.data.titleSearch.includes(search.query) : false; + const isMatch = search?.query ? innerProps.item.data.titleSearch.includes(search.query) : false; const classModifiers: string[] = getItemClassModifiers(innerProps.item); return ( @@ -189,7 +189,7 @@ export const Items = memo(function Items({ isStatic, items, shouldMarkItemsCompl return ( <> {items.map((item, i) => { - return search && !search.items.has(item) ? null : ( + return search?.query && !search.items.has(item) ? null : ( { e.preventDefault(); + + const tagAction = stateManager.getSetting('tag-action'); + if (search && tagAction === 'kanban') { + search.search(tag, true); + return; + } + (stateManager.app as any).internalPlugins .getPluginById('global-search') .instance.openGlobalSearch(`tag:${tag}`); diff --git a/src/components/Kanban.tsx b/src/components/Kanban.tsx index 91ad72ed..5f92945e 100644 --- a/src/components/Kanban.tsx +++ b/src/components/Kanban.tsx @@ -19,7 +19,7 @@ import { Lanes } from './Lane/Lane'; import { LaneForm } from './Lane/LaneForm'; import { TableView } from './Table/Table'; import { KanbanContext, SearchContext } from './context'; -import { baseClassName, c, getDateColorFn, getSearchHits, getTagColorFn } from './helpers'; +import { baseClassName, c, getDateColorFn, getTagColorFn, useSearchValue } from './helpers'; import { DataTypes } from './types'; const boardScrollTiggers = [DataTypes.Item, DataTypes.Lane]; @@ -182,16 +182,18 @@ export const Kanban = ({ view, stateManager }: KanbanProps) => { } const axis = boardView === 'list' ? 'vertical' : 'horizontal'; - - const searchHits = useMemo( - () => getSearchHits(boardData, debouncedSearchQuery), - [boardData, debouncedSearchQuery] + const searchValue = useSearchValue( + boardData, + debouncedSearchQuery, + setSearchQuery, + setDebouncedSearchQuery, + setIsSearching ); return ( - +
- {!search && !isCollapsed && shouldPrepend && ( + {!search?.query && !isCollapsed && shouldPrepend && ( )} - {!search && !isCollapsed && !shouldPrepend && ( + {!search?.query && !isCollapsed && !shouldPrepend && ( )} @@ -223,7 +223,7 @@ function LanesRaw({ lanes, collapseDir }: LanesProps) { return ( { @@ -52,6 +52,7 @@ export const StaticMarkdownRenderer = memo(function StaticMarkdownRenderer({ searchQuery, ...divProps }: MarkdownRendererProps) { + const search = useContext(SearchContext); const { stateManager, view, filePath } = useContext(KanbanContext); const wrapperRef = useRef(); const contentRef = useRef(); @@ -161,11 +162,8 @@ export const StaticMarkdownRenderer = memo(function StaticMarkdownRenderer({ const tag = closestAnchor.getAttr('href'); const tagAction = stateManager.getSetting('tag-action'); - if (tagAction === 'kanban') { - setSearchQuery(tag); - setDebouncedSearchQuery(tag); - setIsSearching(true); - + if (search && tagAction === 'kanban') { + search.search(tag, true); return; } @@ -181,7 +179,7 @@ export const StaticMarkdownRenderer = memo(function StaticMarkdownRenderer({ window.open(closestAnchor.getAttr('href'), '_blank'); } }, - [stateManager, filePath] + [stateManager, filePath, search] ); const onContextMenu = useCallback( @@ -411,7 +409,8 @@ export function postProcessor(plugin: KanbanPlugin) { if (!tagAs.length) return; tagAs.forEach((a) => { - const color = tagColors.find((c) => c.tagKey === a.getAttr('href')); + const tag = a.getAttr('href'); + const color = tagColors.find((c) => c.tagKey === tag); if (!color) return; a.setCssProps({ @@ -429,6 +428,7 @@ export const MarkdownPreviewRenderer = memo(function MarkdownPreviewRenderer({ searchQuery, ...divProps }: MarkdownPreviewRendererProps) { + const search = useContext(SearchContext); const { view, stateManager, getDateColor } = useContext(KanbanContext); const markRef = useRef(); const renderer = useRef(); @@ -527,6 +527,28 @@ export const MarkdownPreviewRenderer = memo(function MarkdownPreviewRenderer({ } }, []); + const onClick = useCallback( + async (e: MouseEvent) => { + if (e.type === 'auxclick' || e.button === 2) return; + + const targetEl = e.targetNode as HTMLElement; + const closestAnchor = targetEl.tagName === 'A' ? targetEl : targetEl.closest('a'); + + if (!closestAnchor) return; + if (closestAnchor.hasClass('tag')) { + const tagAction = stateManager.getSetting('tag-action'); + if (search && tagAction === 'kanban') { + e.preventDefault(); + e.stopPropagation(); + const tag = closestAnchor.getAttr('href'); + search.search(tag, true); + return; + } + } + }, + [stateManager, search] + ); + let styles: CSSProperties | undefined = undefined; if (!renderer.current && view.previewCache.has(entityId)) { const preview = view.previewCache.get(entityId); @@ -541,6 +563,7 @@ export const MarkdownPreviewRenderer = memo(function MarkdownPreviewRenderer({ return (
{ elRef.current = el; const preview = renderer.current; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 7ba5f2c3..8b740364 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -320,7 +320,7 @@ function useTableColumns(boardData: Board, stateManager: StateManager) { }, [setSortingRaw] ); - const state = useMemo(() => ({ sorting, globalFilter: search?.query }), [sorting, search]); + const state = useMemo(() => ({ sorting, globalFilter: search?.query }), [sorting, search?.query]); const { items, metadata, fileMetadata } = useTableData(boardData, stateManager); const withMetadata: ColumnDef[] = useMemo(() => { diff --git a/src/components/context.ts b/src/components/context.ts index ae165169..582e67fc 100644 --- a/src/components/context.ts +++ b/src/components/context.ts @@ -20,6 +20,7 @@ export interface SearchContextProps { query: string; items: Set; lanes: Set; + search: (query: string, immediate?: boolean) => void; } export const SearchContext = createContext(null); diff --git a/src/components/helpers.ts b/src/components/helpers.ts index a97f89a8..8537564a 100644 --- a/src/components/helpers.ts +++ b/src/components/helpers.ts @@ -1,10 +1,12 @@ import update from 'immutability-helper'; import { App, MarkdownView, TFile, moment } from 'obsidian'; -import Preact, { RefObject, useEffect } from 'preact/compat'; +import Preact, { Dispatch, RefObject, useEffect } from 'preact/compat'; +import { StateUpdater, useMemo } from 'preact/hooks'; import { StateManager } from 'src/StateManager'; import { Path } from 'src/dnd/types'; import { getEntityFromPath } from 'src/dnd/util/data'; +import { SearchContextProps } from './context'; import { Board, DateColorKey, Item, Lane, TagColorKey } from './types'; export const baseClassName = 'kanban-plugin'; @@ -291,23 +293,50 @@ export function useOnMount(refs: RefObject[], cb: () => void, onUnm }, []); } -export function getSearchHits(board: Board, query: string) { - query = query.trim().toLocaleLowerCase(); - if (!query) return null; - - const lanes = new Set(); - const items = new Set(); - - board.children.forEach((lane) => { - let laneMatched = false; - lane.children.forEach((item) => { - if (item.data.titleSearch.includes(query)) { - laneMatched = true; - items.add(item); - } - }); - if (laneMatched) lanes.add(lane); - }); +export function useSearchValue( + board: Board, + query: string, + setSearchQuery: Dispatch>, + setDebouncedSearchQuery: Dispatch>, + setIsSearching: Dispatch> +) { + return useMemo(() => { + query = query.trim().toLocaleLowerCase(); + + const lanes = new Set(); + const items = new Set(); + + if (query) { + board.children.forEach((lane) => { + let laneMatched = false; + lane.children.forEach((item) => { + if (item.data.titleSearch.includes(query)) { + laneMatched = true; + items.add(item); + } + }); + if (laneMatched) lanes.add(lane); + }); + } - return { lanes, items, query }; + return { + lanes, + items, + query, + search: (query, immediate) => { + if (!query) { + setIsSearching(false); + setSearchQuery(''); + setDebouncedSearchQuery(''); + } + setIsSearching(true); + if (immediate) { + setSearchQuery(query); + setDebouncedSearchQuery(query); + } else { + setSearchQuery(query); + } + }, + }; + }, [board, query, setSearchQuery, setDebouncedSearchQuery]); } diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index fccdfeef..4325bcf6 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -126,7 +126,7 @@ export default { 'Hide card display tags': 'Hide card display tags', 'When toggled, tags will not be displayed below the card title.': 'When toggled, tags will not be displayed below the card title.', - 'Tag action': 'Tag action', + 'Tag click action': 'Tag click action', 'Search Kanban Board': 'Search Kanban Board', 'Search Obsidian Vault': 'Search Obsidian Vault', 'This setting controls whether clicking the tags displayed below the card title opens the Obsidian search or the Kanban board search.': diff --git a/src/parsers/formats/list.ts b/src/parsers/formats/list.ts index ad11ced6..f47421a8 100644 --- a/src/parsers/formats/list.ts +++ b/src/parsers/formats/list.ts @@ -68,9 +68,17 @@ export function listItemToItemData(stateManager: StateManager, md: string, item: let title = itemContent; let titleSearch = ''; - visit(item, ['text', 'wikilink', 'embedWikilink', 'image', 'inlineCode', 'code'], (node: any) => { - titleSearch += node.value || node.alt || ''; - }); + visit( + item, + ['text', 'wikilink', 'embedWikilink', 'image', 'inlineCode', 'code', 'hashtag'], + (node: any) => { + if (node.type === 'hashtag') { + titleSearch += '#' + node.value; + } else { + titleSearch += node.value || node.alt || ''; + } + } + ); const itemData: ItemData = { titleRaw: dedentNewLines(replaceBrs(itemContent)), diff --git a/src/styles.less b/src/styles.less index 59a2697c..c1b03c04 100644 --- a/src/styles.less +++ b/src/styles.less @@ -305,10 +305,6 @@ button.kanban-plugin__search-cancel-button .kanban-plugin__icon { height: 100%; } - .view-content:not(.is-mobile-editing) & > div { - padding-bottom: calc(1rem + var(--mobile-navbar-height)); - } - &.kanban-plugin__vertical { > div { height: fit-content; @@ -318,6 +314,10 @@ button.kanban-plugin__search-cancel-button .kanban-plugin__icon { } } +.is-mobile .view-content:not(.is-mobile-editing) .kanban-plugin__board > div { + padding-bottom: calc(1rem + var(--mobile-navbar-height)); +} + .kanban-plugin__board.is-adding-lane > div { padding-inline-end: calc(250px + 1rem); }