@stravica-ai/digital-operator-pa 0.1.7 → 0.1.8

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.
@@ -66,8 +66,14 @@
66
66
  .ws-next-text { color: var(--text); }
67
67
  .ws-docs { margin-top: 0.4rem; padding-top: 0.35rem; border-top: 1px solid var(--border); }
68
68
  .doc-list { list-style: none; }
69
- .doc-item { padding: 0.3rem 0; cursor: pointer; color: var(--accent); font-size: 0.78rem; transition: color 0.12s; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; text-decoration: none; }
69
+ /* Each row: the document link (flex-grows) + a pop-out-to-new-tab button. */
70
+ .doc-row { display: flex; align-items: center; gap: 0.4rem; }
71
+ .doc-item { padding: 0.3rem 0; cursor: pointer; color: var(--accent); font-size: 0.78rem; transition: color 0.12s; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; text-decoration: none; flex: 1 1 auto; min-width: 0; }
70
72
  .doc-item:hover { color: var(--text-bright); }
73
+ /* Open-in-new-tab affordance. Dim until hovered so it doesn't compete with
74
+ * the label; opens the document in a full browser tab (target=_blank). */
75
+ .doc-ext { flex: 0 0 auto; color: var(--text-dim); font-size: 0.85rem; line-height: 1; text-decoration: none; padding: 0.2rem 0.3rem; border-radius: 4px; transition: color 0.12s, background 0.12s; }
76
+ .doc-ext:hover { color: var(--accent); background: var(--bg-highlight); }
71
77
  .doc-item .doc-label { color: var(--accent); }
72
78
  .doc-item:hover .doc-label { color: var(--text-bright); }
73
79
  .doc-item .doc-path { color: var(--text-dim); font-size: 0.68rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
@@ -140,6 +146,10 @@
140
146
  document.getElementById('task-modal').classList.remove('open');
141
147
  }
142
148
  function esc(s) { var el = document.createElement('span'); el.textContent = s; return el.innerHTML; }
149
+ // Direct-serve URL for a repo-relative doc path — the document's full-page
150
+ // URL (server renders .md, serves .html as-is, other types raw). Used for
151
+ // the open-in-new-tab affordance. Segments encoded so spaces/unicode survive.
152
+ function rawUrl(path) { return '/' + String(path).split('/').map(encodeURIComponent).join('/'); }
143
153
  function statusBadgeClass(status) {
144
154
  var s = (status || '').toLowerCase();
145
155
  if (s === 'active' || s === 'in-progress') return 'badge-active';
@@ -194,12 +204,15 @@
194
204
  var docLabel = (typeof doc === 'object' && doc.label) ? doc.label : docPath.split('/').pop();
195
205
  var docStatus = (typeof doc === 'object' && doc.status) ? doc.status : null;
196
206
  var viewPath = '/core/tools/workspace/app/viewer.html?file=' + encodeURIComponent(docPath);
207
+ var extPath = rawUrl(docPath);
197
208
  var labelMatchesPath = docLabel === docPath || docLabel === docPath.split('/').pop();
198
- var html = '<li><a class="doc-item" href="' + viewPath + '" onclick="if(!event.metaKey&&!event.ctrlKey){event.preventDefault();navTo(\'' + viewPath + '\')}" title="' + esc(docPath) + '">';
209
+ var html = '<li class="doc-row"><a class="doc-item" href="' + viewPath + '" onclick="if(!event.metaKey&&!event.ctrlKey){event.preventDefault();navTo(\'' + viewPath + '\')}" title="' + esc(docPath) + '">';
199
210
  html += docStatusBadge(docStatus);
200
211
  html += '<span class="doc-label">' + esc(docLabel) + '</span>';
201
212
  if (!labelMatchesPath) html += '<span class="doc-path">' + esc(docPath) + '</span>';
202
- html += '</a></li>';
213
+ html += '</a>';
214
+ html += '<a class="doc-ext" href="' + esc(extPath) + '" target="_blank" rel="noopener" title="Open in a full browser tab">&#8599;</a>';
215
+ html += '</li>';
203
216
  return html;
204
217
  }
205
218
 
@@ -15,6 +15,18 @@
15
15
  .back-btn:hover { border-color: var(--accent); }
16
16
  .breadcrumb { color: var(--text-dim); font-size: 0.75rem; }
17
17
  .breadcrumb span { color: var(--text); }
