hocmai-feedback-widget 0.2.0 → 0.2.2

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.
Files changed (57) hide show
  1. package/README.md +132 -97
  2. package/dist/capture/device-detection.d.ts +13 -0
  3. package/dist/capture/fonts.d.ts +4 -0
  4. package/dist/capture/index.d.ts +14 -0
  5. package/dist/capture/mathjax-fonts.d.ts +5 -0
  6. package/dist/capture/types.d.ts +25 -0
  7. package/dist/ckeditor.mjs +113 -0
  8. package/dist/ckeditor.mjs.map +1 -0
  9. package/dist/components/CkEditor.d.ts +14 -0
  10. package/dist/components/FeedbackWidget.d.ts +11 -0
  11. package/dist/context/FeedbackContext.d.ts +5 -0
  12. package/dist/hocmai-feedback-widget.mjs +7544 -0
  13. package/dist/hocmai-feedback-widget.mjs.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/package.json +32 -28
  16. package/src/App.tsx +143 -58
  17. package/src/capture/device-detection.ts +52 -0
  18. package/src/capture/fonts.ts +34 -0
  19. package/src/capture/index.ts +362 -0
  20. package/src/capture/mathjax-fonts.ts +143 -0
  21. package/src/capture/types.ts +34 -0
  22. package/src/ckeditor.d.ts +14 -0
  23. package/src/components/CkEditor.tsx +130 -0
  24. package/src/components/CropOverlay.tsx +29 -47
  25. package/src/components/FeedbackWidget.tsx +96 -90
  26. package/src/context/FeedbackContext.tsx +11 -4
  27. package/src/index.ts +9 -0
  28. package/src/main.tsx +7 -8
  29. package/src/utils/device.ts +14 -14
  30. package/dist/support-feedback-lib.es.js +0 -3867
  31. package/dist/support-feedback-lib.umd.js +0 -84
  32. package/dist/utils/screenshot/background-renderer.d.ts +0 -3
  33. package/dist/utils/screenshot/canvas-renderer.d.ts +0 -9
  34. package/dist/utils/screenshot/cleanup.d.ts +0 -3
  35. package/dist/utils/screenshot/device-detection.d.ts +0 -2
  36. package/dist/utils/screenshot/element-collector.d.ts +0 -10
  37. package/dist/utils/screenshot/image-renderer.d.ts +0 -21
  38. package/dist/utils/screenshot/performance.d.ts +0 -3
  39. package/dist/utils/screenshot/svg-renderer.d.ts +0 -24
  40. package/dist/utils/screenshot/text-renderer.d.ts +0 -23
  41. package/dist/utils/screenshot/types.d.ts +0 -35
  42. package/dist/utils/screenshot.d.ts +0 -9
  43. package/dist/vite.svg +0 -1
  44. package/src/App.css +0 -42
  45. package/src/assets/react.svg +0 -1
  46. package/src/index.css +0 -68
  47. package/src/utils/screenshot/background-renderer.ts +0 -54
  48. package/src/utils/screenshot/canvas-renderer.ts +0 -217
  49. package/src/utils/screenshot/cleanup.ts +0 -32
  50. package/src/utils/screenshot/device-detection.ts +0 -38
  51. package/src/utils/screenshot/element-collector.ts +0 -83
  52. package/src/utils/screenshot/image-renderer.ts +0 -148
  53. package/src/utils/screenshot/performance.ts +0 -57
  54. package/src/utils/screenshot/svg-renderer.ts +0 -126
  55. package/src/utils/screenshot/text-renderer.ts +0 -212
  56. package/src/utils/screenshot/types.ts +0 -40
  57. package/src/utils/screenshot.ts +0 -68
