@mxfb/tools 0.3.0 → 1.0.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/TODO.md +1 -0
- package/agnostic/@publisher/index.d.ts +47 -0
- package/agnostic/@publisher/index.js +361 -0
- package/agnostic/elo/index.d.ts +23 -0
- package/agnostic/elo/index.js +28 -0
- package/agnostic/elo/index.test.d.ts +1 -0
- package/agnostic/elo/index.test.js +67 -0
- package/agnostic/index.d.ts +5 -4
- package/agnostic/index.js +5 -0
- package/agnostic/poetry/generate-rhymes/index.d.ts +1 -0
- package/agnostic/poetry/generate-rhymes/index.js +125 -0
- package/agnostic/poetry/index.d.ts +1 -0
- package/agnostic/poetry/index.js +1 -0
- package/agnostic/scrapers/@meteociel/index.d.ts +36 -0
- package/agnostic/scrapers/@meteociel/index.js +248 -0
- package/agnostic/scrapers/@meteociel/index.test.d.ts +1 -0
- package/agnostic/scrapers/@meteociel/index.test.js +43 -0
- package/agnostic/scrapers/@meteociel/locations/index.d.ts +11 -0
- package/agnostic/scrapers/@meteociel/locations/index.js +10 -0
- package/agnostic/scrapers/@meteociel/types.d.ts +1 -0
- package/agnostic/scrapers/@meteociel/types.js +1 -0
- package/agnostic/scrapers/@tumblr/blogs/delabienbonnemusique/index.d.ts +22 -0
- package/agnostic/scrapers/@tumblr/blogs/delabienbonnemusique/index.js +124 -0
- package/agnostic/scrapers/@tumblr/blogs/delabienbonnemusique/index.test.d.ts +1 -0
- package/agnostic/scrapers/@tumblr/blogs/delabienbonnemusique/index.test.js +107 -0
- package/agnostic/scrapers/@tumblr/blogs/index.d.ts +8 -0
- package/agnostic/scrapers/@tumblr/blogs/index.js +23 -0
- package/agnostic/scrapers/@tumblr/index.d.ts +73 -0
- package/agnostic/scrapers/@tumblr/index.js +180 -0
- package/agnostic/scrapers/@tumblr/index.test.d.ts +1 -0
- package/agnostic/scrapers/@tumblr/index.test.js +319 -0
- package/agnostic/scrapers/@tumblr/types.d.ts +238 -0
- package/agnostic/scrapers/@tumblr/types.js +38 -0
- package/agnostic/scrapers/@tumblr/utils.d.ts +12 -0
- package/agnostic/scrapers/@tumblr/utils.js +407 -0
- package/agnostic/scrapers/index.d.ts +2 -0
- package/agnostic/scrapers/index.js +2 -0
- package/agnostic/weather/index.d.ts +1 -0
- package/agnostic/weather/index.js +1 -0
- package/agnostic/weather/types.d.ts +181 -0
- package/agnostic/weather/types.js +8 -0
- package/components/index.d.ts +1 -1
- package/components/index.js +1 -0
- package/components/public-classnames.d.ts +5 -0
- package/components/public-classnames.js +5 -0
- package/components/utils/index.d.ts +1 -0
- package/components/utils/index.js +12 -0
- package/components/utils/types.d.ts +3 -0
- package/components/utils/types.js +1 -0
- package/index.d.ts +3 -3
- package/index.js +3 -0
- package/node/headless-browser/index.d.ts +37 -0
- package/node/headless-browser/index.js +126 -0
- package/node/headless-browser/types.d.ts +47 -0
- package/node/headless-browser/types.js +5 -0
- package/node/index.d.ts +1 -2
- package/node/index.js +1 -0
- package/node/index.test.d.ts +1 -0
- package/node/index.test.js +6 -0
- package/node/scrapers/@tumblr/index.d.ts +61 -0
- package/node/scrapers/@tumblr/index.js +182 -0
- package/node/scrapers/@tumblr/index.test.d.ts +1 -0
- package/node/scrapers/@tumblr/index.test.js +185 -0
- package/node/scrapers/@tumblr/types.d.ts +91 -0
- package/node/scrapers/@tumblr/types.js +1 -0
- package/node/scrapers/@tumblr/utils.d.ts +23 -0
- package/node/scrapers/@tumblr/utils.js +328 -0
- package/node/scrapers/index.d.ts +1 -0
- package/node/scrapers/index.js +1 -0
- package/package.json +211 -14
- package/agnostic/_WIP_tennis/index.d.ts +0 -2
- package/agnostic/_WIP_tennis/index.js +0 -62
- package/agnostic/arrays/index.d.ts +0 -11
- package/agnostic/arrays/is-array-of/index.d.ts +0 -5
- package/agnostic/arrays/is-array-of/index.js +0 -23
- package/agnostic/async/index.d.ts +0 -8
- package/agnostic/async/index.js +0 -20
- package/agnostic/async/timeout-call/index.d.ts +0 -1
- package/agnostic/async/timeout-call/index.js +0 -6
- package/agnostic/async/transitions/index.d.ts +0 -41
- package/agnostic/async/transitions/index.js +0 -7
- package/agnostic/async/wait/index.d.ts +0 -1
- package/agnostic/async/wait/index.js +0 -6
- package/agnostic/booleans/index.d.ts +0 -8
- package/agnostic/booleans/index.js +0 -17
- package/agnostic/booleans/is-falsy/index.d.ts +0 -7
- package/agnostic/booleans/is-falsy/index.js +0 -11
- package/agnostic/css/bem/BEM.d.ts +0 -29
- package/agnostic/css/bem/getNamesArr.d.ts +0 -2
- package/agnostic/css/bem/index.d.ts +0 -7
- package/agnostic/css/bem/index.js +0 -9
- package/agnostic/css/generate-nice-color/index.d.ts +0 -2
- package/agnostic/css/generate-nice-color/index.js +0 -8
- package/agnostic/css/index.d.ts +0 -14
- package/agnostic/css/index.js +0 -177
- package/agnostic/css/is-valid-css-class-name/index.d.ts +0 -2
- package/agnostic/css/is-valid-css-class-name/index.js +0 -8
- package/agnostic/css/styles-set/index.d.ts +0 -37
- package/agnostic/errors/index.d.ts +0 -30
- package/agnostic/errors/index.js +0 -6
- package/agnostic/html/get-node-ancestors/index.d.ts +0 -1
- package/agnostic/html/get-node-ancestors/index.js +0 -6
- package/agnostic/html/get-position-inside-parent/index.d.ts +0 -1
- package/agnostic/html/get-position-inside-parent/index.js +0 -6
- package/agnostic/html/index.d.ts +0 -17
- package/agnostic/html/index.js +0 -39
- package/agnostic/html/insert-node/index.d.ts +0 -2
- package/agnostic/html/insert-node/index.js +0 -6
- package/agnostic/html/placeholders/index.d.ts +0 -9
- package/agnostic/html/placeholders/index.js +0 -6
- package/agnostic/html/sanitize/index.d.ts +0 -24
- package/agnostic/html/sanitize/index.js +0 -9
- package/agnostic/html/selector-to-element/index.d.ts +0 -1
- package/agnostic/html/selector-to-element/index.js +0 -9
- package/agnostic/html/string-to-nodes/index.d.ts +0 -7
- package/agnostic/html/string-to-nodes/index.js +0 -10
- package/agnostic/misc/assert/index.d.ts +0 -5
- package/agnostic/misc/assert/index.js +0 -6
- package/agnostic/misc/cast/index.d.ts +0 -9
- package/agnostic/misc/cast/index.js +0 -8
- package/agnostic/misc/crossenv/index.d.ts +0 -13
- package/agnostic/misc/crossenv/index.js +0 -6
- package/agnostic/misc/elo/index.d.ts +0 -31
- package/agnostic/misc/elo/index.js +0 -6
- package/agnostic/misc/get-current-downlink/index.d.ts +0 -1
- package/agnostic/misc/get-current-downlink/index.js +0 -6
- package/agnostic/misc/index.d.ts +0 -6
- package/agnostic/misc/index.js +0 -16
- package/agnostic/misc/is-constructor-function/index.d.ts +0 -2
- package/agnostic/misc/is-constructor-function/index.js +0 -6
- package/agnostic/misc/is-nullish/index.d.ts +0 -4
- package/agnostic/misc/is-nullish/index.js +0 -10
- package/agnostic/misc/logs/index.d.ts +0 -55
- package/agnostic/misc/logs/index.js +0 -6
- package/agnostic/misc/random/index.d.ts +0 -10
- package/agnostic/misc/random/index.js +0 -6
- package/agnostic/misc/tennis/index.d.ts +0 -60
- package/agnostic/misc/tennis/index.js +0 -6
- package/agnostic/numbers/absolute-modulo/index.d.ts +0 -1
- package/agnostic/numbers/absolute-modulo/index.js +0 -6
- package/agnostic/numbers/clamp/index.d.ts +0 -1
- package/agnostic/numbers/clamp/index.js +0 -6
- package/agnostic/numbers/index.d.ts +0 -13
- package/agnostic/numbers/index.js +0 -30
- package/agnostic/numbers/interpolate/index.d.ts +0 -1
- package/agnostic/numbers/interpolate/index.js +0 -6
- package/agnostic/numbers/responsive-harmonics/index.d.ts +0 -10
- package/agnostic/numbers/responsive-harmonics/index.js +0 -9
- package/agnostic/numbers/round/index.d.ts +0 -1
- package/agnostic/numbers/round/index.js +0 -6
- package/agnostic/objects/enums/index.d.ts +0 -4
- package/agnostic/objects/enums/index.js +0 -7
- package/agnostic/objects/enums/is-in-enum/index.d.ts +0 -1
- package/agnostic/objects/enums/is-in-enum/index.js +0 -6
- package/agnostic/objects/flatten-getters/index.d.ts +0 -1
- package/agnostic/objects/flatten-getters/index.js +0 -9
- package/agnostic/objects/index.d.ts +0 -12
- package/agnostic/objects/index.js +0 -32
- package/agnostic/objects/is-object/index.d.ts +0 -1
- package/agnostic/objects/is-object/index.js +0 -6
- package/agnostic/objects/is-record/index.d.ts +0 -1
- package/agnostic/objects/is-record/index.js +0 -6
- package/agnostic/objects/record-format/index.d.ts +0 -11
- package/agnostic/objects/record-format/index.js +0 -16
- package/agnostic/objects/validation/index.d.ts +0 -3
- package/agnostic/objects/validation/index.js +0 -6
- package/agnostic/optim/index.d.ts +0 -7
- package/agnostic/optim/index.js +0 -18
- package/agnostic/optim/memoize/index.d.ts +0 -3
- package/agnostic/optim/memoize/index.js +0 -6
- package/agnostic/optim/throttle-debounce/index.d.ts +0 -30
- package/agnostic/optim/throttle-debounce/index.js +0 -8
- package/agnostic/optim/throttle-debounce/types.d.ts +0 -2
- package/agnostic/regexps/index.d.ts +0 -23
- package/agnostic/regexps/index.js +0 -89
- package/agnostic/strings/char-codes/index.d.ts +0 -17
- package/agnostic/strings/char-codes/index.js +0 -6
- package/agnostic/strings/index.d.ts +0 -14
- package/agnostic/strings/index.js +0 -32
- package/agnostic/strings/matches/index.d.ts +0 -3
- package/agnostic/strings/matches/index.js +0 -10
- package/agnostic/strings/normalize-indent/index.d.ts +0 -1
- package/agnostic/strings/normalize-indent/index.js +0 -6
- package/agnostic/strings/replace-all/index.d.ts +0 -1
- package/agnostic/strings/replace-all/index.js +0 -6
- package/agnostic/strings/to-alphanum/index.d.ts +0 -1
- package/agnostic/strings/to-alphanum/index.js +0 -6
- package/chunks/chunk-32IRF4OP.js +0 -9
- package/chunks/chunk-34U4HX4V.js +0 -73
- package/chunks/chunk-4GDNHUCZ.js +0 -162
- package/chunks/chunk-4QQBKBYH.js +0 -59
- package/chunks/chunk-4VC5PT5T.js +0 -13
- package/chunks/chunk-5LNUCDQ6.js +0 -132
- package/chunks/chunk-7AWTHZLY.js +0 -12
- package/chunks/chunk-7DBNMU6N.js +0 -14
- package/chunks/chunk-COVPTTAD.js +0 -10
- package/chunks/chunk-DNIOWD7K.js +0 -8
- package/chunks/chunk-DQNJQBC6.js +0 -10
- package/chunks/chunk-E6MSDKON.js +0 -13
- package/chunks/chunk-EDVNAV3G.js +0 -72
- package/chunks/chunk-EW46EXFB.js +0 -8
- package/chunks/chunk-FPEW3A27.js +0 -14
- package/chunks/chunk-GJOU3UAL.js +0 -36
- package/chunks/chunk-HC6ZOHCS.js +0 -14
- package/chunks/chunk-HGCG2J77.js +0 -122
- package/chunks/chunk-IFD7GSGK.js +0 -130
- package/chunks/chunk-JQXNEJAP.js +0 -18
- package/chunks/chunk-JWBDZPQG.js +0 -18
- package/chunks/chunk-K37RALUX.js +0 -8
- package/chunks/chunk-KIONYWA7.js +0 -168
- package/chunks/chunk-KQT5CTGF.js +0 -21
- package/chunks/chunk-LEXQ6E6L.js +0 -31
- package/chunks/chunk-LQFKUNVQ.js +0 -19
- package/chunks/chunk-MF56TTC5.js +0 -15
- package/chunks/chunk-MT74WIQR.js +0 -41
- package/chunks/chunk-NRITRUZW.js +0 -10
- package/chunks/chunk-NRJQKVQV.js +0 -31
- package/chunks/chunk-OSAXBA7G.js +0 -10
- package/chunks/chunk-PO3V3I57.js +0 -23
- package/chunks/chunk-QXAJXTXV.js +0 -19
- package/chunks/chunk-RCO57B6F.js +0 -9
- package/chunks/chunk-SQZGZ3VT.js +0 -15
- package/chunks/chunk-TTZHPIH7.js +0 -237
- package/chunks/chunk-UYTXXUU7.js +0 -54
- package/chunks/chunk-VTPRO4NJ.js +0 -10
- package/chunks/chunk-VYW4IADX.js +0 -35
- package/chunks/chunk-VZDUZTW6.js +0 -113
- package/chunks/chunk-W5A2TON3.js +0 -10
- package/chunks/chunk-W7YBGJ4H.js +0 -28
- package/chunks/chunk-WLL3FNVL.js +0 -40
- package/chunks/chunk-WOAYU6LB.js +0 -13
- package/chunks/chunk-XA4HVHJ4.js +0 -18
- package/chunks/chunk-XN3PZIVL.js +0 -38
- package/chunks/chunk-Y3OZHZUF.js +0 -237
- package/chunks/chunk-YDIBNEGA.js +0 -9
- package/chunks/chunk-ZC5RZBNY.js +0 -33
- package/components/Comp/index.d.ts +0 -1
- package/node/files/index.d.ts +0 -9
- package/node/files/index.js +0 -21
- package/node/files/is-in-directory/index.d.ts +0 -1
- package/node/files/is-in-directory/index.js +0 -6
- package/node/files/read-write/index.d.ts +0 -9
- package/node/files/read-write/index.js +0 -6
- package/node/files/subpaths/index.d.ts +0 -34
- package/node/files/subpaths/index.js +0 -7
- package/node/image/index.d.ts +0 -9
- package/node/image/index.js +0 -74
- package/node/mxfb-cli-config/index.d.ts +0 -19
- package/node/mxfb-cli-config/index.js +0 -113
- package/node/process/index.d.ts +0 -8
- package/node/process/index.js +0 -20
- package/node/process/on-exit/index.d.ts +0 -5
- package/node/process/on-exit/index.js +0 -14
- package/shared/errors/index.d.ts +0 -138
- package/shared/index.d.ts +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
const TUMBLR_HOSTNAME_SUFFIX = '.tumblr.com';
|
|
2
|
+
const TUMBLR_CANONICAL_HOSTNAMES = ['www.tumblr.com', 'tumblr.com'];
|
|
3
|
+
const PERMALINK_PATHNAME_PATTERN = /^\/post\/(\d+)(?:\/|$)/v;
|
|
4
|
+
const INDEX_PAGE_PATHNAME_PATTERN = /^\/page\/(\d+)(?:\/|$)/v;
|
|
5
|
+
const ARCHIVE_PATHNAME_PATTERN = /^\/archive(?:\/|$)/v;
|
|
6
|
+
const TAGGED_PATHNAME_PATTERN = /^\/tagged\//v;
|
|
7
|
+
// A post block never reaches across one of these, so they stop the walk up the tree on a
|
|
8
|
+
// permalink page, where no sibling post is there to stop it.
|
|
9
|
+
const PAGE_LANDMARK_SELECTOR = 'nav, header, footer, aside';
|
|
10
|
+
export function toIntegerOrNull(raw) {
|
|
11
|
+
if (raw === null)
|
|
12
|
+
return null;
|
|
13
|
+
const parsed = Number.parseInt(raw, 10);
|
|
14
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
15
|
+
}
|
|
16
|
+
export function readSitemapChildUrls(document) {
|
|
17
|
+
const urls = [];
|
|
18
|
+
for (const entry of Array.from(document.getElementsByTagName('sitemap'))) {
|
|
19
|
+
const url = readFirstChildText(entry, 'loc');
|
|
20
|
+
if (url !== null)
|
|
21
|
+
urls.push(url);
|
|
22
|
+
}
|
|
23
|
+
return urls;
|
|
24
|
+
}
|
|
25
|
+
export function readSitemapLocations(document) {
|
|
26
|
+
const locations = [];
|
|
27
|
+
for (const entry of Array.from(document.getElementsByTagName('url'))) {
|
|
28
|
+
const url = readFirstChildText(entry, 'loc');
|
|
29
|
+
if (url === null)
|
|
30
|
+
continue;
|
|
31
|
+
locations.push({
|
|
32
|
+
url,
|
|
33
|
+
lastModifiedOn: toDateOrNull(readFirstChildText(entry, 'lastmod'))
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return locations;
|
|
37
|
+
}
|
|
38
|
+
export function toOrigin(url) {
|
|
39
|
+
if (url === null)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
return new URL(url).origin;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function readMetaProperty(document, property) {
|
|
49
|
+
return readAttribute(document, `meta[property="${property}"]`, 'content');
|
|
50
|
+
}
|
|
51
|
+
export function extractCanonicalUrl(document) {
|
|
52
|
+
return readLinkHref(document, 'canonical')
|
|
53
|
+
?? readMetaProperty(document, 'og:url');
|
|
54
|
+
}
|
|
55
|
+
export function extractPageKind(pageUrl) {
|
|
56
|
+
const pathname = toPathname(pageUrl);
|
|
57
|
+
if (pathname === null)
|
|
58
|
+
return 'unknown';
|
|
59
|
+
if (PERMALINK_PATHNAME_PATTERN.test(pathname))
|
|
60
|
+
return 'permalink';
|
|
61
|
+
if (ARCHIVE_PATHNAME_PATTERN.test(pathname))
|
|
62
|
+
return 'archive';
|
|
63
|
+
// Kept as a pattern to stay symmetric with the three above.
|
|
64
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with */
|
|
65
|
+
if (TAGGED_PATHNAME_PATTERN.test(pathname))
|
|
66
|
+
return 'tagged';
|
|
67
|
+
if (INDEX_PAGE_PATHNAME_PATTERN.test(pathname))
|
|
68
|
+
return 'index';
|
|
69
|
+
if (pathname === '/')
|
|
70
|
+
return 'index';
|
|
71
|
+
return 'custom';
|
|
72
|
+
}
|
|
73
|
+
export function extractBlog(document, pageUrl, canonicalUrl, pageKind) {
|
|
74
|
+
const baseUrl = toOrigin(pageUrl);
|
|
75
|
+
const iconHref = readLinkHref(document, 'apple-touch-icon')
|
|
76
|
+
?? readLinkHref(document, 'icon');
|
|
77
|
+
return {
|
|
78
|
+
name: extractBlogName(pageUrl, canonicalUrl),
|
|
79
|
+
title: readMetaProperty(document, 'og:site_name')
|
|
80
|
+
?? toTextOrNull(document.querySelector('title')?.textContent ?? null),
|
|
81
|
+
description: extractBlogDescription(document, pageKind),
|
|
82
|
+
baseUrl,
|
|
83
|
+
avatarUrl: toAbsoluteUrl(iconHref, baseUrl),
|
|
84
|
+
navLinks: extractNavLinks(document, baseUrl)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function extractPostFromPermalinkPage(document, pageUrl) {
|
|
88
|
+
const id = extractPostIdFromUrl(pageUrl);
|
|
89
|
+
const postIdByAnchor = indexPostAnchors(document, toOrigin(pageUrl));
|
|
90
|
+
return {
|
|
91
|
+
...createIdentifiedPost(id, pageUrl),
|
|
92
|
+
type: extractPostType(readMetaProperty(document, 'og:type')),
|
|
93
|
+
publishedOn: toDateOrNull(readMetaProperty(document, 'article:published_time')),
|
|
94
|
+
title: readMetaProperty(document, 'og:title'),
|
|
95
|
+
tags: readEveryMetaProperty(document, 'article:tag'),
|
|
96
|
+
media: extractMedia(document),
|
|
97
|
+
...readRawHtml(findPostElement(document, id, postIdByAnchor), 'permalink')
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function extractPostsFromListingPage(document, baseUrl) {
|
|
101
|
+
const postIdByAnchor = indexPostAnchors(document, baseUrl);
|
|
102
|
+
const postsById = new Map();
|
|
103
|
+
for (const [anchor, id] of postIdByAnchor) {
|
|
104
|
+
if (postsById.has(id))
|
|
105
|
+
continue;
|
|
106
|
+
const url = toAbsoluteUrl(anchor.getAttribute('href'), baseUrl);
|
|
107
|
+
postsById.set(id, {
|
|
108
|
+
...createIdentifiedPost(id, url),
|
|
109
|
+
...readRawHtml(findPostElement(document, id, postIdByAnchor), 'listing')
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return Array.from(postsById.values());
|
|
113
|
+
}
|
|
114
|
+
export function extractPagination(document, pageUrl, baseUrl) {
|
|
115
|
+
const currentPageNumber = extractCurrentPageNumber(pageUrl);
|
|
116
|
+
const pageUrlsByNumber = collectPageUrlsByNumber(document, baseUrl);
|
|
117
|
+
const siblingPageUrl = (offset) => {
|
|
118
|
+
if (currentPageNumber === null)
|
|
119
|
+
return null;
|
|
120
|
+
return pageUrlsByNumber.get(currentPageNumber + offset) ?? null;
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
currentPageNumber,
|
|
124
|
+
nextPageUrl: toAbsoluteUrl(readLinkHref(document, 'next'), baseUrl) ?? siblingPageUrl(1),
|
|
125
|
+
previousPageUrl: toAbsoluteUrl(readLinkHref(document, 'prev'), baseUrl) ?? siblingPageUrl(-1)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function readFirstChildText(element, tagName) {
|
|
129
|
+
return toTextOrNull(element.getElementsByTagName(tagName)[0]?.textContent ?? null);
|
|
130
|
+
}
|
|
131
|
+
function normalizeWhitespace(raw) {
|
|
132
|
+
return raw.replace(/\s+/gv, ' ').trim();
|
|
133
|
+
}
|
|
134
|
+
function toTextOrNull(raw) {
|
|
135
|
+
if (raw === null)
|
|
136
|
+
return null;
|
|
137
|
+
const normalized = normalizeWhitespace(raw);
|
|
138
|
+
return normalized === '' ? null : normalized;
|
|
139
|
+
}
|
|
140
|
+
function toDateOrNull(raw) {
|
|
141
|
+
if (raw === null)
|
|
142
|
+
return null;
|
|
143
|
+
const parsed = new Date(raw);
|
|
144
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
145
|
+
}
|
|
146
|
+
function toAbsoluteUrl(href, baseUrl) {
|
|
147
|
+
if (href === null)
|
|
148
|
+
return null;
|
|
149
|
+
try {
|
|
150
|
+
return new URL(href, baseUrl ?? undefined).href;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function toPathname(url) {
|
|
157
|
+
if (url === null)
|
|
158
|
+
return null;
|
|
159
|
+
try {
|
|
160
|
+
return new URL(url).pathname;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function sharesOrigin(url, baseUrl) {
|
|
167
|
+
if (baseUrl === null)
|
|
168
|
+
return true;
|
|
169
|
+
return toOrigin(url) === toOrigin(baseUrl);
|
|
170
|
+
}
|
|
171
|
+
function readAttribute(document, selector, attributeName) {
|
|
172
|
+
return toTextOrNull(document.querySelector(selector)?.getAttribute(attributeName) ?? null);
|
|
173
|
+
}
|
|
174
|
+
function readMetaName(document, name) {
|
|
175
|
+
return readAttribute(document, `meta[name="${name}"]`, 'content');
|
|
176
|
+
}
|
|
177
|
+
function readLinkHref(document, relation) {
|
|
178
|
+
return readAttribute(document, `link[rel="${relation}"]`, 'href');
|
|
179
|
+
}
|
|
180
|
+
function readEveryMetaProperty(document, property) {
|
|
181
|
+
return Array
|
|
182
|
+
.from(document.querySelectorAll(`meta[property="${property}"]`))
|
|
183
|
+
.map(element => toTextOrNull(element.getAttribute('content')))
|
|
184
|
+
.filter((content) => content !== null);
|
|
185
|
+
}
|
|
186
|
+
function extractBlogName(pageUrl, canonicalUrl) {
|
|
187
|
+
return readBlogNameFromCanonicalUrl(canonicalUrl)
|
|
188
|
+
?? readBlogNameFromHostname(pageUrl);
|
|
189
|
+
}
|
|
190
|
+
function readBlogNameFromCanonicalUrl(canonicalUrl) {
|
|
191
|
+
if (canonicalUrl === null)
|
|
192
|
+
return null;
|
|
193
|
+
let url;
|
|
194
|
+
try {
|
|
195
|
+
url = new URL(canonicalUrl);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (!TUMBLR_CANONICAL_HOSTNAMES.includes(url.hostname))
|
|
201
|
+
return null;
|
|
202
|
+
const segments = url.pathname.split('/').filter(segment => segment !== '');
|
|
203
|
+
return segments[0] ?? null;
|
|
204
|
+
}
|
|
205
|
+
function readBlogNameFromHostname(pageUrl) {
|
|
206
|
+
let hostname;
|
|
207
|
+
try {
|
|
208
|
+
hostname = new URL(pageUrl).hostname;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (!hostname.endsWith(TUMBLR_HOSTNAME_SUFFIX))
|
|
214
|
+
return null;
|
|
215
|
+
const name = hostname.slice(0, -TUMBLR_HOSTNAME_SUFFIX.length);
|
|
216
|
+
if (name === '' || name === 'www' || name.includes('.'))
|
|
217
|
+
return null;
|
|
218
|
+
return name;
|
|
219
|
+
}
|
|
220
|
+
function extractBlogDescription(document, pageKind) {
|
|
221
|
+
if (pageKind === 'permalink')
|
|
222
|
+
return null;
|
|
223
|
+
return readMetaName(document, 'description')
|
|
224
|
+
?? readMetaProperty(document, 'og:description');
|
|
225
|
+
}
|
|
226
|
+
function extractNavLinks(document, baseUrl) {
|
|
227
|
+
const navLinks = [];
|
|
228
|
+
const seenUrls = new Set();
|
|
229
|
+
for (const anchor of document.querySelectorAll('nav a[href], header a[href]')) {
|
|
230
|
+
const url = toAbsoluteUrl(anchor.getAttribute('href'), baseUrl);
|
|
231
|
+
const label = toTextOrNull(anchor.textContent);
|
|
232
|
+
if (url === null || label === null)
|
|
233
|
+
continue;
|
|
234
|
+
if (!sharesOrigin(url, baseUrl) || seenUrls.has(url))
|
|
235
|
+
continue;
|
|
236
|
+
seenUrls.add(url);
|
|
237
|
+
navLinks.push({ label, url });
|
|
238
|
+
}
|
|
239
|
+
return navLinks;
|
|
240
|
+
}
|
|
241
|
+
function extractPostIdFromUrl(url) {
|
|
242
|
+
const pathname = toPathname(url);
|
|
243
|
+
if (pathname === null)
|
|
244
|
+
return null;
|
|
245
|
+
return PERMALINK_PATHNAME_PATTERN.exec(pathname)?.[1] ?? null;
|
|
246
|
+
}
|
|
247
|
+
function extractPageNumberFromUrl(url) {
|
|
248
|
+
const pathname = toPathname(url);
|
|
249
|
+
if (pathname === null)
|
|
250
|
+
return null;
|
|
251
|
+
return toIntegerOrNull(INDEX_PAGE_PATHNAME_PATTERN.exec(pathname)?.[1] ?? null);
|
|
252
|
+
}
|
|
253
|
+
function extractPostType(openGraphType) {
|
|
254
|
+
if (openGraphType === null)
|
|
255
|
+
return 'unknown';
|
|
256
|
+
if (openGraphType.startsWith('video'))
|
|
257
|
+
return 'video';
|
|
258
|
+
if (openGraphType.startsWith('music'))
|
|
259
|
+
return 'audio';
|
|
260
|
+
return 'unknown';
|
|
261
|
+
}
|
|
262
|
+
function extractMedia(document) {
|
|
263
|
+
const media = [];
|
|
264
|
+
const imageUrl = readMetaProperty(document, 'og:image');
|
|
265
|
+
if (imageUrl !== null) {
|
|
266
|
+
media.push({
|
|
267
|
+
kind: 'image',
|
|
268
|
+
url: imageUrl,
|
|
269
|
+
widthPx: toIntegerOrNull(readMetaProperty(document, 'og:image:width')),
|
|
270
|
+
heightPx: toIntegerOrNull(readMetaProperty(document, 'og:image:height')),
|
|
271
|
+
alternativeText: readMetaProperty(document, 'og:image:alt')
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const videoUrl = readMetaProperty(document, 'og:video');
|
|
275
|
+
if (videoUrl !== null) {
|
|
276
|
+
media.push({
|
|
277
|
+
kind: 'video',
|
|
278
|
+
url: videoUrl,
|
|
279
|
+
widthPx: toIntegerOrNull(readMetaProperty(document, 'og:video:width')),
|
|
280
|
+
heightPx: toIntegerOrNull(readMetaProperty(document, 'og:video:height')),
|
|
281
|
+
alternativeText: null
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const audioUrl = readMetaProperty(document, 'og:audio');
|
|
285
|
+
if (audioUrl !== null) {
|
|
286
|
+
media.push({
|
|
287
|
+
kind: 'audio',
|
|
288
|
+
url: audioUrl,
|
|
289
|
+
widthPx: null,
|
|
290
|
+
heightPx: null,
|
|
291
|
+
alternativeText: null
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return media;
|
|
295
|
+
}
|
|
296
|
+
function indexPostAnchors(document, baseUrl) {
|
|
297
|
+
const postIdByAnchor = new Map();
|
|
298
|
+
for (const anchor of document.querySelectorAll('a[href]')) {
|
|
299
|
+
const url = toAbsoluteUrl(anchor.getAttribute('href'), baseUrl);
|
|
300
|
+
if (url === null || !sharesOrigin(url, baseUrl))
|
|
301
|
+
continue;
|
|
302
|
+
const id = extractPostIdFromUrl(url);
|
|
303
|
+
if (id === null)
|
|
304
|
+
continue;
|
|
305
|
+
postIdByAnchor.set(anchor, id);
|
|
306
|
+
}
|
|
307
|
+
return postIdByAnchor;
|
|
308
|
+
}
|
|
309
|
+
function findPostElement(document, postId, postIdByAnchor) {
|
|
310
|
+
if (postId === null)
|
|
311
|
+
return null;
|
|
312
|
+
const blocks = [];
|
|
313
|
+
for (const [anchor, id] of postIdByAnchor) {
|
|
314
|
+
if (id !== postId)
|
|
315
|
+
continue;
|
|
316
|
+
const block = climbToPostBlock(anchor, postId, postIdByAnchor);
|
|
317
|
+
if (block !== null && !blocks.includes(block))
|
|
318
|
+
blocks.push(block);
|
|
319
|
+
}
|
|
320
|
+
if (blocks.length === 0)
|
|
321
|
+
return document.querySelector('article');
|
|
322
|
+
const namedBlocks = blocks.filter(block => namesPost(block, postId));
|
|
323
|
+
return widestElement(namedBlocks.length > 0 ? namedBlocks : blocks);
|
|
324
|
+
}
|
|
325
|
+
function climbToPostBlock(anchor, postId, postIdByAnchor) {
|
|
326
|
+
let candidate = anchor;
|
|
327
|
+
let widestBlock = null;
|
|
328
|
+
let nearestNamedBlock = null;
|
|
329
|
+
while (holdsOnlyThisPost(candidate, postId, postIdByAnchor)) {
|
|
330
|
+
widestBlock = candidate;
|
|
331
|
+
if (nearestNamedBlock === null && namesPost(candidate, postId))
|
|
332
|
+
nearestNamedBlock = candidate;
|
|
333
|
+
const parent = candidate.parentElement;
|
|
334
|
+
if (parent === null || parent.tagName === 'BODY' || parent.tagName === 'HTML')
|
|
335
|
+
break;
|
|
336
|
+
candidate = parent;
|
|
337
|
+
}
|
|
338
|
+
return nearestNamedBlock ?? widestBlock;
|
|
339
|
+
}
|
|
340
|
+
function widestElement(elements) {
|
|
341
|
+
let widest = null;
|
|
342
|
+
for (const element of elements) {
|
|
343
|
+
if (widest === null || element.outerHTML.length > widest.outerHTML.length)
|
|
344
|
+
widest = element;
|
|
345
|
+
}
|
|
346
|
+
return widest;
|
|
347
|
+
}
|
|
348
|
+
function holdsOnlyThisPost(element, postId, postIdByAnchor) {
|
|
349
|
+
if (element.matches(PAGE_LANDMARK_SELECTOR))
|
|
350
|
+
return false;
|
|
351
|
+
if (element.querySelector(PAGE_LANDMARK_SELECTOR) !== null)
|
|
352
|
+
return false;
|
|
353
|
+
for (const anchor of element.querySelectorAll('a[href]')) {
|
|
354
|
+
const id = postIdByAnchor.get(anchor);
|
|
355
|
+
if (id !== undefined && id !== postId)
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
function namesPost(element, postId) {
|
|
361
|
+
return element.tagName === 'ARTICLE' || element.id.includes(postId);
|
|
362
|
+
}
|
|
363
|
+
function readRawHtml(postElement, source) {
|
|
364
|
+
if (postElement === null)
|
|
365
|
+
return { rawHtml: null, rawHtmlSource: null };
|
|
366
|
+
return { rawHtml: postElement.outerHTML, rawHtmlSource: source };
|
|
367
|
+
}
|
|
368
|
+
function createIdentifiedPost(id, url) {
|
|
369
|
+
return {
|
|
370
|
+
id,
|
|
371
|
+
url,
|
|
372
|
+
type: 'unknown',
|
|
373
|
+
publishedOn: null,
|
|
374
|
+
title: null,
|
|
375
|
+
contentHtml: null,
|
|
376
|
+
rawHtml: null,
|
|
377
|
+
rawHtmlSource: null,
|
|
378
|
+
detail: null,
|
|
379
|
+
tags: [],
|
|
380
|
+
media: [],
|
|
381
|
+
notesCount: null,
|
|
382
|
+
reblogOfUrl: null,
|
|
383
|
+
lastModifiedOn: null
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function collectPageUrlsByNumber(document, baseUrl) {
|
|
387
|
+
const pageUrlsByNumber = new Map();
|
|
388
|
+
for (const anchor of document.querySelectorAll('a[href]')) {
|
|
389
|
+
const url = toAbsoluteUrl(anchor.getAttribute('href'), baseUrl);
|
|
390
|
+
if (url === null || !sharesOrigin(url, baseUrl))
|
|
391
|
+
continue;
|
|
392
|
+
const pageNumber = extractPageNumberFromUrl(url);
|
|
393
|
+
if (pageNumber === null || pageUrlsByNumber.has(pageNumber))
|
|
394
|
+
continue;
|
|
395
|
+
pageUrlsByNumber.set(pageNumber, url);
|
|
396
|
+
}
|
|
397
|
+
return pageUrlsByNumber;
|
|
398
|
+
}
|
|
399
|
+
function extractCurrentPageNumber(pageUrl) {
|
|
400
|
+
const pathname = toPathname(pageUrl);
|
|
401
|
+
if (pathname === null)
|
|
402
|
+
return null;
|
|
403
|
+
const declaredPageNumber = extractPageNumberFromUrl(pageUrl);
|
|
404
|
+
if (declaredPageNumber !== null)
|
|
405
|
+
return declaredPageNumber;
|
|
406
|
+
return pathname === '/' ? 1 : null;
|
|
407
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core weather data types for archiving and exchanging meteorological information.
|
|
3
|
+
*
|
|
4
|
+
* The structures are inspired by WMO (World Meteorological Organization) guidance
|
|
5
|
+
* and CF Conventions. Units are encoded explicitly in property names to avoid
|
|
6
|
+
* ambiguity and to make transformations between systems easier and safer.
|
|
7
|
+
*/
|
|
8
|
+
/** Geographic location following the WGS84 reference system. */
|
|
9
|
+
export type GeoLocation = {
|
|
10
|
+
/** Latitude in decimal degrees (-90 to +90). */ latitudeDeg: number;
|
|
11
|
+
/** Longitude in decimal degrees (-180 to +180). */ longitudeDeg: number;
|
|
12
|
+
/** Elevation in meters above mean sea level. */ elevationM?: number;
|
|
13
|
+
/** Human-readable place name (city, station, etc.). */ name?: string;
|
|
14
|
+
/** Country code in ISO 3166-1 alpha-2 format (e.g. "FR", "US"). */ countryCode31661a2?: string;
|
|
15
|
+
/** Administrative region, state, or province name. */ region?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Source of observed meteorological data. */
|
|
18
|
+
export type ObservationDataSource = {
|
|
19
|
+
/** Data provider name */ provider: string;
|
|
20
|
+
/** High-level type of source */ type: 'satellite' | 'radar' | 'station' | 'derived' | 'manual' | 'other';
|
|
21
|
+
/** Identifier of the observing station or sensor when applicable. */ stationId?: string;
|
|
22
|
+
/** URL pointing to the data source or documentation. */ url?: string;
|
|
23
|
+
/** Data license or usage terms (short free-text). */ license?: string;
|
|
24
|
+
};
|
|
25
|
+
/** Source of forecast data. Describes both deterministic and ensemble-based forecast systems. */
|
|
26
|
+
export type ForecastDataSource = {
|
|
27
|
+
/** Provider or operator name */ provider: string;
|
|
28
|
+
/** Type of forecast product. */ type: 'model' | 'ensemble' | 'derived' | 'other';
|
|
29
|
+
/** Model name or product identifier (e.g. "GFS", "AROME"). */ model?: string;
|
|
30
|
+
/** Model version, configuration, or cycle identifier. */ modelVersion?: string;
|
|
31
|
+
/** Model initialization date */ runTime?: Date;
|
|
32
|
+
/** Ensemble member identifier for probabilistic forecasts. */ ensembleMember?: string;
|
|
33
|
+
/** URL pointing to model documentation or the data endpoint. */ url?: string;
|
|
34
|
+
/** Data license or usage terms for the forecast. */ license?: string;
|
|
35
|
+
};
|
|
36
|
+
/** Temperature-related fields. */
|
|
37
|
+
export type Temperature = {
|
|
38
|
+
/** Air temperature in degrees Celsius. */ tempC: number;
|
|
39
|
+
/** Feels-like temperature in °C (wind chill or heat index depending on conditions). */ feelsLikeC?: number;
|
|
40
|
+
/** Dew point temperature in °C. */ dewPointC?: number;
|
|
41
|
+
/** Minimum temperature over the period in °C. */ minC?: number;
|
|
42
|
+
/** Maximum temperature over the period in °C. */ maxC?: number;
|
|
43
|
+
};
|
|
44
|
+
/** Humidity parameters. */
|
|
45
|
+
export type Humidity = {
|
|
46
|
+
/** Relative humidity in percent (0–100). */ relativePercent: number;
|
|
47
|
+
/** Absolute humidity in grams per cubic meter. */ absoluteGm3?: number;
|
|
48
|
+
};
|
|
49
|
+
/** Wind according to common WMO conventions. */
|
|
50
|
+
export type Wind = {
|
|
51
|
+
/** Mean wind speed in meters per second. */ speedMs: number;
|
|
52
|
+
/** Wind direction in degrees (0–360, where 0/360 = North, 90 = East). */ directionDeg: number;
|
|
53
|
+
/** Maximum gust speed in meters per second over the period. */ gustMs?: number;
|
|
54
|
+
};
|
|
55
|
+
/** Canonical list of WMO cloud genera. */
|
|
56
|
+
export type CloudType = 'cirrus' | 'cirrocumulus' | 'cirrostratus' | 'altocumulus' | 'altostratus' | 'nimbostratus' | 'stratocumulus' | 'stratus' | 'cumulus' | 'cumulonimbus';
|
|
57
|
+
/** Cloud cover information following common WMO recommendations. */
|
|
58
|
+
export type CloudCover = {
|
|
59
|
+
/** Total cloud cover in percent (0–100). */ totalPercent: number;
|
|
60
|
+
/** Low-level cloud fraction in percent (0–100). */ lowPercent?: number;
|
|
61
|
+
/** Mid-level cloud fraction in percent (0–100). */ middlePercent?: number;
|
|
62
|
+
/** High-level cloud fraction in percent (0–100). */ highPercent?: number;
|
|
63
|
+
/** Cloud base height in meters above ground level. */ cloudBaseHeightM?: number;
|
|
64
|
+
/** Dominant cloud genus when it can be identified. */ dominantGenre?: CloudType;
|
|
65
|
+
};
|
|
66
|
+
/** Canonical list of atmospheric phenomena based on WMO classification. */
|
|
67
|
+
export type AtmosphericPhenomenon = 'rain' | 'drizzle' | 'freezing-rain' | 'snow' | 'snow-grains' | 'ice-pellets' | 'hail' | 'fog' | 'mist' | 'haze' | 'smoke' | 'dust' | 'sand' | 'volcanic-ash' | 'thunderstorm' | 'squall' | 'funnel-cloud';
|
|
68
|
+
/** Single atmospheric phenomenon, observed or forecast. */
|
|
69
|
+
export type WeatherPhenomenon = {
|
|
70
|
+
/** Phenomenon type according to the WMO-inspired list. */ phenomenon: AtmosphericPhenomenon;
|
|
71
|
+
/** Qualitative intensity: "+" for heavy, "-" for light, or null for normal. */ intensity: '+' | '-' | null;
|
|
72
|
+
/** Optional free-text remark describing special characteristics. */ remark?: string;
|
|
73
|
+
};
|
|
74
|
+
/** Atmospheric pressure. */
|
|
75
|
+
export type Pressure = {
|
|
76
|
+
/** Sea-level pressure in hectopascals (millibars). */ seaLevelHpa?: number;
|
|
77
|
+
/** Station-level pressure in hectopascals at the observing elevation. */ stationLevelHpa: number;
|
|
78
|
+
};
|
|
79
|
+
/** Visibility metrics. */
|
|
80
|
+
export type Visibility = {
|
|
81
|
+
/** Horizontal visibility in meters. */ horizontalM?: number;
|
|
82
|
+
/** Vertical visibility in meters (e.g. ceiling height). */ verticalM?: number;
|
|
83
|
+
};
|
|
84
|
+
/** Solar radiation and UV information. */
|
|
85
|
+
export type Solar = {
|
|
86
|
+
/** Global solar radiation in watts per square meter. */ radiationWm2?: number;
|
|
87
|
+
/** Dimensionless UV index, typically between 0 and ~12. */ uvIndex?: number;
|
|
88
|
+
};
|
|
89
|
+
/** Air quality indicators. */
|
|
90
|
+
export type AirQuality = {
|
|
91
|
+
/** Fine particulate matter PM2.5 in micrograms per cubic meter. */ pm25Ugm3?: number;
|
|
92
|
+
/** Particulate matter PM10 in micrograms per cubic meter. */ pm10Ugm3?: number;
|
|
93
|
+
/** Ozone (O₃) concentration in micrograms per cubic meter. */ o3Ugm3?: number;
|
|
94
|
+
/** Nitrogen dioxide (NO₂) concentration in micrograms per cubic meter. */ no2Ugm3?: number;
|
|
95
|
+
/** Sulfur dioxide (SO₂) concentration in micrograms per cubic meter. */ so2Ugm3?: number;
|
|
96
|
+
/** Carbon monoxide (CO) concentration in micrograms per cubic meter. */ coUgm3?: number;
|
|
97
|
+
};
|
|
98
|
+
/** Quality metadata attached to an observation or measurement. */
|
|
99
|
+
export type ObservationQualityMetadata = {
|
|
100
|
+
/** Status of the data, typically resulting from QC checks or post-processing. */ status?: 'verified' | 'unverified' | 'estimated' | 'interpolated' | 'suspect';
|
|
101
|
+
/** Free-text notes or comments about the data quality. */ notes?: string;
|
|
102
|
+
};
|
|
103
|
+
/** Validity period of a forecast or alert. */
|
|
104
|
+
export type ValidityPeriod = {
|
|
105
|
+
/** Start of validity */ from: Date;
|
|
106
|
+
/** End of validity */ to: Date;
|
|
107
|
+
};
|
|
108
|
+
/** Weather alert or warning issued by an authority or provider. */
|
|
109
|
+
export type WeatherAlert = {
|
|
110
|
+
/** Alert type or category (e.g. "wind", "flood", "heat"). */ type: string;
|
|
111
|
+
/** Severity level as a normalized qualitative scale. */ severity: 'minor' | 'moderate' | 'severe' | 'extreme';
|
|
112
|
+
/** Human-readable description of the alert and its expected impacts. */ description?: string;
|
|
113
|
+
/** Validity period of a forecast or alert. */ validity?: ValidityPeriod;
|
|
114
|
+
};
|
|
115
|
+
/** Probabilistic forecast data for a single parameter. */
|
|
116
|
+
export type ProbabilisticData = {
|
|
117
|
+
/** Confidence level for the forecast (0–100%). */ confidencePercent?: number;
|
|
118
|
+
/** Temperature 10th percentile in °C (optimistic or cooler scenario). */ tempP10C?: number;
|
|
119
|
+
/** Temperature 90th percentile in °C (pessimistic or warmer scenario). */ tempP90C?: number;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Core weather data structure containing essential meteorological measurements.
|
|
123
|
+
*
|
|
124
|
+
* This type represents the fundamental weather parameters that can be used
|
|
125
|
+
* for both observations and forecasts. It follows WMO conventions and uses
|
|
126
|
+
* explicit unit encoding in property names to ensure clarity and type safety.
|
|
127
|
+
*
|
|
128
|
+
* All core measurements are required, while some extended parameters
|
|
129
|
+
* (visibility, solar, air quality, alerts) are optional.
|
|
130
|
+
*
|
|
131
|
+
* @see WeatherObservation - For observation-specific data with location and time
|
|
132
|
+
* @see WeatherForecast - For forecast-specific data with validity periods
|
|
133
|
+
*/
|
|
134
|
+
export type WeatherDataCore = {
|
|
135
|
+
/** Temperature-related measurements at the observation time. */ temperature: Temperature;
|
|
136
|
+
/** Humidity-related measurements at the observation time. */ humidity: Humidity;
|
|
137
|
+
/** Wind conditions at the observation time. */ wind: Wind;
|
|
138
|
+
/** Instantaneous precipitation rates at the observation time. */ precipitationMmh: number;
|
|
139
|
+
/** Atmospheric pressure at or reduced to station/sea level. */ pressure: Pressure;
|
|
140
|
+
/** List of significant weather phenomena observed. */ phenomena?: WeatherPhenomenon[];
|
|
141
|
+
/** Cloud cover information at the observation time. */ clouds?: CloudCover;
|
|
142
|
+
/** Visibility metrics at the observation time. */ visibility?: Visibility;
|
|
143
|
+
/** Solar radiation and UV information at the observation time. */ solar?: Solar;
|
|
144
|
+
/** Air quality indicators at or near the observation site. */ airQuality?: AirQuality;
|
|
145
|
+
/** Weather alerts that are active for this forecast period and location. */ alerts?: WeatherAlert[];
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Weather observation.
|
|
149
|
+
*
|
|
150
|
+
* Represents a real-world measurement or observation at a specific point in time,
|
|
151
|
+
* typically coming from an instrumented station or human report.
|
|
152
|
+
*/
|
|
153
|
+
export type WeatherObservation = WeatherDataCore & {
|
|
154
|
+
/** Exact time when the observation was made. */ observedOn: Date;
|
|
155
|
+
/** Geographic location of the observation. */ location: GeoLocation;
|
|
156
|
+
/** Observation metadata */ metadata?: {
|
|
157
|
+
/** Observation source metadata (station, provider, license...). */ source?: ObservationDataSource;
|
|
158
|
+
/** Time when the data was collected or ingested by the system. */ collectedOn?: Date;
|
|
159
|
+
/** Human-readable description or summary of the observation. */ description?: string;
|
|
160
|
+
/** Raw source data as received from the provider. */ rawData?: Record<string, unknown> | string;
|
|
161
|
+
/** Quality control and processing metadata for the observation. */ observationQuality?: ObservationQualityMetadata;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Weather forecast.
|
|
166
|
+
*
|
|
167
|
+
* Represents a forecast for a given future period, possibly including a mix
|
|
168
|
+
* of deterministic and probabilistic information as provided by a model.
|
|
169
|
+
*/
|
|
170
|
+
export type WeatherForecast = WeatherDataCore & {
|
|
171
|
+
/** Time when the forecast was issued. */ issuedOn: Date;
|
|
172
|
+
/** Period of time this forecast is valid for. */ validity: ValidityPeriod;
|
|
173
|
+
/** Geographic location to which the forecast applies. */ location: GeoLocation;
|
|
174
|
+
/** Observation metadata */ metadata?: {
|
|
175
|
+
/** Forecast model and source metadata. */ source?: ForecastDataSource;
|
|
176
|
+
/** Time when the data was collected or ingested by the system. */ collectedOn?: Date;
|
|
177
|
+
/** Human-readable description or summary of the observation. */ description?: string;
|
|
178
|
+
/** Raw source data as received from the provider. */ rawData?: Record<string, unknown> | string;
|
|
179
|
+
/** Associated probabilistic forecast information, if available. */ probabilistic?: ProbabilisticData;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core weather data types for archiving and exchanging meteorological information.
|
|
3
|
+
*
|
|
4
|
+
* The structures are inspired by WMO (World Meteorological Organization) guidance
|
|
5
|
+
* and CF Conventions. Units are encoded explicitly in property names to avoid
|
|
6
|
+
* ambiguity and to make transformations between systems easier and safer.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/components/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * as utils from './utils/index.js'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as utils from './utils/index.js'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mergeClassNames(...names: Array<string | null | undefined | Array<string | null | undefined>>): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function mergeClassNames(...names) {
|
|
2
|
+
return names.map(name => {
|
|
3
|
+
if (typeof name !== 'string' && !Array.isArray(name))
|
|
4
|
+
return false;
|
|
5
|
+
if (Array.isArray(name))
|
|
6
|
+
return mergeClassNames(...name);
|
|
7
|
+
if (name.trim() === '')
|
|
8
|
+
return false;
|
|
9
|
+
return name.trim();
|
|
10
|
+
}).filter((name) => typeof name === 'string')
|
|
11
|
+
.join(' ');
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
export * as agnostic from './agnostic/index.js'
|
|
2
|
+
export * as components from './components/index.js'
|
|
3
|
+
export * as node from './node/index.js'
|
package/index.js
ADDED