From 4a7a5961800395298bcea82e69503afd2767bf2c Mon Sep 17 00:00:00 2001
From: Pete F
Date: Mon, 21 Oct 2024 13:17:31 +0100
Subject: [PATCH 01/15] Remove export from non-imported types
---
client/src/types/PayloadAndType.ts | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/client/src/types/PayloadAndType.ts b/client/src/types/PayloadAndType.ts
index 633a3c4a..ba57b864 100644
--- a/client/src/types/PayloadAndType.ts
+++ b/client/src/types/PayloadAndType.ts
@@ -1,19 +1,17 @@
import * as Sentry from "@sentry/react";
-export const sources = ["grid", "mam"] as const;
-export const sourceTypes = ["crop", "original", "search", "video"] as const;
+const sources = ["grid", "mam"] as const;
+const sourceTypes = ["crop", "original", "search", "video"] as const;
-export type Source = (typeof sources)[number];
-export const isSource = (source: unknown): source is Source =>
+type Source = (typeof sources)[number];
+const isSource = (source: unknown): source is Source =>
sources.includes(source as Source);
-export type SourceType = (typeof sourceTypes)[number];
-export const isSourceType = (sourceType: unknown): sourceType is SourceType =>
+type SourceType = (typeof sourceTypes)[number];
+const isSourceType = (sourceType: unknown): sourceType is SourceType =>
sourceTypes.includes(sourceType as SourceType);
export type PayloadType = `${Source}-${SourceType}`; // TODO improve this type as it enumerates all the combinations, e.g. mam-original which is not valid
-export const isPayloadType = (
- payloadType: string
-): payloadType is PayloadType => {
+const isPayloadType = (payloadType: string): payloadType is PayloadType => {
const parts = payloadType.split("-");
return parts.length === 2 && isSource(parts[0]) && isSourceType(parts[1]);
};
@@ -36,11 +34,11 @@ export interface PayloadWithApiUrl extends PayloadCommon {
apiUrl: string;
}
-export type Payload =
+type Payload =
| PayloadWithThumbnail
| PayloadWithApiUrl
| PayloadWithExternalUrl;
-export const isPayload = (maybePayload: unknown): maybePayload is Payload => {
+const isPayload = (maybePayload: unknown): maybePayload is Payload => {
return (
typeof maybePayload === "object" &&
maybePayload !== null &&
From ca269b9f54b6498e445616f012f53cd2b09a5fee Mon Sep 17 00:00:00 2001
From: Pete F
Date: Mon, 21 Oct 2024 13:18:21 +0100
Subject: [PATCH 02/15] Stash: send snippet to pinboard
---
bootstrapping-lambda/local/index.html | 26 +++++
client/src/app.tsx | 2 +
client/src/newswires/newswiresIntegration.tsx | 102 ++++++++++++++++++
client/src/payloadDisplay.tsx | 10 ++
client/src/types/PayloadAndType.ts | 23 +++-
client/src/util.ts | 15 +++
6 files changed, 173 insertions(+), 5 deletions(-)
create mode 100644 client/src/newswires/newswiresIntegration.tsx
diff --git a/bootstrapping-lambda/local/index.html b/bootstrapping-lambda/local/index.html
index 7ba61983..4d5bda1b 100644
--- a/bootstrapping-lambda/local/index.html
+++ b/bootstrapping-lambda/local/index.html
@@ -21,6 +21,32 @@
+
+ not inside target
+ Not inside target
+
+
Pinboard selection target
+
+ This is a target for the Pinboard library to render the selection
+ interface into. It will be hidden by the library when not in use. This
+ is a target for the Pinboard library to render the selection interface
+ into. It will be hidden by the library when not in use. This is a target
+ for the Pinboard library to render the selection interface into. It will
+ be hidden by the library when not in use. This is a target for the
+ Pinboard library to render the selection interface into. It will be
+ hidden by the library when not in use. This is a target for the Pinboard
+ library to render the selection interface into. It will be hidden by the
+ library when not in use. This is a target for the Pinboard library to
+ render the selection interface into. It will be hidden by the library
+ when not in use.
+
+
+ not inside target
+
+
Expand pinboard via query param ?expandPinboard=true
diff --git a/client/src/app.tsx b/client/src/app.tsx
index bb1d9d88..d74ad398 100644
--- a/client/src/app.tsx
+++ b/client/src/app.tsx
@@ -58,6 +58,7 @@ import {
SUGGEST_ALTERNATE_CROP_QUERY_SELECTOR,
SuggestAlternateCrops,
} from "./fronts/suggestAlternateCrops";
+import { NewswiresIntegration } from "./newswires/newswiresIntegration";
const PRESELECT_PINBOARD_HTML_TAG = "pinboard-preselect";
const PRESET_UNREAD_NOTIFICATIONS_COUNT_HTML_TAG = "pinboard-bubble-preset";
@@ -509,6 +510,7 @@ export const PinBoardApp = ({
expand={() => setIsExpanded(true)}
/>
))}
+
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
new file mode 100644
index 00000000..dc785b3a
--- /dev/null
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -0,0 +1,102 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { debounce } from "../util";
+import React from "react";
+import { css } from "@emotion/react";
+import { useGlobalStateContext } from "../globalState";
+
+const SELECTION_TARGET_DATA_ATTR = "[data-pinboard-selection-target]";
+
+export const NewswiresIntegration = () => {
+ const { setPayloadToBeSent } = useGlobalStateContext();
+ const [selectedHTML, setSelectedHTML] = useState(null);
+
+ const handleSelectionChange = () => {
+ const selection = window.getSelection();
+ if (selection) {
+ const maybeOriginalTargetEl = document.querySelector(
+ SELECTION_TARGET_DATA_ATTR
+ );
+ try {
+ const clonedContents = selection.getRangeAt(0).cloneContents();
+ const maybeClonedTargetEl = clonedContents.querySelector(
+ SELECTION_TARGET_DATA_ATTR
+ );
+ if (maybeClonedTargetEl) {
+ console.log(
+ "selection contains whole target element; contents:",
+ maybeClonedTargetEl.innerHTML
+ );
+ setSelectedHTML(maybeClonedTargetEl.innerHTML);
+ } else {
+ if (
+ maybeOriginalTargetEl?.contains(selection.anchorNode) &&
+ maybeOriginalTargetEl?.contains(selection.focusNode)
+ ) {
+ const tempEl = document.createElement("div");
+ tempEl.appendChild(clonedContents);
+ console.log(
+ "selection is within target element; contents:",
+ tempEl.innerHTML
+ );
+ setSelectedHTML(tempEl.innerHTML);
+ }
+ }
+ } catch (e) {
+ console.error("Error cloning selection contents", e);
+ }
+ }
+ };
+
+ const debouncedSelectionHandler = useMemo(
+ () => debounce(handleSelectionChange, 750),
+ [handleSelectionChange]
+ );
+
+ useEffect(() => {
+ document.addEventListener("selectionchange", debouncedSelectionHandler);
+ /**
+ * todos:
+ * [ ] limit to newswires domain
+ * [x] add selection listener -- addEventListener("selectionchange", (event) => {});
+ * [x] debounce handler
+ * [x] check parent node of selection is newswires body text el (maybe add data attribute to body text el)
+ * - (find first shared parent of anchorNode and focusNode, make sure we're not sharing bits of text outside of the target)
+ * [x] extract HTML from selection (see chat thread)
+ * [ ] render button when there's a selection
+ * [ ] do things with pinboard
+ */
+ return () =>
+ document.removeEventListener(
+ "selectionchange",
+ debouncedSelectionHandler
+ );
+ }, []);
+
+ const addSelectionToPinboard = useCallback(() => {
+ if (selectedHTML) {
+ setPayloadToBeSent({
+ type: "newswires-snippet",
+ payload: {
+ embeddableHtml: selectedHTML,
+ embeddableUrl: window.location.href,
+ },
+ });
+ }
+ }, [selectedHTML, setPayloadToBeSent]);
+
+ return (
+
+ {selectedHTML && (
+
+ Add to pinboard
+
+ )}
+
+ );
+};
diff --git a/client/src/payloadDisplay.tsx b/client/src/payloadDisplay.tsx
index 1d37f3e1..5290ec01 100644
--- a/client/src/payloadDisplay.tsx
+++ b/client/src/payloadDisplay.tsx
@@ -105,6 +105,16 @@ export const PayloadDisplay = ({
payload={payloadAndType.payload}
/>
)}
+ {payloadAndType.type === "newswires-snippet" && (
+
+ {payloadAndType.payload.embeddableHtml}
+
+ )}
{clearPayloadToBeSent && (
diff --git a/client/src/types/PayloadAndType.ts b/client/src/types/PayloadAndType.ts
index ba57b864..a7523e9d 100644
--- a/client/src/types/PayloadAndType.ts
+++ b/client/src/types/PayloadAndType.ts
@@ -1,7 +1,7 @@
import * as Sentry from "@sentry/react";
-const sources = ["grid", "mam"] as const;
-const sourceTypes = ["crop", "original", "search", "video"] as const;
+const sources = ["grid", "mam", "newswires"] as const;
+const sourceTypes = ["crop", "original", "search", "video", "snippet"] as const;
type Source = (typeof sources)[number];
const isSource = (source: unknown): source is Source =>
@@ -34,16 +34,23 @@ export interface PayloadWithApiUrl extends PayloadCommon {
apiUrl: string;
}
+export interface PayloadWithSnippet extends PayloadCommon {
+ embeddableHtml: string;
+}
+
type Payload =
| PayloadWithThumbnail
| PayloadWithApiUrl
- | PayloadWithExternalUrl;
+ | PayloadWithExternalUrl
+ | PayloadWithSnippet;
const isPayload = (maybePayload: unknown): maybePayload is Payload => {
return (
typeof maybePayload === "object" &&
maybePayload !== null &&
"embeddableUrl" in maybePayload &&
- ("thumbnail" in maybePayload || "apiUrl" in maybePayload)
+ ("thumbnail" in maybePayload ||
+ "apiUrl" in maybePayload ||
+ "embeddableHtml" in maybePayload)
);
};
@@ -62,10 +69,16 @@ export type MamVideoPayload = {
payload: PayloadWithExternalUrl;
};
+export type NewswiresSnippetPayload = {
+ type: "newswires-snippet";
+ payload: PayloadWithSnippet;
+};
+
export type PayloadAndType =
| StaticGridPayload
| DynamicGridPayload
- | MamVideoPayload;
+ | MamVideoPayload
+ | NewswiresSnippetPayload;
export const buildPayloadAndType = (
type: string,
diff --git a/client/src/util.ts b/client/src/util.ts
index 2650e8e8..7caa38f4 100644
--- a/client/src/util.ts
+++ b/client/src/util.ts
@@ -90,3 +90,18 @@ export const readAndThenSilentlyDropQueryParamFromURL = (param: string) => {
);
return value;
};
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any -- suitably generic function
+export const debounce = void>(
+ f: F,
+ delay: number
+): ((...args: Parameters) => void) => {
+ let waiting: ReturnType | undefined;
+
+ return (...args: Parameters) => {
+ if (waiting !== undefined) {
+ clearTimeout(waiting);
+ }
+ waiting = setTimeout(() => f(...args), delay);
+ };
+};
From a1244c0a743412b437b3d0e7232f5e6b6896ca09 Mon Sep 17 00:00:00 2001
From: Pete F
Date: Tue, 22 Oct 2024 17:29:29 +0100
Subject: [PATCH 03/15] WIP
---
client/package.json | 4 +-
client/src/newswires/newswiresIntegration.tsx | 110 +++++++++------
client/src/payloadDisplay.tsx | 62 ++++++--
client/src/types/PayloadAndType.ts | 29 ++--
yarn.lock | 132 ++++++++++++++++--
5 files changed, 261 insertions(+), 76 deletions(-)
diff --git a/client/package.json b/client/package.json
index fd5546a8..41416c80 100644
--- a/client/package.json
+++ b/client/package.json
@@ -30,7 +30,8 @@
"preact": "10.15.1",
"react-draggable": "^4.4.5",
"react-joyride": "^2.5.3",
- "react-shadow": "^19.0.2"
+ "react-shadow": "^19.0.2",
+ "sanitize-html": "^2.13.1"
},
"devDependencies": {
"@babel/core": "^7.17.4",
@@ -41,6 +42,7 @@
"@svgr/webpack": "^6.2.1",
"@types/react": "16.9.56",
"@types/react-dom": "16.9.9",
+ "@types/sanitize-html": "^2",
"@types/webpack-env": "^1.16.3",
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
"babel-loader": "^8.2.3",
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index dc785b3a..974be931 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -1,48 +1,52 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { debounce } from "../util";
import React from "react";
-import { css } from "@emotion/react";
+import { css, Global } from "@emotion/react";
import { useGlobalStateContext } from "../globalState";
+import { pinboard, pinMetal } from "../../colours";
+import { textSans } from "../../fontNormaliser";
+import { space } from "@guardian/source-foundations";
+import ReactDOM from "react-dom";
const SELECTION_TARGET_DATA_ATTR = "[data-pinboard-selection-target]";
export const NewswiresIntegration = () => {
- const { setPayloadToBeSent } = useGlobalStateContext();
+ const { setPayloadToBeSent, setIsExpanded } = useGlobalStateContext();
const [selectedHTML, setSelectedHTML] = useState(null);
+ const [mountPoint, setMountPoint] = useState(null);
+ const [buttonCoords, setButtonCoords] = useState({ x: 0, y: 0 });
const handleSelectionChange = () => {
const selection = window.getSelection();
- if (selection) {
+ if (selection && selection.rangeCount > 0) {
const maybeOriginalTargetEl = document.querySelector(
SELECTION_TARGET_DATA_ATTR
);
- try {
- const clonedContents = selection.getRangeAt(0).cloneContents();
- const maybeClonedTargetEl = clonedContents.querySelector(
- SELECTION_TARGET_DATA_ATTR
+
+ setMountPoint(maybeOriginalTargetEl);
+ const clonedContents = selection.getRangeAt(0).cloneContents();
+ const maybeClonedTargetEl = clonedContents.querySelector(
+ SELECTION_TARGET_DATA_ATTR
+ );
+ if (maybeClonedTargetEl) {
+ console.log(
+ "selection contains whole target element; contents:",
+ maybeClonedTargetEl.innerHTML
);
- if (maybeClonedTargetEl) {
- console.log(
- "selection contains whole target element; contents:",
- maybeClonedTargetEl.innerHTML
- );
- setSelectedHTML(maybeClonedTargetEl.innerHTML);
- } else {
- if (
- maybeOriginalTargetEl?.contains(selection.anchorNode) &&
- maybeOriginalTargetEl?.contains(selection.focusNode)
- ) {
- const tempEl = document.createElement("div");
- tempEl.appendChild(clonedContents);
- console.log(
- "selection is within target element; contents:",
- tempEl.innerHTML
- );
- setSelectedHTML(tempEl.innerHTML);
- }
- }
- } catch (e) {
- console.error("Error cloning selection contents", e);
+ setSelectedHTML(maybeClonedTargetEl.innerHTML);
+ setSelectionFocusNode(selectionFocusNode); // todo: set coords instead, based on selection
+ } else if (
+ maybeOriginalTargetEl?.contains(selection.anchorNode) &&
+ maybeOriginalTargetEl?.contains(selection.focusNode)
+ ) {
+ const tempEl = document.createElement("div");
+ tempEl.appendChild(clonedContents);
+ console.log(
+ "selection is within target element; contents:",
+ tempEl.innerHTML
+ );
+ setSelectedHTML(tempEl.innerHTML);
+ setSelectionFocusNode(selection.focusNode);
}
}
};
@@ -81,22 +85,44 @@ export const NewswiresIntegration = () => {
embeddableUrl: window.location.href,
},
});
+ setIsExpanded(true);
}
}, [selectedHTML, setPayloadToBeSent]);
return (
-
- {selectedHTML && (
-
- Add to pinboard
-
- )}
-
+ <>
+
+ {selectedHTML &&
+ mountPoint &&
+ ReactDOM.createPortal(
+
+
+ Add selection to pinboard
+
+
,
+ mountPoint
+ )}
+ >
);
};
diff --git a/client/src/payloadDisplay.tsx b/client/src/payloadDisplay.tsx
index 5290ec01..0a2c0b3f 100644
--- a/client/src/payloadDisplay.tsx
+++ b/client/src/payloadDisplay.tsx
@@ -1,7 +1,8 @@
-import React, { useContext } from "react";
+import React, { useContext, useMemo } from "react";
import { css } from "@emotion/react";
+import sanitizeHtml from "sanitize-html";
import { PayloadAndType } from "./types/PayloadAndType";
-import { neutral, palette, space } from "@guardian/source-foundations";
+import { brand, neutral, palette, space } from "@guardian/source-foundations";
import { GridStaticImageDisplay } from "./grid/gridStaticImageDisplay";
import { GridDynamicSearchDisplay } from "./grid/gridDynamicSearchDisplay";
import { TelemetryContext, PINBOARD_TELEMETRY_TYPE } from "./types/Telemetry";
@@ -24,6 +25,13 @@ export const PayloadDisplay = ({
}: PayloadDisplayProps) => {
const { payload } = payloadAndType;
const sendTelemetryEvent = useContext(TelemetryContext);
+
+ const safeSnippetHtml = useMemo(() => {
+ return payloadAndType.type === "newswires-snippet"
+ ? sanitizeHtml(payloadAndType.payload.embeddableHtml)
+ : undefined;
+ }, [payloadAndType]);
+
return (
{
- event.dataTransfer.setData("URL", payload.embeddableUrl);
- event.dataTransfer.setData(
- // prevent grid from accepting these as drops, as per https://github.com/guardian/grid/commit/4b72d93eedcbacb4f90680764d468781a72507f5#diff-771b9da876348ce4b4e057e2d8253324c30a8f3db4e434d49b3ce70dbbdb0775R138-R140
- "application/vnd.mediaservice.kahuna.image",
- "true"
- );
+ if (payloadAndType.type === "newswires-snippet") {
+ // event.dataTransfer.setData("text/plain", "This is text to drag");
+
+ event.dataTransfer.setData(
+ "text/plain",
+ sanitizeHtml(payloadAndType.payload.embeddableHtml)
+ );
+ } else {
+ event.dataTransfer.setData("URL", payload.embeddableUrl);
+ event.dataTransfer.setData(
+ // prevent grid from accepting these as drops, as per https://github.com/guardian/grid/commit/4b72d93eedcbacb4f90680764d468781a72507f5#diff-771b9da876348ce4b4e057e2d8253324c30a8f3db4e434d49b3ce70dbbdb0775R138-R140
+ "application/vnd.mediaservice.kahuna.image",
+ "true"
+ );
+ }
sendTelemetryEvent?.(PINBOARD_TELEMETRY_TYPE.DRAG_FROM_PINBOARD, {
assetType: payloadAndType?.type,
...(tab && { tab }),
@@ -106,14 +123,35 @@ export const PayloadDisplay = ({
/>
)}
{payloadAndType.type === "newswires-snippet" && (
-
- {payloadAndType.payload.embeddableHtml}
-
+
Newswires snippet:
+
+
+
+
)}
{clearPayloadToBeSent && (
diff --git a/client/src/types/PayloadAndType.ts b/client/src/types/PayloadAndType.ts
index a7523e9d..a898f09c 100644
--- a/client/src/types/PayloadAndType.ts
+++ b/client/src/types/PayloadAndType.ts
@@ -1,19 +1,16 @@
import * as Sentry from "@sentry/react";
-const sources = ["grid", "mam", "newswires"] as const;
-const sourceTypes = ["crop", "original", "search", "video", "snippet"] as const;
-
-type Source = (typeof sources)[number];
-const isSource = (source: unknown): source is Source =>
- sources.includes(source as Source);
-type SourceType = (typeof sourceTypes)[number];
-const isSourceType = (sourceType: unknown): sourceType is SourceType =>
- sourceTypes.includes(sourceType as SourceType);
-
-export type PayloadType = `${Source}-${SourceType}`; // TODO improve this type as it enumerates all the combinations, e.g. mam-original which is not valid
+const payloadTypes = [
+ "grid-crop",
+ "grid-original",
+ "grid-search",
+ "mam-video",
+ "newswires-snippet",
+] as const;
+
+export type PayloadType = (typeof payloadTypes)[number];
const isPayloadType = (payloadType: string): payloadType is PayloadType => {
- const parts = payloadType.split("-");
- return parts.length === 2 && isSource(parts[0]) && isSourceType(parts[1]);
+ return payloadTypes.includes(payloadType as PayloadType);
};
interface PayloadCommon {
@@ -99,6 +96,12 @@ export const buildPayloadAndType = (
"externalUrl" in payload
) {
return { type, payload };
+ } else if (
+ type === "newswires-snippet" &&
+ "embeddableHtml" in payload &&
+ "embeddableUrl" in payload
+ ) {
+ return { type, payload };
}
};
diff --git a/yarn.lock b/yarn.lock
index 55422e33..760bd5d7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7313,6 +7313,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/sanitize-html@npm:^2":
+ version: 2.13.0
+ resolution: "@types/sanitize-html@npm:2.13.0"
+ dependencies:
+ htmlparser2: "npm:^8.0.0"
+ checksum: 10c0/c6614b38f67dd6fb3a94c9163a55fa43b9aa81a845fe9584d9ffbd5da0e00e0ac8162ede9f4bde095840b2ef9db12265f5fcc40b707f48a420405c6aa7c3ff51
+ languageName: node
+ linkType: hard
+
"@types/send@npm:*":
version: 0.17.1
resolution: "@types/send@npm:0.17.1"
@@ -9469,6 +9478,7 @@ __metadata:
"@svgr/webpack": "npm:^6.2.1"
"@types/react": "npm:16.9.56"
"@types/react-dom": "npm:16.9.9"
+ "@types/sanitize-html": "npm:^2"
"@types/webpack-env": "npm:^1.16.3"
"@types/webscopeio__react-textarea-autocomplete": "npm:^4.7.2"
"@webscopeio/react-textarea-autocomplete": "npm:4.9.2"
@@ -9484,6 +9494,7 @@ __metadata:
react-draggable: "npm:^4.4.5"
react-joyride: "npm:^2.5.3"
react-shadow: "npm:^19.0.2"
+ sanitize-html: "npm:^2.13.1"
webpack: "npm:^5.76.0"
webpack-bundle-analyzer: "npm:^4.5.0"
webpack-cli: "npm:^4.9.2"
@@ -10473,10 +10484,21 @@ __metadata:
languageName: node
linkType: hard
-"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0":
- version: 2.2.0
- resolution: "domelementtype@npm:2.2.0"
- checksum: 10c0/0e3824e21fb9ff2cda9579ad04ef0068c58cc1746cf723560e1b4cb73ccae324062d468b25a576948459df7dd99e42d8a100b7fcfc6e05c8eefa2e6fed3f8f7d
+"dom-serializer@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "dom-serializer@npm:2.0.0"
+ dependencies:
+ domelementtype: "npm:^2.3.0"
+ domhandler: "npm:^5.0.2"
+ entities: "npm:^4.2.0"
+ checksum: 10c0/d5ae2b7110ca3746b3643d3ef60ef823f5f078667baf530cec096433f1627ec4b6fa8c072f09d079d7cda915fd2c7bc1b7b935681e9b09e591e1e15f4040b8e2
+ languageName: node
+ linkType: hard
+
+"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "domelementtype@npm:2.3.0"
+ checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9
languageName: node
linkType: hard
@@ -10498,6 +10520,15 @@ __metadata:
languageName: node
linkType: hard
+"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3":
+ version: 5.0.3
+ resolution: "domhandler@npm:5.0.3"
+ dependencies:
+ domelementtype: "npm:^2.3.0"
+ checksum: 10c0/bba1e5932b3e196ad6862286d76adc89a0dbf0c773e5ced1eb01f9af930c50093a084eff14b8de5ea60b895c56a04d5de8bbc4930c5543d029091916770b2d2a
+ languageName: node
+ linkType: hard
+
"domutils@npm:^2.5.2, domutils@npm:^2.8.0":
version: 2.8.0
resolution: "domutils@npm:2.8.0"
@@ -10509,6 +10540,17 @@ __metadata:
languageName: node
linkType: hard
+"domutils@npm:^3.0.1":
+ version: 3.1.0
+ resolution: "domutils@npm:3.1.0"
+ dependencies:
+ dom-serializer: "npm:^2.0.0"
+ domelementtype: "npm:^2.3.0"
+ domhandler: "npm:^5.0.3"
+ checksum: 10c0/342d64cf4d07b8a0573fb51e0a6312a88fb520c7fefd751870bf72fa5fc0f2e0cb9a3958a573610b1d608c6e2a69b8e9b4b40f0bfb8f87a71bce4f180cca1887
+ languageName: node
+ linkType: hard
+
"dot-case@npm:^3.0.4":
version: 3.0.4
resolution: "dot-case@npm:3.0.4"
@@ -10745,6 +10787,13 @@ __metadata:
languageName: node
linkType: hard
+"entities@npm:^4.2.0, entities@npm:^4.4.0":
+ version: 4.5.0
+ resolution: "entities@npm:4.5.0"
+ checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250
+ languageName: node
+ linkType: hard
+
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -12694,6 +12743,18 @@ __metadata:
languageName: node
linkType: hard
+"htmlparser2@npm:^8.0.0":
+ version: 8.0.2
+ resolution: "htmlparser2@npm:8.0.2"
+ dependencies:
+ domelementtype: "npm:^2.3.0"
+ domhandler: "npm:^5.0.3"
+ domutils: "npm:^3.0.1"
+ entities: "npm:^4.4.0"
+ checksum: 10c0/609cca85886d0bf2c9a5db8c6926a89f3764596877492e2caa7a25a789af4065bc6ee2cdc81807fe6b1d03a87bf8a373b5a754528a4cc05146b713c20575aab4
+ languageName: node
+ linkType: hard
+
"http-cache-semantics@npm:^4.1.1":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
@@ -13419,6 +13480,13 @@ __metadata:
languageName: node
linkType: hard
+"is-plain-object@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "is-plain-object@npm:5.0.0"
+ checksum: 10c0/893e42bad832aae3511c71fd61c0bf61aa3a6d853061c62a307261842727d0d25f761ce9379f7ba7226d6179db2a3157efa918e7fe26360f3bf0842d9f28942c
+ languageName: node
+ linkType: hard
+
"is-potential-custom-element-name@npm:^1.0.1":
version: 1.0.1
resolution: "is-potential-custom-element-name@npm:1.0.1"
@@ -15698,6 +15766,15 @@ __metadata:
languageName: node
linkType: hard
+"nanoid@npm:^3.3.7":
+ version: 3.3.8
+ resolution: "nanoid@npm:3.3.8"
+ bin:
+ nanoid: bin/nanoid.cjs
+ checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120
+ languageName: node
+ linkType: hard
+
"nanomatch@npm:^1.2.9":
version: 1.2.13
resolution: "nanomatch@npm:1.2.13"
@@ -16304,6 +16381,13 @@ __metadata:
languageName: node
linkType: hard
+"parse-srcset@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "parse-srcset@npm:1.0.2"
+ checksum: 10c0/2f268e3d110d4c53d06ed2a8e8ee61a7da0cee13bf150819a6da066a8ca9b8d15b5600d6e6cae8be940e2edc50ee7c1e1052934d6ec858324065ecef848f0497
+ languageName: node
+ linkType: hard
+
"parse-url@npm:^9.2.0":
version: 9.2.0
resolution: "parse-url@npm:9.2.0"
@@ -16476,10 +16560,10 @@ __metadata:
languageName: node
linkType: hard
-"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1":
- version: 1.0.1
- resolution: "picocolors@npm:1.0.1"
- checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400
+"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.1":
+ version: 1.1.1
+ resolution: "picocolors@npm:1.1.1"
+ checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58
languageName: node
linkType: hard
@@ -16570,6 +16654,17 @@ __metadata:
languageName: node
linkType: hard
+"postcss@npm:^8.3.11":
+ version: 8.4.49
+ resolution: "postcss@npm:8.4.49"
+ dependencies:
+ nanoid: "npm:^3.3.7"
+ picocolors: "npm:^1.1.1"
+ source-map-js: "npm:^1.2.1"
+ checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3
+ languageName: node
+ linkType: hard
+
"postgres@npm:^3.2.4":
version: 3.2.4
resolution: "postgres@npm:3.2.4"
@@ -17557,6 +17652,20 @@ __metadata:
languageName: node
linkType: hard
+"sanitize-html@npm:^2.13.1":
+ version: 2.13.1
+ resolution: "sanitize-html@npm:2.13.1"
+ dependencies:
+ deepmerge: "npm:^4.2.2"
+ escape-string-regexp: "npm:^4.0.0"
+ htmlparser2: "npm:^8.0.0"
+ is-plain-object: "npm:^5.0.0"
+ parse-srcset: "npm:^1.0.2"
+ postcss: "npm:^8.3.11"
+ checksum: 10c0/306c811a254e48eb45e9c523fb91cced893d77786323a64fb47f4bb3f1237b4d29e3722c0723c329e6cb6ac674ae903e961d446c3636b9db5961c83b2c0995fe
+ languageName: node
+ linkType: hard
+
"sax@npm:1.2.1":
version: 1.2.1
resolution: "sax@npm:1.2.1"
@@ -18089,6 +18198,13 @@ __metadata:
languageName: node
linkType: hard
+"source-map-js@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "source-map-js@npm:1.2.1"
+ checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf
+ languageName: node
+ linkType: hard
+
"source-map-resolve@npm:^0.5.0":
version: 0.5.3
resolution: "source-map-resolve@npm:0.5.3"
From 4515c925f133b29b1b99cf858ecba5910c7ae9ac Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Wed, 23 Oct 2024 09:57:04 +0100
Subject: [PATCH 04/15] display selection buttons at each end of the selected
text
---
client/src/newswires/newswiresIntegration.tsx | 130 +++++++++++++-----
1 file changed, 93 insertions(+), 37 deletions(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index 974be931..7ed3476a 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -10,31 +10,56 @@ import ReactDOM from "react-dom";
const SELECTION_TARGET_DATA_ATTR = "[data-pinboard-selection-target]";
+interface ButtonPosition {
+ top: number;
+ left: number;
+}
+
export const NewswiresIntegration = () => {
const { setPayloadToBeSent, setIsExpanded } = useGlobalStateContext();
- const [selectedHTML, setSelectedHTML] = useState(null);
- const [mountPoint, setMountPoint] = useState(null);
- const [buttonCoords, setButtonCoords] = useState({ x: 0, y: 0 });
+
+ const [state, setState] = useState<{
+ selectedHTML: string;
+ mountPoint: HTMLElement;
+ firstButtonPosition: ButtonPosition;
+ lastButtonPosition: ButtonPosition;
+ } | null>(null);
const handleSelectionChange = () => {
const selection = window.getSelection();
- if (selection && selection.rangeCount > 0) {
- const maybeOriginalTargetEl = document.querySelector(
- SELECTION_TARGET_DATA_ATTR
- );
-
- setMountPoint(maybeOriginalTargetEl);
+ const maybeOriginalTargetEl: HTMLElement | null = document.querySelector(
+ SELECTION_TARGET_DATA_ATTR
+ );
+ if (selection && selection.rangeCount > 0 && maybeOriginalTargetEl) {
const clonedContents = selection.getRangeAt(0).cloneContents();
const maybeClonedTargetEl = clonedContents.querySelector(
SELECTION_TARGET_DATA_ATTR
);
+ const parentRect = maybeOriginalTargetEl.getBoundingClientRect();
+ const selectionRects = Array.from(
+ selection.getRangeAt(0).getClientRects()
+ );
+ const firstRect = selectionRects[0];
+ const lastRect = selectionRects[selectionRects.length - 1];
+ const newFirstButtonCoords = {
+ top: firstRect.y - parentRect.y,
+ left: firstRect.x - parentRect.x,
+ };
+ const newLastButtonCoords = {
+ top: lastRect.y - parentRect.y + lastRect.height,
+ left: lastRect.x - parentRect.x + lastRect.width,
+ };
if (maybeClonedTargetEl) {
console.log(
"selection contains whole target element; contents:",
maybeClonedTargetEl.innerHTML
);
- setSelectedHTML(maybeClonedTargetEl.innerHTML);
- setSelectionFocusNode(selectionFocusNode); // todo: set coords instead, based on selection
+ setState({
+ selectedHTML: maybeClonedTargetEl.innerHTML,
+ mountPoint: maybeOriginalTargetEl,
+ firstButtonPosition: newFirstButtonCoords,
+ lastButtonPosition: newLastButtonCoords,
+ });
} else if (
maybeOriginalTargetEl?.contains(selection.anchorNode) &&
maybeOriginalTargetEl?.contains(selection.focusNode)
@@ -45,14 +70,22 @@ export const NewswiresIntegration = () => {
"selection is within target element; contents:",
tempEl.innerHTML
);
- setSelectedHTML(tempEl.innerHTML);
- setSelectionFocusNode(selection.focusNode);
+ setState({
+ selectedHTML: tempEl.innerHTML,
+ mountPoint: maybeOriginalTargetEl,
+ firstButtonPosition: newFirstButtonCoords,
+ lastButtonPosition: newLastButtonCoords,
+ });
+ //TODO might need to clean up tempEl
}
}
};
const debouncedSelectionHandler = useMemo(
- () => debounce(handleSelectionChange, 750),
+ () => () => {
+ setState(null); // clear selection to hide buttons
+ debounce(handleSelectionChange, 750)();
+ },
[handleSelectionChange]
);
@@ -77,51 +110,74 @@ export const NewswiresIntegration = () => {
}, []);
const addSelectionToPinboard = useCallback(() => {
- if (selectedHTML) {
+ if (state) {
setPayloadToBeSent({
type: "newswires-snippet",
payload: {
- embeddableHtml: selectedHTML,
+ embeddableHtml: state.selectedHTML,
embeddableUrl: window.location.href,
},
});
setIsExpanded(true);
}
- }, [selectedHTML, setPayloadToBeSent]);
+ }, [state, setPayloadToBeSent]);
return (
<>
- {selectedHTML &&
- mountPoint &&
+ {state &&
ReactDOM.createPortal(
-
- Add selection to pinboard
-
+ {[state.firstButtonPosition, state.lastButtonPosition].map(
+ (buttonCoords, index) => (
+
+ Add selection to pinboard
+
+ )
+ )}
,
- mountPoint
+ state.mountPoint
)}
>
);
From ad075e862be39b1f57c6270971f229a910b6408a Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Wed, 23 Oct 2024 20:48:51 +0100
Subject: [PATCH 05/15] [newswires] ignore empty selections
---
client/src/newswires/newswiresIntegration.tsx | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index 7ed3476a..cb6ad632 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -30,7 +30,12 @@ export const NewswiresIntegration = () => {
const maybeOriginalTargetEl: HTMLElement | null = document.querySelector(
SELECTION_TARGET_DATA_ATTR
);
- if (selection && selection.rangeCount > 0 && maybeOriginalTargetEl) {
+ if (
+ selection &&
+ selection.rangeCount > 0 &&
+ selection.toString().length > 0 &&
+ maybeOriginalTargetEl
+ ) {
const clonedContents = selection.getRangeAt(0).cloneContents();
const maybeClonedTargetEl = clonedContents.querySelector(
SELECTION_TARGET_DATA_ATTR
From 376fb8e1bd43eaa98c9e2fe1612d446687c917ff Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Wed, 23 Oct 2024 20:49:35 +0100
Subject: [PATCH 06/15] [newswires] improve button formatting
---
client/src/newswires/newswiresIntegration.tsx | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index cb6ad632..355e2c45 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -7,6 +7,7 @@ import { pinboard, pinMetal } from "../../colours";
import { textSans } from "../../fontNormaliser";
import { space } from "@guardian/source-foundations";
import ReactDOM from "react-dom";
+import PinIcon from "../../icons/pin-icon.svg";
const SELECTION_TARGET_DATA_ATTR = "[data-pinboard-selection-target]";
@@ -161,7 +162,7 @@ export const NewswiresIntegration = () => {
display: flex;
align-items: center;
background-color: ${pinboard[500]};
- ${textSans.xsmall()};
+ ${textSans.xsmall({ fontWeight: "bold" })};
border: none;
border-radius: 100px;
border-${
@@ -177,7 +178,17 @@ export const NewswiresIntegration = () => {
`}
onClick={addSelectionToPinboard}
>
- Add selection to pinboard
+ Add selection to{" "}
+
)
)}
From 2ea5fb9fcdb7eefc835c8b2ea26bc40bb0c885ad Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Wed, 23 Oct 2024 20:53:02 +0100
Subject: [PATCH 07/15] [newswires] use shadow-DOM to isolate styles
---
client/src/newswires/newswiresIntegration.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index 355e2c45..303ca82a 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -8,6 +8,7 @@ import { textSans } from "../../fontNormaliser";
import { space } from "@guardian/source-foundations";
import ReactDOM from "react-dom";
import PinIcon from "../../icons/pin-icon.svg";
+import root from "react-shadow/emotion";
const SELECTION_TARGET_DATA_ATTR = "[data-pinboard-selection-target]";
@@ -143,7 +144,7 @@ export const NewswiresIntegration = () => {
/>
{state &&
ReactDOM.createPortal(
-
+
{[state.firstButtonPosition, state.lastButtonPosition].map(
(buttonCoords, index) => (
{
)
)}
-
,
+ ,
state.mountPoint
)}
>
From 17c196f0f605835378b29cae0121d16ef648bd6f Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Wed, 23 Oct 2024 20:55:06 +0100
Subject: [PATCH 08/15] [newswires] speed-up debounce on selection change
---
client/src/newswires/newswiresIntegration.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index 303ca82a..a2820150 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -91,7 +91,7 @@ export const NewswiresIntegration = () => {
const debouncedSelectionHandler = useMemo(
() => () => {
setState(null); // clear selection to hide buttons
- debounce(handleSelectionChange, 750)();
+ debounce(handleSelectionChange, 500)();
},
[handleSelectionChange]
);
From 3da8728ba1a38eefe470981641b1ea9aa633ba7f Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Thu, 24 Oct 2024 08:53:34 +0100
Subject: [PATCH 09/15] [newswires] fix selection highlighting and improve
distinction between add buttons and the selection itself
---
client/src/newswires/newswiresIntegration.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index a2820150..53be7641 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -9,6 +9,7 @@ import { space } from "@guardian/source-foundations";
import ReactDOM from "react-dom";
import PinIcon from "../../icons/pin-icon.svg";
import root from "react-shadow/emotion";
+import { boxShadow } from "../styling";
const SELECTION_TARGET_DATA_ATTR = "[data-pinboard-selection-target]";
@@ -54,7 +55,7 @@ export const NewswiresIntegration = () => {
};
const newLastButtonCoords = {
top: lastRect.y - parentRect.y + lastRect.height,
- left: lastRect.x - parentRect.x + lastRect.width,
+ left: lastRect.x - parentRect.x + lastRect.width - 1,
};
if (maybeClonedTargetEl) {
console.log(
@@ -136,7 +137,7 @@ export const NewswiresIntegration = () => {
${SELECTION_TARGET_DATA_ATTR} {
position: relative;
}
- ${SELECTION_TARGET_DATA_ATTR}::selection {
+ ${SELECTION_TARGET_DATA_ATTR}::selection, ${SELECTION_TARGET_DATA_ATTR} ::selection {
background-color: ${pinboard[500]};
color: ${pinMetal};
}
@@ -164,6 +165,7 @@ export const NewswiresIntegration = () => {
align-items: center;
background-color: ${pinboard[500]};
${textSans.xsmall({ fontWeight: "bold" })};
+ box-shadow: ${boxShadow};
border: none;
border-radius: 100px;
border-${
@@ -175,7 +177,7 @@ export const NewswiresIntegration = () => {
line-height: 2;
cursor: pointer;
color: ${pinMetal};
- text-wrap: nowrap;
+ text-wrap: nowrap;
`}
onClick={addSelectionToPinboard}
>
From 227241d7ec356df397b5ff8140dacbd7ee23a9b9 Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Thu, 24 Oct 2024 09:11:33 +0100
Subject: [PATCH 10/15] [newswires] set drag'n'drop MIME type to `text/html`
not `text/plain`
---
client/src/payloadDisplay.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/payloadDisplay.tsx b/client/src/payloadDisplay.tsx
index 0a2c0b3f..82d05f0b 100644
--- a/client/src/payloadDisplay.tsx
+++ b/client/src/payloadDisplay.tsx
@@ -70,7 +70,7 @@ export const PayloadDisplay = ({
// event.dataTransfer.setData("text/plain", "This is text to drag");
event.dataTransfer.setData(
- "text/plain",
+ "text/html",
sanitizeHtml(payloadAndType.payload.embeddableHtml)
);
} else {
From 1f58cef15d6753af3b1f22548a649ff0475af3bc Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Thu, 24 Oct 2024 09:54:51 +0100
Subject: [PATCH 11/15] [newswires] add `gu-note` when dropping snippet which
includes any usage note (or fallback message)
---
client/src/newswires/newswiresIntegration.tsx | 9 +++++----
client/src/payloadDisplay.tsx | 8 +++++++-
client/src/types/PayloadAndType.ts | 1 +
3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/client/src/newswires/newswiresIntegration.tsx b/client/src/newswires/newswiresIntegration.tsx
index 53be7641..e791ef99 100644
--- a/client/src/newswires/newswiresIntegration.tsx
+++ b/client/src/newswires/newswiresIntegration.tsx
@@ -23,7 +23,7 @@ export const NewswiresIntegration = () => {
const [state, setState] = useState<{
selectedHTML: string;
- mountPoint: HTMLElement;
+ containerElement: HTMLElement;
firstButtonPosition: ButtonPosition;
lastButtonPosition: ButtonPosition;
} | null>(null);
@@ -64,7 +64,7 @@ export const NewswiresIntegration = () => {
);
setState({
selectedHTML: maybeClonedTargetEl.innerHTML,
- mountPoint: maybeOriginalTargetEl,
+ containerElement: maybeOriginalTargetEl,
firstButtonPosition: newFirstButtonCoords,
lastButtonPosition: newLastButtonCoords,
});
@@ -80,7 +80,7 @@ export const NewswiresIntegration = () => {
);
setState({
selectedHTML: tempEl.innerHTML,
- mountPoint: maybeOriginalTargetEl,
+ containerElement: maybeOriginalTargetEl,
firstButtonPosition: newFirstButtonCoords,
lastButtonPosition: newLastButtonCoords,
});
@@ -124,6 +124,7 @@ export const NewswiresIntegration = () => {
payload: {
embeddableHtml: state.selectedHTML,
embeddableUrl: window.location.href,
+ maybeUsageNote: state.containerElement.dataset.usageNote,
},
});
setIsExpanded(true);
@@ -196,7 +197,7 @@ export const NewswiresIntegration = () => {
)
)}
,
- state.mountPoint
+ state.containerElement
)}
>
);
diff --git a/client/src/payloadDisplay.tsx b/client/src/payloadDisplay.tsx
index 82d05f0b..07632732 100644
--- a/client/src/payloadDisplay.tsx
+++ b/client/src/payloadDisplay.tsx
@@ -71,7 +71,13 @@ export const PayloadDisplay = ({
event.dataTransfer.setData(
"text/html",
- sanitizeHtml(payloadAndType.payload.embeddableHtml)
+ // TODO consider also add a gu-note for who shared it and when
+ `${sanitizeHtml(
+ payloadAndType.payload.embeddableHtml
+ )}${
+ payloadAndType.payload.maybeUsageNote ||
+ "NO USAGE NOTE IN THE WIRE"
+ } `
);
} else {
event.dataTransfer.setData("URL", payload.embeddableUrl);
diff --git a/client/src/types/PayloadAndType.ts b/client/src/types/PayloadAndType.ts
index a898f09c..95aaef89 100644
--- a/client/src/types/PayloadAndType.ts
+++ b/client/src/types/PayloadAndType.ts
@@ -33,6 +33,7 @@ export interface PayloadWithApiUrl extends PayloadCommon {
export interface PayloadWithSnippet extends PayloadCommon {
embeddableHtml: string;
+ maybeUsageNote: string | undefined;
}
type Payload =
From 86f779ebc6af682dc6adaaf1adc284fd91942a48 Mon Sep 17 00:00:00 2001
From: Tom Richards
Date: Mon, 28 Oct 2024 10:17:45 +0000
Subject: [PATCH 12/15] [newswires] ensure 'add selection' buttons don't ever
burst out of container, by detecting left vs right and shifting the position
accordingly
---
bootstrapping-lambda/local/index.html | 54 ++++++++++---------
client/src/newswires/newswiresIntegration.tsx | 50 ++++++++++-------
2 files changed, 60 insertions(+), 44 deletions(-)
diff --git a/bootstrapping-lambda/local/index.html b/bootstrapping-lambda/local/index.html
index 4d5bda1b..c85862bd 100644
--- a/bootstrapping-lambda/local/index.html
+++ b/bootstrapping-lambda/local/index.html
@@ -21,32 +21,6 @@
-
- not inside target
- Not inside target
-
-
Pinboard selection target
-
- This is a target for the Pinboard library to render the selection
- interface into. It will be hidden by the library when not in use. This
- is a target for the Pinboard library to render the selection interface
- into. It will be hidden by the library when not in use. This is a target
- for the Pinboard library to render the selection interface into. It will
- be hidden by the library when not in use. This is a target for the
- Pinboard library to render the selection interface into. It will be
- hidden by the library when not in use. This is a target for the Pinboard
- library to render the selection interface into. It will be hidden by the
- library when not in use. This is a target for the Pinboard library to
- render the selection interface into. It will be hidden by the library
- when not in use.
-
-
- not inside target
-
-
Expand pinboard via query param ?expandPinboard=true
@@ -175,6 +149,34 @@ presented after 1 second
and Pinboard detecting that and still taking over the element
+
+
Pinboard selection target
+
+
+ This is a target for the Pinboard library to render
+ the selection interface into. It will be hidden by the library when
+ not in use. This is a target for the Pinboard library to
+ render the selection interface into. It will be hidden by the library
+ when not in use. This is a target for the
+ Pinboard library to render the selection interface
+ into. It will be hidden by the library when not in use.
+
+
+ This is a target for the Pinboard library to render the
+ selection interface into. It will be hidden by the library when not in
+ use. This is a target for the Pinboard library to
+ render the selection interface into. It will be hidden by the library
+ when not in use. This is a target for the Pinboard library to
+ render the selection interface into. It will be hidden by the library
+ when not in use.
+
+
+
+
+