@@ -1,126 +0,0 @@
1
- export interface SVGRenderResult {
2
- success: boolean;
3
- error?: string;
4
- }
5
-
6
- /**
7
- * Serialize SVG element to string
8
- */
9
- function serializeSVG(svg: SVGElement): string {
10
- const serializer = new XMLSerializer();
11
- let svgString = serializer.serializeToString(svg);
12
-
13
- // Ensure xmlns is present
14
- if (!svgString.includes('xmlns=')) {
15
- svgString = svgString.replace(
16
- '<svg',
17
- '<svg xmlns="http://www.w3.org/2000/svg"'
18
- );
19
- }
20
-
21
- return svgString;
22
- }
23
-
24
- /**
25
- * Render SVG element to canvas
26
- */
27
- export async function renderSVG(
28
- svg: SVGElement,
29
- ctx: CanvasRenderingContext2D
30
- ): Promise<SVGRenderResult> {
31
- try {
32
- const rect = svg.getBoundingClientRect();
33
-
34
- // Use viewport coordinates directly
35
- const x = rect.left;
36
- const y = rect.top;
37
- const width = rect.width;
38
- const height = rect.height;
39
-
40
- // Skip very small SVGs
41
- if (width < 1 || height < 1) {
42
- return { success: true };
43
- }
44
-
45
- // Serialize SVG to string
46
- const svgString = serializeSVG(svg);
47
-
48
- // Create blob URL
49
- const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
50
- const url = URL.createObjectURL(blob);
51
-
52
- try {
53
- // Load as image
54
- const img = new Image();
55
- await new Promise<void>((resolve, reject) => {
56
- img.onload = () => resolve();
57
- img.onerror = () => reject(new Error('SVG image load failed'));
58
- img.src = url;
59
- });
60
-
61
- // Draw to canvas
62
- ctx.drawImage(img, x, y, width, height);
63
-
64
- return { success: true };
65
- } finally {
66
- // Clean up blob URL
67
- URL.revokeObjectURL(url);
68
- }
69
- } catch (error) {
70
- return {
71
- success: false,
72
- error: error instanceof Error ? error.message : 'SVG render failed'
73
- };
74
- }
75
- }
76
-
77
- /**
78
- * Render all SVG elements
79
- */
80
- export async function renderSVGs(
81
- svgs: SVGElement[],
82
- ctx: CanvasRenderingContext2D,
83
- onProgress?: (current: number, total: number) => void
84
- ): Promise<void> {
85
- for (let i = 0; i < svgs.length; i++) {
86
- await renderSVG(svgs[i], ctx);
87
-
88
- if (onProgress) {
89
- onProgress(i + 1, svgs.length);
90
- }
91
- }
92
- }
93
-
94
- /**
95
- * Check if element is MathJax rendered
96
- */
97
- export function isMathJaxElement(element: Element): boolean {
98
- return (
99
- element.classList.contains('MathJax') ||
100
- element.classList.contains('MathJax_Display') ||
101
- element.classList.contains('MJX-TeXAtom-ORD') ||
102
- element.hasAttribute('data-mathml')
103
- );
104
- }
105
-
106
- /**
107
- * Get all SVG elements from the document
108
- */
109
- export function getSVGElements(elements: Element[]): SVGElement[] {
110
- const svgs: SVGElement[] = [];
111
-
112
- for (const element of elements) {
113
- if (element instanceof SVGElement) {
114
- svgs.push(element);
115
- }
116
- }
117
-
118
- return svgs;
119
- }
120
-
121
- /**
122
- * Get all MathJax elements from the document
123
- */
124
- export function getMathJaxElements(elements: Element[]): Element[] {
125
- return elements.filter(element => isMathJaxElement(element));
126
- }
@@ -1,212 +0,0 @@
1
- /**
2
- * Text Content Renderer - Layer 4
3
- * Renders HTML text elements to canvas using SVG foreignObject technique
4
- * This preserves exact browser rendering including fonts, wrapping, and nested styles
5
- */
6
-
7
- export interface TextRenderResult {
8
- success: boolean;
9
- error?: string;
10
- }
11
-
12
- /**
13
- * Render HTML element as image using SVG foreignObject
14
- * This technique captures the exact browser rendering of text with all styles
15
- */
16
- export async function renderTextElement(
17
- element: HTMLElement,
18
- ctx: CanvasRenderingContext2D
19
- ): Promise<TextRenderResult> {
20
- try {
21
- const rect = element.getBoundingClientRect();
22
-
23
- // Use viewport coordinates directly (canvas is already translated)
24
- const x = rect.left;
25
- const y = rect.top;
26
- const width = rect.width;
27
- const height = rect.height;
28
-
29
- // Skip elements with no dimensions
30
- if (width < 1 || height < 1) {
31
- return { success: true };
32
- }
33
-
34
- // Get computed styles to preserve appearance
35
- const styles = window.getComputedStyle(element);
36
-
37
- // Clone element to avoid modifying original
38
- const clonedElement = element.cloneNode(true) as HTMLElement;
39
-
40
- // Get all relevant styles
41
- const relevantStyles = [
42
- 'color',
43
- 'font-family',
44
- 'font-size',
45
- 'font-weight',
46
- 'font-style',
47
- 'line-height',
48
- 'text-align',
49
- 'text-decoration',
50
- 'text-transform',
51
- 'letter-spacing',
52
- 'word-spacing',
53
- 'white-space',
54
- 'padding',
55
- 'margin',
56
- 'display',
57
- 'flex-direction',
58
- 'align-items',
59
- 'justify-content'
60
- ];
61
-
62
- // Build inline styles from computed styles
63
- let inlineStyle = '';
64
- relevantStyles.forEach(prop => {
65
- const value = styles.getPropertyValue(prop);
66
- if (value) {
67
- inlineStyle += `${prop}: ${value}; `;
68
- }
69
- });
70
-
71
- // Apply styles to cloned element
72
- clonedElement.setAttribute('style', inlineStyle);
73
-
74
- // Wrap in foreignObject SVG
75
- const foreignObject = `
76
- <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
77
- <foreignObject width="100%" height="100%">
78
- <div xmlns="http://www.w3.org/1999/xhtml" style="width: ${width}px; height: ${height}px; overflow: hidden;">
79
- ${clonedElement.outerHTML}
80
- </div>
81
- </foreignObject>
82
- </svg>
83
- `;
84
-
85
- // Create blob URL
86
- const blob = new Blob([foreignObject], { type: 'image/svg+xml;charset=utf-8' });
87
- const url = URL.createObjectURL(blob);
88
-
89
- try {
90
- // Load as image with timeout
91
- const img = new Image();
92
- await new Promise<void>((resolve, reject) => {
93
- const timeout = setTimeout(() => {
94
- reject(new Error('Text element render timeout'));
95
- }, 1000); // 1 second timeout
96
-
97
- img.onload = () => {
98
- clearTimeout(timeout);
99
- resolve();
100
- };
101
-
102
- img.onerror = () => {
103
- clearTimeout(timeout);
104
- reject(new Error('Text element image load failed'));
105
- };
106
-
107
- img.src = url;
108
- });
109
-
110
- // Draw to canvas
111
- ctx.drawImage(img, x, y, width, height);
112
-
113
- return { success: true };
114
- } finally {
115
- // Clean up blob URL
116
- URL.revokeObjectURL(url);
117
- }
118
- } catch (error) {
119
- console.warn('[TextRenderer] Failed to render text element:', error);
120
- return {
121
- success: false,
122
- error: error instanceof Error ? error.message : 'Text render failed'
123
- };
124
- }
125
- }
126
-
127
- /**
128
- * Check if element contains renderable text content
129
- */
130
- function hasTextContent(element: HTMLElement): boolean {
131
- // Skip non-visible elements
132
- const styles = window.getComputedStyle(element);
133
- if (styles.display === 'none' || styles.visibility === 'hidden') {
134
- return false;
135
- }
136
-
137
- // Check if element or its children have text
138
- const text = element.textContent?.trim();
139
- if (!text || text.length === 0) {
140
- return false;
141
- }
142
-
143
- return true;
144
- }
145
-
146
- /**
147
- * Get text elements suitable for rendering
148
- * Selects leaf elements that contain actual text content
149
- */
150
- export function getTextElements(elements: Element[]): HTMLElement[] {
151
- const textElements: HTMLElement[] = [];
152
-
153
- for (const element of elements) {
154
- if (!(element instanceof HTMLElement)) continue;
155
-
156
- const tag = element.tagName;
157
-
158
- // Skip elements that are rendered by other layers
159
- if (['IMG', 'SVG', 'CANVAS', 'VIDEO', 'IFRAME', 'SCRIPT', 'STYLE', 'NOSCRIPT'].includes(tag)) {
160
- continue;
161
- }
162
-
163
- // Focus on text-containing elements
164
- // Prioritize leaf elements (P, SPAN, H1-H6, A, etc.)
165
- const isTextElement = [
166
- 'P', 'SPAN', 'DIV', 'A', 'BUTTON', 'LABEL', 'LI',
167
- 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
168
- 'TD', 'TH', 'STRONG', 'EM', 'B', 'I', 'U',
169
- 'CODE', 'PRE', 'BLOCKQUOTE'
170
- ].includes(tag);
171
-
172
- if (isTextElement && hasTextContent(element)) {
173
- // Check if this is a leaf element or has only text children
174
- const hasBlockChildren = Array.from(element.children).some(child => {
175
- const childTag = child.tagName;
176
- return ['DIV', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'TABLE'].includes(childTag);
177
- });
178
-
179
- // Only add if it's a leaf element (no block children)
180
- if (!hasBlockChildren) {
181
- textElements.push(element);
182
- }
183
- }
184
- }
185
-
186
- return textElements;
187
- }
188
-
189
- /**
190
- * Render all text elements to canvas
191
- */
192
- export async function renderTextContent(
193
- elements: Element[],
194
- ctx: CanvasRenderingContext2D,
195
- onProgress?: (current: number, total: number) => void
196
- ): Promise<void> {
197
- const textElements = getTextElements(elements);
198
-
199
- console.log('[TextRenderer] Rendering', textElements.length, 'text elements');
200
-
201
- for (let i = 0; i < textElements.length; i++) {
202
- const result = await renderTextElement(textElements[i], ctx);
203
-
204
- if (!result.success) {
205
- console.warn('[TextRenderer] Failed to render element:', textElements[i], result.error);
206
- }
207
-
208
- if (onProgress) {
209
- onProgress(i + 1, textElements.length);
210
- }
211
- }
212
- }
@@ -1,40 +0,0 @@
1
- export interface Rect {
2
- x: number;
3
- y: number;
4
- width: number;
5
- height: number;
6
- }
7
-
8
- export interface DeviceCapabilities {
9
- maxCanvasSize: number;
10
- recommendedScale: number;
11
- recommendedQuality: number;
12
- chunkSize: number;
13
- yieldInterval: number;
14
- maxTimeout: number;
15
- }
16
-
17
- export interface CaptureOptions {
18
- quality: number;
19
- scale: number;
20
- includeMathJax: boolean;
21
- timeout: number;
22
- }
23
-
24
- export interface CaptureProgress {
25
- phase: 'analyzing' | 'rendering' | 'exporting';
26
- progress: number; // 0-100
27
- message: string;
28
- }
29
-
30
- export interface CleanupTracker {
31
- objectUrls: string[];
32
- tempCanvases: HTMLCanvasElement[];
33
- }
34
-
35
- export interface CaptureBounds {
36
- left: number;
37
- top: number;
38
- right: number;
39
- bottom: number;
40
- }
@@ -1,68 +0,0 @@
1
- import type { Rect, CaptureProgress } from './screenshot/types';
2
- import { captureWithCanvas } from './screenshot/canvas-renderer';
3
- import { domToPng } from 'modern-screenshot';
4
-
5
- // Re-export Rect type for backwards compatibility
6
- export type { Rect };
7
-
8
- /**
9
- * Capture screenshot with 3-tier fallback strategy:
10
- * 1. Canvas-based capture (primary)
11
- * 2. modern-screenshot library (fallback)
12
- * 3. Return null (final fallback)
13
- */
14
- export const captureScreenshot = async (
15
- cropRect?: Rect,
16
- onProgress?: (progress: CaptureProgress) => void
17
- ): Promise<string | null> => {
18
- console.log('[Screenshot] Starting capture with 3-tier fallback strategy');
19
-
20
- try {
21
- // Tier 1: Try canvas-based capture
22
- console.log('[Screenshot] Tier 1: Attempting canvas-based capture...');
23
- const canvasResult = await captureWithCanvas(cropRect, {}, onProgress);
24
-
25
- if (canvasResult) {
26
- console.log('[Screenshot] Tier 1 successful - canvas-based capture');
27
- return canvasResult;
28
- }
29
-
30
- console.warn('[Screenshot] Tier 1 failed, falling back to Tier 2');
31
- } catch (error) {
32
- console.warn('[Screenshot] Tier 1 error:', error);
33
- }
34
-
35
- try {
36
- // Tier 2: Fallback to modern-screenshot
37
- console.log('[Screenshot] Tier 2: Attempting modern-screenshot...');
38
-
39
- const element = document.documentElement;
40
- const targetX = cropRect ? cropRect.x : 0;
41
- const targetY = cropRect ? cropRect.y : 0;
42
- const targetWidth = cropRect ? cropRect.width : window.innerWidth;
43
- const targetHeight = cropRect ? cropRect.height : window.innerHeight;
44
-
45
- const scrollX = window.scrollX;
46
- const scrollY = window.scrollY;
47
- const offsetX = scrollX + targetX;
48
- const offsetY = scrollY + targetY;
49
-
50
- const dataUrl = await domToPng(element, {
51
- quality: 0.9,
52
- width: targetWidth,
53
- height: targetHeight,
54
- style: {
55
- transform: `translate(${-offsetX}px, ${-offsetY}px)`
56
- }
57
- });
58
-
59
- console.log('[Screenshot] Tier 2 successful - modern-screenshot');
60
- return dataUrl;
61
- } catch (error) {
62
- console.error('[Screenshot] Tier 2 failed:', error);
63
- }
64
-
65
- // Tier 3: All methods failed
66
- console.error('[Screenshot] All capture methods failed');
67
- return null;
68
- };