FEATURE: date-range tag for local dates (#15474)
New range tag for local dates with syntax like: ``` [date-range from=2022-01-06T13:00 to=2022-01-08 timezone=Australia/Sydney] ``` Previously, 2 dates in one line were considered as range. It was hard to decide if 2 dates are range when they were in separate lines or have some content between them. New explicit tag should clearly distinguish between single date and range. Common code from `addLocalDate` is extracted to `addSingleLocalDate`. Both `addLocalDate` and new `addLocalRange` are using `addSingleLocalDate`. Also, `defaultDateConfig` was extracted to have one place for all possible parameters.
This commit is contained in:
parent
fff8b98485
commit
17ec3bc5b9
|
@ -258,15 +258,27 @@ export default Component.extend({
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
_generateDateMarkup(config, options, isRange) {
|
_generateDateMarkup(fromDateTime, options, isRange, toDateTime) {
|
||||||
let text = `[date=${config.date}`;
|
let text = ``;
|
||||||
|
|
||||||
if (config.time) {
|
if (isRange) {
|
||||||
text += ` time=${config.time}`;
|
let from = [fromDateTime.date, fromDateTime.time]
|
||||||
|
.filter((element) => !isEmpty(element))
|
||||||
|
.join("T");
|
||||||
|
let to = [toDateTime.date, toDateTime.time]
|
||||||
|
.filter((element) => !isEmpty(element))
|
||||||
|
.join("T");
|
||||||
|
text += `[date-range from=${from} to=${to}`;
|
||||||
|
} else {
|
||||||
|
text += `[date=${fromDateTime.date}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.format && config.format.length) {
|
if (fromDateTime.time && !isRange) {
|
||||||
text += ` format="${config.format}"`;
|
text += ` time=${fromDateTime.time}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromDateTime.format && fromDateTime.format.length) {
|
||||||
|
text += ` format="${fromDateTime.format}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.timezone) {
|
if (options.timezone) {
|
||||||
|
@ -298,11 +310,15 @@ export default Component.extend({
|
||||||
let text;
|
let text;
|
||||||
|
|
||||||
if (isValid && config.from) {
|
if (isValid && config.from) {
|
||||||
text = this._generateDateMarkup(config.from, options, isRange);
|
|
||||||
|
|
||||||
if (config.to && config.to.range) {
|
if (config.to && config.to.range) {
|
||||||
text += ` → `;
|
text = this._generateDateMarkup(
|
||||||
text += this._generateDateMarkup(config.to, options, isRange);
|
config.from,
|
||||||
|
options,
|
||||||
|
isRange,
|
||||||
|
config.to
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
text = this._generateDateMarkup(config.from, options, isRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,16 @@ function _rangeElements(element) {
|
||||||
if (!element.parentElement) {
|
if (!element.parentElement) {
|
||||||
return [];
|
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(
|
return Array.from(element.parentElement.children).filter(
|
||||||
(span) => span.dataset.date
|
(span) => span.dataset.date
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,38 +2,8 @@ import { parseBBCodeTag } from "pretty-text/engines/discourse-markdown/bbcode-bl
|
||||||
|
|
||||||
const timezoneNames = moment.tz.names();
|
const timezoneNames = moment.tz.names();
|
||||||
|
|
||||||
function addLocalDate(buffer, matches, state) {
|
function addSingleLocalDate(buffer, state, config) {
|
||||||
let token;
|
let token = new state.Token("span_open", "span", 1);
|
||||||
|
|
||||||
let config = {
|
|
||||||
date: null,
|
|
||||||
time: null,
|
|
||||||
timezone: null,
|
|
||||||
format: null,
|
|
||||||
timezones: null,
|
|
||||||
displayedTimezone: null,
|
|
||||||
countdown: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const matchString = matches[1].replace(/‘|’|„|“|«|»|”/g, '"');
|
|
||||||
|
|
||||||
let parsed = parseBBCodeTag(
|
|
||||||
"[date date" + matchString + "]",
|
|
||||||
0,
|
|
||||||
matchString.length + 11
|
|
||||||
);
|
|
||||||
|
|
||||||
config.date = parsed.attrs.date;
|
|
||||||
config.format = parsed.attrs.format;
|
|
||||||
config.calendar = parsed.attrs.calendar;
|
|
||||||
config.time = parsed.attrs.time;
|
|
||||||
config.timezone = (parsed.attrs.timezone || "").trim();
|
|
||||||
config.recurring = parsed.attrs.recurring;
|
|
||||||
config.timezones = parsed.attrs.timezones;
|
|
||||||
config.displayedTimezone = parsed.attrs.displayedTimezone;
|
|
||||||
config.countdown = parsed.attrs.countdown;
|
|
||||||
|
|
||||||
token = new state.Token("span_open", "span", 1);
|
|
||||||
token.attrs = [["data-date", state.md.utils.escapeHtml(config.date)]];
|
token.attrs = [["data-date", state.md.utils.escapeHtml(config.date)]];
|
||||||
|
|
||||||
if (!config.date.match(/\d{4}-\d{2}-\d{2}/)) {
|
if (!config.date.match(/\d{4}-\d{2}-\d{2}/)) {
|
||||||
|
@ -76,6 +46,9 @@ function addLocalDate(buffer, matches, state) {
|
||||||
state.md.utils.escapeHtml(config.calendar),
|
state.md.utils.escapeHtml(config.calendar),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
if (config.range) {
|
||||||
|
token.attrs.push(["data-range", true]);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
config.displayedTimezone &&
|
config.displayedTimezone &&
|
||||||
|
@ -127,6 +100,77 @@ function addLocalDate(buffer, matches, state) {
|
||||||
closeBuffer(buffer, state, dateTime.utc().format(config.format));
|
closeBuffer(buffer, state, dateTime.utc().format(config.format));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultDateConfig() {
|
||||||
|
return {
|
||||||
|
date: null,
|
||||||
|
time: null,
|
||||||
|
timezone: null,
|
||||||
|
format: null,
|
||||||
|
timezones: null,
|
||||||
|
displayedTimezone: null,
|
||||||
|
countdown: null,
|
||||||
|
range: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTagAttributes(tag) {
|
||||||
|
const matchString = tag.replace(/‘|’|„|“|«|»|”/g, '"');
|
||||||
|
|
||||||
|
return parseBBCodeTag(
|
||||||
|
"[date date" + matchString + "]",
|
||||||
|
0,
|
||||||
|
matchString.length + 12
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLocalDate(buffer, matches, state) {
|
||||||
|
let config = defaultDateConfig();
|
||||||
|
|
||||||
|
const parsed = parseTagAttributes(matches[1]);
|
||||||
|
|
||||||
|
config.date = parsed.attrs.date;
|
||||||
|
config.format = parsed.attrs.format;
|
||||||
|
config.calendar = parsed.attrs.calendar;
|
||||||
|
config.time = parsed.attrs.time;
|
||||||
|
config.timezone = (parsed.attrs.timezone || "").trim();
|
||||||
|
config.recurring = parsed.attrs.recurring;
|
||||||
|
config.timezones = parsed.attrs.timezones;
|
||||||
|
config.displayedTimezone = parsed.attrs.displayedTimezone;
|
||||||
|
config.countdown = parsed.attrs.countdown;
|
||||||
|
addSingleLocalDate(buffer, state, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLocalRange(buffer, matches, state) {
|
||||||
|
let config = defaultDateConfig();
|
||||||
|
let date, time;
|
||||||
|
const parsed = parseTagAttributes(matches[1]);
|
||||||
|
|
||||||
|
config.format = parsed.attrs.format;
|
||||||
|
config.calendar = parsed.attrs.calendar;
|
||||||
|
config.timezone = (parsed.attrs.timezone || "").trim();
|
||||||
|
config.recurring = parsed.attrs.recurring;
|
||||||
|
config.timezones = parsed.attrs.timezones;
|
||||||
|
config.displayedTimezone = parsed.attrs.displayedTimezone;
|
||||||
|
config.countdown = parsed.attrs.countdown;
|
||||||
|
config.range = parsed.attrs.from && parsed.attrs.to;
|
||||||
|
|
||||||
|
if (parsed.attrs.from) {
|
||||||
|
[date, time] = parsed.attrs.from.split("T");
|
||||||
|
config.date = date;
|
||||||
|
config.time = time;
|
||||||
|
addSingleLocalDate(buffer, state, config);
|
||||||
|
}
|
||||||
|
if (config.range) {
|
||||||
|
closeBuffer(buffer, state, "→");
|
||||||
|
}
|
||||||
|
if (parsed.attrs.to) {
|
||||||
|
[date, time] = parsed.attrs.to.split("T");
|
||||||
|
config.date = date;
|
||||||
|
config.time = time;
|
||||||
|
addSingleLocalDate(buffer, state, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeBuffer(buffer, state, text) {
|
function closeBuffer(buffer, state, text) {
|
||||||
let token;
|
let token;
|
||||||
|
|
||||||
|
@ -171,4 +215,13 @@ export function setup(helper) {
|
||||||
|
|
||||||
md.core.textPostProcess.ruler.push("discourse-local-dates", rule);
|
md.core.textPostProcess.ruler.push("discourse-local-dates", rule);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
helper.registerPlugin((md) => {
|
||||||
|
const rule = {
|
||||||
|
matcher: /\[date-range(.+?)\]/,
|
||||||
|
onMatch: addLocalRange,
|
||||||
|
};
|
||||||
|
|
||||||
|
md.core.textPostProcess.ruler.push("discourse-local-dates", rule);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,4 +86,34 @@ RSpec.describe "Local Dates" do
|
||||||
|
|
||||||
expect(cooked).to include("data-countdown=")
|
expect(cooked).to include("data-countdown=")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'ranges' do
|
||||||
|
it 'generates ranges without time' do
|
||||||
|
raw = "[date-range from=2022-01-06 to=2022-01-08]"
|
||||||
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
|
expect(cooked).to include('data-date="2022-01-06')
|
||||||
|
expect(cooked).to include('data-range="true"')
|
||||||
|
expect(cooked).not_to include('data-time=')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'supports time and timezone' do
|
||||||
|
raw = "[date-range from=2022-01-06T13:00 to=2022-01-08 timezone=Australia/Sydney]"
|
||||||
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
|
expect(cooked).to include('data-date="2022-01-06')
|
||||||
|
expect(cooked).to include('data-range="true"')
|
||||||
|
expect(cooked).to include('data-time="13:00"')
|
||||||
|
expect(cooked).to include('data-timezone="Australia/Sydney"')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates single date when range without end date' do
|
||||||
|
raw = "[date-range from=2022-01-06T13:00]"
|
||||||
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
|
expect(cooked).to include('data-date="2022-01-06')
|
||||||
|
expect(cooked).to include('data-time="13:00"')
|
||||||
|
expect(cooked).not_to include('data-range=')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue