0
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-01-30 22:52:24 +01:00

docs: bundle assets with Hugo

Also:

* load any docs' third-party dependencies from node_modules (except for examples)
* exclude docsearch from layouts that don't use it
* preconnect to algolia only when not examples layout
* switch to `RelPermalink`
* refactor JS assets into partials
This commit is contained in:
XhmikosR 2024-03-08 12:43:44 +02:00
parent e340674d06
commit 24f749cb76
20 changed files with 656 additions and 270 deletions

View File

@ -197,6 +197,15 @@
"unicorn/no-array-for-each": "off"
}
},
{
"files": [
"site/assets/js/**"
],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
}
},
{
"files": [
"**/*.md"

View File

@ -8,8 +8,6 @@ security:
getenv:
- ^HUGO_
- NETLIFY
gotemplates:
allowActionJSTmpl: true
markup:
goldmark:
@ -21,6 +19,9 @@ markup:
startLevel: 2
endLevel: 6
build:
noJSConfigInAssets: true
buildDrafts: true
buildFuture: true
@ -44,6 +45,8 @@ module:
target: layouts
- source: site/static
target: static
- source: node_modules/@docsearch/css
target: assets/scss/@docsearch/css
- source: site/static/docs/5.3/assets/img/favicons/apple-touch-icon.png
target: static/apple-touch-icon.png
- source: site/static/docs/5.3/assets/img/favicons/favicon.ico

308
package-lock.json generated
View File

@ -22,14 +22,17 @@
"@babel/cli": "^7.23.9",
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@docsearch/js": "^3.6.0",
"@popperjs/core": "^2.11.8",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@stackblitz/sdk": "^1.9.0",
"autoprefixer": "^10.4.18",
"bundlewatch": "^0.3.3",
"clean-css-cli": "^5.6.3",
"clipboard": "^2.0.11",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-xo": "^0.44.0",
@ -88,6 +91,180 @@
"integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==",
"dev": true
},
"node_modules/@algolia/autocomplete-core": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz",
"integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-plugin-algolia-insights": "1.9.3",
"@algolia/autocomplete-shared": "1.9.3"
}
},
"node_modules/@algolia/autocomplete-plugin-algolia-insights": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz",
"integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-shared": "1.9.3"
},
"peerDependencies": {
"search-insights": ">= 1 < 3"
}
},
"node_modules/@algolia/autocomplete-preset-algolia": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz",
"integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-shared": "1.9.3"
},
"peerDependencies": {
"@algolia/client-search": ">= 4.9.1 < 6",
"algoliasearch": ">= 4.9.1 < 6"
}
},
"node_modules/@algolia/autocomplete-shared": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz",
"integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==",
"dev": true,
"peerDependencies": {
"@algolia/client-search": ">= 4.9.1 < 6",
"algoliasearch": ">= 4.9.1 < 6"
}
},
"node_modules/@algolia/cache-browser-local-storage": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz",
"integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==",
"dev": true,
"dependencies": {
"@algolia/cache-common": "4.22.1"
}
},
"node_modules/@algolia/cache-common": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz",
"integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==",
"dev": true
},
"node_modules/@algolia/cache-in-memory": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz",
"integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==",
"dev": true,
"dependencies": {
"@algolia/cache-common": "4.22.1"
}
},
"node_modules/@algolia/client-account": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz",
"integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.22.1",
"@algolia/client-search": "4.22.1",
"@algolia/transporter": "4.22.1"
}
},
"node_modules/@algolia/client-analytics": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz",
"integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.22.1",
"@algolia/client-search": "4.22.1",
"@algolia/requester-common": "4.22.1",
"@algolia/transporter": "4.22.1"
}
},
"node_modules/@algolia/client-common": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz",
"integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==",
"dev": true,
"dependencies": {
"@algolia/requester-common": "4.22.1",
"@algolia/transporter": "4.22.1"
}
},
"node_modules/@algolia/client-personalization": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz",
"integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.22.1",
"@algolia/requester-common": "4.22.1",
"@algolia/transporter": "4.22.1"
}
},
"node_modules/@algolia/client-search": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz",
"integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==",
"dev": true,
"dependencies": {
"@algolia/client-common": "4.22.1",
"@algolia/requester-common": "4.22.1",
"@algolia/transporter": "4.22.1"
}
},
"node_modules/@algolia/logger-common": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz",
"integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==",
"dev": true
},
"node_modules/@algolia/logger-console": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz",
"integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==",
"dev": true,
"dependencies": {
"@algolia/logger-common": "4.22.1"
}
},
"node_modules/@algolia/requester-browser-xhr": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz",
"integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==",
"dev": true,
"dependencies": {
"@algolia/requester-common": "4.22.1"
}
},
"node_modules/@algolia/requester-common": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz",
"integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==",
"dev": true
},
"node_modules/@algolia/requester-node-http": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz",
"integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==",
"dev": true,
"dependencies": {
"@algolia/requester-common": "4.22.1"
}
},
"node_modules/@algolia/transporter": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz",
"integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==",
"dev": true,
"dependencies": {
"@algolia/cache-common": "4.22.1",
"@algolia/logger-common": "4.22.1",
"@algolia/requester-common": "4.22.1"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@ -1879,6 +2056,54 @@
"postcss-selector-parser": "^6.0.13"
}
},
"node_modules/@docsearch/css": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz",
"integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==",
"dev": true
},
"node_modules/@docsearch/js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz",
"integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==",
"dev": true,
"dependencies": {
"@docsearch/react": "3.6.0",
"preact": "^10.0.0"
}
},
"node_modules/@docsearch/react": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz",
"integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==",
"dev": true,
"dependencies": {
"@algolia/autocomplete-core": "1.9.3",
"@algolia/autocomplete-preset-algolia": "1.9.3",
"@docsearch/css": "3.6.0",
"algoliasearch": "^4.19.1"
},
"peerDependencies": {
"@types/react": ">= 16.8.0 < 19.0.0",
"react": ">= 16.8.0 < 19.0.0",
"react-dom": ">= 16.8.0 < 19.0.0",
"search-insights": ">= 1 < 3"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"search-insights": {
"optional": true
}
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -2596,6 +2821,12 @@
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
"dev": true
},
"node_modules/@stackblitz/sdk": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@stackblitz/sdk/-/sdk-1.9.0.tgz",
"integrity": "sha512-3m6C7f8pnR5KXys/Hqx2x6ylnpqOak6HtnZI6T5keEO0yT+E4Spkw37VEbdwuC+2oxmjdgq6YZEgiKX7hM1GmQ==",
"dev": true
},
"node_modules/@stylistic/stylelint-config": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-1.0.1.tgz",
@ -3013,6 +3244,28 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/algoliasearch": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz",
"integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==",
"dev": true,
"dependencies": {
"@algolia/cache-browser-local-storage": "4.22.1",
"@algolia/cache-common": "4.22.1",
"@algolia/cache-in-memory": "4.22.1",
"@algolia/client-account": "4.22.1",
"@algolia/client-analytics": "4.22.1",
"@algolia/client-common": "4.22.1",
"@algolia/client-personalization": "4.22.1",
"@algolia/client-search": "4.22.1",
"@algolia/logger-common": "4.22.1",
"@algolia/logger-console": "4.22.1",
"@algolia/requester-browser-xhr": "4.22.1",
"@algolia/requester-common": "4.22.1",
"@algolia/requester-node-http": "4.22.1",
"@algolia/transporter": "4.22.1"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -4028,6 +4281,17 @@
"node": ">=4"
}
},
"node_modules/clipboard": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
"dev": true,
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@ -4421,6 +4685,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"dev": true
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -6119,6 +6389,15 @@
"integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
"dev": true
},
"node_modules/good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
"dev": true,
"dependencies": {
"delegate": "^3.1.2"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@ -9147,6 +9426,16 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"node_modules/preact": {
"version": "10.19.6",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz",
"integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -9905,6 +10194,13 @@
"node": ">=18"
}
},
"node_modules/search-insights": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz",
"integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==",
"dev": true,
"peer": true
},
"node_modules/seek-bzip": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
@ -9924,6 +10220,12 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
"dev": true
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -11076,6 +11378,12 @@
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
},
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"dev": true
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",

