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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ To use TSML UI on your website you only need to add some HTML to your web page.

You don't need to do anything other than enable HTTPS on your website. To ensure all users see this functionality, make sure that anyone who enters a `http://` address for your site is redirected to the `https://` address.

### Enable "pretty" permalinks

12 Step Meeting List sites have this enabled by default when they have a permalink structure like `/%postname%`. If you are not using 12 Step Meeting List and still want this functionality, this can be achieved by resolving your meeting detail pages to the index page. On a WordPress website this can be achieved by adding this PHP code to your theme's functions.php:

```php
add_action('init', function () {
add_rewrite_rule('^meetings/(.*)?', 'index.php?pagename=meetings', 'top');
});
```

Then add this parameter to your embed code: `data-path="/meetings"`.

### Add custom types

Here is an example of extending the `tsml_react_config` object to include a definition for an additional meeting type.
Expand Down
4 changes: 2 additions & 2 deletions public/app.js

Large diffs are not rendered by default.

40 changes: 37 additions & 3 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import {
RouteObject,
RouterProvider,
createBrowserRouter,
createHashRouter,
useRouteError,
} from 'react-router-dom';

import { Global } from '@emotion/react';
import { TsmlUI } from './components';
import { Index, Meeting, TsmlUI } from './components';
import { errorCss, globalCss } from './styles';

// locate element
Expand All @@ -17,12 +18,13 @@ export let routes: RouteObject[] = [];
export let router: ReturnType<typeof createBrowserRouter>;

