youssefreda9 commited on
Commit
e58b76f
·
1 Parent(s): bdf0b17

feat(extension): update icons, fix logo in ui, and improve content script positioning/observers

Browse files
extension/_locales/ar/messages.json CHANGED
@@ -8,19 +8,19 @@
8
  "description": "Extension description shown in Chrome Web Store"
9
  },
10
  "contextMenuCorrect": {
11
- "message": "تصحيح مع بيان",
12
  "description": "Context menu item for correcting selected text"
13
  },
14
  "contextMenuSummarize": {
15
- "message": "تلخيص مع بيان",
16
  "description": "Context menu item for summarizing selected text"
17
  },
18
  "contextMenuDialect": {
19
- "message": "تحويل اللهجة إلى الفصحى مع بيان",
20
  "description": "Context menu: dialect→MSA"
21
  },
22
  "contextMenuQuran": {
23
- "message": "تدقيق الآية مع بيان",
24
  "description": "Context menu: Quran verify"
25
  }
26
  }
 
8
  "description": "Extension description shown in Chrome Web Store"
9
  },
10
  "contextMenuCorrect": {
11
+ "message": "تصحيح النص مع بيان",
12
  "description": "Context menu item for correcting selected text"
13
  },
14
  "contextMenuSummarize": {
15
+ "message": "تلخيص النص مع بيان",
16
  "description": "Context menu item for summarizing selected text"
17
  },
18
  "contextMenuDialect": {
19
+ "message": "تحويل اللهجة إلى العربية الفصحى مع بيان",
20
  "description": "Context menu: dialect→MSA"
21
  },
22
  "contextMenuQuran": {
23
+ "message": "تدقيق النص القرآني مع بيان",
24
  "description": "Context menu: Quran verify"
25
  }
26
  }
extension/assets/icons/icon128.png ADDED

Git LFS Details

  • SHA256: c5ee28b2d2e3fe100a925bf2d6412d6879a5f20a0a130260346cdab757811285
  • Pointer size: 130 Bytes
  • Size of remote file: 18.3 kB
extension/assets/icons/icon16.png ADDED

Git LFS Details

  • SHA256: 199d7a6107aed5a8af9dbf6457a87235c7cc2a908b04a68fd867918e824d4920
  • Pointer size: 128 Bytes
  • Size of remote file: 732 Bytes
extension/assets/icons/icon32.png ADDED

Git LFS Details

  • SHA256: d615f4654fff91e034341ac86dfb3d8f582ff57180c191b4b112c51980a80c21
  • Pointer size: 129 Bytes
  • Size of remote file: 1.98 kB
extension/assets/icons/icon48.png ADDED

Git LFS Details

  • SHA256: cfe7d9b88fb6f8e6e08d766b21cab6dd6c7519c6af35e1ab1050343064ee7d03
  • Pointer size: 129 Bytes
  • Size of remote file: 3.67 kB
extension/background.js CHANGED
@@ -51,22 +51,22 @@ function getStorage() {
51
  chrome.runtime.onInstalled.addListener(() => {
52
  chrome.contextMenus.create({
53
  id: 'bayan-correct',
54
- title: chrome.i18n.getMessage('contextMenuCorrect') || 'تصحيح مع بيان',
55
  contexts: ['selection'],
56
  });
57
  chrome.contextMenus.create({
58
  id: 'bayan-summarize',
59
- title: chrome.i18n.getMessage('contextMenuSummarize') || 'تلخيص مع بيان',
60
  contexts: ['selection'],
61
  });
62
  chrome.contextMenus.create({
63
  id: 'bayan-dialect',
64
- title: chrome.i18n.getMessage('contextMenuDialect') || 'تحويل اللهجة إلى الفصحى مع بيان',
65
  contexts: ['selection'],
66
  });
67
  chrome.contextMenus.create({
68
  id: 'bayan-quran',
69
- title: chrome.i18n.getMessage('contextMenuQuran') || 'تدقيق الآية مع بيان',
70
  contexts: ['selection'],
71
  });
72
  });
 
51
  chrome.runtime.onInstalled.addListener(() => {
52
  chrome.contextMenus.create({
53
  id: 'bayan-correct',
54
+ title: chrome.i18n.getMessage('contextMenuCorrect') || 'تصحيح النص مع بيان',
55
  contexts: ['selection'],
56
  });
57
  chrome.contextMenus.create({
58
  id: 'bayan-summarize',
59
+ title: chrome.i18n.getMessage('contextMenuSummarize') || 'تلخيص النص مع بيان',
60
  contexts: ['selection'],
61
  });
62
  chrome.contextMenus.create({
63
  id: 'bayan-dialect',
64
+ title: chrome.i18n.getMessage('contextMenuDialect') || 'تحويل اللهجة إلى العربية الفصحى مع بيان',
65
  contexts: ['selection'],
66
  });
67
  chrome.contextMenus.create({
68
  id: 'bayan-quran',
69
+ title: chrome.i18n.getMessage('contextMenuQuran') || 'تدقيق النص القرآني مع بيان',
70
  contexts: ['selection'],
71
  });
72
  });
extension/content-inline.css CHANGED
@@ -29,20 +29,20 @@
29
  }
