import deprecated from "discourse-common/lib/deprecated";
import { getOwner } from "discourse-common/lib/get-owner";
import { hidePopover, showPopover } from "discourse/lib/d-popover";
import LocalDateBuilder from "../lib/local-date-builder";
import { withPluginApi } from "discourse/lib/plugin-api";
import showModal from "discourse/lib/show-modal";
import { downloadCalendar } from "discourse/lib/download-calendar";
import { renderIcon } from "discourse-common/lib/icon-library";
import I18n from "I18n";
// Import applyLocalDates from discourse/lib/local-dates instead
export function applyLocalDates(dates, siteSettings) {
if (!siteSettings.discourse_local_dates_enabled) {
return;
}
const currentUserTZ = moment.tz.guess();
dates.forEach((element) => {
const opts = buildOptionsFromElement(element, siteSettings);
const localDateBuilder = new LocalDateBuilder(opts, currentUserTZ).build();
element.innerText = "";
element.insertAdjacentHTML(
"beforeend",
`
${localDateBuilder.formated}
`
);
element.setAttribute("aria-label", localDateBuilder.textPreview);
const classes = ["cooked-date"];
if (localDateBuilder.pastEvent) {
classes.push("past");
}
element.classList.add(...classes);
});
}
function buildOptionsFromElement(element, siteSettings) {
const opts = {};
const dataset = element.dataset;
if (_rangeElements(element).length === 2) {
opts.duration = _calculateDuration(element);
}
opts.time = dataset.time;
opts.date = dataset.date;
opts.recurring = dataset.recurring;
opts.timezones = (
dataset.timezones ||
siteSettings.discourse_local_dates_default_timezones ||
"Etc/UTC"
)
.split("|")
.filter(Boolean);
opts.timezone = dataset.timezone;
opts.calendar = (dataset.calendar || "on") === "on";
opts.displayedTimezone = dataset.displayedTimezone;
opts.format = dataset.format || (opts.time ? "LLL" : "LL");
opts.countdown = dataset.countdown;
return opts;
}
function _rangeElements(element) {
if (!element.parentElement) {
return [];
}
// TODO: element.parentElement.children.length !== 2 is a fallback to old solution for ranges
// Condition can be removed after migration to [date-range]
if (
element.dataset.range !== "true" &&
element.parentElement.children.length !== 2
) {
return [element];
}
return Array.from(element.parentElement.children).filter(
(span) => span.dataset.date
);
}
function initializeDiscourseLocalDates(api) {
const siteSettings = api.container.lookup("site-settings:main");
const defaultTitle = I18n.t("discourse_local_dates.default_title", {
site_name: siteSettings.title,
});
api.decorateCookedElement(
(elem, helper) => {
const dates = elem.querySelectorAll(".discourse-local-date");
applyLocalDates(dates, siteSettings);
const topicTitle = helper?.getModel()?.topic?.title;
dates.forEach((date) => {
date.dataset.title = date.dataset.title || topicTitle || defaultTitle;
});
},
{ id: "discourse-local-date" }
);
api.onToolbarCreate((toolbar) => {
toolbar.addButton({
title: "discourse_local_dates.title",
id: "local-dates",
group: "extras",
icon: "calendar-alt",
sendAction: (event) =>
toolbar.context.send("insertDiscourseLocalDate", event),
});
});
api.modifyClass("component:d-editor", {
pluginId: "discourse-local-dates",
actions: {
insertDiscourseLocalDate(toolbarEvent) {
showModal("discourse-local-dates-create-modal").setProperties({
insertDate: (markup) => {
toolbarEvent.addText(markup);
},
});
},
},
});
}
function buildHtmlPreview(element, siteSettings) {
const opts = buildOptionsFromElement(element, siteSettings);
const localDateBuilder = new LocalDateBuilder(
opts,
moment.tz.guess()
).build();
const htmlPreviews = localDateBuilder.previews.map((preview) => {
const previewNode = document.createElement("div");
previewNode.classList.add("preview");
if (preview.current) {
previewNode.classList.add("current");
}
const timezoneNode = document.createElement("span");
timezoneNode.classList.add("timezone");
timezoneNode.innerText = preview.timezone;
previewNode.appendChild(timezoneNode);
const dateTimeNode = document.createElement("span");
dateTimeNode.classList.add("date-time");
dateTimeNode.innerHTML = preview.formated;
previewNode.appendChild(dateTimeNode);
return previewNode;
});
const previewsNode = document.createElement("div");
previewsNode.classList.add("locale-dates-previews");
htmlPreviews.forEach((htmlPreview) => previewsNode.appendChild(htmlPreview));
const calendarNode = _downloadCalendarNode(element);
if (calendarNode) {
previewsNode.appendChild(calendarNode);
}
return previewsNode.outerHTML;
}
function calculateStartAndEndDate(startDataset, endDataset) {
let startDate, endDate;
startDate = moment.tz(
`${startDataset.date} ${startDataset.time || ""}`.trim(),
startDataset.timezone
);
if (endDataset) {
endDate = moment.tz(
`${endDataset.date} ${endDataset.time || ""}`.trim(),
endDataset.timezone
);
}
return [startDate, endDate];
}
function _downloadCalendarNode(element) {
const [startDataset, endDataset] = _rangeElements(element).map(
(dateElement) => dateElement.dataset
);
const [startDate, endDate] = calculateStartAndEndDate(
startDataset,
endDataset
);
if (startDate < moment().tz(startDataset.timezone)) {
return false;
}
const node = document.createElement("div");
node.classList.add("download-calendar");
node.innerHTML = `${renderIcon("string", "file")} ${I18n.t(
"download_calendar.add_to_calendar"
)}`;
node.setAttribute("data-starts-at", startDate.toISOString());
if (endDataset) {
node.setAttribute("data-ends-at", endDate.toISOString());
}
if (!startDataset.time && !endDataset) {
node.setAttribute("data-ends-at", startDate.add(24, "hours").toISOString());
}
node.setAttribute("data-title", startDataset.title);
return node;
}
function _calculateDuration(element) {
const [startDataset, endDataset] = _rangeElements(element).map(
(dateElement) => dateElement.dataset
);
const startDateTime = moment(
`${startDataset.date} ${startDataset.time || ""}`.trim()
);
const endDateTime = moment(
`${endDataset.date} ${endDataset.time || ""}`.trim()
);
const duration = endDateTime.diff(startDateTime, "minutes");
// negative duration is used when we calculate difference for end date from range
return element.dataset === startDataset ? duration : -duration;
}
export default {
name: "discourse-local-dates",
showDatePopover(event) {
const owner = getOwner(this);
if (owner.isDestroyed || owner.isDestroying) {
return;
}
const siteSettings = owner.lookup("site-settings:main");
if (event?.target?.classList?.contains("discourse-local-date")) {
if ($(document.getElementById("d-popover"))[0]) {
hidePopover(event);
} else {
showPopover(event, {
htmlContent: buildHtmlPreview(event.target, siteSettings),
});
}
} else if (event?.target?.classList?.contains("download-calendar")) {
const dataset = event.target.dataset;
hidePopover(event);
downloadCalendar(dataset.title, [
{
startsAt: dataset.startsAt,
endsAt: dataset.endsAt,
},
]);
} else {
hidePopover(event);
}
},
hideDatePopover(event) {
if (event?.target?.classList?.contains("discourse-local-date")) {
hidePopover(event);
}
},
initialize(container) {
const router = container.lookup("router:main");
router.on("routeWillChange", hidePopover);
window.addEventListener("click", this.showDatePopover);
const siteSettings = container.lookup("site-settings:main");
if (siteSettings.discourse_local_dates_enabled) {
$.fn.applyLocalDates = function () {
deprecated(
"`$.applyLocalDates()` is deprecated, import and use `applyLocalDates()` instead."
);
return applyLocalDates(this.toArray(), siteSettings);
};
withPluginApi("0.8.8", initializeDiscourseLocalDates);
}
},
teardown() {
window.removeEventListener("click", this.showDatePopover);
},
};