@nextcloud/vue
Version:
Nextcloud vue components
293 lines (292 loc) • 11.9 kB
JavaScript
import '../assets/NcReferenceWidget.css';
import { defineComponent, inject, ref, useTemplateRef, nextTick, computed, watch, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, normalizeStyle, createElementVNode, withKeys, withModifiers, unref, createCommentVNode, createBlock, resolveDynamicComponent, mergeProps, withCtx, toDisplayString, createTextVNode } from "vue";
import { useElementSize, useIntersectionObserver } from "@vueuse/core";
import { routerKey, RouterLink } from "vue-router";
import { N as NcButton } from "./NcButton.mjs";
import { r as register, m as t26, a as t } from "./_l10n.mjs";
import { c as createElementId } from "./createElementId.mjs";
import { b as hasFullWidth, c as isResizable, i as isWidgetRegistered, h as hasInteractiveView, a as renderWidget, d as destroyWidget } from "./widgets.mjs";
import { g as getRoute } from "./autolink.mjs";
import { _ as _export_sfc } from "./_plugin-vue_export-helper.mjs";
register(t26);
const _hoisted_1 = ["aria-label", "aria-valuenow", "aria-valuemin", "aria-valuemax"];
const _hoisted_2 = ["src"];
const _hoisted_3 = { class: "widget-default--details" };
const _hoisted_4 = { class: "widget-default--name" };
const _hoisted_5 = { class: "widget-default--link" };
const IDLE_TIMEOUT = 3 * 60 * 1e3;
const RESIZE_KEYBOARD_STEP = 10;
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "NcReferenceWidget",
props: {
reference: {},
interactive: { type: Boolean, default: true },
interactiveOptIn: { type: Boolean, default: false }
},
setup(__props) {
const props = __props;
const router = inject(routerKey, null);
const widgetId = `nc-reference-widget-${createElementId()}`;
const isVisible = ref(false);
const customWidget = useTemplateRef("customWidget");
const customWidgetContent = useTemplateRef("customWidgetContent");
const widgetRoot = useTemplateRef("widgetRoot");
const { width } = useElementSize(widgetRoot);
const { height: customWidgetHeight } = useElementSize(customWidget);
useIntersectionObserver(widgetRoot, ([entry]) => {
nextTick(() => {
isVisible.value = entry.isIntersecting;
});
});
const showInteractive = ref(false);
const rendered = ref(false);
let idleTimeout = null;
const isResizing = ref(false);
const resizedHeight = ref(null);
const resizeStartY = ref(0);
const resizeStartHeight = ref(0);
const resizeMinHeight = ref(100);
const resizeMaxHeight = ref(window.innerHeight - 120);
const isInteractive = computed(() => {
return !props.interactiveOptIn && props.interactive || showInteractive.value;
});
const referenceHasFullWidth = computed(() => {
return hasFullWidth(props.reference.richObjectType);
});
const isWidgetResizable = computed(() => {
return isResizable(props.reference.richObjectType);
});
const hasCustomWidget = computed(() => {
return isWidgetRegistered(props.reference.richObjectType);
});
const customWidgetStyle = computed(() => {
if (!resizedHeight.value) {
return null;
}
return { height: `${resizedHeight.value}px !important` };
});
const ariaValueNow = computed(() => {
return Math.round(resizedHeight.value ?? customWidgetHeight.value);
});
const referenceHasInteractiveView = computed(() => {
return hasCustomWidget.value && hasInteractiveView(props.reference.richObjectType);
});
const noAccess = computed(() => {
return !props.reference.accessible;
});
const numberOfLines = computed(() => {
const lineCountOffsets = [450, 550, 650, Infinity];
return lineCountOffsets.findIndex((max) => width.value < max);
});
const descriptionStyle = computed(() => {
if (numberOfLines.value === 0) {
return {
display: "none"
};
}
const lineClamp = numberOfLines.value;
return {
lineClamp,
webkitLineClamp: lineClamp
};
});
const compactLink = computed(() => {
const link = props.reference.openGraphObject.link;
if (!link) {
return "";
}
if (link.startsWith("https://")) {
return link.substring(8);
}
if (link.startsWith("http://")) {
return link.substring(7);
}
return link;
});
const route = computed(() => {
return getRoute(router, props.reference.openGraphObject.link);
});
const referenceWidgetLinkComponent = computed(() => {
return route.value ? RouterLink : "a";
});
const referenceWidgetLinkProps = computed(() => {
return route.value ? { to: route.value } : { href: props.reference.openGraphObject.link, target: "_blank" };
});
watch(isVisible, (val) => {
if (!val) {
idleTimeout = setTimeout(() => {
if (!isVisible.value) {
destroyReferenceWidget();
}
}, IDLE_TIMEOUT);
return;
}
if (idleTimeout) {
clearTimeout(idleTimeout);
idleTimeout = null;
}
if (!rendered.value) {
renderReferenceWidget();
}
}, { immediate: true });
onBeforeUnmount(() => {
if (idleTimeout) {
clearTimeout(idleTimeout);
idleTimeout = null;
}
stopResize();
destroyReferenceWidget();
});
function enableInteractive() {
showInteractive.value = true;
renderReferenceWidget();
}
function renderReferenceWidget() {
if (!customWidgetContent.value) {
return;
}
if (props.reference.richObjectType === "open-graph") {
return;
}
customWidgetContent.value.innerHTML = "";
const widget = document.createElement("div");
widget.style.width = "100%";
customWidgetContent.value.appendChild(widget);
nextTick(() => {
renderWidget(widget, {
...props.reference,
interactive: isInteractive.value
});
rendered.value = true;
});
}
function destroyReferenceWidget() {
if (rendered.value && widgetRoot.value) {
destroyWidget(props.reference.richObjectType, widgetRoot.value);
rendered.value = false;
}
}
function initResizeLimits() {
const widgetEl = customWidgetContent.value?.firstElementChild;
if (widgetEl) {
const computedStyle = window.getComputedStyle(widgetEl);
const parsedMin = parseFloat(computedStyle.minHeight);
const parsedMax = parseFloat(computedStyle.maxHeight);
resizeMinHeight.value = parsedMin > 0 ? parsedMin : 100;
resizeMaxHeight.value = Number.isFinite(parsedMax) && parsedMax > 0 ? parsedMax : window.innerHeight - 120;
} else {
resizeMinHeight.value = 100;
resizeMaxHeight.value = window.innerHeight - 120;
}
}
function startResize(event) {
if (!isWidgetResizable.value || !customWidget.value) {
return;
}
initResizeLimits();
isResizing.value = true;
resizeStartY.value = event.clientY;
resizeStartHeight.value = customWidget.value.getBoundingClientRect().height;
window.addEventListener("pointermove", onResize);
window.addEventListener("pointerup", stopResize);
}
function onResizeKeydown(delta) {
if (!isWidgetResizable.value || !customWidget.value) {
return;
}
initResizeLimits();
const currentHeight = resizedHeight.value ?? resizeStartHeight.value;
const next = currentHeight + delta;
resizedHeight.value = Math.min(resizeMaxHeight.value, Math.max(resizeMinHeight.value, next));
}
function onResize(event) {
if (!isResizing.value || !customWidget.value) {
return;
}
const deltaY = event.clientY - resizeStartY.value;
resizedHeight.value = Math.min(resizeMaxHeight.value, Math.max(resizeMinHeight.value, resizeStartHeight.value + deltaY));
}
function stopResize() {
if (!isResizing.value) {
return;
}
isResizing.value = false;
window.removeEventListener("pointermove", onResize);
window.removeEventListener("pointerup", stopResize);
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref_key: "widgetRoot",
ref: widgetRoot,
class: normalizeClass({ "toggle-interactive": referenceHasInteractiveView.value && !isInteractive.value })
}, [
__props.reference && hasCustomWidget.value ? (openBlock(), createElementBlock("div", {
key: 0,
id: widgetId,
ref_key: "customWidget",
ref: customWidget,
class: normalizeClass(["widget-custom", { "full-width": referenceHasFullWidth.value }]),
style: normalizeStyle(customWidgetStyle.value)
}, [
createElementVNode("div", {
ref_key: "customWidgetContent",
ref: customWidgetContent,
class: "widget-custom__content"
}, null, 512),
isWidgetResizable.value ? (openBlock(), createElementBlock("div", {
key: 0,
class: "widget-custom__resize-handle",
role: "slider",
tabindex: "0",
"aria-orientation": "vertical",
"aria-label": unref(t)("Widget height"),
"aria-valuenow": ariaValueNow.value,
"aria-valuemin": resizeMinHeight.value,
"aria-valuemax": resizeMaxHeight.value,
"aria-controls": widgetId,
onPointerdown: withModifiers(startResize, ["stop", "prevent"]),
onKeydown: [
_cache[0] || (_cache[0] = withKeys(withModifiers(($event) => onResizeKeydown(-RESIZE_KEYBOARD_STEP), ["stop", "prevent"]), ["up"])),
_cache[1] || (_cache[1] = withKeys(withModifiers(($event) => onResizeKeydown(RESIZE_KEYBOARD_STEP), ["stop", "prevent"]), ["down"]))
]
}, null, 40, _hoisted_1)) : createCommentVNode("", true)
], 6)) : !noAccess.value && __props.reference && __props.reference.openGraphObject && !hasCustomWidget.value ? (openBlock(), createBlock(resolveDynamicComponent(referenceWidgetLinkComponent.value), mergeProps({ key: 1 }, referenceWidgetLinkProps.value, {
rel: "noopener noreferrer",
class: "widget-default"
}), {
default: withCtx(() => [
__props.reference.openGraphObject.thumb ? (openBlock(), createElementBlock("img", {
key: 0,
class: "widget-default--image",
src: __props.reference.openGraphObject.thumb
}, null, 8, _hoisted_2)) : createCommentVNode("", true),
createElementVNode("div", _hoisted_3, [
createElementVNode("p", _hoisted_4, toDisplayString(__props.reference.openGraphObject.name), 1),
createElementVNode("p", {
class: "widget-default--description",
style: normalizeStyle(descriptionStyle.value)
}, toDisplayString(__props.reference.openGraphObject.description), 5),
createElementVNode("p", _hoisted_5, toDisplayString(compactLink.value), 1)
])
]),
_: 1
}, 16)) : createCommentVNode("", true),
__props.interactiveOptIn && referenceHasInteractiveView.value && !isInteractive.value ? (openBlock(), createBlock(NcButton, {
key: 2,
class: "toggle-interactive--button",
onClick: enableInteractive
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(t)("Enable interactive view")), 1)
]),
_: 1
})) : createCommentVNode("", true)
], 2);
};
}
});
const NcReferenceWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-4b144346"]]);
export {
NcReferenceWidget as N
};
//# sourceMappingURL=NcReferenceWidget.mjs.map