30
 
31
  .bayan-il-punctuation {
32
- background: rgba(59, 130, 246, 0.12) !important;
33
- border-bottom: 2px solid #3b82f6 !important;
34
  border-radius: 2px !important;
35
  cursor: pointer !important;
36
  transition: background 150ms ease !important;
37
  }
38
  .bayan-il-punctuation:hover {
39
- background: rgba(59, 130, 246, 0.25) !important;
40
  }
41
 
42
  /* ── Floating Action Button (FAB) ── */
43
 
44
  .bayan-il-fab {
45
- position: absolute;
46
  z-index: 2147483646 !important;
47
  width: 34px;
48
  height: 34px;
@@ -176,8 +176,8 @@
176
  color: #f59e0b !important;
177
  }
178
  .bayan-il-badge-punctuation {
179
- background: rgba(59, 130, 246, 0.15) !important;
180
- color: #3b82f6 !important;
181
  }
182
 
183
  .bayan-il-tooltip-close {
@@ -278,3 +278,11 @@
278
  color: rgba(120, 120, 130, 0.75) !important;
279
  opacity: 0.9;
280
  }
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  .bayan-il-punctuation {
32
+ background: rgba(107, 201, 138, 0.14) !important;
33
+ border-bottom: 2px solid #6BC98A !important;
34
  border-radius: 2px !important;
35
  cursor: pointer !important;
36
  transition: background 150ms ease !important;
37
  }
38
  .bayan-il-punctuation:hover {
39
+ background: rgba(107, 201, 138, 0.28) !important;
40
  }
41
 
42
  /* ── Floating Action Button (FAB) ── */
43
 
44
  .bayan-il-fab {
45
+ position: fixed;
46
  z-index: 2147483646 !important;
47
  width: 34px;
48
  height: 34px;
 
176
  color: #f59e0b !important;
177
  }
178
  .bayan-il-badge-punctuation {
179
+ background: rgba(107, 201, 138, 0.15) !important;
180
+ color: #6BC98A !important;
181
  }
182
 
183
  .bayan-il-tooltip-close {
 
278
  color: rgba(120, 120, 130, 0.75) !important;
279
  opacity: 0.9;
280
  }
281
+
282
+ /* ── FAB dragging state ── */
283
+
284
+ .bayan-il-fab--dragging {
285
+ cursor: grabbing !important;
286
+ opacity: 0.85 !important;
287
+ transition: none !important;
288
+ }
extension/content-inline.js CHANGED
@@ -76,7 +76,7 @@
76
  const AC_MIN_CONTEXT = 3;
77
 
78
  // ══════════════════════════════════════════════════════════
79
- // 1. DETECTION
80
  // ══════════════════════════════════════════════════════════
81
 
82
  function isEditableField(el) {
@@ -87,8 +87,8 @@
87
  const type = (el.type || '').toLowerCase();
88
  return ['text', 'search', 'url', ''].includes(type);
89
  }
90
- // Contenteditable: allow on non-protected sites
91
  if (el.isContentEditable && !IS_PROTECTED) return true;
 
92
  return false;
93
  }
94
 
