Skip to content

Commit

Permalink
add: 0.6.0, open new tab if some modifier key is pressed
Browse files Browse the repository at this point in the history
  • Loading branch information
Odaimoko committed May 6, 2024
1 parent 5b8e96f commit f1c368c
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 55 deletions.
23 changes: 22 additions & 1 deletion src/settings/TPMSettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import {App, PluginSettingTab, Setting, ValueComponent} from "obsidian";
import TPMPlugin from "../main";
import {createRoot, Root} from "react-dom/client";
import React, {useEffect, useState} from "react";
import {getSettings, setSettingsValueAndSave, SettingName, TPM_DEFAULT_SETTINGS, usePluginSettings} from "./settings";
import {
getSettings,
ModifierKeyOnClick,
setSettingsValueAndSave,
SettingName,
TPM_DEFAULT_SETTINGS,
usePluginSettings
} from "./settings";
import {SerializedType} from "./SerializedType";
import {isTagNameValid} from "../data-model/markdown-parse";
import {HStack, VStack} from "../ui/react-view/view-template/h-stack";
Expand Down Expand Up @@ -128,6 +135,20 @@ export class TPMSettingsTab extends PluginSettingTab {
</PluginContext.Provider>
)

new Setting(containerEl).setName("Always open task in new tab modify key")
.setDesc("If the key is pressed when clicking the task, always open in a new tab. None for never apply modifier keys.")
.addDropdown(dropdown => {
dropdown.addOptions({
[ModifierKeyOnClick.None]: "None",
[ModifierKeyOnClick.Alt]: "Alt (Option)",
[ModifierKeyOnClick.MetaOrCtrl]: "Ctrl (Cmd)",
[ModifierKeyOnClick.Shift]: "Shift",
}).setValue(this.plugin.settings.always_open_task_in_new_tab_modify_key)
.onChange(async (value) => {
await setSettingsValueAndSave(this.plugin, "always_open_task_in_new_tab_modify_key", value)
})

})
}

setValueAndSave<T extends SerializedType>(settingName: SettingName) {
Expand Down
29 changes: 27 additions & 2 deletions src/settings/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ export enum TaskPriority {
Low = 4,
}

export enum ModifierKeyOnClick {
None = "None", // never open a new tab
MetaOrCtrl = "MetaOrCtrl", // meta is command on mac, windows key on windows
// Ctrl = "Ctrl", // ctrl on windows. not detected on mac
Alt = "Alt", // alt on windows, option on mac
Shift = "Shift", // shift
}

function getDefaultPriority() {
return Math.floor(maxPriorityTags / 2); // 1 -> medium's index
}
Expand Down Expand Up @@ -87,7 +95,7 @@ export interface TPMSettings {
priority_tags: SerializedType[], // 0.5.0, from high to low, should add the prefix `tpm/tag/`
search_opened_tabs_before_navigating_tasks: boolean, // 0.6.0, if true, look for the existing tabs first, if not found, open a new tab; if false, always open in current tab
open_new_tab_if_task_tab_not_found: boolean, // 0.6.0, if true, when the task file is not found in existing tabs, open a new tab; if false, open in current editor,
always_open_task_in_new_tab_modify_key: SerializedType, // 0.6.0. Key stroke enum. If click the task with this key pressed, always open a new tab.
always_open_task_in_new_tab_modify_key: ModifierKeyOnClick, // 0.6.0. Key stroke enum. If click the task with this key pressed, always open a new tab.
cached_help_page_tutorial_tldr: SerializedType,
}

Expand Down Expand Up @@ -115,7 +123,7 @@ export const TPM_DEFAULT_SETTINGS: Partial<TPMSettings> = {
priority_tags: ["hi", "med_hi", "med", "med_lo", "lo"] as SerializedType[], // 0.5.0
search_opened_tabs_before_navigating_tasks: true, // 0.6.0
open_new_tab_if_task_tab_not_found: true, // 0.6.0
always_open_task_in_new_tab_modify_key: "Meta", // TODO
always_open_task_in_new_tab_modify_key: ModifierKeyOnClick.MetaOrCtrl, // 0.6.0
cached_help_page_tutorial_tldr: false,
}
export type SettingName = keyof TPMSettings;
Expand Down Expand Up @@ -170,3 +178,20 @@ export function usePluginSettings<T extends SerializedType>(name: SettingName):
}, [name, plugin, handler]);
return [value, setValueAndSave];
}


