pi-studio 0.9.52 → 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.
- package/CHANGELOG.md +18 -0
- package/README.md +9 -7
- package/ROADMAP.md +14 -1
- package/client/studio-client.js +598 -106
- package/client/studio-preview-resource-helpers.js +61 -12
- package/client/studio.css +130 -3
- package/index.ts +547 -70
- package/package.json +1 -1
- package/shared/studio-resource-grants.js +157 -0
- package/shared/studio-side-question-context.js +17 -4
|
@@ -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
|
-
|
|
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
|
|
26
|
-
.filter((
|
|
27
|
-
.slice(0,
|
|
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(
|
|
31
|
-
const originalSource = String(
|
|
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 (!
|
|
35
|
-
if (
|
|
36
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
@@ -3032,6 +3032,97 @@
|
|
|
3032
3032
|
overflow-anchor: none;
|
|
3033
3033
|
}
|
|
3034
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
|
+
|
|
3035
3126
|
.preview-error {
|
|
3036
3127
|
color: var(--warn);
|
|
3037
3128
|
margin-bottom: 0.75em;
|
|
@@ -3701,13 +3792,15 @@
|
|
|
3701
3792
|
|
|
3702
3793
|
.files-toolbar-actions button,
|
|
3703
3794
|
.files-actions button,
|
|
3704
|
-
.files-sort-select
|
|
3795
|
+
.files-sort-select,
|
|
3796
|
+
.files-location-select {
|
|
3705
3797
|
padding: 4px 7px;
|
|
3706
3798
|
font-size: 11px;
|
|
3707
3799
|
}
|
|
3708
3800
|
|
|
3709
|
-
.files-sort-select
|
|
3710
|
-
|
|
3801
|
+
.files-sort-select,
|
|
3802
|
+
.files-location-select {
|
|
3803
|
+
max-width: 240px;
|
|
3711
3804
|
border: 1px solid var(--control-border);
|
|
3712
3805
|
border-radius: 7px;
|
|
3713
3806
|
background: var(--panel);
|
|
@@ -3715,6 +3808,26 @@
|
|
|
3715
3808
|
font-family: var(--font-ui);
|
|
3716
3809
|
}
|
|
3717
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
|
+
|
|
3718
3831
|
.files-subtitle,
|
|
3719
3832
|
.files-notice,
|
|
3720
3833
|
.files-empty {
|
|
@@ -6317,6 +6430,13 @@
|
|
|
6317
6430
|
font-weight: 650;
|
|
6318
6431
|
}
|
|
6319
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
|
+
|
|
6320
6440
|
.studio-decision-confirm.is-destructive {
|
|
6321
6441
|
border-color: var(--warn-border);
|
|
6322
6442
|
background: color-mix(in srgb, var(--warn) 15%, var(--panel));
|
|
@@ -6435,6 +6555,13 @@
|
|
|
6435
6555
|
margin: 6px 0 0 18px;
|
|
6436
6556
|
}
|
|
6437
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
|
+
|
|
6438
6565
|
.side-question-context-grid select,
|
|
6439
6566
|
.side-question-path-label input,
|
|
6440
6567
|
.side-question-composer-label textarea {
|