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