18
+ /* Pop the current document out to a full browser tab (escapes the workspace
19
+ * shell iframe). Pushed to the right of the toolbar. Hidden until a file is
20
+ * resolved. Styled like .back-btn — it's toolbar chrome. */
21
+ .open-ext { margin-left: auto; background: var(--bg-highlight); border: 1px solid var(--border); border-radius: 4px; padding: 0.3rem 0.7rem; color: var(--accent); font-family: inherit; font-size: 0.8rem; cursor: pointer; text-decoration: none; transition: border-color 0.12s; white-space: nowrap; }
22
+ .open-ext:hover { border-color: var(--accent); }
23
+ /* HTML documents (e.g. infographics) render as the actual page inside a
24
+ * full-bleed frame rather than as escaped source. Height fills the viewport
25
+ * below the sticky toolbar. */
26
+ .html-frame { display: block; width: 100%; height: calc(100vh - 2.9rem); border: 0; background: var(--bg); }
27
+ /* When the content area hosts a full-bleed HTML frame, drop the prose
28
+ * column constraints so the page uses the full width. */
29
+ .content.is-frame { max-width: none; margin: 0; padding: 0; }
18
30
  /* Prose surface: humanist sans for long-form reading. The toolbar keeps
19
31
  * the inherited monospace (it's chrome, not content), and code / pre /
20
32
  * plain-text within .content explicitly opt back to monospace below.
@@ -64,6 +76,7 @@
64
76
  <div class="toolbar">
65
77
  <a class="back-btn" id="back-btn" onclick="goBack()">Back</a>
66
78
  <div class="breadcrumb" id="breadcrumb">No document selected.</div>
79
+ <a class="open-ext" id="open-ext" target="_blank" rel="noopener" style="display:none" title="Open in a full browser tab">Open in new tab &#8599;</a>
67
80
  </div>
68
81
  <div class="content" id="content"><div class="empty-state">No document selected.</div></div>
69
82
  <noscript><div style="padding:2rem; color:#a1a1aa; text-align:center;">Enable JavaScript to view documents.</div></noscript>
@@ -88,6 +101,13 @@
88
101
  window.history.back();
89
102
  }
90
103
  function esc(s) { var el = document.createElement('span'); el.textContent = s; return el.innerHTML; }
104
+ // Direct-serve URL for a repo-relative path. The workspace server serves any
105
+ // repo file at its own path (.md is rendered to HTML, .html served as-is,
106
+ // everything else raw) — so this is the canonical "full page" URL for a
107
+ // document. Each segment is encoded so spaces / unicode survive.
108
+ function rawUrl(path) {
109
+ return '/' + String(path).split('/').map(encodeURIComponent).join('/');
110
+ }
91
111
  var params = new URLSearchParams(window.location.search);
92
112
  var filePath = params.get('file');
93
113
  if (filePath) {
@@ -95,21 +115,42 @@
95
115
  var breadHtml = parts.map(function(p, i) { return i === parts.length - 1 ? '<span>' + esc(p) + '</span>' : esc(p); }).join(' / ');
96
116
  document.getElementById('breadcrumb').innerHTML = breadHtml;
97
117
  document.title = parts[parts.length - 1] || 'Document Viewer';
118
+ // Wire the "Open in new tab" button to the document's full-page URL for
119
+ // every file type (md rendered, html raw, other raw).
120
+ var docUrl = rawUrl(filePath);
121
+ var openExt = document.getElementById('open-ext');
122
+ openExt.href = docUrl;
123
+ openExt.style.display = '';
98
124
  var contentEl = document.getElementById('content');
99
- contentEl.innerHTML = '<div class="empty-state">Loading...</div>';
100
125
  var isMarkdown = /\.(md|markdown)$/i.test(filePath);
101
- fetch('/__api/file?path=' + encodeURIComponent(filePath)).then(function(r) {
102
- if (!r.ok) throw new Error('File not found');
103
- return r.text();
104
- }).then(function(text) {
105
- var body = text;
106
- try { var j = JSON.parse(text); if (typeof j.content === 'string') body = j.content; } catch (e) { /* raw text */ }
107
- if (isMarkdown && typeof window.renderMd === 'function') {
108
- contentEl.innerHTML = renderMd(body);
109
- } else {
110
- contentEl.innerHTML = '<div class="plain-text">' + esc(body) + '</div>';
111
- }
112
- }).catch(function(err) { contentEl.innerHTML = '<div class="empty-state">Could not load document: ' + esc(err.message) + '</div>'; });
126
+ var isHtml = /\.(html?|htm)$/i.test(filePath);
127
+ if (isHtml) {
128
+ // Render the HTML document as the actual page inside a full-bleed frame,
129
+ // not as escaped source. src points at the server's direct-serve route.
130
+ // Sandboxed: scripts run (infographics animate) and popups/same-origin
131
+ // are permitted for the operator's own repo content; no top-navigation.
132
+ var frame = document.createElement('iframe');
133
+ frame.className = 'html-frame';
134
+ frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
135
+ frame.src = docUrl;
136
+ contentEl.classList.add('is-frame');
137
+ contentEl.innerHTML = '';
138
+ contentEl.appendChild(frame);
139
+ } else {
140
+ contentEl.innerHTML = '<div class="empty-state">Loading...</div>';
141
+ fetch('/__api/file?path=' + encodeURIComponent(filePath)).then(function(r) {
142
+ if (!r.ok) throw new Error('File not found');
143
+ return r.text();
144
+ }).then(function(text) {
145
+ var body = text;
146
+ try { var j = JSON.parse(text); if (typeof j.content === 'string') body = j.content; } catch (e) { /* raw text */ }
147
+ if (isMarkdown && typeof window.renderMd === 'function') {
148
+ contentEl.innerHTML = renderMd(body);
149
+ } else {
150
+ contentEl.innerHTML = '<div class="plain-text">' + esc(body) + '</div>';
151
+ }
152
+ }).catch(function(err) { contentEl.innerHTML = '<div class="empty-state">Could not load document: ' + esc(err.message) + '</div>'; });
153
+ }
113
154
  }
114
155
  </script>
115
156
  </body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stravica-ai/digital-operator-pa",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Digital Operator PA — single-bundled public package surface (bins + library) for the platform.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",