diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js b/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js index f1a95cfaa33..46dee48eee6 100644 --- a/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js +++ b/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js @@ -258,15 +258,27 @@ export default Component.extend({ ]; }, - _generateDateMarkup(config, options, isRange) { - let text = `[date=${config.date}`; + _generateDateMarkup(fromDateTime, options, isRange, toDateTime) { + let text = ``; - if (config.time) { - text += ` time=${config.time}`; + if (isRange) { + 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) { - text += ` format="${config.format}"`; + if (fromDateTime.time && !isRange) { + text += ` time=${fromDateTime.time}`; + } + + if (fromDateTime.format && fromDateTime.format.length) { + text += ` format="${fromDateTime.format}"`; } if (options.timezone) { @@ -298,11 +310,15 @@ export default Component.extend({ let text; if (isValid && config.from) { - text = this._generateDateMarkup(config.from, options, isRange); - if (config.to && config.to.range) { - text += ` → `; - text += this._generateDateMarkup(config.to, options, isRange); + text = this._generateDateMarkup( + config.from, + options, + isRange, + config.to + ); + } else { + text = this._generateDateMarkup(config.from, options, isRange); } } diff --git a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js index 7a7ed872215..19136629a07 100644 --- a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js +++ b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js @@ -69,6 +69,16 @@ 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 ); diff --git a/plugins/discourse-local-dates/assets/javascripts/lib/discourse-markdown/discourse-local-dates.js b/plugins/discourse-local-dates/assets/javascripts/lib/discourse-markdown/discourse-local-dates.js index f0de3dc312e..a9bd9dc35ee 100644 --- a/plugins/discourse-local-dates/assets/javascripts/lib/discourse-markdown/discourse-local-dates.js +++ b/plugins/discourse-local-dates/assets/javascripts/lib/discourse-markdown/discourse-local-dates.js @@ -2,38 +2,8 @@ import { parseBBCodeTag } from "pretty-text/engines/discourse-markdown/bbcode-bl const timezoneNames = moment.tz.names(); -function addLocalDate(buffer, matches, state) { - let token; - - 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); +function addSingleLocalDate(buffer, state, config) { + let token = new state.Token("span_open", "span", 1); token.attrs = [["data-date", state.md.utils.escapeHtml(config.date)]]; 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), ]); } + if (config.range) { + token.attrs.push(["data-range", true]); + } if ( config.displayedTimezone && @@ -127,6 +100,77 @@ function addLocalDate(buffer, matches, state) { 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) { let token; @@ -171,4 +215,13 @@ export function setup(helper) { 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); + }); } diff --git a/plugins/discourse-local-dates/spec/integration/local_dates_spec.rb b/plugins/discourse-local-dates/spec/integration/local_dates_spec.rb index e59b695d235..f1974732758 100644 --- a/plugins/discourse-local-dates/spec/integration/local_dates_spec.rb +++ b/plugins/discourse-local-dates/spec/integration/local_dates_spec.rb @@ -86,4 +86,34 @@ RSpec.describe "Local Dates" do expect(cooked).to include("data-countdown=") 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