diff --git a/src/packages/frontend/editors/task-editor/actions.ts b/src/packages/frontend/editors/task-editor/actions.ts index f70dc86cfa..6c7309aafe 100644 --- a/src/packages/frontend/editors/task-editor/actions.ts +++ b/src/packages/frontend/editors/task-editor/actions.ts @@ -34,10 +34,12 @@ import { TaskMap, TaskState, } from "./types"; -import { TaskStore } from "./store"; import { SyncDB } from "@cocalc/sync/editor/db"; import { webapp_client } from "../../webapp-client"; -import type { Actions as TaskFrameActions } from "@cocalc/frontend/frame-editors/task-editor/actions"; +import type { + Actions as TaskFrameActions, + Store as TaskStore, +} from "@cocalc/frontend/frame-editors/task-editor/actions"; import Fragment from "@cocalc/frontend/misc/fragment-id"; const LAST_EDITED_THRESH_S = 30; @@ -48,7 +50,7 @@ export class TaskActions extends Actions { private project_id: string; private path: string; private truePath: string; - private store: TaskStore; + public store: TaskStore; _update_visible: Function; private is_closed: boolean = false; private key_handler?: (any) => void; @@ -663,7 +665,6 @@ export class TaskActions extends Actions { } public blur_find_box(): void { - this.enable_key_handler(); this.setFrameData({ focus_find_box: false }); } diff --git a/src/packages/frontend/editors/task-editor/editor.tsx b/src/packages/frontend/editors/task-editor/editor.tsx index 1a9c7b6a1e..3890361670 100644 --- a/src/packages/frontend/editors/task-editor/editor.tsx +++ b/src/packages/frontend/editors/task-editor/editor.tsx @@ -9,9 +9,8 @@ Top-level react component for task list import { Button } from "antd"; import { fromJS } from "immutable"; - import { Col, Row } from "@cocalc/frontend/antd-bootstrap"; -import { React, useEditorRedux } from "@cocalc/frontend/app-framework"; +import { useEditorRedux } from "@cocalc/frontend/app-framework"; import { Loading } from "@cocalc/frontend/components"; import { Icon } from "@cocalc/frontend/components/icon"; import { TaskActions } from "./actions"; @@ -31,111 +30,115 @@ interface Props { read_only?: boolean; } -export const TaskEditor: React.FC = React.memo( - ({ actions, path, project_id, desc, read_only }) => { - const useEditor = useEditorRedux({ project_id, path }); - const tasks = useEditor("tasks"); - const visible = desc.get("data-visible"); - const local_task_state = desc.get("data-local_task_state") ?? fromJS({}); - const local_view_state = desc.get("data-local_view_state") ?? fromJS({}); - const hashtags = desc.get("data-hashtags"); - const current_task_id = desc.get("data-current_task_id"); - const counts = desc.get("data-counts"); - const search_terms = desc.get("data-search_terms"); - const search_desc = desc.get("data-search_desc"); - const focus_find_box = desc.get("data-focus_find_box"); +export function TaskEditor({ + actions, + path, + project_id, + desc, + read_only, +}: Props) { + const useEditor = useEditorRedux({ project_id, path }); + const tasks = useEditor("tasks"); + const visible = desc.get("data-visible"); + const local_task_state = desc.get("data-local_task_state") ?? fromJS({}); + const local_view_state = desc.get("data-local_view_state") ?? fromJS({}); + const hashtags = desc.get("data-hashtags"); + const current_task_id = desc.get("data-current_task_id"); + const counts = desc.get("data-counts"); + const search_terms = desc.get("data-search_terms"); + const search_desc = desc.get("data-search_desc"); + const focus_find_box = desc.get("data-focus_find_box"); - if (tasks == null || visible == null) { - return ( -
+ +
+ ); + } + + return ( +
+ + + + + + + + + + + + {hashtags != null && ( + + )} + + + +
+ {visible.size == 0 ? ( + - -
- ); - } - - return ( - + ); +} diff --git a/src/packages/frontend/editors/task-editor/find.tsx b/src/packages/frontend/editors/task-editor/find.tsx index 07c0c9e626..5f0a5517f5 100644 --- a/src/packages/frontend/editors/task-editor/find.tsx +++ b/src/packages/frontend/editors/task-editor/find.tsx @@ -44,7 +44,7 @@ export function Find({ ref={inputRef} allowClear value={local_view_state.get("search") ?? ""} - placeholder={"Search for tasks..."} + placeholder={"Filter tasks..."} onChange={(e) => actions.set_local_view_state({ search: e.target.value, @@ -56,6 +56,7 @@ export function Find({ if (evt.which === 27) { actions.set_local_view_state({ search: "" }); inputRef.current?.blur(); + actions.enable_key_handler(); return false; } }} diff --git a/src/packages/frontend/editors/task-editor/store.ts b/src/packages/frontend/editors/task-editor/store.ts index 4be243db1e..8b871c348d 100644 --- a/src/packages/frontend/editors/task-editor/store.ts +++ b/src/packages/frontend/editors/task-editor/store.ts @@ -4,6 +4,6 @@ */ import { Store } from "../../app-framework"; -import { TaskState } from "./types"; +import type { TaskState } from "./types"; export class TaskStore extends Store {} diff --git a/src/packages/frontend/editors/task-editor/task.tsx b/src/packages/frontend/editors/task-editor/task.tsx index e223d4fb85..76d0f9318e 100644 --- a/src/packages/frontend/editors/task-editor/task.tsx +++ b/src/packages/frontend/editors/task-editor/task.tsx @@ -99,7 +99,10 @@ export default function Task({ return ( actions?.set_current_task(task.get("task_id"))} + onClick={() => { + actions?.set_current_task(task.get("task_id")); + actions?.enable_key_handler(); + }} > diff --git a/src/packages/frontend/frame-editors/chat-editor/search.tsx b/src/packages/frontend/frame-editors/chat-editor/search.tsx index 1e082a837f..f0fc472ccc 100644 --- a/src/packages/frontend/frame-editors/chat-editor/search.tsx +++ b/src/packages/frontend/frame-editors/chat-editor/search.tsx @@ -17,4 +17,8 @@ function Preview({ id, content }) { ); } -export const search = createSearchEditor({ Preview }); +export const search = createSearchEditor({ + Preview, + updateField: "messages", + title: "Chatroom", +}); diff --git a/src/packages/frontend/frame-editors/generic/search/index.tsx b/src/packages/frontend/frame-editors/generic/search/index.tsx index 654d9ec502..71b5707da0 100644 --- a/src/packages/frontend/frame-editors/generic/search/index.tsx +++ b/src/packages/frontend/frame-editors/generic/search/index.tsx @@ -16,18 +16,59 @@ import { throttle } from "lodash"; import useSearchIndex from "./use-search-index"; import ShowError from "@cocalc/frontend/components/error"; import { useEditorRedux } from "@cocalc/frontend/app-framework"; -import type { ChatState } from "@cocalc/frontend/chat/store"; + +export function createSearchEditor({ + Preview, + updateField, + previewStyle, +}: { + // component for previewing search results. + Preview?; + // name of a field in the store so that we should update the search index + // exactly when the value of that field changes. + updateField: string; + // overload styles for component that contains the preview, e.g., maxHeight could be made bigger. + previewStyle?; + title?: string; +}): EditorDescription { + return { + type: "search", + short: "Search", + name: "Search", + icon: "comment", + commands: set(["decrease_font_size", "increase_font_size"]), + component: (props) => ( + + ), + } as EditorDescription; +} interface Props { font_size: number; desc; + updateField: string; Preview?; + previewStyle?; + title?; } -function Search({ font_size, desc, Preview }: Props) { +function Search({ + font_size: fontSize, + desc, + Preview, + updateField, + previewStyle, + title, +}: Props) { const { project_id, path, actions, id } = useFrameContext(); - const useEditor = useEditorRedux({ project_id, path }); - const messages = useEditor("messages"); + const useEditor = useEditorRedux({ project_id, path }); + // @ts-ignore + const messages = useEditor(updateField); const [indexedMessages, setIndexedMessages] = useState(messages); const [search, setSearch] = useState(desc.get("data-search") ?? ""); const [result, setResult] = useState(null); @@ -52,7 +93,7 @@ function Search({ font_size, desc, Preview }: Props) { return; } (async () => { - const result = await index.search({ term: search }); + const result = await index.search({ term: search, limit: 30 /* todo */ }); setResult(result); })(); }, [search, index]); @@ -69,19 +110,18 @@ function Search({ font_size, desc, Preview }: Props) { - Search Chatroom{" "} - {separate_file_extension(path_split(path).tail).name} + Search {title} {separate_file_extension(path_split(path).tail).name} } - style={{ fontSize: font_size }} + style={{ fontSize }} >
+
+ {!search?.trim() && Enter a search above} + {(result?.hits?.length ?? 0) == 0 && search?.trim() && ( + No Matches + )} + {(result?.count ?? 0) > (result?.hits?.length ?? 0) && ( + + Showing {result?.hits.length} of {result?.count ?? 0} results + + )} +
{result?.hits?.map((hit) => ( ))} - {result?.hits == null && search?.trim() &&
No hits
}
); } -function SearchResult({ hit, actions, fragmentKey, Preview }) { +function SearchResult({ + hit, + actions, + fragmentKey, + Preview, + previewStyle, + fontSize, +}) { const { document } = hit; return (
{ actions.gotoFragment({ [fragmentKey]: document.id }); }} > {Preview != null ? ( - + ) : (
{document.content}
)}
); } - -export function createSearchEditor({ - Preview, -}: { - Preview?; -}): EditorDescription { - return { - type: "search", - short: "Search", - name: "Search", - icon: "comment", - commands: set(["decrease_font_size", "increase_font_size"]), - component: (props) => , - } as EditorDescription; -} diff --git a/src/packages/frontend/frame-editors/task-editor/actions.ts b/src/packages/frontend/frame-editors/task-editor/actions.ts index 308bb84fc2..2ea4c3e1f9 100644 --- a/src/packages/frontend/frame-editors/task-editor/actions.ts +++ b/src/packages/frontend/frame-editors/task-editor/actions.ts @@ -13,19 +13,23 @@ import { } from "../code-editor/actions"; import { FrameTree } from "../frame-tree/types"; import { TaskActions } from "@cocalc/frontend/editors/task-editor/actions"; -import { TaskStore } from "@cocalc/frontend/editors/task-editor/store"; -import { redux_name } from "../../app-framework"; +import { redux_name } from "@cocalc/frontend/app-framework"; +import type { Store as BaseStore } from "@cocalc/frontend/app-framework"; import { aux_file, cmp } from "@cocalc/util/misc"; import { Map } from "immutable"; import { delay } from "awaiting"; import type { FragmentId } from "@cocalc/frontend/misc/fragment-id"; +import type { Tasks } from "@cocalc/frontend/editors/task-editor/types"; +import { DONE } from "./search"; interface TaskEditorState extends CodeEditorState { - // nothing yet + tasks?: Tasks; } const FRAME_TYPE = "tasks"; +export type Store = BaseStore; + export class Actions extends CodeEditorActions { protected doctype: string = "syncdb"; protected primary_keys: string[] = ["task_id"]; @@ -36,15 +40,10 @@ export class Actions extends CodeEditorActions { metaColumns: ["due_date", "done"], }; taskActions: { [frameId: string]: TaskActions } = {}; - taskStore: TaskStore; auxPath: string; _init2(): void { this.auxPath = aux_file(this.path, "tasks"); - this.taskStore = this.redux.createStore( - redux_name(this.project_id, this.auxPath), - TaskStore, - ); const syncdb = this._syncstring; syncdb.on("change", this.syncdbChange); syncdb.once("change", this.ensurePositionsAreUnique); @@ -52,7 +51,7 @@ export class Actions extends CodeEditorActions { private syncdbChange(changes) { const syncdb = this._syncstring; - const store = this.taskStore; + const store = this.store; if (syncdb == null || store == null) { // may happen during close return; @@ -77,7 +76,7 @@ export class Actions extends CodeEditorActions { } private ensurePositionsAreUnique() { - let tasks = this.taskStore.get("tasks"); + let tasks = this.store.get("tasks"); if (tasks == null) { return; } @@ -139,11 +138,12 @@ export class Actions extends CodeEditorActions { this.project_id, this.auxPath, this._syncstring, - this.taskStore, + this.store, this.path, ); actions._init_frame(frameId, this); this.taskActions[frameId] = actions; + actions.store = this.store; return actions; } @@ -185,7 +185,7 @@ export class Actions extends CodeEditorActions { for (const frameId in this.taskActions) { this.closeTaskFrame(frameId); } - this.redux.removeStore(this.taskStore.name); + this.redux.removeStore(this.store.name); super.close(); } @@ -201,14 +201,23 @@ export class Actions extends CodeEditorActions { } } + private hideAllTaskActionsExcept = (id) => { + for (const id0 in this.taskActions) { + if (id0 != id) { + this.taskActions[id0].hide(); + } + } + }; + public focus(id?: string): void { if (id === undefined) { id = this._get_active_id(); } - if (this._get_frame_type(id) == FRAME_TYPE) { - this.getTaskActions(id)?.show(); - return; - } + this.hideAllTaskActionsExcept(id); + // if (this._get_frame_type(id) == FRAME_TYPE) { + // this.getTaskActions(id)?.show(); + // return; + // } super.focus(id); } @@ -260,4 +269,26 @@ export class Actions extends CodeEditorActions { await delay(d); } } + + getSearchIndexData = () => { + const tasks = this.store?.get("tasks"); + if (tasks == null) { + return {}; + } + const data: { [id: string]: string } = {}; + for (const [id, task] of tasks) { + if (task.get("deleted")) { + continue; + } + let content = task.get("desc")?.trim(); + if (!content) { + continue; + } + if (task.get("done")) { + content = DONE + content; + } + data[id] = content; + } + return { data, fragmentKey: "id" }; + }; } diff --git a/src/packages/frontend/frame-editors/task-editor/editor.ts b/src/packages/frontend/frame-editors/task-editor/editor.ts index 2581c3ca87..d39fdeb02a 100644 --- a/src/packages/frontend/frame-editors/task-editor/editor.ts +++ b/src/packages/frontend/frame-editors/task-editor/editor.ts @@ -14,6 +14,7 @@ import { createEditor } from "../frame-tree/editor"; import { EditorDescription } from "../frame-tree/types"; import { terminal } from "../terminal-editor/editor"; import { time_travel } from "../time-travel-editor/editor"; +import { search } from "./search"; const tasks: EditorDescription = { type: "tasks", @@ -25,7 +26,6 @@ const tasks: EditorDescription = { return createElement(TaskEditor, { ...props, actions, - path: actions.path, }); }, commands: set([ @@ -38,6 +38,14 @@ const tasks: EditorDescription = { "help", "export_to_markdown", "chatgpt", + "show_search", + ]), + buttons: set([ + "undo", + "redo", + "decrease_font_size", + "increase_font_size", + "show_search", ]), } as const; @@ -45,6 +53,7 @@ const EDITOR_SPEC = { tasks, terminal, time_travel, + search, } as const; export const Editor = createEditor({ diff --git a/src/packages/frontend/frame-editors/task-editor/search.tsx b/src/packages/frontend/frame-editors/task-editor/search.tsx new file mode 100644 index 0000000000..ec7966ca31 --- /dev/null +++ b/src/packages/frontend/frame-editors/task-editor/search.tsx @@ -0,0 +1,22 @@ +import StaticMarkdown from "@cocalc/frontend/editors/slate/static-markdown"; +import { createSearchEditor } from "@cocalc/frontend/frame-editors/generic/search"; + +export const DONE = "☑ "; + +function Preview({ content }) { + return ( + */, + opacity: content.startsWith(DONE) ? 0.5 : undefined, + }} + /> + ); +} + +export const search = createSearchEditor({ + Preview, + updateField: "tasks", + title: "Task List", +});