DEV: Convert group timezones from widgets to glimmer (#731)
Also introduces a basic system spec for the feature (previously there was no test coverage of the frontend at all)
This commit is contained in:
parent
8152a0ca7c
commit
d471bbdf9a
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { apiInitializer } from "discourse/lib/api";
|
||||||
|
import GroupTimezones from "../components/group-timezones";
|
||||||
|
|
||||||
|
const GroupTimezonesShim = <template>
|
||||||
|
<GroupTimezones
|
||||||
|
@members={{@data.members}}
|
||||||
|
@group={{@data.group}}
|
||||||
|
@size={{@data.size}}
|
||||||
|
/>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export default apiInitializer((api) => {
|
||||||
|
api.decorateCookedElement((element, helper) => {
|
||||||
|
element.querySelectorAll(".group-timezones").forEach((el) => {
|
||||||
|
const post = helper.getModel();
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = el.dataset.group;
|
||||||
|
if (!group) {
|
||||||
|
throw new Error(
|
||||||
|
"Group timezone element is missing 'data-group' attribute"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.renderGlimmer(el, GroupTimezonesShim, {
|
||||||
|
group,
|
||||||
|
members: (post.group_timezones || {})[group] || [],
|
||||||
|
size: el.dataset.size || "medium",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,38 +1,27 @@
|
||||||
import hbs from "discourse/widgets/hbs-compiler";
|
import Component from "@glimmer/component";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
import { tracked } from "@glimmer/tracking";
|
||||||
import roundTime from "../lib/round-time";
|
import { fn } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import { eq } from "truth-helpers";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
import roundTime from "../../lib/round-time";
|
||||||
|
import NewDay from "./new-day";
|
||||||
|
import TimeTraveller from "./time-traveller";
|
||||||
|
import Timezone from "./timezone";
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones", {
|
export default class GroupTimezones extends Component {
|
||||||
tagName: "div.group-timezones",
|
@service siteSettings;
|
||||||
|
|
||||||
buildKey: (attrs) => `group-timezones-${attrs.id}`,
|
@tracked filter = "";
|
||||||
|
@tracked localTimeOffset = 0;
|
||||||
|
|
||||||
buildClasses(attrs) {
|
get groupedTimezones() {
|
||||||
return attrs.size;
|
|
||||||
},
|
|
||||||
|
|
||||||
buildAttributes(attrs) {
|
|
||||||
return {
|
|
||||||
id: attrs.id,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultState() {
|
|
||||||
return {
|
|
||||||
localTimeOffset: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onChangeCurrentUserTimeOffset(offset) {
|
|
||||||
this.state.localTimeOffset = offset;
|
|
||||||
},
|
|
||||||
|
|
||||||
transform(attrs, state) {
|
|
||||||
const members = attrs.members || [];
|
|
||||||
let groupedTimezones = [];
|
let groupedTimezones = [];
|
||||||
|
|
||||||
members.filterBy("timezone").forEach((member) => {
|
this.args.members.filterBy("timezone").forEach((member) => {
|
||||||
if (this._shouldAddMemberToGroup(this.state.filter, member)) {
|
if (this.#shouldAddMemberToGroup(this.filter, member)) {
|
||||||
const timezone = member.timezone;
|
const timezone = member.timezone;
|
||||||
const identifier = parseInt(moment.tz(timezone).format("YYYYMDHm"), 10);
|
const identifier = parseInt(moment.tz(timezone).format("YYYYMDHm"), 10);
|
||||||
let groupedTimezone = groupedTimezones.findBy("identifier", identifier);
|
let groupedTimezone = groupedTimezones.findBy("identifier", identifier);
|
||||||
|
|
@ -40,18 +29,18 @@ export default createWidget("discourse-group-timezones", {
|
||||||
if (groupedTimezone) {
|
if (groupedTimezone) {
|
||||||
groupedTimezone.members.push(member);
|
groupedTimezone.members.push(member);
|
||||||
} else {
|
} else {
|
||||||
const now = this._roundMoment(moment.tz(timezone));
|
const now = this.#roundMoment(moment.tz(timezone));
|
||||||
const workingDays = this._workingDays();
|
const workingDays = this.#workingDays();
|
||||||
const offset = moment.tz(moment.utc(), timezone).utcOffset();
|
const offset = moment.tz(moment.utc(), timezone).utcOffset();
|
||||||
|
|
||||||
groupedTimezone = {
|
groupedTimezone = {
|
||||||
identifier,
|
identifier,
|
||||||
offset,
|
offset,
|
||||||
type: "discourse-group-timezone",
|
type: "discourse-group-timezone",
|
||||||
nowWithOffset: now.add(state.localTimeOffset, "minutes"),
|
nowWithOffset: now.add(this.localTimeOffset, "minutes"),
|
||||||
closeToWorkingHours: this._closeToWorkingHours(now, workingDays),
|
closeToWorkingHours: this.#closeToWorkingHours(now, workingDays),
|
||||||
inWorkingHours: this._inWorkingHours(now, workingDays),
|
inWorkingHours: this.#inWorkingHours(now, workingDays),
|
||||||
utcOffset: this._utcOffset(offset),
|
utcOffset: this.#utcOffset(offset),
|
||||||
members: [member],
|
members: [member],
|
||||||
};
|
};
|
||||||
groupedTimezones.push(groupedTimezone);
|
groupedTimezones.push(groupedTimezone);
|
||||||
|
|
@ -84,36 +73,10 @@ export default createWidget("discourse-group-timezones", {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { groupedTimezones };
|
return groupedTimezones;
|
||||||
},
|
}
|
||||||
|
|
||||||
onChangeFilter(filter) {
|
#shouldAddMemberToGroup(filter, member) {
|
||||||
this.state.filter = filter && filter.length ? filter : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
template: hbs`
|
|
||||||
{{attach
|
|
||||||
widget="discourse-group-timezones-header"
|
|
||||||
attrs=(hash
|
|
||||||
id=attrs.id
|
|
||||||
group=attrs.group
|
|
||||||
localTimeOffset=state.localTimeOffset
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
<div class="group-timezones-body">
|
|
||||||
{{#each transformed.groupedTimezones as |groupedTimezone|}}
|
|
||||||
{{attach
|
|
||||||
widget=groupedTimezone.type
|
|
||||||
attrs=(hash
|
|
||||||
usersOnHoliday=attrs.usersOnHoliday
|
|
||||||
groupedTimezone=groupedTimezone
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
|
|
||||||
_shouldAddMemberToGroup(filter, member) {
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
filter = filter.toLowerCase();
|
filter = filter.toLowerCase();
|
||||||
if (
|
if (
|
||||||
|
|
@ -127,17 +90,17 @@ export default createWidget("discourse-group-timezones", {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
}
|
||||||
|
|
||||||
_roundMoment(date) {
|
#roundMoment(date) {
|
||||||
if (this.state.localTimeOffset) {
|
if (this.localTimeOffset) {
|
||||||
date = roundTime(date);
|
date = roundTime(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
return date;
|
return date;
|
||||||
},
|
}
|
||||||
|
|
||||||
_closeToWorkingHours(moment, workingDays) {
|
#closeToWorkingHours(moment, workingDays) {
|
||||||
const hours = moment.hours();
|
const hours = moment.hours();
|
||||||
const startHour = this.siteSettings.working_day_start_hour;
|
const startHour = this.siteSettings.working_day_start_hour;
|
||||||
const endHour = this.siteSettings.working_day_end_hour;
|
const endHour = this.siteSettings.working_day_end_hour;
|
||||||
|
|
@ -148,18 +111,18 @@ export default createWidget("discourse-group-timezones", {
|
||||||
(hours <= Math.min(endHour + extension, 23) && hours >= endHour)) &&
|
(hours <= Math.min(endHour + extension, 23) && hours >= endHour)) &&
|
||||||
workingDays.includes(moment.isoWeekday())
|
workingDays.includes(moment.isoWeekday())
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
_inWorkingHours(moment, workingDays) {
|
#inWorkingHours(moment, workingDays) {
|
||||||
const hours = moment.hours();
|
const hours = moment.hours();
|
||||||
return (
|
return (
|
||||||
hours > this.siteSettings.working_day_start_hour &&
|
hours > this.siteSettings.working_day_start_hour &&
|
||||||
hours < this.siteSettings.working_day_end_hour &&
|
hours < this.siteSettings.working_day_end_hour &&
|
||||||
workingDays.includes(moment.isoWeekday())
|
workingDays.includes(moment.isoWeekday())
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
_utcOffset(offset) {
|
#utcOffset(offset) {
|
||||||
const sign = Math.sign(offset) === 1 ? "+" : "-";
|
const sign = Math.sign(offset) === 1 ? "+" : "-";
|
||||||
offset = Math.abs(offset);
|
offset = Math.abs(offset);
|
||||||
let hours = Math.floor(offset / 60).toString();
|
let hours = Math.floor(offset / 60).toString();
|
||||||
|
|
@ -170,9 +133,9 @@ export default createWidget("discourse-group-timezones", {
|
||||||
/:00$/,
|
/:00$/,
|
||||||
""
|
""
|
||||||
)}`.replace(/-0/, " ");
|
)}`.replace(/-0/, " ");
|
||||||
},
|
}
|
||||||
|
|
||||||
_workingDays() {
|
#workingDays() {
|
||||||
const enMoment = moment().locale("en");
|
const enMoment = moment().locale("en");
|
||||||
const getIsoWeekday = (day) =>
|
const getIsoWeekday = (day) =>
|
||||||
enMoment.localeData()._weekdays.indexOf(day) || 7;
|
enMoment.localeData()._weekdays.indexOf(day) || 7;
|
||||||
|
|
@ -180,5 +143,40 @@ export default createWidget("discourse-group-timezones", {
|
||||||
.split("|")
|
.split("|")
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((x) => getIsoWeekday(x));
|
.map((x) => getIsoWeekday(x));
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
@action
|
||||||
|
handleFilterChange(event) {
|
||||||
|
this.filter = event.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="group-timezones-header">
|
||||||
|
<TimeTraveller
|
||||||
|
@localTimeOffset={{this.localTimeOffset}}
|
||||||
|
@setOffset={{fn (mut this.localTimeOffset)}}
|
||||||
|
/>
|
||||||
|
<span class="title">
|
||||||
|
{{i18n "group_timezones.group_availability" group=@group}}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={{i18n "group_timezones.search"}}
|
||||||
|
class="group-timezones-filter"
|
||||||
|
{{on "input" this.handleFilterChange}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="group-timezones-body">
|
||||||
|
{{#each this.groupedTimezones key="identifier" as |groupedTimezone|}}
|
||||||
|
{{#if (eq groupedTimezone.type "discourse-group-timezone-new-day")}}
|
||||||
|
<NewDay
|
||||||
|
@beforeDate={{groupedTimezone.beforeDate}}
|
||||||
|
@afterDate={{groupedTimezone.afterDate}}
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<Timezone @groupedTimezone={{groupedTimezone}} />
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import icon from "discourse/helpers/d-icon";
|
||||||
|
|
||||||
|
const NewDay = <template>
|
||||||
|
<div class="group-timezone-new-day">
|
||||||
|
<span class="before">
|
||||||
|
{{icon "chevron-left"}}
|
||||||
|
{{@beforeDate}}
|
||||||
|
</span>
|
||||||
|
<span class="after">
|
||||||
|
{{@afterDate}}
|
||||||
|
{{icon "chevron-right"}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export default NewDay;
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { not } from "truth-helpers";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import roundTime from "../../lib/round-time";
|
||||||
|
|
||||||
|
export default class TimeTraveller extends Component {
|
||||||
|
get localTimeWithOffset() {
|
||||||
|
let date = moment().add(this.args.localTimeOffset, "minutes");
|
||||||
|
|
||||||
|
if (this.args.localTimeOffset) {
|
||||||
|
date = roundTime(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.format("HH:mm");
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
reset() {
|
||||||
|
this.args.setOffset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
sliderMoved(event) {
|
||||||
|
const value = parseInt(event.target.value, 10);
|
||||||
|
const offset = value * 15;
|
||||||
|
this.args.setOffset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="group-timezones-time-traveler">
|
||||||
|
<span class="time">
|
||||||
|
{{this.localTimeWithOffset}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="discourse-group-timezones-slider-wrapper">
|
||||||
|
<input
|
||||||
|
class="group-timezones-slider"
|
||||||
|
{{on "input" this.sliderMoved}}
|
||||||
|
step="1"
|
||||||
|
value="0"
|
||||||
|
type="range"
|
||||||
|
min="-48"
|
||||||
|
max="48"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="group-timezones-reset">
|
||||||
|
<DButton
|
||||||
|
disabled={{not @localTimeOffset}}
|
||||||
|
@action={{this.reset}}
|
||||||
|
@icon="arrow-rotate-left"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import UserAvatar from "discourse/components/user-avatar";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
|
||||||
|
export default class GroupTimezone extends Component {
|
||||||
|
get formattedTime() {
|
||||||
|
return this.args.groupedTimezone.nowWithOffset.format("LT");
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class={{concatClass
|
||||||
|
"group-timezone"
|
||||||
|
(if @groupedTimezone.closeToWorkingHours "close-to-working-hours")
|
||||||
|
(if @groupedTimezone.inWorkingHours "in-working-hours")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="info">
|
||||||
|
<span class="time">
|
||||||
|
{{this.formattedTime}}
|
||||||
|
</span>
|
||||||
|
<span class="offset" title="UTC offset">
|
||||||
|
{{@groupedTimezone.utcOffset}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul class="group-timezones-members">
|
||||||
|
{{#each @groupedTimezone.members key="username" as |member|}}
|
||||||
|
<li
|
||||||
|
class={{concatClass
|
||||||
|
"group-timezones-member"
|
||||||
|
(if member.on_holiday "on-holiday" "not-on-holiday")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserAvatar
|
||||||
|
@user={{member}}
|
||||||
|
@size="small"
|
||||||
|
class="group-timezones-member-avatar"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import $ from "jquery";
|
|
||||||
import { getRegister } from "discourse/lib/get-owner";
|
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
|
||||||
import WidgetGlue from "discourse/widgets/glue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "discourse-group-timezones",
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
withPluginApi("0.8.7", (api) => {
|
|
||||||
let _glued = [];
|
|
||||||
|
|
||||||
function cleanUp() {
|
|
||||||
_glued.forEach((g) => g.cleanUp());
|
|
||||||
_glued = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _attachWidget(container, options) {
|
|
||||||
const glue = new WidgetGlue(
|
|
||||||
"discourse-group-timezones",
|
|
||||||
getRegister(api),
|
|
||||||
options
|
|
||||||
);
|
|
||||||
glue.appendTo(container);
|
|
||||||
_glued.push(glue);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _attachGroupTimezones($elem, post) {
|
|
||||||
const $groupTimezones = $(".group-timezones", $elem);
|
|
||||||
|
|
||||||
if (!$groupTimezones.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$groupTimezones.each((idx, groupTimezone) => {
|
|
||||||
const group = groupTimezone.getAttribute("data-group");
|
|
||||||
if (!group) {
|
|
||||||
throw "[group] attribute is necessary when using timezones.";
|
|
||||||
}
|
|
||||||
|
|
||||||
const members = (post.get("group_timezones") || {})[group] || [];
|
|
||||||
|
|
||||||
_attachWidget(groupTimezone, {
|
|
||||||
id: `${post.id}-${idx}`,
|
|
||||||
members,
|
|
||||||
group,
|
|
||||||
usersOnHoliday:
|
|
||||||
api.container.lookup("service:site").users_on_holiday || [],
|
|
||||||
size: groupTimezone.getAttribute("data-size") || "medium",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _attachPostWithGroupTimezones($elem, helper) {
|
|
||||||
if (helper) {
|
|
||||||
const post = helper.getModel();
|
|
||||||
|
|
||||||
if (post) {
|
|
||||||
api.preventCloak(post.id);
|
|
||||||
_attachGroupTimezones($elem, post);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
api.decorateCooked(_attachPostWithGroupTimezones, {
|
|
||||||
id: "discourse-group-timezones",
|
|
||||||
});
|
|
||||||
|
|
||||||
api.cleanupStream(cleanUp);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import hbs from "discourse/widgets/hbs-compiler";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezone-new-day", {
|
|
||||||
tagName: "div.group-timezone-new-day",
|
|
||||||
|
|
||||||
template: hbs`
|
|
||||||
<span class="before">
|
|
||||||
{{d-icon "chevron-left"}}
|
|
||||||
{{this.attrs.groupedTimezone.beforeDate}}
|
|
||||||
</span>
|
|
||||||
<span class="after">
|
|
||||||
{{this.attrs.groupedTimezone.afterDate}}
|
|
||||||
{{d-icon "chevron-right"}}
|
|
||||||
</span>
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import hbs from "discourse/widgets/hbs-compiler";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezone", {
|
|
||||||
tagName: "div.group-timezone",
|
|
||||||
|
|
||||||
buildClasses(attrs) {
|
|
||||||
const classes = [];
|
|
||||||
|
|
||||||
if (attrs.groupedTimezone.closeToWorkingHours) {
|
|
||||||
classes.push("close-to-working-hours");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attrs.groupedTimezone.inWorkingHours) {
|
|
||||||
classes.push("in-working-hours");
|
|
||||||
}
|
|
||||||
|
|
||||||
return classes.join(" ");
|
|
||||||
},
|
|
||||||
|
|
||||||
transform(attrs) {
|
|
||||||
return {
|
|
||||||
formatedTime: attrs.groupedTimezone.nowWithOffset.format("LT"),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
template: hbs`
|
|
||||||
<div class="info">
|
|
||||||
<span class="time">
|
|
||||||
{{transformed.formatedTime}}
|
|
||||||
</span>
|
|
||||||
<span class="offset" title="UTC offset">
|
|
||||||
{{{attrs.groupedTimezone.utcOffset}}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ul class="group-timezones-members">
|
|
||||||
{{#each attrs.groupedTimezone.members as |member|}}
|
|
||||||
{{attach
|
|
||||||
widget="discourse-group-timezones-member"
|
|
||||||
attrs=(hash
|
|
||||||
usersOnHoliday=attrs.usersOnHoliday
|
|
||||||
member=member
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { throttle } from "@ember/runloop";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones-filter", {
|
|
||||||
tagName: "input.group-timezones-filter",
|
|
||||||
|
|
||||||
input(event) {
|
|
||||||
this.changeFilterThrottler(event.target.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeFilterThrottler(filter) {
|
|
||||||
throttle(
|
|
||||||
this,
|
|
||||||
function () {
|
|
||||||
this.sendWidgetAction("onChangeFilter", filter);
|
|
||||||
},
|
|
||||||
100
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
buildAttributes() {
|
|
||||||
return {
|
|
||||||
type: "text",
|
|
||||||
placeholder: i18n("group_timezones.search"),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import hbs from "discourse/widgets/hbs-compiler";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones-header", {
|
|
||||||
tagName: "div.group-timezones-header",
|
|
||||||
|
|
||||||
transform(attrs) {
|
|
||||||
return {
|
|
||||||
title: i18n("group_timezones.group_availability", {
|
|
||||||
group: attrs.group,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
template: hbs`
|
|
||||||
{{attach
|
|
||||||
widget="discourse-group-timezones-time-traveler"
|
|
||||||
attrs=(hash
|
|
||||||
id=attrs.id
|
|
||||||
localTimeOffset=attrs.localTimeOffset
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
<span class="title">
|
|
||||||
{{transformed.title}}
|
|
||||||
</span>
|
|
||||||
{{attach widget="discourse-group-timezones-filter"}}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { h } from "virtual-dom";
|
|
||||||
import { formatUsername } from "discourse/lib/utilities";
|
|
||||||
import { avatarImg } from "discourse/widgets/post";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones-member", {
|
|
||||||
tagName: "li.group-timezones-member",
|
|
||||||
|
|
||||||
buildClasses(attrs) {
|
|
||||||
return attrs.member.on_holiday ? "on-holiday" : "not-on-holiday";
|
|
||||||
},
|
|
||||||
|
|
||||||
html(attrs) {
|
|
||||||
const { name, username, avatar_template } = attrs.member;
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"a",
|
|
||||||
{
|
|
||||||
attributes: {
|
|
||||||
class: "group-timezones-member-avatar",
|
|
||||||
"data-user-card": username,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
avatarImg("small", {
|
|
||||||
template: avatar_template,
|
|
||||||
username: name || formatUsername(username),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import hbs from "discourse/widgets/hbs-compiler";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones-reset", {
|
|
||||||
tagName: "div.group-timezones-reset",
|
|
||||||
|
|
||||||
onResetOffset() {
|
|
||||||
this.sendWidgetAction("onChangeCurrentUserTimeOffset", 0);
|
|
||||||
|
|
||||||
const container = document.getElementById(this.attrs.id);
|
|
||||||
const slider = container.querySelector(
|
|
||||||
"input[type=range].group-timezones-slider"
|
|
||||||
);
|
|
||||||
if (slider) {
|
|
||||||
slider.value = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
transform(attrs) {
|
|
||||||
return {
|
|
||||||
isDisabled: attrs.localTimeOffset === 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
template: hbs`
|
|
||||||
{{attach
|
|
||||||
widget="button"
|
|
||||||
attrs=(hash
|
|
||||||
disabled=this.transformed.isDisabled
|
|
||||||
action="onResetOffset"
|
|
||||||
icon="arrow-rotate-left"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import { throttle } from "@ember/runloop";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones-slider", {
|
|
||||||
tagName: "input.group-timezones-slider",
|
|
||||||
|
|
||||||
input(event) {
|
|
||||||
this._handleSliderEvent(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
change(event) {
|
|
||||||
this._handleSliderEvent(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeOffsetThrottler(offset) {
|
|
||||||
throttle(
|
|
||||||
this,
|
|
||||||
function () {
|
|
||||||
this.sendWidgetAction("onChangeCurrentUserTimeOffset", offset);
|
|
||||||
},
|
|
||||||
75
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
buildAttributes() {
|
|
||||||
return {
|
|
||||||
step: 1,
|
|
||||||
value: 0,
|
|
||||||
min: -48,
|
|
||||||
max: 48,
|
|
||||||
type: "range",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleSliderEvent(event) {
|
|
||||||
const value = parseInt(event.target.value, 10);
|
|
||||||
const offset = value * 15;
|
|
||||||
this.changeOffsetThrottler(offset);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import hbs from "discourse/widgets/hbs-compiler";
|
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
import roundTime from "../lib/round-time";
|
|
||||||
|
|
||||||
export default createWidget("discourse-group-timezones-time-traveler", {
|
|
||||||
tagName: "div.group-timezones-time-traveler",
|
|
||||||
|
|
||||||
transform(attrs) {
|
|
||||||
let date = moment().add(attrs.localTimeOffset, "minutes");
|
|
||||||
|
|
||||||
if (attrs.localTimeOffset) {
|
|
||||||
date = roundTime(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
localTimeWithOffset: date.format("HH:mm"),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
template: hbs`
|
|
||||||
<span class="time">
|
|
||||||
{{transformed.localTimeWithOffset}}
|
|
||||||
</span>
|
|
||||||
<span class="discourse-group-timezones-slider-wrapper">
|
|
||||||
{{attach
|
|
||||||
widget="discourse-group-timezones-slider"
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
{{attach
|
|
||||||
widget="discourse-group-timezones-reset"
|
|
||||||
attrs=(hash
|
|
||||||
id=attrs.id
|
|
||||||
localTimeOffset=attrs.localTimeOffset
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe "Group timezones feature", type: :system do
|
||||||
|
fab!(:group) { Fabricate(:group, name: "test-group") }
|
||||||
|
|
||||||
|
fab!(:users) do
|
||||||
|
Fabricate
|
||||||
|
.times(5, :user)
|
||||||
|
.each do |user|
|
||||||
|
user.user_option.timezone = "America/New_York"
|
||||||
|
user.user_option.save!
|
||||||
|
group.add(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Jobs.run_immediately!
|
||||||
|
SiteSetting.calendar_enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:post) { create_post(raw: <<~RAW) }
|
||||||
|
[timezones group="test-group"]
|
||||||
|
[/timezones]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
it "renders successfully" do
|
||||||
|
visit(post.url)
|
||||||
|
expect(page).to have_selector(".group-timezones")
|
||||||
|
expect(page).to have_selector(".group-timezones-member", count: 5)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue