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
|
@@ -6,12 +6,59 @@ const index = require('./index-9a8f4784.js');
|
|
|
6
6
|
|
|
7
7
|
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}}";
|
|
8
8
|
|
|
9
|
+
// Upper bound on waiting for the capture stream to produce video metadata,
|
|
10
|
+
// after which the stream is stopped so it cannot leak
|
|
11
|
+
const CAPTURE_TIMEOUT_MS = 10000;
|
|
9
12
|
const CanvasEditor = class {
|
|
10
13
|
constructor(hostRef) {
|
|
11
14
|
index.registerInstance(this, hostRef);
|
|
12
15
|
this.screenshotReady = index.createEvent(this, "screenshotReady", 7);
|
|
13
16
|
this.screenshotCancelled = index.createEvent(this, "screenshotCancelled", 7);
|
|
14
17
|
this.screenshotFailed = index.createEvent(this, "screenshotFailed", 7);
|
|
18
|
+
this.handleKeyDown = (event) => {
|
|
19
|
+
if (!this.showCanvasEditor || !this.escapeClose || event.key !== 'Escape') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// While the browser is capturing the screen, the picker owns the keyboard
|
|
23
|
+
if (this.takingScreenshot) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
// The colour picker is a popup on top of the editor, so Escape dismisses
|
|
28
|
+
// that first and leaves the editor (and any annotations) in place
|
|
29
|
+
if (this.showColorPicker) {
|
|
30
|
+
this.showColorPicker = false;
|
|
31
|
+
this.editingColorIndex = -1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Deselect before closing, the way drawing tools do. Closing throws away
|
|
35
|
+
// every annotation, so someone who has just selected one gets a cheap
|
|
36
|
+
// Escape that only drops the selection rather than their work
|
|
37
|
+
if (this.selectedAnnotation) {
|
|
38
|
+
this.selectedAnnotation = null;
|
|
39
|
+
this.redrawAnnotations();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Same as the Cancel button: discards annotations and returns to the form
|
|
43
|
+
this.closeCanvasEditor();
|
|
44
|
+
};
|
|
45
|
+
// Tracks whether the press that started this click landed on the backdrop.
|
|
46
|
+
// Dragging an annotation and releasing past the edge of the editor fires a
|
|
47
|
+
// click on the backdrop too, and that must not be read as "clicked outside".
|
|
48
|
+
this.pressStartedOnOverlay = false;
|
|
49
|
+
this.handleOverlayMouseDown = (event) => {
|
|
50
|
+
this.pressStartedOnOverlay = event.target === event.currentTarget;
|
|
51
|
+
};
|
|
52
|
+
this.handleOverlayClick = (event) => {
|
|
53
|
+
const startedOutside = this.pressStartedOnOverlay;
|
|
54
|
+
this.pressStartedOnOverlay = false;
|
|
55
|
+
if (!this.clickOutsideClose || !startedOutside || event.target !== event.currentTarget) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Closes the editor only. The feedback form reopens behind it, so a stray
|
|
59
|
+
// click never dismisses the whole widget
|
|
60
|
+
this.closeCanvasEditor();
|
|
61
|
+
};
|
|
15
62
|
this.handleWindowResize = () => {
|
|
16
63
|
// Debounce resize events
|
|
17
64
|
if (this.resizeTimeout) {
|
|
@@ -691,8 +738,16 @@ const CanvasEditor = class {
|
|
|
691
738
|
if (!this.isDrawing || !this.currentAnnotation)
|
|
692
739
|
return;
|
|
693
740
|
this.isDrawing = false;
|
|
694
|
-
|
|
741
|
+
const annotation = this.currentAnnotation;
|
|
695
742
|
this.currentAnnotation = null;
|
|
743
|
+
// A click with no drag never reaches mousemove, so the shape has no end
|
|
744
|
+
// point or size. Committing it added an invisible annotation that could
|
|
745
|
+
// not be selected, dragged or deleted, and it still consumed an undo step.
|
|
746
|
+
if (this.isDegenerateAnnotation(annotation)) {
|
|
747
|
+
this.redrawAnnotations();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
this.annotations = [...this.annotations, annotation];
|
|
696
751
|
this.redrawAnnotations();
|
|
697
752
|
};
|
|
698
753
|
// Convert screen coordinates to canvas coordinates
|
|
@@ -734,11 +789,28 @@ const CanvasEditor = class {
|
|
|
734
789
|
}
|
|
735
790
|
case 'line':
|
|
736
791
|
case 'arrow': {
|
|
737
|
-
// Distance from point to line
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
792
|
+
// Distance from the point to the line *segment*, not to the infinite
|
|
793
|
+
// line through it. The previous formula measured the infinite line, so
|
|
794
|
+
// a click far past either end still counted as a hit, and a zero-length
|
|
795
|
+
// segment divided by zero and gave NaN, leaving that annotation
|
|
796
|
+
// impossible to select, drag or delete.
|
|
797
|
+
// A click with no drag never reaches mousemove, so endX/endY are
|
|
798
|
+
// undefined rather than equal to the start. Treat that as a segment
|
|
799
|
+
// collapsed to its start point instead of letting NaN propagate.
|
|
800
|
+
const endX = Number.isFinite(annotation.endX) ? annotation.endX : annotation.startX;
|
|
801
|
+
const endY = Number.isFinite(annotation.endY) ? annotation.endY : annotation.startY;
|
|
802
|
+
const dx = endX - annotation.startX;
|
|
803
|
+
const dy = endY - annotation.startY;
|
|
804
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
805
|
+
// Zero-length segment: t stays 0 and we measure to the single point
|
|
806
|
+
let t = 0;
|
|
807
|
+
if (lengthSquared > 0) {
|
|
808
|
+
t = ((x - annotation.startX) * dx + (y - annotation.startY) * dy) / lengthSquared;
|
|
809
|
+
t = Math.max(0, Math.min(1, t));
|
|
810
|
+
}
|
|
811
|
+
const closestX = annotation.startX + t * dx;
|
|
812
|
+
const closestY = annotation.startY + t * dy;
|
|
813
|
+
const distance = Math.hypot(x - closestX, y - closestY);
|
|
742
814
|
return distance <= tolerance;
|
|
743
815
|
}
|
|
744
816
|
case 'text': {
|
|
@@ -763,6 +835,8 @@ const CanvasEditor = class {
|
|
|
763
835
|
this.screenshotTakingText = 'Taking screenshot...';
|
|
764
836
|
this.screenshotAttachedText = 'Screenshot attached';
|
|
765
837
|
this.screenshotButtonText = 'Add a screenshot';
|
|
838
|
+
this.escapeClose = true;
|
|
839
|
+
this.clickOutsideClose = true;
|
|
766
840
|
this.autoStartScreenshot = false;
|
|
767
841
|
this.existingScreenshot = '';
|
|
768
842
|
this.editTextButtonText = 'Edit Text';
|
|
@@ -818,10 +892,12 @@ const CanvasEditor = class {
|
|
|
818
892
|
// Add window resize listener for canvas adaptation
|
|
819
893
|
this.handleWindowResize = this.handleWindowResize.bind(this);
|
|
820
894
|
window.addEventListener('resize', this.handleWindowResize);
|
|
895
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
821
896
|
}
|
|
822
897
|
disconnectedCallback() {
|
|
823
898
|
// Clean up resize listener
|
|
824
899
|
window.removeEventListener('resize', this.handleWindowResize);
|
|
900
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
825
901
|
}
|
|
826
902
|
async captureViewportScreenshot() {
|
|
827
903
|
try {
|
|
@@ -845,6 +921,30 @@ const CanvasEditor = class {
|
|
|
845
921
|
video.autoplay = true;
|
|
846
922
|
video.muted = true;
|
|
847
923
|
return new Promise((resolve, reject) => {
|
|
924
|
+
// If neither onloadedmetadata nor onerror ever fires, the promise would
|
|
925
|
+
// never settle: the capture stream would stay live with the browser's
|
|
926
|
+
// recording indicator on, and the widget would sit in "Taking
|
|
927
|
+
// screenshot..." forever with no way back short of a page reload.
|
|
928
|
+
let settled = false;
|
|
929
|
+
const stopStream = () => {
|
|
930
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
931
|
+
};
|
|
932
|
+
const finish = (fn) => {
|
|
933
|
+
if (settled) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
settled = true;
|
|
937
|
+
clearTimeout(watchdog);
|
|
938
|
+
video.onloadedmetadata = null;
|
|
939
|
+
video.onerror = null;
|
|
940
|
+
fn();
|
|
941
|
+
};
|
|
942
|
+
const watchdog = setTimeout(() => {
|
|
943
|
+
finish(() => {
|
|
944
|
+
stopStream();
|
|
945
|
+
reject(new Error('Screenshot capture timed out'));
|
|
946
|
+
});
|
|
947
|
+
}, CAPTURE_TIMEOUT_MS);
|
|
848
948
|
video.onloadedmetadata = () => {
|
|
849
949
|
video.play();
|
|
850
950
|
// Wait a moment for video to stabilize
|
|
@@ -856,22 +956,26 @@ const CanvasEditor = class {
|
|
|
856
956
|
canvas.height = video.videoHeight;
|
|
857
957
|
const ctx = canvas.getContext('2d');
|
|
858
958
|
ctx.drawImage(video, 0, 0);
|
|
859
|
-
// Stop the stream
|
|
860
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
861
959
|
// Convert to data URL
|
|
862
960
|
const dataUrl = canvas.toDataURL('image/png');
|
|
863
|
-
|
|
864
|
-
|
|
961
|
+
finish(() => {
|
|
962
|
+
stopStream();
|
|
963
|
+
resolve(dataUrl);
|
|
964
|
+
});
|
|
865
965
|
}
|
|
866
966
|
catch (error) {
|
|
867
|
-
|
|
868
|
-
|
|
967
|
+
finish(() => {
|
|
968
|
+
stopStream();
|
|
969
|
+
reject(error);
|
|
970
|
+
});
|
|
869
971
|
}
|
|
870
972
|
}, 100);
|
|
871
973
|
};
|
|
872
974
|
video.onerror = () => {
|
|
873
|
-
|
|
874
|
-
|
|
975
|
+
finish(() => {
|
|
976
|
+
stopStream();
|
|
977
|
+
reject(new Error('Failed to load video for screenshot capture'));
|
|
978
|
+
});
|
|
875
979
|
};
|
|
876
980
|
});
|
|
877
981
|
}
|
|
@@ -880,9 +984,27 @@ const CanvasEditor = class {
|
|
|
880
984
|
throw error;
|
|
881
985
|
}
|
|
882
986
|
}
|
|
987
|
+
// Whether a freshly drawn shape is too small to be worth keeping
|
|
988
|
+
isDegenerateAnnotation(annotation) {
|
|
989
|
+
const MIN_SIZE = 2; // canvas pixels
|
|
990
|
+
if (annotation.type === 'line' || annotation.type === 'arrow') {
|
|
991
|
+
if (!Number.isFinite(annotation.endX) || !Number.isFinite(annotation.endY)) {
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
return (Math.hypot(annotation.endX - annotation.startX, annotation.endY - annotation.startY) <
|
|
995
|
+
MIN_SIZE);
|
|
996
|
+
}
|
|
997
|
+
if (annotation.type === 'rectangle') {
|
|
998
|
+
if (!Number.isFinite(annotation.width) || !Number.isFinite(annotation.height)) {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
return Math.abs(annotation.width) < MIN_SIZE && Math.abs(annotation.height) < MIN_SIZE;
|
|
1002
|
+
}
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
883
1005
|
render() {
|
|
884
1006
|
var _a, _b, _c, _d, _e, _f;
|
|
885
|
-
return (index.h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (index.h("div", { class: "canvas-editor-overlay", part: "overlay" }, index.h("div", { class: "canvas-editor-modal", part: "modal" }, index.h("div", { class: "canvas-editor-header", part: "header" }, index.h("div", { class: "canvas-editor-title", part: "title" }, index.h("h3",
|
|
1007
|
+
return (index.h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (index.h("div", { class: "canvas-editor-overlay", part: "overlay", onMouseDown: this.handleOverlayMouseDown, onClick: this.handleOverlayClick }, index.h("div", { class: "canvas-editor-modal", part: "modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "canvas-editor-title" }, index.h("div", { class: "canvas-editor-header", part: "header" }, index.h("div", { class: "canvas-editor-title", part: "title" }, index.h("h3", { id: "canvas-editor-title" }, this.canvasEditorTitle)), index.h("div", { class: "canvas-editor-toolbar", part: "toolbar" }, index.h("div", { class: "toolbar-section" }, index.h("div", { class: "tool-group" }, index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'rectangle'), title: "Rectangle" }, index.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" }, index.h("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'line'), title: "Line" }, index.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" }, index.h("path", { d: "M5 12h14" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'arrow'), title: "Arrow" }, index.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" }, index.h("path", { d: "M13 5H19V11" }), index.h("path", { d: "M19 5L5 19" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'text'), title: "Text" }, index.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" }, index.h("path", { d: "M12 4v16" }), index.h("path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }), index.h("path", { d: "M9 20h6" }))), index.h("div", { class: "toolbar-divider" }), index.h("button", { class: "tool-btn undo-btn", part: "tool-button", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, index.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" }, index.h("path", { d: "M3 7v6h6" }), index.h("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" }))), this.selectedAnnotation && (index.h("button", { class: "tool-btn delete-btn", part: "tool-button", onClick: this.deleteSelectedAnnotation, title: "Delete" }, index.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" }, index.h("path", { d: "M3 6h18" }), index.h("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), index.h("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }), index.h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), index.h("line", { x1: "14", x2: "14", y1: "11", y2: "17" })))))), index.h("div", { class: "toolbar-section" }, index.h("div", { class: "color-palette" }, this.defaultColors.map((color, index$1) => (index.h("div", { class: "color-slot-wrapper" }, index.h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index$1 ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index$1), title: `Color ${index$1 + 1} - Click to customize` }, this.editingColorIndex === index$1 && (index.h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, index.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$1 && this.showColorPicker && (index.h("div", { class: "color-picker-dropdown" }, index.h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (index.h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' ||
|
|
886
1008
|
(!this.selectedAnnotation && this.canvasDrawingTool === 'text')) && (index.h("div", { class: "text-controls" }, index.h("div", { class: "font-size-control" }, index.h("label", null, this.sizeLabelText), index.h("input", { type: "range", min: "8", max: "72", value: ((_b = this.selectedAnnotation) === null || _b === void 0 ? void 0 : _b.fontSize) || this.canvasTextSize, onInput: (e) => {
|
|
887
1009
|
const newSize = parseInt(e.target.value);
|
|
888
1010
|
if (this.selectedAnnotation) {
|
|
@@ -906,7 +1028,7 @@ const CanvasEditor = class {
|
|
|
906
1028
|
};
|
|
907
1029
|
CanvasEditor.style = canvasEditorCss;
|
|
908
1030
|
|
|
909
|
-
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}}";
|
|
1031
|
+
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}}";
|
|
910
1032
|
|
|
911
1033
|
const FeedbackButton = class {
|
|
912
1034
|
constructor(hostRef) {
|
|
@@ -920,8 +1042,11 @@ const FeedbackButton = class {
|
|
|
920
1042
|
this.sessionId = '';
|
|
921
1043
|
this.metadata = '';
|
|
922
1044
|
this.submit = false;
|
|
1045
|
+
this.apiKey = '';
|
|
1046
|
+
this.clickOutsideClose = true;
|
|
923
1047
|
this.customFont = false;
|
|
924
1048
|
this.emailAddress = '';
|
|
1049
|
+
this.historyClose = true;
|
|
925
1050
|
this.isEmailRequired = false;
|
|
926
1051
|
this.fetchData = true;
|
|
927
1052
|
this.hideEmail = false;
|
|
@@ -934,6 +1059,7 @@ const FeedbackButton = class {
|
|
|
934
1059
|
this.ratingMode = 'thumbs';
|
|
935
1060
|
this.emailPlaceholder = 'Email address (optional)';
|
|
936
1061
|
this.errorMessage = 'Please try again later.';
|
|
1062
|
+
this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
|
|
937
1063
|
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
938
1064
|
this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
|
|
939
1065
|
this.footerText = '';
|
|
@@ -979,13 +1105,19 @@ const FeedbackButton = class {
|
|
|
979
1105
|
}
|
|
980
1106
|
}
|
|
981
1107
|
componentDidLoad() {
|
|
982
|
-
if (this.buttonPosition === 'center-right') {
|
|
1108
|
+
if (this.buttonPosition === 'center-right' || this.buttonPosition === 'center-left') {
|
|
983
1109
|
const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
|
|
984
1110
|
let adjustement = 0;
|
|
985
1111
|
if (this.isSafariBrowser()) {
|
|
986
1112
|
adjustement = 5;
|
|
987
1113
|
}
|
|
988
|
-
|
|
1114
|
+
const offset = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
|
|
1115
|
+
if (this.buttonPosition === 'center-right') {
|
|
1116
|
+
buttonContent.style.right = offset;
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
buttonContent.style.left = offset;
|
|
1120
|
+
}
|
|
989
1121
|
}
|
|
990
1122
|
if (!this.customFont) {
|
|
991
1123
|
this.loadInterFont();
|
|
@@ -994,9 +1126,12 @@ const FeedbackButton = class {
|
|
|
994
1126
|
connectedCallback() {
|
|
995
1127
|
this.feedbackModal = document.createElement('feedback-modal');
|
|
996
1128
|
const props = [
|
|
1129
|
+
'apiKey',
|
|
1130
|
+
'clickOutsideClose',
|
|
997
1131
|
'customFont',
|
|
998
1132
|
'emailAddress',
|
|
999
1133
|
'fetchData',
|
|
1134
|
+
'historyClose',
|
|
1000
1135
|
'hideEmail',
|
|
1001
1136
|
'hidePrivacyPolicy',
|
|
1002
1137
|
'hideRating',
|
|
@@ -1022,6 +1157,7 @@ const FeedbackButton = class {
|
|
|
1022
1157
|
'screenshotErrorUnexpected',
|
|
1023
1158
|
'emailPlaceholder',
|
|
1024
1159
|
'errorMessage',
|
|
1160
|
+
'errorMessage401',
|
|
1025
1161
|
'errorMessage403',
|
|
1026
1162
|
'errorMessage404',
|
|
1027
1163
|
'footerText',
|
|
@@ -1086,12 +1222,16 @@ const FeedbackButton = class {
|
|
|
1086
1222
|
}
|
|
1087
1223
|
}
|
|
1088
1224
|
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 }));
|
|
1089
|
-
const
|
|
1225
|
+
const headers = {
|
|
1226
|
+
'Content-Type': 'application/json',
|
|
1227
|
+
};
|
|
1228
|
+
if (this.apiKey) {
|
|
1229
|
+
headers['Authorization'] = `Api-Key ${this.apiKey}`;
|
|
1230
|
+
}
|
|
1231
|
+
const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
|
|
1090
1232
|
method: 'POST',
|
|
1091
1233
|
body: JSON.stringify(body),
|
|
1092
|
-
headers
|
|
1093
|
-
'Content-Type': 'application/json',
|
|
1094
|
-
},
|
|
1234
|
+
headers,
|
|
1095
1235
|
});
|
|
1096
1236
|
if (res.status === 201) {
|
|
1097
1237
|
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
@@ -1122,19 +1262,139 @@ const FeedbackButton = class {
|
|
|
1122
1262
|
}
|
|
1123
1263
|
}
|
|
1124
1264
|
render() {
|
|
1125
|
-
return (index.h(index.Host, null, index.h("
|
|
1265
|
+
return (index.h(index.Host, null, index.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' && (index.h("span", { class: "feedback-button-content-icon", part: "icon" }, index.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" }, index.h("path", { d: "M12 20h9" }), index.h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), index.h("slot", null))));
|
|
1126
1266
|
}
|
|
1127
1267
|
get el() { return index.getElement(this); }
|
|
1128
1268
|
};
|
|
1129
1269
|
FeedbackButton.style = feedbackButtonCss;
|
|
1130
1270
|
|
|
1131
|
-
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}}";
|
|
1271
|
+
// Tags allowed in the rich-text props (privacy policy, reCAPTCHA notice,
|
|
1272
|
+
// footer). These only ever need links and light inline emphasis.
|
|
1273
|
+
const ALLOWED_TAGS = new Set([
|
|
1274
|
+
'A',
|
|
1275
|
+
'B',
|
|
1276
|
+
'BR',
|
|
1277
|
+
'CODE',
|
|
1278
|
+
'EM',
|
|
1279
|
+
'I',
|
|
1280
|
+
'P',
|
|
1281
|
+
'SMALL',
|
|
1282
|
+
'SPAN',
|
|
1283
|
+
'STRONG',
|
|
1284
|
+
'U',
|
|
1285
|
+
]);
|
|
1286
|
+
// No `class`: an injected class name cannot reach the host page's CSS (this
|
|
1287
|
+
// renders inside a shadow root), but it could match the widget's own internal
|
|
1288
|
+
// styles. The default prop values do not use classes, so dropping it is free.
|
|
1289
|
+
const ALLOWED_ATTRS = new Set(['href', 'target', 'rel', 'title']);
|
|
1290
|
+
// Schemes permitted in href. Anything else, notably javascript:, is dropped.
|
|
1291
|
+
const SAFE_URL = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
|
|
1292
|
+
/**
|
|
1293
|
+
* Removes anything that can execute from a fragment: script and style
|
|
1294
|
+
* elements, event-handler attributes, javascript: URLs, and any tag not on the
|
|
1295
|
+
* allowlist. Text inside a disallowed tag is kept, so stripping a wrapper does
|
|
1296
|
+
* not silently delete the words inside it.
|
|
1297
|
+
*/
|
|
1298
|
+
function scrub(root) {
|
|
1299
|
+
const elements = Array.from(root.querySelectorAll('*'));
|
|
1300
|
+
for (const el of elements) {
|
|
1301
|
+
// The node may already have been removed along with an ancestor
|
|
1302
|
+
if (!root.contains(el)) {
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const tag = el.tagName.toUpperCase();
|
|
1306
|
+
if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'IFRAME' || tag === 'OBJECT') {
|
|
1307
|
+
el.remove();
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
1311
|
+
// Unwrap: keep the text, drop the element
|
|
1312
|
+
el.replaceWith(...Array.from(el.childNodes));
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1316
|
+
const name = attr.name.toLowerCase();
|
|
1317
|
+
if (name.startsWith('on') || !ALLOWED_ATTRS.has(name)) {
|
|
1318
|
+
el.removeAttribute(attr.name);
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (name === 'href' && !SAFE_URL.test(attr.value.trim())) {
|
|
1322
|
+
el.removeAttribute(attr.name);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
// Links opening a new tab must not hand the opener over
|
|
1326
|
+
if (tag === 'A' && el.getAttribute('target') === '_blank') {
|
|
1327
|
+
el.setAttribute('rel', 'noopener noreferrer');
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Sanitizes a fragment of HTML for use with innerHTML.
|
|
1333
|
+
*
|
|
1334
|
+
* Prefers the browser's native Element.setHTML() where available, which parses
|
|
1335
|
+
* without executing. Otherwise parses in an inert document (no scripts run, no
|
|
1336
|
+
* images or other subresources are fetched) and scrubs the result.
|
|
1337
|
+
*/
|
|
1338
|
+
function sanitizeHtml(html) {
|
|
1339
|
+
if (!html) {
|
|
1340
|
+
return '';
|
|
1341
|
+
}
|
|
1342
|
+
const template = document.createElement('template');
|
|
1343
|
+
// Native sanitizer, when the browser has it
|
|
1344
|
+
const withSetHtml = template;
|
|
1345
|
+
if (typeof withSetHtml.setHTML === 'function') {
|
|
1346
|
+
try {
|
|
1347
|
+
withSetHtml.setHTML(html);
|
|
1348
|
+
scrub(template.content);
|
|
1349
|
+
return template.innerHTML;
|
|
1350
|
+
}
|
|
1351
|
+
catch (_a) {
|
|
1352
|
+
// Fall through to the manual path
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
// Inert parsing: content inside <template> is not rendered, so scripts do not
|
|
1356
|
+
// run and no subresource requests are made while we clean it up.
|
|
1357
|
+
template.innerHTML = html;
|
|
1358
|
+
scrub(template.content);
|
|
1359
|
+
return template.innerHTML;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
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}}";
|
|
1132
1363
|
|
|
1133
1364
|
const FeedbackModal = class {
|
|
1134
1365
|
constructor(hostRef) {
|
|
1135
1366
|
index.registerInstance(this, hostRef);
|
|
1136
1367
|
this.feedbackSent = index.createEvent(this, "feedbackSent", 7);
|
|
1137
1368
|
this.feedbackError = index.createEvent(this, "feedbackError", 7);
|
|
1369
|
+
// Track the history entry pushed while the modal is open
|
|
1370
|
+
this.historyEntryPushed = false;
|
|
1371
|
+
// Element that had focus before the modal opened, so it can be restored on close
|
|
1372
|
+
this.previouslyFocused = null;
|
|
1373
|
+
this.handleKeyDown = (event) => {
|
|
1374
|
+
// The canvas editor renders its own overlay and handles its own keys
|
|
1375
|
+
if (!this.showModal || this.embedded || this.showCanvasEditor) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (event.key === 'Escape' && this.escapeClose) {
|
|
1379
|
+
event.preventDefault();
|
|
1380
|
+
// Escape is an accidental-dismissal path like clicking outside, so keep
|
|
1381
|
+
// whatever the user already typed
|
|
1382
|
+
this.doClose(!this.formSuccess);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
if (event.key === 'Tab') {
|
|
1386
|
+
this.trapFocus(event);
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
this.handlePopState = () => {
|
|
1390
|
+
// Back button pressed while the modal is open: close it and consume our entry
|
|
1391
|
+
if (this.historyEntryPushed) {
|
|
1392
|
+
this.historyEntryPushed = false;
|
|
1393
|
+
if (this.showModal) {
|
|
1394
|
+
this.close();
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1138
1398
|
this.onScrollDebounced = () => {
|
|
1139
1399
|
clearTimeout(this.scrollTimeout);
|
|
1140
1400
|
this.scrollTimeout = setTimeout(() => {
|
|
@@ -1169,12 +1429,16 @@ const FeedbackModal = class {
|
|
|
1169
1429
|
}
|
|
1170
1430
|
}
|
|
1171
1431
|
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 }));
|
|
1172
|
-
const
|
|
1432
|
+
const headers = {
|
|
1433
|
+
'Content-Type': 'application/json',
|
|
1434
|
+
};
|
|
1435
|
+
if (this.apiKey) {
|
|
1436
|
+
headers['Authorization'] = `Api-Key ${this.apiKey}`;
|
|
1437
|
+
}
|
|
1438
|
+
const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
|
|
1173
1439
|
method: 'POST',
|
|
1174
1440
|
body: JSON.stringify(body),
|
|
1175
|
-
headers
|
|
1176
|
-
'Content-Type': 'application/json',
|
|
1177
|
-
},
|
|
1441
|
+
headers,
|
|
1178
1442
|
});
|
|
1179
1443
|
if (res.status === 201) {
|
|
1180
1444
|
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
@@ -1219,14 +1483,22 @@ const FeedbackModal = class {
|
|
|
1219
1483
|
}
|
|
1220
1484
|
};
|
|
1221
1485
|
this.close = () => {
|
|
1486
|
+
this.doClose(false);
|
|
1487
|
+
};
|
|
1488
|
+
// Closing by clicking outside keeps the typed input, so an accidental
|
|
1489
|
+
// click does not throw away a half-written message or screenshot
|
|
1490
|
+
this.handleOverlayClick = () => {
|
|
1491
|
+
this.doClose(!this.formSuccess);
|
|
1492
|
+
};
|
|
1493
|
+
this.doClose = (preserveInput) => {
|
|
1222
1494
|
this.isAnimating = false;
|
|
1495
|
+
this.popHistoryEntry();
|
|
1223
1496
|
setTimeout(() => {
|
|
1224
1497
|
this.sending = false;
|
|
1225
1498
|
this.showModal = false;
|
|
1226
1499
|
this.showScreenshotMode = false;
|
|
1227
1500
|
this.showScreenshotTopBar = false;
|
|
1228
1501
|
this.hasSelectedElement = false;
|
|
1229
|
-
this.encodedScreenshot = null;
|
|
1230
1502
|
// Remove highlight from ALL selected elements
|
|
1231
1503
|
document.querySelectorAll('.feedback-modal-element-selected').forEach((el) => {
|
|
1232
1504
|
el.classList.remove('feedback-modal-element-selected');
|
|
@@ -1235,9 +1507,19 @@ const FeedbackModal = class {
|
|
|
1235
1507
|
this.formSuccess = false;
|
|
1236
1508
|
this.formError = false;
|
|
1237
1509
|
this.formErrorStatus = 500;
|
|
1238
|
-
|
|
1239
|
-
|
|
1510
|
+
if (!preserveInput) {
|
|
1511
|
+
this.encodedScreenshot = null;
|
|
1512
|
+
this.formMessage = '';
|
|
1513
|
+
this.formEmail = '';
|
|
1514
|
+
// The rating and the privacy checkbox are part of the same in-progress
|
|
1515
|
+
// input. Leaving the rating set meant reopening the modal showed the
|
|
1516
|
+
// previous session's choice still highlighted, and submitting then sent
|
|
1517
|
+
// that stale rating instead of one chosen for this submission.
|
|
1518
|
+
this.selectedRating = this.initialRating();
|
|
1519
|
+
this.isPrivacyChecked = false;
|
|
1520
|
+
}
|
|
1240
1521
|
this.resetOverflow();
|
|
1522
|
+
this.restoreFocus();
|
|
1241
1523
|
}, 200);
|
|
1242
1524
|
};
|
|
1243
1525
|
// Handle screenshot events from canvas editor
|
|
@@ -1247,12 +1529,16 @@ const FeedbackModal = class {
|
|
|
1247
1529
|
this.takingScreenshot = false;
|
|
1248
1530
|
this.showCanvasEditor = false;
|
|
1249
1531
|
this.autoStartCapture = false;
|
|
1532
|
+
this.focusModal();
|
|
1250
1533
|
};
|
|
1251
1534
|
this.handleScreenshotCancelled = () => {
|
|
1252
1535
|
this.showModal = true;
|
|
1253
1536
|
this.takingScreenshot = false;
|
|
1254
1537
|
this.showCanvasEditor = false;
|
|
1255
1538
|
this.autoStartCapture = false;
|
|
1539
|
+
// Focus was inside the editor that just went away, so bring it back to the
|
|
1540
|
+
// form rather than letting it fall to the top of the page
|
|
1541
|
+
this.focusModal();
|
|
1256
1542
|
};
|
|
1257
1543
|
this.handleScreenshotError = (event) => {
|
|
1258
1544
|
console.error('Screenshot error:', event.detail.error);
|
|
@@ -1264,6 +1550,7 @@ const FeedbackModal = class {
|
|
|
1264
1550
|
this.takingScreenshot = false;
|
|
1265
1551
|
this.showCanvasEditor = false;
|
|
1266
1552
|
this.autoStartCapture = false;
|
|
1553
|
+
this.focusModal();
|
|
1267
1554
|
// Auto-hide error after 8 seconds
|
|
1268
1555
|
setTimeout(() => {
|
|
1269
1556
|
this.showScreenshotError = false;
|
|
@@ -1305,6 +1592,7 @@ const FeedbackModal = class {
|
|
|
1305
1592
|
this.screenshotError = '';
|
|
1306
1593
|
this.showCanvasEditor = false;
|
|
1307
1594
|
this.autoStartCapture = false;
|
|
1595
|
+
this.apiKey = '';
|
|
1308
1596
|
this.customFont = false;
|
|
1309
1597
|
this.emailAddress = '';
|
|
1310
1598
|
this.hideEmail = false;
|
|
@@ -1322,8 +1610,12 @@ const FeedbackModal = class {
|
|
|
1322
1610
|
this.metadata = undefined;
|
|
1323
1611
|
this.fetchData = true;
|
|
1324
1612
|
this.embedded = false;
|
|
1613
|
+
this.clickOutsideClose = true;
|
|
1614
|
+
this.historyClose = true;
|
|
1615
|
+
this.escapeClose = true;
|
|
1325
1616
|
this.emailPlaceholder = 'Email address (optional)';
|
|
1326
1617
|
this.errorMessage = 'Please try again later.';
|
|
1618
|
+
this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
|
|
1327
1619
|
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
1328
1620
|
this.errorMessage404 = 'We could not find the provided project ID in PushFeedback.';
|
|
1329
1621
|
this.messagePlaceholder = 'Share your thoughts...';
|
|
@@ -1336,6 +1628,10 @@ const FeedbackModal = class {
|
|
|
1336
1628
|
this.ratingPlaceholder = 'Was this page helpful?';
|
|
1337
1629
|
this.ratingStarsPlaceholder = 'How would you rate this page?';
|
|
1338
1630
|
this.sendButtonText = 'Send';
|
|
1631
|
+
this.closeButtonLabel = 'Close feedback form';
|
|
1632
|
+
this.ratingPositiveLabel = 'Yes';
|
|
1633
|
+
this.ratingNegativeLabel = 'No';
|
|
1634
|
+
this.ratingStarLabel = 'Rate {rating} out of 5';
|
|
1339
1635
|
this.successMessage = '';
|
|
1340
1636
|
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.';
|
|
1341
1637
|
this.screenshotAttachedText = 'Screenshot attached';
|
|
@@ -1356,16 +1652,98 @@ const FeedbackModal = class {
|
|
|
1356
1652
|
this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari on desktop.';
|
|
1357
1653
|
this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
|
|
1358
1654
|
}
|
|
1655
|
+
connectedCallback() {
|
|
1656
|
+
window.addEventListener('popstate', this.handlePopState);
|
|
1657
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
1658
|
+
}
|
|
1659
|
+
disconnectedCallback() {
|
|
1660
|
+
window.removeEventListener('popstate', this.handlePopState);
|
|
1661
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
1662
|
+
}
|
|
1663
|
+
// Keeps Tab cycling inside the dialog while it is open
|
|
1664
|
+
trapFocus(event) {
|
|
1665
|
+
var _a, _b;
|
|
1666
|
+
const focusable = this.getFocusableElements();
|
|
1667
|
+
if (focusable.length === 0) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
const first = focusable[0];
|
|
1671
|
+
const last = focusable[focusable.length - 1];
|
|
1672
|
+
const active = (_a = this.modalContent) === null || _a === void 0 ? void 0 : _a.getRootNode();
|
|
1673
|
+
const current = active === null || active === void 0 ? void 0 : active.activeElement;
|
|
1674
|
+
if (current === this.modalContent) {
|
|
1675
|
+
// Focus is on the dialog container itself (where it lands on open).
|
|
1676
|
+
// Tab falls through to the first control natively; Shift+Tab would
|
|
1677
|
+
// escape the dialog, so wrap it to the last one.
|
|
1678
|
+
if (event.shiftKey) {
|
|
1679
|
+
event.preventDefault();
|
|
1680
|
+
last.focus();
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
else if (event.shiftKey && current === first) {
|
|
1684
|
+
event.preventDefault();
|
|
1685
|
+
last.focus();
|
|
1686
|
+
}
|
|
1687
|
+
else if (!event.shiftKey && current === last) {
|
|
1688
|
+
event.preventDefault();
|
|
1689
|
+
first.focus();
|
|
1690
|
+
}
|
|
1691
|
+
else if (!current || !((_b = this.modalContent) === null || _b === void 0 ? void 0 : _b.contains(current))) {
|
|
1692
|
+
// Focus escaped the dialog (or never entered it) - pull it back in
|
|
1693
|
+
event.preventDefault();
|
|
1694
|
+
first.focus();
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
// offsetParent is not usable here: it is null for `position: fixed` elements
|
|
1698
|
+
// even when they are perfectly visible, and it is non-null for
|
|
1699
|
+
// `visibility: hidden` ones, which cannot be focused. getClientRects covers
|
|
1700
|
+
// display:none and detached nodes, so visibility is the only extra check.
|
|
1701
|
+
isFocusable(el) {
|
|
1702
|
+
if (el.hidden || el.getClientRects().length === 0) {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
return getComputedStyle(el).visibility !== 'hidden';
|
|
1706
|
+
}
|
|
1707
|
+
getFocusableElements() {
|
|
1708
|
+
if (!this.modalContent) {
|
|
1709
|
+
return [];
|
|
1710
|
+
}
|
|
1711
|
+
const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1712
|
+
return Array.from(this.modalContent.querySelectorAll(selector)).filter((el) => this.isFocusable(el));
|
|
1713
|
+
}
|
|
1714
|
+
// Moves focus to the dialog container once it has rendered, so screen readers
|
|
1715
|
+
// announce the dialog title rather than jumping straight to the first control
|
|
1716
|
+
focusModal() {
|
|
1717
|
+
requestAnimationFrame(() => {
|
|
1718
|
+
if (this.modalContent) {
|
|
1719
|
+
this.modalContent.focus();
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
restoreFocus() {
|
|
1724
|
+
const target = this.previouslyFocused;
|
|
1725
|
+
this.previouslyFocused = null;
|
|
1726
|
+
if (target && typeof target.focus === 'function' && target.isConnected) {
|
|
1727
|
+
target.focus();
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
// The rating the widget starts from. Closing restores this rather than
|
|
1731
|
+
// blanking to neutral, so a consumer's configured `rating` default survives
|
|
1732
|
+
// the modal being closed and reopened.
|
|
1733
|
+
initialRating() {
|
|
1734
|
+
if (this.ratingMode == 'thumbs' && this.rating == 0) {
|
|
1735
|
+
return 5;
|
|
1736
|
+
}
|
|
1737
|
+
if (this.rating) {
|
|
1738
|
+
return this.rating;
|
|
1739
|
+
}
|
|
1740
|
+
return -1;
|
|
1741
|
+
}
|
|
1359
1742
|
componentWillLoad() {
|
|
1360
1743
|
if (this.fetchData)
|
|
1361
1744
|
this.fetchProjectData();
|
|
1362
1745
|
this.formEmail = this.emailAddress;
|
|
1363
|
-
|
|
1364
|
-
this.selectedRating = this.rating;
|
|
1365
|
-
}
|
|
1366
|
-
if (this.ratingMode == 'thumbs' && this.rating == 0) {
|
|
1367
|
-
this.selectedRating = 5;
|
|
1368
|
-
}
|
|
1746
|
+
this.selectedRating = this.initialRating();
|
|
1369
1747
|
// Automatically show modal when embedded
|
|
1370
1748
|
if (this.embedded) {
|
|
1371
1749
|
this.showModal = true;
|
|
@@ -1373,7 +1751,9 @@ const FeedbackModal = class {
|
|
|
1373
1751
|
}
|
|
1374
1752
|
async fetchProjectData() {
|
|
1375
1753
|
try {
|
|
1376
|
-
|
|
1754
|
+
// Public config endpoint: never send the API key here, it is only
|
|
1755
|
+
// needed (and only allowed to travel) on the feedback POST
|
|
1756
|
+
const response = await fetch('https://app.pushfeedback.com/api/v1/projects/' + this.project + '/');
|
|
1377
1757
|
const data = await response.json();
|
|
1378
1758
|
this.whitelabel = data.whitelabel;
|
|
1379
1759
|
this.recaptchaEnabled = data.recaptcha_enabled || false;
|
|
@@ -1446,6 +1826,12 @@ const FeedbackModal = class {
|
|
|
1446
1826
|
handleEmailInput(event) {
|
|
1447
1827
|
this.formEmail = event.target.value;
|
|
1448
1828
|
}
|
|
1829
|
+
popHistoryEntry() {
|
|
1830
|
+
if (this.historyEntryPushed) {
|
|
1831
|
+
this.historyEntryPushed = false;
|
|
1832
|
+
history.back();
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1449
1835
|
handleCheckboxChange(event) {
|
|
1450
1836
|
this.isPrivacyChecked = event.target.checked;
|
|
1451
1837
|
}
|
|
@@ -1456,28 +1842,28 @@ const FeedbackModal = class {
|
|
|
1456
1842
|
this.selectedRating = newRating;
|
|
1457
1843
|
}
|
|
1458
1844
|
render() {
|
|
1459
|
-
return (index.h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (index.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 && (index.h("div", { class: "screenshot-error-notification" }, index.h("div", { class: "screenshot-error-content" }, index.h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("circle", { cx: "12", cy: "12", r: "10" }), index.h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), index.h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), index.h("span", null, this.screenshotError), index.h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (index.h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay" })), this.showModal && (index.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) }, index.h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
|
|
1845
|
+
return (index.h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (index.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 && (index.h("div", { class: "screenshot-error-notification" }, index.h("div", { class: "screenshot-error-content" }, index.h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("circle", { cx: "12", cy: "12", r: "10" }), index.h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), index.h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), index.h("span", null, this.screenshotError), index.h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (index.h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay", "aria-hidden": "true", onClick: this.clickOutsideClose ? this.handleOverlayClick : undefined })), this.showModal && (index.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) }, index.h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
|
|
1460
1846
|
? 'feedback-modal-header--no-content'
|
|
1461
|
-
: ''}`, part: "header" }, index.h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-header-text" }, index.h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (index.h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', index.h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (index.h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (index.h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (index.h("button", { class: "feedback-modal-close", part: "close-button", onClick: this.close }, index.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" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), index.h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (index.h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (index.h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, index.h("button", { title: "
|
|
1847
|
+
: ''}`, part: "header" }, index.h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-header-text" }, index.h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (index.h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', index.h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (index.h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (index.h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (index.h("button", { class: "feedback-modal-close", part: "close-button", type: "button", "aria-label": this.closeButtonLabel, onClick: this.close }, index.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" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), index.h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (index.h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (index.h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, index.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
|
|
1462
1848
|
? 'feedback-modal-rating-button--selected'
|
|
1463
1849
|
: ''}`, part: "rating-button rating-button-positive", onClick: (event) => {
|
|
1464
1850
|
event.preventDefault();
|
|
1465
1851
|
this.handleRatingChange(1);
|
|
1466
|
-
} }, index.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" }, index.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" }))), index.h("button", { title: "
|
|
1852
|
+
} }, index.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" }, index.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" }))), index.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
|
|
1467
1853
|
? 'feedback-modal-rating-button--selected'
|
|
1468
1854
|
: ''}`, part: "rating-button rating-button-negative", onClick: (event) => {
|
|
1469
1855
|
event.preventDefault();
|
|
1470
1856
|
this.handleRatingChange(5);
|
|
1471
|
-
} }, index.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" }, index.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" })))))) : (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (index.h("button", { key: rating, class: `feedback-modal-rating-button ${this.selectedRating >= rating
|
|
1857
|
+
} }, index.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" }, index.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" })))))) : (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (index.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
|
|
1472
1858
|
? 'feedback-modal-rating-button--selected'
|
|
1473
1859
|
: ''}`, part: "rating-button", onClick: (event) => {
|
|
1474
1860
|
event.preventDefault();
|
|
1475
1861
|
this.handleRatingChange(rating);
|
|
1476
|
-
} }, index.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" }, index.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" })))))))))), index.h("div", { class: "feedback-modal-text" }, index.h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (index.h("div", { class: "feedback-modal-email" }, index.h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), index.h("div", { class: "feedback-verification" }, index.h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (index.h("div", { class: "feedback-modal-privacy", part: "privacy" }, index.h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), index.h("span", { innerHTML: this.privacyPolicyText }))), index.h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (index.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 && (index.h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, index.h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, index.h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (index.h("div", { class: "screenshot-loading" }, index.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" }, index.h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), index.h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), index.h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), index.h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), index.h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), index.h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), index.h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), index.h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
|
|
1862
|
+
} }, index.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" }, index.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" })))))))))), index.h("div", { class: "feedback-modal-text" }, index.h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (index.h("div", { class: "feedback-modal-email" }, index.h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), index.h("div", { class: "feedback-verification" }, index.h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (index.h("div", { class: "feedback-modal-privacy", part: "privacy" }, index.h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), index.h("span", { innerHTML: sanitizeHtml(this.privacyPolicyText) }))), index.h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (index.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 && (index.h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, index.h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, index.h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (index.h("div", { class: "screenshot-loading" }, index.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" }, index.h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), index.h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), index.h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), index.h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), index.h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), index.h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), index.h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), index.h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
|
|
1477
1863
|
? this.screenshotTakingText
|
|
1478
1864
|
: this.encodedScreenshot
|
|
1479
1865
|
? this.screenshotAttachedText
|
|
1480
|
-
: this.screenshotButtonText)), index.h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-success" }, index.h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (index.h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (index.h("div", { class: "feedback-modal-footer", part: "footer" }, index.h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && index.h("span", { innerHTML: this.recaptchaText }), this.recaptchaEnabled && this.footerText && ' ', this.footerText && index.h("span", { innerHTML: this.footerText }))))))));
|
|
1866
|
+
: this.screenshotButtonText)), index.h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-success" }, index.h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 401 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage401)) : this.formError && this.formErrorStatus == 403 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (index.h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (index.h("div", { class: "feedback-modal-footer", part: "footer" }, index.h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && (index.h("span", { innerHTML: sanitizeHtml(this.recaptchaText) })), this.recaptchaEnabled && this.footerText && ' ', this.footerText && index.h("span", { innerHTML: sanitizeHtml(this.footerText) }))))))));
|
|
1481
1867
|
}
|
|
1482
1868
|
componentDidRender() {
|
|
1483
1869
|
if (this.showModal) {
|
|
@@ -1487,7 +1873,17 @@ const FeedbackModal = class {
|
|
|
1487
1873
|
}
|
|
1488
1874
|
}
|
|
1489
1875
|
async openModal() {
|
|
1876
|
+
// Remember the trigger so focus can return there when the modal closes
|
|
1877
|
+
if (!this.embedded) {
|
|
1878
|
+
const active = document.activeElement;
|
|
1879
|
+
this.previouslyFocused = active && active !== document.body ? active : null;
|
|
1880
|
+
}
|
|
1490
1881
|
this.showModal = true;
|
|
1882
|
+
this.focusModal();
|
|
1883
|
+
if (this.historyClose && !this.embedded && !this.historyEntryPushed) {
|
|
1884
|
+
history.pushState({ feedbackModal: true }, '');
|
|
1885
|
+
this.historyEntryPushed = true;
|
|
1886
|
+
}
|
|
1491
1887
|
requestAnimationFrame(() => {
|
|
1492
1888
|
requestAnimationFrame(() => {
|
|
1493
1889
|
this.isAnimating = true;
|