@tightknitai/block-kitchen 0.10.12 → 0.10.14
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/README.md +1 -1
- package/dist/helpers.cjs +47 -5
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +5 -3
- package/dist/helpers.d.ts +5 -3
- package/dist/helpers.js +47 -5
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +125 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +125 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1868,6 +1868,7 @@ function groupValidatorErrors(errors, blocks) {
|
|
|
1868
1868
|
var SAFE_LINK_PROTOCOLS = /^(https?|ircs?|mailto|tel|sms|xmpp)$/i;
|
|
1869
1869
|
var SAFE_IMAGE_PROTOCOLS = /^(https?)$/i;
|
|
1870
1870
|
var SAFE_IMAGE_DATA_PREFIX = /^data:image\/(?:png|jpeg|jpg|gif|webp|avif|x-icon|vnd\.microsoft\.icon)(?:;[a-z0-9-]+(?:=[^,;]*)?)*,/i;
|
|
1871
|
+
var SAFE_EMBED_PROTOCOLS = /^(https?)$/i;
|
|
1871
1872
|
function getScheme(value) {
|
|
1872
1873
|
const trimmed = value.replace(/^\s+/, "");
|
|
1873
1874
|
const colon = trimmed.indexOf(":");
|
|
@@ -1923,16 +1924,54 @@ function isSafeImageSrc(value) {
|
|
|
1923
1924
|
}
|
|
1924
1925
|
return SAFE_IMAGE_PROTOCOLS.test(scheme);
|
|
1925
1926
|
}
|
|
1927
|
+
function isSafeEmbedSrc(value) {
|
|
1928
|
+
if (typeof value !== "string") {
|
|
1929
|
+
return false;
|
|
1930
|
+
}
|
|
1931
|
+
if (value.length === 0) {
|
|
1932
|
+
return true;
|
|
1933
|
+
}
|
|
1934
|
+
const scheme = getScheme(value);
|
|
1935
|
+
if (scheme === null) {
|
|
1936
|
+
return false;
|
|
1937
|
+
}
|
|
1938
|
+
return SAFE_EMBED_PROTOCOLS.test(scheme);
|
|
1939
|
+
}
|
|
1926
1940
|
function sanitizeHref(value) {
|
|
1927
1941
|
return isSafeHref(value) ? value ?? "" : "";
|
|
1928
1942
|
}
|
|
1929
1943
|
function sanitizeImageSrc(value) {
|
|
1930
1944
|
return isSafeImageSrc(value) ? value ?? "" : "";
|
|
1931
1945
|
}
|
|
1946
|
+
function sanitizeEmbedSrc(value) {
|
|
1947
|
+
return isSafeEmbedSrc(value) ? value ?? "" : "";
|
|
1948
|
+
}
|
|
1932
1949
|
|
|
1933
1950
|
// src/lib/sanitize-blocks.ts
|
|
1934
|
-
var
|
|
1935
|
-
var
|
|
1951
|
+
var EMBED_KEYS = /* @__PURE__ */ new Set(["video_url"]);
|
|
1952
|
+
var URL_KEY = /(^|_)(url|uri|link|href|src)$/;
|
|
1953
|
+
var MAYBE_URL_KEY = /url|uri|link|href|src/i;
|
|
1954
|
+
var IMAGE_KEY_HINT = /image|thumb|icon|avatar|logo|photo|picture/;
|
|
1955
|
+
var PROP_BAG_KEY = /(^|_)props$/;
|
|
1956
|
+
function normalizeKey(key) {
|
|
1957
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
1958
|
+
}
|
|
1959
|
+
function isRendererPropBag(key) {
|
|
1960
|
+
return PROP_BAG_KEY.test(normalizeKey(key));
|
|
1961
|
+
}
|
|
1962
|
+
function classifyUrlKey(key) {
|
|
1963
|
+
if (!MAYBE_URL_KEY.test(key)) {
|
|
1964
|
+
return null;
|
|
1965
|
+
}
|
|
1966
|
+
const normalized = normalizeKey(key);
|
|
1967
|
+
if (EMBED_KEYS.has(normalized)) {
|
|
1968
|
+
return "embed";
|
|
1969
|
+
}
|
|
1970
|
+
if (!URL_KEY.test(normalized)) {
|
|
1971
|
+
return null;
|
|
1972
|
+
}
|
|
1973
|
+
return IMAGE_KEY_HINT.test(normalized) ? "image" : "href";
|
|
1974
|
+
}
|
|
1936
1975
|
var RETRIEVAL_ONLY_KEYS = /* @__PURE__ */ new Map([
|
|
1937
1976
|
// Covers both the image block and the image element — both `type: 'image'`.
|
|
1938
1977
|
["image", /* @__PURE__ */ new Set(["image_width", "image_height", "image_bytes", "fallback", "is_animated"])],
|
|
@@ -1960,7 +1999,7 @@ function sanitizeValue(value) {
|
|
|
1960
1999
|
const dropKeys = typeof src.type === "string" ? RETRIEVAL_ONLY_KEYS.get(src.type) : void 0;
|
|
1961
2000
|
let copy = null;
|
|
1962
2001
|
for (const key of Object.keys(src)) {
|
|
1963
|
-
if (dropKeys?.has(key)) {
|
|
2002
|
+
if (dropKeys?.has(key) || isRendererPropBag(key)) {
|
|
1964
2003
|
copy ??= { ...src };
|
|
1965
2004
|
delete copy[key];
|
|
1966
2005
|
continue;
|
|
@@ -1968,10 +2007,13 @@ function sanitizeValue(value) {
|
|
|
1968
2007
|
const original = src[key];
|
|
1969
2008
|
let next = original;
|
|
1970
2009
|
if (typeof original === "string") {
|
|
1971
|
-
|
|
2010
|
+
const kind = classifyUrlKey(key);
|
|
2011
|
+
if (kind === "href") {
|
|
1972
2012
|
next = sanitizeHref(original);
|
|
1973
|
-
} else if (
|
|
2013
|
+
} else if (kind === "image") {
|
|
1974
2014
|
next = sanitizeImageSrc(original);
|
|
2015
|
+
} else if (kind === "embed") {
|
|
2016
|
+
next = sanitizeEmbedSrc(original);
|
|
1975
2017
|
}
|
|
1976
2018
|
} else if (typeof original === "object" && original !== null) {
|
|
1977
2019
|
next = sanitizeValue(original);
|
|
@@ -3284,6 +3326,7 @@ function ChannelCombobox({
|
|
|
3284
3326
|
)
|
|
3285
3327
|
] });
|
|
3286
3328
|
}
|
|
3329
|
+
var EMBED_SELECTOR = "iframe[src], embed[src], object[data], source[src], video[src], audio[src], track[src]";
|
|
3287
3330
|
function SlackBlockPreview({
|
|
3288
3331
|
block,
|
|
3289
3332
|
hooks,
|
|
@@ -3316,6 +3359,17 @@ function SlackBlockPreview({
|
|
|
3316
3359
|
img.setAttribute("data-bk-blocked-src", "1");
|
|
3317
3360
|
}
|
|
3318
3361
|
}
|
|
3362
|
+
for (const el of root.querySelectorAll(EMBED_SELECTOR)) {
|
|
3363
|
+
const attr = el.tagName === "OBJECT" ? "data" : "src";
|
|
3364
|
+
if (!isSafeEmbedSrc(el.getAttribute(attr))) {
|
|
3365
|
+
el.removeAttribute(attr);
|
|
3366
|
+
el.setAttribute("data-bk-blocked-src", "1");
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
for (const frame of root.querySelectorAll("iframe[srcdoc]")) {
|
|
3370
|
+
frame.removeAttribute("srcdoc");
|
|
3371
|
+
frame.setAttribute("data-bk-blocked-srcdoc", "1");
|
|
3372
|
+
}
|
|
3319
3373
|
});
|
|
3320
3374
|
return (
|
|
3321
3375
|
// The library scopes all of its CSS under `#slack_blocks_to_jsx` and
|
|
@@ -9168,7 +9222,14 @@ function SourcesField({
|
|
|
9168
9222
|
] })
|
|
9169
9223
|
] });
|
|
9170
9224
|
}
|
|
9225
|
+
function UnsafeUrlNote({ children }) {
|
|
9226
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[11px] text-destructive", children });
|
|
9227
|
+
}
|
|
9171
9228
|
function VideoEditor({ block, onChange }) {
|
|
9229
|
+
const videoUrlIsUnsafe = (block.video_url ?? "").length > 0 && !isSafeEmbedSrc(block.video_url);
|
|
9230
|
+
const thumbnailIsUnsafe = (block.thumbnail_url ?? "").length > 0 && !isSafeImageSrc(block.thumbnail_url);
|
|
9231
|
+
const titleUrlIsUnsafe = (block.title_url ?? "").length > 0 && !isSafeHref(block.title_url);
|
|
9232
|
+
const providerIconIsUnsafe = (block.provider_icon_url ?? "").length > 0 && !isSafeImageSrc(block.provider_icon_url);
|
|
9172
9233
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
9173
9234
|
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Title", help: "Up to 199 characters. Shown above the embedded player.", htmlFor: "video-title", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9174
9235
|
Input,
|
|
@@ -9192,50 +9253,62 @@ function VideoEditor({ block, onChange }) {
|
|
|
9192
9253
|
onChange: (e) => onChange({ ...block, alt_text: e.target.value })
|
|
9193
9254
|
}
|
|
9194
9255
|
) }),
|
|
9195
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9256
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9196
9257
|
EditorField,
|
|
9197
9258
|
{
|
|
9198
9259
|
label: "Video URL",
|
|
9199
9260
|
help: "HTTPS embed URL. Must match an unfurl domain on your Slack app.",
|
|
9200
9261
|
htmlFor: "video-url",
|
|
9201
|
-
children:
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9262
|
+
children: [
|
|
9263
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9264
|
+
Input,
|
|
9265
|
+
{
|
|
9266
|
+
id: "video-url",
|
|
9267
|
+
type: "url",
|
|
9268
|
+
value: block.video_url ?? "",
|
|
9269
|
+
placeholder: "e.g. https://www.youtube.com/embed/dQw4w9WgXcQ",
|
|
9270
|
+
onChange: (e) => onChange({ ...block, video_url: e.target.value }),
|
|
9271
|
+
"aria-invalid": videoUrlIsUnsafe || void 0
|
|
9272
|
+
}
|
|
9273
|
+
),
|
|
9274
|
+
videoUrlIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "The embedded player only accepts a full http(s) URL. This URL will be stripped before send and preview." })
|
|
9275
|
+
]
|
|
9211
9276
|
}
|
|
9212
9277
|
),
|
|
9213
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9278
|
+
/* @__PURE__ */ jsxRuntime.jsxs(EditorField, { label: "Thumbnail URL", help: "Preview image shown before the video loads.", htmlFor: "video-thumbnail", children: [
|
|
9279
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9280
|
+
Input,
|
|
9281
|
+
{
|
|
9282
|
+
id: "video-thumbnail",
|
|
9283
|
+
type: "url",
|
|
9284
|
+
value: block.thumbnail_url ?? "",
|
|
9285
|
+
placeholder: "e.g. https://example.com/thumb.png",
|
|
9286
|
+
onChange: (e) => onChange({ ...block, thumbnail_url: e.target.value }),
|
|
9287
|
+
"aria-invalid": thumbnailIsUnsafe || void 0
|
|
9288
|
+
}
|
|
9289
|
+
),
|
|
9290
|
+
thumbnailIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "Only http(s) image URLs are allowed. This URL will be stripped before send and preview." })
|
|
9291
|
+
] }),
|
|
9292
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9224
9293
|
EditorField,
|
|
9225
9294
|
{
|
|
9226
9295
|
label: "Title link URL",
|
|
9227
9296
|
help: "Optional. HTTPS link the title text opens (the non-embed page).",
|
|
9228
9297
|
htmlFor: "video-title-url",
|
|
9229
|
-
children:
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9298
|
+
children: [
|
|
9299
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9300
|
+
Input,
|
|
9301
|
+
{
|
|
9302
|
+
id: "video-title-url",
|
|
9303
|
+
type: "url",
|
|
9304
|
+
value: block.title_url ?? "",
|
|
9305
|
+
placeholder: "e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
9306
|
+
onChange: (e) => onChange({ ...block, title_url: e.target.value || void 0 }),
|
|
9307
|
+
"aria-invalid": titleUrlIsUnsafe || void 0
|
|
9308
|
+
}
|
|
9309
|
+
),
|
|
9310
|
+
titleUrlIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "Only http(s), mailto, tel, sms, and xmpp links are allowed. This URL will be stripped before send and preview." })
|
|
9311
|
+
]
|
|
9239
9312
|
}
|
|
9240
9313
|
),
|
|
9241
9314
|
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Description", help: "Optional. Up to 199 characters.", htmlFor: "video-description", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9279,22 +9352,26 @@ function VideoEditor({ block, onChange }) {
|
|
|
9279
9352
|
)
|
|
9280
9353
|
}
|
|
9281
9354
|
),
|
|
9282
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9355
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9283
9356
|
EditorField,
|
|
9284
9357
|
{
|
|
9285
9358
|
label: "Provider icon URL",
|
|
9286
9359
|
help: "Optional. Small icon shown next to the provider name.",
|
|
9287
9360
|
htmlFor: "video-provider-icon",
|
|
9288
|
-
children:
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9361
|
+
children: [
|
|
9362
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9363
|
+
Input,
|
|
9364
|
+
{
|
|
9365
|
+
id: "video-provider-icon",
|
|
9366
|
+
type: "url",
|
|
9367
|
+
value: block.provider_icon_url ?? "",
|
|
9368
|
+
placeholder: "e.g. https://example.com/youtube-favicon.png",
|
|
9369
|
+
onChange: (e) => onChange({ ...block, provider_icon_url: e.target.value || void 0 }),
|
|
9370
|
+
"aria-invalid": providerIconIsUnsafe || void 0
|
|
9371
|
+
}
|
|
9372
|
+
),
|
|
9373
|
+
providerIconIsUnsafe && /* @__PURE__ */ jsxRuntime.jsx(UnsafeUrlNote, { children: "Only http(s) image URLs are allowed. This URL will be stripped before send and preview." })
|
|
9374
|
+
]
|
|
9298
9375
|
}
|
|
9299
9376
|
)
|
|
9300
9377
|
] });
|