@autobusal/common 1.33.0 → 1.33.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.33.2
4
+
5
+ ### Added
6
+
7
+ - **`Meta` now defaults `og:image`/`twitter:image` to the label's own homepage hero photo** (`{settings.url}/home/background-light.jpg`) when a page doesn't pass its own `image`. Most of the ~136 call sites never had a share-preview image at all, so a link pasted into a chat assistant or social post previewed with nothing. A page with something more specific (an operator's logo, a blog post's own photo) still passes its own `image` and wins - this only fills the gap.
8
+
9
+ ## 1.33.1
10
+
11
+ ### Added
12
+
13
+ - **`Table` accepts an optional `onReorder(draggedId, targetId)` prop.** When set, every row becomes an HTML5 drag source/drop target (dims to 40% opacity while being dragged) and a completed drag reports the dragged row's id and the row it was dropped on - the caller decides what "reorder" means for its own data. Omitted by every existing table, so this is purely additive; first consumer is `@autobusal/admin-menu` 1.1.2.
3
14
 
4
15
  ## 1.33.0
5
16
 
package/Meta.tsx CHANGED
@@ -237,11 +237,38 @@ const useAlternates = (noIndex: boolean, explicitUrl?: string): { locale: string
237
237
  ];
238
238
  };
239
239
 
240
+ /**
241
+ * Edited: Ferjolt Ozuni - Date: 2026-08-11
242
+ *
243
+ * A default og:image/twitter:image for whichever of Meta's ~136 call
244
+ * sites never passes one - route detail, Help, blog index, and most
245
+ * static pages had NO share-preview image at all, so a link pasted into
246
+ * a chat assistant or social post previewed with nothing. Rather than
247
+ * hunt down every call site individually, the fallback lives here: the
248
+ * same hero photo the homepage itself uses (`home/background-light.jpg`,
249
+ * already uploaded per-label - see RoutesOrder.tsx's identical asset
250
+ * path), which every label already has. The DARK variant is deliberately
251
+ * not used here regardless of the visitor's own theme - a share preview
252
+ * is generated by whatever unpacks the URL server-side, not by this
253
+ * visitor's browser, so there is no "current theme" to match; picking
254
+ * one consistently is more correct than guessing.
255
+ *
256
+ * A page with something more specific to show (an operator's logo, a
257
+ * blog post's own photo) still passes its own `image` and wins - this
258
+ * only fills the gap when nothing more specific exists.
259
+ */
260
+ const useDefaultImage = (explicit?: string): (string | undefined) => {
261
+ const { data: settings } = useGetSettings();
262
+
263
+ return explicit ?? (settings ? `${ settings.url }/home/background-light.jpg` : undefined);
264
+ };
265
+
240
266
  const Meta = ({ title, keywords, description, image, url, type = 'website', noIndex = false, children }: Props): JSX.Element => {
241
267
  const fullTitle = withBrand(title);
242
268
  const canonicalUrl = useCanonicalUrl(url);
243
269
  const location = useLocation();
244
270
  const robots = noIndex ? 'noindex, nofollow' : 'index, follow';
271
+ const resolvedImage = useDefaultImage(image);
245
272
 
246
273
  const alternates = useAlternates(noIndex, url);
247
274
 
@@ -255,13 +282,13 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
255
282
  { selector: 'meta[property="og:type"]', attr: 'content', value: type },
256
283
  { selector: 'meta[property="og:title"]', attr: 'content', value: fullTitle },
257
284
  { selector: 'meta[property="og:description"]', attr: 'content', value: description },
258
- { selector: 'meta[property="og:image"]', attr: 'content', value: image },
285
+ { selector: 'meta[property="og:image"]', attr: 'content', value: resolvedImage },
259
286
  { selector: 'meta[property="og:url"]', attr: 'content', value: canonicalUrl },
260
287
  { selector: 'meta[property="og:site_name"]', attr: 'content', value: brand },
261
288
  { selector: 'meta[name="twitter:card"]', attr: 'content', value: 'summary_large_image' },
262
289
  { selector: 'meta[name="twitter:title"]', attr: 'content', value: fullTitle },
263
290
  { selector: 'meta[name="twitter:description"]', attr: 'content', value: description },
264
- { selector: 'meta[name="twitter:image"]', attr: 'content', value: image }
291
+ { selector: 'meta[name="twitter:image"]', attr: 'content', value: resolvedImage }
265
292
  ]);
266
293
  usePageviewTracking(location.pathname + location.search, fullTitle);
267
294
 
@@ -284,7 +311,7 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
284
311
  <meta property="og:type" content={ type } />
285
312
  <meta property="og:title" content={ fullTitle } />
286
313
  { description && <meta property="og:description" content={ description } /> }
287
- { image && <meta property="og:image" content={ image } /> }
314
+ { resolvedImage && <meta property="og:image" content={ resolvedImage } /> }
288
315
  <meta property="og:url" content={ canonicalUrl } />
