Skip to content

Commit 2460c97

Browse files
authored
Render suggestive mode overlay button (#465)
* extract suggestive mode personal setting * add home setting * remove console log
1 parent 8464cf1 commit 2460c97

4 files changed

Lines changed: 215 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { Button, Tooltip } from "@blueprintjs/core";
2+
import React, { useCallback, useEffect, useMemo, useState } from "react";
3+
import ReactDOM from "react-dom";
4+
import useInViewport from "react-in-viewport/dist/es/lib/useInViewport";
5+
import { OnloadArgs } from "roamjs-components/types/native";
6+
import { getBlockUidFromTarget } from "roamjs-components/dom";
7+
import ExtensionApiContextProvider from "roamjs-components/components/ExtensionApiContext";
8+
9+
// TODO: REMOVE THE STUB FUNCTIONS BELOW'
10+
11+
const panelManager = {
12+
isOpen: (tag: string) => false,
13+
toggle: ({
14+
tag,
15+
blockUid,
16+
onloadArgs,
17+
}: {
18+
tag: string;
19+
blockUid: string;
20+
onloadArgs: OnloadArgs;
21+
}) => {},
22+
};
23+
24+
const subscribeToPanelState = (
25+
tag: string,
26+
setIsPanelOpen: (isPanelOpen: boolean) => void,
27+
) => {
28+
return () => {};
29+
};
30+
31+
// END OF STUB FUNCTIONS
32+
33+
const SuggestiveModeOverlay = ({
34+
tag,
35+
parentEl,
36+
onloadArgs,
37+
}: {
38+
tag: string;
39+
parentEl: HTMLElement;
40+
onloadArgs: OnloadArgs;
41+
}) => {
42+
const blockUid = useMemo(() => getBlockUidFromTarget(parentEl), [parentEl]);
43+
const [isPanelOpen, setIsPanelOpen] = useState(() =>
44+
panelManager.isOpen(tag),
45+
);
46+
useEffect(() => {
47+
const unsubscribe = subscribeToPanelState(tag, setIsPanelOpen);
48+
return unsubscribe;
49+
}, [tag]);
50+
51+
const toggleHighlight = useCallback(
52+
(on: boolean) => {
53+
const nodes = document.querySelectorAll(
54+
`[data-dg-block-uid="${blockUid}"]`,
55+
);
56+
nodes.forEach((el) => {
57+
const elem = el as HTMLElement;
58+
if (
59+
elem.classList.contains("suggestive-mode-overlay") ||
60+
elem.closest(".suggestive-mode-overlay")
61+
)
62+
return;
63+
elem.classList.toggle("dg-highlight", on);
64+
});
65+
},
66+
[blockUid],
67+
);
68+
69+
const handleTogglePanel = useCallback(
70+
(e: React.MouseEvent) => {
71+
e.stopPropagation();
72+
e.preventDefault();
73+
toggleHighlight(false);
74+
panelManager.toggle({ tag, blockUid, onloadArgs });
75+
},
76+
[tag, blockUid, onloadArgs, toggleHighlight],
77+
);
78+
79+
return (
80+
<div className="suggestive-mode-overlay flex max-w-3xl">
81+
<Tooltip
82+
content={
83+
isPanelOpen ? "Close suggestions panel" : "Open suggestions panel"
84+
}
85+
hoverOpenDelay={200}
86+
>
87+
<Button
88+
data-dg-role="panel-toggle"
89+
data-dg-tag={tag}
90+
data-dg-block-uid={blockUid}
91+
icon={isPanelOpen ? "panel-table" : "panel-stats"}
92+
minimal
93+
small
94+
intent={isPanelOpen ? "primary" : "none"}
95+
onClick={handleTogglePanel}
96+
onMouseEnter={() => toggleHighlight(true)}
97+
onMouseLeave={() => toggleHighlight(false)}
98+
/>
99+
</Tooltip>
100+
</div>
101+
);
102+
};
103+
104+
const Wrapper = ({
105+
parent,
106+
tag,
107+
onloadArgs,
108+
}: {
109+
parent: HTMLElement;
110+
tag: string;
111+
onloadArgs: OnloadArgs;
112+
}) => {
113+
const { inViewport } = useInViewport(
114+
{ current: parent },
115+
{},
116+
{ disconnectOnLeave: false },
117+
{},
118+
);
119+
return inViewport ? (
120+
<SuggestiveModeOverlay
121+
tag={tag}
122+
parentEl={parent}
123+
onloadArgs={onloadArgs}
124+
/>
125+
) : null;
126+
};
127+
128+
export const renderSuggestive = ({
129+
tag,
130+
parent,
131+
onloadArgs,
132+
}: {
133+
tag: string;
134+
parent: HTMLElement;
135+
onloadArgs: OnloadArgs;
136+
}) => {
137+
parent.style.margin = "0 8px";
138+
parent.style.display = "inline-block";
139+
parent.onmousedown = (e) => e.stopPropagation();
140+
ReactDOM.render(
141+
<ExtensionApiContextProvider {...onloadArgs}>
142+
<Wrapper tag={tag} parent={parent} onloadArgs={onloadArgs} />
143+
</ExtensionApiContextProvider>,
144+
parent,
145+
);
146+
};
147+
148+
export default SuggestiveModeOverlay;

apps/roam/src/components/settings/HomePersonalSettings.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Description from "roamjs-components/components/Description";
55
import { NodeMenuTriggerComponent } from "~/components/DiscourseNodeMenu";
66
import {
77
getOverlayHandler,
8+
getSuggestiveOverlayHandler,
89
onPageRefObserverChange,
910
previewPageRefHandler,
1011
} from "~/utils/pageRefObserverHandlers";
@@ -76,6 +77,31 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {
7677
</>
7778
}
7879
/>
80+
<Checkbox
81+
defaultChecked={
82+
extensionAPI.settings.get("suggestive-mode-overlay") as boolean
83+
}
84+
onChange={(e) => {
85+
const target = e.target as HTMLInputElement;
86+
void extensionAPI.settings.set(
87+
"suggestive-mode-overlay",
88+
target.checked,
89+
);
90+
onPageRefObserverChange(getSuggestiveOverlayHandler(onloadArgs))(
91+
target.checked,
92+
);
93+
}}
94+
labelElement={
95+
<>
96+
Suggestive Mode Overlay
97+
<Description
98+
description={
99+
"Whether or not to overlay Suggestive Mode button over Discourse Node references."
100+
}
101+
/>
102+
</>
103+
}
104+
/>
79105
<Checkbox
80106
defaultChecked={
81107
extensionAPI.settings.get("text-selection-popup") !== false

apps/roam/src/utils/initializeObserversAndListeners.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
getPageRefObserversSize,
2525
previewPageRefHandler,
2626
overlayPageRefHandler,
27+
getSuggestiveOverlayHandler,
2728
} from "~/utils/pageRefObserverHandlers";
2829
import getDiscourseNodes from "~/utils/getDiscourseNodes";
2930
import { OnloadArgs } from "roamjs-components/types";
@@ -142,6 +143,10 @@ export const initObservers = async ({
142143
});
143144
}) as EventListener;
144145