View File

@ -82,7 +82,7 @@
"docs-serve": "hugo server --port 9001 --disableFastRender --noHTTPCache --renderToMemory --printPathWarnings --printUnusedTemplates",
"docs-serve-only": "npx sirv-cli _site --port 9001",
"lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json",
"update-deps": "ncu -u -x jasmine,karma-browserstack-launcher,karma-rollup-preprocessor && echo Manually update site/assets/js/vendor",
"update-deps": "ncu -u -x jasmine,karma-browserstack-launcher,karma-rollup-preprocessor",
"release": "npm-run-all dist release-sri docs-build release-zip*",
"release-sri": "node build/generate-sri.mjs",
"release-version": "node build/change-version.mjs",
@ -106,14 +106,17 @@
"@babel/cli": "^7.23.9",
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@docsearch/js": "^3.6.0",
"@popperjs/core": "^2.11.8",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@stackblitz/sdk": "^1.9.0",
"autoprefixer": "^10.4.18",
"bundlewatch": "^0.3.3",
"clean-css-cli": "^5.6.3",
"clipboard": "^2.0.11",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-xo": "^0.44.0",

View File

@ -9,22 +9,12 @@
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
(() => {
'use strict'
/* eslint-disable import/no-unresolved */
import sidebarScroll from 'js/partials/sidebar.js'
import codeExamples from 'js/partials/code-examples.js'
import snippets from 'js/partials/snippets.js'
/* eslint-enable import/no-unresolved */
// Scroll the active sidebar link into view
const sidenav = document.querySelector('.bd-sidebar')
const sidenavActiveLink = document.querySelector('.bd-links-nav .active')
if (sidenav && sidenavActiveLink) {
const sidenavHeight = sidenav.clientHeight
const sidenavActiveLinkTop = sidenavActiveLink.offsetTop
const sidenavActiveLinkHeight = sidenavActiveLink.clientHeight
const viewportTop = sidenavActiveLinkTop
const viewportBottom = viewportTop - sidenavHeight + sidenavActiveLinkHeight
if (sidenav.scrollTop > viewportTop || sidenav.scrollTop < viewportBottom) {
sidenav.scrollTop = viewportTop - (sidenavHeight / 2) + (sidenavActiveLinkHeight / 2)
}
}
})()
sidebarScroll()
codeExamples()
snippets()

