pi-studio 0.9.52 → 0.9.54
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 +32 -0
- package/README.md +15 -9
- package/ROADMAP.md +15 -2
- package/client/studio-client.js +1387 -245
- package/client/studio-preview-resource-helpers.js +61 -12
- package/client/studio.css +180 -3
- package/index.ts +991 -166
- package/package.json +1 -1
- package/shared/studio-disk-revisions.js +558 -0
- package/shared/studio-file-watcher.js +181 -0
- package/shared/studio-resource-grants.js +157 -0
- package/shared/studio-side-question-context.js +17 -4
- package/shared/studio-workspace-state.js +4 -0
|
@@ -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
|
@@ -496,6 +496,45 @@
|
|
|
496
496
|
font-size: 14px;
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
body[data-watched-file-preview="1"] #rightSectionHeader .section-header-main::before {
|
|
500
|
+
content: "Watched preview";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
body[data-watched-file-preview="1"] #saveAsBtn,
|
|
504
|
+
body[data-watched-file-preview="1"] #saveOverBtn,
|
|
505
|
+
body[data-watched-file-preview="1"] #refreshFromDiskBtn,
|
|
506
|
+
body[data-watched-file-preview="1"] #clearWorkspaceBtn,
|
|
507
|
+
body[data-watched-file-preview="1"] #importFileBtn,
|
|
508
|
+
body[data-watched-file-preview="1"] #suggestCompletionBtn,
|
|
509
|
+
body[data-watched-file-preview="1"] #suggestCompletionOptionsBtn,
|
|
510
|
+
body[data-watched-file-preview="1"] #insertHeaderBtn,
|
|
511
|
+
body[data-watched-file-preview="1"] #annotationModeSelect,
|
|
512
|
+
body[data-watched-file-preview="1"] #stripAnnotationsBtn,
|
|
513
|
+
body[data-watched-file-preview="1"] #saveAnnotatedBtn,
|
|
514
|
+
body[data-watched-file-preview="1"] #getEditorBtn {
|
|
515
|
+
display: none !important;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
body[data-watched-file-preview="1"] #sourceText {
|
|
519
|
+
cursor: default;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.studio-watched-preview-error {
|
|
523
|
+
position: sticky;
|
|
524
|
+
top: 8px;
|
|
525
|
+
z-index: 2;
|
|
526
|
+
display: flex;
|
|
527
|
+
align-items: center;
|
|
528
|
+
justify-content: space-between;
|
|
529
|
+
gap: 10px;
|
|
530
|
+
margin: 8px 0;
|
|
531
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.studio-watched-preview-error button {
|
|
535
|
+
flex: 0 0 auto;
|
|
536
|
+
}
|
|
537
|
+
|
|
499
538
|
.reference-meta {
|
|
500
539
|
padding: 8px 10px;
|
|
501
540
|
border-bottom: 1px solid var(--panel-border);
|
|
@@ -3009,6 +3048,17 @@
|
|
|
3009
3048
|
font-style: italic;
|
|
3010
3049
|
}
|
|
3011
3050
|
|
|
3051
|
+
.studio-preview-staging {
|
|
3052
|
+
position: fixed !important;
|
|
3053
|
+
top: 0;
|
|
3054
|
+
left: -100000px;
|
|
3055
|
+
z-index: -1;
|
|
3056
|
+
max-height: none !important;
|
|
3057
|
+
overflow: visible !important;
|
|
3058
|
+
visibility: hidden;
|
|
3059
|
+
pointer-events: none;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3012
3062
|
.panel-scroll.preview-pending::after {
|
|
3013
3063
|
content: "Updating";
|
|
3014
3064
|
position: absolute;
|
|
@@ -3032,6 +3082,97 @@
|
|
|
3032
3082
|
overflow-anchor: none;
|
|
3033
3083
|
}
|
|
3034
3084
|
|
|
3085
|
+
.rendered-markdown .studio-blocked-media {
|
|
3086
|
+
display: flex;
|
|
3087
|
+
align-items: center;
|
|
3088
|
+
justify-content: space-between;
|
|
3089
|
+
gap: 12px;
|
|
3090
|
+
width: 100%;
|
|
3091
|
+
margin: 10px 0;
|
|
3092
|
+
padding: 10px 12px;
|
|
3093
|
+
border: 1px solid color-mix(in srgb, var(--warn) 42%, var(--panel-border));
|
|
3094
|
+
border-radius: 9px;
|
|
3095
|
+
background: color-mix(in srgb, var(--warn) 7%, var(--panel));
|
|
3096
|
+
color: var(--text);
|
|
3097
|
+
font-family: var(--font-ui);
|
|
3098
|
+
font-size: 12px;
|
|
3099
|
+
font-style: normal;
|
|
3100
|
+
line-height: 1.35;
|
|
3101
|
+
box-sizing: border-box;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
.rendered-markdown .studio-blocked-media-text {
|
|
3105
|
+
min-width: 0;
|
|
3106
|
+
display: flex;
|
|
3107
|
+
flex-direction: column;
|
|
3108
|
+
gap: 3px;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
.rendered-markdown .studio-blocked-media-text strong {
|
|
3112
|
+
color: var(--warn);
|
|
3113
|
+
font-weight: 650;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.rendered-markdown .studio-blocked-media-text code {
|
|
3117
|
+
display: block;
|
|
3118
|
+
max-width: min(72vw, 760px);
|
|
3119
|
+
overflow: hidden;
|
|
3120
|
+
color: var(--studio-info-text, var(--muted));
|
|
3121
|
+
font-family: var(--font-mono);
|
|
3122
|
+
font-size: 11px;
|
|
3123
|
+
text-overflow: ellipsis;
|
|
3124
|
+
white-space: nowrap;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
.rendered-markdown button.studio-blocked-media-allow {
|
|
3128
|
+
flex: 0 0 auto;
|
|
3129
|
+
min-height: 30px;
|
|
3130
|
+
padding: 5px 10px;
|
|
3131
|
+
border: 1px solid color-mix(in srgb, var(--warn) 48%, var(--control-border));
|
|
3132
|
+
border-radius: 8px;
|
|
3133
|
+
background: var(--panel);
|
|
3134
|
+
color: var(--text);
|
|
3135
|
+
font: inherit;
|
|
3136
|
+
font-weight: 600;
|
|
3137
|
+
cursor: pointer;
|
|
3138
|
+
white-space: nowrap;
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
.rendered-markdown button.studio-blocked-media-allow:not(:disabled):hover,
|
|
3142
|
+
.rendered-markdown button.studio-blocked-media-allow:focus-visible {
|
|
3143
|
+
border-color: var(--warn);
|
|
3144
|
+
background: var(--control-hover-bg, var(--panel-2));
|
|
3145
|
+
outline: none;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
.rendered-markdown button.studio-blocked-media-allow:disabled {
|
|
3149
|
+
cursor: default;
|
|
3150
|
+
opacity: 0.6;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
.rendered-markdown .studio-html-artifact-blocked-media {
|
|
3154
|
+
display: grid;
|
|
3155
|
+
gap: 6px;
|
|
3156
|
+
padding: 0 10px;
|
|
3157
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
3158
|
+
background: var(--panel);
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
.rendered-markdown .studio-html-artifact-blocked-media .studio-blocked-media {
|
|
3162
|
+
margin: 8px 0;
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
@media (max-width: 680px) {
|
|
3166
|
+
.rendered-markdown .studio-blocked-media {
|
|
3167
|
+
align-items: stretch;
|
|
3168
|
+
flex-direction: column;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
.rendered-markdown button.studio-blocked-media-allow {
|
|
3172
|
+
align-self: flex-start;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3035
3176
|
.preview-error {
|
|
3036
3177
|
color: var(--warn);
|
|
3037
3178
|
margin-bottom: 0.75em;
|
|
@@ -3701,13 +3842,15 @@
|
|
|
3701
3842
|
|
|
3702
3843
|
.files-toolbar-actions button,
|
|
3703
3844
|
.files-actions button,
|
|
3704
|
-
.files-sort-select
|
|
3845
|
+
.files-sort-select,
|
|
3846
|
+
.files-location-select {
|
|
3705
3847
|
padding: 4px 7px;
|
|
3706
3848
|
font-size: 11px;
|
|
3707
3849
|
}
|
|
3708
3850
|
|
|
3709
|
-
.files-sort-select
|
|
3710
|
-
|
|
3851
|
+
.files-sort-select,
|
|
3852
|
+
.files-location-select {
|
|
3853
|
+
max-width: 240px;
|
|
3711
3854
|
border: 1px solid var(--control-border);
|
|
3712
3855
|
border-radius: 7px;
|
|
3713
3856
|
background: var(--panel);
|
|
@@ -3715,6 +3858,26 @@
|
|
|
3715
3858
|
font-family: var(--font-ui);
|
|
3716
3859
|
}
|
|
3717
3860
|
|
|
3861
|
+
.files-sort-select {
|
|
3862
|
+
max-width: 150px;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
.files-no-locations,
|
|
3866
|
+
.files-section-title {
|
|
3867
|
+
color: var(--studio-info-text, var(--muted));
|
|
3868
|
+
font-size: 11px;
|
|
3869
|
+
font-weight: 700;
|
|
3870
|
+
letter-spacing: 0.03em;
|
|
3871
|
+
text-transform: uppercase;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
.files-exact-section {
|
|
3875
|
+
display: grid;
|
|
3876
|
+
gap: 6px;
|
|
3877
|
+
padding-bottom: 10px;
|
|
3878
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3718
3881
|
.files-subtitle,
|
|
3719
3882
|
.files-notice,
|
|
3720
3883
|
.files-empty {
|
|
@@ -6317,6 +6480,13 @@
|
|
|
6317
6480
|
font-weight: 650;
|
|
6318
6481
|
}
|
|
6319
6482
|
|
|
6483
|
+
.studio-decision-confirm:not(.is-destructive):not(:disabled):hover,
|
|
6484
|
+
.studio-decision-confirm:not(.is-destructive):focus-visible {
|
|
6485
|
+
border-color: color-mix(in srgb, var(--accent) 84%, var(--text));
|
|
6486
|
+
background: color-mix(in srgb, var(--accent) 84%, var(--text));
|
|
6487
|
+
color: var(--accent-contrast);
|
|
6488
|
+
}
|
|
6489
|
+
|
|
6320
6490
|
.studio-decision-confirm.is-destructive {
|
|
6321
6491
|
border-color: var(--warn-border);
|
|
6322
6492
|
background: color-mix(in srgb, var(--warn) 15%, var(--panel));
|
|
@@ -6435,6 +6605,13 @@
|
|
|
6435
6605
|
margin: 6px 0 0 18px;
|
|
6436
6606
|
}
|
|
6437
6607
|
|
|
6608
|
+
.side-question-context-access-note {
|
|
6609
|
+
margin: 9px 0 0;
|
|
6610
|
+
color: var(--muted);
|
|
6611
|
+
font-size: 0.78rem;
|
|
6612
|
+
line-height: 1.42;
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6438
6615
|
.side-question-context-grid select,
|
|
6439
6616
|
.side-question-path-label input,
|
|
6440
6617
|
.side-question-composer-label textarea {
|