146+
if (onloadArgs.extensionAPI.settings.get("suggestive-mode-overlay")) {
147+
addPageRefObserver(getSuggestiveOverlayHandler(onloadArgs));
148+
}
149+
145150
const graphOverviewExportObserver = createHTMLObserver({
146151
tag: "DIV",
147152
className: "rm-graph-view-control-panel__main-options",

apps/roam/src/utils/pageRefObserverHandlers.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import isDiscourseNode from "./isDiscourseNode";
44
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
55
import { render as discourseOverlayRender } from "~/components/DiscourseContextOverlay";
66
import { OnloadArgs } from "roamjs-components/types";
7+
import { renderSuggestive as renderSuggestiveOverlay } from "~/components/SuggestiveModeOverlay";
78

89
const pageRefObservers = new Set<(s: HTMLSpanElement) => void>();
910
const pageRefObserverRef: { current?: MutationObserver } = {
@@ -17,6 +18,12 @@ export const getOverlayHandler = (onloadArgs: OnloadArgs) =>
1718
(cachedHandler = (s: HTMLSpanElement) =>
1819
overlayPageRefHandler(s, onloadArgs));
1920

21+
let cachedSuggestiveHandler: ((s: HTMLSpanElement) => void) | undefined;
22+
export const getSuggestiveOverlayHandler = (onloadArgs: OnloadArgs) =>
23+
cachedSuggestiveHandler ||
24+
(cachedSuggestiveHandler = (s: HTMLSpanElement) =>
25+
suggestiveOverlayPageRefHandler(s, onloadArgs));
26+
2027
export const overlayPageRefHandler = (
2128
s: HTMLSpanElement,
2229
onloadArgs: OnloadArgs,
@@ -46,6 +53,35 @@ export const overlayPageRefHandler = (
4653
}
4754
};
4855

56+
export const suggestiveOverlayPageRefHandler = (
57+
s: HTMLSpanElement,
58+
onloadArgs: OnloadArgs,
59+
) => {
60+
if (s.parentElement && !s.parentElement.closest(".rm-page-ref")) {
61+
const tag =
62+
s.getAttribute("data-tag") ||
63+
s.parentElement.getAttribute("data-link-title");
64+
if (
65+
tag &&
66+
!s.getAttribute("data-discourse-suggestive-overlay") &&
67+
isDiscourseNode(getPageUidByPageTitle(tag))
68+
) {
69+
s.setAttribute("data-discourse-suggestive-overlay", "true");
70+
const parent = document.createElement("span");
71+
renderSuggestiveOverlay({
72+
parent,
73+
tag: tag.replace(/\\"/g, '"'),
74+
onloadArgs,
75+
});
76+
if (s.hasAttribute("data-tag")) {
77+
s.appendChild(parent);
78+
} else {
79+
s.parentElement.appendChild(parent);
80+
}
81+
}
82+
}
83+
};
84+
4985
export const previewPageRefHandler = (s: HTMLSpanElement) => {
5086
const tag =
5187
s.getAttribute("data-tag") ||

0 commit comments

Comments
 (0)