View File

@ -2,18 +2,18 @@
// IT'S ALL JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++
/*!
/*
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
/* global ClipboardJS: false, bootstrap: false */
/* global bootstrap: false */
(() => {
'use strict'
import ClipboardJS from 'clipboard'
export default () => {
// Insert copy to clipboard button before .highlight
const btnTitle = 'Copy to clipboard'
const btnEdit = 'Edit on StackBlitz'
@ -87,4 +87,4 @@
tooltipBtn.setContent({ '.tooltip-inner': btnTitle })
}, { once: true })
})
})()
}

View File

@ -0,0 +1,30 @@
// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
// IT'S ALL JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++
/*
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
export default () => {
// Scroll the active sidebar link into view
const sidenav = document.querySelector('.bd-sidebar')
const sidenavActiveLink = document.querySelector('.bd-links-nav .active')
if (!sidenav || !sidenavActiveLink) {
return
}
const sidenavHeight = sidenav.clientHeight
const sidenavActiveLinkTop = sidenavActiveLink.offsetTop
const sidenavActiveLinkHeight = sidenavActiveLink.clientHeight
const viewportTop = sidenavActiveLinkTop
const viewportBottom = viewportTop - sidenavHeight + sidenavActiveLinkHeight
if (sidenav.scrollTop > viewportTop || sidenav.scrollTop < viewportBottom) {
sidenav.scrollTop = viewportTop - (sidenavHeight / 2) + (sidenavActiveLinkHeight / 2)
}
}

View File

@ -0,0 +1,168 @@
// NOTICE!!! Initially embedded in our docs this JavaScript
// file contains elements that can help you create reproducible
// use cases in StackBlitz for instance.
// In a real project please adapt this content to your needs.
// ++++++++++++++++++++++++++++++++++++++++++
/*
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
/* global bootstrap: false */
export default () => {
// --------
// Tooltips
// --------
// Instantiate all tooltips in a docs or StackBlitz
document.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach(tooltip => {
new bootstrap.Tooltip(tooltip)
})
// --------
// Popovers
// --------
// Instantiate all popovers in docs or StackBlitz
document.querySelectorAll('[data-bs-toggle="popover"]')
.forEach(popover => {
new bootstrap.Popover(popover)
})
// -------------------------------
// Toasts
// -------------------------------
// Used by 'Placement' example in docs or StackBlitz
const toastPlacement = document.getElementById('toastPlacement')
if (toastPlacement) {
document.getElementById('selectToastPlacement').addEventListener('change', function () {
if (!toastPlacement.dataset.originalClass) {
toastPlacement.dataset.originalClass = toastPlacement.className
}
toastPlacement.className = `${toastPlacement.dataset.originalClass} ${this.value}`
})
}
// Instantiate all toasts in docs pages only
document.querySelectorAll('.bd-example .toast')
.forEach(toastNode => {
const toast = new bootstrap.Toast(toastNode, {
autohide: false
})
toast.show()
})
// Instantiate all toasts in docs pages only
// js-docs-start live-toast
const toastTrigger = document.getElementById('liveToastBtn')
const toastLiveExample = document.getElementById('liveToast')
if (toastTrigger) {
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
toastTrigger.addEventListener('click', () => {
toastBootstrap.show()
})
}
// js-docs-end live-toast
// -------------------------------
// Alerts
// -------------------------------
// Used in 'Show live alert' example in docs or StackBlitz
// js-docs-start live-alert
const alertPlaceholder = document.getElementById('liveAlertPlaceholder')
const appendAlert = (message, type) => {
const wrapper = document.createElement('div')
wrapper.innerHTML = [
`<div class="alert alert-${type} alert-dismissible" role="alert">`,
` <div>${message}</div>`,
' <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
'</div>'
].join('')
alertPlaceholder.append(wrapper)
}
const alertTrigger = document.getElementById('liveAlertBtn')
if (alertTrigger) {
alertTrigger.addEventListener('click', () => {
appendAlert('Nice, you triggered this alert message!', 'success')
})
}
// js-docs-end live-alert
// --------
// Carousels
// --------
// Instantiate all non-autoplaying carousels in docs or StackBlitz
document.querySelectorAll('.carousel:not([data-bs-ride="carousel"])')
.forEach(carousel => {
bootstrap.Carousel.getOrCreateInstance(carousel)
})
// -------------------------------
// Checks & Radios
// -------------------------------
// Indeterminate checkbox example in docs and StackBlitz
document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')
.forEach(checkbox => {
if (checkbox.id.includes('Indeterminate')) {
checkbox.indeterminate = true
}
})
// -------------------------------
// Links
// -------------------------------
// Disable empty links in docs examples only
document.querySelectorAll('.bd-content [href="#"]')
.forEach(link => {
link.addEventListener('click', event => {
event.preventDefault()
})
})
// -------------------------------
// Modal
// -------------------------------
// Modal 'Varying modal content' example in docs and StackBlitz
// js-docs-start varying-modal-content
const exampleModal = document.getElementById('exampleModal')
if (exampleModal) {
exampleModal.addEventListener('show.bs.modal', event => {
// Button that triggered the modal
const button = event.relatedTarget
// Extract info from data-bs-* attributes
const recipient = button.getAttribute('data-bs-whatever')
// If necessary, you could initiate an Ajax request here
// and then do the updating in a callback.
// Update the modal's content.
const modalTitle = exampleModal.querySelector('.modal-title')
const modalBodyInput = exampleModal.querySelector('.modal-body input')
modalTitle.textContent = `New message to ${recipient}`
modalBodyInput.value = recipient
})
}
// js-docs-end varying-modal-content
// -------------------------------
// Offcanvas
// -------------------------------
// 'Offcanvas components' example in docs only
const myOffcanvas = document.querySelectorAll('.bd-example-offcanvas .offcanvas')
if (myOffcanvas) {
myOffcanvas.forEach(offcanvas => {
offcanvas.addEventListener('show.bs.offcanvas', event => {
event.preventDefault()
}, false)
})
}
}