@@ -106,30 +106,29 @@
106
  function onFieldInput(e) {
107
  if (paused || !activeField) return;
108
 
109
- // Only a genuine user keystroke (a trusted `input` event) clears
110
- // suppression and re-enables analysis. Our own write-back dispatches an
111
- // untrusted event (isTrusted === false), and internal re-runs pass no
112
- // event at all — neither should count as a manual edit (Change 3).
113
  const isUserKeystroke = !!e && e.isTrusted === true;
114
  if (isUserKeystroke && analysisSuppressed) analysisSuppressed = false;
115
- // A real edit invalidates any selection captured at right-click time.
116
  if (isUserKeystroke) pendingSelection = null;
117
 
118
  const text = getFieldText(activeField);
119
 
120
- // Ghost-text autocomplete (textarea/input only) runs independently of
121
- // analysis. Kept active even when suppressed (separate, opt-in feature).
122
  scheduleGhost();
123
 
124
  if (analysisSuppressed) {
125
- // Model output (summarize/dialect/quran) was just written — skip the
126
- // correction pipeline and show the clean badge, not the spinner.
127
  clearHighlights();
128
  updateBadge(0);
129
  return;
130
  }
131
 
 
 
 
 
 
 
132
  if (!BayanController.hasArabic(text)) {
 
 
133
  clearHighlights();
134
  updateBadge(0);
135
  return;
@@ -241,8 +240,8 @@
241
 
242
  ghostEl = document.createElement('div');
243
  ghostEl.className = 'bayan-il-ghost';
244
- ghostEl.style.cssText = `position:absolute;top:${rect.top + window.scrollY}px;`
245
- + `left:${rect.left + window.scrollX}px;width:${rect.width}px;height:${rect.height}px;`
246
  + `font-family:${cs.fontFamily};font-size:${cs.fontSize};line-height:${cs.lineHeight};`
247
  + `padding:${cs.padding};border:${cs.border};border-color:transparent;`
248
  + `direction:${cs.direction};text-align:${cs.textAlign};overflow:hidden;`
@@ -329,14 +328,17 @@
329
  clearHighlights();
330
  if (!field || !suggs?.length) return;
331
 
 
 
 
332
  try {
333
  const rect = field.getBoundingClientRect();
334
  const cs = window.getComputedStyle(field);
335
 
336
  overlayContainer = document.createElement('div');
337
  overlayContainer.className = 'bayan-il-overlay';
338
- overlayContainer.style.cssText = `position:absolute;top:${rect.top + window.scrollY}px;`
339
- + `left:${rect.left + window.scrollX}px;width:${rect.width}px;height:${rect.height}px;`
340
  + `font-family:${cs.fontFamily};font-size:${cs.fontSize};line-height:${cs.lineHeight};`
341
  + `padding:${cs.padding};border:${cs.border};border-color:transparent;`
342
  + `direction:${cs.direction};text-align:${cs.textAlign};overflow:hidden;`
@@ -533,9 +535,32 @@
533
  // UI: FAB + Badge
534
  // ══════════════════════════════════════════════════════════
535
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  function createFloatingBtn() {
537
  if (floatingBtn) return;
538
 
 
 
539
  floatingBtn = document.createElement('div');
540
  floatingBtn.className = 'bayan-il-fab';
541
  safeHTML(floatingBtn, `
@@ -556,7 +581,47 @@
556
  document.body.appendChild(floatingBtn);
557
  badgeCount = floatingBtn.querySelector('.bayan-il-badge');
558
 
559
- floatingBtn.addEventListener('click', (e) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  e.stopPropagation();
561
  if (paused) {
562
  paused = false;
@@ -566,11 +631,7 @@
566
  }
567
  if (suggestions.length > 0) {
568
  try {
569
- // FAB = whole-field intent. Drop any selection captured by an earlier
570
- // right-click so write-back replaces the entire field, not a range.
571
  pendingSelection = null;
572
- // Tag the source field so write-back can re-find it after focus
573
- // moves to the side panel (Change 1).
574
  if (lastInteractedField) lastInteractedField.dataset.bayanSource = '1';
575
  chrome.runtime.sendMessage({ type: 'OPEN_SIDEPANEL', text: lastAnalyzedText });
576
  } catch {}
@@ -580,9 +641,16 @@
580
 
581
  function positionFab(field) {
582
  if (!floatingBtn || !field) return;
583
- const rect = field.getBoundingClientRect();
584
- floatingBtn.style.top = `${Math.max(4, rect.top + window.scrollY + 6)}px`;
585
- floatingBtn.style.left = `${Math.max(4, rect.left + window.scrollX + 6)}px`;
 
 
 
 
 
 
 
586
  floatingBtn.classList.add('bayan-il-fab--visible');
587
  }
588
 
@@ -933,13 +1001,13 @@
933
  if (activeField && floatingBtn) positionFab(activeField);
934
  if (overlayContainer && activeField) {
935
  const rect = activeField.getBoundingClientRect();
936
- overlayContainer.style.top = `${rect.top + window.scrollY}px`;
937
- overlayContainer.style.left = `${rect.left + window.scrollX}px`;
938
  }
939
  if (ghostEl && activeField) {
940
  const rect = activeField.getBoundingClientRect();
941
- ghostEl.style.top = `${rect.top + window.scrollY}px`;
942
- ghostEl.style.left = `${rect.left + window.scrollX}px`;
943
  }
944
  }, { passive: true });
945
 
@@ -953,9 +1021,28 @@
953
  }
954
  }, { passive: true });
955
 
956
- // ── MutationObserver ──
957
- observer = new MutationObserver(() => {
958
  if (activeField && !document.body.contains(activeField)) detachField();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
  });
960
  observer.observe(document.body, { childList: true, subtree: true });
961
 
 
76
  const AC_MIN_CONTEXT = 3;
77
 
78
  // ══════════════════════════════════════════════════════════
79
+ // 1. DETECTION — universal editable field finder
80
  // ══════════════════════════════════════════════════════════
81
 
82
  function isEditableField(el) {
 
87
  const type = (el.type || '').toLowerCase();
88
  return ['text', 'search', 'url', ''].includes(type);
89
  }
 
90
  if (el.isContentEditable && !IS_PROTECTED) return true;
91
+ if (!IS_PROTECTED && (el.getAttribute('role') === 'textbox' || el.getAttribute('aria-multiline') === 'true')) return true;
92
  return false;
93
  }
94
 
 
106
  function onFieldInput(e) {
107
  if (paused || !activeField) return;
108
 
 
 
 
 
109
  const isUserKeystroke = !!e && e.isTrusted === true;
110
  if (isUserKeystroke && analysisSuppressed) analysisSuppressed = false;
 
111
  if (isUserKeystroke) pendingSelection = null;
112
 
113
  const text = getFieldText(activeField);
114
 
 
 
115
  scheduleGhost();
116
 
117
  if (analysisSuppressed) {
 
 
118
  clearHighlights();
119
  updateBadge(0);
120
  return;
121
  }
122
 
123
+ // Immediately clear stale highlights when text changes — the old overlay
124
+ // was rendered for a different string and its offsets are invalid now.
125
+ if (text !== lastAnalyzedText && overlayContainer) {
126
+ clearHighlights();
127
+ }
128
+
129
  if (!BayanController.hasArabic(text)) {
130
+ suggestions = [];
131
+ lastAnalyzedText = '';
132
  clearHighlights();
133
  updateBadge(0);
134
  return;
 
240
 
241
  ghostEl = document.createElement('div');
242
  ghostEl.className = 'bayan-il-ghost';
243
+ ghostEl.style.cssText = `position:fixed;top:${rect.top}px;`
244
+ + `left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;`
245
  + `font-family:${cs.fontFamily};font-size:${cs.fontSize};line-height:${cs.lineHeight};`
246
  + `padding:${cs.padding};border:${cs.border};border-color:transparent;`
247
  + `direction:${cs.direction};text-align:${cs.textAlign};overflow:hidden;`
 
328
  clearHighlights();
329
  if (!field || !suggs?.length) return;
330
 
331
+ const currentText = getFieldText(field);
332
+ if (currentText !== originalText) return;
333
+
334
  try {
335
  const rect = field.getBoundingClientRect();
336
  const cs = window.getComputedStyle(field);
337
 
338
  overlayContainer = document.createElement('div');
339
  overlayContainer.className = 'bayan-il-overlay';
340
+ overlayContainer.style.cssText = `position:fixed;top:${rect.top}px;`
341
+ + `left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;`
342
  + `font-family:${cs.fontFamily};font-size:${cs.fontSize};line-height:${cs.lineHeight};`
343
  + `padding:${cs.padding};border:${cs.border};border-color:transparent;`
344
  + `direction:${cs.direction};text-align:${cs.textAlign};overflow:hidden;`
 
535
  // UI: FAB + Badge
536
  // ══════════════════════════════════════════════════════════
537
 
538
+ // ── Draggable FAB state ──
539
+ let fabDragging = false;
540
+ let fabDragStartX = 0;
541
+ let fabDragStartY = 0;
542
+ let fabDragOffsetX = 0;
543
+ let fabDragOffsetY = 0;
544
+ let fabCustomPos = null; // {x, y} when user has dragged to a custom position
545
+
546
+ function loadFabPosition() {
547
+ try {
548
+ const saved = localStorage.getItem('bayan_fab_pos');
549
+ if (saved) fabCustomPos = JSON.parse(saved);
550
+ } catch {}
551
+ }
552
+
553
+ function saveFabPosition() {
554
+ try {
555
+ if (fabCustomPos) localStorage.setItem('bayan_fab_pos', JSON.stringify(fabCustomPos));
556
+ } catch {}
557
+ }
558
+
559
  function createFloatingBtn() {
560
  if (floatingBtn) return;
561
 
562
+ loadFabPosition();
563
+
564
  floatingBtn = document.createElement('div');
565
  floatingBtn.className = 'bayan-il-fab';
566
  safeHTML(floatingBtn, `
 
581
  document.body.appendChild(floatingBtn);
582
  badgeCount = floatingBtn.querySelector('.bayan-il-badge');
583
 
584
+ // ── Drag handling ──
585
+ floatingBtn.addEventListener('pointerdown', (e) => {
586
+ fabDragging = false;
587
+ fabDragStartX = e.clientX;
588
+ fabDragStartY = e.clientY;
589
+ const rect = floatingBtn.getBoundingClientRect();
590
+ fabDragOffsetX = e.clientX - rect.left;
591
+ fabDragOffsetY = e.clientY - rect.top;
592
+ floatingBtn.setPointerCapture(e.pointerId);
593
+ });
594
+
595
+ floatingBtn.addEventListener('pointermove', (e) => {
596
+ if (!floatingBtn.hasPointerCapture(e.pointerId)) return;
597
+ const dx = e.clientX - fabDragStartX;
598
+ const dy = e.clientY - fabDragStartY;
599
+ if (!fabDragging && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {
600
+ fabDragging = true;
601
+ floatingBtn.classList.add('bayan-il-fab--dragging');
602
+ }
603
+ if (fabDragging) {
604
+ const x = Math.max(0, Math.min(window.innerWidth - 40, e.clientX - fabDragOffsetX));
605
+ const y = Math.max(0, Math.min(window.innerHeight - 40, e.clientY - fabDragOffsetY));
606
+ floatingBtn.style.position = 'fixed';
607
+ floatingBtn.style.left = `${x}px`;
608
+ floatingBtn.style.top = `${y}px`;
609
+ }
610
+ });
611
+
612
+ floatingBtn.addEventListener('pointerup', (e) => {
613
+ floatingBtn.releasePointerCapture(e.pointerId);
614
+ if (fabDragging) {
615
+ fabDragging = false;
616
+ floatingBtn.classList.remove('bayan-il-fab--dragging');
617
+ fabCustomPos = {
618
+ x: parseInt(floatingBtn.style.left, 10),
619
+ y: parseInt(floatingBtn.style.top, 10),
620
+ };
621
+ saveFabPosition();
622
+ return;
623
+ }
624
+ // Click (no drag)
625
  e.stopPropagation();
626
  if (paused) {
627
  paused = false;
 
631
  }
632
  if (suggestions.length > 0) {
633
  try {
 
 
634
  pendingSelection = null;
 
 
635
  if (lastInteractedField) lastInteractedField.dataset.bayanSource = '1';
636
  chrome.runtime.sendMessage({ type: 'OPEN_SIDEPANEL', text: lastAnalyzedText });
637
  } catch {}
 
641
 
642
  function positionFab(field) {
643
  if (!floatingBtn || !field) return;
644
+ if (fabCustomPos) {
645
+ floatingBtn.style.position = 'fixed';
646
+ floatingBtn.style.left = `${fabCustomPos.x}px`;
647
+ floatingBtn.style.top = `${fabCustomPos.y}px`;
648
+ } else {
649
+ const rect = field.getBoundingClientRect();
650
+ floatingBtn.style.position = 'fixed';
651
+ floatingBtn.style.top = `${Math.max(4, rect.top + 6)}px`;
652
+ floatingBtn.style.left = `${Math.max(4, rect.left + 6)}px`;
653
+ }
654
  floatingBtn.classList.add('bayan-il-fab--visible');
655
  }
656
 
 
1001
  if (activeField && floatingBtn) positionFab(activeField);
1002
  if (overlayContainer && activeField) {
1003
  const rect = activeField.getBoundingClientRect();
1004
+ overlayContainer.style.top = `${rect.top}px`;
1005
+ overlayContainer.style.left = `${rect.left}px`;
1006
  }
1007
  if (ghostEl && activeField) {
1008
  const rect = activeField.getBoundingClientRect();
1009
+ ghostEl.style.top = `${rect.top}px`;
1010
+ ghostEl.style.left = `${rect.left}px`;
1011
  }
1012
  }, { passive: true });
1013
 
 
1021
  }
1022
  }, { passive: true });
1023
 
1024
+ // ── MutationObserver — detect removed fields AND newly added editable areas ──
1025
+ observer = new MutationObserver((mutations) => {
1026
  if (activeField && !document.body.contains(activeField)) detachField();
1027
+ for (const m of mutations) {
1028
+ if (m.type !== 'childList') continue;
1029
+ for (const node of m.addedNodes) {
1030
+ if (node.nodeType !== 1) continue;
1031
+ if (isEditableField(node) && document.activeElement === node) {
1032
+ attachField(node);
1033
+ return;
1034
+ }
1035
+ const nested = node.querySelectorAll?.('textarea, input, [contenteditable="true"], [role="textbox"]');
1036
+ if (nested) {
1037
+ for (const el of nested) {
1038
+ if (isEditableField(el) && document.activeElement === el) {
1039
+ attachField(el);
1040
+ return;
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ }
1046
  });
1047
  observer.observe(document.body, { childList: true, subtree: true });
1048
 
extension/manifest.json CHANGED
@@ -8,6 +8,7 @@
8
 
9
  "icons": {
10
  "16": "assets/icons/icon16.png",
 
11
  "48": "assets/icons/icon48.png",
12
  "128": "assets/icons/icon128.png"
13
  },
@@ -16,6 +17,7 @@
16
  "default_popup": "popup.html",
17
  "default_icon": {
18
  "16": "assets/icons/icon16.png",
 
19
  "48": "assets/icons/icon48.png"
20
  },
21
  "default_title": "__MSG_extName__"
 
8
 
9
  "icons": {
10
  "16": "assets/icons/icon16.png",
11
+ "32": "assets/icons/icon32.png",
12
  "48": "assets/icons/icon48.png",
13
  "128": "assets/icons/icon128.png"
14
  },
 
17
  "default_popup": "popup.html",
18
  "default_icon": {
19
  "16": "assets/icons/icon16.png",
20
+ "32": "assets/icons/icon32.png",
21
  "48": "assets/icons/icon48.png"
22
  },
23
  "default_title": "__MSG_extName__"
extension/popup.html CHANGED
@@ -12,17 +12,7 @@
12
  <!-- ══════════════════════════════════════════════ -->
13
  <header class="bayan-header">
14
  <div class="bayan-header-brand">
15
- <svg class="bayan-logo" width="28" height="28" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
16
- <circle cx="50" cy="50" r="46" fill="url(#logoGrad)" />
17
- <path d="M30 55 Q35 35, 50 30 Q65 35, 70 55 Q65 65, 50 70 Q35 65, 30 55Z" fill="rgba(255,255,255,0.9)" />
18
- <circle cx="50" cy="42" r="4" fill="url(#logoGrad)" />
19
- <defs>
20
- <linearGradient id="logoGrad" x1="0" y1="0" x2="100" y2="100">
21
- <stop offset="0%" stop-color="#6366f1"/>
22
- <stop offset="100%" stop-color="#8b5cf6"/>
23
- </linearGradient>
24
- </defs>
25
- </svg>
26
  <span class="bayan-header-title">بيان</span>
27
  </div>
28
  <div class="bayan-header-status" id="status-indicator">
@@ -49,7 +39,7 @@
49
  </button>
50
  <button class="bayan-tab" role="tab" aria-selected="false" data-tab="quran" id="tab-quran">
51
  <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
52
- قرآن
53
  </button>
54
  <button class="bayan-tab" role="tab" aria-selected="false" data-tab="autocomplete" id="tab-autocomplete">
55
  <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
 
12
  <!-- ══════════════════════════════════════════════ -->
13
  <header class="bayan-header">
14
  <div class="bayan-header-brand">
15
+ <img src="assets/icons/icon48.png" alt="بيان" width="28" height="28" style="border-radius:6px;">
 
 
 
 
 
 
 
 
 
 
16
  <span class="bayan-header-title">بيان</span>
17
  </div>
18
  <div class="bayan-header-status" id="status-indicator">
 
39
  </button>
40
  <button class="bayan-tab" role="tab" aria-selected="false" data-tab="quran" id="tab-quran">
41
  <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
42
+ تدقيق النص القرآني
43
  </button>
44
  <button class="bayan-tab" role="tab" aria-selected="false" data-tab="autocomplete" id="tab-autocomplete">
45
  <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
extension/sidepanel/sidepanel.html CHANGED
@@ -12,17 +12,7 @@
12
  <!-- ══════════════════════════════════════════════ -->
13
  <header class="sp-header">
14
  <div class="sp-header-brand">
15
- <svg class="sp-logo" width="24" height="24" viewBox="0 0 100 100" fill="none">
16
- <circle cx="50" cy="50" r="46" fill="url(#spGrad)" />
17
- <path d="M30 55 Q35 35, 50 30 Q65 35, 70 55 Q65 65, 50 70 Q35 65, 30 55Z" fill="rgba(255,255,255,0.9)" />
18
- <circle cx="50" cy="42" r="4" fill="url(#spGrad)" />
19
- <defs>
20
- <linearGradient id="spGrad" x1="0" y1="0" x2="100" y2="100">
21
- <stop offset="0%" stop-color="#6366f1"/>
22
- <stop offset="100%" stop-color="#8b5cf6"/>
23
- </linearGradient>
24
- </defs>
25
- </svg>
26
  <span class="sp-header-title">بيان</span>
27
  </div>
28
  <div class="sp-header-status" id="status-indicator">
@@ -49,7 +39,7 @@
49
  </button>
50
  <button class="sp-tab" role="tab" aria-selected="false" data-tab="quran" id="tab-quran">
51
  <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
52
- قرآن
53
  </button>
54
  <button class="sp-tab" role="tab" aria-selected="false" data-tab="autocomplete" id="tab-autocomplete">
55
  <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
 
12
  <!-- ══════════════════════════════════════════════ -->
13
  <header class="sp-header">
14
  <div class="sp-header-brand">
15
+ <img src="../assets/icons/icon48.png" alt="بيان" width="24" height="24" style="border-radius:5px;">
 
 
 
 
 
 
 
 
 
 
16
  <span class="sp-header-title">بيان</span>
17
  </div>
18
  <div class="sp-header-status" id="status-indicator">
 
39
  </button>
40
  <button class="sp-tab" role="tab" aria-selected="false" data-tab="quran" id="tab-quran">
41
  <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
42
+ تدقيق النص القرآني
43
  </button>
44
  <button class="sp-tab" role="tab" aria-selected="false" data-tab="autocomplete" id="tab-autocomplete">
45
  <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
src/index.html CHANGED
@@ -7,7 +7,12 @@
7
  <meta name="supabase-anon-key" content="">
8
  <title>بيان - مساعد الكتابة العربية الذكي</title>
9
  <meta name="description" content="بيان — منصة ذكاء اصطناعي متكاملة لتصحيح الإملاء والنحو والترقيم وتلخيص النصوص والإكمال التلقائي وتدقيق القرآن الكريم وتحويل اللهجات — مصمّمة خصيصًا للغة العربية. جرّبه مجاناً!">
10
- <link rel="icon" type="image/svg+xml" href="/favicon.svg">
 
 
 
 
 
11
  <script src="https://cdn.tailwindcss.com"></script>
12
  <link rel="preconnect" href="https://fonts.googleapis.com">
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -59,7 +64,7 @@
59
  <button id="mobile-menu-btn" class="mobile-menu-btn md:hidden" aria-label="فتح القائمة" aria-expanded="false" aria-controls="mobile-drawer">
60
  <svg width="22" height="22" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
61
  </button>
62
- <button onclick="showPage('home')" class="flex items-center gap-3" style="background:none;border:none;cursor:pointer;" aria-label="الرئيسية"><svg width="22" height="22" fill="none" stroke="url(#navGrad)" viewBox="0 0 24 24" style="flex-shrink:0;"><defs><linearGradient id="navGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:var(--color-primary)"/><stop offset="100%" style="stop-color:var(--color-secondary)"/></linearGradient></defs><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg><span id="nav-brand" class="text-xl md:text-2xl font-bold text-gradient">بيان</span></button>
63
  </div>
64
  <div class="hidden md:flex items-center gap-8">
65
  <button onclick="showPage('home')" class="nav-link active text-base font-medium" data-page="home">الرئيسية</button>
@@ -1154,7 +1159,7 @@
1154
  <div class="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12" dir="rtl">
1155
  <div>
1156
  <div class="flex items-center gap-3 mb-4">
1157
- <svg width="24" height="24" fill="none" stroke="url(#grad1)" viewBox="0 0 24 24" style="flex-shrink:0;"><defs><linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:var(--color-primary)"/><stop offset="100%" style="stop-color:var(--color-secondary)"/></linearGradient></defs><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg><span id="footer-brand" class="text-2xl font-bold text-gradient">بيان</span>
1158
  </div>
1159
  <p class="text-sm leading-relaxed" style="color: var(--text-secondary); line-height: 1.8;">منصة ذكاء اصطناعي للكتابة العربية — تدقيق إملائي ونحوي، ترقيم، تلخيص، إكمال تلقائي، تدقيق القرآن، وتحويل اللهجات.</p>
1160
  </div>
 
7
  <meta name="supabase-anon-key" content="">
8
  <title>بيان - مساعد الكتابة العربية الذكي</title>
9
  <meta name="description" content="بيان — منصة ذكاء اصطناعي متكاملة لتصحيح الإملاء والنحو والترقيم وتلخيص النصوص والإكمال التلقائي وتدقيق القرآن الكريم وتحويل اللهجات — مصمّمة خصيصًا للغة العربية. جرّبه مجاناً!">
10
+ <meta property="og:image" content="/LOGOS/3.png">
11
+ <meta property="og:title" content="بيان — مساعدك الذكي للكتابة بالعربية">
12
+ <meta property="og:description" content="تدقيق إملائي ونحوي وترقيم، تلخيص ذكي، إكمال تلقائي، تدقيق القرآن الكريم، وتحويل اللهجات">
13
+ <meta name="twitter:card" content="summary_large_image">
14
+ <meta name="twitter:image" content="/LOGOS/3.png">
15
+ <link rel="icon" type="image/png" href="/LOGOS/1.png">
16
  <script src="https://cdn.tailwindcss.com"></script>
17
  <link rel="preconnect" href="https://fonts.googleapis.com">
18
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
64
  <button id="mobile-menu-btn" class="mobile-menu-btn md:hidden" aria-label="فتح القائمة" aria-expanded="false" aria-controls="mobile-drawer">
65
  <svg width="22" height="22" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
66
  </button>
67
+ <button onclick="showPage('home')" class="flex items-center gap-3" style="background:none;border:none;cursor:pointer;" aria-label="الرئيسية"><img src="/LOGOS/2.png" alt="بيان" width="28" height="28" style="flex-shrink:0;"><span id="nav-brand" class="text-xl md:text-2xl font-bold text-gradient">بيان</span></button>
68
  </div>
69
  <div class="hidden md:flex items-center gap-8">
70
  <button onclick="showPage('home')" class="nav-link active text-base font-medium" data-page="home">الرئيسية</button>
 
1159
  <div class="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12" dir="rtl">
1160
  <div>
1161
  <div class="flex items-center gap-3 mb-4">
1162
+ <img src="/LOGOS/2.png" alt="بيان" width="28" height="28" style="flex-shrink:0;"><span id="footer-brand" class="text-2xl font-bold text-gradient">بيان</span>
1163
  </div>
1164
  <p class="text-sm leading-relaxed" style="color: var(--text-secondary); line-height: 1.8;">منصة ذكاء اصطناعي للكتابة العربية — تدقيق إملائي ونحوي، ترقيم، تلخيص، إكمال تلقائي، تدقيق القرآن، وتحويل اللهجات.</p>
1165
  </div>
src/js/editor.js CHANGED
@@ -82,13 +82,13 @@ function initEditor() {
82
  // Debounced undo push — saves state after 500ms of no typing
83
  let _undoInputTimer = null;
84
  editor.addEventListener('input', () => {
85
- // Pipeline Hardening v3.3: Skip re-analysis when programmatically applying suggestions
86
  if (_isApplyingSuggestion) return;
87
  _lastInputTime = Date.now();
 
 
88
  updateEditorStats();
89
  updatePlaceholder();
90
  analyzeTextDelayed();
91
- // Push undo state after typing pauses
92
  clearTimeout(_undoInputTimer);
93
  _undoInputTimer = setTimeout(pushUndoState, 500);
94
  try {
 
82
  // Debounced undo push — saves state after 500ms of no typing
83
  let _undoInputTimer = null;
84
  editor.addEventListener('input', () => {
 
85
  if (_isApplyingSuggestion) return;
86
  _lastInputTime = Date.now();
87
+ clearOverlays(editor);
88
+ window.currentSuggestions = [];
89
  updateEditorStats();
90
  updatePlaceholder();
91
  analyzeTextDelayed();
 
92
  clearTimeout(_undoInputTimer);
93
  _undoInputTimer = setTimeout(pushUndoState, 500);
94
  try {
tests/phase10/reports/phase10_results.json CHANGED
The diff for this file is too large to render. See raw diff