hocmai-feedback-widget 0.2.1 → 0.2.3

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 (59) hide show
  1. package/README.md +78 -113
  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 +13 -1
  11. package/dist/context/FeedbackContext.d.ts +14 -2
  12. package/dist/hocmai-feedback-widget.mjs +7550 -0
  13. package/dist/hocmai-feedback-widget.mjs.map +1 -0
  14. package/dist/index.d.ts +4 -2
  15. package/package.json +39 -29
  16. package/src/App.tsx +147 -58
  17. package/src/capture/device-detection.ts +52 -0
  18. package/src/capture/fonts.ts +34 -0
  19. package/src/capture/index.ts +361 -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 +105 -93
  26. package/src/context/FeedbackContext.tsx +49 -9
  27. package/src/index.ts +9 -3
  28. package/src/main.tsx +7 -9
  29. package/src/utils/device.ts +14 -14
  30. package/dist/support-feedback-lib.es.js +0 -3946
  31. package/dist/support-feedback-lib.umd.js +0 -84
  32. package/dist/test-mobile-screenshot.html +0 -211
  33. package/dist/test-simple-capture.html +0 -107
  34. package/dist/utils/screenshot/background-renderer.d.ts +0 -3
  35. package/dist/utils/screenshot/canvas-renderer.d.ts +0 -9
  36. package/dist/utils/screenshot/cleanup.d.ts +0 -3
  37. package/dist/utils/screenshot/device-detection.d.ts +0 -16
  38. package/dist/utils/screenshot/element-collector.d.ts +0 -10
  39. package/dist/utils/screenshot/image-renderer.d.ts +0 -21
  40. package/dist/utils/screenshot/performance.d.ts +0 -3
  41. package/dist/utils/screenshot/svg-renderer.d.ts +0 -24
  42. package/dist/utils/screenshot/text-renderer.d.ts +0 -23
  43. package/dist/utils/screenshot/types.d.ts +0 -35
  44. package/dist/utils/screenshot.d.ts +0 -9
  45. package/dist/vite.svg +0 -1
  46. package/src/App.css +0 -42
  47. package/src/assets/react.svg +0 -1
  48. package/src/index.css +0 -68
  49. package/src/utils/screenshot/background-renderer.ts +0 -54
  50. package/src/utils/screenshot/canvas-renderer.ts +0 -343
  51. package/src/utils/screenshot/cleanup.ts +0 -32
  52. package/src/utils/screenshot/device-detection.ts +0 -90
  53. package/src/utils/screenshot/element-collector.ts +0 -134
  54. package/src/utils/screenshot/image-renderer.ts +0 -148
  55. package/src/utils/screenshot/performance.ts +0 -57
  56. package/src/utils/screenshot/svg-renderer.ts +0 -149
  57. package/src/utils/screenshot/text-renderer.ts +0 -212
  58. package/src/utils/screenshot/types.ts +0 -40
  59. package/src/utils/screenshot.ts +0 -83
@@ -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,83 +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
- // Use Visual Viewport API for accurate mobile dimensions
40
- const getViewportWidth = () => {
41
- if (window.visualViewport) {
42
- return window.visualViewport.width;
43
- }
44
- return document.documentElement.clientWidth || window.innerWidth;
45
- };
46
-
47
- const getViewportHeight = () => {
48
- if (window.visualViewport) {
49
- return window.visualViewport.height;
50
- }
51
- return document.documentElement.clientHeight || window.innerHeight;
52
- };
53
-
54
- const element = document.documentElement;
55
- const targetX = cropRect ? cropRect.x : 0;
56
- const targetY = cropRect ? cropRect.y : 0;
57
- const targetWidth = cropRect ? cropRect.width : getViewportWidth();
58
- const targetHeight = cropRect ? cropRect.height : getViewportHeight();
59
-
60
- const scrollX = window.scrollX;
61
- const scrollY = window.scrollY;
62
- const offsetX = scrollX + targetX;
63
- const offsetY = scrollY + targetY;
64
-
65
- const dataUrl = await domToPng(element, {
66
- quality: 0.9,
67
- width: targetWidth,
68
- height: targetHeight,
69
- style: {
70
- transform: `translate(${-offsetX}px, ${-offsetY}px)`
71
- }
72
- });
73
-
74
- console.log('[Screenshot] Tier 2 successful - modern-screenshot');
75
- return dataUrl;
76
- } catch (error) {
77
- console.error('[Screenshot] Tier 2 failed:', error);
78
- }
79
-
80
- // Tier 3: All methods failed
81
- console.error('[Screenshot] All capture methods failed');
82
- return null;
83
- };