UX: events are now managed through md markup
This commit is contained in:
parent
1213b09932
commit
d12d45d247
|
@ -55,21 +55,7 @@ module DiscoursePostEvent
|
|||
event = Event.find(params[:id])
|
||||
guardian.ensure_can_edit!(event.post)
|
||||
guardian.ensure_can_act_on_event!(event)
|
||||
event.enforce_utc!(event_params)
|
||||
|
||||
case event_params[:status].to_i
|
||||
when Event.statuses[:private]
|
||||
raw_invitees = Array(event_params[:raw_invitees])
|
||||
event.update!(event_params.merge(raw_invitees: raw_invitees))
|
||||
event.enforce_raw_invitees!
|
||||
when Event.statuses[:public]
|
||||
event.update!(event_params.merge(raw_invitees: []))
|
||||
when Event.statuses[:standalone]
|
||||
event.update!(event_params.merge(raw_invitees: []))
|
||||
event.invitees.destroy_all
|
||||
end
|
||||
|
||||
event.publish_update!
|
||||
event.update_with_params!(event_params)
|
||||
serializer = EventSerializer.new(event, scope: guardian)
|
||||
render_json_dump(serializer)
|
||||
end
|
||||
|
|
|
@ -42,8 +42,10 @@ module DiscoursePostEvent
|
|||
|
||||
validates :starts_at, presence: true
|
||||
|
||||
MIN_NAME_LENGTH = 5
|
||||
MAX_NAME_LENGTH = 30
|
||||
validates :name,
|
||||
length: { in: 5..30 },
|
||||
length: { in: MIN_NAME_LENGTH..MAX_NAME_LENGTH },
|
||||
unless: -> (event) { event.name.blank? }
|
||||
|
||||
validate :raw_invitees_length
|
||||
|
@ -148,11 +150,11 @@ module DiscoursePostEvent
|
|||
end
|
||||
|
||||
def enforce_utc!(params)
|
||||
if params['starts_at'].present?
|
||||
params['starts_at'] = Time.parse(params['starts_at']).utc
|
||||
if params[:starts_at].present?
|
||||
params[:starts_at] = Time.parse(params[:starts_at]).utc
|
||||
end
|
||||
if params['ends_at'].present?
|
||||
params['ends_at'] = Time.parse(params['ends_at']).utc
|
||||
if params[:ends_at].present?
|
||||
params[:ends_at] = Time.parse(params[:ends_at]).utc
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -171,5 +173,40 @@ module DiscoursePostEvent
|
|||
def is_expired?
|
||||
Time.now > (self.ends_at || self.starts_at || Time.now)
|
||||
end
|
||||
|
||||
def self.update_from_raw(post)
|
||||
events = DiscoursePostEvent::EventParser.extract_events(post.raw)
|
||||
if events.present?
|
||||
event_params = events.first
|
||||
event = post.event || Event.new(id: post.id)
|
||||
params = {
|
||||
name: event_params[:name] || event.name,
|
||||
starts_at: event_params[:start] || event.starts_at,
|
||||
ends_at: event_params[:end] || event.ends_at,
|
||||
status: event_params[:status].present? ? Event.statuses[event_params[:status].to_sym] : event.status,
|
||||
raw_invitees: event_params[:allowedGroups] ? event_params[:allowedGroups].split(',') : nil
|
||||
}
|
||||
event.enforce_utc!(params)
|
||||
event.update_with_params!(params)
|
||||
elsif post.event
|
||||
post.event.destroy
|
||||
end
|
||||
end
|
||||
|
||||
def update_with_params!(params)
|
||||
case params[:status].to_i
|
||||
when Event.statuses[:private]
|
||||
raw_invitees = Array(params[:raw_invitees])
|
||||
self.update!(params.merge(raw_invitees: raw_invitees))
|
||||
self.enforce_raw_invitees!
|
||||
when Event.statuses[:public]
|
||||
self.update!(params.merge(raw_invitees: []))
|
||||
when Event.statuses[:standalone]
|
||||
self.update!(params.merge(raw_invitees: []))
|
||||
self.invitees.destroy_all
|
||||
end
|
||||
|
||||
self.publish_update!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import TextLib from "discourse/lib/text";
|
||||
import Group from "discourse/models/group";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import Controller from "@ember/controller";
|
||||
|
@ -58,14 +59,32 @@ export default Controller.extend(ModalFunctionality, {
|
|||
@action
|
||||
destroyPostEvent() {
|
||||
bootbox.confirm(
|
||||
I18n.t("event.ui_builder.confirm_delete"),
|
||||
I18n.t("discourse_post_event.builder_modal.confirm_delete"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
confirmed => {
|
||||
if (confirmed) {
|
||||
this.model.eventModel
|
||||
.destroyRecord()
|
||||
.then(() => this.send("closeModal"));
|
||||
return this.store
|
||||
.find("post", this.model.eventModel.id)
|
||||
.then(post => {
|
||||
const raw = post.raw;
|
||||
const newRaw = this._removeRawEvent(raw);
|
||||
|
||||
if (newRaw) {
|
||||
const props = {
|
||||
raw: newRaw,
|
||||
edit_reason: I18n.t("discourse_post_event.destroy_event")
|
||||
};
|
||||
|
||||
return TextLib.cookAsync(newRaw).then(cooked => {
|
||||
props.cooked = cooked.string;
|
||||
return post
|
||||
.save(props)
|
||||
.catch(e => this.flash(extractError(e), "error"))
|
||||
.then(result => result && this.send("closeModal"));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -73,17 +92,93 @@ export default Controller.extend(ModalFunctionality, {
|
|||
|
||||
@action
|
||||
createEvent() {
|
||||
this.model.eventModel
|
||||
.save()
|
||||
.then(() => this.send("closeModal"))
|
||||
.catch(e => this.flash(extractError(e), "error"));
|
||||
if (!this.startsAt) {
|
||||
this.send("closeModal");
|
||||
return;
|
||||
}
|
||||
|
||||
const eventParams = this._buildEventParams();
|
||||
const markdownParams = [];
|
||||
Object.keys(eventParams).forEach(key => {
|
||||
markdownParams.push(`${key}="${eventParams[key]}"`);
|
||||
});
|
||||
|
||||
this.toolbarEvent.addText(
|
||||
`[wrap=event ${markdownParams.join(" ")}]\n[/wrap]`
|
||||
);
|
||||
this.send("closeModal");
|
||||
},
|
||||
|
||||
@action
|
||||
updateEvent() {
|
||||
this.model.eventModel
|
||||
.save()
|
||||
.then(() => this.send("closeModal"))
|
||||
.catch(e => this.flash(extractError(e), "error"));
|
||||
const eventParams = this._buildEventParams();
|
||||
return this.store.find("post", this.model.eventModel.id).then(post => {
|
||||
const raw = post.raw;
|
||||
const newRaw = this._replaceRawEvent(eventParams, raw);
|
||||
|
||||
if (newRaw) {
|
||||
const props = {
|
||||
raw: newRaw,
|
||||
edit_reason: I18n.t("discourse_post_event.update_event")
|
||||
};
|
||||
|
||||
return TextLib.cookAsync(newRaw).then(cooked => {
|
||||
props.cooked = cooked.string;
|
||||
return post
|
||||
.save(props)
|
||||
.catch(e => this.flash(extractError(e), "error"))
|
||||
.then(result => result && this.send("closeModal"));
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_buildEventParams() {
|
||||
const eventParams = {
|
||||
start: this.startsAt,
|
||||
status: this.model.eventModel.status,
|
||||
name: this.model.eventModel.name
|
||||
};
|
||||
|
||||
if (this.endsAt) {
|
||||
eventParams.end = this.endsAt;
|
||||
}
|
||||
|
||||
if (this.model.eventModel.status === "private") {
|
||||
eventParams.allowedGroups = (
|
||||
this.model.eventModel.raw_invitees || []
|
||||
).join(",");
|
||||
}
|
||||
|
||||
return eventParams;
|
||||
},
|
||||
|
||||
_removeRawEvent(raw) {
|
||||
const eventRegex = new RegExp(
|
||||
`\\[wrap=event\\s(.*?)\\]\\n\\[\\/wrap\\]`,
|
||||
"m"
|
||||
);
|
||||
|
||||
return raw.replace(eventRegex, "");
|
||||
},
|
||||
|
||||
_replaceRawEvent(eventparams, raw) {
|
||||
const eventRegex = new RegExp(`\\[wrap=event\\s(.*?)\\]`, "m");
|
||||
const eventMatches = raw.match(eventRegex);
|
||||
|
||||
if (eventMatches && eventMatches[1]) {
|
||||
const markdownParams = [];
|
||||
const eventParams = this._buildEventParams();
|
||||
Object.keys(eventParams).forEach(eventParam =>
|
||||
markdownParams.push(`${eventParam}="${eventParams[eventParam]}"`)
|
||||
);
|
||||
|
||||
return raw.replace(
|
||||
eventRegex,
|
||||
`[wrap=event ${markdownParams.join(" ")}]`
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -19,19 +19,6 @@
|
|||
}}
|
||||
|
||||
{{#event-field label="discourse_post_event.builder_modal.status.label"}}
|
||||
<label class="radio-label">
|
||||
{{radio-button
|
||||
name="status"
|
||||
value="standalone"
|
||||
selection=model.eventModel.status
|
||||
onChange=(action (mut model.eventModel.status))
|
||||
}}
|
||||
<span class="message">
|
||||
<span class="title">{{i18n "discourse_post_event.models.event.status.standalone.title"}}</span>
|
||||
<span class="description">{{i18n "discourse_post_event.models.event.status.standalone.description"}}</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="radio-label">
|
||||
{{radio-button
|
||||
name="status"
|
||||
|
@ -56,6 +43,18 @@
|
|||
<span class="description">{{i18n "discourse_post_event.models.event.status.private.description"}}</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
{{radio-button
|
||||
name="status"
|
||||
value="standalone"
|
||||
selection=model.eventModel.status
|
||||
onChange=(action (mut model.eventModel.status))
|
||||
}}
|
||||
<span class="message">
|
||||
<span class="title">{{i18n "discourse_post_event.models.event.status.standalone.title"}}</span>
|
||||
<span class="description">{{i18n "discourse_post_event.models.event.status.standalone.description"}}</span>
|
||||
</span>
|
||||
</label>
|
||||
{{/event-field}}
|
||||
|
||||
{{#event-field enabled=allowsInvitees label="discourse_post_event.builder_modal.invitees.label"}}
|
||||
|
|
|
@ -25,7 +25,7 @@ export default createWidget("discourse-post-event-dates", {
|
|||
attrs.eventModel.status !== "standalone"
|
||||
) {
|
||||
let participants;
|
||||
const label = I18n.t("event.post_ui.participants", {
|
||||
const label = I18n.t("discourse_post_event.event_ui.participants", {
|
||||
count: attrs.eventModel.stats.going
|
||||
});
|
||||
if (attrs.eventModel.stats.going > 0) {
|
||||
|
|
|
@ -136,7 +136,7 @@ export default createWidget("discourse-post-event", {
|
|||
{{#unless transformed.isStandaloneEvent}}
|
||||
{{#if state.eventModel.is_expired}}
|
||||
<span class="status expired">
|
||||
{{i18n "event.expired"}}
|
||||
{{i18n "discourse_post_event.models.event.expired"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<span class={{transformed.statusClass}} title={{transformed.eventStatusDescription}}>
|
||||
|
|
|
@ -1,45 +1,43 @@
|
|||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
function initializeEventBuilder(api) {
|
||||
api.attachWidgetAction("post", "showEventBuilder", function({
|
||||
postId,
|
||||
topicId
|
||||
}) {
|
||||
return new Promise(resolve => {
|
||||
if (postId) {
|
||||
this.store
|
||||
.find("discourse-post-event-event", postId)
|
||||
.then(resolve)
|
||||
.catch(() => {
|
||||
const eventModel = this.store.createRecord(
|
||||
const currentUser = api.getCurrentUser();
|
||||
const siteSettings = api.container.lookup("site-settings:main");
|
||||
|
||||
api.onToolbarCreate(toolbar => {
|
||||
if (!currentUser.staff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const composer = toolbar.context.outletArgs.composer;
|
||||
if (
|
||||
!composer.replyingToTopic &&
|
||||
(composer.topicFirstPost ||
|
||||
(composer.editingPost &&
|
||||
composer.post &&
|
||||
composer.post.post_number === 1))
|
||||
) {
|
||||
toolbar.addButton({
|
||||
title: "discourse_post_event.builder_modal.attach",
|
||||
id: "insertEvent",
|
||||
group: "insertions",
|
||||
icon: "calendar-day",
|
||||
perform: toolbarEvent => {
|
||||
const eventModel = toolbar.context.store.createRecord(
|
||||
"discourse-post-event-event"
|
||||
);
|
||||
eventModel.setProperties({
|
||||
id: postId,
|
||||
status: "public"
|
||||
});
|
||||
resolve(eventModel);
|
||||
});
|
||||
} else if (this.model) {
|
||||
resolve(this.model);
|
||||
}
|
||||
}).then(eventModel => {
|
||||
showModal("discourse-post-event-builder", {
|
||||
model: { eventModel, topicId },
|
||||
modalClass: "discourse-post-event-builder"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
api.decorateWidget("post-admin-menu:after", dec => {
|
||||
return dec.attach("post-admin-menu-button", {
|
||||
icon: "calendar-day",
|
||||
label: "discourse_event.builder.attach",
|
||||
action: "showEventBuilder",
|
||||
actionParam: { postId: dec.attrs.id, topicId: dec.attrs.topicId }
|
||||
showModal("discourse-post-event-builder").setProperties({
|
||||
toolbarEvent,
|
||||
model: { eventModel }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@ function cleanUp() {
|
|||
|
||||
function _attachWidget(api, cooked, eventModel) {
|
||||
const existing = cooked.querySelector(".discourse-post-event");
|
||||
const wrap = cooked.querySelector("[data-wrap=event]");
|
||||
|
||||
if (eventModel) {
|
||||
if (eventModel && wrap) {
|
||||
let widgetHeight = 300;
|
||||
|
||||
if (eventModel.can_update_attendance) {
|
||||
|
@ -31,7 +32,7 @@ function _attachWidget(api, cooked, eventModel) {
|
|||
eventContainer.classList.add("is-loading");
|
||||
eventContainer.style.height = `${widgetHeight}px`;
|
||||
eventContainer.innerHTML = '<div class="spinner medium"></div>';
|
||||
cooked.prepend(eventContainer);
|
||||
wrap.prepend(eventContainer);
|
||||
|
||||
const dates = [];
|
||||
const startsAt = moment(eventModel.starts_at);
|
||||
|
@ -79,7 +80,7 @@ function _attachWidget(api, cooked, eventModel) {
|
|||
}
|
||||
}
|
||||
|
||||
function initializeEventDecorator(api) {
|
||||
function initializeDiscoursePostEventDecorator(api) {
|
||||
api.cleanupStream(cleanUp);
|
||||
|
||||
api.decorateCooked(($cooked, helper) => {
|
||||
|
@ -128,7 +129,7 @@ export default {
|
|||
initialize(container) {
|
||||
const siteSettings = container.lookup("site-settings:main");
|
||||
if (siteSettings.discourse_post_event_enabled) {
|
||||
withPluginApi("0.8.7", initializeEventDecorator);
|
||||
withPluginApi("0.8.7", initializeDiscoursePostEventDecorator);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -24,5 +24,11 @@ en:
|
|||
errors:
|
||||
models:
|
||||
event:
|
||||
raw_invitees_length: "An event is limited to %{count} users/groups"
|
||||
ends_at_before_starts_at: "An event can't end before it starts"
|
||||
only_one_event: A post can only have one event.
|
||||
must_be_in_first_post: An event can only be in the first post of a topic.
|
||||
raw_invitees_length: "An event is limited to %{count} users/groups."
|
||||
ends_at_before_starts_at: "An event can't end before it starts."
|
||||
start_must_be_present_and_a_valid_date: "An event requires a valid start date."
|
||||
end_must_be_a_valid_date: "End date must be a valid date."
|
||||
name:
|
||||
length: "Event name length must be between %{minimum} and %{maximum} characters."
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
EVENT_REGEX = /\[wrap=event\s(.*?)\](?:\n|\\n)?\[\/wrap\]/m
|
||||
|
||||
VALID_OPTIONS = [
|
||||
:start,
|
||||
:end,
|
||||
:status,
|
||||
:allowedGroups,
|
||||
:name
|
||||
]
|
||||
|
||||
module DiscoursePostEvent
|
||||
class EventParser
|
||||
def self.extract_events(str)
|
||||
str.scan(EVENT_REGEX).map do |scan|
|
||||
extract_options(scan[0].gsub(/\\/, ''))
|
||||
end.compact
|
||||
end
|
||||
|
||||
def self.extract_options(str)
|
||||
options = nil
|
||||
str.split(" ").each do |option|
|
||||
key, value = option.split("=")
|
||||
if VALID_OPTIONS.include?(key.to_sym) && value
|
||||
options ||= {}
|
||||
options[key.to_sym] = value.delete('\\"')
|
||||
end
|
||||
end
|
||||
options
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscoursePostEvent
|
||||
class EventValidator
|
||||
def initialize(post)
|
||||
@post = post
|
||||
end
|
||||
|
||||
def validate_event
|
||||
extracted_events = DiscoursePostEvent::EventParser::extract_events(@post.raw)
|
||||
|
||||
if extracted_events.count == 0
|
||||
return false
|
||||
end
|
||||
|
||||
if extracted_events.count > 1
|
||||
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.only_one_event"))
|
||||
return false
|
||||
end
|
||||
|
||||
if !@post.is_first_post?
|
||||
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.must_be_in_first_post"))
|
||||
return false
|
||||
end
|
||||
|
||||
extracted_event = extracted_events.first
|
||||
|
||||
if extracted_event[:start].blank? || (DateTime.parse(extracted_event[:start]) rescue nil).nil?
|
||||
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.start_must_be_present_and_a_valid_date"))
|
||||
return false
|
||||
end
|
||||
|
||||
if extracted_event[:end].present? && (DateTime.parse(extracted_event[:end]) rescue nil).nil?
|
||||
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.end_must_be_a_valid_date"))
|
||||
return false
|
||||
end
|
||||
|
||||
if extracted_event[:start].present? && extracted_event[:end].present?
|
||||
if Time.parse(extracted_event[:start]) > Time.parse(extracted_event[:end])
|
||||
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.ends_at_before_starts_at"))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if extracted_event[:name].present? && extracted_event[:name]
|
||||
if !(Event::MIN_NAME_LENGTH..Event::MAX_NAME_LENGTH).cover?(extracted_event[:name].length)
|
||||
@post.errors.add(:base, I18n.t('discourse_post_event.errors.models.event.name.length', minimum: Event::MIN_NAME_LENGTH, maximum: Event::MAX_NAME_LENGTH))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
51
plugin.rb
51
plugin.rb
|
@ -81,6 +81,8 @@ after_initialize do
|
|||
"../app/controllers/discourse_post_event/upcoming_events_controller.rb",
|
||||
"../app/models/discourse_post_event/event.rb",
|
||||
"../app/models/discourse_post_event/invitee.rb",
|
||||
"../lib/discourse_post_event/event_parser.rb",
|
||||
"../lib/discourse_post_event/event_validator.rb",
|
||||
"../lib/discourse_post_event/event_finder.rb",
|
||||
"../app/serializers/discourse_post_event/invitee_serializer.rb",
|
||||
"../app/serializers/discourse_post_event/event_serializer.rb"
|
||||
|
@ -88,25 +90,6 @@ after_initialize do
|
|||
|
||||
::ActionController::Base.prepend_view_path File.expand_path("../app/views", __FILE__)
|
||||
|
||||
reloadable_patch do
|
||||
require 'post'
|
||||
|
||||
class ::Post
|
||||
has_one :event,
|
||||
dependent: :destroy,
|
||||
class_name: 'DiscoursePostEvent::Event',
|
||||
foreign_key: :id
|
||||
end
|
||||
end
|
||||
|
||||
add_to_serializer(:post, :event) do
|
||||
DiscoursePostEvent::EventSerializer.new(object.event, scope: scope, root: false)
|
||||
end
|
||||
|
||||
add_to_serializer(:post, :include_event?) do
|
||||
SiteSetting.discourse_post_event_enabled
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::DiscoursePostEvent::Engine, at: '/'
|
||||
end
|
||||
|
@ -124,6 +107,36 @@ after_initialize do
|
|||
get '/upcoming-events' => 'upcoming_events#index'
|
||||
end
|
||||
|
||||
reloadable_patch do
|
||||
require 'post'
|
||||
|
||||
class ::Post
|
||||
has_one :event,
|
||||
dependent: :destroy,
|
||||
class_name: 'DiscoursePostEvent::Event',
|
||||
foreign_key: :id
|
||||
|
||||
validate :valid_event
|
||||
def valid_event
|
||||
return unless self.raw_changed?
|
||||
validator = DiscoursePostEvent::EventValidator.new(self)
|
||||
validator.validate_event
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_to_serializer(:post, :event) do
|
||||
DiscoursePostEvent::EventSerializer.new(object.event, scope: scope, root: false)
|
||||
end
|
||||
|
||||
add_to_serializer(:post, :include_event?) do
|
||||
SiteSetting.discourse_post_event_enabled
|
||||
end
|
||||
|
||||
DiscourseEvent.on(:post_process_cooked) do |doc, post|
|
||||
DiscoursePostEvent::Event.update_from_raw(post)
|
||||
end
|
||||
|
||||
DiscourseEvent.on(:post_destroyed) do |post|
|
||||
if SiteSetting.discourse_post_event_enabled && post.event
|
||||
post.event.update!(deleted_at: Time.now)
|
||||
|
|
|
@ -17,6 +17,105 @@ describe Post do
|
|||
SiteSetting.discourse_post_event_enabled = true
|
||||
end
|
||||
|
||||
context 'when a post is updated' do
|
||||
context 'when the post has a valid event' do
|
||||
context 'context the event markup is removed' do
|
||||
it 'destroys the associated event' do
|
||||
start = Time.now.utc.iso8601(3)
|
||||
|
||||
post = PostCreator.create!(
|
||||
user,
|
||||
title: 'Sell a boat party',
|
||||
raw: "[wrap=event start=#{start}]\n[/wrap]",
|
||||
)
|
||||
|
||||
expect(post.event.persisted?).to eq(true)
|
||||
|
||||
revisor = PostRevisor.new(post, post.topic)
|
||||
revisor.revise!(user, raw: 'The event is over. Come back another day.')
|
||||
|
||||
expect(post.reload.event).to be(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a post is created' do
|
||||
context 'when the post contains one valid event' do
|
||||
it 'creates the post event' do
|
||||
start = Time.now.utc.iso8601(3)
|
||||
|
||||
post = PostCreator.create!(
|
||||
user,
|
||||
title: 'Sell a boat party',
|
||||
raw: "[wrap=event start=#{start}]\n[/wrap]",
|
||||
)
|
||||
|
||||
expect(post.persisted?).to eq(true)
|
||||
expect(post.event.persisted?).to eq(true)
|
||||
expect(post.event.starts_at).to eq_time(Time.parse(start))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the post contains one invalid event' do
|
||||
context 'when start is not provided or is invalid' do
|
||||
it 'raises an error' do
|
||||
expect {
|
||||
PostCreator.create!(
|
||||
user,
|
||||
title: 'Sell a boat party',
|
||||
raw: "[wrap=event end=\"1\"]\n[/wrap]",
|
||||
)
|
||||
}.to(
|
||||
raise_error(ActiveRecord::RecordNotSaved)
|
||||
.with_message(I18n.t("discourse_post_event.errors.models.event.start_must_be_present_and_a_valid_date"))
|
||||
)
|
||||
|
||||
expect {
|
||||
PostCreator.create!(
|
||||
user,
|
||||
title: 'Sell a boat party',
|
||||
raw: "[wrap=event start=\"x\"]\n[/wrap]",
|
||||
)
|
||||
}.to(
|
||||
raise_error(ActiveRecord::RecordNotSaved)
|
||||
.with_message(I18n.t("discourse_post_event.errors.models.event.start_must_be_present_and_a_valid_date"))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when end is provided and is invalid' do
|
||||
it 'raises an error' do
|
||||
expect {
|
||||
PostCreator.create!(
|
||||
user,
|
||||
title: 'Sell a boat party',
|
||||
raw: "[wrap=event start=\"#{Time.now.utc.iso8601(3)}\" end=\"d\"]\n[/wrap]",
|
||||
)
|
||||
}.to(
|
||||
raise_error(ActiveRecord::RecordNotSaved)
|
||||
.with_message(I18n.t("discourse_post_event.errors.models.event.end_must_be_a_valid_date"))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the post contains multiple events' do
|
||||
it 'raises an error' do
|
||||
expect {
|
||||
PostCreator.create!(
|
||||
user,
|
||||
title: 'Sell a boat party',
|
||||
raw: "[wrap=event start=\"#{Time.now.utc.iso8601(3)}\"]\n[/wrap] foo [wrap=event start=\"#{Time.now.utc.iso8601(3)}\"]\n[/wrap]",
|
||||
)
|
||||
}.to(
|
||||
raise_error(ActiveRecord::RecordNotSaved)
|
||||
.with_message(I18n.t("discourse_post_event.errors.models.event.only_one_event"))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a post with an event is destroyed' do
|
||||
it 'sets deleted_at on the post_event' do
|
||||
expect(post_event.deleted_at).to be_nil
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe DiscoursePostEvent::EventParser do
|
||||
subject { DiscoursePostEvent::EventParser }
|
||||
|
||||
it 'works with no event' do
|
||||
events = subject.extract_events('this could be a nice event')
|
||||
expect(events.length).to eq(0)
|
||||
end
|
||||
|
||||
it 'finds one event' do
|
||||
events = subject.extract_events('[wrap=event start="foo" end="bar"]\n[/wrap]')
|
||||
expect(events.length).to eq(1)
|
||||
end
|
||||
|
||||
it 'finds multiple events' do
|
||||
events = subject.extract_events('[wrap=event start="foo" end="bar"]\n[/wrap] baz [wrap=event start="foo" end="bar"]\n[/wrap]')
|
||||
expect(events.length).to eq(2)
|
||||
end
|
||||
|
||||
it 'parses options' do
|
||||
events = subject.extract_events('[wrap=event start="foo" end="bar"]\n[/wrap]')
|
||||
expect(events[0][:start]).to eq("foo")
|
||||
expect(events[0][:end]).to eq("bar")
|
||||
end
|
||||
|
||||
it 'works with escaped string' do
|
||||
events = subject.extract_events("I am going to get that fixed.\n\n[wrap=event start=\"bar\"]\n[/wrap]\n\n[wrap=event start=\"foo\"]\n[/wrap]")
|
||||
expect(events[0][:start]).to eq("bar")
|
||||
expect(events[1][:start]).to eq("foo")
|
||||
end
|
||||
|
||||
it 'doesn’t parse invalid options' do
|
||||
events = subject.extract_events("I am going to get that fixed.\n\n[wrap=event start=\"foo\" something=\"bar\"]\n[/wrap]")
|
||||
expect(events[0][:something]).to be(nil)
|
||||
|
||||
events = subject.extract_events("I am going to get that fixed.\n\n[wrap=event something=\"bar\"]\n[/wrap]")
|
||||
expect(events).to eq([])
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue