pi-studio 0.9.51 → 0.9.53

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.
@@ -1,11 +1,21 @@
1
1
  (() => {
2
2
  const STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT = 100;
3
+ const STUDIO_PREVIEW_LOCAL_PDF_LIMIT = 40;
3
4
  const STUDIO_PREVIEW_IMAGE_DATA_URL_PATTERN = /^data:image\/(?:png|jpeg|gif|webp);base64,/i;
5
+ const STUDIO_PREVIEW_PDF_DATA_URL_PATTERN = /^data:application\/pdf;base64,/i;
4
6
 
5
7
  function isResolvableStudioPreviewImageSource(value) {
6
8
  const source = String(value || "").trim();
7
9
  if (!source || source.startsWith("#") || source.startsWith("//")) return false;
8
10
  if (/^(?:data|blob|https?|about|javascript):/i.test(source)) return false;
11
+ if (/^file:/i.test(source)) {
12
+ try {
13
+ const fileUrl = new URL(source);
14
+ return fileUrl.protocol === "file:" && (!fileUrl.hostname || fileUrl.hostname === "localhost");
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
9
19
  if (/^[a-z][a-z0-9+.-]*:/i.test(source) && !/^[a-z]:[\\/]/i.test(source)) return false;
10
20
  return true;
11
21
  }
@@ -17,30 +27,66 @@
17
27
  && String(a.resourceDir || "") === String(b.resourceDir || "");
18
28
  }
19
29
 
20
- async function hydrateStudioPreviewLocalImages(target, resolveResource) {
30
+ function isResolvableStudioPreviewPdfSource(value) {
31
+ const source = String(value || "").trim();
32
+ return isResolvableStudioPreviewImageSource(source) && /\.pdf(?:[?#].*)?$/i.test(source);
33
+ }
34
+
35
+ async function hydrateStudioPreviewLocalMediaElements(target, options) {
36
+ const selector = options && options.selector;
37
+ const sourceAllowed = options && options.sourceAllowed;
38
+ const dataUrlAllowed = options && options.dataUrlAllowed;
39
+ const resolveResource = options && options.resolveResource;
40
+ const onError = options && options.onError;
41
+ const limit = Math.max(1, Number(options && options.limit) || 1);
21
42
  if (!target || typeof target.querySelectorAll !== "function" || typeof resolveResource !== "function") {
22
43
  return { attempted: 0, resolved: 0 };
23
44
  }
24
45
 
25
- const images = Array.from(target.querySelectorAll("img[src]"))
26
- .filter((image) => image && typeof image.getAttribute === "function" && isResolvableStudioPreviewImageSource(image.getAttribute("src")))
27
- .slice(0, STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT);
46
+ const elements = Array.from(target.querySelectorAll(selector))
47
+ .filter((element) => element && typeof element.getAttribute === "function" && sourceAllowed(element.getAttribute("src")))
48
+ .slice(0, limit);
28
49
 
29
50
  let resolved = 0;
30
- await Promise.all(images.map(async (image) => {
31
- const originalSource = String(image.getAttribute("src") || "").trim();
51
+ await Promise.all(elements.map(async (element) => {
52
+ const originalSource = String(element.getAttribute("src") || "").trim();
32
53
  try {
33
54
  const dataUrl = await resolveResource(originalSource);
34
- if (!STUDIO_PREVIEW_IMAGE_DATA_URL_PATTERN.test(String(dataUrl || ""))) return;
35
- if (image.isConnected === false || String(image.getAttribute("src") || "").trim() !== originalSource) return;
36
- image.setAttribute("src", dataUrl);
55
+ if (!dataUrlAllowed.test(String(dataUrl || ""))) throw new Error("Studio returned an unsupported local media payload.");
56
+ if (element.isConnected === false || String(element.getAttribute("src") || "").trim() !== originalSource) return;
57
+ element.setAttribute("src", dataUrl);
37
58
  resolved += 1;
38
- } catch {
39
- // Leave unresolved images unchanged so authored browser URLs retain their normal behavior.
59
+ } catch (error) {
60
+ if (element.isConnected === false || String(element.getAttribute("src") || "").trim() !== originalSource) return;
61
+ if (typeof onError === "function") {
62
+ try { await onError(element, originalSource, error); } catch {}
63
+ }
40
64
  }
41
65
  }));
42
66
 
43
- return { attempted: images.length, resolved };
67
+ return { attempted: elements.length, resolved };
68
+ }
69
+
70
+ async function hydrateStudioPreviewLocalImages(target, resolveResource, options) {
71
+ return hydrateStudioPreviewLocalMediaElements(target, {
72
+ selector: "img[src]",
73
+ sourceAllowed: isResolvableStudioPreviewImageSource,
74
+ dataUrlAllowed: STUDIO_PREVIEW_IMAGE_DATA_URL_PATTERN,
75
+ resolveResource,
76
+ onError: options && options.onError,
77
+ limit: STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT,
78
+ });
79
+ }
80
+
81
+ async function hydrateStudioPreviewLocalPdfEmbeds(target, resolveResource, options) {
82
+ return hydrateStudioPreviewLocalMediaElements(target, {
83
+ selector: "embed[src]",
84
+ sourceAllowed: isResolvableStudioPreviewPdfSource,
85
+ dataUrlAllowed: STUDIO_PREVIEW_PDF_DATA_URL_PATTERN,
86
+ resolveResource,
87
+ onError: options && options.onError,
88
+ limit: STUDIO_PREVIEW_LOCAL_PDF_LIMIT,
89
+ });
44
90
  }
45
91
 
46
92
  function buildStudioPdfVersionSignature(headers) {
@@ -94,11 +140,14 @@
94
140
 
95
141
  globalThis.PiStudioPreviewResourceHelpers = Object.freeze({
96
142
  STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT,
143
+ STUDIO_PREVIEW_LOCAL_PDF_LIMIT,
97
144
  areStudioPreviewResourceContextsEqual,
98
145
  buildStudioPdfVersionSignature,
99
146
  createStudioPdfVersionObservationState,
100
147
  hydrateStudioPreviewLocalImages,
148
+ hydrateStudioPreviewLocalPdfEmbeds,
101
149
  isResolvableStudioPreviewImageSource,
150
+ isResolvableStudioPreviewPdfSource,
102
151
  observeStudioPdfVersion,
103
152
  });
104
153
  })();
package/client/studio.css CHANGED
@@ -29,7 +29,7 @@
29
29
  justify-content: space-between;
30
30
  }
31
31
 
32
- h1 {
32
+ body > header h1 {
33
33
  margin: 0;
34
34
  font-size: 18px;
35
35
  font-weight: 600;
@@ -202,6 +202,10 @@
202
202
  font-weight: 450;
203
203
  }
204
204
 
205
+ .export-preview-menu button[hidden] {
206
+ display: none !important;
207
+ }
208
+
205
209
  .export-preview-menu button:not(:disabled):hover,
206
210
  .export-preview-menu button:not(:disabled):focus-visible {
207
211
  background: var(--panel-2);
@@ -1182,6 +1186,7 @@
1182
1186
  .rendered-markdown h4,
1183
1187
  .rendered-markdown h5,
1184
1188
  .rendered-markdown h6 {
1189
+ display: block;
1185
1190
  margin-top: 1.2em;
1186
1191
  margin-bottom: 0.5em;
1187
1192
  line-height: 1.25;
@@ -1190,17 +1195,39 @@
1190
1195
  }
1191
1196
 
1192
1197
  .rendered-markdown h1 {
1193
- font-size: 1.6em;
1198
+ font-size: 1.7em;
1199
+ font-weight: 720;
1194
1200
  border-bottom: 0;
1195
1201
  padding-bottom: 0;
1196
1202
  }
1197
1203
 
1198
1204
  .rendered-markdown h2 {
1199
- font-size: 1.25em;
1205
+ font-size: 1.4em;
1206
+ font-weight: 700;
1200
1207
  border-bottom: 0;
1201
1208
  padding-bottom: 0;
1202
1209
  }
1203
1210
 
1211
+ .rendered-markdown h3 {
1212
+ font-size: 1.2em;
1213
+ font-weight: 680;
1214
+ }
1215
+
1216
+ .rendered-markdown h4 {
1217
+ font-size: 1.08em;
1218
+ font-weight: 660;
1219
+ }
1220
+
1221
+ .rendered-markdown h5 {
1222
+ font-size: 1em;
1223
+ font-weight: 650;
1224
+ }
1225
+
1226
+ .rendered-markdown h6 {
1227
+ font-size: 0.92em;
1228
+ font-weight: 650;
1229
+ }
1230
+
1204
1231
  .rendered-markdown #title-block-header {
1205
1232
  text-align: center;
1206
1233
  margin-bottom: 2em;
@@ -1767,6 +1794,7 @@
1767
1794
  }
1768
1795
 
1769
1796
  .rendered-markdown .studio-pdf-preview {
1797
+ position: relative;
1770
1798
  display: block;
1771
1799
  max-width: 100%;
1772
1800
  border: 1px solid var(--md-table-border);
@@ -1776,6 +1804,10 @@
1776
1804
  line-height: 0;
1777
1805
  }
1778
1806
 
1807
+ .rendered-markdown .studio-pdf-preview-focus-target {
1808
+ cursor: zoom-in;
1809
+ }
1810
+
1779
1811
  .rendered-markdown .studio-pdf-preview[data-fig-align="center"] {
1780
1812
  margin-left: auto;
1781
1813
  margin-right: auto;
@@ -1881,6 +1913,19 @@
1881
1913
  border-color: var(--control-border);
1882
1914
  }
1883
1915
 
1916
+ .rendered-markdown button.studio-pdf-card-action[data-studio-action-feedback="success"],
1917
+ .studio-pdf-focus-btn[data-studio-action-feedback="success"] {
1918
+ background: var(--accent-soft);
1919
+ color: var(--accent);
1920
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--control-border));
1921
+ }
1922
+
1923
+ .rendered-markdown button.studio-pdf-card-action[data-studio-action-feedback="warning"],
1924
+ .studio-pdf-focus-btn[data-studio-action-feedback="warning"] {
1925
+ color: var(--warn);
1926
+ border-color: color-mix(in srgb, var(--warn) 45%, var(--control-border));
1927
+ }
1928
+
1884
1929
  .rendered-markdown button.studio-pdf-card-auto-refresh[aria-pressed="true"] {
1885
1930
  background: transparent;
1886
1931
  color: var(--accent);
@@ -1896,13 +1941,14 @@
1896
1941
  }
1897
1942
 
1898
1943
  .rendered-markdown button.studio-pdf-card-focus {
1899
- width: 29px;
1944
+ width: auto;
1900
1945
  min-width: 29px;
1901
1946
  min-height: 29px;
1902
- padding: 0;
1947
+ padding: 0 8px;
1903
1948
  display: inline-flex;
1904
1949
  align-items: center;
1905
1950
  justify-content: center;
1951
+ gap: 5px;
1906
1952
  }
1907
1953
 
1908
1954
  .rendered-markdown button.studio-pdf-card-focus .studio-refresh-icon {
@@ -2075,6 +2121,10 @@
2075
2121
  height: 14px;
2076
2122
  }
2077
2123
 
2124
+ .studio-pdf-focus-fullscreen[hidden] {
2125
+ display: none !important;
2126
+ }
2127
+
2078
2128
  .studio-pdf-focus-fullscreen,
2079
2129
  .studio-pdf-focus-close {
2080
2130
  width: 29px;
@@ -2982,6 +3032,97 @@
2982
3032
  overflow-anchor: none;
2983
3033
  }
2984
3034
 
3035
+ .rendered-markdown .studio-blocked-media {
3036
+ display: flex;
3037
+ align-items: center;
3038
+ justify-content: space-between;
3039
+ gap: 12px;
3040
+ width: 100%;
3041
+ margin: 10px 0;
3042
+ padding: 10px 12px;
3043
+ border: 1px solid color-mix(in srgb, var(--warn) 42%, var(--panel-border));
3044
+ border-radius: 9px;
3045
+ background: color-mix(in srgb, var(--warn) 7%, var(--panel));
3046
+ color: var(--text);
3047
+ font-family: var(--font-ui);
3048
+ font-size: 12px;
3049
+ font-style: normal;
3050
+ line-height: 1.35;
3051
+ box-sizing: border-box;
3052
+ }
3053
+
3054
+ .rendered-markdown .studio-blocked-media-text {
3055
+ min-width: 0;
3056
+ display: flex;
3057
+ flex-direction: column;
3058
+ gap: 3px;
3059
+ }
3060
+
3061
+ .rendered-markdown .studio-blocked-media-text strong {
3062
+ color: var(--warn);
3063
+ font-weight: 650;
3064
+ }
3065
+
3066
+ .rendered-markdown .studio-blocked-media-text code {
3067
+ display: block;
3068
+ max-width: min(72vw, 760px);
3069
+ overflow: hidden;
3070
+ color: var(--studio-info-text, var(--muted));
3071
+ font-family: var(--font-mono);
3072
+ font-size: 11px;
3073
+ text-overflow: ellipsis;
3074
+ white-space: nowrap;
3075
+ }
3076
+
3077
+ .rendered-markdown button.studio-blocked-media-allow {
3078
+ flex: 0 0 auto;
3079
+ min-height: 30px;
3080
+ padding: 5px 10px;
3081
+ border: 1px solid color-mix(in srgb, var(--warn) 48%, var(--control-border));
3082
+ border-radius: 8px;
3083
+ background: var(--panel);
3084
+ color: var(--text);
3085
+ font: inherit;
3086
+ font-weight: 600;
3087
+ cursor: pointer;
3088
+ white-space: nowrap;
3089
+ }
3090
+
3091
+ .rendered-markdown button.studio-blocked-media-allow:not(:disabled):hover,
3092
+ .rendered-markdown button.studio-blocked-media-allow:focus-visible {
3093
+ border-color: var(--warn);
3094
+ background: var(--control-hover-bg, var(--panel-2));
3095
+ outline: none;
3096
+ }
3097
+
3098
+ .rendered-markdown button.studio-blocked-media-allow:disabled {
3099
+ cursor: default;
3100
+ opacity: 0.6;
3101
+ }
3102
+
3103
+ .rendered-markdown .studio-html-artifact-blocked-media {
3104
+ display: grid;
3105
+ gap: 6px;
3106
+ padding: 0 10px;
3107
+ border-bottom: 1px solid var(--border-subtle);
3108
+ background: var(--panel);
3109
+ }
3110
+
3111
+ .rendered-markdown .studio-html-artifact-blocked-media .studio-blocked-media {
3112
+ margin: 8px 0;
3113
+ }
3114
+
3115
+ @media (max-width: 680px) {
3116
+ .rendered-markdown .studio-blocked-media {
3117
+ align-items: stretch;
3118
+ flex-direction: column;
3119
+ }
3120
+
3121
+ .rendered-markdown button.studio-blocked-media-allow {
3122
+ align-self: flex-start;
3123
+ }
3124
+ }
3125
+
2985
3126
  .preview-error {
2986
3127
  color: var(--warn);
2987
3128
  margin-bottom: 0.75em;
@@ -3651,13 +3792,15 @@
3651
3792
 
3652
3793
  .files-toolbar-actions button,
3653
3794
  .files-actions button,
3654
- .files-sort-select {
3795
+ .files-sort-select,
3796
+ .files-location-select {
3655
3797
  padding: 4px 7px;
3656
3798
  font-size: 11px;
3657
3799
  }
3658
3800
 
3659
- .files-sort-select {
3660
- max-width: 150px;
3801
+ .files-sort-select,
3802
+ .files-location-select {
3803
+ max-width: 240px;
3661
3804
  border: 1px solid var(--control-border);
3662
3805
  border-radius: 7px;
3663
3806
  background: var(--panel);
@@ -3665,6 +3808,26 @@
3665
3808
  font-family: var(--font-ui);
3666
3809
  }
3667
3810
 
3811
+ .files-sort-select {
3812
+ max-width: 150px;
3813
+ }
3814
+
3815
+ .files-no-locations,
3816
+ .files-section-title {
3817
+ color: var(--studio-info-text, var(--muted));
3818
+ font-size: 11px;
3819
+ font-weight: 700;
3820
+ letter-spacing: 0.03em;
3821
+ text-transform: uppercase;
3822
+ }
3823
+
3824
+ .files-exact-section {
3825
+ display: grid;
3826
+ gap: 6px;
3827
+ padding-bottom: 10px;
3828
+ border-bottom: 1px solid var(--border-subtle);
3829
+ }
3830
+
3668
3831
  .files-subtitle,
3669
3832
  .files-notice,
3670
3833
  .files-empty {
@@ -5429,7 +5592,7 @@
5429
5592
  gap: 9px;
5430
5593
  }
5431
5594
 
5432
- body.studio-ui-refresh h1 {
5595
+ body.studio-ui-refresh > header h1 {
5433
5596
  font-size: 16px;
5434
5597
  }
5435
5598
 
@@ -6267,6 +6430,13 @@
6267
6430
  font-weight: 650;
6268
6431
  }
6269
6432
 
6433
+ .studio-decision-confirm:not(.is-destructive):not(:disabled):hover,
6434
+ .studio-decision-confirm:not(.is-destructive):focus-visible {
6435
+ border-color: color-mix(in srgb, var(--accent) 84%, var(--text));
6436
+ background: color-mix(in srgb, var(--accent) 84%, var(--text));
6437
+ color: var(--accent-contrast);
6438
+ }
6439
+
6270
6440
  .studio-decision-confirm.is-destructive {
6271
6441
  border-color: var(--warn-border);
6272
6442
  background: color-mix(in srgb, var(--warn) 15%, var(--panel));
@@ -6385,6 +6555,13 @@
6385
6555
  margin: 6px 0 0 18px;
6386
6556
  }
6387
6557
 
6558
+ .side-question-context-access-note {
6559
+ margin: 9px 0 0;
6560
+ color: var(--muted);
6561
+ font-size: 0.78rem;
6562
+ line-height: 1.42;
6563
+ }
6564
+
6388
6565
  .side-question-context-grid select,
6389
6566
  .side-question-path-label input,
6390
6567
  .side-question-composer-label textarea {