export function getForceNewTabOnClick(plugin: OdaPmToolPlugin, event: React.MouseEvent) {
let forceNewTab = false;
switch (plugin.settings.always_open_task_in_new_tab_modify_key) {
case ModifierKeyOnClick.MetaOrCtrl:
forceNewTab = event.metaKey || event.ctrlKey;
break;
case ModifierKeyOnClick.Alt:
forceNewTab = event.altKey;
break;
case ModifierKeyOnClick.Shift:
forceNewTab = event.shiftKey;
break;
}
return forceNewTab;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {MouseEvent} from "react";

// dom has its own MouseEvent, but we want to use React's MouseEvent
// to satisfy react's generic type MouseEventHandler<T> while ignore the generic type T
export type GeneralMouseEventHandler = ((e: MouseEvent) => void) | (() => void)
8 changes: 5 additions & 3 deletions src/ui/react-view/project-view.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {OdaPmProject, OdaPmProjectDefinition} from "../../data-model/OdaPmProject";
import React, {useContext} from "react";
import React, {MouseEvent, useContext} from "react";
import {PluginContext} from "../obsidian/manage-page-view";
import {ClickableIconView, InternalLinkView} from "./view-template/icon-view";
import {iconViewAsAWholeStyle} from "./style-def";
import {openProjectPrecisely} from "../../utils/io-util";
import {VStack} from "./view-template/h-stack";
import {HoveringPopup, usePopup} from "./view-template/hovering-popup";
import {getForceNewTabOnClick} from "../../settings/settings";

export const IconName_Project = "folder";

Expand All @@ -18,8 +19,9 @@ function ProjectLinkView(props: {
onContentClicked={openProject}
content={<label style={{whiteSpace: "nowrap"}}>{props.def.getLinkText()}</label>}/>

function openProject() {
openProjectPrecisely(props.project, props.def, plugin.app.workspace);
function openProject(e: MouseEvent) {
const forceNewTab = getForceNewTabOnClick(plugin, e);
openProjectPrecisely(props.project, props.def, plugin.app.workspace, forceNewTab);
}
}

Expand Down
15 changes: 7 additions & 8 deletions src/ui/react-view/task-table-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {OdaPmTask, setTaskPriority} from "../../data-model/OdaPmTask";
import OdaPmToolPlugin from "../../main";
import {I_OdaPmStep, I_OdaPmWorkflow, TaskStatus_checked, TaskStatus_unchecked} from "../../data-model/workflow-def";
import {openTaskPrecisely, rewriteTask} from "../../utils/io-util";
import React, {ReactElement, useContext, useEffect, useState} from "react";
import React, {MouseEvent, ReactElement, useContext, useEffect, useState} from "react";
import {PluginContext} from "../obsidian/manage-page-view";
import {
getForceNewTabOnClick,
getSettings,
setSettingsValueAndSave,
TableSortBy,
Expand Down Expand Up @@ -120,18 +121,16 @@ export const OdaTaskSummaryCell = ({oTask, taskFirstColumn, showCheckBox, showPr
{showCheckBox ? <ExternalControlledCheckbox
content={checkBoxContent}
onChange={tickSummary}
onContentClicked={() => {
devLog("[taskview] ExternalControlledCheckbox Clicked")
openThisTask();
}}
onContentClicked={openThisTask}
externalControl={oTask.stepCompleted()}
/> : checkBoxContent}

</HStack>;

function openThisTask() {
devLog("[taskview] Open this task")
openTaskPrecisely(workspace, oTask.boundTask);
function openThisTask(event: MouseEvent) {
const forceNewTab = getForceNewTabOnClick(plugin, event);
devLog(`[taskview] Open this task alt: ${event.altKey} shift: ${event.shiftKey} ctrl: ${event.ctrlKey} meta: ${event.metaKey} forceNewTab: ${forceNewTab}`)
openTaskPrecisely(workspace, oTask.boundTask, forceNewTab);
}


Expand Down
3 changes: 2 additions & 1 deletion src/ui/react-view/view-template/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {JSX, useState} from "react";
import {I_Stylable} from "./icon-view";
import {IRenderable} from "../../common/i-renderable";
import {GeneralMouseEventHandler} from "../event-handling/general-mouse-event-handler";

/**
* A checkbox that is totally controlled by its parent.
Expand All @@ -15,7 +16,7 @@ export const ExternalControlledCheckbox = ({externalControl, onChange, onContent
{
externalControl: boolean,
onChange: () => void,
onContentClicked?: () => void,
onContentClicked?: GeneralMouseEventHandler,
content?: IRenderable,

} & I_Stylable) => {
Expand Down
9 changes: 5 additions & 4 deletions src/ui/react-view/view-template/icon-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
import {getIcon} from "obsidian";
import {HtmlStringComponent} from "./html-string-component";
import {I_InteractableId} from "../props-typing/i-interactable-id";
import {GeneralMouseEventHandler} from "../event-handling/general-mouse-event-handler";

export const CssClass_Link = "cm-underline";
export const obsidianIconTopOffset = 4;
Expand Down Expand Up @@ -33,8 +34,8 @@ export function ObsidianIconView({iconName, style}: { iconName: string } & I_Sty
*/
export function InternalLinkView({content, onIconClicked, onContentClicked, style}: {
content: IRenderable,
onIconClicked?: () => void,
onContentClicked?: () => void,
onIconClicked?: GeneralMouseEventHandler,
onContentClicked?: GeneralMouseEventHandler,
} & I_Stylable) {
return <ClickableIconView style={style} content={content} onIconClicked={onIconClicked}
onContentClicked={onContentClicked}
Expand All @@ -44,8 +45,8 @@ export function InternalLinkView({content, onIconClicked, onContentClicked, styl

interface I_IconClickable {
content?: IRenderable;
onIconClicked?: () => void;
onContentClicked?: () => void;
onIconClicked?: GeneralMouseEventHandler
onContentClicked?: GeneralMouseEventHandler
clickable?: boolean;

}
Expand Down
13 changes: 9 additions & 4 deletions src/ui/react-view/workflow-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// region Legend
import {HStack} from "./view-template/h-stack";
import {I_OdaPmWorkflow, Workflow_Type_Enum_Array, WorkflowType} from "../../data-model/workflow-def";
import React, {useContext} from "react";
import React, {MouseEvent, useContext} from "react";
import {PluginContext} from "../obsidian/manage-page-view";
import {ExternalControlledCheckbox} from "./view-template/checkbox";
import {I_Stylable, InternalLinkView} from "./view-template/icon-view";
Expand All @@ -14,6 +14,7 @@ import {taskCheckBoxMargin} from "./task-table-view";
import {ExternalToggleView} from "./view-template/toggle-view";
import {OptionValueType, SearchableDropdown} from "./view-template/searchable-dropdown";
import {devLog} from "../../utils/env-util";
import {getForceNewTabOnClick} from "../../settings/settings";

/**
* Accept children as a HStack with a unified style
Expand Down Expand Up @@ -200,11 +201,15 @@ export const ClickableWorkflowView = ({workflow, displayNames, setDisplayNames,
{showWorkflowIcon ? getIconByWorkflow(workflow) : null}
<label style={taskCheckBoxMargin}>{wfName}</label>
</span>}
onIconClicked={() =>
// Go to workflow def
openTaskPrecisely(plugin.app.workspace, workflow.boundTask)}
onIconClicked={openThisWorkflow}
onContentClicked={tickCheckbox}/>
</>;

function openThisWorkflow(e: MouseEvent) {
const forceNewTab = getForceNewTabOnClick(plugin, e);
return openTaskPrecisely(plugin.app.workspace, workflow.boundTask, forceNewTab);
}

return <span style={{display: "inline-block", marginRight: 15}}>
{showCheckBox ? <ExternalControlledCheckbox
content={content}
Expand Down
74 changes: 42 additions & 32 deletions src/utils/io-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,16 @@ export const LIST_ITEM_REGEX = /^[\s>]*(\d+\.|\d+\)|\*|-|\+)\s*(\[.{0,1}\])?\s*(

// endregion

function openFileAtStart(workspace: Workspace, path: string) {
workspace.openLinkText(path, path, false, {
state: {
active: true
},
eState: {
line: 0,
cursor: {
from: {line: 0, ch: 0},
to: {line: 1, ch: 0},
},
},
});

}

// if we use workspace.openLinkText, a task without a block id will be opened with its section
export async function openTaskPrecisely(workspace: Workspace, task: STask) {
devLog(`openTaskPrecisely ${workspace.getLeavesOfType("markdown").length} tabs`)
async function getOpenInNewTab(forceNewTab: boolean, workspace: Workspace, path: string) {
const settings = getSettings();
let foundTabWithFile = false;
if (settings?.search_opened_tabs_before_navigating_tasks) {
if (
!forceNewTab && settings?.search_opened_tabs_before_navigating_tasks
) {
const mdLeaf = workspace.getLeavesOfType("markdown").find((k: WorkspaceLeaf) => {
const mdView = k.view as MarkdownView;
// mdView can have no file (ie the file is not saved to disk)
return mdView.file?.path === task.path;
return mdView.file?.path === path;
})
if (mdLeaf) {
// If found, set the active leaf to this view
Expand All @@ -86,18 +70,44 @@ export async function openTaskPrecisely(workspace: Workspace, task: STask) {

}

let openInNewLeaf = false;
if (!settings?.search_opened_tabs_before_navigating_tasks) {
// if we don't search opened tabs, we always open in current tab
openInNewLeaf = false;
} else {
if (foundTabWithFile) {
let openInNewLeaf = forceNewTab;
if (!forceNewTab) {
// if we do not force new tab, we need to check if we should open in new tab
if (!settings?.search_opened_tabs_before_navigating_tasks) {
// if we don't search opened tabs, we always open in current tab
openInNewLeaf = false;
} else {
openInNewLeaf = settings?.open_new_tab_if_task_tab_not_found ?? false;
if (foundTabWithFile) {
openInNewLeaf = false;
} else {
openInNewLeaf = settings?.open_new_tab_if_task_tab_not_found ?? false;
}
}
}

return openInNewLeaf;
}

async function openFileAtStart(workspace: Workspace, path: string, forceNewTab = false) {
const openInNewLeaf = await getOpenInNewTab(forceNewTab, workspace, path);
workspace.openLinkText(path, path, openInNewLeaf, {
state: {
active: true
},
eState: {
line: 0,
cursor: {
from: {line: 0, ch: 0},
to: {line: 1, ch: 0},
},
},
});
}


// if we use workspace.openLinkText, a task without a block id will be opened with its section
export async function openTaskPrecisely(workspace: Workspace, task: STask, forceNewTab = false) {
devLog(`[taskview] openTaskPrecisely ${workspace.getLeavesOfType("markdown").length} tabs. ForceNewTab ${forceNewTab}`)
const openInNewLeaf = await getOpenInNewTab(forceNewTab, workspace, task.path);
// Copy from dataview. See TaskItem.
// highlight cursor
devLog(`[OpenTask] Task ${task.path} openInNewLeaf ${openInNewLeaf}`)
Expand All @@ -117,16 +127,16 @@ export async function openTaskPrecisely(workspace: Workspace, task: STask) {
);
}

export function openProjectPrecisely(project: OdaPmProject, defType: OdaPmProjectDefinition, workspace: Workspace) {
export function openProjectPrecisely(project: OdaPmProject, defType: OdaPmProjectDefinition, workspace: Workspace, forceNewTab = false) {
switch (defType.type) {
case "folder": // Project_FolderProject_Frontmatter
case "file": // Project_FileProject_Frontmatter
openFileAtStart(workspace, defType.page?.path)
openFileAtStart(workspace, defType.page?.path, forceNewTab)
console.log(defType.page?.path)
break;
case "tag_override": // task or wf override
console.log(`openProjectPrecisely: tag_override. ${defType.taskable} ${defType.path}`)
openTaskPrecisely(workspace, defType.taskable?.boundTask)
openTaskPrecisely(workspace, defType.taskable?.boundTask, forceNewTab)
break;
}
}
Expand Down

0 comments on commit f1c368c

Please sign in to comment.