Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ Here is an example of extending the `tsml_react_config` object to include a defi

```js
var tsml_react_config = {
timezone: 'Pacific/Honolulu',
strings: {
en: {
types: {
Expand Down Expand Up @@ -54,6 +53,14 @@ var tsml_react_config = {

## Frequently asked questions

### How do timezones work?

If you are only listing meetings in a single timezone, e.g. Philadelphia, PA, then you should specify a `data-timezone` attribute in your embed code. This must be in the proper [IANA timezone format](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) e.g. `America/New_York`. TSML UI will assume that any meetings without a specified timezone are in that zone.

However if your site lists meetings in a variety of timezones, and you have a timezone key/column in your meeting data, then you may omit the `data-timezone` attribute and times will be translated into the user's timezone.

Note: The WordPress plugin 12 Step Meeting List does not yet support timezone keys in meeting data.

### How are metatypes like "Active" and "Online" calculated?

Metatypes are types that are not specified explicitly in the data, they are inferred from the data based on this logic:
Expand Down Expand Up @@ -81,10 +88,3 @@ Not yet! Please open a pull request and walk us through the process of adding it
Pull requests are welcome. To get started, clone this repository, run `npm i`, and point your web root at the `public` folder.

While developing, run `npx mix watch` to compile assets as you edit them. When you're ready to commit, run `npx mix --production` to minify them for production.

## Changelog

| Version | Date | Changes |
| ------- | ----------- | ------------------------------------------------------------------------- |
| 1.4.1 | Sep 5, 2022 | Google Sheet support<br/>Better error handling<br/>Mobile layout tweaks |
| 1.4 | Sep 4, 2022 | Replaced `moment-timezone` with `luxon`<br/>Restyled in-progress meetings |
2 changes: 1 addition & 1 deletion e2e/tests/meeting-details.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test.describe('Meeting Details', () => {
: 'https://www.google.com/maps/dir/?api=1&destination=37.15777%2C-121.98421'
);

await expect(time).toHaveText('Thursday 7:30 PM – 8:30 PM');
await expect(time).toHaveText('Thursday 7:30 PM – 8:30 PM PDT');

await expect(await types.allTextContents()).toStrictEqual([
'Birthday',
Expand Down
1 change: 0 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ interface TSMLReactConfig {
columns: Array<
'time' | 'distance' | 'name' | 'location_group' | 'address' | 'region'
>;
timezone: string;
conference_providers: Record<string, string>;
defaults: {
distance: string[];
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tsml-ui",
"version": "1.4.1",
"version": "1.4.2",
"private": false,
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion public/app.js

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,16 @@ <h2 class="h4">Custom JSON feed</h2>
<a href="https://github.com/code4recovery/spec" target="_blank"
>Meeting Guide-spec JSON feed</a
>, say, from a custom database, you can plug that info in to the
format below. To enable maps, add a
format below. If your data has geographic coordinates, adding a
<a href="https://www.mapbox.com/" target="_blank"
>Mapbox API access token</a
>
(or remove that parameter). Be sure to add a
> will enable maps (otherwise remove that parameter). To specify a default timezone for your location, set a
<a
href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
target="_blank"
>proper time zone</a
>
for your location.
> in the <code>data-timezone</code> parameter. If you remove it, meeting times will
be translated into the user's timezone.
</p>
<pre class="rounded bg-dark text-light p-3 overflow-hidden">
&lt;div
Expand Down
15 changes: 15 additions & 0 deletions public/tests/timezones.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
</head>
<div
id="tsml-ui"
data-src="https://docs.google.com/spreadsheets/d/1ILXvx4P0YoceEYiLL-TghfpaQPnyESWHnbKqvn-eQbk/edit#gid=0"
data-google="AIzaSyCS9M8Dqk5cMFqA7xvUrQEzT1u5IvcbT7c"
></div>
<script src="/app.js" async></script>
</html>
22 changes: 5 additions & 17 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,15 @@ import ReactDOM from 'react-dom';
import { TsmlUI } from './components';

//locate element
let element = document.getElementById('tsml-ui');

//legacy support, can remove once sites have had a chance to migrate (implemented Jul 1 2021)
if (!element) {
[element] = document.getElementsByTagName(
'meetings'
) as unknown as HTMLElement[];
}
const element = document.getElementById('tsml-ui');

if (element) {
ReactDOM.render(
<TsmlUI
{...{
json: element.getAttribute('data-src') || element.getAttribute('src'),
mapbox:
element.getAttribute('data-mapbox') || element.getAttribute('mapbox'),
google:
element.getAttribute('data-google') || element.getAttribute('google'),
timezone:
element.getAttribute('data-timezone') || tsml_react_config?.timezone,
}}
google={element.getAttribute('data-google')}
mapbox={element.getAttribute('data-mapbox')}
src={element.getAttribute('data-src')}
timezone={element.getAttribute('data-timezone')}
/>,
element
);
Expand Down
37 changes: 29 additions & 8 deletions src/components/Meeting.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,6 @@ export default function Meeting({
//set page title
document.title = meeting.name;

//format time string (duration? or appointment?)
const timeString = meeting.start
? `${meeting.start.toFormat('cccc t')}${
meeting.end ? ` – ${meeting.end.toFormat('t')}` : ''
}`
: strings.appointment;

//feedback URL link
if (!meeting.feedback_url && feedback_emails.length) {
meeting.feedback_url = formatFeedbackEmail(
Expand Down Expand Up @@ -223,7 +216,18 @@ export default function Meeting({
<div className="list-group">
<div className="d-grid gap-2 list-group-item py-3">
<h2 className="h5">{strings.meeting_information}</h2>
<p>{timeString}</p>
<p>{formatTime(meeting.start, meeting.end)}</p>

{meeting.start && meeting.start.zoneName !== meeting.timezone && (
<p className="text-muted">
(
{formatTime(
meeting.start.setZone(meeting.timezone),
meeting.end.setZone(meeting.timezone)
)}
)
</p>
)}
{state.capabilities.type && meeting.types && (
<ul className="ms-4">
{meeting.types
Expand Down Expand Up @@ -459,6 +463,23 @@ function formatWeekdays(weekday, slug, state, setState) {
));
}

//format time string (duration? or appointment?)
function formatTime(start, end) {
if (!start) {
return strings.appointment;
}

if (end) {
if (start.weekday === end.weekday) {
return `${start.toFormat('cccc t')} – ${end.toFormat('t ZZZZ')}`;
}

return `${start.toFormat('cccc t')} – ${end.toFormat('cccc t ZZZZ')}`;
}

return start.toFormat('cccc t ZZZZ');
}

//add or remove an "edit meeting" link on WordPress
function wordPressEditLink(url) {
const adminBar = document.getElementById('wp-admin-bar-root-default');
Expand Down
18 changes: 9 additions & 9 deletions src/components/TsmlUI.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
translateGoogleSheet,
} from '../helpers';

export default function TsmlUI({ json, mapbox, google, timezone }) {
export default function TsmlUI({ src, mapbox, google, timezone }) {
const [state, setState] = useState({
capabilities: {
coordinates: false,
Expand Down Expand Up @@ -76,24 +76,24 @@ export default function TsmlUI({ json, mapbox, google, timezone }) {
};
}, []);

//load data once from json
//load data once
if (state.loading) {
console.log(
'TSML UI meeting finder: https://github.com/code4recovery/tsml-ui'
);

const input = getQueryString();

if (!json) {
if (!src) {
setState({
...state,
error: 'Configuration error: a data source must be specified.',
loading: false,
ready: true,
});
} else {
const sheetId = json.startsWith('https://docs.google.com/spreadsheets/d/')
? json.split('/')[5]
const sheetId = src.startsWith('https://docs.google.com/spreadsheets/d/')
? src.split('/')[5]
: undefined;

//google sheet
Expand All @@ -105,16 +105,16 @@ export default function TsmlUI({ json, mapbox, google, timezone }) {
loading: false,
});
}
json = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/A1:ZZ?key=${google}`;
src = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/A1:ZZ?key=${google}`;
}

//cache busting
if (json.endsWith('.json') && input.meeting) {
json = `${json}?${new Date().getTime()}`;
if (src.endsWith('.json') && input.meeting) {
src = `${src}?${new Date().getTime()}`;
}

//fetch json data file and build indexes
fetch(json)
fetch(src)
.then(res => (res.ok ? res.json() : Promise.reject(res.status)))
.then(data => {
if (sheetId) {
Expand Down
Loading