Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a00f580
split html and js
CommanderStorm Apr 9, 2026
8c90ee8
Apply suggestion from @CommanderStorm
CommanderStorm Apr 9, 2026
1a441f6
fix lints
CommanderStorm Apr 9, 2026
f2c4dc3
move the dates to git
CommanderStorm Apr 13, 2026
1a6c6e1
fix fmt
CommanderStorm Apr 13, 2026
8137a39
make the docs overview prettier
CommanderStorm Apr 9, 2026
ee8d137
Handle null custom protocol array buffer response (#7427)
neodescis Apr 9, 2026
114a21c
change all terrrain examples to mapterhorn (#7431)
CommanderStorm Apr 9, 2026
801303a
document zoom direction clearer (#7438)
CommanderStorm Apr 9, 2026
d07c880
docs: simplify a few examples (#7439)
CommanderStorm Apr 9, 2026
4dae4bf
chore: better internal types (#7413)
CommanderStorm Apr 9, 2026
4d9a908
chore(deps-dev): bump jsdom from 29.0.1 to 29.0.2 (#7445)
dependabot[bot] Apr 10, 2026
bde3ed7
chore(deps-dev): bump basic-ftp from 5.2.1 to 5.2.2 (#7447)
dependabot[bot] Apr 10, 2026
0fae734
Fix cache key for `_zoomLevelsToOverscale` experimental feature (#7450)
HarelM Apr 11, 2026
5a41e74
feat(marker): support number for opacity options (#7442)
YuChunTsao Apr 11, 2026
e2d9069
fix: guard against null style in _contextRestored (#7446)
mvanhorn Apr 11, 2026
218c157
Improve ability to import scripts in workers (#7451)
HarelM Apr 11, 2026
c32329a
test (#7452)
CommanderStorm Apr 12, 2026
4170c54
chore(deps-dev): bump stylelint from 16.26.1 to 17.6.0 (#7365)
dependabot[bot] Apr 12, 2026
4aa97c4
chore(deps): bump codecov/codecov-action from 5.5.3 to 6.0.0 (#7358)
dependabot[bot] Apr 12, 2026
15fde5d
Update geojson-vt to version 6.1.0 (#7454)
HarelM Apr 12, 2026
4662f0b
Bump js version to 5.23.0 (#7455)
github-actions[bot] Apr 13, 2026
1a2d095
chore(deps-dev): bump the vitest group with 4 updates (#7458)
dependabot[bot] Apr 13, 2026
276a9cb
chore(deps-dev): bump cspell from 9.8.0 to 10.0.0 (#7459)
dependabot[bot] Apr 13, 2026
435afa3
chore(deps-dev): bump react from 19.2.4 to 19.2.5 (#7460)
dependabot[bot] Apr 13, 2026
7b7256d
chore(deps): bump @mapbox/tiny-sdf from 2.0.7 to 2.1.0 (#7461)
dependabot[bot] Apr 13, 2026
a6c1907
chore(deps-dev): bump @typescript-eslint/parser from 8.58.0 to 8.58.1…
dependabot[bot] Apr 13, 2026
f9b4ef6
chore(deps-dev): bump react-dom from 19.2.4 to 19.2.5 (#7463)
dependabot[bot] Apr 13, 2026
d2fa7a3
chore(deps-dev): bump postcss from 8.5.8 to 8.5.9 (#7465)
dependabot[bot] Apr 13, 2026
7601a59
chore(deps-dev): bump @typescript-eslint/eslint-plugin (#7466)
dependabot[bot] Apr 13, 2026
da24f33
chore(deps-dev): bump @types/node from 25.5.2 to 25.6.0 (#7464)
dependabot[bot] Apr 13, 2026
989b1b9
Merge branch 'main' into docs-overview
CommanderStorm Apr 13, 2026
2bd78f3
Apply suggestion from @CommanderStorm
CommanderStorm Apr 13, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### 🐞 Bug fixes
- Fix `Popup` not updating its position when switching between terrain/globe projections ([#7468](https://github.com/maplibre/maplibre-gl-js/pull/7468)) (by [@CommanderStorm](https://github.com/CommanderStorm))
- _...Add new stuff here..._

## 5.23.0

Expand Down
94 changes: 80 additions & 14 deletions build/generate-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type HtmlDoc = {
title: string;
description: string;
mdFileName: string;
isNew: boolean;
};

function generateAPIIntroMarkdown(lines: string[]): string {
Expand All @@ -33,36 +34,99 @@ Import declarations are omitted from the examples for brevity.
return intro;
}

function generateMarkdownForExample(title: string, description: string, file: string, htmlContent: string): string {
return `
function isNewExample(htmlContentLines: string[]): boolean {
const createdLine = htmlContentLines.find(l => l.includes('og:created'));
if (!createdLine) return false;
const match = createdLine.match(/content=["'](\d{4}-\d{2}-\d{2})["']/);
if (!match) return false;
const createdDate = new Date(match[1]);
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
return createdDate >= sixMonthsAgo;
}

function extractJsFromHtml(htmlContent: string): string | null {
const scriptRegex = /<script(?![^>]*\bsrc\b)(?![^>]*type="importmap")[^>]*>([\s\S]*?)<\/script>/g;
const scripts: string[] = [];
let match;
while ((match = scriptRegex.exec(htmlContent)) !== null) {
if (match[1].trim()) {
scripts.push(match[1]);
}
}
if (scripts.length === 0) return null;

let js = scripts.join('\n\n');

// Strip common leading whitespace
const lines = js.split('\n');
const nonEmptyLines = lines.filter(l => l.trim().length > 0);
if (nonEmptyLines.length > 0) {
const minIndent = Math.min(...nonEmptyLines.map(l => l.match(/^\s*/)[0].length));
if (minIndent > 0) {
js = lines.map(l => l.length >= minIndent ? l.slice(minIndent) : l).join('\n');
}
}

return js.trim();
}

function escapeHtml(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}


function indentBlock(text: string, spaces: number = 4): string {
const indent = ' '.repeat(spaces);
return text.split('\n').map(line => line.length > 0 ? `${indent}${line}` : '').join('\n');
}

function generateMarkdownForExample(title: string, description: string, file: string, htmlContent: string, isNew: boolean): string {
const frontmatter = isNew ? '---\nstatus: new\n---\n' : '';
const jsContent = extractJsFromHtml(htmlContent);

let codeBlock: string;
if (jsContent) {
const jsBlock = indentBlock(`\`\`\`js\n${jsContent}\n\`\`\``);
const htmlBlock = indentBlock(`\`\`\`html\n${htmlContent.trimEnd()}\n\`\`\``);
codeBlock = `=== "Only JS"\n\n${jsBlock}\n\n=== "Full HTML"\n\n${htmlBlock}`;
} else {
codeBlock = `\`\`\`html\n${htmlContent}\n\`\`\``;
}

return `${frontmatter}
# ${title}

${description}

<iframe src="../${file}" width="100%" style="border:none; height:400px"></iframe>

\`\`\`html
${htmlContent}
\`\`\`
${codeBlock}
`;
}

async function generateMarkdownIndexFileOfAllExamplesAndPackImages(indexArray: HtmlDoc[]): Promise<string> {
let indexMarkdown = '# Overview \n\n';
let indexMarkdown = '# Overview\n\n<div class="examples-grid">\n';
const promises: Array<Promise<any>> = [];
for (const indexArrayItem of indexArray) {
const imagePath = `docs/assets/examples/${indexArrayItem.mdFileName.replace('.md', '.png')}`;
const outputPath = imagePath.replace('.png', '.webp');
promises.push(sharp(imagePath).webp({quality: 90, lossless: false}).toFile(outputPath));
indexMarkdown += `
## [${indexArrayItem.title}](./${indexArrayItem.mdFileName})

![${indexArrayItem.description}](${outputPath.replace('docs/', '../')}){ loading=lazy }

${indexArrayItem.description}
const desc = indexArrayItem.description || '';
indexMarkdown += `<a class="example-card" href="./${indexArrayItem.mdFileName}">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, sorry, markdown only, no HTML.
I've seen what happened to maplibre.io repo, it's full of HTML and CSS and it's unmaintainable. I don't want that here, sorry...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that is simply impossible in pure markdown.
I cannot do a table that flexes from 1 to 3 element.

I also don't think this is unmaintanable..
It is fairly simple html and just where I cannot get away with this otherwise

You cannot with a straight face tell me that this is better...

image

<div class="example-card-image">
<img src="${outputPath.replace('docs/', '../')}" loading="lazy" alt="${escapeHtml(desc)}">
${indexArrayItem.isNew ? '<span class="example-card-badge">new</span>' : '';}
</div>
<div class="example-card-content">
<h3>${escapeHtml(indexArrayItem.title || '')}</h3>
<p>${escapeHtml(desc)}</p>
</div>
</a>
`;
}
await Promise.all(promises);
indexMarkdown += '</div>\n';
return indexMarkdown;
}

Expand Down Expand Up @@ -104,12 +168,14 @@ async function generateExamplesFolder() {
const description = htmlContentLines.find(l => l.includes('og:description'))?.replace(/.*content=\"(.*)\".*/, '$1');
fs.writeFileSync(path.join(examplesDocsFolder, file), htmlContent);
const mdFileName = file.replace('.html', '.md');
const isNew = isNewExample(htmlContentLines);
indexArray.push({
title,
description,
mdFileName
mdFileName,
isNew
});
const exampleMarkdown = generateMarkdownForExample(title, description, file, htmlContent);
const exampleMarkdown = generateMarkdownForExample(title, description, file, htmlContent, isNew);
fs.writeFileSync(path.join(examplesDocsFolder, mdFileName), exampleMarkdown);
}

Expand Down
77 changes: 76 additions & 1 deletion docs/assets/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,83 @@
--md-primary-fg-color: #295DAA;
--md-accent-fg-color: #568ad6;
}

[data-md-color-scheme="slate"] {
--md-primary-fg-color: #295DAA;
--md-accent-fg-color: #568ad6;
}

/* Examples overview card grid */
.examples-grid {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opposite of KISS... I'd like to avoid this please.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a way to do this.

How can we get to a more overviewy Overview page without custom styling and without using any of zenzicals features?
It really does not make sense to me why you want this to be worse.

display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}

.example-card {
border: 1px solid var(--md-default-fg-color--lightest);
border-radius: 0.5rem;
text-decoration: none !important;
color: inherit !important;
transition: box-shadow 0.2s, transform 0.2s;
display: flex;
flex-direction: column;
}

.example-card-image {
position: relative;
overflow: hidden;
border-radius: 0.5rem 0.5rem 0 0;
}

.example-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}

[data-md-color-scheme="slate"] .example-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}

.example-card img {
width: 100%;
aspect-ratio: 12 / 5;
object-fit: cover;
display: block;
}

.example-card-content {
padding: 0.6rem 0.8rem;
}

.example-card-content h3 {
margin: 0 0 0.25rem;
font-size: 0.85rem;
font-weight: 600;
}

.example-card-content p {
margin: 0;
font-size: 0.75rem;
color: var(--md-default-fg-color--light);
line-height: 1.4;
}

/* Hide the right sidebar (TOC) when it has no entries */
.md-sidebar--secondary:not(:has(.md-nav__list)) {
display: none;
}

.example-card-badge {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.15rem 0.5rem;
font-size: 0.75rem;
font-weight: 800;
text-transform: uppercase;
background: #285DAA;
color: white;
border-radius: 0.25rem;
}
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@ Note too that if the CSS isn't available by the first render, as soon as the CSS
MapLibre GL JS is also distributed via UNPKG. Our latest version can installed by adding below tags this in the html `<head>`. Further instructions on how to select specific versions and semver ranges can be found on at [unpkg.com](https://unpkg.com).

```html
<script src="https://unpkg.com/maplibre-gl@^5.19.0/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@^5.19.0/dist/maplibre-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/maplibre-gl@^5.22.0/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@^5.22.0/dist/maplibre-gl.css" rel="stylesheet" />
```
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import preferTypeForDataShapes from './build/eslint-rules/prefer-type-for-data-s

export default [
{
ignores: ['build/*.js', 'build/rollup/**', 'staging/**', 'coverage/**', 'node_modules/**', 'docs/**', 'dist/**', '**/*_generated.js']
ignores: ['build/*.js', 'build/rollup/**', 'staging/**', 'coverage/**', 'node_modules/**', 'docs/**', 'dist/**', 'site/**', '**/*_generated.js']
},
{
ignores: ['test/bench/**'],
Expand Down
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.escapeall:
hardbreak: True
nbsp: True
- attr_list
- admonition

extra:
status:
new: Recently added
social:
- icon: fontawesome/brands/bluesky
link: https://bsky.app/profile/maplibre.org
Expand Down
91 changes: 91 additions & 0 deletions test/build/examples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {describe, test, expect} from 'vitest';
import {globSync} from 'glob';
import fs from 'fs';

/**
* Show a snippet of the file around `lineNum` (1-based) with a gutter,
* highlighting the target line. If lineNum is null, shows the first few
* lines of <head> as context.
*/
function snippet(lines: string[], lineNum: number | null, file: string): string {
const target = lineNum ?? (lines.findIndex(l => /<head/i.test(l)) + 1 || 1);
const start = Math.max(0, target - 2);
const end = Math.min(lines.length, target + 2);
const gutterWidth = String(end).length;

const out: string[] = [];
out.push(` --> ${file}:${target}`);
out.push(`${' '.repeat(gutterWidth + 1)} |`);
for (let i = start; i < end; i++) {
const num = String(i + 1).padStart(gutterWidth);
const marker = (i + 1 === target) ? '>' : ' ';
out.push(`${num} ${marker}| ${lines[i]}`);
}
out.push(`${' '.repeat(gutterWidth + 1)} |`);
return out.join('\n');
}

/**
* Find the 1-based line number of the first line matching `pattern`,
* or null if not found.
*/
function findLine(lines: string[], pattern: RegExp): number | null {
const idx = lines.findIndex(l => pattern.test(l));
return idx >= 0 ? idx + 1 : null;
}

describe('Example HTML files', () => {
const exampleFiles = globSync('test/examples/*.html').sort();

test.each(exampleFiles)('%s has required meta tags', (file) => {
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n');
const errors: string[] = [];

// Check og:description
const descriptionMatch = content.match(/<meta\s+property=["']og:description["']\s+content=["']([^"']*)["']/);
if (!descriptionMatch) {
const loc = findLine(lines, /<head/i);
errors.push(
`error: missing \`og:description\` meta tag\n${
snippet(lines, loc, file)}\n` +
' = help: add inside <head>:\n' +
' <meta property="og:description" content="A short description of what this example demonstrates." />'
);
} else if (!descriptionMatch[1].trim()) {
const loc = findLine(lines, /og:description/);
errors.push(
`error: \`og:description\` content is empty\n${
snippet(lines, loc, file)}\n` +
' = help: provide a meaningful description, e.g.:\n' +
' <meta property="og:description" content="Demonstrates how to ..." />'
);
}

// Check og:created
const createdMatch = content.match(/<meta\s+property=["']og:created["']\s+content=["']([^"']*)["']/);
if (!createdMatch) {
const descLine = findLine(lines, /og:description/);
errors.push(
`error: missing \`og:created\` meta tag\n${
snippet(lines, descLine, file)}\n` +
' = help: add right after og:description:\n' +
` <meta property="og:created" content="${new Date().toISOString().slice(0, 10)}" />`
);
} else if (!/^\d{4}-\d{2}-\d{2}$/.test(createdMatch[1])) {
const loc = findLine(lines, /og:created/);
errors.push(
`error: \`og:created\` has invalid date format "${createdMatch[1]}"\n${
snippet(lines, loc, file)}\n` +
' = help: use YYYY-MM-DD format, e.g.:\n' +
' <meta property="og:created" content="2025-10-31" />'
);
}

if (errors.length > 0) {
expect.fail(
`\n${errors.join('\n\n')}\n`
);
}
});
});
1 change: 1 addition & 0 deletions test/examples/3d-terrain.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<title>3D Terrain</title>
<meta property="og:description" content="Go beyond hillshade and show elevation in actual 3D." />
<meta property="og:created" content="2023-06-27" />
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='../../dist/maplibre-gl.css' />
Expand Down
1 change: 1 addition & 0 deletions test/examples/add-3d-tiles-using-threejs.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<title>Add 3D tiles using three.js</title>
<meta property="og:description" content="Use a custom style layer with three.js to add 3D tiles to the map." />
<meta property="og:created" content="2026-03-03" />
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='../../dist/maplibre-gl.css' />
Expand Down
1 change: 1 addition & 0 deletions test/examples/add-a-3d-model-to-globe-using-threejs.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<head>
<title>Add a 3D model to globe using three.js</title>
<meta property="og:description" content="Use a custom style layer with three.js to add a 3D model to a globe." />
<meta property="og:created" content="2025-06-25" />
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='../../dist/maplibre-gl.css' />
Expand Down
1 change: 1 addition & 0 deletions test/examples/add-a-3d-model-using-threejs.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<title>Add a 3D model using three.js</title>
<meta property="og:description" content="Use a custom style layer with three.js to add a 3D model to the map." />
<meta property="og:created" content="2025-06-25" />
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='../../dist/maplibre-gl.css' />
Expand Down
1 change: 1 addition & 0 deletions test/examples/add-a-3d-model-with-babylonjs.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<title>Add a 3D model with babylon.js</title>
<meta property="og:description" content="Use a custom style layer with babylon.js to add a 3D model to the map." />
<meta property="og:created" content="2025-06-25" />
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='../../dist/maplibre-gl.css' />
Expand Down
Loading
Loading