n8n-editor-ui
Version:
Workflow Editor UI for n8n
461 lines (460 loc) • 16.3 kB
JavaScript
import { o as __toESM } from "./chunk-6z4oVpB-.js";
import { C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, Ut as toValue, V as isVNode, Z as onMounted, _ as Fragment, at as renderSlot, bn as normalizeStyle, bt as withCtx, ct as resolveDynamicComponent, et as openBlock, h as withModifiers, mn as isString, n as Transition, o as render, p as vShow, tn as init_shared_esm_bundler, vn as normalizeClass, w as createBaseVNode, xt as withDirectives } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { gt as useI18n } from "./_MapCache-B159LOu5.js";
import { F as definePropType, J as isElement, Lt as useTimeoutFn, P as buildProps, Pt as isClient, S as useGlobalComponentSettings, V as debugWarn, b as require_lib, kt as useEventListener } from "./truncate-Dc5upPom.js";
import { c as CloseComponents, f as iconPropType, n as _export_sfc, o as withInstallFunction, r as EVENT_CODE, t as ElIcon, u as TypeComponentsMap } from "./icon-MZ_3fB_h.js";
import { Fi as useUIStore, o as useWorkflowsStore, t as useTelemetry } from "./useTelemetry-C797TaYa.js";
import { A as ALLOWED_HTML_TAGS, Io as VIEWS, k as ALLOWED_HTML_ATTRIBUTES } from "./constants-CX1ozgFz.js";
import { t as useExternalHooks } from "./useExternalHooks-CYMS6JpD.js";
import { t as useStyles } from "./useStyles-DhM0cpS7.js";
var notificationTypes = [
"success",
"info",
"warning",
"error"
];
var notificationProps = buildProps({
customClass: {
type: String,
default: ""
},
dangerouslyUseHTMLString: {
type: Boolean,
default: false
},
duration: {
type: Number,
default: 4500
},
icon: { type: iconPropType },
id: {
type: String,
default: ""
},
message: {
type: definePropType([String, Object]),
default: ""
},
offset: {
type: Number,
default: 0
},
onClick: {
type: definePropType(Function),
default: () => void 0
},
onClose: {
type: definePropType(Function),
required: true
},
position: {
type: String,
values: [
"top-right",
"top-left",
"bottom-right",
"bottom-left"
],
default: "top-right"
},
showClose: {
type: Boolean,
default: true
},
title: {
type: String,
default: ""
},
type: {
type: String,
values: [...notificationTypes, ""],
default: ""
},
zIndex: Number
});
var notificationEmits = { destroy: () => true };
var _hoisted_1 = ["id"];
var _hoisted_2 = ["textContent"];
var _hoisted_3 = { key: 0 };
var _hoisted_4 = ["innerHTML"];
var NotificationConstructor = /* @__PURE__ */ _export_sfc(/* @__PURE__ */ defineComponent({
...defineComponent({ name: "ElNotification" }),
props: notificationProps,
emits: notificationEmits,
setup(__props, { expose }) {
const props = __props;
const { ns, zIndex } = useGlobalComponentSettings("notification");
const { nextZIndex, currentZIndex } = zIndex;
const { Close } = CloseComponents;
const visible = ref(false);
let timer = void 0;
const typeClass = computed(() => {
const type = props.type;
return type && TypeComponentsMap[props.type] ? ns.m(type) : "";
});
const iconComponent = computed(() => {
if (!props.type) return props.icon;
return TypeComponentsMap[props.type] || props.icon;
});
const horizontalClass = computed(() => props.position.endsWith("right") ? "right" : "left");
const verticalProperty = computed(() => props.position.startsWith("top") ? "top" : "bottom");
const positionStyle = computed(() => {
var _a;
return {
[verticalProperty.value]: `${props.offset}px`,
zIndex: (_a = props.zIndex) != null ? _a : currentZIndex.value
};
});
function startTimer() {
if (props.duration > 0) ({stop: timer} = useTimeoutFn(() => {
if (visible.value) close$1();
}, props.duration));
}
function clearTimer() {
timer?.();
}
function close$1() {
visible.value = false;
}
function onKeydown({ code }) {
if (code === EVENT_CODE.delete || code === EVENT_CODE.backspace) clearTimer();
else if (code === EVENT_CODE.esc) {
if (visible.value) close$1();
} else startTimer();
}
onMounted(() => {
startTimer();
nextZIndex();
visible.value = true;
});
useEventListener(document, "keydown", onKeydown);
expose({
visible,
close: close$1
});
return (_ctx, _cache) => {
return openBlock(), createBlock(Transition, {
name: unref(ns).b("fade"),
onBeforeLeave: _ctx.onClose,
onAfterLeave: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("destroy")),
persisted: ""
}, {
default: withCtx(() => [withDirectives(createBaseVNode("div", {
id: _ctx.id,
class: normalizeClass([
unref(ns).b(),
_ctx.customClass,
unref(horizontalClass)
]),
style: normalizeStyle(unref(positionStyle)),
role: "alert",
onMouseenter: clearTimer,
onMouseleave: startTimer,
onClick: _cache[0] || (_cache[0] = (...args) => _ctx.onClick && _ctx.onClick(...args))
}, [unref(iconComponent) ? (openBlock(), createBlock(unref(ElIcon), {
key: 0,
class: normalizeClass([unref(ns).e("icon"), unref(typeClass)])
}, {
default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(unref(iconComponent))))]),
_: 1
}, 8, ["class"])) : createCommentVNode("v-if", true), createBaseVNode("div", { class: normalizeClass(unref(ns).e("group")) }, [
createBaseVNode("h2", {
class: normalizeClass(unref(ns).e("title")),
textContent: toDisplayString(_ctx.title)
}, null, 10, _hoisted_2),
withDirectives(createBaseVNode("div", {
class: normalizeClass(unref(ns).e("content")),
style: normalizeStyle(!!_ctx.title ? void 0 : { margin: 0 })
}, [renderSlot(_ctx.$slots, "default", {}, () => [!_ctx.dangerouslyUseHTMLString ? (openBlock(), createElementBlock("p", _hoisted_3, toDisplayString(_ctx.message), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" Caution here, message could've been compromised, never use user's input as message "), createBaseVNode("p", { innerHTML: _ctx.message }, null, 8, _hoisted_4)], 2112))])], 6), [[vShow, _ctx.message]]),
_ctx.showClose ? (openBlock(), createBlock(unref(ElIcon), {
key: 0,
class: normalizeClass(unref(ns).e("closeBtn")),
onClick: withModifiers(close$1, ["stop"])
}, {
default: withCtx(() => [createVNode(unref(Close))]),
_: 1
}, 8, ["class", "onClick"])) : createCommentVNode("v-if", true)
], 2)], 46, _hoisted_1), [[vShow, visible.value]])]),
_: 3
}, 8, ["name", "onBeforeLeave"]);
};
}
}), [["__file", "/home/runner/work/element-plus/element-plus/packages/components/notification/src/notification.vue"]]);
init_shared_esm_bundler();
var notifications = {
"top-left": [],
"top-right": [],
"bottom-left": [],
"bottom-right": []
};
var GAP_SIZE = 16;
var seed = 1;
var notify = function(options = {}, context = null) {
if (!isClient) return { close: () => void 0 };
if (typeof options === "string" || isVNode(options)) options = { message: options };
const position = options.position || "top-right";
let verticalOffset = options.offset || 0;
notifications[position].forEach(({ vm: vm2 }) => {
var _a;
verticalOffset += (((_a = vm2.el) == null ? void 0 : _a.offsetHeight) || 0) + GAP_SIZE;
});
verticalOffset += GAP_SIZE;
const id = `notification_${seed++}`;
const userOnClose = options.onClose;
const props = {
...options,
offset: verticalOffset,
id,
onClose: () => {
close(id, position, userOnClose);
}
};
let appendTo = document.body;
if (isElement(options.appendTo)) appendTo = options.appendTo;
else if (isString(options.appendTo)) appendTo = document.querySelector(options.appendTo);
if (!isElement(appendTo)) {
debugWarn("ElNotification", "the appendTo option is not an HTMLElement. Falling back to document.body.");
appendTo = document.body;
}
const container = document.createElement("div");
const vm = createVNode(NotificationConstructor, props, isVNode(props.message) ? { default: () => props.message } : null);
vm.appContext = context != null ? context : notify._context;
vm.props.onDestroy = () => {
render(null, container);
};
render(vm, container);
notifications[position].push({ vm });
appendTo.appendChild(container.firstElementChild);
return { close: () => {
vm.component.exposed.visible.value = false;
} };
};
notificationTypes.forEach((type) => {
notify[type] = (options = {}) => {
if (typeof options === "string" || isVNode(options)) options = { message: options };
return notify({
...options,
type
});
};
});
function close(id, position, userOnClose) {
const orientedNotifications = notifications[position];
const idx = orientedNotifications.findIndex(({ vm: vm2 }) => {
var _a;
return ((_a = vm2.component) == null ? void 0 : _a.props.id) === id;
});
if (idx === -1) return;
const { vm } = orientedNotifications[idx];
if (!vm) return;
userOnClose?.(vm);
const removedHeight = vm.el.offsetHeight;
const verticalPos = position.split("-")[0];
orientedNotifications.splice(idx, 1);
const len = orientedNotifications.length;
if (len < 1) return;
for (let i = idx; i < len; i++) {
const { el, component } = orientedNotifications[i].vm;
const pos = Number.parseInt(el.style[verticalPos], 10) - removedHeight - GAP_SIZE;
component.props.offset = pos;
}
}
function closeAll() {
for (const orientedNotifications of Object.values(notifications)) orientedNotifications.forEach(({ vm }) => {
vm.component.exposed.visible.value = false;
});
}
notify.closeAll = closeAll;
notify._context = null;
var ElNotification = withInstallFunction(notify, "$notify");
var import_lib = /* @__PURE__ */ __toESM(require_lib());
function sanitizeHtml(dirtyHtml) {
return (0, import_lib.default)(dirtyHtml, {
onTagAttr: (tag, name, value) => {
if (tag === "img" && name === "src") {
const isStaticImageFile = value.split("#")[0].match(/\.(jpeg|jpg|gif|png|webp)$/) !== null && value.startsWith("/static/");
if (!value.startsWith("https://") && !isStaticImageFile) return "";
}
if (ALLOWED_HTML_ATTRIBUTES.includes(name) || name.startsWith("data-")) {
if (name === "href" && !value.match(/^https?:\/\//gm) && !value.startsWith("/")) return "";
return `${name}="${(0, import_lib.escapeAttrValue)(value)}"`;
}
},
onTag: (tag) => {
if (!ALLOWED_HTML_TAGS.includes(tag)) return "";
}
});
}
const sanitizeIfString = (message) => {
if (typeof message === "string") return sanitizeHtml(message);
return message;
};
const capitalizeFirstLetter = (text) => {
return text.charAt(0).toUpperCase() + text.slice(1);
};
const getBannerRowHeight = async () => {
return await new Promise((resolve) => {
setTimeout(() => {
resolve(document.getElementById("banners")?.clientHeight ?? 0);
}, 0);
});
};
function isOutsideSelected(el) {
const selection = document.getSelection();
if (!selection?.anchorNode || !selection.focusNode || !el) return false;
return !el.contains(selection.anchorNode) && !el.contains(selection.focusNode) && (selection.anchorNode !== selection.focusNode || selection.anchorOffset !== selection.focusOffset);
}
var scrollbarWidth;
function getScrollbarWidth() {
if (scrollbarWidth !== void 0) return scrollbarWidth;
const outer = document.createElement("div");
const inner = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.overflow = "scroll";
document.body.appendChild(outer);
outer.appendChild(inner);
scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
outer.parentElement?.removeChild(outer);
return scrollbarWidth;
}
function isEventTargetContainedBy(eventTarget, maybeContainer) {
return !!(eventTarget instanceof Node && toValue(maybeContainer)?.contains(eventTarget));
}
var stickyNotificationQueue = [];
function useToast() {
const telemetry = useTelemetry();
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
const externalHooks = useExternalHooks();
const i18n = useI18n();
const { APP_Z_INDEXES } = useStyles();
function showMessage(messageData, track = true) {
const messageDefaults = {
dangerouslyUseHTMLString: true,
position: "bottom-right",
zIndex: APP_Z_INDEXES.TOASTS,
appendTo: "#app-grid",
customClass: "content-toast"
};
const { message, title } = messageData;
const params = {
...messageDefaults,
...messageData
};
if (typeof message === "string") params.message = sanitizeHtml(message);
if (typeof title === "string") params.title = sanitizeHtml(title);
const notification = ElNotification(params);
if (params.duration === 0) stickyNotificationQueue.push(notification);
if (params.type === "error" && track) {
let messageForTelemetry;
if (typeof params.message === "string") messageForTelemetry = params.message;
else if (params.message && typeof params.message === "object" && "props" in params.message && params.message.props) {
const props = params.message.props;
const hasErrorMessage = typeof props === "object" && props !== null && "errorMessage" in props;
const hasMessage = typeof props === "object" && props !== null && "message" in props;
if (hasErrorMessage) messageForTelemetry = String(props.errorMessage);
else if (hasMessage) messageForTelemetry = String(props.message);
else messageForTelemetry = "Unknown error";
} else messageForTelemetry = "Unknown error";
telemetry.track("Instance FE emitted error", {
error_title: params.title,
error_message: messageForTelemetry,
caused_by_credential: causedByCredential(messageForTelemetry),
workflow_id: workflowsStore.workflowId
});
}
return notification;
}
function showToast(config) {
let notification;
if (config.closeOnClick) {
const cb = config.onClick;
config.onClick = () => {
if (notification) notification.close();
if (cb) cb();
};
}
notification = showMessage({
title: config.title,
message: config.message,
onClick: config.onClick,
onClose: config.onClose,
duration: config.duration,
customClass: config.customClass,
type: config.type
});
return notification;
}
function collapsableDetails({ description, node }) {
if (!description) return "";
const errorDescription = description.length > 500 ? `${description.slice(0, 500)}...` : description;
return `
<br>
<br>
<details>
<summary
style="color: #ff6d5a; font-weight: bold; cursor: pointer;"
>
${i18n.baseText("showMessage.showDetails")}
</summary>
<p>${node.name}: ${errorDescription}</p>
</details>
`;
}
function showError(e, title, message) {
const error = e;
showMessage({
title,
message: `
${message ? `${message}<br/>` : ""}
<i>${error.message}</i>
${collapsableDetails(error)}`,
type: "error",
duration: 0
}, false);
externalHooks.run("showMessage.showError", {
title,
message,
errorMessage: error.message
});
telemetry.track("Instance FE emitted error", {
error_title: title,
error_description: message,
error_message: error.message,
caused_by_credential: causedByCredential(error.message),
workflow_id: workflowsStore.workflowId
});
}
function causedByCredential(message) {
if (!message || typeof message !== "string") return false;
return message.includes("Credentials for") && message.includes("are not set");
}
function clearAllStickyNotifications() {
stickyNotificationQueue.forEach((notification) => {
if (notification) notification.close();
});
stickyNotificationQueue.length = 0;
}
function showNotificationForViews(views) {
const notifications$1 = [];
views.forEach((view) => {
notifications$1.push(...uiStore.pendingNotificationsForViews[view] ?? []);
});
if (notifications$1.length) {
notifications$1.forEach(async (notification) => {
setTimeout(() => {
showMessage(notification);
}, 5);
});
uiStore.setNotificationsForView(VIEWS.WORKFLOW, []);
}
}
return {
showMessage,
showToast,
showError,
clearAllStickyNotifications,
showNotificationForViews
};
}
export { isEventTargetContainedBy as a, sanitizeIfString as c, getScrollbarWidth as i, capitalizeFirstLetter as n, isOutsideSelected as o, getBannerRowHeight as r, sanitizeHtml as s, useToast as t };