rails-markup 1.4.5 → 1.4.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f116e4650e408eb4c0157a1ecdfe528025dcc1fe56e48d56d5d878e6aeb76625
4
- data.tar.gz: 989ea2f307e753b10c68e257b8206b7b3fe8fef47b1d3eeace916e1fc50f8306
3
+ metadata.gz: 71cb0dfae1e50403d787bb64d3760a95181fbf2e4fbd7ce6c57629d226fd2dae
4
+ data.tar.gz: ba57fa0fde60fd7b8c314ad63d8b2df16c3580076269392991928e81e3a6d53a
5
5
  SHA512:
6
- metadata.gz: dbcf8ce0f1c775122d1b58440dd648c7e02f22c63f25a0817d514e4951078ee283d859d3c7ac6ee5da37695feaaa756411d368b450a9f14be09050f6a99bf14f
7
- data.tar.gz: 4b2d4640b67a39c5e8ce41a99481afe6cf153112657919d9e2b3160df732c319fb34ecbbd458db32974bdb545b68d17a4c3ab04dcc62095b04bcb58f9a17a480
6
+ metadata.gz: 7bb524122341ef07f5ea882964f931e4bb766e4c493a7fb0493f0a0c1ca7fb793055ad513219df8f13bafc578b2ea5113bba0fe86f3b51eabaa15b4e12b36cb5
7
+ data.tar.gz: 7f2f47ab1f1692dcb9ff5127b7691dd5ca2691a1a2944056d6e65bc9f2cf5dd998c2e58554722f23b80df36e64aa486f94b5f746b60e4a95d01e1a3f93898e16
@@ -27,6 +27,7 @@
27
27
  activeFilter: "all",
28
28
  editingId: null,
29
29
  _currentScreenshot: null,
30
+ toolbarSettings: {},
30
31
  _storageError: null,
31
32
  _outboxFlushScheduled: false,
32
33
  _outboxFlushTimer: null,
@@ -44,6 +45,8 @@
44
45
  _syncMalformedLimit: 3,
45
46
  _syncUnavailable: null,
46
47
  legacyStorageEndpoint: null,
48
+ _bootstrapOptions: null,
49
+ _settingsPanelOpen: false,
47
50
 
48
51
  // Drawing state
49
52
  drawingMode: null, // null | "arrow" | "rect" | "highlight"
@@ -9,13 +9,17 @@
9
9
  @keyframes rm-toast-out { from{opacity:1;transform:translateY(0) scale(1)} to{opacity:0;transform:translateY(16px) scale(0.95)} }
10
10
  #rm-toolbar-root { position:fixed; inset:0; pointer-events:none; z-index:9979; }
11
11
  #rm-toolbar-root * { box-sizing:border-box; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif; }
12
- .rm-fab, .rm-panel-toggle, .rm-panel, .rm-popup { pointer-events:auto; }
12
+ .rm-fab, .rm-panel-toggle, .rm-toolbar-settings, .rm-panel, .rm-popup { pointer-events:auto; }
13
13
  .rm-fab { position:fixed; z-index:9980; border-radius:50%; border:none; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.2s; box-shadow:0 4px 12px rgba(0,0,0,0.15); }
