2023-10-11 02:38:59 +08:00
|
|
|
import { inject as service } from "@ember/service";
|
|
|
|
import { htmlSafe } from "@ember/template";
|
2021-10-06 11:11:52 +08:00
|
|
|
import { downloadCalendar } from "discourse/lib/download-calendar";
|
2023-10-11 02:38:59 +08:00
|
|
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
2022-06-21 08:07:21 +08:00
|
|
|
import {
|
|
|
|
addTagDecorateCallback,
|
|
|
|
addTextDecorateCallback,
|
|
|
|
} from "discourse/lib/to-markdown";
|
2023-10-11 02:38:59 +08:00
|
|
|
import { renderIcon } from "discourse-common/lib/icon-library";
|
|
|
|
import { bind } from "discourse-common/utils/decorators";
|
|
|
|
import I18n from "I18n";
|
2022-06-21 08:07:21 +08:00
|
|
|
import generateDateMarkup from "discourse/plugins/discourse-local-dates/lib/local-date-markup-generator";
|
2023-10-11 02:38:59 +08:00
|
|
|
import LocalDatesCreateModal from "../discourse/components/modal/local-dates-create";
|
|
|
|
import LocalDateBuilder from "../lib/local-date-builder";
|
2021-08-04 22:28:07 +08:00
|
|
|
|
2022-03-31 08:58:38 +08:00
|
|
|
// Import applyLocalDates from discourse/lib/local-dates instead
|
2021-08-04 22:28:07 +08:00
|
|
|
export function applyLocalDates(dates, siteSettings) {
|
|
|
|
if (!siteSettings.discourse_local_dates_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const currentUserTZ = moment.tz.guess();
|
|
|
|
|
2022-10-07 09:39:41 +08:00
|
|
|
dates.forEach((element, index, arr) => {
|
2021-08-04 22:28:07 +08:00
|
|
|
const opts = buildOptionsFromElement(element, siteSettings);
|
|
|
|
|
2022-10-07 09:39:41 +08:00
|
|
|
if (
|
|
|
|
element.attributes["data-range"]?.value === "to" &&
|
|
|
|
index !== 0 &&
|
|
|
|
arr[index - 1].attributes["data-range"]?.value === "from"
|
|
|
|
) {
|
|
|
|
const fromElement = arr[index - 1];
|
|
|
|
if (_rangeIsSameLocalDay(fromElement, element)) {
|
|
|
|
opts.sameLocalDayAsFrom = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-04 22:28:07 +08:00
|
|
|
const localDateBuilder = new LocalDateBuilder(opts, currentUserTZ).build();
|
2022-10-07 09:39:41 +08:00
|
|
|
|
2021-08-04 22:28:07 +08:00
|
|
|
element.innerText = "";
|
|
|
|
element.insertAdjacentHTML(
|
|
|
|
"beforeend",
|
|
|
|
`
|
|
|
|
<svg class="fa d-icon d-icon-globe-americas svg-icon" xmlns="http://www.w3.org/2000/svg">
|
2021-11-25 12:22:43 +08:00
|
|
|
<use href="#globe-americas"></use>
|
2021-08-04 22:28:07 +08:00
|
|
|
</svg>
|
2022-06-21 02:02:05 +08:00
|
|
|
<span class="relative-time">${localDateBuilder.formatted}</span>
|
2021-08-04 22:28:07 +08:00
|
|
|
`
|
|
|
|
);
|
|
|
|
element.setAttribute("aria-label", localDateBuilder.textPreview);
|
|
|
|
|
|
|
|
const classes = ["cooked-date"];
|
|
|
|
if (localDateBuilder.pastEvent) {
|
|
|
|
classes.push("past");
|
|
|
|
}
|
|
|
|
element.classList.add(...classes);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-10-07 09:39:41 +08:00
|
|
|
function _rangeIsSameLocalDay(fromElement, toElement) {
|
2022-10-07 21:07:27 +08:00
|
|
|
if (
|
|
|
|
!fromElement.attributes["data-time"] ||
|
|
|
|
!toElement.attributes["data-time"]
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-10-07 09:39:41 +08:00
|
|
|
const timezone = fromElement.attributes["data-timezone"].value;
|
|
|
|
const from = moment(_getDateFromElement(fromElement)).tz(timezone);
|
|
|
|
const to = moment(_getDateFromElement(toElement)).tz(timezone);
|
|
|
|
return from.isSame(to, "day");
|
|
|
|
}
|
|
|
|
|
|
|
|
function _getDateFromElement(element) {
|
|
|
|
return `${element.attributes["data-date"].value}T${element.attributes["data-time"].value}`;
|
|
|
|
}
|
|
|
|
|
2021-08-04 22:28:07 +08:00
|
|
|
function buildOptionsFromElement(element, siteSettings) {
|
|
|
|
const opts = {};
|
|
|
|
const dataset = element.dataset;
|
2021-09-20 07:23:18 +08:00
|
|
|
|
|
|
|
if (_rangeElements(element).length === 2) {
|
|
|
|
opts.duration = _calculateDuration(element);
|
|
|
|
}
|
|
|
|
|
2021-08-04 22:28:07 +08:00
|
|
|
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;
|
|
|
|
}
|
2020-04-08 14:53:21 +08:00
|
|
|
|
2022-06-21 08:07:21 +08:00
|
|
|
function buildOptionsFromMarkdownTag(element) {
|
|
|
|
const opts = {};
|
|
|
|
|
|
|
|
// siteSettings defaults as used by buildOptionsFromElement are purposefully
|
|
|
|
// ommitted to reproduce exactly what was on the original element
|
|
|
|
opts.time = element.attributes["data-time"];
|
|
|
|
opts.date = element.attributes["data-date"];
|
|
|
|
opts.recurring = element.attributes["data-recurring"];
|
|
|
|
opts.timezones = element.attributes["data-timezones"];
|
|
|
|
opts.timezone = element.attributes["data-timezone"];
|
|
|
|
opts.calendar = (element.attributes["data-calendar"] || "on") === "on";
|
|
|
|
opts.displayedTimezone = element.attributes["data-displayed-timezone"];
|
|
|
|
opts.format = element.attributes["data-format"];
|
|
|
|
opts.countdown = element.attributes["data-countdown"];
|
2022-10-07 09:39:41 +08:00
|
|
|
opts.range = element.attributes["data-range"];
|
2022-06-21 08:07:21 +08:00
|
|
|
|
|
|
|
return opts;
|
|
|
|
}
|
|
|
|
|
2021-09-20 07:23:18 +08:00
|
|
|
function _rangeElements(element) {
|
2021-09-20 14:03:02 +08:00
|
|
|
if (!element.parentElement) {
|
|
|
|
return [];
|
|
|
|
}
|
2022-01-10 15:02:36 +08:00
|
|
|
|
2022-12-16 14:48:39 +08:00
|
|
|
if (element.dataset.range) {
|
|
|
|
return _partitionedRanges(element).find((pair) => pair.includes(element));
|
2022-01-10 15:02:36 +08:00
|
|
|
}
|
|
|
|
|
2022-12-16 14:48:39 +08:00
|
|
|
return [element];
|
|
|
|
}
|
|
|
|
|
|
|
|
function _partitionedRanges(element) {
|
|
|
|
const partitions = [];
|
|
|
|
const ranges = Array.from(element.parentElement.children).filter(
|
|
|
|
(span) => span.dataset.range
|
2021-09-20 07:23:18 +08:00
|
|
|
);
|
2022-12-16 14:48:39 +08:00
|
|
|
|
|
|
|
while (ranges.length > 0) {
|
|
|
|
partitions.push(ranges.splice(0, 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
return partitions;
|
2021-09-20 07:23:18 +08:00
|
|
|
}
|
|
|
|
|
2018-05-04 14:51:41 +08:00
|
|
|
function initializeDiscourseLocalDates(api) {
|
2022-08-01 16:43:33 +08:00
|
|
|
const siteSettings = api.container.lookup("service:site-settings");
|
2021-10-09 03:22:52 +08:00
|
|
|
const defaultTitle = I18n.t("discourse_local_dates.default_title", {
|
|
|
|
site_name: siteSettings.title,
|
|
|
|
});
|
2021-09-23 02:46:06 +08:00
|
|
|
|
2023-09-12 23:32:04 +08:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
});
|
2018-05-04 14:51:41 +08:00
|
|
|
|
2019-04-08 21:52:09 +08:00
|
|
|
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),
|
|
|
|
});
|
2018-05-04 14:51:41 +08:00
|
|
|
});
|
|
|
|
|
2019-04-08 21:52:09 +08:00
|
|
|
api.modifyClass("component:d-editor", {
|
2023-09-13 22:00:38 +08:00
|
|
|
modal: service(),
|
2021-09-02 02:29:07 +08:00
|
|
|
pluginId: "discourse-local-dates",
|
2018-05-04 14:51:41 +08:00
|
|
|
actions: {
|
2019-04-08 21:52:09 +08:00
|
|
|
insertDiscourseLocalDate(toolbarEvent) {
|
2023-09-13 22:00:38 +08:00
|
|
|
this.modal.show(LocalDatesCreateModal, {
|
|
|
|
model: {
|
|
|
|
insertDate: (markup) => {
|
|
|
|
toolbarEvent.addText(markup);
|
|
|
|
},
|
2021-09-23 02:46:06 +08:00
|
|
|
},
|
2018-05-04 14:51:41 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
2022-06-21 08:07:21 +08:00
|
|
|
|
|
|
|
addTextDecorateCallback(function (
|
|
|
|
text,
|
|
|
|
nextElement,
|
|
|
|
_previousElement,
|
|
|
|
metadata
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
metadata.discourseLocalDateStartRangeOpts &&
|
|
|
|
nextElement?.attributes.class?.includes("discourse-local-date") &&
|
|
|
|
text === "→"
|
|
|
|
) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
addTagDecorateCallback(function () {
|
|
|
|
if (this.element.attributes.class?.includes("discourse-local-date")) {
|
|
|
|
if (this.metadata.discourseLocalDateStartRangeOpts) {
|
|
|
|
const startRangeOpts = this.metadata.discourseLocalDateStartRangeOpts;
|
|
|
|
const endRangeOpts = buildOptionsFromMarkdownTag(this.element);
|
|
|
|
const markup = generateDateMarkup(
|
|
|
|
{
|
|
|
|
date: startRangeOpts.date,
|
|
|
|
time: startRangeOpts.time,
|
|
|
|
format: startRangeOpts.format,
|
|
|
|
},
|
|
|
|
endRangeOpts,
|
|
|
|
true,
|
|
|
|
{
|
|
|
|
date: endRangeOpts.date,
|
|
|
|
time: endRangeOpts.time,
|
|
|
|
format: endRangeOpts.format,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
this.prefix = markup;
|
|
|
|
this.metadata.discourseLocalDateStartRangeOpts = null;
|
|
|
|
return "";
|
|
|
|
}
|
2022-10-07 09:39:41 +08:00
|
|
|
if (
|
|
|
|
this.element.attributes["data-range"] === "true" ||
|
|
|
|
this.element.attributes["data-range"] === "from" ||
|
|
|
|
this.element.attributes["data-range"] === "to"
|
|
|
|
) {
|
2022-07-06 16:37:54 +08:00
|
|
|
this.metadata.discourseLocalDateStartRangeOpts =
|
|
|
|
buildOptionsFromMarkdownTag(this.element);
|
2022-06-21 08:07:21 +08:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
const opts = buildOptionsFromMarkdownTag(this.element, siteSettings);
|
|
|
|
const markup = generateDateMarkup(
|
|
|
|
{ date: opts.date, time: opts.time, format: opts.format },
|
|
|
|
opts,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
this.prefix = markup;
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
});
|
2018-05-04 14:51:41 +08:00
|
|
|
}
|
|
|
|
|
2021-08-04 22:28:07 +08:00
|
|
|
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");
|
2022-06-21 02:02:05 +08:00
|
|
|
dateTimeNode.innerHTML = preview.formatted;
|
2021-08-04 22:28:07 +08:00
|
|
|
previewNode.appendChild(dateTimeNode);
|
|
|
|
|
|
|
|
return previewNode;
|
|
|
|
});
|
|
|
|
|
|
|
|
const previewsNode = document.createElement("div");
|
|
|
|
previewsNode.classList.add("locale-dates-previews");
|
|
|
|
htmlPreviews.forEach((htmlPreview) => previewsNode.appendChild(htmlPreview));
|
|
|
|
|
2021-10-14 06:22:44 +08:00
|
|
|
const calendarNode = _downloadCalendarNode(element);
|
|
|
|
if (calendarNode) {
|
|
|
|
previewsNode.appendChild(calendarNode);
|
|
|
|
}
|
2021-10-06 11:11:52 +08:00
|
|
|
|
2021-08-04 22:28:07 +08:00
|
|
|
return previewsNode.outerHTML;
|
|
|
|
}
|
|
|
|
|
2021-10-14 06:22:44 +08:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2021-10-06 11:11:52 +08:00
|
|
|
function _downloadCalendarNode(element) {
|
2021-10-14 06:22:44 +08:00
|
|
|
const [startDataset, endDataset] = _rangeElements(element).map(
|
|
|
|
(dateElement) => dateElement.dataset
|
|
|
|
);
|
|
|
|
const [startDate, endDate] = calculateStartAndEndDate(
|
|
|
|
startDataset,
|
|
|
|
endDataset
|
|
|
|
);
|
|
|
|
|
|
|
|
if (startDate < moment().tz(startDataset.timezone)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-10-06 11:11:52 +08:00
|
|
|
const node = document.createElement("div");
|
|
|
|
node.classList.add("download-calendar");
|
|
|
|
node.innerHTML = `${renderIcon("string", "file")} ${I18n.t(
|
|
|
|
"download_calendar.add_to_calendar"
|
|
|
|
)}`;
|
2021-10-14 06:22:44 +08:00
|
|
|
node.setAttribute("data-starts-at", startDate.toISOString());
|
2021-10-06 11:11:52 +08:00
|
|
|
if (endDataset) {
|
2021-10-14 06:22:44 +08:00
|
|
|
node.setAttribute("data-ends-at", endDate.toISOString());
|
2021-10-06 11:11:52 +08:00
|
|
|
}
|
|
|
|
if (!startDataset.time && !endDataset) {
|
2021-10-14 06:22:44 +08:00
|
|
|
node.setAttribute("data-ends-at", startDate.add(24, "hours").toISOString());
|
2021-10-06 11:11:52 +08:00
|
|
|
}
|
2021-10-09 03:22:52 +08:00
|
|
|
node.setAttribute("data-title", startDataset.title);
|
2021-10-06 11:11:52 +08:00
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2021-09-20 07:23:18 +08:00
|
|
|
function _calculateDuration(element) {
|
|
|
|
const [startDataset, endDataset] = _rangeElements(element).map(
|
|
|
|
(dateElement) => dateElement.dataset
|
|
|
|
);
|
|
|
|
const startDateTime = moment(
|
2021-10-01 04:07:48 +08:00
|
|
|
`${startDataset.date} ${startDataset.time || ""}`.trim()
|
|
|
|
);
|
|
|
|
const endDateTime = moment(
|
|
|
|
`${endDataset.date} ${endDataset.time || ""}`.trim()
|
2021-09-20 07:23:18 +08:00
|
|
|
);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-05-04 14:51:41 +08:00
|
|
|
export default {
|
|
|
|
name: "discourse-local-dates",
|
|
|
|
|
DEV: FloatKit (#23650)
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@Label={{i18n "foo.bar"}}
@ICON="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-26 19:39:52 +08:00
|
|
|
@bind
|
2021-08-04 22:28:07 +08:00
|
|
|
showDatePopover(event) {
|
DEV: FloatKit (#23650)
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@Label={{i18n "foo.bar"}}
@ICON="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-26 19:39:52 +08:00
|
|
|
const tooltip = this.container.lookup("service:tooltip");
|
2021-08-04 22:28:07 +08:00
|
|
|
|
2022-05-02 23:10:26 +08:00
|
|
|
if (event?.target?.classList?.contains("download-calendar")) {
|
2021-10-06 11:11:52 +08:00
|
|
|
const dataset = event.target.dataset;
|
2021-10-14 06:22:44 +08:00
|
|
|
downloadCalendar(dataset.title, [
|
2021-10-06 11:11:52 +08:00
|
|
|
{
|
|
|
|
startsAt: dataset.startsAt,
|
|
|
|
endsAt: dataset.endsAt,
|
|
|
|
},
|
|
|
|
]);
|
2022-05-03 02:35:15 +08:00
|
|
|
|
DEV: FloatKit (#23650)
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@Label={{i18n "foo.bar"}}
@ICON="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-26 19:39:52 +08:00
|
|
|
return tooltip.close();
|
2022-05-02 23:10:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!event?.target?.classList?.contains("discourse-local-date")) {
|
|
|
|
return;
|
2021-08-04 22:28:07 +08:00
|
|
|
}
|
2022-05-02 23:10:26 +08:00
|
|
|
|
DEV: FloatKit (#23650)
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@Label={{i18n "foo.bar"}}
@ICON="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-26 19:39:52 +08:00
|
|
|
const siteSettings = this.container.lookup("service:site-settings");
|
|
|
|
return tooltip.show(event.target, {
|
|
|
|
content: htmlSafe(buildHtmlPreview(event.target, siteSettings)),
|
2022-05-02 23:10:26 +08:00
|
|
|
});
|
2021-08-04 22:28:07 +08:00
|
|
|
},
|
|
|
|
|
2018-05-04 14:51:41 +08:00
|
|
|
initialize(container) {
|
DEV: FloatKit (#23650)
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@Label={{i18n "foo.bar"}}
@ICON="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-26 19:39:52 +08:00
|
|
|
this.container = container;
|
|
|
|
window.addEventListener("click", this.showDatePopover, { passive: true });
|
2021-08-04 22:28:07 +08:00
|
|
|
|
2022-08-01 16:43:33 +08:00
|
|
|
const siteSettings = container.lookup("service:site-settings");
|
2018-05-04 14:51:41 +08:00
|
|
|
if (siteSettings.discourse_local_dates_enabled) {
|
|
|
|
withPluginApi("0.8.8", initializeDiscourseLocalDates);
|
|
|
|
}
|
|
|
|
},
|
2021-08-04 22:28:07 +08:00
|
|
|
|
|
|
|
teardown() {
|
|
|
|
window.removeEventListener("click", this.showDatePopover);
|
|
|
|
},
|
2018-05-04 14:51:41 +08:00
|
|
|
};
|