mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
some list things; image uplaod
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -223,7 +223,6 @@
|
||||
{{- if $i }},{{ end }}
|
||||
"{{ $content.Id }}": {
|
||||
extent: "{{ $content.Extent }}",
|
||||
pagination: "{{ $content.MusenalmPagination }}",
|
||||
musenalmType: [{{- range $j, $t := $content.MusenalmType -}}{{- if $j }},{{ end }}"{{ $t }}"{{- end -}}],
|
||||
preferredTitle: "{{ $content.PreferredTitle }}",
|
||||
titleStmt: "{{ $content.TitleStmt }}",
|
||||
@@ -296,7 +295,6 @@
|
||||
// Field labels for display
|
||||
const fieldLabels = {
|
||||
extent: 'Seite',
|
||||
pagination: 'Paginierung',
|
||||
musenalmType: 'Typ',
|
||||
titleStmt: 'Titel',
|
||||
subtitleStmt: 'Untertitel',
|
||||
@@ -311,6 +309,51 @@
|
||||
annotation: 'Anmerkung'
|
||||
};
|
||||
|
||||
const getMarkTargets = () => {
|
||||
if (!list) {
|
||||
return [];
|
||||
}
|
||||
const visibleItems = Array.from(list.querySelectorAll("[data-role='content-item']")).filter((item) => item.style.display !== "none");
|
||||
const targets = [];
|
||||
visibleItems.forEach((item) => {
|
||||
targets.push(...item.querySelectorAll(".content-search-text"));
|
||||
});
|
||||
return targets;
|
||||
};
|
||||
|
||||
const updateMarks = () => {
|
||||
if (typeof Mark !== "function") {
|
||||
return;
|
||||
}
|
||||
const terms = [];
|
||||
const rawQuery = (filterInput.value || "").trim();
|
||||
const rawPage = (filterPage?.value || "").trim();
|
||||
const rawType = (filterType?.value || "").trim();
|
||||
if (rawQuery) {
|
||||
terms.push(rawQuery);
|
||||
}
|
||||
if (rawPage) {
|
||||
terms.push(rawPage);
|
||||
}
|
||||
if (rawType) {
|
||||
terms.push(rawType);
|
||||
}
|
||||
const targets = getMarkTargets();
|
||||
if (targets.length === 0) {
|
||||
return;
|
||||
}
|
||||
const markInstance = new Mark(targets);
|
||||
markInstance.unmark({
|
||||
done: () => {
|
||||
if (terms.length > 0) {
|
||||
markInstance.mark(terms, {
|
||||
separateWordSearch: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const performFilter = () => {
|
||||
const query = normalizeText(filterInput.value);
|
||||
const pageQuery = normalizeText(filterPage?.value || '');
|
||||
@@ -401,8 +444,6 @@
|
||||
};
|
||||
|
||||
// Check text search fields (excluding language, status, preferredTitle)
|
||||
checkField('extent', contentData.extent, fieldLabels.extent);
|
||||
checkField('pagination', contentData.pagination, fieldLabels.pagination);
|
||||
checkArrayField('musenalmType', contentData.musenalmType, fieldLabels.musenalmType);
|
||||
checkField('titleStmt', contentData.titleStmt, fieldLabels.titleStmt);
|
||||
checkField('subtitleStmt', contentData.subtitleStmt, fieldLabels.subtitleStmt);
|
||||
@@ -463,6 +504,8 @@
|
||||
? `${totalCount} Einträge`
|
||||
: `${visibleCount} von ${totalCount} Einträgen`;
|
||||
}
|
||||
|
||||
updateMarks();
|
||||
};
|
||||
|
||||
const debounceFilter = () => {
|
||||
@@ -522,6 +565,15 @@
|
||||
}
|
||||
syncIndicator.classList.toggle("hidden", !active);
|
||||
};
|
||||
const setDraggingState = (active) => {
|
||||
if (active) {
|
||||
document.body.dataset.dragging = "true";
|
||||
} else {
|
||||
delete document.body.dataset.dragging;
|
||||
}
|
||||
window.__toolTipDragging = active;
|
||||
window.dispatchEvent(new CustomEvent("contentsdragging", { detail: { active } }));
|
||||
};
|
||||
// Shared delete dialog
|
||||
const deleteDialog = document.getElementById("content-delete-dialog");
|
||||
const deleteDialogTitle = document.getElementById("content-delete-dialog-title");
|
||||
@@ -665,8 +717,59 @@
|
||||
if (list.dataset.pageInit !== "true") {
|
||||
list.dataset.pageInit = "true";
|
||||
let draggedItem = null;
|
||||
let pointerDrag = null;
|
||||
let lastDragOverTime = 0;
|
||||
const DRAG_THROTTLE_MS = 100;
|
||||
const startPointerDrag = (event) => {
|
||||
const handle = event.target.closest("[data-role='content-drag-handle']");
|
||||
if (!handle || event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
const item = handle.closest("[data-role='content-item']");
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
pointerDrag = {
|
||||
item,
|
||||
pointerId: event.pointerId,
|
||||
};
|
||||
item.dataset.dragging = "true";
|
||||
item.classList.add("opacity-60");
|
||||
setDraggingState(true);
|
||||
if (handle.setPointerCapture) {
|
||||
handle.setPointerCapture(event.pointerId);
|
||||
}
|
||||
};
|
||||
|
||||
const movePointerDrag = (event) => {
|
||||
if (!pointerDrag || event.pointerId !== pointerDrag.pointerId) {
|
||||
return;
|
||||
}
|
||||
const item = pointerDrag.item;
|
||||
const targetItem = document.elementFromPoint(event.clientX, event.clientY)?.closest("[data-role='content-item']");
|
||||
if (!targetItem || targetItem === item) {
|
||||
return;
|
||||
}
|
||||
const rect = targetItem.getBoundingClientRect();
|
||||
const before = event.clientY - rect.top < rect.height / 2;
|
||||
if (before) {
|
||||
targetItem.before(item);
|
||||
} else {
|
||||
targetItem.after(item);
|
||||
}
|
||||
};
|
||||
|
||||
const endPointerDrag = (event) => {
|
||||
if (!pointerDrag || event.pointerId !== pointerDrag.pointerId) {
|
||||
return;
|
||||
}
|
||||
pointerDrag.item.classList.remove("opacity-60");
|
||||
pointerDrag.item.dataset.dragging = "";
|
||||
pointerDrag = null;
|
||||
setDraggingState(false);
|
||||
syncOrder();
|
||||
};
|
||||
list.addEventListener("click", (event) => {
|
||||
const moveUp = event.target.closest("[data-role='content-move-up']");
|
||||
const moveDown = event.target.closest("[data-role='content-move-down']");
|
||||
@@ -699,6 +802,10 @@
|
||||
});
|
||||
|
||||
list.addEventListener("dragstart", (event) => {
|
||||
if (pointerDrag) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.target.closest("[data-role='content-move-up']") || event.target.closest("[data-role='content-move-down']")) {
|
||||
return;
|
||||
}
|
||||
@@ -717,6 +824,7 @@
|
||||
draggedItem = item;
|
||||
item.dataset.dragging = "true";
|
||||
draggedItem.classList.add("opacity-60");
|
||||
setDraggingState(true);
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", "move");
|
||||
});
|
||||
@@ -745,8 +853,14 @@
|
||||
draggedItem.dataset.dragging = "";
|
||||
}
|
||||
draggedItem = null;
|
||||
setDraggingState(false);
|
||||
syncOrder();
|
||||
});
|
||||
|
||||
list.addEventListener("pointerdown", startPointerDrag);
|
||||
list.addEventListener("pointermove", movePointerDrag);
|
||||
list.addEventListener("pointerup", endPointerDrag);
|
||||
list.addEventListener("pointercancel", endPointerDrag);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -209,6 +209,12 @@
|
||||
data-initial-values='[{{- range $i, $lang := $model.content.Language -}}{{- if $i }},{{ end -}}{{ printf "%q" $lang }}{{- end -}}]'>
|
||||
</multi-select-simple>
|
||||
</div>
|
||||
{{- template "_content_images_panel" (Dict
|
||||
"content" $model.content
|
||||
"entry" $model.result.Entry
|
||||
"csrf_token" $model.csrf_token
|
||||
"is_new" false
|
||||
) -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,30 +4,18 @@
|
||||
{{- $isNew := index . "is_new" -}}
|
||||
|
||||
{{- if or $content.ImagePaths (not $isNew) -}}
|
||||
<div class="w-full md:w-56 lg:w-72 shrink-0" data-role="content-images-panel">
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
{{- if $content.ImagePaths -}}
|
||||
<content-images
|
||||
data-images='[{{- range $i, $scan := $content.ImagePaths -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
|
||||
data-files='[{{- range $i, $scan := $content.Scans -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
|
||||
data-delete-endpoint="/almanach/{{ $entry.MusenalmID }}/contents/scan/delete"
|
||||
data-content-id="{{ $content.Id }}"
|
||||
data-csrf-token="{{ $csrf }}">
|
||||
</content-images>
|
||||
{{- end -}}
|
||||
<div class="w-full" data-role="content-images-panel">
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<content-images
|
||||
class="w-full"
|
||||
data-images='[{{- range $i, $scan := $content.ImagePaths -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
|
||||
data-files='[{{- range $i, $scan := $content.Scans -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
|
||||
data-delete-endpoint="/almanach/{{ $entry.MusenalmID }}/contents/scan/delete"
|
||||
data-content-id="{{ $content.Id }}"
|
||||
data-csrf-token="{{ $csrf }}">
|
||||
</content-images>
|
||||
{{- if not $isNew -}}
|
||||
<form
|
||||
class="flex"
|
||||
method="POST"
|
||||
action="/almanach/{{ $entry.MusenalmID }}/contents/upload"
|
||||
hx-post="/almanach/{{ $entry.MusenalmID }}/contents/upload"
|
||||
hx-trigger="change"
|
||||
hx-target="closest [data-role='content-images-panel']"
|
||||
hx-swap="outerHTML"
|
||||
hx-encoding="multipart/form-data"
|
||||
data-loading-label="Digitalisat wird hochgeladen">
|
||||
<input type="hidden" name="csrf_token" value="{{ $csrf }}" />
|
||||
<input type="hidden" name="content_id" value="{{ $content.Id }}" />
|
||||
<div class="flex" data-role="content-images-upload">
|
||||
<label
|
||||
for="content-{{ $content.Id }}-scan-upload"
|
||||
class="flex h-28 w-28 items-center justify-center rounded-xs border-2 border-dashed border-slate-300 bg-stone-50 text-lg font-semibold text-slate-600 transition hover:border-slate-400 hover:text-slate-800"
|
||||
@@ -40,8 +28,12 @@
|
||||
name="scans"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="sr-only" />
|
||||
</form>
|
||||
class="sr-only"
|
||||
data-role="content-images-upload-input"
|
||||
data-upload-endpoint="/almanach/{{ $entry.MusenalmID }}/contents/upload"
|
||||
data-content-id="{{ $content.Id }}"
|
||||
data-csrf-token="{{ $csrf }}" />
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,21 +37,25 @@
|
||||
</tool-tip>
|
||||
</div>
|
||||
{{- if $content.Extent -}}
|
||||
<span class="bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-sm font-semibold shadow-sm shrink-0" data-role="content-page-pill">S. {{- $content.Extent -}}</span>
|
||||
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-sm font-semibold shadow-sm shrink-0" data-role="content-page-pill">S. {{- $content.Extent -}}</span>
|
||||
{{- end -}}
|
||||
{{- if $content.MusenalmType -}}
|
||||
<span class="flex flex-nowrap gap-1 text-gray-700 font-normal overflow-hidden">
|
||||
{{- range $i, $t := $content.MusenalmType -}}
|
||||
<span class="bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-base font-semibold shadow-sm" data-role="content-type-pill">{{- $t -}}</span>
|
||||
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-base font-semibold shadow-sm" data-role="content-type-pill">{{- $t -}}</span>
|
||||
{{- end -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
<div class="flex items-baseline gap-2 text-gray-800 min-w-0 flex-1 overflow-hidden flex-nowrap whitespace-nowrap" data-role="content-header-title">
|
||||
{{- if $content.PreferredTitle -}}
|
||||
<span class="text-lg font-normal truncate min-w-0 overflow-hidden" data-role="content-header-title-text">{{- $content.PreferredTitle -}}</span>
|
||||
{{- else if $content.TitleStmt -}}
|
||||
<span class="text-lg font-normal italic truncate min-w-0 overflow-hidden" data-role="content-header-title-text">{{- $content.TitleStmt -}}</span>
|
||||
{{- end -}}
|
||||
<a
|
||||
href="/almanach/{{ $entry.MusenalmID }}/contents/{{ $content.MusenalmID }}/edit"
|
||||
class="no-underline hover:text-slate-900 cursor-pointer">
|
||||
{{- if $content.PreferredTitle -}}
|
||||
<span class="content-search-text text-lg font-normal truncate min-w-0 overflow-hidden" data-role="content-header-title-text">{{- $content.PreferredTitle -}}</span>
|
||||
{{- else if $content.TitleStmt -}}
|
||||
<span class="content-search-text text-lg font-normal italic truncate min-w-0 overflow-hidden" data-role="content-header-title-text">{{- $content.TitleStmt -}}</span>
|
||||
{{- end -}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-nowrap whitespace-nowrap shrink-0">
|
||||
@@ -79,7 +83,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content-match-display" class="hidden px-2 py-1 text-sm text-gray-600 bg-amber-50 border-l-2 border-amber-400"></div>
|
||||
<div data-role="content-match-display" class="content-search-text hidden px-2 py-1 text-sm text-gray-600 bg-amber-50 border-l-2 border-amber-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -57,6 +57,8 @@ export class ContentImages extends HTMLElement {
|
||||
}
|
||||
this.dataset.init = "true";
|
||||
|
||||
this._wireUpload();
|
||||
|
||||
const raw = this.getAttribute("data-images") || "[]";
|
||||
const rawFiles = this.getAttribute("data-files") || "[]";
|
||||
let images = [];
|
||||
@@ -82,8 +84,62 @@ export class ContentImages extends HTMLElement {
|
||||
this._render(normalized);
|
||||
}
|
||||
|
||||
_wireUpload() {
|
||||
const panel = this.closest("[data-role='content-images-panel']");
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
const uploadInput = panel.querySelector("[data-role='content-images-upload-input']");
|
||||
if (!uploadInput || uploadInput.dataset.bound === "true") {
|
||||
return;
|
||||
}
|
||||
uploadInput.dataset.bound = "true";
|
||||
uploadInput.addEventListener("change", () => {
|
||||
this._uploadFiles(uploadInput, panel);
|
||||
});
|
||||
}
|
||||
|
||||
_uploadFiles(input, panel) {
|
||||
const endpoint = input.getAttribute("data-upload-endpoint") || "";
|
||||
const contentId = input.getAttribute("data-content-id") || "";
|
||||
const csrfToken = input.getAttribute("data-csrf-token") || "";
|
||||
const files = Array.from(input.files || []);
|
||||
if (!endpoint || !contentId || !csrfToken || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
const payload = new FormData();
|
||||
payload.append("csrf_token", csrfToken);
|
||||
payload.append("content_id", contentId);
|
||||
files.forEach((file) => payload.append("scans", file));
|
||||
fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"HX-Request": "true",
|
||||
},
|
||||
body: payload,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then((html) => {
|
||||
if (!html || !panel) {
|
||||
return;
|
||||
}
|
||||
this._applyServerResponse(html, panel);
|
||||
})
|
||||
.catch(() => null)
|
||||
.finally(() => {
|
||||
input.value = "";
|
||||
});
|
||||
}
|
||||
|
||||
_render(images) {
|
||||
this.classList.add("inline-flex");
|
||||
this.classList.add("block");
|
||||
this.style.display = "block";
|
||||
this.style.width = "100%";
|
||||
const list = this._ensureList();
|
||||
list.innerHTML = "";
|
||||
|
||||
@@ -161,6 +217,11 @@ export class ContentImages extends HTMLElement {
|
||||
list.appendChild(wrapper);
|
||||
});
|
||||
|
||||
const uploadTile = this._findUploadTile();
|
||||
if (uploadTile) {
|
||||
list.appendChild(uploadTile);
|
||||
}
|
||||
|
||||
const dialog = this._ensureDialog();
|
||||
const fullImage = dialog.querySelector(`[data-role='${CONTENT_IMAGES_FULL_ROLE}']`);
|
||||
|
||||
@@ -185,12 +246,26 @@ export class ContentImages extends HTMLElement {
|
||||
if (!list) {
|
||||
list = document.createElement("div");
|
||||
list.dataset.role = CONTENT_IMAGES_LIST_ROLE;
|
||||
list.className = "inline-flex flex-wrap gap-2";
|
||||
this.appendChild(list);
|
||||
}
|
||||
list.className = "grid gap-2";
|
||||
list.style.gridTemplateColumns = "repeat(auto-fill, minmax(7rem, 1fr))";
|
||||
list.style.width = "100%";
|
||||
return list;
|
||||
}
|
||||
|
||||
_findUploadTile() {
|
||||
const panel = this.closest("[data-role='content-images-panel']");
|
||||
if (!panel) {
|
||||
return null;
|
||||
}
|
||||
const upload = panel.querySelector("[data-role='content-images-upload']");
|
||||
if (!upload) {
|
||||
return null;
|
||||
}
|
||||
return upload;
|
||||
}
|
||||
|
||||
_ensureDialog() {
|
||||
let dialog = this.querySelector(`[data-role='${CONTENT_IMAGES_DIALOG_ROLE}']`);
|
||||
if (dialog) {
|
||||
@@ -377,11 +452,35 @@ export class ContentImages extends HTMLElement {
|
||||
if (!html || !panel) {
|
||||
return;
|
||||
}
|
||||
panel.outerHTML = html;
|
||||
this._applyServerResponse(html, panel);
|
||||
})
|
||||
.catch(() => null)
|
||||
.finally(() => {
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
|
||||
_applyServerResponse(html, panel) {
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = html.trim();
|
||||
const oobNodes = Array.from(template.content.querySelectorAll("[hx-swap-oob]"));
|
||||
oobNodes.forEach((node) => {
|
||||
const swapRaw = node.getAttribute("hx-swap-oob") || "";
|
||||
const [swapTypeRaw, selector] = swapRaw.split(":");
|
||||
const swapType = swapTypeRaw || "outerHTML";
|
||||
const target = selector ? document.querySelector(selector) : (node.id ? document.getElementById(node.id) : null);
|
||||
if (target) {
|
||||
if (swapType === "innerHTML") {
|
||||
target.innerHTML = node.innerHTML;
|
||||
} else {
|
||||
target.outerHTML = node.outerHTML;
|
||||
}
|
||||
}
|
||||
node.remove();
|
||||
});
|
||||
const replacement = template.content.firstElementChild;
|
||||
if (replacement) {
|
||||
panel.replaceWith(replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,22 @@
|
||||
@apply bg-stone-50;
|
||||
}
|
||||
|
||||
body[data-dragging="true"] tool-tip {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body[data-dragging="true"] .tooltip-box {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html.dragging tool-tip {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html.dragging .tooltip-box {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply normal-nums;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,58 @@ export class ToolTip extends HTMLElement {
|
||||
return ["position", "timeout"];
|
||||
}
|
||||
|
||||
static _dragGuardInitialized = false;
|
||||
|
||||
static _setDragging(active) {
|
||||
window.__toolTipDragging = active;
|
||||
if (document.documentElement) {
|
||||
document.documentElement.classList.toggle("dragging", active);
|
||||
}
|
||||
if (document.body) {
|
||||
if (active) {
|
||||
document.body.dataset.dragging = "true";
|
||||
} else {
|
||||
delete document.body.dataset.dragging;
|
||||
}
|
||||
}
|
||||
if (active) {
|
||||
document.querySelectorAll(".tooltip-box").forEach((box) => {
|
||||
box.classList.remove("opacity-100");
|
||||
box.classList.add("opacity-0");
|
||||
box.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static _ensureDragGuard() {
|
||||
if (ToolTip._dragGuardInitialized) {
|
||||
return;
|
||||
}
|
||||
ToolTip._dragGuardInitialized = true;
|
||||
const start = (event) => {
|
||||
const handle = event.target?.closest?.("[data-role='content-drag-handle']");
|
||||
if (handle || event.type === "dragstart") {
|
||||
ToolTip._setDragging(true);
|
||||
}
|
||||
};
|
||||
const end = () => {
|
||||
ToolTip._setDragging(false);
|
||||
};
|
||||
document.addEventListener("pointerdown", start, true);
|
||||
document.addEventListener("mousedown", start, true);
|
||||
document.addEventListener("dragstart", start, true);
|
||||
document.addEventListener("pointerup", end, true);
|
||||
document.addEventListener("mouseup", end, true);
|
||||
document.addEventListener("pointercancel", end, true);
|
||||
document.addEventListener("dragend", end, true);
|
||||
document.addEventListener("drop", end, true);
|
||||
window.addEventListener("blur", end);
|
||||
window.addEventListener("contentsdragging", (event) => {
|
||||
const active = Boolean(event.detail?.active);
|
||||
ToolTip._setDragging(active);
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._tooltipBox = null;
|
||||
@@ -14,6 +66,7 @@ export class ToolTip extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
ToolTip._ensureDragGuard();
|
||||
this.classList.add("relative", "block", "leading-none", "[&>*]:leading-normal");
|
||||
this._dataTipElem = this.querySelector(".data-tip");
|
||||
const tipContent = this._dataTipElem ? this._dataTipElem.innerHTML : "Tooltip";
|
||||
@@ -25,6 +78,7 @@ export class ToolTip extends HTMLElement {
|
||||
this._tooltipBox = document.createElement("div");
|
||||
this._tooltipBox.innerHTML = tipContent;
|
||||
this._tooltipBox.className = [
|
||||
"tooltip-box",
|
||||
"opacity-0",
|
||||
"hidden",
|
||||
"absolute",
|
||||
@@ -78,7 +132,32 @@ export class ToolTip extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
_forceHide() {
|
||||
clearTimeout(this._hideTimeout);
|
||||
clearTimeout(this._hiddenTimeout);
|
||||
if (!this._tooltipBox) {
|
||||
return;
|
||||
}
|
||||
this._tooltipBox.classList.remove("opacity-100");
|
||||
this._tooltipBox.classList.add("opacity-0");
|
||||
this._tooltipBox.classList.add("hidden");
|
||||
}
|
||||
|
||||
_isDragging() {
|
||||
if (window.__toolTipDragging) {
|
||||
return true;
|
||||
}
|
||||
if (document.body?.dataset?.dragging === "true") {
|
||||
return true;
|
||||
}
|
||||
return Boolean(document.querySelector("[data-dragging='true']"));
|
||||
}
|
||||
|
||||
_showTooltip() {
|
||||
if (this._isDragging()) {
|
||||
this._forceHide();
|
||||
return;
|
||||
}
|
||||
clearTimeout(this._hideTimeout);
|
||||
clearTimeout(this._hiddenTimeout);
|
||||
this._tooltipBox.classList.remove("hidden");
|
||||
|
||||
Reference in New Issue
Block a user