289
316
  { brand && <meta property="og:site_name" content={ brand } /> }
290
317
 
@@ -292,7 +319,7 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
292
319
  <meta name="twitter:card" content="summary_large_image" />
293
320
  <meta name="twitter:title" content={ fullTitle } />
294
321
  { description && <meta name="twitter:description" content={ description } /> }
295
- { image && <meta name="twitter:image" content={ image } /> }
322
+ { resolvedImage && <meta name="twitter:image" content={ resolvedImage } /> }
296
323
 
297
324
  { children }
298
325
  </>
@@ -1,4 +1,5 @@
1
1
  import { TFunction } from 'i18next';
2
+ import { useState } from 'react';
2
3
  import { NavigateFunction } from 'react-router-dom';
3
4
  import Loading from './Loading';
4
5
  import NotFound from './NotFound';
@@ -18,9 +19,11 @@ interface Props {
18
19
  actions?: string[]
19
20
  handlers?: any
20
21
  navigate: NavigateFunction
22
+ onReorder?: (draggedId: number, targetId: number) => void
21
23
  }
22
24
 
23
- const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, actions, handlers, navigate }: Props): JSX.Element => {
25
+ const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, actions, handlers, navigate, onReorder }: Props): JSX.Element => {
26
+ const [ draggedId, setDraggedId ] = useState<number | null>(null);
24
27
  if (loading) {
25
28
  return <Loading length={ colLength } t={ t } />;
26
29
  }
@@ -43,8 +46,28 @@ const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, acti
43
46
  // table's list, which is what every existing caller relies on.
44
47
  const available = item.props.actions ?? actions;
45
48
 
49
+ const id = item.props.id;
50
+
46
51
  return (
47
- <Tr key={ index } $viewable={ isViewable } onClick={ () => onClick(item.props.id) }>
52
+ <Tr
53
+ key={ index }
54
+ $viewable={ isViewable }
55
+ $dragging={ onReorder !== undefined && draggedId === id }
56
+ draggable={ onReorder !== undefined }
57
+ onClick={ () => onClick(id) }
58
+ onDragStart={ onReorder && (() => setDraggedId(id)) }
59
+ onDragOver={ onReorder && (event => event.preventDefault()) }
60
+ onDragEnd={ onReorder && (() => setDraggedId(null)) }
61
+ onDrop={ onReorder && (event => {
62
+ event.preventDefault();
63
+
64
+ if (draggedId !== null && draggedId !== id) {
65
+ onReorder(draggedId, id);
66
+ }
67
+
68
+ setDraggedId(null);
69
+ }) }
70
+ >
48
71
  { item }
49
72
 
50
73
  { rowActions(available).length > 0 && (
@@ -19,11 +19,15 @@ export const Tbody = styled.tbody<{ $fetching: boolean }>`
19
19
  ` }
20
20
  `;
21
21
 
22
- export const Tr = styled.tr<{ $viewable?: boolean }>`
22
+ export const Tr = styled.tr<{ $viewable?: boolean, $dragging?: boolean }>`
23
23
  ${ props => props.$viewable && css`
24
24
  cursor: pointer;
25
25
  ` }
26
26
 
27
+ ${ props => props.$dragging && css`
28
+ opacity: .4;
29
+ ` }
30
+
27
31
  text-align: center;
28
32
 
29
33
  & > td:first-of-type {
package/Table/Table.tsx CHANGED
@@ -33,9 +33,18 @@ interface Props {
33
33
  */
34
34
  empty?: string
35
35
  handlers?: any
36
+ /*
37
+ * Edited: Ferjolt Ozuni - Date: 2026-08-10
38
+ * Turns every row into an HTML5 drag source/drop target and reports a
39
+ * completed drag as (the dragged row's id, the row it was dropped on).
40
+ * The caller decides what "reorder" means for its own data (e.g.
41
+ * recomputing a full id list to persist) - Table only reports the drag.
42
+ * Omitted by every other table, so this is purely additive.
43
+ */
44
+ onReorder?: (draggedId: number, targetId: number) => void
36
45
  }
37
46
 
38
- const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, fetching, t, actions, extra, empty, handlers }: Props): JSX.Element => {
47
+ const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, fetching, t, actions, extra, empty, handlers, onReorder }: Props): JSX.Element => {
39
48
  const navigate = useNavigate();
40
49
 
41
50
  const colLength = columns.length + 1;
@@ -75,6 +84,7 @@ const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, f
75
84
  actions={ actions }
76
85
  handlers={ handlers }
77
86
  navigate={ navigate }
87
+ onReorder={ onReorder }
78
88
  />
79
89
 
80
90
  <Paginate data={ pages } colLength={ colLength } t={ t } />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.33.0",
3
+ "version": "1.33.2",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"