View File

@ -2,18 +2,25 @@
// IT'S ALL JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++
(() => {
'use strict'
/*!
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
import docsearch from '@docsearch/js'
(() => {
const searchElement = document.getElementById('docsearch')
if (!window.docsearch || !searchElement) {
if (!searchElement) {
return
}
const siteDocsVersion = searchElement.getAttribute('data-bd-docs-version')
window.docsearch({
docsearch({
apiKey: '3151f502c7b9e9dafd5e6372b691a24e',
indexName: 'bootstrap',
appId: 'AK7KMZKZHQ',

View File

@ -1,170 +1,15 @@
// NOTICE!!! Initially embedded in our docs this JavaScript
// file contains elements that can help you create reproducible
// use cases in StackBlitz for instance.
// In a real project please adapt this content to your needs.
// ++++++++++++++++++++++++++++++++++++++++++
/*!
/*
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
/* global bootstrap: false */
// Note that this file is not published; we only include it in scrpts.html
// for StackBlitz to work
(() => {
'use strict'
/* eslint-disable import/no-unresolved */
import snippets from 'js/partials/snippets.js'
/* eslint-enable import/no-unresolved */
// --------
// Tooltips
// --------
// Instantiate all tooltips in a docs or StackBlitz
document.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach(tooltip => {
new bootstrap.Tooltip(tooltip)
})
// --------
// Popovers
// --------
// Instantiate all popovers in docs or StackBlitz
document.querySelectorAll('[data-bs-toggle="popover"]')
.forEach(popover => {
new bootstrap.Popover(popover)
})
// -------------------------------
// Toasts
// -------------------------------
// Used by 'Placement' example in docs or StackBlitz
const toastPlacement = document.getElementById('toastPlacement')
if (toastPlacement) {
document.getElementById('selectToastPlacement').addEventListener('change', function () {
if (!toastPlacement.dataset.originalClass) {
toastPlacement.dataset.originalClass = toastPlacement.className
}
toastPlacement.className = `${toastPlacement.dataset.originalClass} ${this.value}`
})
}
// Instantiate all toasts in docs pages only
document.querySelectorAll('.bd-example .toast')
.forEach(toastNode => {
const toast = new bootstrap.Toast(toastNode, {
autohide: false
})
toast.show()
})
// Instantiate all toasts in docs pages only
// js-docs-start live-toast
const toastTrigger = document.getElementById('liveToastBtn')
const toastLiveExample = document.getElementById('liveToast')
if (toastTrigger) {
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
toastTrigger.addEventListener('click', () => {
toastBootstrap.show()
})
}
// js-docs-end live-toast
// -------------------------------
// Alerts
// -------------------------------
// Used in 'Show live alert' example in docs or StackBlitz
// js-docs-start live-alert
const alertPlaceholder = document.getElementById('liveAlertPlaceholder')
const appendAlert = (message, type) => {
const wrapper = document.createElement('div')
wrapper.innerHTML = [
`<div class="alert alert-${type} alert-dismissible" role="alert">`,
` <div>${message}</div>`,
' <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
'</div>'
].join('')
alertPlaceholder.append(wrapper)
}
const alertTrigger = document.getElementById('liveAlertBtn')
if (alertTrigger) {
alertTrigger.addEventListener('click', () => {
appendAlert('Nice, you triggered this alert message!', 'success')
})
}
// js-docs-end live-alert
// --------
// Carousels
// --------
// Instantiate all non-autoplaying carousels in docs or StackBlitz
document.querySelectorAll('.carousel:not([data-bs-ride="carousel"])')
.forEach(carousel => {
bootstrap.Carousel.getOrCreateInstance(carousel)
})
// -------------------------------
// Checks & Radios
// -------------------------------
// Indeterminate checkbox example in docs and StackBlitz
document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')
.forEach(checkbox => {
if (checkbox.id.includes('Indeterminate')) {
checkbox.indeterminate = true
}
})
// -------------------------------
// Links
// -------------------------------
// Disable empty links in docs examples only
document.querySelectorAll('.bd-content [href="#"]')
.forEach(link => {
link.addEventListener('click', event => {
event.preventDefault()
})
})
// -------------------------------
// Modal
// -------------------------------
// Modal 'Varying modal content' example in docs and StackBlitz
// js-docs-start varying-modal-content
const exampleModal = document.getElementById('exampleModal')
if (exampleModal) {
exampleModal.addEventListener('show.bs.modal', event => {
// Button that triggered the modal
const button = event.relatedTarget
// Extract info from data-bs-* attributes
const recipient = button.getAttribute('data-bs-whatever')
// If necessary, you could initiate an Ajax request here
// and then do the updating in a callback.
// Update the modal's content.
const modalTitle = exampleModal.querySelector('.modal-title')
const modalBodyInput = exampleModal.querySelector('.modal-body input')
modalTitle.textContent = `New message to ${recipient}`
modalBodyInput.value = recipient
})
}
// js-docs-end varying-modal-content
// -------------------------------
// Offcanvas
// -------------------------------
// 'Offcanvas components' example in docs only
const myOffcanvas = document.querySelectorAll('.bd-example-offcanvas .offcanvas')
if (myOffcanvas) {
myOffcanvas.forEach(offcanvas => {
offcanvas.addEventListener('show.bs.offcanvas', event => {
event.preventDefault()
}, false)
})
}
})()
snippets()

View File

@ -0,0 +1,67 @@
// NOTICE!!! Initially embedded in our docs this JavaScript
// file contains elements that can help you create reproducible
// use cases in StackBlitz for instance.
// In a real project please adapt this content to your needs.
// ++++++++++++++++++++++++++++++++++++++++++
/*!
* JavaScript for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
* For details, see https://creativecommons.org/licenses/by/3.0/.
*/
import sdk from '@stackblitz/sdk'
// https://gohugo.io/hugo-pipes/js/#options
import {
cssCdn, docsVersion, jsBundleCdn, jsSnippetFile
} from '@params' // eslint-disable-line import/no-unresolved
// Open in StackBlitz logic
document.querySelectorAll('.btn-edit').forEach(btn => {
btn.addEventListener('click', event => {
const codeSnippet = event.target.closest('.bd-code-snippet')
const exampleEl = codeSnippet.querySelector('.bd-example')
const htmlSnippet = exampleEl.innerHTML
const jsSnippet = codeSnippet.querySelector('.btn-edit').getAttribute('data-sb-js-snippet')
// Get extra classes for this example
const classes = Array.from(exampleEl.classList).join(' ')
sdk.openBootstrapSnippet(htmlSnippet, jsSnippet, classes)
})
})
sdk.openBootstrapSnippet = (htmlSnippet, jsSnippet, classes) => {
const markup = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="${cssCdn}" rel="stylesheet">
<link href="https://getbootstrap.com/docs/${docsVersion}/assets/css/docs.css" rel="stylesheet">
<title>Bootstrap Example</title>
<${'script'} src="${jsBundleCdn}"></${'script'}>
</head>
<body class="p-3 m-0 border-0 ${classes}">
<!-- Example Code -->
${htmlSnippet.replace(/^/gm, ' ')}
<!-- End Example Code -->
</body>
</html>`
const jsSnippetContent = jsSnippet ? jsSnippetFile : null
const project = {
files: {
'index.html': markup,
'index.js': jsSnippetContent
},
title: 'Bootstrap Example',
description: `Official example from ${window.location.href}`,
template: jsSnippet ? 'javascript' : 'html',
tags: ['bootstrap']
}
sdk.openProject(project, { openFile: 'index.html' })
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,7 @@
// stylelint-disable selector-class-pattern
@import "@docsearch/css/dist/style";
:root {
--docsearch-primary-color: var(--bd-violet);
--docsearch-logo-color: var(--bd-violet);

View File

@ -38,7 +38,7 @@ Click the button below to show an alert (hidden with inline styles to start), th
We use the following JavaScript to trigger our live alert demo:
{{< js-docs name="live-alert" file="site/assets/js/snippets.js" >}}
{{< js-docs name="live-alert" file="site/assets/js/partials/snippets.js" >}}
### Link color

View File

@ -481,7 +481,7 @@ Below is a live demo followed by example HTML and JavaScript. For more informati
</div>
{{< /example >}}
{{< js-docs name="varying-modal-content" file="site/assets/js/snippets.js" >}}
{{< js-docs name="varying-modal-content" file="site/assets/js/partials/snippets.js" >}}
### Toggle between modals

View File

@ -87,7 +87,7 @@ Click the button below to show a toast (positioned with our utilities in the low
We use the following JavaScript to trigger our live toast demo:
{{< js-docs name="live-toast" file="site/assets/js/snippets.js" >}}
{{< js-docs name="live-toast" file="site/assets/js/partials/snippets.js" >}}
### Translucent

View File

@ -46,4 +46,4 @@ sitemap_exclude: true
{{< scss-docs name="variable-gradient" file="scss/_variables.scss" >}}
{{< js-docs name="live-toast" file="site/assets/js/snippets.js" >}}
{{< js-docs name="live-toast" file="site/assets/js/partials/snippets.js" >}}

View File

@ -7,11 +7,13 @@
<meta name="docsearch:language" content="en">
<meta name="docsearch:version" content="{{ .Site.Params.docs_version }}">
<title>{{ if .IsHome }}{{ .Site.Title | markdownify }} · {{ .Site.Params.subtitle | markdownify }}{{ else }}{{ .Title | markdownify }} · {{ .Site.Title | markdownify }} v{{ .Site.Params.docs_version }}{{ end }}</title>
<link rel="canonical" href="{{ .Permalink }}">
<link rel="preconnect" href="https://AK7KMZKZHQ-dsn.algolia.net" crossorigin>
{{ if (ne .Page.Layout "examples") -}}
<link rel="preconnect" href="https://ak7kmzkzhq-dsn.algolia.net" crossorigin>
{{- end }}
<title>{{ if .IsHome }}{{ .Site.Title | markdownify }} · {{ .Site.Params.subtitle | markdownify }}{{ else }}{{ .Title | markdownify }} · {{ .Site.Title | markdownify }} v{{ .Site.Params.docs_version }}{{ end }}</title>
{{ with .Params.robots -}}
<meta name="robots" content="{{ . }}">

View File

@ -4,70 +4,31 @@
<script src="/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.js"></script>
{{- end }}
<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@3"></script>
{{ if eq .Page.Layout "docs" -}}
<script src="https://cdn.jsdelivr.net/npm/@stackblitz/sdk@1/bundles/sdk.umd.js"></script>
{{- end }}
{{- $vendor := resources.Match "js/vendor/*.js" -}}
{{- $js := resources.Match "js/*.js" -}}
{{- $targetDocsJSPath := path.Join "/docs" .Site.Params.docs_version "assets/js/docs.js" -}}
{{- $docsJs := append $js $vendor | resources.Concat $targetDocsJSPath -}}
{{- $esbuildOptions := dict "target" "es2019" -}}
{{- $targetDocsJSPath := path.Join "/docs" .Site.Params.docs_version -}}
{{- if hugo.IsProduction -}}
{{- $docsJs = $docsJs | resources.Minify -}}
{{- $esbuildOptions = merge $esbuildOptions (dict "minify" "true") -}}
{{- end }}
<script src="{{ $docsJs.Permalink | relURL }}"></script>
{{- $applicationJs := resources.Get "js/application.js" | js.Build $esbuildOptions | resources.Copy (path.Join $targetDocsJSPath "/assets/js/application.js") }}
<script src="{{ $applicationJs.RelPermalink }}"></script>
{{- if (ne .Page.Layout "examples") -}}
{{- $searchJs := resources.Get "js/search.js" | js.Build $esbuildOptions | resources.Copy (path.Join $targetDocsJSPath "/assets/js/search.js") }}
<script async src="{{ $searchJs.RelPermalink }}"></script>
{{- end -}}
{{ if eq .Page.Layout "docs" -}}
<script>
// Open in StackBlitz logic
document.querySelectorAll('.btn-edit').forEach(btn => {
btn.addEventListener('click', event => {
const htmlSnippet = event.target.closest('.bd-code-snippet').querySelector('.bd-example').innerHTML
// Get extra classes for this example
const classes = Array.from(event.target.closest('.bd-code-snippet').querySelector('.bd-example').classList).join(' ')
const jsSnippet = event.target.closest('.bd-code-snippet').querySelector('.btn-edit').getAttribute('data-sb-js-snippet')
StackBlitzSDK.openBootstrapSnippet(htmlSnippet, jsSnippet, classes)
})
})
StackBlitzSDK.openBootstrapSnippet = (htmlSnippet, jsSnippet, classes) => {
const markup = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="{{ .Site.Params.cdn.css }}" rel="stylesheet">
<link href="https://getbootstrap.com/docs/{{ .Site.Params.docs_version }}/assets/css/docs.css" rel="stylesheet">
<title>Bootstrap Example</title>
<${'script'} src="{{ .Site.Params.cdn.js_bundle }}"></${'script'}>
</head>
<body class="p-3 m-0 border-0 ${classes}">
<!-- Example Code -->
${htmlSnippet.replace(/^/gm, ' ')}
<!-- End Example Code -->
</body>
</html>`
const jsSnippetContent = jsSnippet ? '{{ os.ReadFile "site/assets/js/snippets.js" }}' : null
const project = {
files: {
'index.html': markup,
'index.js': jsSnippetContent
},
title: 'Bootstrap Example',
description: `Official example from ${window.location.href}`,
template: jsSnippet ? 'javascript' : 'html',
tags: ['bootstrap']
}
StackBlitzSDK.openProject(project, { openFile: 'index.html' })
}
</script>
{{- end }}
{{- /* Disable minify options for snippets.js so that the file's readable on StackBlitz */ -}}
{{- $snippetsFile := resources.Get "js/snippets.js" | js.Build (merge $esbuildOptions (dict "minify" "false")) -}}
{{- $esbuildParams := dict
"cssCdn" .Site.Params.cdn.css
"jsBundleCdn" .Site.Params.cdn.js_bundle
"docsVersion" .Site.Params.docs_version
"jsSnippetFile" $snippetsFile.Content
-}}
{{- $esbuildOptions = merge $esbuildOptions (dict "params" $esbuildParams) -}}
{{- $stackblitzJs := resources.Get "js/stackblitz.js" | js.Build $esbuildOptions | resources.Copy (path.Join $targetDocsJSPath "/assets/js/stackblitz.js") }}
<script async src="{{ $stackblitzJs.RelPermalink }}"></script>
{{- end -}}

View File

@ -1,5 +1,3 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">
{{ if hugo.IsProduction -}}
{{ if eq .Page.Params.direction "rtl" -}}
<link href="/docs/{{ .Site.Params.docs_version }}/dist/css/bootstrap.rtl.min.css" rel="stylesheet" {{ printf "integrity=%q" .Site.Params.cdn.css_rtl_hash | safeHTMLAttr }}>
@ -21,5 +19,5 @@
{{- $style := resources.Get "scss/docs.scss" | toCSS $sassOptions | postCSS $postcssOptions }}
<link href="{{ $style.Permalink | relURL }}" rel="stylesheet">
<link href="{{ $style.RelPermalink }}" rel="stylesheet">
{{- end }}