Skip to content

Commit

Permalink
[MOC-63] Re edit feature (#77)
Browse files Browse the repository at this point in the history
* WIP re-editing DOM changes

* DOMManipulator now removes changed highlighted modifications

* Feature re-editing modifications

* run lint

* typo fix

* changed to uuid generated id
  • Loading branch information
NicoMorenoSirius committed Jun 26, 2024
1 parent d9351e2 commit 541b21b
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 22 deletions.
21 changes: 14 additions & 7 deletions apps/mocksi-lite/commands/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface Command {
}

interface DOMModification {
previousKey: string;
keyToSave: string;
oldValue: string;
newValue: string;
Expand Down Expand Up @@ -42,19 +43,25 @@ export const buildQuerySelector = (
export class SaveModificationCommand implements Command {
constructor(
private prevModifications: {
[querySelector: string]: {
newValue: string;
oldValue: string;
type: "text" | "image";
};
[querySelector: string]:
| {
newValue: string;
oldValue: string;
type: "text" | "image";
}
| undefined;
},
private modification: DOMModification,
) {}

execute() {
const { keyToSave, newValue, oldValue, type } = this.modification;
const { keyToSave, previousKey, newValue, oldValue, type } =
this.modification;
const { oldValue: oldValueFromStorage } =
this.prevModifications[keyToSave] || {};
this.prevModifications[previousKey] || {};
if (this.prevModifications[previousKey]) {
this.prevModifications[previousKey] = undefined;
}
this.prevModifications[keyToSave] = {
newValue,
oldValue: oldValueFromStorage || oldValue,
Expand Down
29 changes: 26 additions & 3 deletions apps/mocksi-lite/content/EditMode/decorator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import { applyChanges, cancelEditWithoutChanges } from "./actions";

export function decorate(text: string, width: string, shiftMode: boolean) {
// function to decorate the portion of TextNode with the textArea to edit the content
// functions parameter is to add some extra functionality at the moment of submitting or cancel.
export function decorate(
text: string,
width: string,
shiftMode: boolean,
functions: {
onSubmit: (() => void) | undefined;
onCancel: (() => void) | undefined;
} = { onSubmit: undefined, onCancel: undefined },
) {
const newSpan = document.createElement("span");
newSpan.style.position = "relative";
newSpan.id = "mocksiSelectedText";
newSpan.appendChild(document.createTextNode(text));
const textArea = injectTextArea(shiftMode ? width : undefined, text);
const textArea = injectTextArea(
shiftMode ? width : undefined,
text,
functions.onSubmit,
functions.onCancel,
);
newSpan.appendChild(textArea);
return newSpan;
}

function injectTextArea(width: string | undefined, value: string) {
function injectTextArea(
width: string | undefined,
value: string,
onSubmit?: () => void,
onCancel?: () => void,
) {
const ndiv = document.createElement("textarea");
ndiv.setAttribute("tabindex", "-1");
const elementStyle = {
Expand Down Expand Up @@ -39,15 +59,18 @@ function injectTextArea(width: string | undefined, value: string) {
}
event.preventDefault(); // Prevents the addition of a new line in the text field
} else if (event.key === "Escape") {
onCancel?.();
cancelEditWithoutChanges(document.getElementById("mocksiSelectedText"));
}
};
ndiv.onsubmit = (event: SubmitEvent) => {
const selectedText = document.getElementById("mocksiSelectedText");
// @ts-ignore I don't know why the value property is no inside the target object
const newValue = event.target?.value;
onSubmit?.();
applyChanges(selectedText, newValue, value);
};

//@ts-ignore
ndiv.value = value;
ndiv.id = "mocksiTextArea";
Expand Down
68 changes: 64 additions & 4 deletions apps/mocksi-lite/content/EditMode/highlighter.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,52 @@
import { v4 as uuidv4 } from "uuid";
import { MOCKSI_HIGHLIGHTER_ID } from "../../consts";
import { decorate } from "./decorator";

class Highlighter {
private contentRanger = document.createRange();
private highlightedNodes = [];
private highlightedNodes: { highlightedElem: Node; highlightId: string }[] =
[];

highlightNode = (elementToHighlight: Node) => {
this.contentRanger.selectNodeContents(elementToHighlight);
const { x, y, width, height } =
this.contentRanger.getBoundingClientRect() || {};
const textHighlight = highlight({ x, y, width, height });
const textHighlight = highlight({
x,
y,
width,
height,
highlightedElement: elementToHighlight,
});
textHighlight.id = uuidv4();
document.body.appendChild(textHighlight);
//@ts-ignore just don't know what is meaning here
this.highlightedNodes.push(elementToHighlight);
this.highlightedNodes.push({
highlightedElem: elementToHighlight,
highlightId: textHighlight.id,
});
};

removeHighlightNode = (elementToUnhighlight: Node) => {
const { highlightId } =
this.highlightedNodes.find(
({ highlightedElem }) => highlightedElem === elementToUnhighlight,
) || {};
if (highlightId) {
const highlightDOMElem = document.getElementById(highlightId);
highlightDOMElem?.remove();
}
};

showHideHighlight = (show: boolean, elementInvolved: Node) => {
const { highlightId } =
this.highlightedNodes.find(
({ highlightedElem }) => highlightedElem === elementInvolved,
) || {};
if (highlightId) {
const highlightDOMElem = document.getElementById(highlightId);
(highlightDOMElem as HTMLElement).style.display = show ? "block" : "none";
}
};

showHideHighlights = (show: boolean) => {
Expand Down Expand Up @@ -45,7 +80,14 @@ const highlight = ({
y,
width,
height,
}: { x: number; y: number; width: number; height: number }) => {
highlightedElement,
}: {
x: number;
y: number;
width: number;
height: number;
highlightedElement: Node;
}) => {
const highlightDiv = document.createElement("div");
highlightDiv.className = MOCKSI_HIGHLIGHTER_ID;
highlightDiv.style.position = "absolute";
Expand All @@ -56,5 +98,23 @@ const highlight = ({
highlightDiv.style.height = `${height + 4}px`;
highlightDiv.style.border = "2px solid purple";
highlightDiv.style.backgroundColor = "transparent";
highlightDiv.style.cursor = "text";
highlightDiv.ondblclick = (event: MouseEvent) => {
if (!highlightedElement?.parentElement) {
return;
}
(event.target as HTMLElement).style.display = "none";
highlightedElement.parentElement?.replaceChild(
decorate(highlightedElement.textContent || "", `${width || ""}`, false, {
onSubmit: undefined,
onCancel: () => {
(event.target as HTMLElement).style.display = "block";
},
}),
highlightedElement,
);
document.getElementById("mocksiTextArea")?.focus();
event.stopPropagation();
};
return highlightDiv;
};
20 changes: 12 additions & 8 deletions apps/mocksi-lite/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const saveModification = (
const saveModificationCommand = new SaveModificationCommand(
domainModifications,
{
previousKey: buildQuerySelector(parentElement, oldValue),
keyToSave: buildQuerySelector(parentElement, newValue),
newValue: sanitizeHtml(newValue),
oldValue,
Expand All @@ -92,7 +93,7 @@ export const saveModification = (
};

export const persistModifications = async (recordingId: string) => {
const alterations: Alteration[] = buildAlterations(domainModifications);
const alterations: Alteration[] = buildAlterations();
chrome.storage.local.set({
[MOCKSI_MODIFICATIONS]: JSON.stringify(domainModifications),
});
Expand Down Expand Up @@ -151,9 +152,10 @@ export const loadAlterations = (

// This is from chrome.storage.local
export const loadPreviousModifications = () => {
for (const [querySelector, { oldValue, newValue, type }] of Object.entries(
domainModifications,
)) {
for (const [
querySelector,
{ oldValue, newValue, type },
] of modificationsIterable()) {
const sanitizedOldValue = sanitizeHtml(oldValue);
const elemToModify = getHTMLElementFromSelector(querySelector);
// here newValue and oldValue is in altered order because we want to revert the changes
Expand Down Expand Up @@ -223,10 +225,8 @@ export const sendMessage = (
}
};

const buildAlterations = (
modifications: DOMModificationsType,
): Alteration[] => {
return Object.entries(modifications).map(
const buildAlterations = (): Alteration[] => {
return modificationsIterable().map(
([querySelector, { newValue, oldValue, type }]) => ({
selector: querySelector,
action: oldValue ? "modified" : "added",
Expand All @@ -237,6 +237,10 @@ const buildAlterations = (
);
};

function modificationsIterable() {
return Object.entries(domainModifications).filter(([, values]) => values);
}

// biome-ignore lint/suspicious/noExplicitAny: dynamic arguments
export function debounce_leading<T extends (...args: any[]) => void>(
func: T,
Expand Down
2 changes: 2 additions & 0 deletions packages/dodom/receivers/DOMManipulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type FragmentTextNode = (

interface ContentHighlighterInterface {
highlightNode(node: Node): void;
removeHighlightNode(node: Node): void;
}

type SaveModification = (
Expand Down Expand Up @@ -83,6 +84,7 @@ export class DOMManipulator {
if (nodeToReplace.parentElement == null) {
continue;
}
this.contentHighlighter.removeHighlightNode(nodeToReplace);
nodeToReplace.parentElement.replaceChild(replacement, nodeToReplace);
}

Expand Down

0 comments on commit 541b21b

Please sign in to comment.