@sirendesign/markup 1.0.32 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +356 -183
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +356 -183
- package/dist/index.js.map +1 -1
- package/dist/markup-widget.esm.js +222 -222
- package/dist/markup-widget.esm.js.map +1 -1
- package/dist/markup-widget.js +224 -224
- package/dist/markup-widget.js.map +1 -1
- package/dist/styles.css +2 -2
- package/dist/types/index.d.ts +8 -0
- package/dist/utils/componentLocator.d.ts +5 -0
- package/package.json +4 -7
package/dist/index.esm.js
CHANGED
|
@@ -8601,62 +8601,122 @@ const getElementInfo = (element) => {
|
|
|
8601
8601
|
info += '>';
|
|
8602
8602
|
return info;
|
|
8603
8603
|
};
|
|
8604
|
+
/**
|
|
8605
|
+
* Generates a stable CSS selector for a DOM element, preferring IDs and
|
|
8606
|
+
* falling back to a tag + nth-of-type path anchored to the nearest ID ancestor.
|
|
8607
|
+
*/
|
|
8608
|
+
const getElementSelector$1 = (element) => {
|
|
8609
|
+
// If the element itself has an id, that's unique enough.
|
|
8610
|
+
if (element.id) {
|
|
8611
|
+
return `#${element.id}`;
|
|
8612
|
+
}
|
|
8613
|
+
const parts = [];
|
|
8614
|
+
let el = element;
|
|
8615
|
+
while (el && el !== document.documentElement && el !== document.body) {
|
|
8616
|
+
let part = el.tagName.toLowerCase();
|
|
8617
|
+
if (el.id) {
|
|
8618
|
+
// Anchor the path here – an id is unique.
|
|
8619
|
+
parts.unshift(`#${el.id}`);
|
|
8620
|
+
break;
|
|
8621
|
+
}
|
|
8622
|
+
// Add nth-of-type disambiguator if there are siblings of the same tag.
|
|
8623
|
+
const parent = el.parentElement;
|
|
8624
|
+
if (parent) {
|
|
8625
|
+
const sameTagSiblings = Array.from(parent.children).filter((c) => c.tagName === el.tagName);
|
|
8626
|
+
if (sameTagSiblings.length > 1) {
|
|
8627
|
+
const index = sameTagSiblings.indexOf(el) + 1;
|
|
8628
|
+
part += `:nth-of-type(${index})`;
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
parts.unshift(part);
|
|
8632
|
+
el = el.parentElement;
|
|
8633
|
+
}
|
|
8634
|
+
return parts.join(' > ') || element.tagName.toLowerCase();
|
|
8635
|
+
};
|
|
8604
8636
|
|
|
8605
8637
|
async function captureScreenshot(clickElement) {
|
|
8606
8638
|
try {
|
|
8607
|
-
// Hide the markup widget before capturing
|
|
8639
|
+
// Hide the markup widget, pins overlay and viewport controls before capturing
|
|
8608
8640
|
const widget = document.querySelector('[data-markup-widget]');
|
|
8609
|
-
|
|
8641
|
+
const pinsOverlay = document.querySelector('[data-markup-pins]');
|
|
8642
|
+
const viewportBar = document.querySelector('[data-markup-viewport]');
|
|
8643
|
+
if (widget)
|
|
8610
8644
|
widget.style.visibility = 'hidden';
|
|
8611
|
-
|
|
8645
|
+
if (pinsOverlay)
|
|
8646
|
+
pinsOverlay.style.visibility = 'hidden';
|
|
8647
|
+
if (viewportBar)
|
|
8648
|
+
viewportBar.style.visibility = 'hidden';
|
|
8649
|
+
// Use the constrained clientWidth so viewport-mode captures render at the
|
|
8650
|
+
// correct mobile/tablet width rather than the real browser window width.
|
|
8651
|
+
const captureWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
8652
|
+
const captureHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
8612
8653
|
const canvas = await html2canvas(document.body, {
|
|
8613
8654
|
allowTaint: true,
|
|
8614
8655
|
useCORS: true,
|
|
8615
8656
|
scale: window.devicePixelRatio,
|
|
8616
8657
|
logging: false,
|
|
8617
|
-
backgroundColor:
|
|
8618
|
-
width:
|
|
8619
|
-
height:
|
|
8658
|
+
backgroundColor: '#ffffff',
|
|
8659
|
+
width: captureWidth,
|
|
8660
|
+
height: captureHeight,
|
|
8620
8661
|
x: window.scrollX,
|
|
8621
8662
|
y: window.scrollY,
|
|
8622
|
-
windowWidth:
|
|
8623
|
-
windowHeight:
|
|
8663
|
+
windowWidth: captureWidth,
|
|
8664
|
+
windowHeight: captureHeight,
|
|
8624
8665
|
ignoreElements: (element) => {
|
|
8625
|
-
return element.hasAttribute('data-markup-widget')
|
|
8666
|
+
return (element.hasAttribute('data-markup-widget') ||
|
|
8667
|
+
element.hasAttribute('data-markup-pins') ||
|
|
8668
|
+
element.hasAttribute('data-markup-viewport'));
|
|
8626
8669
|
},
|
|
8627
8670
|
onclone: (clonedDoc) => {
|
|
8628
|
-
//
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8671
|
+
// Reset any viewport-mode inline constraints that were applied to <html>
|
|
8672
|
+
// so that html2canvas lays out the clone at the correct capture width.
|
|
8673
|
+
const clonedHtml = clonedDoc.documentElement;
|
|
8674
|
+
const clonedBody = clonedDoc.body;
|
|
8675
|
+
// Clear viewport-mode styles that would distort the clone layout
|
|
8676
|
+
clonedHtml.style.removeProperty('width');
|
|
8677
|
+
clonedHtml.style.removeProperty('max-width');
|
|
8678
|
+
clonedHtml.style.removeProperty('min-width');
|
|
8679
|
+
clonedHtml.style.removeProperty('margin');
|
|
8680
|
+
clonedHtml.style.removeProperty('border-radius');
|
|
8681
|
+
clonedHtml.style.removeProperty('box-shadow');
|
|
8682
|
+
// Restore body to a neutral white background so the backdrop colour
|
|
8683
|
+
// (#18181b when in responsive mode) does not bleed into the capture.
|
|
8684
|
+
clonedBody.style.removeProperty('background');
|
|
8685
|
+
clonedBody.style.removeProperty('min-height');
|
|
8686
|
+
// Process all style elements and inline styles to remove unsupported
|
|
8687
|
+
// CSS color functions (oklch etc.) that html2canvas cannot parse.
|
|
8688
|
+
const processCSS = (css) => css.replace(/oklch\([^)]*\)|oklab\([^)]*\)|lch\([^)]*\)|lab\([^)]*\)/gi, 'transparent');
|
|
8634
8689
|
clonedDoc.querySelectorAll('style').forEach((style) => {
|
|
8635
|
-
if (style.textContent)
|
|
8690
|
+
if (style.textContent)
|
|
8636
8691
|
style.textContent = processCSS(style.textContent);
|
|
8637
|
-
}
|
|
8638
8692
|
});
|
|
8639
|
-
// Process inline styles
|
|
8640
8693
|
clonedDoc.querySelectorAll('[style]').forEach((el) => {
|
|
8641
8694
|
const htmlEl = el;
|
|
8642
|
-
if (htmlEl.style.cssText)
|
|
8695
|
+
if (htmlEl.style.cssText)
|
|
8643
8696
|
htmlEl.style.cssText = processCSS(htmlEl.style.cssText);
|
|
8644
|
-
}
|
|
8645
|
-
});
|
|
8646
|
-
// Process linked stylesheets (can't modify external, but can try)
|
|
8647
|
-
clonedDoc.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
|
|
8648
|
-
// We can't easily modify external stylesheets, but we can override with inline styles
|
|
8649
|
-
// This is handled by the style tag processing above
|
|
8650
8697
|
});
|
|
8651
8698
|
},
|
|
8652
8699
|
});
|
|
8653
|
-
// Restore
|
|
8654
|
-
if (widget)
|
|
8655
|
-
widget.style.visibility = '
|
|
8656
|
-
|
|
8700
|
+
// Restore visibility of all hidden overlay elements
|
|
8701
|
+
if (widget)
|
|
8702
|
+
widget.style.visibility = '';
|
|
8703
|
+
if (pinsOverlay)
|
|
8704
|
+
pinsOverlay.style.visibility = '';
|
|
8705
|
+
if (viewportBar)
|
|
8706
|
+
viewportBar.style.visibility = '';
|
|
8657
8707
|
return canvas.toDataURL('image/png');
|
|
8658
8708
|
}
|
|
8659
8709
|
catch (error) {
|
|
8710
|
+
// Make sure overlays are always restored even on failure
|
|
8711
|
+
const widget = document.querySelector('[data-markup-widget]');
|
|
8712
|
+
const pinsOverlay = document.querySelector('[data-markup-pins]');
|
|
8713
|
+
const viewportBar = document.querySelector('[data-markup-viewport]');
|
|
8714
|
+
if (widget)
|
|
8715
|
+
widget.style.visibility = '';
|
|
8716
|
+
if (pinsOverlay)
|
|
8717
|
+
pinsOverlay.style.visibility = '';
|
|
8718
|
+
if (viewportBar)
|
|
8719
|
+
viewportBar.style.visibility = '';
|
|
8660
8720
|
console.error('Failed to capture screenshot:', error);
|
|
8661
8721
|
throw error;
|
|
8662
8722
|
}
|
|
@@ -17203,7 +17263,7 @@ const AnnotationOverlay = ({ screenshot, onComplete, onCancel, }) => {
|
|
|
17203
17263
|
|
|
17204
17264
|
const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastClickedElement, lastClickedText, }) => {
|
|
17205
17265
|
var _a, _b;
|
|
17206
|
-
const { config, currentScreenshot, setCurrentScreenshot, annotations, setAnnotations, clearAnnotations, setIsCapturing, isCapturing, currentUser, setIsOpen, } = useMarkupStore();
|
|
17266
|
+
const { config, currentScreenshot, setCurrentScreenshot, annotations, setAnnotations, clearAnnotations, setIsCapturing, isCapturing, currentUser, setIsOpen, viewportMode, } = useMarkupStore();
|
|
17207
17267
|
const [title, setTitle] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.title) || "");
|
|
17208
17268
|
const [description, setDescription] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.description) || "");
|
|
17209
17269
|
const [priority, setPriority] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.priority) || "medium");
|
|
@@ -17252,19 +17312,40 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17252
17312
|
const handlePinClick = (e) => {
|
|
17253
17313
|
e.preventDefault();
|
|
17254
17314
|
e.stopPropagation();
|
|
17315
|
+
// Capture element info so pins can track the element when the layout changes
|
|
17316
|
+
let elementSelector;
|
|
17317
|
+
let elementOffsetX;
|
|
17318
|
+
let elementOffsetY;
|
|
17319
|
+
const target = e.target;
|
|
17320
|
+
if (target &&
|
|
17321
|
+
target !== document.documentElement &&
|
|
17322
|
+
target !== document.body) {
|
|
17323
|
+
try {
|
|
17324
|
+
elementSelector = getElementSelector$1(target);
|
|
17325
|
+
const rect = target.getBoundingClientRect();
|
|
17326
|
+
elementOffsetX = e.pageX - (rect.left + window.scrollX);
|
|
17327
|
+
elementOffsetY = e.pageY - (rect.top + window.scrollY);
|
|
17328
|
+
}
|
|
17329
|
+
catch (_) {
|
|
17330
|
+
// non-critical – fall back to percentage-based positioning
|
|
17331
|
+
}
|
|
17332
|
+
}
|
|
17255
17333
|
setPinPosition({
|
|
17256
17334
|
x: e.clientX,
|
|
17257
17335
|
y: e.clientY,
|
|
17258
17336
|
pageX: e.pageX,
|
|
17259
17337
|
pageY: e.pageY,
|
|
17338
|
+
elementSelector,
|
|
17339
|
+
elementOffsetX,
|
|
17340
|
+
elementOffsetY,
|
|
17260
17341
|
});
|
|
17261
17342
|
setIsPlacingPin(false);
|
|
17262
17343
|
};
|
|
17263
|
-
document.addEventListener(
|
|
17264
|
-
document.body.style.cursor =
|
|
17344
|
+
document.addEventListener("click", handlePinClick, true);
|
|
17345
|
+
document.body.style.cursor = "crosshair";
|
|
17265
17346
|
return () => {
|
|
17266
|
-
document.removeEventListener(
|
|
17267
|
-
document.body.style.cursor =
|
|
17347
|
+
document.removeEventListener("click", handlePinClick, true);
|
|
17348
|
+
document.body.style.cursor = "";
|
|
17268
17349
|
// Reopen widget when pin placement is done
|
|
17269
17350
|
setIsOpen(true);
|
|
17270
17351
|
};
|
|
@@ -17280,34 +17361,64 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17280
17361
|
e.stopPropagation();
|
|
17281
17362
|
const target = e.target;
|
|
17282
17363
|
// Check for img tag
|
|
17283
|
-
if (target.tagName ===
|
|
17364
|
+
if (target.tagName === "IMG") {
|
|
17284
17365
|
const img = target;
|
|
17285
17366
|
setOriginalImageSrc(img.src);
|
|
17286
|
-
setOriginalImageBackground(
|
|
17367
|
+
setOriginalImageBackground("");
|
|
17287
17368
|
// Place pin at image location
|
|
17369
|
+
let elSel;
|
|
17370
|
+
let elOffX;
|
|
17371
|
+
let elOffY;
|
|
17372
|
+
try {
|
|
17373
|
+
elSel = getElementSelector$1(target);
|
|
17374
|
+
const rect = target.getBoundingClientRect();
|
|
17375
|
+
elOffX = e.pageX - (rect.left + window.scrollX);
|
|
17376
|
+
elOffY = e.pageY - (rect.top + window.scrollY);
|
|
17377
|
+
}
|
|
17378
|
+
catch (_) {
|
|
17379
|
+
/* non-critical */
|
|
17380
|
+
}
|
|
17288
17381
|
setPinPosition({
|
|
17289
17382
|
x: e.clientX,
|
|
17290
17383
|
y: e.clientY,
|
|
17291
17384
|
pageX: e.pageX,
|
|
17292
17385
|
pageY: e.pageY,
|
|
17386
|
+
elementSelector: elSel,
|
|
17387
|
+
elementOffsetX: elOffX,
|
|
17388
|
+
elementOffsetY: elOffY,
|
|
17293
17389
|
});
|
|
17294
17390
|
setIsSelectingImage(false);
|
|
17295
17391
|
return;
|
|
17296
17392
|
}
|
|
17297
17393
|
// Check for background-image
|
|
17298
17394
|
const bgImage = window.getComputedStyle(target).backgroundImage;
|
|
17299
|
-
if (bgImage && bgImage !==
|
|
17395
|
+
if (bgImage && bgImage !== "none") {
|
|
17300
17396
|
// Extract URL from url("...") or url('...')
|
|
17301
17397
|
const urlMatch = bgImage.match(/url\(['"]?([^'"]+)['"]?\)/);
|
|
17302
17398
|
if (urlMatch) {
|
|
17303
17399
|
setOriginalImageBackground(urlMatch[1]);
|
|
17304
|
-
setOriginalImageSrc(
|
|
17400
|
+
setOriginalImageSrc("");
|
|
17305
17401
|
// Place pin at element location
|
|
17402
|
+
let elSel;
|
|
17403
|
+
let elOffX;
|
|
17404
|
+
let elOffY;
|
|
17405
|
+
try {
|
|
17406
|
+
elSel = getElementSelector$1(target);
|
|
17407
|
+
const rect = target.getBoundingClientRect();
|
|
17408
|
+
elOffX = e.pageX - (rect.left + window.scrollX);
|
|
17409
|
+
elOffY = e.pageY - (rect.top + window.scrollY);
|
|
17410
|
+
}
|
|
17411
|
+
catch (_) {
|
|
17412
|
+
/* non-critical */
|
|
17413
|
+
}
|
|
17306
17414
|
setPinPosition({
|
|
17307
17415
|
x: e.clientX,
|
|
17308
17416
|
y: e.clientY,
|
|
17309
17417
|
pageX: e.pageX,
|
|
17310
17418
|
pageY: e.pageY,
|
|
17419
|
+
elementSelector: elSel,
|
|
17420
|
+
elementOffsetX: elOffX,
|
|
17421
|
+
elementOffsetY: elOffY,
|
|
17311
17422
|
});
|
|
17312
17423
|
setIsSelectingImage(false);
|
|
17313
17424
|
}
|
|
@@ -17316,27 +17427,27 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17316
17427
|
// Add hover effect to show which element would be selected
|
|
17317
17428
|
const handleImageHover = (e) => {
|
|
17318
17429
|
const target = e.target;
|
|
17319
|
-
const isImage = target.tagName ===
|
|
17320
|
-
const hasBgImage = window.getComputedStyle(target).backgroundImage !==
|
|
17430
|
+
const isImage = target.tagName === "IMG";
|
|
17431
|
+
const hasBgImage = window.getComputedStyle(target).backgroundImage !== "none";
|
|
17321
17432
|
if (isImage || hasBgImage) {
|
|
17322
|
-
target.style.outline =
|
|
17323
|
-
target.style.outlineOffset =
|
|
17433
|
+
target.style.outline = "3px solid #3b82f6";
|
|
17434
|
+
target.style.outlineOffset = "2px";
|
|
17324
17435
|
}
|
|
17325
17436
|
};
|
|
17326
17437
|
const handleImageHoverOut = (e) => {
|
|
17327
17438
|
const target = e.target;
|
|
17328
|
-
target.style.outline =
|
|
17329
|
-
target.style.outlineOffset =
|
|
17439
|
+
target.style.outline = "";
|
|
17440
|
+
target.style.outlineOffset = "";
|
|
17330
17441
|
};
|
|
17331
|
-
document.addEventListener(
|
|
17332
|
-
document.addEventListener(
|
|
17333
|
-
document.addEventListener(
|
|
17334
|
-
document.body.style.cursor =
|
|
17442
|
+
document.addEventListener("click", handleImageClick, true);
|
|
17443
|
+
document.addEventListener("mouseover", handleImageHover, true);
|
|
17444
|
+
document.addEventListener("mouseout", handleImageHoverOut, true);
|
|
17445
|
+
document.body.style.cursor = "crosshair";
|
|
17335
17446
|
return () => {
|
|
17336
|
-
document.removeEventListener(
|
|
17337
|
-
document.removeEventListener(
|
|
17338
|
-
document.removeEventListener(
|
|
17339
|
-
document.body.style.cursor =
|
|
17447
|
+
document.removeEventListener("click", handleImageClick, true);
|
|
17448
|
+
document.removeEventListener("mouseover", handleImageHover, true);
|
|
17449
|
+
document.removeEventListener("mouseout", handleImageHoverOut, true);
|
|
17450
|
+
document.body.style.cursor = "";
|
|
17340
17451
|
// Reopen widget when image selection is done
|
|
17341
17452
|
setIsOpen(true);
|
|
17342
17453
|
};
|
|
@@ -17471,7 +17582,9 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17471
17582
|
const handleSubmit = async (e) => {
|
|
17472
17583
|
var _a, _b, _c, _d;
|
|
17473
17584
|
e.preventDefault();
|
|
17474
|
-
if (feedbackType !== "copy-amendment" &&
|
|
17585
|
+
if (feedbackType !== "copy-amendment" &&
|
|
17586
|
+
feedbackType !== "image-change" &&
|
|
17587
|
+
!title.trim())
|
|
17475
17588
|
return;
|
|
17476
17589
|
if (feedbackType === "copy-amendment" && !newCopy.trim())
|
|
17477
17590
|
return;
|
|
@@ -17500,7 +17613,13 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17500
17613
|
}
|
|
17501
17614
|
const pageWidth = document.documentElement.scrollWidth;
|
|
17502
17615
|
const pageHeight = document.documentElement.scrollHeight;
|
|
17503
|
-
const
|
|
17616
|
+
const rawMetadata = (initialData === null || initialData === void 0 ? void 0 : initialData.pageMetadata) ||
|
|
17617
|
+
getPageMetadata(lastClickedElement || undefined);
|
|
17618
|
+
// When in responsive/viewport mode, stamp the simulated device type so
|
|
17619
|
+
// reviewers know this feedback was captured at mobile/tablet dimensions.
|
|
17620
|
+
const pageMetadata = viewportMode
|
|
17621
|
+
? { ...rawMetadata, deviceType: viewportMode.deviceType }
|
|
17622
|
+
: rawMetadata;
|
|
17504
17623
|
const feedback = {
|
|
17505
17624
|
id: (initialData === null || initialData === void 0 ? void 0 : initialData.id) || generateId(),
|
|
17506
17625
|
type: feedbackType,
|
|
@@ -17546,14 +17665,19 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17546
17665
|
pageHeight,
|
|
17547
17666
|
scrollX: window.scrollX,
|
|
17548
17667
|
scrollY: window.scrollY,
|
|
17549
|
-
percentX: pageWidth
|
|
17550
|
-
|
|
17668
|
+
percentX: pageWidth
|
|
17669
|
+
? (pinPosition.pageX / pageWidth) * 100
|
|
17670
|
+
: undefined,
|
|
17671
|
+
percentY: pageHeight
|
|
17672
|
+
? (pinPosition.pageY / pageHeight) * 100
|
|
17673
|
+
: undefined,
|
|
17674
|
+
elementSelector: pinPosition.elementSelector,
|
|
17675
|
+
elementOffsetX: pinPosition.elementOffsetX,
|
|
17676
|
+
elementOffsetY: pinPosition.elementOffsetY,
|
|
17551
17677
|
}
|
|
17552
17678
|
: undefined,
|
|
17553
17679
|
// Image change data
|
|
17554
|
-
originalImageSrc: feedbackType === "image-change"
|
|
17555
|
-
? originalImageSrc
|
|
17556
|
-
: undefined,
|
|
17680
|
+
originalImageSrc: feedbackType === "image-change" ? originalImageSrc : undefined,
|
|
17557
17681
|
originalImageBackground: feedbackType === "image-change"
|
|
17558
17682
|
? originalImageBackground
|
|
17559
17683
|
: undefined,
|
|
@@ -17592,13 +17716,13 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
|
|
|
17592
17716
|
if (isSelectingImage) {
|
|
17593
17717
|
return (jsx("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-[999999] pointer-events-none", children: jsxs("div", { className: "bg-white rounded-2xl shadow-2xl p-6 max-w-md mx-4 text-center border-2 border-blue-500", children: [jsx("div", { className: "w-12 h-12 mx-auto mb-3 bg-blue-100 rounded-full flex items-center justify-center", children: jsx("svg", { className: "w-6 h-6 text-blue-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }) }), jsx("h3", { className: "text-lg font-bold text-gray-900 mb-2", children: "Click on an Image" }), jsx("p", { className: "text-gray-600 text-sm mb-3", children: "Click on any image or element with a background image" }), jsx("button", { type: "button", onClick: () => setIsSelectingImage(false), className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors pointer-events-auto", children: "Cancel" })] }) }));
|
|
17594
17718
|
}
|
|
17595
|
-
return (jsxs("form", { onSubmit: handleSubmit, children: [jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Type" }), jsx("div", { className: "flex gap-2 flex-wrap", children: [
|
|
17719
|
+
return (jsxs("form", { onSubmit: handleSubmit, children: [viewportMode && (jsxs("div", { className: "flex items-center gap-2 mb-5 px-3 py-2.5 rounded-xl bg-[#E6B6CF]/15 border border-[#E6B6CF]/40", children: [jsx("svg", { className: "w-4 h-4 text-[#d9a3c0] shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" }) }), jsxs("span", { className: "text-xs font-medium text-gray-700", children: [jsx("span", { className: "font-semibold", children: viewportMode.name }), jsxs("span", { className: "text-gray-500 ml-1", children: ["\u00B7 ", viewportMode.width, "\u00D7", viewportMode.height, " \u00B7 This feedback will be tagged as ", viewportMode.deviceType] })] })] })), jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Type" }), jsx("div", { className: "flex gap-2 flex-wrap", children: [
|
|
17596
17720
|
{ id: "general", label: "General" },
|
|
17597
17721
|
{ id: "copy-amendment", label: "Copy Change" },
|
|
17598
17722
|
{ id: "image-change", label: "Image Change" },
|
|
17599
17723
|
].map((type) => (jsx("div", { onClick: () => setFeedbackType(type.id), className: cn("flex-1 py-2 px-3 rounded-xl text-xs font-medium cursor-pointer transition-all text-center", feedbackType === type.id
|
|
17600
17724
|
? "bg-[#C2D1D9] text-black shadow-md shadow-[#C2D1D9]/30"
|
|
17601
|
-
: "bg-gray-100 text-gray-700 hover:bg-gray-200 hover:shadow-sm"), children: type.label }, type.id))) })] }), feedbackType === "image-change" && (jsxs(Fragment, { children: [jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Select Image" }), jsxs("button", { type: "button", onClick: () => setIsSelectingImage(true), className: "w-full px-4 py-3 bg-
|
|
17725
|
+
: "bg-gray-100 text-gray-700 hover:bg-gray-200 hover:shadow-sm"), children: type.label }, type.id))) })] }), feedbackType === "image-change" && (jsxs(Fragment, { children: [jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Select Image" }), jsxs("button", { type: "button", onClick: () => setIsSelectingImage(true), className: "w-full px-4 py-3 bg-foreground border rounded-xl text-sm font-medium text-background transition-all flex items-center justify-center gap-2", children: [jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122" }) }), originalImageSrc || originalImageBackground
|
|
17602
17726
|
? "Change Image"
|
|
17603
17727
|
: "Click to Select Image"] }), (originalImageSrc || originalImageBackground) && (jsxs("div", { className: "mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200", children: [jsx("p", { className: "text-xs font-semibold text-gray-700 mb-2", children: "Current Image:" }), originalImageSrc ? (jsxs(Fragment, { children: [jsx("img", { src: originalImageSrc, alt: "Selected", className: "w-full h-32 object-cover rounded-lg mb-2" }), jsx("p", { className: "text-[10px] text-gray-500 font-mono break-all", children: originalImageSrc })] })) : (jsxs("p", { className: "text-[10px] text-gray-500 font-mono break-all", children: ["Background: ", originalImageBackground] }))] }))] }), jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Replacement Image URL *" }), jsx("input", { type: "url", placeholder: "https://example.com/new-image.jpg", value: replacementImageUrl, onChange: (e) => setReplacementImageUrl(e.target.value), required: feedbackType === "image-change", className: "w-full px-4 py-3 bg-green-50 border border-green-200 rounded-xl text-sm focus:outline-none focus:bg-white focus:border-green-500 focus:shadow-lg focus:shadow-green-500/10 transition-all" }), replacementImageUrl && (jsxs("div", { className: "mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200", children: [jsx("p", { className: "text-xs font-semibold text-gray-700 mb-2", children: "Preview:" }), jsx("img", { src: replacementImageUrl, alt: "Replacement Preview", className: "w-full h-32 object-cover rounded-lg", onError: (e) => {
|
|
17604
17728
|
e.target.style.display = "none";
|
|
@@ -40269,161 +40393,190 @@ const AuthForm = ({ onSuccess }) => {
|
|
|
40269
40393
|
: "Already have an account? Sign in" }) })] }), jsx("p", { className: "text-center text-xs text-gray-500 mt-6", children: "Your feedback will be associated with your account" })] }) }));
|
|
40270
40394
|
};
|
|
40271
40395
|
|
|
40272
|
-
const
|
|
40396
|
+
const MOBILE_PRESETS = [
|
|
40273
40397
|
{ name: "iPhone SE", width: 375, height: 667, deviceType: "mobile" },
|
|
40274
|
-
{ name: "iPhone
|
|
40275
|
-
{ name: "iPhone
|
|
40398
|
+
{ name: "iPhone 15 Pro", width: 393, height: 852, deviceType: "mobile" },
|
|
40399
|
+
{ name: "iPhone 15 Pro Max", width: 430, height: 932, deviceType: "mobile" },
|
|
40400
|
+
{ name: "Samsung S24", width: 360, height: 780, deviceType: "mobile" },
|
|
40401
|
+
];
|
|
40402
|
+
const TABLET_PRESETS = [
|
|
40276
40403
|
{ name: "iPad Mini", width: 768, height: 1024, deviceType: "tablet" },
|
|
40277
|
-
{ name:
|
|
40278
|
-
{ name: "Desktop", width: 1920, height: 1080, deviceType: "desktop" },
|
|
40404
|
+
{ name: 'iPad Pro 12.9"', width: 1024, height: 1366, deviceType: "tablet" },
|
|
40279
40405
|
];
|
|
40406
|
+
const ALL_PRESETS = [...MOBILE_PRESETS, ...TABLET_PRESETS];
|
|
40407
|
+
const DeviceIcon = ({ deviceType, className }) => deviceType === "tablet"
|
|
40408
|
+
? jsx(TabletIcon, { className: className })
|
|
40409
|
+
: jsx(MobileIcon, { className: className });
|
|
40280
40410
|
const ViewportControls = () => {
|
|
40281
40411
|
const { viewportMode, setViewportMode, isOpen: isWidgetOpen } = useMarkupStore();
|
|
40282
40412
|
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
40283
40413
|
const dropdownRef = React.useRef(null);
|
|
40284
40414
|
const handlePresetClick = (preset) => {
|
|
40285
|
-
setViewportMode({ width: preset.width, height: preset.height });
|
|
40415
|
+
setViewportMode({ width: preset.width, height: preset.height, deviceType: preset.deviceType, name: preset.name });
|
|
40286
40416
|
setIsExpanded(false);
|
|
40287
40417
|
};
|
|
40288
40418
|
const handleReset = () => {
|
|
40289
40419
|
setViewportMode(null);
|
|
40290
40420
|
setIsExpanded(false);
|
|
40291
40421
|
};
|
|
40292
|
-
const isActivePreset = (preset) => {
|
|
40293
|
-
return (viewportMode === null || viewportMode === void 0 ? void 0 : viewportMode.width) === preset.width && (viewportMode === null || viewportMode === void 0 ? void 0 : viewportMode.height) === preset.height;
|
|
40294
|
-
};
|
|
40295
|
-
// Close dropdown on outside click
|
|
40296
40422
|
React.useEffect(() => {
|
|
40297
40423
|
if (!isExpanded)
|
|
40298
40424
|
return;
|
|
40299
|
-
const
|
|
40300
|
-
if (dropdownRef.current && !dropdownRef.current.contains(
|
|
40425
|
+
const onOutside = (e) => {
|
|
40426
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target))
|
|
40301
40427
|
setIsExpanded(false);
|
|
40302
|
-
}
|
|
40303
40428
|
};
|
|
40304
|
-
document.addEventListener(
|
|
40305
|
-
return () => document.removeEventListener(
|
|
40429
|
+
document.addEventListener("mousedown", onOutside);
|
|
40430
|
+
return () => document.removeEventListener("mousedown", onOutside);
|
|
40306
40431
|
}, [isExpanded]);
|
|
40307
|
-
// Handle ESC key to close dropdown or reset viewport
|
|
40308
40432
|
React.useEffect(() => {
|
|
40309
|
-
const
|
|
40310
|
-
if (
|
|
40311
|
-
|
|
40312
|
-
|
|
40313
|
-
|
|
40314
|
-
|
|
40315
|
-
|
|
40316
|
-
|
|
40317
|
-
|
|
40318
|
-
|
|
40433
|
+
const onKey = (e) => {
|
|
40434
|
+
if (e.key !== "Escape")
|
|
40435
|
+
return;
|
|
40436
|
+
if (isExpanded) {
|
|
40437
|
+
setIsExpanded(false);
|
|
40438
|
+
e.preventDefault();
|
|
40439
|
+
}
|
|
40440
|
+
else if (viewportMode) {
|
|
40441
|
+
handleReset();
|
|
40442
|
+
e.preventDefault();
|
|
40319
40443
|
}
|
|
40320
40444
|
};
|
|
40321
|
-
document.addEventListener(
|
|
40322
|
-
return () => document.removeEventListener(
|
|
40445
|
+
document.addEventListener("keydown", onKey);
|
|
40446
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
40323
40447
|
}, [isExpanded, viewportMode]);
|
|
40324
|
-
|
|
40325
|
-
if (!isWidgetOpen)
|
|
40448
|
+
if (!isWidgetOpen && !viewportMode)
|
|
40326
40449
|
return null;
|
|
40327
|
-
return (jsxs("div", { ref: dropdownRef, className: "fixed
|
|
40328
|
-
|
|
40329
|
-
|
|
40330
|
-
|
|
40331
|
-
|
|
40332
|
-
? "bg-
|
|
40333
|
-
: "
|
|
40334
|
-
};
|
|
40335
|
-
|
|
40450
|
+
return (jsxs("div", { ref: dropdownRef, className: "fixed top-4 left-1/2 -translate-x-1/2 z-[999999] select-none", "data-markup-viewport": true, children: [!viewportMode && (jsxs("div", { className: "relative", children: [jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: cn("flex items-center gap-2 px-4 py-2 rounded-2xl text-sm font-medium bg-white border transition-all shadow-md", isExpanded
|
|
40451
|
+
? "border-[#E6B6CF] text-black shadow-lg shadow-[#E6B6CF]/20"
|
|
40452
|
+
: "border-gray-200 text-gray-600 hover:border-[#E6B6CF] hover:text-black hover:shadow-lg hover:shadow-[#E6B6CF]/20"), children: [jsx(MobileIcon, { className: cn("w-4 h-4 transition-colors", isExpanded ? "text-[#d9a3c0]" : "text-gray-400 group-hover:text-[#d9a3c0]") }), jsx("span", { children: "Responsive" }), jsx("svg", { className: cn("w-3 h-3 text-gray-400 transition-transform duration-200", isExpanded && "rotate-180"), fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }), isExpanded && (jsxs("div", { className: "absolute top-full left-1/2 -translate-x-1/2 mt-2 bg-white border border-gray-200 rounded-2xl shadow-xl p-2 min-w-[260px]", children: [jsx("p", { className: "text-[10px] font-semibold text-gray-400 uppercase tracking-widest px-3 pt-2 pb-1", children: "Mobile" }), MOBILE_PRESETS.map(preset => (jsxs("button", { onClick: () => handlePresetClick(preset), className: "w-full flex items-center gap-3 px-3 py-2 rounded-xl hover:bg-[#E6B6CF]/10 transition-colors text-left", children: [jsx(MobileIcon, { className: "w-4 h-4 text-gray-400 shrink-0" }), jsx("span", { className: "flex-1 text-sm font-medium text-gray-700", children: preset.name }), jsxs("span", { className: "text-xs text-gray-400 tabular-nums", children: [preset.width, "\u00D7", preset.height] })] }, preset.name))), jsx("div", { className: "my-2 border-t border-gray-100" }), jsx("p", { className: "text-[10px] font-semibold text-gray-400 uppercase tracking-widest px-3 pb-1", children: "Tablet" }), TABLET_PRESETS.map(preset => (jsxs("button", { onClick: () => handlePresetClick(preset), className: "w-full flex items-center gap-3 px-3 py-2 rounded-xl hover:bg-[#E6B6CF]/10 transition-colors text-left", children: [jsx(TabletIcon, { className: "w-4 h-4 text-gray-400 shrink-0" }), jsx("span", { className: "flex-1 text-sm font-medium text-gray-700", children: preset.name }), jsxs("span", { className: "text-xs text-gray-400 tabular-nums", children: [preset.width, "\u00D7", preset.height] })] }, preset.name)))] }))] })), viewportMode && (jsxs("div", { className: "flex items-center gap-1 bg-white border border-gray-200 rounded-2xl shadow-lg px-1.5 py-1.5", children: [jsxs("div", { className: "flex items-center gap-1.5 px-2.5 py-1 rounded-xl bg-[#E6B6CF]/20", children: [jsx(DeviceIcon, { deviceType: viewportMode.deviceType, className: "w-3.5 h-3.5 text-[#d9a3c0] shrink-0" }), jsx("span", { className: "text-xs font-semibold text-gray-800", children: viewportMode.name }), jsxs("span", { className: "text-[10px] text-gray-500 tabular-nums", children: [viewportMode.width, "\u00D7", viewportMode.height] })] }), jsx("div", { className: "w-px h-5 bg-gray-200 mx-0.5" }), ALL_PRESETS.map(preset => {
|
|
40453
|
+
const isActive = viewportMode.name === preset.name;
|
|
40454
|
+
return (jsx("button", { onClick: () => handlePresetClick(preset), title: `${preset.name} — ${preset.width}×${preset.height}`, className: cn("flex items-center justify-center w-7 h-7 rounded-xl transition-all", isActive
|
|
40455
|
+
? "bg-[#E6B6CF] text-black shadow-sm shadow-[#E6B6CF]/40"
|
|
40456
|
+
: "text-gray-400 hover:bg-[#E6B6CF]/15 hover:text-gray-700"), children: jsx(DeviceIcon, { deviceType: preset.deviceType, className: "w-3.5 h-3.5" }) }, preset.name));
|
|
40457
|
+
}), jsx("div", { className: "w-px h-5 bg-gray-200 mx-0.5" }), jsx("button", { onClick: handleReset, title: "Exit responsive mode (Esc)", className: "flex items-center justify-center w-7 h-7 rounded-xl text-gray-400 hover:bg-red-50 hover:text-red-500 transition-all", children: jsx(CloseIcon, { className: "w-3.5 h-3.5" }) })] }))] }));
|
|
40458
|
+
};
|
|
40459
|
+
|
|
40460
|
+
/**
|
|
40461
|
+
* Compute the CSS position style for a single pin.
|
|
40462
|
+
*
|
|
40463
|
+
* Priority:
|
|
40464
|
+
* 1. Element-anchored: if `elementSelector` + `elementOffsetX/Y` are stored,
|
|
40465
|
+
* query the element's current bounding rect and add the stored offset so the
|
|
40466
|
+
* pin sticks to the element regardless of scroll / resize / layout-reflow.
|
|
40467
|
+
* 2. Page-percentage fallback: use `percentX`/`percentY` (stored as % of the
|
|
40468
|
+
* full document dimensions at save-time). These already scale correctly
|
|
40469
|
+
* when the document resizes because the container uses the same dimensions.
|
|
40470
|
+
*/
|
|
40471
|
+
function computePinStyle(pinLocation) {
|
|
40472
|
+
// --- Element-anchored positioning ---
|
|
40473
|
+
if (pinLocation.elementSelector &&
|
|
40474
|
+
pinLocation.elementOffsetX != null &&
|
|
40475
|
+
pinLocation.elementOffsetY != null) {
|
|
40476
|
+
try {
|
|
40477
|
+
const el = document.querySelector(pinLocation.elementSelector);
|
|
40478
|
+
if (el) {
|
|
40479
|
+
const rect = el.getBoundingClientRect();
|
|
40480
|
+
const elPageLeft = rect.left + window.scrollX;
|
|
40481
|
+
const elPageTop = rect.top + window.scrollY;
|
|
40482
|
+
return {
|
|
40483
|
+
position: "absolute",
|
|
40484
|
+
left: elPageLeft + pinLocation.elementOffsetX,
|
|
40485
|
+
top: elPageTop + pinLocation.elementOffsetY,
|
|
40486
|
+
transform: "translate(-50%, -50%)",
|
|
40487
|
+
pointerEvents: "auto",
|
|
40488
|
+
};
|
|
40489
|
+
}
|
|
40490
|
+
}
|
|
40491
|
+
catch (_) {
|
|
40492
|
+
// fall through to percentage fallback
|
|
40493
|
+
}
|
|
40494
|
+
}
|
|
40495
|
+
// --- Page-percentage fallback ---
|
|
40496
|
+
const pageWidth = pinLocation.pageWidth ||
|
|
40497
|
+
pinLocation.viewportWidth ||
|
|
40498
|
+
window.innerWidth;
|
|
40499
|
+
const pageHeight = pinLocation.pageHeight ||
|
|
40500
|
+
pinLocation.viewportHeight ||
|
|
40501
|
+
window.innerHeight;
|
|
40502
|
+
const leftPercent = typeof pinLocation.percentX === "number"
|
|
40503
|
+
? pinLocation.percentX
|
|
40504
|
+
: pageWidth
|
|
40505
|
+
? (pinLocation.pageX / pageWidth) * 100
|
|
40506
|
+
: 0;
|
|
40507
|
+
const topPercent = typeof pinLocation.percentY === "number"
|
|
40508
|
+
? pinLocation.percentY
|
|
40509
|
+
: pageHeight
|
|
40510
|
+
? (pinLocation.pageY / pageHeight) * 100
|
|
40511
|
+
: 0;
|
|
40512
|
+
return {
|
|
40513
|
+
position: "absolute",
|
|
40514
|
+
left: `${leftPercent}%`,
|
|
40515
|
+
top: `${topPercent}%`,
|
|
40516
|
+
transform: "translate(-50%, -50%)",
|
|
40517
|
+
pointerEvents: "auto",
|
|
40518
|
+
};
|
|
40519
|
+
}
|
|
40336
40520
|
const FeedbackPins = ({ onPinClick }) => {
|
|
40337
40521
|
const { feedbackItems, showPins } = useMarkupStore();
|
|
40338
|
-
|
|
40522
|
+
// Bump a counter on every scroll / resize so we re-render and re-query
|
|
40523
|
+
// element rects. We don't need to store the actual dimensions here because
|
|
40524
|
+
// computePinStyle reads from the DOM live each render.
|
|
40525
|
+
const [, setLayoutVersion] = useState(0);
|
|
40339
40526
|
useEffect(() => {
|
|
40340
|
-
const
|
|
40341
|
-
|
|
40342
|
-
const body = document.body;
|
|
40343
|
-
const width = root.clientWidth || body.clientWidth || window.innerWidth;
|
|
40344
|
-
const height = Math.max(root.scrollHeight, body.scrollHeight, root.clientHeight, body.clientHeight);
|
|
40345
|
-
setDocSize({ width, height });
|
|
40346
|
-
};
|
|
40347
|
-
measure();
|
|
40348
|
-
const resizeObserver = new ResizeObserver(() => measure());
|
|
40527
|
+
const bump = () => setLayoutVersion((v) => v + 1);
|
|
40528
|
+
const resizeObserver = new ResizeObserver(bump);
|
|
40349
40529
|
resizeObserver.observe(document.documentElement);
|
|
40350
40530
|
resizeObserver.observe(document.body);
|
|
40351
|
-
|
|
40352
|
-
|
|
40353
|
-
window.addEventListener('resize', handleResize);
|
|
40354
|
-
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
40531
|
+
window.addEventListener("resize", bump);
|
|
40532
|
+
window.addEventListener("scroll", bump, { passive: true });
|
|
40355
40533
|
return () => {
|
|
40356
40534
|
resizeObserver.disconnect();
|
|
40357
|
-
window.removeEventListener(
|
|
40358
|
-
window.removeEventListener(
|
|
40535
|
+
window.removeEventListener("resize", bump);
|
|
40536
|
+
window.removeEventListener("scroll", bump);
|
|
40359
40537
|
};
|
|
40360
40538
|
}, []);
|
|
40361
40539
|
// Filter pins for current page URL
|
|
40362
40540
|
const currentPagePins = useMemo(() => {
|
|
40363
40541
|
const currentUrl = window.location.href;
|
|
40364
|
-
return feedbackItems.filter(item => {
|
|
40542
|
+
return feedbackItems.filter((item) => {
|
|
40365
40543
|
var _a;
|
|
40366
|
-
// Only show pins with pinLocation data
|
|
40367
40544
|
if (!item.pinLocation)
|
|
40368
40545
|
return false;
|
|
40369
|
-
// Only show pins from the same page
|
|
40370
40546
|
if (((_a = item.pageMetadata) === null || _a === void 0 ? void 0 : _a.url) !== currentUrl)
|
|
40371
40547
|
return false;
|
|
40372
40548
|
return true;
|
|
40373
40549
|
});
|
|
40374
40550
|
}, [feedbackItems]);
|
|
40375
|
-
|
|
40376
|
-
if (!showPins)
|
|
40377
|
-
return null;
|
|
40378
|
-
if (currentPagePins.length === 0)
|
|
40551
|
+
if (!showPins || currentPagePins.length === 0)
|
|
40379
40552
|
return null;
|
|
40553
|
+
// The container covers the full scrollable document so that absolute-
|
|
40554
|
+
// positioned pins land in the right place regardless of scroll position.
|
|
40555
|
+
const root = document.documentElement;
|
|
40556
|
+
const body = document.body;
|
|
40557
|
+
const docWidth = root.clientWidth || body.clientWidth || window.innerWidth;
|
|
40558
|
+
const docHeight = Math.max(root.scrollHeight, body.scrollHeight, root.clientHeight, body.clientHeight);
|
|
40380
40559
|
const containerStyle = {
|
|
40381
|
-
position:
|
|
40560
|
+
position: "absolute",
|
|
40382
40561
|
top: 0,
|
|
40383
40562
|
left: 0,
|
|
40384
|
-
width:
|
|
40385
|
-
height:
|
|
40386
|
-
pointerEvents:
|
|
40563
|
+
width: docWidth,
|
|
40564
|
+
height: docHeight,
|
|
40565
|
+
pointerEvents: "none",
|
|
40387
40566
|
zIndex: 999998,
|
|
40388
40567
|
};
|
|
40389
40568
|
return (jsx("div", { className: "feedback-pins-container", "data-markup-pins": true, style: containerStyle, children: currentPagePins.map((feedback) => {
|
|
40390
40569
|
const { pinLocation } = feedback;
|
|
40391
40570
|
if (!pinLocation)
|
|
40392
40571
|
return null;
|
|
40393
|
-
|
|
40394
|
-
|
|
40395
|
-
try {
|
|
40396
|
-
const pageWidth = docSize.width || pinLocation.pageWidth || pinLocation.viewportWidth || window.innerWidth;
|
|
40397
|
-
const pageHeight = docSize.height || pinLocation.pageHeight || pinLocation.viewportHeight || window.innerHeight;
|
|
40398
|
-
// Prefer stored percentages; fall back to computing from stored absolute coords
|
|
40399
|
-
const leftPercent = typeof pinLocation.percentX === 'number'
|
|
40400
|
-
? pinLocation.percentX
|
|
40401
|
-
: pageWidth ? (pinLocation.pageX / pageWidth) * 100 : 0;
|
|
40402
|
-
const topPercent = typeof pinLocation.percentY === 'number'
|
|
40403
|
-
? pinLocation.percentY
|
|
40404
|
-
: pageHeight ? (pinLocation.pageY / pageHeight) * 100 : 0;
|
|
40405
|
-
const style = {
|
|
40406
|
-
position: 'absolute',
|
|
40407
|
-
left: `${leftPercent}%`,
|
|
40408
|
-
top: `${topPercent}%`,
|
|
40409
|
-
transform: 'translate(-50%, -50%)',
|
|
40410
|
-
pointerEvents: 'auto',
|
|
40411
|
-
};
|
|
40412
|
-
return (jsx("div", { className: "feedback-pin-wrapper", style: style, onClick: () => onPinClick(feedback), children: jsxs("div", { className: "feedback-pin group relative cursor-pointer", children: [jsx("div", { className: `
|
|
40572
|
+
const pinStyle = computePinStyle(pinLocation);
|
|
40573
|
+
return (jsx("div", { className: "feedback-pin-wrapper", style: pinStyle, onClick: () => onPinClick(feedback), children: jsxs("div", { className: "feedback-pin group relative cursor-pointer", children: [jsx("div", { className: `
|
|
40413
40574
|
w-8 h-8 rounded-full flex items-center justify-center
|
|
40414
40575
|
shadow-lg hover:shadow-xl transition-all
|
|
40415
|
-
${feedback.status ===
|
|
40576
|
+
${feedback.status === "open" ? "bg-[#E6B6CF] hover:bg-[#d9a3c0]" : "bg-green-500 hover:bg-green-600"}
|
|
40416
40577
|
border-2 border-white
|
|
40417
40578
|
hover:scale-110
|
|
40418
|
-
`, children: jsx(MessageIcon, { className: "w-4 h-4 text-white" }) }), jsxs("div", { className: "\n absolute bottom-full left-1/2 -translate-x-1/2 mb-2\n opacity-0 group-hover:opacity-100\n pointer-events-none\n transition-opacity duration-200\n bg-gray-900 text-white text-xs rounded-lg py-2 px-3\n whitespace-nowrap shadow-xl\n max-w-[200px]\n ", children: [jsx("div", { className: "font-semibold truncate", children: feedback.title ||
|
|
40419
|
-
}
|
|
40420
|
-
catch (e) {
|
|
40421
|
-
// If anything goes wrong measuring/positioning, skip this pin
|
|
40422
|
-
return null;
|
|
40423
|
-
}
|
|
40424
|
-
}
|
|
40425
|
-
// If we didn't render a pin above, return null so map callback returns consistently
|
|
40426
|
-
return null;
|
|
40579
|
+
`, children: jsx(MessageIcon, { className: "w-4 h-4 text-white" }) }), jsxs("div", { className: "\n absolute bottom-full left-1/2 -translate-x-1/2 mb-2\n opacity-0 group-hover:opacity-100\n pointer-events-none\n transition-opacity duration-200\n bg-gray-900 text-white text-xs rounded-lg py-2 px-3\n whitespace-nowrap shadow-xl\n max-w-[200px]\n ", children: [jsx("div", { className: "font-semibold truncate", children: feedback.title || "Feedback" }), jsx("div", { className: "text-gray-300 text-[10px] mt-0.5", children: "Click to view" }), jsx("div", { className: "\n absolute top-full left-1/2 -translate-x-1/2\n w-0 h-0\n border-l-4 border-l-transparent\n border-r-4 border-r-transparent\n border-t-4 border-t-gray-900\n " })] })] }) }, feedback.id));
|
|
40427
40580
|
}) }));
|
|
40428
40581
|
};
|
|
40429
40582
|
|
|
@@ -40682,32 +40835,52 @@ const MarkupWidget = ({ config: userConfig, }) => {
|
|
|
40682
40835
|
}, []);
|
|
40683
40836
|
// Apply viewport mode styling to document (like browser dev tools)
|
|
40684
40837
|
useEffect(() => {
|
|
40685
|
-
if (viewportMode)
|
|
40686
|
-
|
|
40687
|
-
|
|
40688
|
-
|
|
40689
|
-
|
|
40690
|
-
|
|
40691
|
-
|
|
40692
|
-
root.style.
|
|
40693
|
-
root.style.maxWidth
|
|
40694
|
-
root.style.
|
|
40695
|
-
root.style.
|
|
40696
|
-
root.style.background
|
|
40697
|
-
root.style.
|
|
40698
|
-
|
|
40699
|
-
|
|
40700
|
-
|
|
40701
|
-
|
|
40702
|
-
|
|
40703
|
-
|
|
40704
|
-
|
|
40705
|
-
|
|
40706
|
-
|
|
40707
|
-
|
|
40708
|
-
|
|
40709
|
-
|
|
40710
|
-
|
|
40838
|
+
if (!viewportMode)
|
|
40839
|
+
return;
|
|
40840
|
+
const root = document.documentElement;
|
|
40841
|
+
const body = document.body;
|
|
40842
|
+
// Snapshot every inline style property we're about to touch so we
|
|
40843
|
+
// can restore them exactly on cleanup.
|
|
40844
|
+
const saved = {
|
|
40845
|
+
htmlWidth: root.style.width,
|
|
40846
|
+
htmlMaxWidth: root.style.maxWidth,
|
|
40847
|
+
htmlMinWidth: root.style.minWidth,
|
|
40848
|
+
htmlMargin: root.style.margin,
|
|
40849
|
+
htmlBackground: root.style.background,
|
|
40850
|
+
htmlBorderRadius: root.style.borderRadius,
|
|
40851
|
+
htmlBoxShadow: root.style.boxShadow,
|
|
40852
|
+
bodyBackground: body.style.background,
|
|
40853
|
+
bodyMinHeight: body.style.minHeight,
|
|
40854
|
+
bodyOverflow: body.style.overflow,
|
|
40855
|
+
};
|
|
40856
|
+
// --- Constrain <html> to the device width so that flex/grid layouts
|
|
40857
|
+
// actually reflow at the target width (max-width alone isn't enough).
|
|
40858
|
+
root.style.width = `${viewportMode.width}px`;
|
|
40859
|
+
root.style.maxWidth = `${viewportMode.width}px`;
|
|
40860
|
+
root.style.minWidth = '0';
|
|
40861
|
+
root.style.margin = '24px auto';
|
|
40862
|
+
root.style.background = '#ffffff';
|
|
40863
|
+
root.style.borderRadius = '16px';
|
|
40864
|
+
root.style.boxShadow = '0 32px 96px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.08)';
|
|
40865
|
+
// --- Body background fills the full viewport and acts as the dark
|
|
40866
|
+
// backdrop (body background-painting area always covers the viewport).
|
|
40867
|
+
body.style.background = '#18181b';
|
|
40868
|
+
body.style.minHeight = '100vh';
|
|
40869
|
+
body.style.overflow = 'visible';
|
|
40870
|
+
// Scroll to top so the user sees the page from the start.
|
|
40871
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
40872
|
+
return () => {
|
|
40873
|
+
root.style.width = saved.htmlWidth;
|
|
40874
|
+
root.style.maxWidth = saved.htmlMaxWidth;
|
|
40875
|
+
root.style.minWidth = saved.htmlMinWidth;
|
|
40876
|
+
root.style.margin = saved.htmlMargin;
|
|
40877
|
+
root.style.background = saved.htmlBackground;
|
|
40878
|
+
root.style.borderRadius = saved.htmlBorderRadius;
|
|
40879
|
+
root.style.boxShadow = saved.htmlBoxShadow;
|
|
40880
|
+
body.style.background = saved.bodyBackground;
|
|
40881
|
+
body.style.minHeight = saved.bodyMinHeight;
|
|
40882
|
+
body.style.overflow = saved.bodyOverflow;
|
|
40883
|
+
};
|
|
40711
40884
|
}, [viewportMode]);
|
|
40712
40885
|
useEffect(() => {
|
|
40713
40886
|
if (userConfig) {
|