14
14
  .rm-fab svg { width:20px; height:20px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
15
15
  .rm-fab-badge { position:absolute; top:-4px; right:-4px; min-width:20px; height:20px; padding:0 4px; border-radius:10px; background:#ef4444; color:#fff; font-size:11px; font-weight:700; display:none; align-items:center; justify-content:center; }
16
16
  .rm-panel-toggle { position:fixed; z-index:9980; width:32px; height:32px; border-radius:50%; border:1px solid #e5e7eb; background:rgba(255,255,255,0.9); cursor:pointer; display:none; align-items:center; justify-content:center; color:#6b7280; transition:all 0.2s; backdrop-filter:blur(8px); }
17
17
  .rm-panel-toggle:hover { color:#4361ee; }
18
18
  .rm-panel-toggle svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
19
+ .rm-panel-toggle-badge { position:absolute; top:-4px; right:-4px; min-width:18px; height:18px; padding:0 4px; border-radius:9px; background:#ef4444; color:#fff; font-size:10px; font-weight:700; display:none; align-items:center; justify-content:center; }
20
+ .rm-toolbar-settings { position:fixed; z-index:9980; width:32px; height:32px; border-radius:50%; border:1px solid #e5e7eb; background:#fff; cursor:pointer; display:none; align-items:center; justify-content:center; color:#6b7280; transition:all 0.2s; backdrop-filter:blur(8px); }
21
+ .rm-toolbar-settings:hover { color:#374151; background:#f9fafb; }
22
+ .rm-toolbar-settings svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
19
23
  .rm-toast-container { position:fixed; z-index:9983; display:flex; flex-direction:column; gap:8px; pointer-events:none; }
20
24
  .rm-pins-container { position:absolute; top:0; left:0; width:100%; z-index:9979; pointer-events:none; }
21
25
  .rm-pin { pointer-events:auto; }
@@ -47,7 +51,13 @@
47
51
  .rm-panel { display:none; position:fixed; z-index:9981; width:380px; max-width:calc(100vw - 48px); max-height:60vh; background:rgba(255,255,255,0.95); backdrop-filter:blur(12px); border-radius:16px; box-shadow:0 25px 50px rgba(0,0,0,0.1); border:1px solid rgba(229,231,235,0.8); flex-direction:column; }
48
52
  .rm-panel-header { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid #f3f4f6; }
49
53
  .rm-panel-header h3 { font-size:14px; font-weight:600; color:#1f2937; }
54
+ .rm-panel-header-actions { display:flex; align-items:center; gap:6px; }
50
55
  .rm-panel-count { min-width:20px; text-align:center; padding:2px 6px; font-size:10px; font-weight:600; border-radius:10px; }
56
+ .rm-settings { padding:12px 16px 4px; border-bottom:1px solid #f3f4f6; }
57
+ .rm-settings[hidden] { display:none; }
58
+ .rm-settings-group { display:flex; flex-direction:column; gap:6px; margin-bottom:10px; }
59
+ .rm-settings-label { font-size:11px; font-weight:600; color:#6b7280; text-transform:uppercase; letter-spacing:0.05em; }
60
+ .rm-settings-options { display:flex; flex-wrap:wrap; gap:6px; }
51
61
  .rm-panel-close { padding:6px; color:#d1d5db; background:none; border:none; cursor:pointer; border-radius:8px; }
52
62
  .rm-panel-close:hover { color:#6b7280; }
53
63
  .rm-panel-close svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
@@ -12,14 +12,17 @@
12
12
  const posMap = { bl: "bottom:24px;left:24px;", br: "bottom:24px;right:24px;", tl: "top:24px;left:24px;", tr: "top:24px;right:24px;" };
13
13
  const fabPos = posMap[this.position] || posMap.bl;
14
14
 
15
- // Size: default (48px), compact (40px), slim (32px)
16
- const sizeMap = { "default": { dim: 48, icon: 20 }, compact: { dim: 40, icon: 18 }, slim: { dim: 32, icon: 16 } };
15
+ // Size: default (40px), compact (36px), slim (32px)
16
+ const sizeMap = { "default": { dim: 40, icon: 18 }, compact: { dim: 36, icon: 16 }, slim: { dim: 32, icon: 16 } };
17
17
  const fabSize = sizeMap[this.size] || sizeMap["default"];
18
18
 
19
- // Panel offset matches FAB position
19
+ const controlSize = 32;
20
+ const controlGap = 8;
21
+ const controlStart = this.fabVisible ? fabSize.dim + controlGap + 24 : 24;
20
22
  const isRight = this.position === "br" || this.position === "tr";
21
23
  const isTop = this.position === "tl" || this.position === "tr";
22
- const panelToggleStyle = isRight ? `${isTop ? 'top' : 'bottom'}:24px;right:${fabSize.dim + 8 + 24}px;` : `${isTop ? 'top' : 'bottom'}:24px;left:${fabSize.dim + 8 + 24}px;`;
24
+ const settingsToggleStyle = isRight ? `${isTop ? 'top' : 'bottom'}:24px;right:${controlStart}px;` : `${isTop ? 'top' : 'bottom'}:24px;left:${controlStart}px;`;
25
+ const panelToggleStyle = isRight ? `${isTop ? 'top' : 'bottom'}:24px;right:${controlStart + controlSize + controlGap}px;` : `${isTop ? 'top' : 'bottom'}:24px;left:${controlStart + controlSize + controlGap}px;`;
23
26
  const panelStyle = isRight
24
27
  ? `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 32}px;right:24px;`
25
28
  : `${isTop ? 'top' : 'bottom'}:${fabSize.dim + 32}px;left:24px;`;
@@ -30,10 +33,13 @@
30
33
  root.innerHTML = `
31
34
  <button class="rm-fab" id="rm-fab" style="${fabPos}width:${fabSize.dim}px;height:${fabSize.dim}px;background:${accentBg};color:#fff;${this.fabVisible ? "" : "display:none;"}" title="Toggle annotation mode" aria-label="Toggle annotation mode" aria-expanded="false" aria-controls="rm-panel">
32
35
  <svg viewBox="0 0 24 24" style="width:${fabSize.icon}px;height:${fabSize.icon}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
33
- <span class="rm-fab-badge" id="rm-fab-badge"></span>
36
+ </button>
37
+ <button class="rm-toolbar-settings" id="rm-settings-toggle" style="${settingsToggleStyle}" title="Toolbar settings" aria-label="Toolbar settings" aria-expanded="false" aria-controls="rm-settings-panel">
38
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 8a4 4 0 100 8 4 4 0 000-8zm8.94 3.06l-1.3-.75.08-1.36a1 1 0 00-.3-.79l-1.72-1.72a1 1 0 00-.79-.3l-1.36.08-.75-1.3a1 1 0 00-.86-.5h-2.44a1 1 0 00-.86.5l-.75 1.3-1.36-.08a1 1 0 00-.79.3L4.58 8.86a1 1 0 00-.3.79l.08 1.36-1.3.75a1 1 0 00-.5.86v2.44a1 1 0 00.5.86l1.3.75-.08 1.36a1 1 0 00.3.79l1.72 1.72a1 1 0 00.79.3l1.36-.08.75 1.3a1 1 0 00.86.5h2.44a1 1 0 00.86-.5l.75-1.3 1.36.08a1 1 0 00.79-.3l1.72-1.72a1 1 0 00.3-.79l-.08-1.36 1.3-.75a1 1 0 00.5-.86v-2.44a1 1 0 00-.5-.86zm-2.24 1.44l-1.14.66a1 1 0 00-.5.86l.08 1.2-.98.98-1.2-.08a1 1 0 00-.86.5l-.66 1.14h-1.96l-.66-1.14a1 1 0 00-.86-.5l-1.2.08-.98-.98.08-1.2a1 1 0 00-.5-.86l-1.14-.66v-1.96l1.14-.66a1 1 0 00.5-.86l-.08-1.2.98-.98 1.2.08a1 1 0 00.86-.5l.66-1.14h1.96l.66 1.14a1 1 0 00.86.5l1.2-.08.98.98-.08 1.2a1 1 0 00.5.86l1.14.66v1.96z"/></svg>
34
39
  </button>
35
40
  <button class="rm-panel-toggle" id="rm-panel-toggle" style="${panelToggleStyle}" title="View annotations" aria-label="View annotations" aria-controls="rm-panel">
36
41
  <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h7"/></svg>
42
+ <span class="rm-panel-toggle-badge" id="rm-panel-toggle-badge"></span>
37
43
  </button>
38
44
  <div class="rm-toast-container" id="rm-toast-container" style="${toastStyle}"></div>
39
45
  <div class="rm-popup" id="rm-popup" role="dialog" aria-label="Add annotation" aria-modal="false">
@@ -61,9 +67,14 @@
61
67
  <h3>Feedback</h3>
62
68
  <span class="rm-panel-count" id="rm-panel-count" style="background:${accentLight};color:${accentText}">0</span>
63
69
  </div>
64
- <button class="rm-panel-close" id="rm-panel-close" aria-label="Close annotations panel">
65
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 18L18 6M6 6l12 12"/></svg>
66
- </button>
70
+ <div class="rm-panel-header-actions">
71
+ <button class="rm-panel-close" id="rm-panel-close" aria-label="Close annotations panel">
72
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 18L18 6M6 6l12 12"/></svg>
73
+ </button>
74
+ </div>
75
+ </div>
76
+ <div class="rm-settings" id="rm-settings-panel" hidden>
77
+ ${this._toolbarSettingsMarkup()}
67
78
  </div>
68
79
  <div class="rm-filter-chips" id="rm-filter-chips">
69
80
  <button class="rm-chip rm-chip-active" data-filter="all" style="background:${accentBg}">All</button>
@@ -4,6 +4,7 @@
4
4
  document.getElementById("rm-fab").addEventListener("click", () => self.toggleMode());
5
5
  document.getElementById("rm-panel-toggle").addEventListener("click", () => self.togglePanel());
6
6
  document.getElementById("rm-panel-close").addEventListener("click", () => self.togglePanel());
7
+ document.getElementById("rm-settings-toggle").addEventListener("click", () => self._toggleSettings());
7
8
  document.getElementById("rm-btn-cancel").addEventListener("click", () => self._closePopup());
8
9
  document.getElementById("rm-btn-submit").addEventListener("click", (e) => self.submitAnnotation(e));
9
10
  document.getElementById("rm-popup-input").addEventListener("input", () => self._updateCharCount());
@@ -15,6 +16,13 @@
15
16
  // Custom menus (intent/severity/status) — button+menu, never native selects
16
17
  // so host FormSelect/Select2 enhancers cannot rewrite our DOM (#4).
17
18
  this.root.addEventListener("click", (e) => {
19
+ const setting = e.target.closest("[data-setting]");
20
+ if (setting) {
21
+ e.preventDefault();
22
+ e.stopPropagation();
23
+ this._setToolbarSetting(setting.dataset.setting, setting.dataset.value);
24
+ return;
25
+ }
18
26
  const option = e.target.closest(".rm-menu-option");
19
27
  if (option) {
20
28
  e.preventDefault();
@@ -147,10 +155,6 @@
147
155
  this.active = !this.active;
148
156
  if (this.active) {
149
157
  this._activateMode();
150
- // Always open panel when activating annotation mode
151
- if (document.getElementById("rm-panel").style.display !== "flex") {
152
- this.togglePanel();
153
- }
154
158
  } else {
155
159
  this._deactivateMode();
156
160
  }
@@ -161,7 +165,7 @@
161
165
  const iconSize = this._fabIconSize();
162
166
  fab.style.transform = "scale(0.9)";
163
167
  fab.style.boxShadow = `0 0 0 3px ${this._accentBg()}, 0 0 0 6px rgba(99,102,241,0.2)`;
164
- fab.innerHTML = `<svg viewBox="0 0 24 24" style="width:${iconSize}px;height:${iconSize}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><path d="M6 18L18 6M6 6l12 12"/></svg><span class="rm-fab-badge" id="rm-fab-badge">${document.getElementById("rm-fab-badge")?.textContent || ""}</span>`;
168
+ fab.innerHTML = `<svg viewBox="0 0 24 24" style="width:${iconSize}px;height:${iconSize}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><path d="M6 18L18 6M6 6l12 12"/></svg>`;
165
169
  document.addEventListener("mousemove", this._boundMouseMove, true);
166
170
  document.addEventListener("mousedown", this._boundMouseDown, true);
167
171
  document.addEventListener("mouseup", this._boundMouseUp, true);
@@ -177,7 +181,7 @@
177
181
  const iconSize = this._fabIconSize();
178
182
  fab.style.transform = "";
179
183
  fab.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
180
- fab.innerHTML = `<svg viewBox="0 0 24 24" style="width:${iconSize}px;height:${iconSize}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg><span class="rm-fab-badge" id="rm-fab-badge">${this.annotations.length || ""}</span>`;
184
+ fab.innerHTML = `<svg viewBox="0 0 24 24" style="width:${iconSize}px;height:${iconSize}px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>`;
181
185
  this._updateCount();
182
186
  }
183
187
  document.removeEventListener("mousemove", this._boundMouseMove, true);
@@ -3,8 +3,41 @@
3
3
  const endpoint = (this.endpoint || "/feedback/api").replace(/\/+$/, "") || "/";
4
4
  return `rm-annotations:${encodeURIComponent(endpoint)}`;
5
5
  },
6
+ _toolbarSettingsKey() {
7
+ const endpoint = (this.endpoint || "/feedback/api").replace(/\/+$/, "") || "/";
8
+ return `rm-toolbar-settings:${encodeURIComponent(endpoint)}`;
9
+ },
6
10
  _pageUrl() { return window.location.pathname + window.location.search; },
7
11
  _pageStorageKey() { return this._storageKey() + ":" + this._pageUrl(); },
12
+ _normalizeToolbarSettings(candidate = {}) {
13
+ const source = candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : {};
14
+ const normalized = {};
15
+ if (this._validToolbarAccent(source.accent)) normalized.accent = source.accent;
16
+ if (this._validToolbarPosition(source.position)) normalized.position = source.position;
17
+ if (this._validToolbarSize(source.size)) normalized.size = source.size;
18
+ if (typeof source.fabVisible === "boolean") normalized.fabVisible = source.fabVisible;
19
+ if (typeof source.enableScreenshots === "boolean") normalized.enableScreenshots = source.enableScreenshots;
20
+ return normalized;
21
+ },
22
+ _loadToolbarSettings() {
23
+ try {
24
+ const raw = localStorage.getItem(this._toolbarSettingsKey());
25
+ if (!raw) return {};
26
+ return this._normalizeToolbarSettings(JSON.parse(raw));
27
+ } catch (e) {
28
+ console.warn("[rails-markup] settings load failed:", e);
29
+ return {};
30
+ }
31
+ },
32
+ _saveToolbarSettings(settings = this.toolbarSettings || {}) {
33
+ try {
34
+ localStorage.setItem(this._toolbarSettingsKey(), JSON.stringify(this._normalizeToolbarSettings(settings)));
35
+ return true;
36
+ } catch (e) {
37
+ console.warn("[rails-markup] settings save failed:", e);
38
+ return false;
39
+ }
40
+ },
8
41
  _saveToStorage() {
9
42
  try {
10
43
  // Cross-tab storage-event merging is deferred: safely reconciling ordered
@@ -72,6 +105,7 @@
72
105
  this.outbox = snapshot.outbox;
73
106
  this.nextId = snapshot.nextId;
74
107
  this.legacyMigrations = snapshot.legacyMigrations;
108
+ this.toolbarSettings = snapshot.toolbarSettings || {};
75
109
  this._storageError = "Changes could not be saved in this browser. Free storage space and try again.";
76
110
  this._renderPins();
77
111
  this._rebuildList();
@@ -87,7 +121,8 @@
87
121
  annotations: this.annotations,
88
122
  outbox: this.outbox,
89
123
  nextId: this.nextId,
90
- legacyMigrations: this.legacyMigrations
124
+ legacyMigrations: this.legacyMigrations,
125
+ toolbarSettings: this.toolbarSettings
91
126
  }));
92
127
  },
93
128
  _mergeDirtyFields(...collections) {
@@ -329,7 +329,7 @@
329
329
  const count = this.annotations.length;
330
330
  const countEl = document.getElementById("rm-panel-count");
331
331
  if (countEl) countEl.textContent = count;
332
- const badge = document.getElementById("rm-fab-badge");
332
+ const badge = document.getElementById("rm-panel-toggle-badge");
333
333
  if (badge) {
334
334
  if (count > 0) { badge.textContent = count; badge.style.display = "flex"; }
335
335
  else { badge.style.display = "none"; }
@@ -419,6 +419,119 @@
419
419
  }
420
420
  if (text) text.textContent = this.serverOnline ? "Connected" : (this._syncUnavailable || "Offline");
421
421
  },
422
+ _validToolbarAccent(value) {
423
+ return ["indigo", "amber", "blue", "emerald", "rose"].includes(value);
424
+ },
425
+ _validToolbarPosition(value) {
426
+ return ["bl", "br", "tl", "tr"].includes(value);
427
+ },
428
+ _validToolbarSize(value) {
429
+ return ["slim", "compact", "default"].includes(value);
430
+ },
431
+ _toolbarSettingOptions(setting) {
432
+ switch (setting) {
433
+ case "accent":
434
+ return [
435
+ ["indigo", "Indigo"],
436
+ ["amber", "Amber"],
437
+ ["blue", "Blue"],
438
+ ["emerald", "Emerald"],
439
+ ["rose", "Rose"]
440
+ ];
441
+ case "position":
442
+ return [
443
+ ["bl", "Bottom-left"],
444
+ ["br", "Bottom-right"],
445
+ ["tl", "Top-left"],
446
+ ["tr", "Top-right"]
447
+ ];
448
+ case "size":
449
+ return [
450
+ ["default", "Default"],
451
+ ["compact", "Compact"],
452
+ ["slim", "Slim"]
453
+ ];
454
+ case "fabVisible":
455
+ case "enableScreenshots":
456
+ return [
457
+ [true, "On"],
458
+ [false, "Off"]
459
+ ];
460
+ default:
461
+ return [];
462
+ }
463
+ },
464
+ _toolbarSettingLabel(setting) {
465
+ switch (setting) {
466
+ case "accent": return "Accent";
467
+ case "position": return "Position";
468
+ case "size": return "Size";
469
+ case "fabVisible": return "Show FAB";
470
+ case "enableScreenshots": return "Screenshots";
471
+ default: return setting;
472
+ }
473
+ },
474
+ _toolbarSettingCurrentValue(setting) {
475
+ switch (setting) {
476
+ case "accent": return this.accent;
477
+ case "position": return this.position;
478
+ case "size": return this.size;
479
+ case "fabVisible": return this.fabVisible;
480
+ case "enableScreenshots": return this.enableScreenshots;
481
+ default: return null;
482
+ }
483
+ },
484
+ _toolbarSettingDisplayValue(value) {
485
+ return value === true ? "true" : value === false ? "false" : String(value);
486
+ },
487
+ _toolbarSettingsMarkup() {
488
+ return [
489
+ "accent",
490
+ "position",
491
+ "size",
492
+ "fabVisible",
493
+ "enableScreenshots"
494
+ ].map(setting => this._toolbarSettingsRowMarkup(setting)).join("");
495
+ },
496
+ _toolbarSettingsRowMarkup(setting) {
497
+ const current = this._toolbarSettingCurrentValue(setting);
498
+ const options = this._toolbarSettingOptions(setting);
499
+ const buttons = options.map(([value, label]) => {
500
+ const active = String(value) === String(current);
501
+ const valueAttr = this._toolbarSettingDisplayValue(value);
502
+ return `<button type="button" class="rm-chip${active ? " rm-chip-active" : " rm-chip-inactive"} rm-setting-chip" data-setting="${setting}" data-value="${this._esc(valueAttr)}"${active ? ` style="background:${this._accentBg()}"` : ""}>${this._esc(label)}</button>`;
503
+ }).join("");
504
+ return `
505
+ <div class="rm-settings-group">
506
+ <div class="rm-settings-label">${this._esc(this._toolbarSettingLabel(setting))}</div>
507
+ <div class="rm-settings-options">${buttons}</div>
508
+ </div>
509
+ `;
510
+ },
511
+ _toolbarSettingValue(setting, rawValue) {
512
+ if (setting === "fabVisible" || setting === "enableScreenshots") {
513
+ return rawValue === true || rawValue === "true";
514
+ }
515
+ return String(rawValue || "");
516
+ },
517
+ _setToolbarSetting(setting, rawValue) {
518
+ const value = this._toolbarSettingValue(setting, rawValue);
519
+ const next = Object.assign({}, this.toolbarSettings, { [setting]: value });
520
+ const normalized = this._normalizeToolbarSettings(next);
521
+ if (JSON.stringify(normalized) === JSON.stringify(this.toolbarSettings)) return;
522
+ if (!this._saveToolbarSettings(normalized)) return;
523
+ this.toolbarSettings = normalized;
524
+ this.destroy();
525
+ this.init(this._bootstrapOptions || {});
526
+ },
527
+ _toggleSettings() {
528
+ const panel = document.getElementById("rm-settings-panel");
529
+ const toggle = document.getElementById("rm-settings-toggle");
530
+ if (!panel) return;
531
+ panel.hidden = !panel.hidden;
532
+ this._settingsPanelOpen = !panel.hidden;
533
+ if (toggle) toggle.setAttribute("aria-expanded", String(!panel.hidden));
534
+ },
422
535
  async _captureElement(element) {
423
536
  try {
424
537
  const rect = element.getBoundingClientRect();
@@ -3,12 +3,21 @@
3
3
  const previousPathname = this._currentPathname;
4
4
  const previousPageUrl = this._currentPageUrl;
5
5
  const currentPageUrl = this._pageUrl();
6
+ this._bootstrapOptions = Object.assign({}, opts);
6
7
  this.endpoint = opts.endpoint || "/feedback/api";
7
- this.accent = opts.accent || "indigo";
8
- this.position = opts.position || "bl";
9
- this.size = opts.size || "default";
10
- this.fabVisible = opts.fabVisible !== false;
11
- this.enableScreenshots = opts.enableScreenshots !== false;
8
+ const savedSettings = this._loadToolbarSettings();
9
+ this.toolbarSettings = this._normalizeToolbarSettings({
10
+ accent: savedSettings.accent || opts.accent || "indigo",
11
+ position: savedSettings.position || opts.position || "bl",
12
+ size: savedSettings.size || opts.size || "default",
13
+ fabVisible: savedSettings.fabVisible != null ? savedSettings.fabVisible : (opts.fabVisible !== false),
14
+ enableScreenshots: savedSettings.enableScreenshots != null ? savedSettings.enableScreenshots : (opts.enableScreenshots !== false)
15
+ });
16
+ this.accent = this.toolbarSettings.accent || "indigo";
17
+ this.position = this.toolbarSettings.position || "bl";
18
+ this.size = this.toolbarSettings.size || "default";
19
+ this.fabVisible = this.toolbarSettings.fabVisible !== false;
20
+ this.enableScreenshots = this.toolbarSettings.enableScreenshots !== false;
12
21
  this.legacyStorageEndpoint = opts.legacyStorageEndpoint || this.legacyStorageEndpoint;
13
22
  this.healthIntervalMs = (opts.healthInterval || 60) * 1000;
14
23
 
@@ -84,6 +93,7 @@
84
93
  if (pins) pins.remove();
85
94
  const styles = document.getElementById("rm-toolbar-styles");
86
95
  if (styles) styles.remove();
96
+ this._settingsPanelOpen = false;
87
97
  },
88
98
  });
89
99
 
@@ -3,6 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <%= csrf_meta_tags %>
6
7
  <title><%= content_for?(:title) ? "#{yield(:title)} — Rails Markup" : "Rails Markup" %></title>
7
8
  <style>
8
9
  /* Reset */
@@ -5,7 +5,7 @@
5
5
  <turbo-frame id="detail-panel">
6
6
  <div class="rm-detail">
7
7
  <%# Close button %>
8
- <button type="button" class="rm-back" onclick="var f=this.closest('turbo-frame'); f.innerHTML=''; f.classList.add('rm-panel-empty'); document.querySelectorAll('.rm-card-active').forEach(c => c.classList.remove('rm-card-active'));">&#10005; Close</button>
8
+ <button type="button" class="rm-back" onclick="var f=this.closest('turbo-frame'); if (this.closest('.rm-layout')) { f.innerHTML=''; f.classList.add('rm-panel-empty'); document.querySelectorAll('.rm-card-active').forEach(c => c.classList.remove('rm-card-active')); } else { window.location.href='<%= j(rails_markup.root_path) %>'; }">&#10005; Close</button>
9
9
 
10
10
  <%# Header badges %>
11
11
  <div class="rm-detail-header">
@@ -31,7 +31,7 @@ RailsMarkup.configure do |config|
31
31
  # "tl" (top-left), "tr" (top-right). Default: "bl"
32
32
  # config.toolbar_position = "bl"
33
33
 
34
- # FAB button size: "default" (48px), "compact" (40px), "slim" (32px)
34
+ # FAB button size: "default" (40px), "compact" (36px), "slim" (32px)
35
35
  # config.toolbar_size = "default"
36
36
 
37
37
  # "Back to app" link in the dashboard header.
@@ -31,7 +31,7 @@ module RailsMarkup
31
31
  title: "Toolbar Size",
32
32
  type: :select,
33
33
  options: %w[slim compact default],
34
- labels: { "slim" => "Slim (32px)", "compact" => "Compact (40px)", "default" => "Default (48px)" }
34
+ labels: { "slim" => "Slim (32px)", "compact" => "Compact (36px)", "default" => "Default (40px)" }
35
35
  },
36
36
  fab: {
37
37
  title: "Show Floating Button",
@@ -73,7 +73,7 @@ module RailsMarkup
73
73
  # "tl" (top-left), "tr" (top-right). Default: "bl"
74
74
  attr_reader :toolbar_position
75
75
 
76
- # FAB button size: "default" (48px), "compact" (40px), "slim" (32px).
76
+ # FAB button size: "default" (40px), "compact" (36px), "slim" (32px).
77
77
  # Default: "default"
78
78
  attr_reader :toolbar_size
79
79
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMarkup
4
- VERSION = "1.4.5"
4
+ VERSION = "1.4.6"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-markup
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.5
4
+ version: 1.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - InventList