@autobusal/common 1.33.5 → 1.33.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AiAssist/AiReviewModal.tsx +14 -3
- package/CHANGELOG.md +20 -0
- package/Calendar/Calendar.tsx +8 -2
- package/Drive/Drive.tsx +3 -2
- package/Map/Tiles.tsx +56 -0
- package/Password/Password.tsx +11 -2
- package/Required/Required.tsx +10 -2
- package/Required/styles.ts +8 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { TFunction } from 'i18next';
|
|
3
|
+
import { sanitizeHtml } from '@autobusal/utilities';
|
|
3
4
|
import Modal from '../Modal/Modal';
|
|
4
5
|
import { useAiAvailable, useAiGenerate, useAiTranslate, useAiImprove } from './services';
|
|
5
6
|
import { Row, Field, Compare, Pane, Draft, Actions } from './styles';
|
|
@@ -146,11 +147,21 @@ const AiReviewModal = ({ mode, text, targetLocale, sourceLocale, onInsert, onClo
|
|
|
146
147
|
</Row>
|
|
147
148
|
) }
|
|
148
149
|
|
|
150
|
+
{ /* Edited: Claude - Date: 2026-08-19
|
|
151
|
+
Both panes are sanitized, for two different reasons. The draft is
|
|
152
|
+
whatever a model wrote: a prompt-injected source document can talk
|
|
153
|
+
it into emitting markup, and this preview renders that straight
|
|
154
|
+
into an admin's browser BEFORE anyone has decided to keep it. The
|
|
155
|
+
source is the same admin-authored CMS HTML every other sink in this
|
|
156
|
+
codebase runs through sanitizeHtml (page/Page.tsx, blog, policies).
|
|
157
|
+
Only the preview is sanitized - Insert still hands the caller the
|
|
158
|
+
raw draft, so nothing the admin actually meant to save is silently
|
|
159
|
+
rewritten on its way into the field. */ }
|
|
149
160
|
{ draft === null && mode === 'translate' && (
|
|
150
161
|
<Pane>
|
|
151
162
|
<h4>{ tc('ai_assist.source') }</h4>
|
|
152
163
|
|
|
153
|
-
<div dangerouslySetInnerHTML={ { __html: text
|
|
164
|
+
<div dangerouslySetInnerHTML={ { __html: sanitizeHtml(text) } } />
|
|
154
165
|
</Pane>
|
|
155
166
|
) }
|
|
156
167
|
|
|
@@ -159,13 +170,13 @@ const AiReviewModal = ({ mode, text, targetLocale, sourceLocale, onInsert, onClo
|
|
|
159
170
|
<Pane>
|
|
160
171
|
<h4>{ tc('ai_assist.source') }</h4>
|
|
161
172
|
|
|
162
|
-
<div dangerouslySetInnerHTML={ { __html: text
|
|
173
|
+
<div dangerouslySetInnerHTML={ { __html: sanitizeHtml(text) } } />
|
|
163
174
|
</Pane>
|
|
164
175
|
|
|
165
176
|
<Pane>
|
|
166
177
|
<h4>{ tc('ai_assist.draft') }</h4>
|
|
167
178
|
|
|
168
|
-
<div dangerouslySetInnerHTML={ { __html: draft } } />
|
|
179
|
+
<div dangerouslySetInnerHTML={ { __html: sanitizeHtml(draft) } } />
|
|
169
180
|
</Pane>
|
|
170
181
|
</Compare>
|
|
171
182
|
) }
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.33.7
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`Required` renders a real `<label>`, and takes an optional `htmlFor`.** It was a `styled.span` sitting beside the field it named, which is a label to a sighted reader and nothing at all to a screen reader - every field it introduced announced as a bare "edit text". Nothing in the stylesheets selects `label` and a flex item computes identically either way, so this is semantics only.
|
|
8
|
+
- **`Password` and `Calendar` take an optional `id`.** A caller cannot associate a visible label with a field it has no way to name, so the shared inputs had to be able to accept one before the forms using them could be fixed.
|
|
9
|
+
- `AiAssist/AiReviewModal` runs its three HTML panes through `sanitizeHtml`. Everything the model returns is HTML rendered into an admin's browser, and every other sink in the codebase sanitises; this one did not.
|
|
10
|
+
|
|
11
|
+
## 1.33.6
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **New `Map/Tiles` component - the one place the basemap comes from.** The same `TileLayer` line was copy-pasted into `Drive.tsx`, `@autobusal/map`'s `Display.tsx` and magus's own `RouteMap.tsx`, with a fourth copy in the mobile app, all hardcoding `tile.openstreetmap.org`. OSM's Tile Usage Policy does not cover commercial or heavy application use, and those servers throttle or block by referer and volume without notice - so the exposure was never the bill, it was that reacting to a block meant editing three repos, publishing to npm, redeploying both frontends and shipping a mobile store release.
|
|
16
|
+
|
|
17
|
+
The URL now comes from `/settings/get` (`map.tile_url`) rather than the build, so all four clients switch from one server env var. `null` - the state until a provider is chosen - means the built-in OpenStreetMap default, so this changes nothing for anyone on upgrade. Attribution is rendered by the component rather than travelling with the URL, since ODbL requires it whoever serves the tiles.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `Drive.tsx` renders `<Tiles />` instead of its own `TileLayer`.
|
|
22
|
+
|
|
3
23
|
## 1.33.4
|
|
4
24
|
|
|
5
25
|
### Added
|
package/Calendar/Calendar.tsx
CHANGED
|
@@ -15,6 +15,12 @@ interface Props {
|
|
|
15
15
|
on the search form, which is the site's primary conversion path.
|
|
16
16
|
Optional, so existing callers are unaffected. */
|
|
17
17
|
label?: string
|
|
18
|
+
/* Edited: Claude - Date: 2026-08-19: id for the visible input, so a
|
|
19
|
+
caller that HAS a visible label can associate it properly instead of
|
|
20
|
+
duplicating the text into aria-label. Preferred over `label` where
|
|
21
|
+
both are possible - the association also makes the label a click
|
|
22
|
+
target, which opens the picker. */
|
|
23
|
+
id?: string
|
|
18
24
|
defaultValue?: string
|
|
19
25
|
t: TFunction<'general'>
|
|
20
26
|
validation?: string
|
|
@@ -36,7 +42,7 @@ interface Props {
|
|
|
36
42
|
onChange?: (value: string) => void
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
const Calendar = ({ type, name, label, defaultValue, t, validation, minDate, refs, onUpdate, onChange }: Props): JSX.Element => {
|
|
45
|
+
const Calendar = ({ type, name, label, id, defaultValue, t, validation, minDate, refs, onUpdate, onChange }: Props): JSX.Element => {
|
|
40
46
|
const [ show, setShow ] = useState<boolean>(false);
|
|
41
47
|
|
|
42
48
|
/**
|
|
@@ -75,7 +81,7 @@ const Calendar = ({ type, name, label, defaultValue, t, validation, minDate, ref
|
|
|
75
81
|
|
|
76
82
|
return (
|
|
77
83
|
<Container ref={ ref }>
|
|
78
|
-
<input type="text" aria-label={ label } autoComplete="off" readOnly={ true } value={ prepared } onClick={ () => setShow(true) } />
|
|
84
|
+
<input type="text" id={ id } aria-label={ label } autoComplete="off" readOnly={ true } value={ prepared } onClick={ () => setShow(true) } />
|
|
79
85
|
|
|
80
86
|
{ show && (
|
|
81
87
|
<Picker
|
package/Drive/Drive.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TFunction } from 'i18next';
|
|
2
|
-
import { MapContainer,
|
|
2
|
+
import { MapContainer, Marker, Popup, Polyline } from 'react-leaflet';
|
|
3
3
|
import { latLngBounds } from 'leaflet';
|
|
4
4
|
import { getCoordinates, getPosition, hasPosition, autobusMarker } from './utilities';
|
|
5
|
+
import Tiles from '../Map/Tiles';
|
|
5
6
|
import { Container, Loading, Disclaimer } from './styles';
|
|
6
7
|
import { LocationData } from '@autobusal/providers/types/locations';
|
|
7
8
|
import { useGetDriveData } from './services';
|
|
@@ -79,7 +80,7 @@ const Drive = ({ locations, t }: Props): JSX.Element => {
|
|
|
79
80
|
maxZoom={ 15 }
|
|
80
81
|
scrollWheelZoom={ true }
|
|
81
82
|
>
|
|
82
|
-
<
|
|
83
|
+
<Tiles />
|
|
83
84
|
|
|
84
85
|
{ markers }
|
|
85
86
|
|
package/Map/Tiles.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TileLayer } from 'react-leaflet';
|
|
2
|
+
import { useGetSettings } from '@autobusal/providers/services';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The one place the basemap comes from.
|
|
6
|
+
*
|
|
7
|
+
* Added: Claude (audit) - Date: 2026-08-17
|
|
8
|
+
*
|
|
9
|
+
* The same TileLayer line was copy-pasted into common's Drive.tsx and map's
|
|
10
|
+
* Display.tsx, with a third copy in the mobile app's Leaflet HTML. All three
|
|
11
|
+
* hardcoded `tile.openstreetmap.org`, whose Tile Usage Policy does not cover
|
|
12
|
+
* commercial or heavy application use - those servers are donation funded for
|
|
13
|
+
* OSM's own purposes and they throttle or block by referer and volume, without
|
|
14
|
+
* notice.
|
|
15
|
+
*
|
|
16
|
+
* The exposure was never the bill, it was how long a switch would take. With
|
|
17
|
+
* the URL inlined in a published package, reacting to a block meant editing
|
|
18
|
+
* three repos, publishing to npm, redeploying both frontends AND shipping a
|
|
19
|
+
* mobile store release - days of broken maps, the last stretch of it waiting
|
|
20
|
+
* on app review.
|
|
21
|
+
*
|
|
22
|
+
* SO THE URL COMES FROM /settings/get, NOT FROM THE BUILD. That is the mobile
|
|
23
|
+
* app's constraint (a compiled-in URL there needs a store release to change),
|
|
24
|
+
* and following it here means all three clients switch from ONE place - a
|
|
25
|
+
* server env var and a cache flush - rather than each on its own release
|
|
26
|
+
* cadence. `useGetSettings` is already resolved at bootstrap with
|
|
27
|
+
* `staleTime: Infinity`, so this reads the warm cache and costs no request.
|
|
28
|
+
*
|
|
29
|
+
* `tile_url` is null until a provider is actually chosen, which means the
|
|
30
|
+
* OpenStreetMap default below - so this lands with nothing changing anywhere
|
|
31
|
+
* and waits on no decision.
|
|
32
|
+
*
|
|
33
|
+
* ATTRIBUTION IS NOT PART OF THE SWITCH. ODbL requires crediting OpenStreetMap
|
|
34
|
+
* for the underlying data whoever serves the tiles, so it is rendered here
|
|
35
|
+
* rather than travelling with the URL - a provider swap must not be able to
|
|
36
|
+
* drop it. A provider needing its own credit too should have it appended, not
|
|
37
|
+
* substituted.
|
|
38
|
+
*/
|
|
39
|
+
const OPENSTREETMAP = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
|
40
|
+
|
|
41
|
+
const ATTRIBUTION = "© <a href='http://osm.org/copyright'>OpenStreetMap</a> contributors";
|
|
42
|
+
|
|
43
|
+
interface Props {
|
|
44
|
+
/** Overrides the configured basemap. Rarely needed - prefer the server setting. */
|
|
45
|
+
url?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Tiles = ({ url }: Props): JSX.Element => {
|
|
49
|
+
const { data: settings } = useGetSettings();
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<TileLayer attribution={ ATTRIBUTION } url={ url ?? settings.map?.tile_url ?? OPENSTREETMAP } />
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default Tiles;
|
package/Password/Password.tsx
CHANGED
|
@@ -7,6 +7,15 @@ import { Container } from './styles';
|
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
name: string
|
|
10
|
+
/**
|
|
11
|
+
* Edited: Claude - Date: 2026-08-19
|
|
12
|
+
* The id to put on the inner input, so a caller's <label htmlFor> can
|
|
13
|
+
* reach it. Optional, because the field's own `name` is what the form
|
|
14
|
+
* payload uses and every existing caller relies on that unchanged - but
|
|
15
|
+
* without an id there is no way to attach a label from outside, and this
|
|
16
|
+
* component owns the only input on the login and reset forms.
|
|
17
|
+
*/
|
|
18
|
+
id?: string
|
|
10
19
|
tabIndex?: number
|
|
11
20
|
validation?: string
|
|
12
21
|
// Pre-fill value (masked). Lets an admin form show a saved secret as dots
|
|
@@ -19,7 +28,7 @@ interface Props {
|
|
|
19
28
|
getValues?: UseFormGetValues<any>
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
const Password = ({ name, tabIndex, validation, defaultValue, t, refs, getValues }: Props): JSX.Element => {
|
|
31
|
+
const Password = ({ name, id, tabIndex, validation, defaultValue, t, refs, getValues }: Props): JSX.Element => {
|
|
23
32
|
const [ show, setShow ] = useState<boolean>(false);
|
|
24
33
|
|
|
25
34
|
const toggle = (): void => {
|
|
@@ -28,7 +37,7 @@ const Password = ({ name, tabIndex, validation, defaultValue, t, refs, getValues
|
|
|
28
37
|
|
|
29
38
|
return (
|
|
30
39
|
<Container>
|
|
31
|
-
<input type={ show ? 'text' : 'password' } tabIndex={ tabIndex } defaultValue={ defaultValue } { ...refs(name, Validate(validation ?? '', t, getValues)) } />
|
|
40
|
+
<input type={ show ? 'text' : 'password' } id={ id } tabIndex={ tabIndex } defaultValue={ defaultValue } { ...refs(name, Validate(validation ?? '', t, getValues)) } />
|
|
32
41
|
|
|
33
42
|
{ show
|
|
34
43
|
? <AiFillEye onClick={ toggle } />
|
package/Required/Required.tsx
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { Label, Marker } from './styles';
|
|
2
2
|
|
|
3
3
|
interface Props {
|
|
4
|
+
/**
|
|
5
|
+
* Edited: Claude - Date: 2026-08-19
|
|
6
|
+
* The id of the field this labels. Optional so existing callers keep
|
|
7
|
+
* working, but pass it wherever there is a field to point at: without it
|
|
8
|
+
* the visible text is only visually a label, and the input announces as
|
|
9
|
+
* a bare "edit text".
|
|
10
|
+
*/
|
|
11
|
+
htmlFor?: string
|
|
4
12
|
children: React.ReactNode
|
|
5
13
|
}
|
|
6
14
|
|
|
@@ -25,8 +33,8 @@ interface Props {
|
|
|
25
33
|
* asterisk two separate flex items - and the marker dropped onto its own
|
|
26
34
|
* line under the label.
|
|
27
35
|
*/
|
|
28
|
-
const Required = ({ children }: Props): JSX.Element => (
|
|
29
|
-
<Label>
|
|
36
|
+
const Required = ({ htmlFor, children }: Props): JSX.Element => (
|
|
37
|
+
<Label htmlFor={ htmlFor }>
|
|
30
38
|
{ children }
|
|
31
39
|
<Marker aria-hidden="true">*</Marker>
|
|
32
40
|
</Label>
|
package/Required/styles.ts
CHANGED
|
@@ -2,7 +2,14 @@ import styled from 'styled-components';
|
|
|
2
2
|
|
|
3
3
|
// keeps the label and its asterisk as ONE flex item - the form rows are
|
|
4
4
|
// column flex containers, so two items would stack vertically
|
|
5
|
-
|
|
5
|
+
//
|
|
6
|
+
// Edited: Claude - Date: 2026-08-19
|
|
7
|
+
// A real <label>, not a <span>. It was already named Label and already
|
|
8
|
+
// sitting next to the field it names, but a span cannot be associated with
|
|
9
|
+
// an input - so the field reached a screen reader unnamed. Nothing in the
|
|
10
|
+
// stylesheets selects `label`, and a flex item computes the same either
|
|
11
|
+
// way, so this changes semantics only.
|
|
12
|
+
export const Label = styled.label`
|
|
6
13
|
display: inline-flex;
|
|
7
14
|
align-items: baseline;
|
|
8
15
|
`;
|