if (element) {
const router = createBrowserRouter([
const routes = [
{
path: '/*',
element: (
<TsmlUI
google={element.getAttribute('data-google') || undefined}
// eslint-disable-next-line no-undef
settings={
typeof tsml_react_config === 'undefined'
? undefined
Expand All @@ -34,8 +36,40 @@ if (element) {
/>
),
errorElement: <ErrorBoundary />,
children: [
{
index: true,
element: <Index />,
},
{
path: ':slug',
element: <Meeting />,
},
],
},
]);
];

const basename = element.getAttribute('data-path');

// if landing on the page with the meeting param, redirect to that meeting
const params = new URLSearchParams(window.location.search);
const meeting = params.get('meeting');
if (meeting) {
params.delete('meeting');
const query = params.toString();

const path = basename
? `/${basename.replace(/^\//, '').replace(/\/$/, '')}/${meeting}${
query ? `?${query}` : ''
}`
: `${window.location.pathname}#/${meeting}${query ? `?${query}` : ''}`;

window.history.replaceState({}, '', path);
}

const router = basename
? createBrowserRouter(routes, { basename })
: createHashRouter(routes);

createRoot(element).render(<RouterProvider router={router} />);
} else {
Expand Down
5 changes: 4 additions & 1 deletion src/components/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export default function Link({ meeting }: { meeting: Meeting }) {

return (
<>
<RouterLink to={formatUrl({ ...input, meeting: meeting.slug }, settings)}>
<RouterLink
to={formatUrl({ ...input, meeting: meeting.slug }, settings)}
onClick={e => e.stopPropagation()}
>
{meeting.name}
</RouterLink>
{flags && <small>{flags}</small>}
Expand Down
93 changes: 52 additions & 41 deletions src/components/Meeting.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';

import { DateTime, Info } from 'luxon';
import { Link as RouterLink } from 'react-router-dom';
import { Link as RouterLink, useParams } from 'react-router-dom';

import {
formatDirectionsUrl,
Expand All @@ -27,15 +27,63 @@ import Map from './Map';

import { useData, useInput, useSettings } from '../hooks';
import type { Meeting as MeetingType } from '../types';
import Loading from './Loading';

export default function Meeting() {
const { slug } = useParams();

const { capabilities, meetings, waitingForData } = useData();

const meeting = meetings[slug as string];

export default function Meeting({ meeting }: { meeting: MeetingType }) {
const { settings, strings } = useSettings();
const { input } = useInput();

// open types
const [define, setDefine] = useState<string | undefined>();

const { capabilities, meetings } = useData();
// scroll to top when you navigate to this page
useEffect(() => {
const el = document.getElementById('tsml-ui');
if (el) {
const headerHeight = Math.max(
0,
...[
...Array.prototype.slice.call(
document.body.getElementsByTagName('*')
),
]
.filter(
x =>
getComputedStyle(x, null).getPropertyValue('position') ===
'fixed' && x.offsetTop < 100
)
.map(x => x.offsetTop + x.offsetHeight)
);
if (headerHeight) {
el.style.scrollMarginTop = `${headerHeight}px`;
}
el.scrollIntoView();
}

document.getElementById('tsml-title')?.focus();

// log edit_url
if (meeting?.edit_url) {
console.log(`TSML UI edit ${meeting.name}: ${meeting.edit_url}`);
wordPressEditLink(meeting.edit_url);
}

return () => {
wordPressEditLink();
};
}, [meeting]);

if (waitingForData) {
return <Loading />;
} else if (!meeting) {
throw new Error('Meeting not found');
}

const sharePayload = {
title: meeting.name,
Expand Down Expand Up @@ -81,43 +129,6 @@ export default function Meeting({ meeting }: { meeting: MeetingType }) {
return start.toFormat('cccc t ZZZZ');
};

// scroll to top when you navigate to this page
useEffect(() => {
const el = document.getElementById('tsml-ui');
if (el) {
const headerHeight = Math.max(
0,
...[
...Array.prototype.slice.call(
document.body.getElementsByTagName('*')
),
]
.filter(
x =>
getComputedStyle(x, null).getPropertyValue('position') ===
'fixed' && x.offsetTop < 100
)
.map(x => x.offsetTop + x.offsetHeight)
);
if (headerHeight) {
el.style.scrollMarginTop = `${headerHeight}px`;
}
el.scrollIntoView();
}

document.getElementById('tsml-title')?.focus();

// log edit_url
if (meeting.edit_url) {
console.log(`TSML UI edit ${meeting.name}: ${meeting.edit_url}`);
wordPressEditLink(meeting.edit_url);
}

return () => {
wordPressEditLink();
};
}, [meeting]);

// directions URL link
const directionsUrl = meeting.isInPerson
? formatDirectionsUrl(meeting)
Expand Down Expand Up @@ -255,7 +266,7 @@ export default function Meeting({ meeting }: { meeting: MeetingType }) {
return (
<div css={meetingCss}>
<h1 id="tsml-title" tabIndex={-1}>
<Link meeting={meeting} />
{meeting.name}
</h1>
<div css={meetingBackCss}>
<Icon icon="back" />
Expand Down
20 changes: 4 additions & 16 deletions src/components/TsmlUI.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect } from 'react';

import { Global } from '@emotion/react';
import { Outlet } from 'react-router-dom';

import {
DataProvider,
Expand All @@ -9,21 +10,11 @@ import {
InputProvider,
SettingsProvider,
useData,
useFilter,
useInput,
} from '../hooks';
import { globalCss } from '../styles';

import {
Alert,
Controls,
DynamicHeight,
Loading,
Map,
Meeting,
Table,
Title,
} from './';
import { Alert, Controls, DynamicHeight, Loading, Map, Table, Title } from './';

export default function TsmlUI({
google,
Expand Down Expand Up @@ -56,7 +47,7 @@ export default function TsmlUI({
<FilterProvider>
<Global styles={globalCss} />
<DynamicHeight>
<Content />
<Outlet />
</DynamicHeight>
</FilterProvider>
</DataProvider>
Expand All @@ -66,14 +57,11 @@ export default function TsmlUI({
);
}

const Content = () => {
export const Index = () => {
const { waitingForData } = useData();
const { meeting } = useFilter();
const { input, waitingForInput } = useInput();
return waitingForData ? (
<Loading />
) : meeting ? (
<Meeting meeting={meeting} />
) : (
<>
<Title />
Expand Down
2 changes: 1 addition & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export { default as Map } from './Map';
export { default as Meeting } from './Meeting';
export { default as Table } from './Table';
export { default as Title } from './Title';
export { default as TsmlUI } from './TsmlUI';
export { Index, default as TsmlUI } from './TsmlUI';
7 changes: 4 additions & 3 deletions src/helpers/format-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export function formatUrl(
) {
const query = {};

const path = input.meeting ?? '';
delete input.meeting;

// region, time, type, and weekday
settings.filters
.filter(filter => typeof input[filter] !== 'undefined')
Expand Down Expand Up @@ -39,7 +42,5 @@ export function formatUrl(

const base = includeDomain ? `${window.location.origin}` : '';

const [path] = window.location.pathname.split('?');

return `${base}${path}${queryString.length ? `?${queryString}` : ''}`;
return `${base}/${path}${queryString.length ? `?${queryString}` : ''}`;
}
Loading