pushfeedback 0.1.83 → 0.1.85
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/cjs/canvas-editor_3.cjs.entry.js +440 -44
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/pushfeedback.cjs.js +1 -1
- package/dist/collection/components/canvas-editor/canvas-editor.js +173 -15
- package/dist/collection/components/feedback-button/feedback-button.css +45 -1
- package/dist/collection/components/feedback-button/feedback-button.js +98 -8
- package/dist/collection/components/feedback-modal/feedback-modal.css +6 -0
- package/dist/collection/components/feedback-modal/feedback-modal.js +351 -20
- package/dist/collection/utils/sanitize-html.js +90 -0
- package/dist/components/canvas-editor2.js +139 -15
- package/dist/components/feedback-button.js +30 -8
- package/dist/components/feedback-modal2.js +286 -21
- package/dist/esm/canvas-editor_3.entry.js +440 -44
- package/dist/esm/loader.js +1 -1
- package/dist/esm/pushfeedback.js +1 -1
- package/dist/pushfeedback/p-17acbd1c.entry.js +1 -0
- package/dist/pushfeedback/pushfeedback.esm.js +1 -1
- package/dist/types/components/canvas-editor/canvas-editor.d.ts +7 -0
- package/dist/types/components/feedback-button/feedback-button.d.ts +16 -0
- package/dist/types/components/feedback-modal/feedback-modal.d.ts +33 -0
- package/dist/types/components.d.ts +48 -0
- package/dist/types/utils/sanitize-html.d.ts +8 -0
- package/package.json +2 -1
- package/dist/pushfeedback/p-1338beed.entry.js +0 -1
|
@@ -2,12 +2,59 @@ import { r as registerInstance, c as createEvent, h, H as Host, g as getElement
|
|
|
2
2
|
|
|
3
3
|
const canvasEditorCss = ":host{display:block}.canvas-editor-wrapper{position:relative}.canvas-editor-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0, 0, 0, 0.8);display:flex;align-items:center;justify-content:center;z-index:9999}.canvas-editor-modal{width:95vw;height:95vh;max-width:none;max-height:none;background:var(--feedback-canvas-editor-bg-color, #ffffff);border-radius:12px;border:1px solid rgba(0, 0, 0, 0.08);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 0 0 1px rgba(0, 0, 0, 0.02),\n 0 8px 16px rgba(0, 0, 0, 0.08),\n 0 16px 32px rgba(0, 0, 0, 0.08),\n 0 32px 64px rgba(0, 0, 0, 0.06)}.canvas-editor-header{background:var(--feedback-canvas-editor-header-bg-color, #fafafa);border-bottom:1px solid rgba(0, 0, 0, 0.06);padding:16px 20px;display:flex;flex-direction:column;gap:16px}.canvas-editor-title h3{margin:0;font-size:18px;font-weight:600;color:var(--feedback-canvas-editor-tool-text-color, #1a1a1a);letter-spacing:-0.02em}.canvas-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:16px}.toolbar-section{display:flex;align-items:center;gap:8px}.tool-group{display:flex;align-items:center;gap:4px}.tool-btn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1.5px solid rgba(0, 0, 0, 0.08);border-radius:8px;cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);padding:0;box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.tool-btn svg{width:18px;height:18px;color:var(--feedback-canvas-editor-tool-text-color, #333)}.tool-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover, #f5f5f5);border-color:rgba(0, 0, 0, 0.12);transform:translateY(-1px);box-shadow:0 2px 4px rgba(0, 0, 0, 0.08)}.tool-btn.active,.tool-btn.active:hover{background:var(--feedback-canvas-editor-tool-bg-active, #0070f4);color:var(--feedback-canvas-editor-tool-text-active, #ffffff);border-color:var(--feedback-canvas-editor-tool-bg-active, #0070f4);box-shadow:0 2px 8px rgba(0, 112, 244, 0.3)}.tool-btn.active svg{color:var(--feedback-canvas-editor-tool-text-active, #ffffff)}.tool-btn:disabled{opacity:0.4;cursor:not-allowed;color:var(--feedback-canvas-editor-tool-text-color, #333)}.toolbar-divider{width:1px;height:24px;background:var(--feedback-canvas-editor-divider-color, #e0e0e0);margin:0 4px}.undo-btn,.delete-btn{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff) !important;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0) !important}.undo-btn:hover:not(:disabled),.delete-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0) !important}.undo-btn:disabled,.delete-btn:disabled{opacity:0.3;cursor:not-allowed}.delete-btn:hover:not(:disabled){background:#fee !important;border-color:#fcc !important}.delete-btn:hover:not(:disabled) svg{color:#c53030}.color-palette{display:flex;align-items:center;gap:6px}.color-slot-wrapper{position:relative;display:flex;align-items:center}.color-btn{width:32px;height:32px;border-radius:8px;border:2px solid transparent;cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:2px solid rgba(0, 0, 0, 0.08);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.color-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.color-btn.active{border-color:var(--feedback-primary-color, #0070f4);box-shadow:0 0 0 2px rgba(0, 112, 244, 0.2)}.color-btn.editing{border-color:var(--feedback-primary-color, #0070f4)}.color-picker-dropdown{position:absolute;top:100%;left:0;z-index:1000;margin-top:4px;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;padding:8px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.1)}.color-picker-dropdown input[type=\"color\"]{width:40px;height:40px;border:none;border-radius:4px;cursor:pointer}.size-control{display:flex;align-items:center;gap:8px;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;padding:6px 12px}.size-slider{width:80px;height:14px;-webkit-appearance:none;appearance:none;background:transparent;border-radius:2px;outline:none;cursor:pointer;border:none;position:relative}.size-slider::before{content:'';position:absolute;top:50%;left:0;right:0;height:4px;background:rgba(0, 0, 0, 0.15);border-radius:2px;transform:translateY(-50%);pointer-events:none}.size-slider::-webkit-slider-track{width:100%;height:4px;background:transparent;border-radius:2px;border:none}.size-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;cursor:pointer;position:relative;z-index:1}.size-slider::-moz-range-track{width:100%;height:4px;background:transparent;border-radius:2px;border:none}.size-slider::-moz-range-thumb{width:14px;height:14px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-slider::-ms-track{width:100%;height:4px;background:transparent;border-radius:2px;border:none;color:transparent}.size-slider::-ms-thumb{width:14px;height:14px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-value{font-weight:500;color:var(--feedback-canvas-editor-tool-text-color, #333);font-size:12px;min-width:30px}.selected-annotation-controls{border-left:2px solid var(--feedback-canvas-editor-divider-color, #e0e0e0);padding-left:12px;margin-left:8px;min-width:200px}.text-controls{display:flex;flex-direction:row;align-items:center;gap:12px}.font-size-control,.border-width-control{display:flex;align-items:center;gap:6px}.font-size-control label,.border-width-control label{font-size:12px;color:var(--feedback-canvas-editor-tool-text-color, #333);font-weight:500;min-width:35px}.action-btn{display:flex;align-items:center;gap:8px;padding:0 20px;border:1.5px solid rgba(0, 0, 0, 0.08);border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);min-width:80px;justify-content:center;height:40px;box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.action-btn.secondary{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);color:var(--feedback-canvas-editor-tool-text-color, #333);border-color:rgba(0, 0, 0, 0.08)}.action-btn.secondary:hover{background:var(--feedback-canvas-editor-tool-bg-hover, #f5f5f5);border-color:rgba(0, 0, 0, 0.12);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.08)}.action-btn.primary{background:var(--feedback-primary-color, #0070f4);color:#ffffff;border-color:var(--feedback-primary-color, #0070f4);box-shadow:0 2px 4px rgba(0, 112, 244, 0.2)}.action-btn.primary:hover{background:#0056cc;border-color:#0056cc;transform:translateY(-1px);box-shadow:0 6px 12px rgba(0, 112, 244, 0.3)}.action-btn:active{transform:translateY(0)}.action-btn.small{height:28px;padding:4px 8px;font-size:12px;min-width:65px}.shape-controls{display:flex;flex-direction:column;gap:8px}.canvas-editor-content{flex:1;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--feedback-canvas-editor-content-bg, #f5f5f5);overflow:hidden;min-height:0;min-width:0;position:relative}.canvas-editor-content::-webkit-scrollbar{width:8px;height:8px}.canvas-editor-content::-webkit-scrollbar-track{background:transparent}.canvas-editor-content::-webkit-scrollbar-thumb{background:rgba(0, 0, 0, 0.2);border-radius:4px}.canvas-editor-content::-webkit-scrollbar-thumb:hover{background:rgba(0, 0, 0, 0.3)}.annotation-canvas{max-width:calc(100% - 48px);max-height:calc(100% - 48px);width:auto !important;height:auto !important;cursor:crosshair;border-radius:8px;box-shadow:0 0 0 1px rgba(0, 0, 0, 0.04),\n 0 4px 6px rgba(0, 0, 0, 0.06),\n 0 8px 16px rgba(0, 0, 0, 0.08);background:#ffffff;transition:box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);object-fit:contain;display:block;margin:auto}.annotation-canvas:hover{box-shadow:0 0 0 1px rgba(0, 0, 0, 0.06),\n 0 6px 10px rgba(0, 0, 0, 0.08),\n 0 12px 24px rgba(0, 0, 0, 0.1)}@media screen and (min-width: 1920px){.canvas-editor-modal{width:90vw;height:90vh}.canvas-editor-content{padding:32px}}@media screen and (min-width: 1200px) and (max-width: 1919px){.canvas-editor-modal{width:92vw;height:92vh}.canvas-editor-content{padding:28px}}@media screen and (min-width: 769px) and (max-width: 1199px){.canvas-editor-modal{width:95vw;height:95vh}}@media screen and (max-width: 768px){.canvas-editor-modal{width:100vw;height:100vh;border-radius:0}.canvas-editor-toolbar{flex-direction:column;align-items:stretch;gap:8px}.toolbar-section{justify-content:center}.selected-annotation-controls{border-left:none;border-top:2px solid var(--feedback-canvas-editor-divider-color, #e0e0e0);padding-left:0;padding-top:8px;margin-left:0;margin-top:8px;min-width:auto}.canvas-editor-content{padding:16px}}";
|
|
4
4
|
|
|
5
|
+
// Upper bound on waiting for the capture stream to produce video metadata,
|
|
6
|
+
// after which the stream is stopped so it cannot leak
|
|
7
|
+
const CAPTURE_TIMEOUT_MS = 10000;
|
|
5
8
|
const CanvasEditor = class {
|
|
6
9
|
constructor(hostRef) {
|
|
7
10
|
registerInstance(this, hostRef);
|
|
8
11
|
this.screenshotReady = createEvent(this, "screenshotReady", 7);
|
|
9
12
|
this.screenshotCancelled = createEvent(this, "screenshotCancelled", 7);
|
|
10
13
|
this.screenshotFailed = createEvent(this, "screenshotFailed", 7);
|
|
14
|
+
this.handleKeyDown = (event) => {
|
|
15
|
+
if (!this.showCanvasEditor || !this.escapeClose || event.key !== 'Escape') {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// While the browser is capturing the screen, the picker owns the keyboard
|
|
19
|
+
if (this.takingScreenshot) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
// The colour picker is a popup on top of the editor, so Escape dismisses
|
|
24
|
+
// that first and leaves the editor (and any annotations) in place
|
|
25
|
+
if (this.showColorPicker) {
|
|
26
|
+
this.showColorPicker = false;
|
|
27
|
+
this.editingColorIndex = -1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Deselect before closing, the way drawing tools do. Closing throws away
|
|
31
|
+
// every annotation, so someone who has just selected one gets a cheap
|
|
32
|
+
// Escape that only drops the selection rather than their work
|
|
33
|
+
if (this.selectedAnnotation) {
|
|
34
|
+
this.selectedAnnotation = null;
|
|
35
|
+
this.redrawAnnotations();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Same as the Cancel button: discards annotations and returns to the form
|
|
39
|
+
this.closeCanvasEditor();
|
|
40
|
+
};
|
|
41
|
+
// Tracks whether the press that started this click landed on the backdrop.
|
|
42
|
+
// Dragging an annotation and releasing past the edge of the editor fires a
|
|
43
|
+
// click on the backdrop too, and that must not be read as "clicked outside".
|
|
44
|
+
this.pressStartedOnOverlay = false;
|
|
45
|
+
this.handleOverlayMouseDown = (event) => {
|
|
46
|
+
this.pressStartedOnOverlay = event.target === event.currentTarget;
|
|
47
|
+
};
|
|
48
|
+
this.handleOverlayClick = (event) => {
|
|
49
|
+
const startedOutside = this.pressStartedOnOverlay;
|
|
50
|
+
this.pressStartedOnOverlay = false;
|
|
51
|
+
if (!this.clickOutsideClose || !startedOutside || event.target !== event.currentTarget) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Closes the editor only. The feedback form reopens behind it, so a stray
|
|
55
|
+
// click never dismisses the whole widget
|
|
56
|
+
this.closeCanvasEditor();
|
|
57
|
+
};
|
|
11
58
|
this.handleWindowResize = () => {
|
|
12
59
|
// Debounce resize events
|
|
13
60
|
if (this.resizeTimeout) {
|
|
@@ -687,8 +734,16 @@ const CanvasEditor = class {
|
|
|
687
734
|
if (!this.isDrawing || !this.currentAnnotation)
|
|
688
735
|
return;
|
|
689
736
|
this.isDrawing = false;
|
|
690
|
-
|
|
737
|
+
const annotation = this.currentAnnotation;
|
|
691
738
|
this.currentAnnotation = null;
|
|
739
|
+
// A click with no drag never reaches mousemove, so the shape has no end
|
|
740
|
+
// point or size. Committing it added an invisible annotation that could
|
|
741
|
+
// not be selected, dragged or deleted, and it still consumed an undo step.
|
|
742
|
+
if (this.isDegenerateAnnotation(annotation)) {
|
|
743
|
+
this.redrawAnnotations();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
this.annotations = [...this.annotations, annotation];
|
|
692
747
|
this.redrawAnnotations();
|
|
693
748
|
};
|
|
694
749
|
// Convert screen coordinates to canvas coordinates
|
|
@@ -730,11 +785,28 @@ const CanvasEditor = class {
|
|
|
730
785
|
}
|
|
731
786
|
case 'line':
|
|
732
787
|
case 'arrow': {
|
|
733
|
-
// Distance from point to line
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
788
|
+
// Distance from the point to the line *segment*, not to the infinite
|
|
789
|
+
// line through it. The previous formula measured the infinite line, so
|
|
790
|
+
// a click far past either end still counted as a hit, and a zero-length
|
|
791
|
+
// segment divided by zero and gave NaN, leaving that annotation
|
|
792
|
+
// impossible to select, drag or delete.
|
|
793
|
+
// A click with no drag never reaches mousemove, so endX/endY are
|
|
794
|
+
// undefined rather than equal to the start. Treat that as a segment
|
|
795
|
+
// collapsed to its start point instead of letting NaN propagate.
|
|
796
|
+
const endX = Number.isFinite(annotation.endX) ? annotation.endX : annotation.startX;
|
|
797
|
+
const endY = Number.isFinite(annotation.endY) ? annotation.endY : annotation.startY;
|
|
798
|
+
const dx = endX - annotation.startX;
|
|
799
|
+
const dy = endY - annotation.startY;
|
|
800
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
801
|
+
// Zero-length segment: t stays 0 and we measure to the single point
|
|
802
|
+
let t = 0;
|
|
803
|
+
if (lengthSquared > 0) {
|
|
804
|
+
t = ((x - annotation.startX) * dx + (y - annotation.startY) * dy) / lengthSquared;
|
|
805
|
+
t = Math.max(0, Math.min(1, t));
|
|
806
|
+
}
|
|
807
|
+
const closestX = annotation.startX + t * dx;
|
|
808
|
+
const closestY = annotation.startY + t * dy;
|
|
809
|
+
const distance = Math.hypot(x - closestX, y - closestY);
|
|
738
810
|
return distance <= tolerance;
|
|
739
811
|
}
|
|
740
812
|
case 'text': {
|
|
@@ -759,6 +831,8 @@ const CanvasEditor = class {
|
|
|
759
831
|
this.screenshotTakingText = 'Taking screenshot...';
|
|
760
832
|
this.screenshotAttachedText = 'Screenshot attached';
|
|
761
833
|
this.screenshotButtonText = 'Add a screenshot';
|
|
834
|
+
this.escapeClose = true;
|
|
835
|
+
this.clickOutsideClose = true;
|
|
762
836
|
this.autoStartScreenshot = false;
|
|
763
837
|
this.existingScreenshot = '';
|
|
764
838
|
this.editTextButtonText = 'Edit Text';
|
|
@@ -814,10 +888,12 @@ const CanvasEditor = class {
|
|
|
814
888
|
// Add window resize listener for canvas adaptation
|
|
815
889
|
this.handleWindowResize = this.handleWindowResize.bind(this);
|
|
816
890
|
window.addEventListener('resize', this.handleWindowResize);
|
|
891
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
817
892
|
}
|
|
818
893
|
disconnectedCallback() {
|
|
819
894
|
// Clean up resize listener
|
|
820
895
|
window.removeEventListener('resize', this.handleWindowResize);
|
|
896
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
821
897
|
}
|
|
822
898
|
async captureViewportScreenshot() {
|
|
823
899
|
try {
|
|
@@ -841,6 +917,30 @@ const CanvasEditor = class {
|
|
|
841
917
|
video.autoplay = true;
|
|
842
918
|
video.muted = true;
|
|
843
919
|
return new Promise((resolve, reject) => {
|
|
920
|
+
// If neither onloadedmetadata nor onerror ever fires, the promise would
|
|
921
|
+
// never settle: the capture stream would stay live with the browser's
|
|
922
|
+
// recording indicator on, and the widget would sit in "Taking
|
|
923
|
+
// screenshot..." forever with no way back short of a page reload.
|
|
924
|
+
let settled = false;
|
|
925
|
+
const stopStream = () => {
|
|
926
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
927
|
+
};
|
|
928
|
+
const finish = (fn) => {
|
|
929
|
+
if (settled) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
settled = true;
|
|
933
|
+
clearTimeout(watchdog);
|
|
934
|
+
video.onloadedmetadata = null;
|
|
935
|
+
video.onerror = null;
|
|
936
|
+
fn();
|
|
937
|
+
};
|
|
938
|
+
const watchdog = setTimeout(() => {
|
|
939
|
+
finish(() => {
|
|
940
|
+
stopStream();
|
|
941
|
+
reject(new Error('Screenshot capture timed out'));
|
|
942
|
+
});
|
|
943
|
+
}, CAPTURE_TIMEOUT_MS);
|
|
844
944
|
video.onloadedmetadata = () => {
|
|
845
945
|
video.play();
|
|
846
946
|
// Wait a moment for video to stabilize
|
|
@@ -852,22 +952,26 @@ const CanvasEditor = class {
|
|
|
852
952
|
canvas.height = video.videoHeight;
|
|
853
953
|
const ctx = canvas.getContext('2d');
|
|
854
954
|
ctx.drawImage(video, 0, 0);
|
|
855
|
-
// Stop the stream
|
|
856
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
857
955
|
// Convert to data URL
|
|
858
956
|
const dataUrl = canvas.toDataURL('image/png');
|
|
859
|
-
|
|
860
|
-
|
|
957
|
+
finish(() => {
|
|
958
|
+
stopStream();
|
|
959
|
+
resolve(dataUrl);
|
|
960
|
+
});
|
|
861
961
|
}
|
|
862
962
|
catch (error) {
|
|
863
|
-
|
|
864
|
-
|
|
963
|
+
finish(() => {
|
|
964
|
+
stopStream();
|
|
965
|
+
reject(error);
|
|
966
|
+
});
|
|
865
967
|
}
|
|
866
968
|
}, 100);
|
|
867
969
|
};
|
|
868
970
|
video.onerror = () => {
|
|
869
|
-
|
|
870
|
-
|
|
971
|
+
finish(() => {
|
|
972
|
+
stopStream();
|
|
973
|
+
reject(new Error('Failed to load video for screenshot capture'));
|
|
974
|
+
});
|
|
871
975
|
};
|
|
872
976
|
});
|
|
873
977
|
}
|
|
@@ -876,9 +980,27 @@ const CanvasEditor = class {
|
|
|
876
980
|
throw error;
|
|
877
981
|
}
|
|
878
982
|
}
|
|
983
|
+
// Whether a freshly drawn shape is too small to be worth keeping
|
|
984
|
+
isDegenerateAnnotation(annotation) {
|
|
985
|
+
const MIN_SIZE = 2; // canvas pixels
|
|
986
|
+
if (annotation.type === 'line' || annotation.type === 'arrow') {
|
|
987
|
+
if (!Number.isFinite(annotation.endX) || !Number.isFinite(annotation.endY)) {
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
return (Math.hypot(annotation.endX - annotation.startX, annotation.endY - annotation.startY) <
|
|
991
|
+
MIN_SIZE);
|
|
992
|
+
}
|
|
993
|
+
if (annotation.type === 'rectangle') {
|
|
994
|
+
if (!Number.isFinite(annotation.width) || !Number.isFinite(annotation.height)) {
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
return Math.abs(annotation.width) < MIN_SIZE && Math.abs(annotation.height) < MIN_SIZE;
|
|
998
|
+
}
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
879
1001
|
render() {
|
|
880
1002
|
var _a, _b, _c, _d, _e, _f;
|
|
881
|
-
return (h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (h("div", { class: "canvas-editor-overlay", part: "overlay" }, h("div", { class: "canvas-editor-modal", part: "modal" }, h("div", { class: "canvas-editor-header", part: "header" }, h("div", { class: "canvas-editor-title", part: "title" }, h("h3",
|
|
1003
|
+
return (h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (h("div", { class: "canvas-editor-overlay", part: "overlay", onMouseDown: this.handleOverlayMouseDown, onClick: this.handleOverlayClick }, h("div", { class: "canvas-editor-modal", part: "modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "canvas-editor-title" }, h("div", { class: "canvas-editor-header", part: "header" }, h("div", { class: "canvas-editor-title", part: "title" }, h("h3", { id: "canvas-editor-title" }, this.canvasEditorTitle)), h("div", { class: "canvas-editor-toolbar", part: "toolbar" }, h("div", { class: "toolbar-section" }, h("div", { class: "tool-group" }, h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'rectangle'), title: "Rectangle" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-square" }, h("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'line'), title: "Line" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-minus" }, h("path", { d: "M5 12h14" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'arrow'), title: "Arrow" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-move-up-right" }, h("path", { d: "M13 5H19V11" }), h("path", { d: "M19 5L5 19" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'text'), title: "Text" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-type" }, h("path", { d: "M12 4v16" }), h("path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }), h("path", { d: "M9 20h6" }))), h("div", { class: "toolbar-divider" }), h("button", { class: "tool-btn undo-btn", part: "tool-button", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-undo" }, h("path", { d: "M3 7v6h6" }), h("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" }))), this.selectedAnnotation && (h("button", { class: "tool-btn delete-btn", part: "tool-button", onClick: this.deleteSelectedAnnotation, title: "Delete" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-trash-2" }, h("path", { d: "M3 6h18" }), h("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), h("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }), h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), h("line", { x1: "14", x2: "14", y1: "11", y2: "17" })))))), h("div", { class: "toolbar-section" }, h("div", { class: "color-palette" }, this.defaultColors.map((color, index) => (h("div", { class: "color-slot-wrapper" }, h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index), title: `Color ${index + 1} - Click to customize` }, this.editingColorIndex === index && (h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, h("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" })))), this.editingColorIndex === index && this.showColorPicker && (h("div", { class: "color-picker-dropdown" }, h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' ||
|
|
882
1004
|
(!this.selectedAnnotation && this.canvasDrawingTool === 'text')) && (h("div", { class: "text-controls" }, h("div", { class: "font-size-control" }, h("label", null, this.sizeLabelText), h("input", { type: "range", min: "8", max: "72", value: ((_b = this.selectedAnnotation) === null || _b === void 0 ? void 0 : _b.fontSize) || this.canvasTextSize, onInput: (e) => {
|
|
883
1005
|
const newSize = parseInt(e.target.value);
|
|
884
1006
|
if (this.selectedAnnotation) {
|
|
@@ -902,7 +1024,7 @@ const CanvasEditor = class {
|
|
|
902
1024
|
};
|
|
903
1025
|
CanvasEditor.style = canvasEditorCss;
|
|
904
1026
|
|
|
905
|
-
const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;letter-spacing:var(--feedback-button-letter-spacing);z-index:var(--feedback-button-z-index);font-family:var(--feedback-font-family)}.feedback-button-content--custom-font{font-family:inherit}.feedback-button-content--light{align-items:center;background-color:var(--feedback-button-light-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-light-text-color);display:flex;font-size:var(--feedback-button-text-font-size);font-weight:var(--feedback-button-text-font-weight);padding:8px 15px}.feedback-button-content--dark{align-items:center;background-color:var(--feedback-button-dark-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-dark-text-color);display:flex;font-weight:var(--feedback-button-text-font-weight);font-size:var(--feedback-button-text-font-size);padding:8px 15px}.icon-edit{stroke:var(--feedback-button-light-icon-color)}.feedback-button-content--dark .icon-edit{stroke:var(--feedback-button-dark-icon-color)}.feedback-button-content--bottom-right{bottom:10px;position:fixed;right:10px}.feedback-button-content--center-right{position:fixed;transform:rotate(-90deg) translateY(-50%);top:50%}.feedback-button-content--center-right.feedback-button-content--dark,.feedback-button-content--center-right.feedback-button-content--light{border-radius:4px;border-bottom-left-radius:0px;border-bottom-right-radius:0px}.feedback-button-content-icon{height:16px;margin-right:5px;width:16px}.feedback-button-content--center-right .feedback-button-content-icon{rotate:90deg}@media screen and (max-width: 767px){.feedback-button-content--hide-mobile{display:none}}";
|
|
1027
|
+
const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;letter-spacing:var(--feedback-button-letter-spacing);z-index:var(--feedback-button-z-index);font-family:var(--feedback-font-family);background:none;border:none;margin:0;padding:0;color:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;text-align:inherit}.feedback-button-content--custom-font{font-family:inherit}.feedback-button-content--light{align-items:center;background-color:var(--feedback-button-light-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-light-text-color);display:flex;font-size:var(--feedback-button-text-font-size);font-weight:var(--feedback-button-text-font-weight);padding:8px 15px}.feedback-button-content--dark{align-items:center;background-color:var(--feedback-button-dark-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-dark-text-color);display:flex;font-weight:var(--feedback-button-text-font-weight);font-size:var(--feedback-button-text-font-size);padding:8px 15px}.icon-edit{stroke:var(--feedback-button-light-icon-color)}.feedback-button-content--dark .icon-edit{stroke:var(--feedback-button-dark-icon-color)}.feedback-button-content--bottom-right{bottom:10px;position:fixed;right:10px}.feedback-button-content--bottom-left{bottom:10px;left:10px;position:fixed}.feedback-button-content--top-right{position:fixed;right:10px;top:10px}.feedback-button-content--top-left{left:10px;position:fixed;top:10px}.feedback-button-content--center-right{position:fixed;transform:rotate(-90deg) translateY(-50%);top:50%}.feedback-button-content--center-left{position:fixed;transform:rotate(90deg) translateY(-50%);top:50%}.feedback-button-content--center-right.feedback-button-content--dark,.feedback-button-content--center-right.feedback-button-content--light,.feedback-button-content--center-left.feedback-button-content--dark,.feedback-button-content--center-left.feedback-button-content--light{border-radius:4px;border-bottom-left-radius:0px;border-bottom-right-radius:0px}.feedback-button-content-icon{height:16px;margin-right:5px;width:16px}.feedback-button-content--center-right .feedback-button-content-icon{rotate:90deg}.feedback-button-content--center-left .feedback-button-content-icon{rotate:-90deg}@media screen and (max-width: 767px){.feedback-button-content--hide-mobile{display:none}}";
|
|
906
1028
|
|
|
907
1029
|
const FeedbackButton = class {
|
|
908
1030
|
constructor(hostRef) {
|
|
@@ -916,8 +1038,11 @@ const FeedbackButton = class {
|
|
|
916
1038
|
this.sessionId = '';
|
|
917
1039
|
this.metadata = '';
|
|
918
1040
|
this.submit = false;
|
|
1041
|
+
this.apiKey = '';
|
|
1042
|
+
this.clickOutsideClose = true;
|
|
919
1043
|
this.customFont = false;
|
|
920
1044
|
this.emailAddress = '';
|
|
1045
|
+
this.historyClose = true;
|
|
921
1046
|
this.isEmailRequired = false;
|
|
922
1047
|
this.fetchData = true;
|
|
923
1048
|
this.hideEmail = false;
|
|
@@ -930,6 +1055,7 @@ const FeedbackButton = class {
|
|
|
930
1055
|
this.ratingMode = 'thumbs';
|
|
931
1056
|
this.emailPlaceholder = 'Email address (optional)';
|
|
932
1057
|
this.errorMessage = 'Please try again later.';
|
|
1058
|
+
this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
|
|
933
1059
|
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
934
1060
|
this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
|
|
935
1061
|
this.footerText = '';
|
|
@@ -975,13 +1101,19 @@ const FeedbackButton = class {
|
|
|
975
1101
|
}
|
|
976
1102
|
}
|
|
977
1103
|
componentDidLoad() {
|
|
978
|
-
if (this.buttonPosition === 'center-right') {
|
|
1104
|
+
if (this.buttonPosition === 'center-right' || this.buttonPosition === 'center-left') {
|
|
979
1105
|
const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
|
|
980
1106
|
let adjustement = 0;
|
|
981
1107
|
if (this.isSafariBrowser()) {
|
|
982
1108
|
adjustement = 5;
|
|
983
1109
|
}
|
|
984
|
-
|
|
1110
|
+
const offset = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
|
|
1111
|
+
if (this.buttonPosition === 'center-right') {
|
|
1112
|
+
buttonContent.style.right = offset;
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
buttonContent.style.left = offset;
|
|
1116
|
+
}
|
|
985
1117
|
}
|
|
986
1118
|
if (!this.customFont) {
|
|
987
1119
|
this.loadInterFont();
|
|
@@ -990,9 +1122,12 @@ const FeedbackButton = class {
|
|
|
990
1122
|
connectedCallback() {
|
|
991
1123
|
this.feedbackModal = document.createElement('feedback-modal');
|
|
992
1124
|
const props = [
|
|
1125
|
+
'apiKey',
|
|
1126
|
+
'clickOutsideClose',
|
|
993
1127
|
'customFont',
|
|
994
1128
|
'emailAddress',
|
|
995
1129
|
'fetchData',
|
|
1130
|
+
'historyClose',
|
|
996
1131
|
'hideEmail',
|
|
997
1132
|
'hidePrivacyPolicy',
|
|
998
1133
|
'hideRating',
|
|
@@ -1018,6 +1153,7 @@ const FeedbackButton = class {
|
|
|
1018
1153
|
'screenshotErrorUnexpected',
|
|
1019
1154
|
'emailPlaceholder',
|
|
1020
1155
|
'errorMessage',
|
|
1156
|
+
'errorMessage401',
|
|
1021
1157
|
'errorMessage403',
|
|
1022
1158
|
'errorMessage404',
|
|
1023
1159
|
'footerText',
|
|
@@ -1082,12 +1218,16 @@ const FeedbackButton = class {
|
|
|
1082
1218
|
}
|
|
1083
1219
|
}
|
|
1084
1220
|
const body = Object.assign({ url: window.location.href, project: this.project, rating: this.rating || -1, ratingMode: this.ratingMode, message: '', metadata: this.metadata, session: localStorage.getItem('pushfeedback_sessionid') || '' }, (recaptchaToken && { recaptchaToken }));
|
|
1085
|
-
const
|
|
1221
|
+
const headers = {
|
|
1222
|
+
'Content-Type': 'application/json',
|
|
1223
|
+
};
|
|
1224
|
+
if (this.apiKey) {
|
|
1225
|
+
headers['Authorization'] = `Api-Key ${this.apiKey}`;
|
|
1226
|
+
}
|
|
1227
|
+
const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
|
|
1086
1228
|
method: 'POST',
|
|
1087
1229
|
body: JSON.stringify(body),
|
|
1088
|
-
headers
|
|
1089
|
-
'Content-Type': 'application/json',
|
|
1090
|
-
},
|
|
1230
|
+
headers,
|
|
1091
1231
|
});
|
|
1092
1232
|
if (res.status === 201) {
|
|
1093
1233
|
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
@@ -1118,19 +1258,139 @@ const FeedbackButton = class {
|
|
|
1118
1258
|
}
|
|
1119
1259
|
}
|
|
1120
1260
|
render() {
|
|
1121
|
-
return (h(Host, null, h("
|
|
1261
|
+
return (h(Host, null, h("button", { type: "button", class: `feedback-button-content feedback-button-content--${this.buttonStyle} feedback-button-content--${this.buttonPosition} ${this.customFont ? 'feedback-button-content--custom-font' : ''} ${this.hideMobile ? 'feedback-button-content--hide-mobile' : ''}`, part: "button", onClick: () => this.showModal() }, !this.hideIcon && this.buttonStyle != 'default' && (h("span", { class: "feedback-button-content-icon", part: "icon" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "icon-edit" }, h("path", { d: "M12 20h9" }), h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), h("slot", null))));
|
|
1122
1262
|
}
|
|
1123
1263
|
get el() { return getElement(this); }
|
|
1124
1264
|
};
|
|
1125
1265
|
FeedbackButton.style = feedbackButtonCss;
|
|
1126
1266
|
|
|
1127
|
-
const feedbackModalCss = ".text-center{flex-grow:1;text-align:center}.feedback-modal-wrapper *{font-family:var(--feedback-font-family)}.feedback-modal-wrapper--custom-font *{font-family:inherit}.feedback-modal-wrapper{position:absolute;z-index:var(--feedback-modal-modal-wrapper-z-index)}.feedback-overlay{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;opacity:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index);transition:opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border:1px solid rgba(0, 0, 0, 0.08);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 0px 0px 1px rgba(0, 0, 0, 0.02),\n 0px 2px 4px rgba(0, 0, 0, 0.04),\n 0px 8px 16px rgba(0, 0, 0, 0.06),\n 0px 16px 32px rgba(0, 0, 0, 0.04);box-sizing:border-box;color:var(--feedback-modal-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:24px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.96);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1)}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-header{color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-modal-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;align-items:flex-start;margin-bottom:24px;letter-spacing:-0.01em}.feedback-modal-header-content{flex:1}.feedback-modal-header-text{display:flex;flex-direction:column;gap:6px}.feedback-modal-title{display:block}.feedback-modal-powered-by{display:block;font-size:13px;font-weight:400;color:var(--feedback-color-gray-500);line-height:1.4}.feedback-modal-powered-by a{color:var(--feedback-modal-header-text-color);text-decoration:none;font-weight:500}.feedback-modal-powered-by a:hover{text-decoration:underline}.feedback-modal-header--no-content{margin-bottom:0}.feedback-modal-rating-buttons{width:100%;margin-bottom:24px;display:flex;gap:4px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:8px;cursor:pointer}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button{transition:transform 0.15s ease, opacity 0.15s ease}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button:hover{transform:scale(1.1)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);font-size:var(--feedback-modal-button-font-size);font-weight:500;margin-right:12px;justify-content:center;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive{background-color:var(--feedback-modal-rating-button-positive-bg-color);border-color:var(--feedback-modal-rating-button-positive-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative{background-color:var(--feedback-modal-rating-button-negative-bg-color);border-color:var(--feedback-modal-rating-button-negative-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover{transform:translateY(-2px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-selected-color)}.feedback-modal-rating-buttons svg{stroke:var(--feedback-modal-rating-button-color);cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive svg{stroke:var(--feedback-modal-rating-button-positive-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative svg{stroke:var(--feedback-modal-rating-button-negative-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-positive-selected-bg-color);border-color:var(--feedback-modal-rating-button-positive-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-negative-selected-bg-color);border-color:var(--feedback-modal-rating-button-negative-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-positive-selected-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-negative-selected-color)}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button--selected svg{fill:var(--feedback-modal-rating-button-stars-selected-color);stroke:var(--feedback-modal-rating-button-stars-selected-color)}.feedback-modal-text textarea{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:100px;min-height:100px;padding:12px;resize:vertical;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease;line-height:1.5}.feedback-modal-text textarea:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:44px;padding:12px;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease}.feedback-modal-email input:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-text textarea:-webkit-autofill,.feedback-modal-email input:-webkit-autofill,.feedback-modal-email input:-webkit-autofill:hover,.feedback-modal-email input:-webkit-autofill:focus,.feedback-modal-email input:-webkit-autofill:active{-webkit-box-shadow:0 0 0 1000px var(--feedback-modal-input-bg-color) inset !important;-webkit-text-fill-color:var(--feedback-modal-input-text-color) !important;transition:background-color 5000s ease-in-out 0s}.feedback-modal-privacy{font-size:var(--feedback-modal-input-font-size);margin-bottom:20px}.feedback-modal-text textarea:focus,.feedback-modal-email input:focus{border:1.5px solid var(--feedback-modal-input-border-color-focused);outline:none;box-shadow:0 0 0 3px rgba(59, 130, 246, 0.1)}.feedback-modal-buttons{display:flex;flex-direction:column}.feedback-modal-buttons .feedback-modal-button{margin-bottom:20px}.feedback-modal-button{align-items:center;background-color:transparent;border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);cursor:pointer;display:flex;font-size:var(--feedback-modal-button-font-size);font-weight:500;justify-content:center;min-height:44px;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-button svg{margin-right:6px}.feedback-modal-button path{fill:var(--feedback-modal-button-icon-color)}.feedback-modal-button:hover path,.feedback-modal-button--active path{fill:var(--feedback-modal-button-icon-color-active)}.feedback-modal-button--submit{background-color:var(--feedback-modal-button-submit-bg-color);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1.5px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover);transform:translateY(-1px);box-shadow:0 6px 12px rgba(0, 0, 0, 0.15)}.feedback-modal-button--submit:active{transform:translateY(0);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:500;padding-bottom:12px;color:var(--feedback-modal-header-text-color);letter-spacing:-0.01em}.feedback-modal-footer{font-size:12px;text-align:center}.feedback-modal-footer a{color:var(--feedback-modal-footer-link);font-weight:500;text-decoration:none}.feedback-logo,.feedback-footer-text{display:block;text-align:center;margin-top:5px}.feedback-footer-text{margin-top:10px;line-height:1.5}.feedback-footer-combined{display:block;text-align:center;font-size:11px;color:#666;line-height:1.5}.feedback-footer-combined a{color:#666;text-decoration:underline}.feedback-recaptcha-notice{display:block;text-align:center;margin-bottom:10px;font-size:11px;color:#666;line-height:1.4}.feedback-recaptcha-notice a{color:#666;text-decoration:underline}.feedback-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:6px;cursor:pointer;height:32px;width:32px;margin-left:auto;padding:0;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease}.feedback-modal-close:hover{background-color:rgba(0, 0, 0, 0.06);transform:scale(1.05)}.feedback-modal-close:active{transform:scale(0.95)}.feedback-modal-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-screenshot{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index)}.feedback-modal-screenshot-header{align-items:center;background-color:var(--feedback-modal-screenshot-header-bg-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-screenshot-header-text-color);cursor:pointer;display:flex;left:50%;top:20px;transform:translateX(-50%);padding:10px;position:fixed;width:max-content;z-index:var(--feedback-modal-screenshot-header-z-index)}.feedback-modal-screenshot-close{height:24px;padding-left:10px;width:24px}.feedback-modal-screenshot-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-message{font-size:var(--feedback-modal-message-font-size);margin:0;line-height:1.6;color:var(--feedback-modal-content-text-color)}.feedback-modal-element-hover{background-color:transparent;cursor:pointer;border:1px solid var(--feedback-modal-element-hover-border-color)}.feedback-modal-element-selected{background-color:transparent;border:3px solid var(--feedback-modal-element-selected-border-color) !important;box-shadow:0 0 0 2px rgba(0, 123, 255, 0.3) !important}.screenshot-preview{display:inline-block;width:32px;height:32px;overflow:hidden;border-radius:6px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);border:2px solid rgba(255, 255, 255, 0.8)}.screenshot-preview:hover{transform:scale(1.15);box-shadow:0 4px 8px rgba(0, 0, 0, 0.15)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}@media screen and (min-width: 768px){.feedback-modal-content{max-width:var(--feedback-modal-content-max-width)}.feedback-modal-content.feedback-modal-content--bottom-right{bottom:var(--feedback-modal-content-position-bottom);left:initial;right:var(--feedback-modal-content-position-right);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--bottom-left{bottom:var(--feedback-modal-content-position-bottom);left:var(--feedback-modal-content-position-left);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--top-right{right:var(--feedback-modal-content-position-right);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--top-left{left:var(--feedback-modal-content-position-left);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--center-left{left:5px;right:auto;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--center-right{left:auto;right:5px;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--sidebar-left.feedback-modal-content--open,.feedback-modal-content.feedback-modal-content--sidebar-right.feedback-modal-content--open{transform:translateX(0)}.feedback-modal-content.feedback-modal-content--sidebar-left{max-width:var(--feedback-modal-content-sidebar-max-width);left:0;right:auto;height:100vh;top:0;transform:translateX(-100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-content.feedback-modal-content--sidebar-right{max-width:var(--feedback-modal-content-sidebar-max-width);left:auto;right:0;height:100vh;top:0;transform:translateX(100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-text textarea{height:150px;min-height:150px}.feedback-modal-content.feedback-modal-content--bottom-right{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--bottom-left{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-left.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-right{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-left{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-left.feedback-modal-content--open{transform:translateY(0)}}@keyframes feather-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.feather-loader{animation:feather-spin 1s linear infinite;display:block}.screenshot-error-notification{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10001;max-width:500px;width:90%;animation:slideDown 0.3s ease-out}@keyframes slideDown{from{opacity:0;transform:translateX(-50%) translateY(-20px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.screenshot-error-content{background:#fee;border:1.5px solid #fcc;border-radius:10px;padding:14px 18px;display:flex;align-items:center;gap:12px;box-shadow:0 0 0 1px rgba(197, 48, 48, 0.05),\n 0 4px 6px rgba(0, 0, 0, 0.07),\n 0 10px 20px rgba(0, 0, 0, 0.1);color:#c53030}.screenshot-error-content svg:first-child{color:#e53e3e;flex-shrink:0}.screenshot-error-content span{flex:1;font-size:14px;line-height:1.4;font-weight:500}.error-close-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:6px;color:#c53030;flex-shrink:0;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center}.error-close-btn:hover{background:rgba(197, 48, 48, 0.15);transform:scale(1.1)}.error-close-btn:active{transform:scale(0.95)}@media screen and (max-width: 768px){.feedback-modal-content{width:100vw;height:100dvh;max-width:none;border-radius:0;top:0;left:0;transform:scale(0.95);padding:24px 20px;display:flex;flex-direction:column}.feedback-modal-content--open{transform:scale(1)}.feedback-modal-buttons{display:flex;flex-direction:column;gap:12px}.feedback-modal-button--screenshot{display:none !important}.feedback-modal-button--submit{width:100%}.feedback-modal-text textarea{flex:1;min-height:120px;resize:none}}.feedback-modal-wrapper--embedded{position:relative;z-index:auto;display:block;width:100%}.feedback-modal-content--embedded{position:relative !important;left:auto !important;right:auto !important;top:auto !important;bottom:auto !important;transform:none !important;opacity:1 !important;max-width:100%;margin:0 auto;box-shadow:0 2px 8px rgba(0, 0, 0, 0.1);text-align:left}.feedback-modal-content--embedded.feedback-modal-content--open{transform:none !important}@media screen and (max-width: 768px){.feedback-modal-content--embedded{width:100%;height:auto;border-radius:var(--feedback-modal-content-border-radius);padding:24px}.feedback-modal-content--embedded .feedback-modal-button--screenshot{display:flex !important}}";
|
|
1267
|
+
// Tags allowed in the rich-text props (privacy policy, reCAPTCHA notice,
|
|
1268
|
+
// footer). These only ever need links and light inline emphasis.
|
|
1269
|
+
const ALLOWED_TAGS = new Set([
|
|
1270
|
+
'A',
|
|
1271
|
+
'B',
|
|
1272
|
+
'BR',
|
|
1273
|
+
'CODE',
|
|
1274
|
+
'EM',
|
|
1275
|
+
'I',
|
|
1276
|
+
'P',
|
|
1277
|
+
'SMALL',
|
|
1278
|
+
'SPAN',
|
|
1279
|
+
'STRONG',
|
|
1280
|
+
'U',
|
|
1281
|
+
]);
|
|
1282
|
+
// No `class`: an injected class name cannot reach the host page's CSS (this
|
|
1283
|
+
// renders inside a shadow root), but it could match the widget's own internal
|
|
1284
|
+
// styles. The default prop values do not use classes, so dropping it is free.
|
|
1285
|
+
const ALLOWED_ATTRS = new Set(['href', 'target', 'rel', 'title']);
|
|
1286
|
+
// Schemes permitted in href. Anything else, notably javascript:, is dropped.
|
|
1287
|
+
const SAFE_URL = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
|
|
1288
|
+
/**
|
|
1289
|
+
* Removes anything that can execute from a fragment: script and style
|
|
1290
|
+
* elements, event-handler attributes, javascript: URLs, and any tag not on the
|
|
1291
|
+
* allowlist. Text inside a disallowed tag is kept, so stripping a wrapper does
|
|
1292
|
+
* not silently delete the words inside it.
|
|
1293
|
+
*/
|
|
1294
|
+
function scrub(root) {
|
|
1295
|
+
const elements = Array.from(root.querySelectorAll('*'));
|
|
1296
|
+
for (const el of elements) {
|
|
1297
|
+
// The node may already have been removed along with an ancestor
|
|
1298
|
+
if (!root.contains(el)) {
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const tag = el.tagName.toUpperCase();
|
|
1302
|
+
if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'IFRAME' || tag === 'OBJECT') {
|
|
1303
|
+
el.remove();
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
1307
|
+
// Unwrap: keep the text, drop the element
|
|
1308
|
+
el.replaceWith(...Array.from(el.childNodes));
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1312
|
+
const name = attr.name.toLowerCase();
|
|
1313
|
+
if (name.startsWith('on') || !ALLOWED_ATTRS.has(name)) {
|
|
1314
|
+
el.removeAttribute(attr.name);
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
if (name === 'href' && !SAFE_URL.test(attr.value.trim())) {
|
|
1318
|
+
el.removeAttribute(attr.name);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
// Links opening a new tab must not hand the opener over
|
|
1322
|
+
if (tag === 'A' && el.getAttribute('target') === '_blank') {
|
|
1323
|
+
el.setAttribute('rel', 'noopener noreferrer');
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Sanitizes a fragment of HTML for use with innerHTML.
|
|
1329
|
+
*
|
|
1330
|
+
* Prefers the browser's native Element.setHTML() where available, which parses
|
|
1331
|
+
* without executing. Otherwise parses in an inert document (no scripts run, no
|
|
1332
|
+
* images or other subresources are fetched) and scrubs the result.
|
|
1333
|
+
*/
|
|
1334
|
+
function sanitizeHtml(html) {
|
|
1335
|
+
if (!html) {
|
|
1336
|
+
return '';
|
|
1337
|
+
}
|
|
1338
|
+
const template = document.createElement('template');
|
|
1339
|
+
// Native sanitizer, when the browser has it
|
|
1340
|
+
const withSetHtml = template;
|
|
1341
|
+
if (typeof withSetHtml.setHTML === 'function') {
|
|
1342
|
+
try {
|
|
1343
|
+
withSetHtml.setHTML(html);
|
|
1344
|
+
scrub(template.content);
|
|
1345
|
+
return template.innerHTML;
|
|
1346
|
+
}
|
|
1347
|
+
catch (_a) {
|
|
1348
|
+
// Fall through to the manual path
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
// Inert parsing: content inside <template> is not rendered, so scripts do not
|
|
1352
|
+
// run and no subresource requests are made while we clean it up.
|
|
1353
|
+
template.innerHTML = html;
|
|
1354
|
+
scrub(template.content);
|
|
1355
|
+
return template.innerHTML;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
const feedbackModalCss = ".text-center{flex-grow:1;text-align:center}.feedback-modal-wrapper *{font-family:var(--feedback-font-family)}.feedback-modal-wrapper--custom-font *{font-family:inherit}.feedback-modal-wrapper{position:absolute;z-index:var(--feedback-modal-modal-wrapper-z-index)}.feedback-overlay{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;opacity:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index);transition:opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border:1px solid rgba(0, 0, 0, 0.08);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 0px 0px 1px rgba(0, 0, 0, 0.02),\n 0px 2px 4px rgba(0, 0, 0, 0.04),\n 0px 8px 16px rgba(0, 0, 0, 0.06),\n 0px 16px 32px rgba(0, 0, 0, 0.04);box-sizing:border-box;color:var(--feedback-modal-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:24px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.96);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1)}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-content:focus{outline:none}.feedback-modal-header{color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-modal-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;align-items:flex-start;margin-bottom:24px;letter-spacing:-0.01em}.feedback-modal-header-content{flex:1}.feedback-modal-header-text{display:flex;flex-direction:column;gap:6px}.feedback-modal-title{display:block}.feedback-modal-powered-by{display:block;font-size:13px;font-weight:400;color:var(--feedback-color-gray-500);line-height:1.4}.feedback-modal-powered-by a{color:var(--feedback-modal-header-text-color);text-decoration:none;font-weight:500}.feedback-modal-powered-by a:hover{text-decoration:underline}.feedback-modal-header--no-content{margin-bottom:0}.feedback-modal-rating-buttons{width:100%;margin-bottom:24px;display:flex;gap:4px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:8px;cursor:pointer}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button{transition:transform 0.15s ease, opacity 0.15s ease}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button:hover{transform:scale(1.1)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);font-size:var(--feedback-modal-button-font-size);font-weight:500;margin-right:12px;justify-content:center;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive{background-color:var(--feedback-modal-rating-button-positive-bg-color);border-color:var(--feedback-modal-rating-button-positive-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative{background-color:var(--feedback-modal-rating-button-negative-bg-color);border-color:var(--feedback-modal-rating-button-negative-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover{transform:translateY(-2px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-selected-color)}.feedback-modal-rating-buttons svg{stroke:var(--feedback-modal-rating-button-color);cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive svg{stroke:var(--feedback-modal-rating-button-positive-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative svg{stroke:var(--feedback-modal-rating-button-negative-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-positive-selected-bg-color);border-color:var(--feedback-modal-rating-button-positive-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-negative-selected-bg-color);border-color:var(--feedback-modal-rating-button-negative-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-positive-selected-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-negative-selected-color)}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button--selected svg{fill:var(--feedback-modal-rating-button-stars-selected-color);stroke:var(--feedback-modal-rating-button-stars-selected-color)}.feedback-modal-text textarea{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:100px;min-height:100px;padding:12px;resize:vertical;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease;line-height:1.5}.feedback-modal-text textarea:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:44px;padding:12px;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease}.feedback-modal-email input:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-text textarea:-webkit-autofill,.feedback-modal-email input:-webkit-autofill,.feedback-modal-email input:-webkit-autofill:hover,.feedback-modal-email input:-webkit-autofill:focus,.feedback-modal-email input:-webkit-autofill:active{-webkit-box-shadow:0 0 0 1000px var(--feedback-modal-input-bg-color) inset !important;-webkit-text-fill-color:var(--feedback-modal-input-text-color) !important;transition:background-color 5000s ease-in-out 0s}.feedback-modal-privacy{font-size:var(--feedback-modal-input-font-size);margin-bottom:20px}.feedback-modal-text textarea:focus,.feedback-modal-email input:focus{border:1.5px solid var(--feedback-modal-input-border-color-focused);outline:none;box-shadow:0 0 0 3px rgba(59, 130, 246, 0.1)}.feedback-modal-buttons{display:flex;flex-direction:column}.feedback-modal-buttons .feedback-modal-button{margin-bottom:20px}.feedback-modal-button{align-items:center;background-color:transparent;border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);cursor:pointer;display:flex;font-size:var(--feedback-modal-button-font-size);font-weight:500;justify-content:center;min-height:44px;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-button svg{margin-right:6px}.feedback-modal-button path{fill:var(--feedback-modal-button-icon-color)}.feedback-modal-button:hover path,.feedback-modal-button--active path{fill:var(--feedback-modal-button-icon-color-active)}.feedback-modal-button--submit{background-color:var(--feedback-modal-button-submit-bg-color);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1.5px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover);transform:translateY(-1px);box-shadow:0 6px 12px rgba(0, 0, 0, 0.15)}.feedback-modal-button--submit:active{transform:translateY(0);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:500;padding-bottom:12px;color:var(--feedback-modal-header-text-color);letter-spacing:-0.01em}.feedback-modal-footer{font-size:12px;text-align:center}.feedback-modal-footer a{color:var(--feedback-modal-footer-link);font-weight:500;text-decoration:none}.feedback-logo,.feedback-footer-text{display:block;text-align:center;margin-top:5px}.feedback-footer-text{margin-top:10px;line-height:1.5}.feedback-footer-combined{display:block;text-align:center;font-size:11px;color:#666;line-height:1.5}.feedback-footer-combined a{color:#666;text-decoration:underline}.feedback-recaptcha-notice{display:block;text-align:center;margin-bottom:10px;font-size:11px;color:#666;line-height:1.4}.feedback-recaptcha-notice a{color:#666;text-decoration:underline}.feedback-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:6px;cursor:pointer;height:32px;width:32px;margin-left:auto;padding:0;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease}.feedback-modal-close:hover{background-color:rgba(0, 0, 0, 0.06);transform:scale(1.05)}.feedback-modal-close:active{transform:scale(0.95)}.feedback-modal-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-screenshot{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index)}.feedback-modal-screenshot-header{align-items:center;background-color:var(--feedback-modal-screenshot-header-bg-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-screenshot-header-text-color);cursor:pointer;display:flex;left:50%;top:20px;transform:translateX(-50%);padding:10px;position:fixed;width:max-content;z-index:var(--feedback-modal-screenshot-header-z-index)}.feedback-modal-screenshot-close{height:24px;padding-left:10px;width:24px}.feedback-modal-screenshot-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-message{font-size:var(--feedback-modal-message-font-size);margin:0;line-height:1.6;color:var(--feedback-modal-content-text-color)}.feedback-modal-element-hover{background-color:transparent;cursor:pointer;border:1px solid var(--feedback-modal-element-hover-border-color)}.feedback-modal-element-selected{background-color:transparent;border:3px solid var(--feedback-modal-element-selected-border-color) !important;box-shadow:0 0 0 2px rgba(0, 123, 255, 0.3) !important}.screenshot-preview{display:inline-block;width:32px;height:32px;overflow:hidden;border-radius:6px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);border:2px solid rgba(255, 255, 255, 0.8)}.screenshot-preview:hover{transform:scale(1.15);box-shadow:0 4px 8px rgba(0, 0, 0, 0.15)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}@media screen and (min-width: 768px){.feedback-modal-content{max-width:var(--feedback-modal-content-max-width)}.feedback-modal-content.feedback-modal-content--bottom-right{bottom:var(--feedback-modal-content-position-bottom);left:initial;right:var(--feedback-modal-content-position-right);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--bottom-left{bottom:var(--feedback-modal-content-position-bottom);left:var(--feedback-modal-content-position-left);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--top-right{right:var(--feedback-modal-content-position-right);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--top-left{left:var(--feedback-modal-content-position-left);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--center-left{left:5px;right:auto;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--center-right{left:auto;right:5px;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--sidebar-left.feedback-modal-content--open,.feedback-modal-content.feedback-modal-content--sidebar-right.feedback-modal-content--open{transform:translateX(0)}.feedback-modal-content.feedback-modal-content--sidebar-left{max-width:var(--feedback-modal-content-sidebar-max-width);left:0;right:auto;height:100vh;top:0;transform:translateX(-100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-content.feedback-modal-content--sidebar-right{max-width:var(--feedback-modal-content-sidebar-max-width);left:auto;right:0;height:100vh;top:0;transform:translateX(100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-text textarea{height:150px;min-height:150px}.feedback-modal-content.feedback-modal-content--bottom-right{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--bottom-left{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-left.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-right{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-left{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-left.feedback-modal-content--open{transform:translateY(0)}}@keyframes feather-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.feather-loader{animation:feather-spin 1s linear infinite;display:block}.screenshot-error-notification{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10001;max-width:500px;width:90%;animation:slideDown 0.3s ease-out}@keyframes slideDown{from{opacity:0;transform:translateX(-50%) translateY(-20px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.screenshot-error-content{background:#fee;border:1.5px solid #fcc;border-radius:10px;padding:14px 18px;display:flex;align-items:center;gap:12px;box-shadow:0 0 0 1px rgba(197, 48, 48, 0.05),\n 0 4px 6px rgba(0, 0, 0, 0.07),\n 0 10px 20px rgba(0, 0, 0, 0.1);color:#c53030}.screenshot-error-content svg:first-child{color:#e53e3e;flex-shrink:0}.screenshot-error-content span{flex:1;font-size:14px;line-height:1.4;font-weight:500}.error-close-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:6px;color:#c53030;flex-shrink:0;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center}.error-close-btn:hover{background:rgba(197, 48, 48, 0.15);transform:scale(1.1)}.error-close-btn:active{transform:scale(0.95)}@media screen and (max-width: 768px){.feedback-modal-content{width:100vw;height:100dvh;max-width:none;border-radius:0;top:0;left:0;transform:scale(0.95);padding:24px 20px;display:flex;flex-direction:column}.feedback-modal-content--open{transform:scale(1)}.feedback-modal-buttons{display:flex;flex-direction:column;gap:12px}.feedback-modal-button--screenshot{display:none !important}.feedback-modal-button--submit{width:100%}.feedback-modal-text textarea{flex:1;min-height:120px;resize:none}}.feedback-modal-wrapper--embedded{position:relative;z-index:auto;display:block;width:100%}.feedback-modal-content--embedded{position:relative !important;left:auto !important;right:auto !important;top:auto !important;bottom:auto !important;transform:none !important;opacity:1 !important;max-width:100%;margin:0 auto;box-shadow:0 2px 8px rgba(0, 0, 0, 0.1);text-align:left}.feedback-modal-content--embedded.feedback-modal-content--open{transform:none !important}@media screen and (max-width: 768px){.feedback-modal-content--embedded{width:100%;height:auto;border-radius:var(--feedback-modal-content-border-radius);padding:24px}.feedback-modal-content--embedded .feedback-modal-button--screenshot{display:flex !important}}";
|
|
1128
1359
|
|
|
1129
1360
|
const FeedbackModal = class {
|
|
1130
1361
|
constructor(hostRef) {
|
|
1131
1362
|
registerInstance(this, hostRef);
|
|
1132
1363
|
this.feedbackSent = createEvent(this, "feedbackSent", 7);
|
|
1133
1364
|
this.feedbackError = createEvent(this, "feedbackError", 7);
|
|
1365
|
+
// Track the history entry pushed while the modal is open
|
|
1366
|
+
this.historyEntryPushed = false;
|
|
1367
|
+
// Element that had focus before the modal opened, so it can be restored on close
|
|
1368
|
+
this.previouslyFocused = null;
|
|
1369
|
+
this.handleKeyDown = (event) => {
|
|
1370
|
+
// The canvas editor renders its own overlay and handles its own keys
|
|
1371
|
+
if (!this.showModal || this.embedded || this.showCanvasEditor) {
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (event.key === 'Escape' && this.escapeClose) {
|
|
1375
|
+
event.preventDefault();
|
|
1376
|
+
// Escape is an accidental-dismissal path like clicking outside, so keep
|
|
1377
|
+
// whatever the user already typed
|
|
1378
|
+
this.doClose(!this.formSuccess);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (event.key === 'Tab') {
|
|
1382
|
+
this.trapFocus(event);
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
this.handlePopState = () => {
|
|
1386
|
+
// Back button pressed while the modal is open: close it and consume our entry
|
|
1387
|
+
if (this.historyEntryPushed) {
|
|
1388
|
+
this.historyEntryPushed = false;
|
|
1389
|
+
if (this.showModal) {
|
|
1390
|
+
this.close();
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1134
1394
|
this.onScrollDebounced = () => {
|
|
1135
1395
|
clearTimeout(this.scrollTimeout);
|
|
1136
1396
|
this.scrollTimeout = setTimeout(() => {
|
|
@@ -1165,12 +1425,16 @@ const FeedbackModal = class {
|
|
|
1165
1425
|
}
|
|
1166
1426
|
}
|
|
1167
1427
|
const body = Object.assign({ url: window.location.href, message: this.formMessage, email: this.formEmail, project: this.project, screenshot: this.encodedScreenshot, rating: this.selectedRating, ratingMode: this.ratingMode, metadata: this.metadata, verification: this.formVerification, session: localStorage.getItem('pushfeedback_sessionid') || '' }, (recaptchaToken && { recaptchaToken }));
|
|
1168
|
-
const
|
|
1428
|
+
const headers = {
|
|
1429
|
+
'Content-Type': 'application/json',
|
|
1430
|
+
};
|
|
1431
|
+
if (this.apiKey) {
|
|
1432
|
+
headers['Authorization'] = `Api-Key ${this.apiKey}`;
|
|
1433
|
+
}
|
|
1434
|
+
const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
|
|
1169
1435
|
method: 'POST',
|
|
1170
1436
|
body: JSON.stringify(body),
|
|
1171
|
-
headers
|
|
1172
|
-
'Content-Type': 'application/json',
|
|
1173
|
-
},
|
|
1437
|
+
headers,
|
|
1174
1438
|
});
|
|
1175
1439
|
if (res.status === 201) {
|
|
1176
1440
|
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
@@ -1215,14 +1479,22 @@ const FeedbackModal = class {
|
|
|
1215
1479
|
}
|
|
1216
1480
|
};
|
|
1217
1481
|
this.close = () => {
|
|
1482
|
+
this.doClose(false);
|
|
1483
|
+
};
|
|
1484
|
+
// Closing by clicking outside keeps the typed input, so an accidental
|
|
1485
|
+
// click does not throw away a half-written message or screenshot
|
|
1486
|
+
this.handleOverlayClick = () => {
|
|
1487
|
+
this.doClose(!this.formSuccess);
|
|
1488
|
+
};
|
|
1489
|
+
this.doClose = (preserveInput) => {
|
|
1218
1490
|
this.isAnimating = false;
|
|
1491
|
+
this.popHistoryEntry();
|
|
1219
1492
|
setTimeout(() => {
|
|
1220
1493
|
this.sending = false;
|
|
1221
1494
|
this.showModal = false;
|
|
1222
1495
|
this.showScreenshotMode = false;
|
|
1223
1496
|
this.showScreenshotTopBar = false;
|
|
1224
1497
|
this.hasSelectedElement = false;
|
|
1225
|
-
this.encodedScreenshot = null;
|
|
1226
1498
|
// Remove highlight from ALL selected elements
|
|
1227
1499
|
document.querySelectorAll('.feedback-modal-element-selected').forEach((el) => {
|
|
1228
1500
|
el.classList.remove('feedback-modal-element-selected');
|
|
@@ -1231,9 +1503,19 @@ const FeedbackModal = class {
|
|
|
1231
1503
|
this.formSuccess = false;
|
|
1232
1504
|
this.formError = false;
|
|
1233
1505
|
this.formErrorStatus = 500;
|
|
1234
|
-
|
|
1235
|
-
|
|
1506
|
+
if (!preserveInput) {
|
|
1507
|
+
this.encodedScreenshot = null;
|
|
1508
|
+
this.formMessage = '';
|
|
1509
|
+
this.formEmail = '';
|
|
1510
|
+
// The rating and the privacy checkbox are part of the same in-progress
|
|
1511
|
+
// input. Leaving the rating set meant reopening the modal showed the
|
|
1512
|
+
// previous session's choice still highlighted, and submitting then sent
|
|
1513
|
+
// that stale rating instead of one chosen for this submission.
|
|
1514
|
+
this.selectedRating = this.initialRating();
|
|
1515
|
+
this.isPrivacyChecked = false;
|
|
1516
|
+
}
|
|
1236
1517
|
this.resetOverflow();
|
|
1518
|
+
this.restoreFocus();
|
|
1237
1519
|
}, 200);
|
|
1238
1520
|
};
|
|
1239
1521
|
// Handle screenshot events from canvas editor
|
|
@@ -1243,12 +1525,16 @@ const FeedbackModal = class {
|
|
|
1243
1525
|
this.takingScreenshot = false;
|
|
1244
1526
|
this.showCanvasEditor = false;
|
|
1245
1527
|
this.autoStartCapture = false;
|
|
1528
|
+
this.focusModal();
|
|
1246
1529
|
};
|
|
1247
1530
|
this.handleScreenshotCancelled = () => {
|
|
1248
1531
|
this.showModal = true;
|
|
1249
1532
|
this.takingScreenshot = false;
|
|
1250
1533
|
this.showCanvasEditor = false;
|
|
1251
1534
|
this.autoStartCapture = false;
|
|
1535
|
+
// Focus was inside the editor that just went away, so bring it back to the
|
|
1536
|
+
// form rather than letting it fall to the top of the page
|
|
1537
|
+
this.focusModal();
|
|
1252
1538
|
};
|
|
1253
1539
|
this.handleScreenshotError = (event) => {
|
|
1254
1540
|
console.error('Screenshot error:', event.detail.error);
|
|
@@ -1260,6 +1546,7 @@ const FeedbackModal = class {
|
|
|
1260
1546
|
this.takingScreenshot = false;
|
|
1261
1547
|
this.showCanvasEditor = false;
|
|
1262
1548
|
this.autoStartCapture = false;
|
|
1549
|
+
this.focusModal();
|
|
1263
1550
|
// Auto-hide error after 8 seconds
|
|
1264
1551
|
setTimeout(() => {
|
|
1265
1552
|
this.showScreenshotError = false;
|
|
@@ -1301,6 +1588,7 @@ const FeedbackModal = class {
|
|
|
1301
1588
|
this.screenshotError = '';
|
|
1302
1589
|
this.showCanvasEditor = false;
|
|
1303
1590
|
this.autoStartCapture = false;
|
|
1591
|
+
this.apiKey = '';
|
|
1304
1592
|
this.customFont = false;
|
|
1305
1593
|
this.emailAddress = '';
|
|
1306
1594
|
this.hideEmail = false;
|
|
@@ -1318,8 +1606,12 @@ const FeedbackModal = class {
|
|
|
1318
1606
|
this.metadata = undefined;
|
|
1319
1607
|
this.fetchData = true;
|
|
1320
1608
|
this.embedded = false;
|
|
1609
|
+
this.clickOutsideClose = true;
|
|
1610
|
+
this.historyClose = true;
|
|
1611
|
+
this.escapeClose = true;
|
|
1321
1612
|
this.emailPlaceholder = 'Email address (optional)';
|
|
1322
1613
|
this.errorMessage = 'Please try again later.';
|
|
1614
|
+
this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
|
|
1323
1615
|
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
1324
1616
|
this.errorMessage404 = 'We could not find the provided project ID in PushFeedback.';
|
|
1325
1617
|
this.messagePlaceholder = 'Share your thoughts...';
|
|
@@ -1332,6 +1624,10 @@ const FeedbackModal = class {
|
|
|
1332
1624
|
this.ratingPlaceholder = 'Was this page helpful?';
|
|
1333
1625
|
this.ratingStarsPlaceholder = 'How would you rate this page?';
|
|
1334
1626
|
this.sendButtonText = 'Send';
|
|
1627
|
+
this.closeButtonLabel = 'Close feedback form';
|
|
1628
|
+
this.ratingPositiveLabel = 'Yes';
|
|
1629
|
+
this.ratingNegativeLabel = 'No';
|
|
1630
|
+
this.ratingStarLabel = 'Rate {rating} out of 5';
|
|
1335
1631
|
this.successMessage = '';
|
|
1336
1632
|
this.recaptchaText = 'This form is protected by reCAPTCHA and the Google <a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</a> and <a href="https://policies.google.com/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a> apply.';
|
|
1337
1633
|
this.screenshotAttachedText = 'Screenshot attached';
|
|
@@ -1352,16 +1648,98 @@ const FeedbackModal = class {
|
|
|
1352
1648
|
this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari on desktop.';
|
|
1353
1649
|
this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
|
|
1354
1650
|
}
|
|
1651
|
+
connectedCallback() {
|
|
1652
|
+
window.addEventListener('popstate', this.handlePopState);
|
|
1653
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
1654
|
+
}
|
|
1655
|
+
disconnectedCallback() {
|
|
1656
|
+
window.removeEventListener('popstate', this.handlePopState);
|
|
1657
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
1658
|
+
}
|
|
1659
|
+
// Keeps Tab cycling inside the dialog while it is open
|
|
1660
|
+
trapFocus(event) {
|
|
1661
|
+
var _a, _b;
|
|
1662
|
+
const focusable = this.getFocusableElements();
|
|
1663
|
+
if (focusable.length === 0) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const first = focusable[0];
|
|
1667
|
+
const last = focusable[focusable.length - 1];
|
|
1668
|
+
const active = (_a = this.modalContent) === null || _a === void 0 ? void 0 : _a.getRootNode();
|
|
1669
|
+
const current = active === null || active === void 0 ? void 0 : active.activeElement;
|
|
1670
|
+
if (current === this.modalContent) {
|
|
1671
|
+
// Focus is on the dialog container itself (where it lands on open).
|
|
1672
|
+
// Tab falls through to the first control natively; Shift+Tab would
|
|
1673
|
+
// escape the dialog, so wrap it to the last one.
|
|
1674
|
+
if (event.shiftKey) {
|
|
1675
|
+
event.preventDefault();
|
|
1676
|
+
last.focus();
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
else if (event.shiftKey && current === first) {
|
|
1680
|
+
event.preventDefault();
|
|
1681
|
+
last.focus();
|
|
1682
|
+
}
|
|
1683
|
+
else if (!event.shiftKey && current === last) {
|
|
1684
|
+
event.preventDefault();
|
|
1685
|
+
first.focus();
|
|
1686
|
+
}
|
|
1687
|
+
else if (!current || !((_b = this.modalContent) === null || _b === void 0 ? void 0 : _b.contains(current))) {
|
|
1688
|
+
// Focus escaped the dialog (or never entered it) - pull it back in
|
|
1689
|
+
event.preventDefault();
|
|
1690
|
+
first.focus();
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
// offsetParent is not usable here: it is null for `position: fixed` elements
|
|
1694
|
+
// even when they are perfectly visible, and it is non-null for
|
|
1695
|
+
// `visibility: hidden` ones, which cannot be focused. getClientRects covers
|
|
1696
|
+
// display:none and detached nodes, so visibility is the only extra check.
|
|
1697
|
+
isFocusable(el) {
|
|
1698
|
+
if (el.hidden || el.getClientRects().length === 0) {
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
return getComputedStyle(el).visibility !== 'hidden';
|
|
1702
|
+
}
|
|
1703
|
+
getFocusableElements() {
|
|
1704
|
+
if (!this.modalContent) {
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1708
|
+
return Array.from(this.modalContent.querySelectorAll(selector)).filter((el) => this.isFocusable(el));
|
|
1709
|
+
}
|
|
1710
|
+
// Moves focus to the dialog container once it has rendered, so screen readers
|
|
1711
|
+
// announce the dialog title rather than jumping straight to the first control
|
|
1712
|
+
focusModal() {
|
|
1713
|
+
requestAnimationFrame(() => {
|
|
1714
|
+
if (this.modalContent) {
|
|
1715
|
+
this.modalContent.focus();
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
restoreFocus() {
|
|
1720
|
+
const target = this.previouslyFocused;
|
|
1721
|
+
this.previouslyFocused = null;
|
|
1722
|
+
if (target && typeof target.focus === 'function' && target.isConnected) {
|
|
1723
|
+
target.focus();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
// The rating the widget starts from. Closing restores this rather than
|
|
1727
|
+
// blanking to neutral, so a consumer's configured `rating` default survives
|
|
1728
|
+
// the modal being closed and reopened.
|
|
1729
|
+
initialRating() {
|
|
1730
|
+
if (this.ratingMode == 'thumbs' && this.rating == 0) {
|
|
1731
|
+
return 5;
|
|
1732
|
+
}
|
|
1733
|
+
if (this.rating) {
|
|
1734
|
+
return this.rating;
|
|
1735
|
+
}
|
|
1736
|
+
return -1;
|
|
1737
|
+
}
|
|
1355
1738
|
componentWillLoad() {
|
|
1356
1739
|
if (this.fetchData)
|
|
1357
1740
|
this.fetchProjectData();
|
|
1358
1741
|
this.formEmail = this.emailAddress;
|
|
1359
|
-
|
|
1360
|
-
this.selectedRating = this.rating;
|
|
1361
|
-
}
|
|
1362
|
-
if (this.ratingMode == 'thumbs' && this.rating == 0) {
|
|
1363
|
-
this.selectedRating = 5;
|
|
1364
|
-
}
|
|
1742
|
+
this.selectedRating = this.initialRating();
|
|
1365
1743
|
// Automatically show modal when embedded
|
|
1366
1744
|
if (this.embedded) {
|
|
1367
1745
|
this.showModal = true;
|
|
@@ -1369,7 +1747,9 @@ const FeedbackModal = class {
|
|
|
1369
1747
|
}
|
|
1370
1748
|
async fetchProjectData() {
|
|
1371
1749
|
try {
|
|
1372
|
-
|
|
1750
|
+
// Public config endpoint: never send the API key here, it is only
|
|
1751
|
+
// needed (and only allowed to travel) on the feedback POST
|
|
1752
|
+
const response = await fetch('https://app.pushfeedback.com/api/v1/projects/' + this.project + '/');
|
|
1373
1753
|
const data = await response.json();
|
|
1374
1754
|
this.whitelabel = data.whitelabel;
|
|
1375
1755
|
this.recaptchaEnabled = data.recaptcha_enabled || false;
|
|
@@ -1442,6 +1822,12 @@ const FeedbackModal = class {
|
|
|
1442
1822
|
handleEmailInput(event) {
|
|
1443
1823
|
this.formEmail = event.target.value;
|
|
1444
1824
|
}
|
|
1825
|
+
popHistoryEntry() {
|
|
1826
|
+
if (this.historyEntryPushed) {
|
|
1827
|
+
this.historyEntryPushed = false;
|
|
1828
|
+
history.back();
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1445
1831
|
handleCheckboxChange(event) {
|
|
1446
1832
|
this.isPrivacyChecked = event.target.checked;
|
|
1447
1833
|
}
|
|
@@ -1452,28 +1838,28 @@ const FeedbackModal = class {
|
|
|
1452
1838
|
this.selectedRating = newRating;
|
|
1453
1839
|
}
|
|
1454
1840
|
render() {
|
|
1455
|
-
return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (h("canvas-editor", { ref: (el) => (this.canvasEditorRef = el), exportparts: "overlay: canvas-editor-overlay, modal: canvas-editor-modal, header: canvas-editor-header, title: canvas-editor-title, toolbar: canvas-editor-toolbar, tool-button: canvas-editor-tool-button, cancel-button: canvas-editor-cancel-button, save-button: canvas-editor-save-button, content: canvas-editor-content, canvas: canvas-editor-canvas", "canvas-editor-title": this.screenshotEditorTitle, "canvas-editor-cancel-text": this.screenshotEditorCancelText, "canvas-editor-save-text": this.screenshotEditorSaveText, "screenshot-taking-text": this.screenshotTakingText, "screenshot-attached-text": this.screenshotAttachedText, "screenshot-button-text": this.screenshotButtonText, "auto-start-screenshot": this.autoStartCapture, "existing-screenshot": this.encodedScreenshot || '', "edit-text-button-text": this.screenshotEditTextButtonText, "size-label-text": this.screenshotSizeLabelText, "border-label-text": this.screenshotBorderLabelText, "edit-text-prompt-text": this.screenshotEditTextPromptText, "screenshot-error-general": this.screenshotErrorGeneral, "screenshot-error-permission": this.screenshotErrorPermission, "screenshot-error-not-supported": this.screenshotErrorNotSupported, "screenshot-error-not-found": this.screenshotErrorNotFound, "screenshot-error-cancelled": this.screenshotErrorCancelled, "screenshot-error-browser-not-supported": this.screenshotErrorBrowserNotSupported, "screenshot-error-unexpected": this.screenshotErrorUnexpected, onScreenshotReady: this.handleScreenshotReady, onScreenshotCancelled: this.handleScreenshotCancelled, onScreenshotFailed: this.handleScreenshotError })), this.showScreenshotError && (h("div", { class: "screenshot-error-notification" }, h("div", { class: "screenshot-error-content" }, h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), h("span", null, this.screenshotError), h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay" })), this.showModal && (h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''} ${this.embedded ? 'feedback-modal-content--embedded' : ''}`, part: "modal", ref: (el) => (this.modalContent = el) }, h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
|
|
1841
|
+
return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (h("canvas-editor", { ref: (el) => (this.canvasEditorRef = el), exportparts: "overlay: canvas-editor-overlay, modal: canvas-editor-modal, header: canvas-editor-header, title: canvas-editor-title, toolbar: canvas-editor-toolbar, tool-button: canvas-editor-tool-button, cancel-button: canvas-editor-cancel-button, save-button: canvas-editor-save-button, content: canvas-editor-content, canvas: canvas-editor-canvas", "canvas-editor-title": this.screenshotEditorTitle, "canvas-editor-cancel-text": this.screenshotEditorCancelText, "canvas-editor-save-text": this.screenshotEditorSaveText, "screenshot-taking-text": this.screenshotTakingText, "screenshot-attached-text": this.screenshotAttachedText, "screenshot-button-text": this.screenshotButtonText, "escape-close": this.escapeClose ? 'true' : 'false', "click-outside-close": this.clickOutsideClose ? 'true' : 'false', "auto-start-screenshot": this.autoStartCapture, "existing-screenshot": this.encodedScreenshot || '', "edit-text-button-text": this.screenshotEditTextButtonText, "size-label-text": this.screenshotSizeLabelText, "border-label-text": this.screenshotBorderLabelText, "edit-text-prompt-text": this.screenshotEditTextPromptText, "screenshot-error-general": this.screenshotErrorGeneral, "screenshot-error-permission": this.screenshotErrorPermission, "screenshot-error-not-supported": this.screenshotErrorNotSupported, "screenshot-error-not-found": this.screenshotErrorNotFound, "screenshot-error-cancelled": this.screenshotErrorCancelled, "screenshot-error-browser-not-supported": this.screenshotErrorBrowserNotSupported, "screenshot-error-unexpected": this.screenshotErrorUnexpected, onScreenshotReady: this.handleScreenshotReady, onScreenshotCancelled: this.handleScreenshotCancelled, onScreenshotFailed: this.handleScreenshotError })), this.showScreenshotError && (h("div", { class: "screenshot-error-notification" }, h("div", { class: "screenshot-error-content" }, h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), h("span", null, this.screenshotError), h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay", "aria-hidden": "true", onClick: this.clickOutsideClose ? this.handleOverlayClick : undefined })), this.showModal && (h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''} ${this.embedded ? 'feedback-modal-content--embedded' : ''}`, part: "modal", role: this.embedded ? 'region' : 'dialog', "aria-modal": this.embedded ? undefined : 'true', "aria-labelledby": "feedback-modal-title", tabindex: "-1", ref: (el) => (this.modalContent = el) }, h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
|
|
1456
1842
|
? 'feedback-modal-header--no-content'
|
|
1457
|
-
: ''}`, part: "header" }, h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-header-text" }, h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (h("button", { class: "feedback-modal-close", part: "close-button", onClick: this.close }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, h("button", { title: "
|
|
1843
|
+
: ''}`, part: "header" }, h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-header-text" }, h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (h("button", { class: "feedback-modal-close", part: "close-button", type: "button", "aria-label": this.closeButtonLabel, onClick: this.close }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, h("button", { type: "button", title: this.ratingPositiveLabel, "aria-label": this.ratingPositiveLabel, "aria-pressed": this.selectedRating === 1 ? 'true' : 'false', class: `feedback-modal-rating-button feedback-modal-rating-button--positive ${this.selectedRating === 1
|
|
1458
1844
|
? 'feedback-modal-rating-button--selected'
|
|
1459
1845
|
: ''}`, part: "rating-button rating-button-positive", onClick: (event) => {
|
|
1460
1846
|
event.preventDefault();
|
|
1461
1847
|
this.handleRatingChange(1);
|
|
1462
|
-
} }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" }))), h("button", { title: "
|
|
1848
|
+
} }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" }))), h("button", { type: "button", title: this.ratingNegativeLabel, "aria-label": this.ratingNegativeLabel, "aria-pressed": this.selectedRating === 5 ? 'true' : 'false', class: `feedback-modal-rating-button feedback-modal-rating-button--negative ${this.selectedRating === 5
|
|
1463
1849
|
? 'feedback-modal-rating-button--selected'
|
|
1464
1850
|
: ''}`, part: "rating-button rating-button-negative", onClick: (event) => {
|
|
1465
1851
|
event.preventDefault();
|
|
1466
1852
|
this.handleRatingChange(5);
|
|
1467
|
-
} }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" })))))) : (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (h("button", { key: rating, class: `feedback-modal-rating-button ${this.selectedRating >= rating
|
|
1853
|
+
} }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" })))))) : (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (h("button", { key: rating, type: "button", "aria-label": this.ratingStarLabel.replace('{rating}', String(rating)), "aria-pressed": this.selectedRating === rating ? 'true' : 'false', class: `feedback-modal-rating-button ${this.selectedRating >= rating
|
|
1468
1854
|
? 'feedback-modal-rating-button--selected'
|
|
1469
1855
|
: ''}`, part: "rating-button", onClick: (event) => {
|
|
1470
1856
|
event.preventDefault();
|
|
1471
1857
|
this.handleRatingChange(rating);
|
|
1472
|
-
} }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), h("div", { class: "feedback-modal-text" }, h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (h("div", { class: "feedback-modal-email" }, h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), h("div", { class: "feedback-verification" }, h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (h("div", { class: "feedback-modal-privacy", part: "privacy" }, h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), h("span", { innerHTML: this.privacyPolicyText }))), h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, part: "screenshot-button", onClick: this.openScreenShot, disabled: this.sending || this.takingScreenshot }, this.encodedScreenshot && (h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (h("div", { class: "screenshot-loading" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather-loader" }, h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
|
|
1858
|
+
} }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), h("div", { class: "feedback-modal-text" }, h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (h("div", { class: "feedback-modal-email" }, h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), h("div", { class: "feedback-verification" }, h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (h("div", { class: "feedback-modal-privacy", part: "privacy" }, h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), h("span", { innerHTML: sanitizeHtml(this.privacyPolicyText) }))), h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, part: "screenshot-button", onClick: this.openScreenShot, disabled: this.sending || this.takingScreenshot }, this.encodedScreenshot && (h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (h("div", { class: "screenshot-loading" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather-loader" }, h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
|
|
1473
1859
|
? this.screenshotTakingText
|
|
1474
1860
|
: this.encodedScreenshot
|
|
1475
1861
|
? this.screenshotAttachedText
|
|
1476
|
-
: this.screenshotButtonText)), h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-success" }, h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (h("div", { class: "feedback-modal-footer", part: "footer" }, h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && h("span", { innerHTML: this.recaptchaText }), this.recaptchaEnabled && this.footerText && ' ', this.footerText && h("span", { innerHTML: this.footerText }))))))));
|
|
1862
|
+
: this.screenshotButtonText)), h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-success" }, h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 401 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage401)) : this.formError && this.formErrorStatus == 403 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (h("div", { class: "feedback-modal-footer", part: "footer" }, h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && (h("span", { innerHTML: sanitizeHtml(this.recaptchaText) })), this.recaptchaEnabled && this.footerText && ' ', this.footerText && h("span", { innerHTML: sanitizeHtml(this.footerText) }))))))));
|
|
1477
1863
|
}
|
|
1478
1864
|
componentDidRender() {
|
|
1479
1865
|
if (this.showModal) {
|
|
@@ -1483,7 +1869,17 @@ const FeedbackModal = class {
|
|
|
1483
1869
|
}
|
|
1484
1870
|
}
|
|
1485
1871
|
async openModal() {
|
|
1872
|
+
// Remember the trigger so focus can return there when the modal closes
|
|
1873
|
+
if (!this.embedded) {
|
|
1874
|
+
const active = document.activeElement;
|
|
1875
|
+
this.previouslyFocused = active && active !== document.body ? active : null;
|
|
1876
|
+
}
|
|
1486
1877
|
this.showModal = true;
|
|
1878
|
+
this.focusModal();
|
|
1879
|
+
if (this.historyClose && !this.embedded && !this.historyEntryPushed) {
|
|
1880
|
+
history.pushState({ feedbackModal: true }, '');
|
|
1881
|
+
this.historyEntryPushed = true;
|
|
1882
|
+
}
|
|
1487
1883
|
requestAnimationFrame(() => {
|
|
1488
1884
|
requestAnimationFrame(() => {
|
|
1489
1885
|
this.isAnimating = true;
|