├── .github ├── CODE_OF_CONDUCT.md └── workflows │ ├── node.yml │ └── npm-publish.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── assets ├── curveball-logo-wordmark.svg ├── curveball-logo.svg ├── curveball.svg ├── icon │ ├── acl.svg │ ├── authenticate.svg │ ├── authenticated-as.svg │ ├── author.svg │ ├── code-repository.svg │ ├── create-form.svg │ ├── describedby.svg │ ├── down.svg │ ├── edit.svg │ ├── help.svg │ ├── home.svg │ ├── logout.svg │ ├── next.svg │ ├── previous.svg │ ├── register-user.svg │ ├── search.svg │ └── up.svg ├── js │ ├── .gitignore │ └── .npmignore └── themes │ ├── curveball │ ├── highlight.css │ └── main.css │ ├── halloween │ ├── highlight.css │ └── main.css │ ├── lfo │ ├── highlight.css │ └── main.css │ ├── spicy-oj │ ├── highlight.css │ └── main.css │ └── xmas │ ├── highlight.css │ └── main.css ├── changelog.md ├── data ├── editor-links.json ├── iana-links.json └── level3-rest-links.json ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── screenshots ├── 0.2.0.png ├── 0.3.0.png ├── 0.5.0.png ├── 0.7.0-csv.png ├── 0.7.0-form.png ├── 0.7.0.png ├── 0.8.6-csv.png ├── 0.8.6-form.png ├── 0.8.6.png ├── 0.9.1-csv.png ├── 0.9.1-form.png └── 0.9.1.png ├── src ├── components │ ├── alternate.tsx │ ├── app.tsx │ ├── body.tsx │ ├── csv-body.tsx │ ├── description.tsx │ ├── embedded.tsx │ ├── forms.tsx │ ├── forms │ │ ├── button.tsx │ │ ├── ketting-action-button.tsx │ │ ├── ketting-action.tsx │ │ └── templated-links.tsx │ ├── hal-body.tsx │ ├── json-viewer.tsx │ ├── links-table.tsx │ ├── markdown-body.tsx │ ├── navigation.tsx │ ├── pager.tsx │ ├── resource.tsx │ └── search.tsx ├── html-index.tsx ├── index.ts ├── types.ts └── util.ts ├── test ├── json-test.ts └── templated-uri.ts ├── tsconfig.json └── util └── fetch-link-relation-data.mjs /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at me@evertpot.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js Test Runner 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | node-test: 14 | name: Node.js tests 15 | 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 10 18 | 19 | strategy: 20 | matrix: 21 | node-version: [18.x, 20.x, 22.x] 22 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - run: npm ci 31 | - run: npm run build --if-present 32 | - run: npm test 33 | 34 | lint: 35 | name: Lint 36 | 37 | runs-on: ubuntu-latest 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Use Node.js 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: 18.x 45 | - run: npm ci 46 | - run: npm run lint 47 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish NPM package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 18 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: 18 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | 35 | publish-gpr: 36 | needs: build 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: actions/setup-node@v4 41 | with: 42 | node-version: 18 43 | - run: npm ci 44 | - uses: actions/setup-node@v4 45 | with: 46 | node-version: 18 47 | registry-url: 'https://npm.pkg.github.com' 48 | scope: '@curveball' 49 | - run: npm publish 50 | env: 51 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | /node_modules 3 | 4 | # typescript output 5 | /dist 6 | 7 | # Directory used for running tests in CommonJS mode 8 | /cjs-test 9 | 10 | # vim 11 | .*.swp 12 | 13 | # nyc 14 | /.nyc_output 15 | 16 | # bun! 17 | /bun.lockb 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2025 Evert Pot. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCE_FILES:=$(shell find src/ -type f -name '*.ts') 2 | TEST_FILES:=$(shell find test/ -type f -name '*.ts') 3 | 4 | .PHONY:all 5 | all: build 6 | 7 | .PHONY:build 8 | build: dist/build assets 9 | 10 | .PHONY:test 11 | test: 12 | npx tsx --test ${TEST_FILES} 13 | 14 | .PHONY:lint 15 | lint: 16 | npx eslint --quiet 17 | 18 | .PHONY:lint-fix 19 | lint-fix: fix 20 | 21 | .PHONY:fix 22 | fix: 23 | npx eslint --quiet --fix 24 | 25 | .PHONY:watch 26 | watch: 27 | npx tsc --watch 28 | 29 | .PHONY:start 30 | start: build 31 | 32 | .PHONY:clean 33 | clean: 34 | rm -rf dist 35 | 36 | dist/build: $(SOURCE_FILES) 37 | npx tsc 38 | touch dist/build 39 | 40 | .PHONY:assets 41 | assets: assets/js/html-form-enhancer.js assets/js/serialize-json-form.js 42 | 43 | assets/js/html-form-enhancer.js: node_modules/html-form-enhancer/dist/html-form-enhancer.js 44 | mkdir -p assets/js 45 | cp node_modules/html-form-enhancer/dist/html-form-enhancer.* assets/js 46 | 47 | assets/js/serialize-json-form.js: node_modules/html-form-enhancer/dist/serialize-json-form.js 48 | cp node_modules/html-form-enhancer/dist/serialize-json-form.* assets/js 49 | 50 | data/iana-links.json: 51 | node util/fetch-link-relation-data.mjs > data/iana-links.json 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Curveball Browser 2 | ================= 3 | 4 | This package provides a middleware that automatically turns JSON responses 5 | from an API into HTML responses. 6 | 7 | It will do so by looking if the API was accessed by a HTTP client that prefers 8 | HTML. Browsers do this by sending an `Accept: text/html` header. 9 | 10 | If this middleware spots this, it will kick in and auto-generate a great looking 11 | HTML document. 12 | 13 | If this header was not provides, this middleware does nothing. 14 | 15 | It automatically decorates the following formats: 16 | 17 | * `application/json` 18 | * `application/problem+json` 19 | * `application/hal+json` 20 | * `text/markdown` 21 | * `text/csv` 22 | * `application/prs.hal-forms+json` 23 | * `application/vnd.siren+json` 24 | 25 | Screenshot 26 | ---------- 27 | 28 | An example. If an API normally returns the following HAL format: 29 | 30 | ```json 31 | { 32 | "_links": { 33 | "self": { "href": "/testing" }, 34 | "previous": { 35 | "href": "/testing/?page=1", 36 | "title": "Previous page" 37 | }, 38 | "next": { 39 | "href": "/testing/?page=2", 40 | "title": "Next page" 41 | }, 42 | "author": { 43 | "href": "https://evertpot.com", 44 | "title": "Evert Pot" 45 | }, 46 | "help": { 47 | "href": "https://google.com/", 48 | "title": "Google it" 49 | }, 50 | "search": { 51 | "href": "https://google.com/{?q}", 52 | "templated": true 53 | }, 54 | "edit": { "href": "/testing" }, 55 | "create-form": { "href": "/testing" }, 56 | "my-link": { 57 | "href": "/foo-bar", 58 | "title": "Custom link" 59 | }, 60 | "alternate": [ 61 | { 62 | "href": "/testing/markdown", 63 | "type": "text/markdown", 64 | "title": "Markdown test" 65 | }, 66 | { 67 | "href": "/testing/csv", 68 | "type": "text/csv", 69 | "title": "Csv test" 70 | }, 71 | { 72 | "href": "/testing/rss", 73 | "type": "application/rss+xml", 74 | "title": "RSS" 75 | }, 76 | { 77 | "href": "/testing/rss", 78 | "type": "application/atom+xml", 79 | "title": "Atom" 80 | } 81 | ], 82 | "code-repository": { "href": "https://github.com/evert/hal-browser" }, 83 | "redirect-test": { "href": "/redirect-test" } 84 | }, 85 | "msg": "Hello world!", 86 | "version": "0.5.0", 87 | "name": "test resource!" 88 | } 89 | ``` 90 | 91 | The browser will automatically convert it to this HTML format: 92 | 93 | ![Screenshot from 0.9.1](https://github.com/curveball/browser/blob/main/screenshots/0.9.1.png) 94 | 95 | This screenshot is an example of the browser automatically formatting a .csv 96 | and parsing HTTP `Link` headers: 97 | 98 | ![Screenshot from 0.9.1](https://github.com/curveball/browser/blob/main/screenshots/0.9.1-csv.png) 99 | 100 | The following example converts this: 101 | 102 | ```json 103 | { 104 | "_links": { 105 | "self": { 106 | "href": "/testing/form" 107 | }, 108 | "up": { 109 | "href": "/testing", 110 | "title": "Back to testing home" 111 | }, 112 | "my-form": { 113 | "href": "/testing/form{?startDate}{?endDate}", 114 | "title": "Search by date range", 115 | "templated": true 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | And automatically turns the templated link into a form: 122 | 123 | ![Screenshot from 0.9.1](https://github.com/curveball/browser/blob/main/screenshots/0.9.1-form.png) 124 | 125 | 126 | Installation 127 | ------------ 128 | 129 | npm install @curveball/browser 130 | 131 | 132 | Getting started 133 | --------------- 134 | 135 | ```typescript 136 | import { Application } from 'curveball/@core'; 137 | import browser from '@curveball/browser'; 138 | 139 | const app = new Application(); 140 | app.use(browser({})); 141 | ``` 142 | 143 | 144 | ### Options 145 | 146 | The halBrowser function takes an options object, which can take the following 147 | settings: 148 | 149 | * `title` - Change the main title. 150 | * `theme` - `curveball` by default, but `lfo` and `spicy-oj` are also provided. 151 | * `stylesheets` - Provide your own stylesheets. This is an array of strings. 152 | these are relative urls, and they are automatically expanded based on the 153 | `assetBaseUrl` setting. 154 | * `navigationLinks` - Specify (or remove) links that show up in the top 155 | navigation. 156 | * `serveAssets` - by default the browser plugin will also take responsibility 157 | for serving icons and stylesheet. If you're hosting these assets elsewhere, 158 | set this to `false`. 159 | * `defaultLinks` - A list of links that will show up by default, whether or not 160 | they were specified by the API. By default a `home` link is added here. 161 | * `hiddenRels` - List of relationship types that will be hidden from the user by 162 | default. This can be used for links that are simply not interesting for a human 163 | to see. (default: `['self', 'curies']`. 164 | * `fullBody` - If turned on, full JSON bodies are always rendered. This can also 165 | be turned on during runtime by adding a `?_browser-fullbody` query parameter. 166 | * `allLinks` - By default the Browser will hide links from the 'Links' table 167 | that will be rendered as 'navigation buttons', forms (templated links), or are 168 | considered special (the 'self' link). While this might be a nicer interface 169 | for an average user browsing the hypermedia graph, as a developer you might 170 | just want to see all the links. 171 | 172 | Example: 173 | 174 | ```javascript 175 | app.use(browser({ 176 | title: 'My API', 177 | stylesheets: [ 178 | '/my-stylesheet.css', 179 | ], 180 | 181 | // This should end with a / generally. 182 | assetBaseUrl: 'http://some-cdn.example.org/', 183 | 184 | navigationLinks: { 185 | // Create new 'author' button 186 | 'author' : { 187 | // optional css class, by default this will be `rel-author` 188 | cssClass: 'rel-blabla', 189 | 190 | // Optional title to show when hovering over button 191 | defaultTitle: 'Click me', 192 | 193 | // Override icon. Also optional 194 | icon: 'icons/foobar.svg', 195 | 196 | // Either 'header' (default) or 'pager' 197 | position: 'header' 198 | 199 | // Set the order. Lower is earlier. Default is 0. 200 | priority: -100, 201 | 202 | }, 203 | // passing 'true' will use default setting for the button 204 | 'help' : true, 205 | 206 | // passing 'null' will remove the icon, if it was a default icon 207 | 'up': null, 208 | }, 209 | 210 | defaultLinks: [ 211 | // Every page will have a 'help' link 212 | { 213 | rel: 'help', 214 | href: 'https://example.org/help', 215 | title: 'Support', 216 | } 217 | ], 218 | }); 219 | ``` 220 | 221 | Future features 222 | --------------- 223 | 224 | * Add a link to allow the user to see the raw format. 225 | * Show metadata, such as `Last-Modified` 226 | 227 | [1]: https://github.com/curveballjs/core 228 | [2]: https://expressjs.com/ 229 | [3]: https://koajs.com/ 230 | [4]: https://github.com/isagalaev/highlight.js/ 231 | [5]: https://github.com/evert/hal-browser-express/ 232 | -------------------------------------------------------------------------------- /assets/curveball-logo-wordmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 19 | 25 | 26 | 27 | 29 | 30 | 32 | image/svg+xml 33 | 35 | 36 | 37 | 38 | 41 | 44 | 48 | 51 | 54 | 60 | 66 | 72 | 76 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /assets/curveball.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 49 | 51 | 55 | 59 | 60 | 63 | 67 | 68 | 72 | 76 | 77 | 81 | 85 | 86 | 89 | 93 | 94 | 97 | 101 | 102 | 106 | 110 | 111 | 115 | 119 | 120 | 124 | 128 | 129 | 132 | 136 | 137 | 140 | 144 | 145 | 148 | 152 | 153 | 156 | 160 | 161 | 164 | 168 | 169 | 173 | 177 | 178 | 182 | 186 | 187 | 191 | 195 | 196 | 200 | 204 | 205 | 209 | 213 | 214 | 217 | 221 | 222 | 225 | 231 | 232 | 233 | 235 | 236 | 238 | image/svg+xml 239 | 241 | 242 | 243 | 244 | 245 | 248 | 253 | 256 | 259 | 265 | 271 | 277 | 281 | 285 | 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /assets/icon/acl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon/authenticate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon/authenticated-as.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon/author.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/code-repository.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/create-form.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/describedby.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/logout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/previous.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/register-user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/js/.gitignore: -------------------------------------------------------------------------------- 1 | /html-form-enhancer.js 2 | /html-form-enhancer.js.map 3 | /html-form-enhancer.d.ts 4 | /serialize-json-form.js 5 | /serialize-json-form.js.map 6 | /serialize-json-form.d.ts 7 | -------------------------------------------------------------------------------- /assets/js/.npmignore: -------------------------------------------------------------------------------- 1 | # Intentionally left empty to ensure that NPM does not ignore 2 | # the files from .gitignore 3 | -------------------------------------------------------------------------------- /assets/themes/curveball/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; color: #ddd; 10 | } 11 | 12 | .hljs-tag, 13 | .hljs-keyword, 14 | .hljs-selector-tag, 15 | .hljs-literal, 16 | .hljs-strong, 17 | .hljs-name { 18 | color: #f92672; 19 | } 20 | 21 | .hljs-code { 22 | color: #66d9ef; 23 | } 24 | 25 | .hljs-class .hljs-title { 26 | color: white; 27 | } 28 | 29 | .hljs-attribute, 30 | .hljs-symbol, 31 | .hljs-regexp, 32 | .hljs-link { 33 | color: #bf79db; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-bullet, 38 | .hljs-subst, 39 | .hljs-title, 40 | .hljs-section, 41 | .hljs-emphasis, 42 | .hljs-type, 43 | .hljs-built_in, 44 | .hljs-builtin-name, 45 | .hljs-selector-attr, 46 | .hljs-selector-pseudo, 47 | .hljs-addition, 48 | .hljs-variable, 49 | .hljs-template-tag, 50 | .hljs-template-variable { 51 | color: #a6e22e; 52 | } 53 | 54 | .hljs-comment, 55 | .hljs-quote, 56 | .hljs-deletion, 57 | .hljs-meta { 58 | color: #75715e; 59 | } 60 | 61 | .hljs-keyword, 62 | .hljs-selector-tag, 63 | .hljs-literal, 64 | .hljs-doctag, 65 | .hljs-title, 66 | .hljs-section, 67 | .hljs-type, 68 | .hljs-selector-id { 69 | font-weight: bold; 70 | } 71 | -------------------------------------------------------------------------------- /assets/themes/curveball/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body, td, input, th { 6 | font-family: "Roboto", "Ubuntu", sans-serif; 7 | color: #444; 8 | } 9 | 10 | .hljs, code { 11 | font-family: 'JetBrains Mono', 'Fira Code', 'Inconsolata', 'Ubuntu Mono', monospace; 12 | } 13 | 14 | p, td, th, li { 15 | line-height: 150%; 16 | } 17 | 18 | body, h1, h2, h3, h4, h5, form, pre { 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | header { 24 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 25 | padding: 0 20px 0 90px; 26 | color: white; 27 | background: #1b1c3c; 28 | background-image: url('../../curveball.svg'); 29 | background-size: 70px; 30 | background-position: 5px center; 31 | background-repeat: no-repeat; 32 | display: flex; 33 | align-items: center; 34 | } 35 | 36 | h1 { 37 | font-weight: 300; 38 | line-height: 100px; 39 | vertical-align: middle; 40 | 41 | flex: 1; 42 | display: flex; 43 | justify-content: space-between; 44 | 45 | } 46 | 47 | h2 { 48 | font-weight: 300; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | 54 | table td, table th { 55 | border-bottom: 1px solid #ddd; 56 | padding: 5px 50px 5px 0; 57 | 58 | } 59 | 60 | table th { 61 | text-align: left; 62 | } 63 | 64 | h1 .divider { 65 | display: none; 66 | } 67 | 68 | header a { 69 | color: white; 70 | text-decoration: none; 71 | } 72 | 73 | ul.button-actions { 74 | display: flex; 75 | flex-direction: row; 76 | gap: 8px; 77 | 78 | list-style: none; 79 | margin: 0; 80 | padding: 0; 81 | } 82 | 83 | /* search */ 84 | form.search { 85 | 86 | background: #fff5; 87 | display: flex; 88 | border-radius: 10px; 89 | padding: 2px 5px; 90 | margin-left: 20px 91 | 92 | } 93 | 94 | form.search img { 95 | filter: invert(100%); 96 | } 97 | 98 | form.search input { 99 | background: none; 100 | border: none; 101 | width: 100px; 102 | transition: width 0.2s; 103 | color: white; 104 | font-size: 16px; 105 | } 106 | form.search input:focus { 107 | width: 200px; 108 | } 109 | 110 | nav.top-nav { 111 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 2px 0 rgba(0,0,0,0.12),0 2px 2px -1px rgba(0,0,0,0.3); 112 | height: 60px; 113 | padding: 2px 15px; 114 | } 115 | 116 | nav { 117 | display: flex; 118 | justify-content: space-between; 119 | } 120 | 121 | nav a { 122 | color: white; 123 | text-decoration: none; 124 | padding: 3px; 125 | min-width: 100px; 126 | display: inline-block; 127 | border-radius: 3px; 128 | } 129 | 130 | nav a:hover { 131 | 132 | box-shadow: 1px 1px 2px #ccc; 133 | } 134 | 135 | nav ul { 136 | list-style: none; 137 | display: flex; 138 | margin: 0; 139 | padding: 0; 140 | } 141 | 142 | nav ul:first-child { 143 | flex: 1; 144 | } 145 | 146 | nav li { 147 | padding: 10px 0; 148 | line-height: 100%; 149 | } 150 | 151 | nav li a { 152 | display: flex; 153 | flex-direction: column; 154 | align-items: center; 155 | 156 | color: #555; 157 | text-decoration: none; 158 | font-size: 80%; 159 | } 160 | nav li a img { 161 | height: 16px; 162 | margin-bottom: 5px; 163 | } 164 | 165 | 166 | nav .alternate { 167 | display: flex; 168 | align-items: center; 169 | padding-left: 30px 170 | } 171 | 172 | nav .alternate h3 { 173 | font-weight: 300; 174 | padding-right: 10px; 175 | font-size: 16px; 176 | } 177 | nav .alternate a { 178 | min-width: 1px; 179 | margin-left: 3px; 180 | font-size: 90%; 181 | background: #555; 182 | display: inline-block; 183 | border-radius: 2px; 184 | padding: 6px; 185 | } 186 | nav .alternate .type-csv { 187 | background: #057212; 188 | } 189 | nav .alternate .type-feed { 190 | background: #ff6600; 191 | } 192 | 193 | main { 194 | padding: 10px 30px 10px 30px; 195 | } 196 | 197 | main h2 { 198 | padding: 20px 0 15px; 199 | } 200 | 201 | /* A link for definitions. This usually shows up softer */ 202 | a.definition { 203 | text-decoration: underline; 204 | text-decoration-style: dotted; 205 | color: inherit; 206 | } 207 | 208 | /* These are little badges that show up next to links, like the methods they support or 'deprecated' */ 209 | .link-badge { 210 | background: #666; 211 | color: white; 212 | border-radius: 3px; 213 | padding: 2px 4px; 214 | font-weight: bold; 215 | margin-left: 3px; 216 | } 217 | 218 | .status-deprecated { background: #d93b3b; } 219 | .status-gone { background: #d93b3b; } 220 | .status-experimental { background: #3e57d6; } 221 | 222 | .method-post { background: #2365a8; } 223 | .method-get { background: #5c945f; } 224 | .method-put { background: #469e98; } 225 | .method-delete { background: #d93b3b; } 226 | .method-patch { background: #7a36ad; } 227 | .method-search { background: #9c1497; } 228 | .method-query { background: #aaaa44; } 229 | 230 | main code { 231 | border-radius: 2px; 232 | font-size: 18px; 233 | } 234 | 235 | 236 | /* Body */ 237 | .body-markdown > * { 238 | padding: 0; 239 | border: 0; 240 | margin: 10px 0 20px; 241 | font-size: 18px; 242 | } 243 | 244 | .body-markdown h1 { 245 | font-size: 40px; 246 | } 247 | 248 | .body-markdown h2 { 249 | font-size: 30px; 250 | } 251 | 252 | .body-markdown h3 { 253 | font-size: 25px; 254 | } 255 | 256 | .body-markdown h4 { 257 | font-size: 20px; 258 | } 259 | 260 | .body-markdown ul, .body-markdown ol { 261 | 262 | margin: 30px; 263 | 264 | } 265 | 266 | .body-markdown code { 267 | margin-left: 0; 268 | } 269 | 270 | .body-csv { 271 | overflow-x: scroll; 272 | } 273 | 274 | .body-csv table { 275 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 276 | overflow-x: auto; 277 | } 278 | 279 | .body-csv td, .body-csv th { 280 | white-space: nowrap; 281 | padding: 5px 50px 5px 5px; 282 | } 283 | 284 | /* Embedded */ 285 | details { 286 | margin: 5px 0 20px; 287 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 288 | border: 1px solid #BDBDBD; 289 | border-radius: 4px; 290 | } 291 | 292 | details summary { 293 | padding: 15px; 294 | } 295 | 296 | form.long-form h3, form.long-form h1 { 297 | grid-column: 1 / 3; 298 | font-size: 16px; 299 | } 300 | 301 | form.long-form { 302 | padding: 0 0 15px; 303 | display: grid; 304 | grid-template-columns: max-content max-content max-content; 305 | grid-gap: 20px; 306 | font-size: 16px; 307 | max-width: 600px; 308 | padding: 20px 0; 309 | margin: 0; 310 | border-radius: 2px; 311 | } 312 | 313 | form.long-form .checkboxes { 314 | grid-column: 2 / 3; 315 | } 316 | 317 | form.long-form label { 318 | grid-column: 1; 319 | text-align: right; 320 | margin: 0; 321 | align-self: center; 322 | padding: 0; 323 | } 324 | 325 | form.long-form input, form.long-form .buttonRow { 326 | grid-column: 2; 327 | } 328 | 329 | form.long-form input { 330 | background: rgba(0,0,0,.04); 331 | border-width: 0 0 1px 0; 332 | border-style: solid; 333 | border-color: #8c8c8c; 334 | padding: 10px; 335 | border-radius: 4px 4px 0 0; 336 | } 337 | 338 | button { 339 | color: white; 340 | border: 0px solid; 341 | min-height: 10px; 342 | padding: 0 25px; 343 | line-height: 2; 344 | border-radius: .25ex; 345 | } 346 | 347 | .hljs ul { 348 | list-style: none; 349 | padding-left: 10px; 350 | margin: 0; 351 | } 352 | .hljs ul li { 353 | padding-left: 10px; 354 | margin: 0; 355 | line-height: normal; 356 | } 357 | .hljs a { 358 | color: inherit; 359 | } 360 | 361 | .hljs details { 362 | border: 0; 363 | margin: 0; 364 | padding: 0; 365 | box-shadow: none; 366 | } 367 | 368 | .hljs summary { 369 | margin: 0; 370 | padding: 0; 371 | } 372 | 373 | .hljs details[open] .hidden-when-open { 374 | display: none; 375 | } 376 | 377 | .hljs summary .teaser { 378 | border: 1px dotted #ccc; 379 | } 380 | 381 | .hidden-copy-paste { 382 | font-size: 0; 383 | } 384 | 385 | 386 | -------------------------------------------------------------------------------- /assets/themes/halloween/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Halloween theme! Hand-written by Evert 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; color: #ddd; 10 | } 11 | 12 | .hljs-tag, 13 | .hljs-keyword, 14 | .hljs-selector-tag, 15 | .hljs-literal, 16 | .hljs-strong, 17 | .hljs-name { 18 | color: #FF8000; 19 | } 20 | 21 | .hljs-code { 22 | color: #66d9ef; 23 | } 24 | 25 | .hljs-class .hljs-title { 26 | color: white; 27 | } 28 | 29 | .hljs-attribute, 30 | .hljs-symbol, 31 | .hljs-regexp, 32 | .hljs-link { 33 | color: #bf79db; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-bullet, 38 | .hljs-subst, 39 | .hljs-title, 40 | .hljs-section, 41 | .hljs-emphasis, 42 | .hljs-type, 43 | .hljs-built_in, 44 | .hljs-builtin-name, 45 | .hljs-selector-attr, 46 | .hljs-selector-pseudo, 47 | .hljs-addition, 48 | .hljs-variable, 49 | .hljs-template-tag, 50 | .hljs-template-variable { 51 | color: #CC00F0; 52 | } 53 | 54 | .hljs-comment, 55 | .hljs-quote, 56 | .hljs-deletion, 57 | .hljs-meta { 58 | color: #75715e; 59 | } 60 | 61 | .hljs-keyword, 62 | .hljs-selector-tag, 63 | .hljs-literal, 64 | .hljs-doctag, 65 | .hljs-title, 66 | .hljs-section, 67 | .hljs-type, 68 | .hljs-selector-id { 69 | font-weight: bold; 70 | } 71 | -------------------------------------------------------------------------------- /assets/themes/halloween/main.css: -------------------------------------------------------------------------------- 1 | @import url(../curveball/main.css); 2 | 3 | header { 4 | background-color: #000000; 5 | background-image: none; 6 | padding-left: 15px; 7 | } 8 | 9 | header::before { 10 | content: "🎃"; 11 | padding-right: 10px; 12 | font-size: 40px; 13 | } 14 | 15 | nav.top-nav { 16 | background: #FF8000; 17 | } 18 | 19 | nav.top-nav li a { 20 | color: white; 21 | } 22 | nav.top-nav li a img { 23 | filter: invert(1) 24 | } 25 | 26 | .status-deprecated::before { 27 | content: "🪦" 28 | } 29 | -------------------------------------------------------------------------------- /assets/themes/lfo/highlight.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Night Eighties Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 4 | 5 | /* Tomorrow Comment */ 6 | .hljs-comment, 7 | .hljs-quote { 8 | color: #999999; 9 | } 10 | 11 | /* Tomorrow Red */ 12 | .hljs-variable, 13 | .hljs-template-variable, 14 | .hljs-tag, 15 | .hljs-name, 16 | .hljs-selector-id, 17 | .hljs-selector-class, 18 | .hljs-regexp, 19 | .hljs-deletion { 20 | color: #f2777a; 21 | } 22 | 23 | /* Tomorrow Orange */ 24 | .hljs-number, 25 | .hljs-built_in, 26 | .hljs-builtin-name, 27 | .hljs-literal, 28 | .hljs-type, 29 | .hljs-params, 30 | .hljs-meta, 31 | .hljs-link { 32 | color: #f99157; 33 | } 34 | 35 | /* Tomorrow Yellow */ 36 | .hljs-attribute { 37 | color: #ffcc66; 38 | } 39 | 40 | /* Tomorrow Green */ 41 | .hljs-string, 42 | .hljs-symbol, 43 | .hljs-bullet, 44 | .hljs-addition { 45 | color: #99cc99; 46 | } 47 | 48 | /* Tomorrow Blue */ 49 | .hljs-title, 50 | .hljs-section { 51 | color: #6699cc; 52 | } 53 | 54 | /* Tomorrow Purple */ 55 | .hljs-keyword, 56 | .hljs-selector-tag { 57 | color: #cc99cc; 58 | } 59 | 60 | .hljs { 61 | display: block; 62 | overflow-x: auto; 63 | background: #2d2d2d; 64 | color: #cccccc; 65 | padding: 0.5em; 66 | } 67 | 68 | .hljs-emphasis { 69 | font-style: italic; 70 | } 71 | 72 | .hljs-strong { 73 | font-weight: bold; 74 | } 75 | -------------------------------------------------------------------------------- /assets/themes/lfo/main.css: -------------------------------------------------------------------------------- 1 | /* Palette generated by Material Palette - materialpalette.com/light-blue/deep-orange */ 2 | :root { 3 | --dark-primary-color : #0288D1; 4 | --default-primary-color : #03A9F4; 5 | --light-primary-color : #B3E5FC; 6 | --text-primary-color : #FFFFFF; 7 | --accent-color : #FF5722; 8 | --dark-accent-color : #D84315; 9 | --primary-text-color : #212121; 10 | --secondary-text-color : #757575; 11 | --divider-color : #BDBDBD; 12 | --warning-color : #F44336; 13 | } 14 | 15 | body, pre, h1, h2, h3, h4, p, table, ul, ol { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | h1 { 21 | font-weight: 300; 22 | font-size: 2em; 23 | } 24 | 25 | body { 26 | font-size: 16px; 27 | font-family: "Roboto", "Helvetica", "Ubuntu Sans", "Arial", sans-serif; 28 | background: #FAFAFA; 29 | font-weight: 300; 30 | box-sizing: border-box; 31 | color: #212121; 32 | } 33 | 34 | header { 35 | /* background: #03A9F4; */ 36 | height: 30px; 37 | color: var(--dark-primary-color); 38 | padding: 2rem; 39 | display: flex; 40 | box-shadow: 0 1px 0 0 #F5F5F5; 41 | position: sticky; 42 | top: 0; 43 | background: white; 44 | z-index: 1; 45 | } 46 | 47 | header h1 { 48 | flex: 1; 49 | color: inherit; 50 | } 51 | 52 | header h1 a { 53 | color: inherit; 54 | text-decoration: none; 55 | box-shadow: 0 1px 0 0 currentColor; 56 | transition: box-shadow .25s; 57 | } 58 | 59 | header h1 a:hover { 60 | box-shadow: 0 1px 0 0 transparent; 61 | } 62 | 63 | main { 64 | padding: 2rem; 65 | } 66 | 67 | /* main */ 68 | 69 | main a { 70 | color: var(--default-primary-color); 71 | transition: text-decoration .5s; 72 | } 73 | 74 | main a:hover { 75 | color: var(--dark-primary-color); 76 | text-decoration-color: transparent; 77 | } 78 | 79 | main a:visited { color:#673AB7} 80 | main a:visited:hover { color:#9C27B0} 81 | 82 | h2 { 83 | margin: 0; 84 | padding: 1ex .5rem; 85 | font-weight: 300; 86 | font-size: 200%; 87 | /* border-top: 1px solid #BDBDBD; */ 88 | } 89 | 90 | * + h2 { 91 | margin: 2em 0 0; 92 | border-top: 1px solid transparent; 93 | } 94 | 95 | 96 | code { 97 | font-family: Monaco, "Lucida Console", "Ubuntu Mono", monospace; 98 | padding: 3px; 99 | border: 1px dashed #ccc; 100 | } 101 | 102 | code.hljs { 103 | padding: 1rem; 104 | font-size: inherit; 105 | border: 0; 106 | background: #2d2d2d; 107 | border-radius: .5ex; 108 | color: #cccccc; 109 | display: block; 110 | overflow-x: auto; 111 | } 112 | 113 | table { 114 | border-collapse: collapse; 115 | margin: 1rem 0; 116 | display: flex; 117 | flex: 1; 118 | } 119 | 120 | th, td { 121 | font-size: 1em; 122 | text-align: left; 123 | vertical-align: top; 124 | padding: 0 1.5ex; 125 | line-height: 2.5em; 126 | transition: background .05s; 127 | /* border-left: 1px solid #eee; */ 128 | } 129 | 130 | tr:not(:last-child) { 131 | border-bottom: 1px solid #E0E0E0; 132 | } 133 | 134 | tr:hover td:not([rowspan]), 135 | tr:hover td[rowspan="1"] { 136 | background: #F5F5F5; 137 | } 138 | 139 | td a { 140 | display: block; 141 | } 142 | 143 | 144 | /* Navigation bar */ 145 | nav { 146 | padding: 1em 2rem; 147 | word-spacing: 5px; 148 | display: flex; 149 | box-shadow: 0 1px 1ex 0 #0000001f; 150 | position: sticky; 151 | top: 5.9rem; 152 | background: white; 153 | } 154 | nav h3 { 155 | word-spacing: 0; 156 | } 157 | nav ul { 158 | list-style: none; 159 | display: block; 160 | word-spacing: 1px; 161 | } 162 | nav ul:nth-of-type(2) { 163 | margin-left: auto; 164 | } 165 | nav li { 166 | display: inline; 167 | } 168 | 169 | nav a { 170 | display: inline-block; 171 | border-radius: 3px; 172 | background: var(--default-primary-color); 173 | padding: 6px; 174 | text-decoration: none; 175 | color: #fff; 176 | font-size: 15px; 177 | line-height: 20px; 178 | transition: background-color .25s; 179 | font-weight: 500; 180 | } 181 | 182 | nav a:hover { 183 | background: var(--dark-primary-color); 184 | } 185 | 186 | nav a img { 187 | width: 22px; 188 | filter: invert(100%); 189 | vertical-align: middle; 190 | margin-top: -2px; 191 | } 192 | 193 | 194 | .link-badge { 195 | --color: var(--text-primary-color); 196 | color: var(--color); 197 | background: transparent; 198 | border-radius: .5ex; 199 | border: 1px solid var(--color); 200 | padding: .5ex 1ex; 201 | font-weight: bold; 202 | margin-left: .5ex; 203 | margin-right: .5ex; 204 | 205 | } 206 | 207 | .link-badge.status-deprecated, 208 | .link-badge.status-gone { 209 | --color: var(--warning-color); 210 | } 211 | 212 | .link-badge.link-badge.status-deprecated::before { 213 | content: '\26A0'; 214 | margin-right: 1ex; 215 | } 216 | 217 | /* Alternate links */ 218 | nav .alternate { 219 | display: flex; 220 | align-items: center; 221 | padding-left: 30px; 222 | } 223 | 224 | nav .alternate h3 { 225 | font-weight: 300; 226 | padding-right: 10px; 227 | } 228 | nav .alternate a { 229 | font-size: 90%; 230 | font-weight: 700; 231 | background: #607D8B; 232 | } 233 | nav .alternate .type-csv { 234 | background: #4CAF50; 235 | } 236 | nav .alternate .type-feed { 237 | background: #FF9800; 238 | } 239 | 240 | /* search */ 241 | form.search { 242 | display: flex; 243 | border: 1px solid #0288D1; 244 | border-radius: 10px; 245 | padding: 2px 5px; 246 | 247 | } 248 | 249 | form.search input { 250 | background: none; 251 | border: none; 252 | width: 100px; 253 | transition: width 0.2s; 254 | } 255 | 256 | form.search input:focus { 257 | width: 200px; 258 | } 259 | 260 | /* Links table */ 261 | table.links td a { 262 | display: block; 263 | max-width: 700px; 264 | overflow: hidden; 265 | text-overflow: ellipsis; 266 | white-space: nowrap; 267 | } 268 | 269 | /* Body */ 270 | .body-markdown { 271 | padding: 25px; 272 | } 273 | 274 | .body-markdown > * { 275 | padding: 0; 276 | border: 0; 277 | margin: 10px 0 20px; 278 | font-size: 18px; 279 | } 280 | 281 | .body-markdown h1 { 282 | font-size: 40px; 283 | } 284 | 285 | .body-markdown h2 { 286 | font-size: 30px; 287 | } 288 | 289 | .body-markdown h3 { 290 | font-size: 25px; 291 | } 292 | 293 | .body-markdown h4 { 294 | font-size: 20px; 295 | } 296 | 297 | .body-markdown ul, .body-markdown ol { 298 | 299 | margin: 30px; 300 | 301 | } 302 | 303 | .body-markdown code { 304 | margin-left: 0; 305 | } 306 | 307 | .body-csv { 308 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 309 | overflow-x: auto; 310 | } 311 | 312 | .body-csv td { 313 | white-space: nowrap; 314 | } 315 | 316 | /* Embedded */ 317 | details { 318 | margin: 5px 25px 20px; 319 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 320 | border: 1px solid #BDBDBD; 321 | border-radius: 4px; 322 | 323 | } 324 | 325 | details summary { 326 | padding: 15px; 327 | font-size: 120%; 328 | } 329 | 330 | form.long-form h3 { 331 | grid-column: 1 / 3; 332 | } 333 | 334 | form.long-form { 335 | padding: 15px; 336 | display: grid; 337 | grid-template-columns: max-content max-content max-content; 338 | grid-gap: 20px; 339 | font-size: 18px; 340 | max-width: 600px; 341 | padding: 20px; 342 | margin: 10px; 343 | border-radius: 2px; 344 | } 345 | 346 | form.long-form label { 347 | grid-column: 1; 348 | text-align: right; 349 | margin: 0; 350 | align-self: center; 351 | padding: 0; 352 | } 353 | 354 | form.long-form input, form.long-form button { 355 | grid-column: 2; 356 | } 357 | 358 | form.long-form input { 359 | background: rgba(0,0,0,.04); 360 | border-width: 0 0 1px 0; 361 | border-style: solid; 362 | border-color: #8c8c8c; 363 | padding: 10px; 364 | border-radius: 4px 4px 0 0; 365 | } 366 | -------------------------------------------------------------------------------- /assets/themes/spicy-oj/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Intellij Idea-like styling (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #000; 12 | } 13 | 14 | .hljs-subst, 15 | .hljs-title { 16 | font-weight: normal; 17 | color: #000; 18 | } 19 | 20 | .hljs-comment, 21 | .hljs-quote { 22 | color: #808080; 23 | font-style: italic; 24 | } 25 | 26 | .hljs-meta { 27 | color: #808000; 28 | } 29 | 30 | .hljs-tag { 31 | background: #efefef; 32 | } 33 | 34 | .hljs-section, 35 | .hljs-name, 36 | .hljs-literal, 37 | .hljs-keyword, 38 | .hljs-selector-tag, 39 | .hljs-type, 40 | .hljs-selector-id, 41 | .hljs-selector-class { 42 | font-weight: bold; 43 | color: #000080; 44 | } 45 | 46 | .hljs-attribute, 47 | .hljs-number, 48 | .hljs-regexp, 49 | .hljs-link { 50 | font-weight: bold; 51 | color: #0000ff; 52 | } 53 | 54 | .hljs-number, 55 | .hljs-regexp, 56 | .hljs-link { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-string { 61 | color: #008000; 62 | font-weight: bold; 63 | } 64 | 65 | .hljs-symbol, 66 | .hljs-bullet, 67 | .hljs-formula { 68 | color: #000; 69 | background: #d0eded; 70 | font-style: italic; 71 | } 72 | 73 | .hljs-doctag { 74 | text-decoration: underline; 75 | } 76 | 77 | .hljs-variable, 78 | .hljs-template-variable { 79 | color: #660e7a; 80 | } 81 | 82 | .hljs-addition { 83 | background: #baeeba; 84 | } 85 | 86 | .hljs-deletion { 87 | background: #ffc8bd; 88 | } 89 | 90 | .hljs-emphasis { 91 | font-style: italic; 92 | } 93 | 94 | .hljs-strong { 95 | font-weight: bold; 96 | } 97 | -------------------------------------------------------------------------------- /assets/themes/spicy-oj/main.css: -------------------------------------------------------------------------------- 1 | /* Palette generated by Material Palette - materialpalette.com/light-blue/deep-orange */ 2 | /* 3 | .dark-primary-color { background: #0288D1; } 4 | .default-primary-color { background: #03A9F4; } 5 | .light-primary-color { background: #B3E5FC; } 6 | .text-primary-color { color: #FFFFFF; } 7 | .accent-color { background: #FF5722; } 8 | .dark-accent-color { background: #D84315 } 9 | .primary-text-color { color: #212121; } 10 | .secondary-text-color { color: #757575; } 11 | .divider-color { border-color: #BDBDBD; } 12 | */ 13 | 14 | body, pre, h1, h2, h3, h4, p, table, ul, ol { 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | h1 { 20 | font-weight: 300; 21 | } 22 | 23 | body { 24 | font-size: 13px; 25 | font-family: "Roboto", "Helvetica", "Ubuntu Sans", "Arial", sans-serif; 26 | background: #FAFAFA; 27 | font-weight: 300; 28 | box-sizing: border-box; 29 | color: #212121; 30 | } 31 | 32 | header { 33 | background: #03A9F4; 34 | height: 30px; 35 | color: #FFF; 36 | border-top: 20px solid #0288D1; 37 | padding: 15px; 38 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 39 | display: flex; 40 | } 41 | 42 | header h1 { 43 | flex: 1; 44 | } 45 | 46 | header h1 a { 47 | color: #FFF; 48 | text-decoration: none; 49 | } 50 | 51 | h2 { 52 | margin: 10px 0px 10px; 53 | padding: 10px 25px; 54 | font-weight: 300; 55 | font-size: 200%; 56 | border-top: 1px solid #BDBDBD; 57 | } 58 | 59 | code { 60 | font-family: Monaco, "Lucida Console", "Ubuntu Mono", monospace; 61 | padding: 3px; 62 | border: 1px dashed #ccc; 63 | } 64 | 65 | code.hljs { 66 | padding: 5px; 67 | margin: 5px 25px; 68 | font-size: 120%; 69 | border: 0; 70 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 71 | } 72 | 73 | table { 74 | border-collapse: collapse; 75 | margin: 5px 25px 20px; 76 | } 77 | 78 | th, td { 79 | font-size: 15px; 80 | text-align: left; 81 | vertical-align: top; 82 | padding: 10px 10px; 83 | } 84 | 85 | tr { 86 | border-bottom: 1px solid #BDBDBD; 87 | } 88 | 89 | /* Navigation bar */ 90 | nav { 91 | padding: 15px; 92 | word-spacing: 5px; 93 | display: flex; 94 | } 95 | nav h3 { 96 | word-spacing: 0; 97 | } 98 | nav ul { 99 | list-style: none; 100 | display: block; 101 | word-spacing: 1px; 102 | } 103 | nav ul:nth-of-type(2) { 104 | margin-left: auto; 105 | } 106 | nav li { 107 | display: inline; 108 | } 109 | 110 | nav a { 111 | display: inline-block; 112 | border-radius: 3px; 113 | background: #FF5722; 114 | padding: 6px; 115 | box-shadow: 0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2); 116 | text-decoration: none; 117 | color: #fff; 118 | font-size: 15px; 119 | vertical-align: center; 120 | line-height: 20px; 121 | } 122 | 123 | nav a:hover { 124 | background: #D84315 125 | } 126 | 127 | nav a img { 128 | width: 22px; 129 | filter: invert(100%); 130 | vertical-align: middle; 131 | margin-top: -2px; 132 | } 133 | 134 | .link-badge { 135 | background: #666; 136 | color: white; 137 | border-radius: 3px; 138 | padding: 2px 4px; 139 | font-weight: bold; 140 | } 141 | 142 | .link-badge.status-deprecated { 143 | background: red; 144 | } 145 | .link-badge.status-gone { 146 | background: red; 147 | } 148 | 149 | /* Alternate links */ 150 | 151 | nav .alternate { 152 | display: flex; 153 | align-items: center; 154 | padding-left: 30px; 155 | } 156 | 157 | nav .alternate h3 { 158 | font-weight: 300; 159 | padding-right: 10px; 160 | } 161 | nav .alternate a { 162 | font-size: 90%; 163 | font-weight: 700; 164 | background: #555; 165 | } 166 | nav .alternate .type-csv { 167 | background: #057212; 168 | } 169 | nav .alternate .type-feed { 170 | background: #ff6600; 171 | } 172 | 173 | /* search */ 174 | form.search { 175 | 176 | background: #0288D1; 177 | display: flex; 178 | border-radius: 10px; 179 | padding: 2px 5px; 180 | 181 | } 182 | 183 | form.search input { 184 | background: none; 185 | border: none; 186 | width: 100px; 187 | transition: width 0.2s; 188 | } 189 | 190 | form.search input:focus { 191 | width: 200px; 192 | } 193 | 194 | /* Body */ 195 | .body-markdown { 196 | padding: 25px; 197 | } 198 | 199 | .body-markdown > * { 200 | padding: 0; 201 | border: 0; 202 | margin: 10px 0 20px; 203 | font-size: 18px; 204 | } 205 | 206 | .body-markdown h1 { 207 | font-size: 40px; 208 | } 209 | 210 | .body-markdown h2 { 211 | font-size: 30px; 212 | } 213 | 214 | .body-markdown h3 { 215 | font-size: 25px; 216 | } 217 | 218 | .body-markdown h4 { 219 | font-size: 20px; 220 | } 221 | 222 | .body-markdown ul, .body-markdown ol { 223 | 224 | margin: 30px; 225 | 226 | } 227 | 228 | .body-markdown code { 229 | margin-left: 0; 230 | } 231 | 232 | .body-csv { 233 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 234 | overflow-x: scroll; 235 | } 236 | 237 | .body-csv td { 238 | white-space: nowrap; 239 | } 240 | 241 | /* Embedded */ 242 | details { 243 | margin: 5px 25px 20px; 244 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3); 245 | border: 1px solid #BDBDBD; 246 | border-radius: 4px; 247 | 248 | } 249 | 250 | details summary { 251 | padding: 15px; 252 | font-size: 120%; 253 | } 254 | 255 | form.long-form h3 { 256 | grid-column: 1 / 3; 257 | } 258 | 259 | form.long-form { 260 | padding: 15px; 261 | display: grid; 262 | grid-template-columns: max-content max-content max-content; 263 | grid-gap: 20px; 264 | font-size: 18px; 265 | max-width: 600px; 266 | padding: 20px; 267 | margin: 10px; 268 | border-radius: 2px; 269 | } 270 | 271 | form.long-form label { 272 | grid-column: 1; 273 | text-align: right; 274 | margin: 0; 275 | align-self: center; 276 | padding: 0; 277 | } 278 | 279 | form.long-form input, form.long-form button { 280 | grid-column: 2; 281 | } 282 | 283 | form.long-form input { 284 | background: rgba(0,0,0,.04); 285 | border-width: 0 0 1px 0; 286 | border-style: solid; 287 | border-color: #8c8c8c; 288 | padding: 10px; 289 | border-radius: 4px 4px 0 0; 290 | } 291 | -------------------------------------------------------------------------------- /assets/themes/xmas/highlight.css: -------------------------------------------------------------------------------- 1 | @import url(../curveball/highlight.css); 2 | -------------------------------------------------------------------------------- /assets/themes/xmas/main.css: -------------------------------------------------------------------------------- 1 | @import url(../curveball/main.css); 2 | 3 | header { 4 | background-color: #004400; 5 | background-image: none; 6 | padding-left: 15px; 7 | } 8 | header::before { 9 | content: "🎄"; 10 | padding-right: 10px; 11 | font-size: 40px; 12 | } 13 | 14 | nav { 15 | background: repeating-linear-gradient( 16 | 45deg, 17 | #440000, 18 | #660000 10px, 19 | #440000 10px, 20 | #660000 20px 21 | ); color: white; 22 | } 23 | 24 | nav li a { 25 | color: white; 26 | } 27 | nav li a img { 28 | filter: invert(1) 29 | } 30 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.2.0 (2025-04-05) 5 | ------------------ 6 | 7 | * Adding support for the 'friend' rel from XFN. 8 | * Update to Ketting 8, React 19 9 | 10 | 11 | 1.1.6 (2025-01-08) 12 | ------------------ 13 | 14 | * Fix CSS class name. 15 | 16 | 17 | 1.1.5 (2025-01-08) 18 | ------------------ 19 | 20 | * Fix layout when multiple single-button actions are rendered. 21 | 22 | 23 | 1.1.4 (2024-12-15) 24 | ------------------ 25 | 26 | * Make sure html-form-enhancer is included in npm build. 27 | 28 | 29 | 1.1.3 (2024-12-06) 30 | ------------------ 31 | 32 | * Added support for tooltips for c2pa-manifest, compression-dictionary, 33 | ice-server and depreciation. 34 | 35 | 36 | 1.1.2 (2024-12-06) 37 | ------------------ 38 | 39 | * Add CSS style for experimental feature 40 | * Fix an issue with loading JSON files when running as a dependency via npx. 41 | * Updated standard Curveball configuration. 42 | * Migrated from mocha to node:test. 43 | * Migrated from chai to node:assert. 44 | 45 | 46 | 1.1.1 (2024-02-01) 47 | ------------------ 48 | 49 | * HAL/Siren forms that have method=GET no longer add a CSRF token or an enctype 50 | attribute. 51 | 52 | 53 | 1.1.0 (2024-01-24) 54 | ------------------ 55 | 56 | * Add color for HTTP QUERY methods. 57 | * Remove node-fetch dependency. 58 | 59 | 60 | 1.0.3 (2024-01-15) 61 | ------------------ 62 | 63 | * Added a DOCTYPE so we're not in quircks mode. 64 | * Added a bug related to loading image assets. 65 | 66 | 67 | 1.0.2 (2024-01-15) 68 | ------------------ 69 | 70 | * Another JSON fail. Files are now explictly included and in the root 71 | directory. 72 | 73 | 74 | 1.0.1 (2024-01-15) 75 | ------------------ 76 | 77 | * Fix weird Typescript / Node disagreement on whether JSON import assertions 78 | are required or forbidden 79 | 80 | 81 | 1.0.0 (2024-01-15) 82 | ------------------ 83 | 84 | * Finally! Curveball v1. Only took 6 years. 85 | * CommonJS support has been dropped. The previous version of this library 86 | supported both CommonJS and ESM. The effort of this no longer feels worth it. 87 | ESM is the future, so we're dropping CommonJS. 88 | * Now requires Node 18. 89 | * Upgraded to Typescript 5.3. 90 | 91 | 92 | 0.20.5 (2024-01-14) 93 | ------------------- 94 | 95 | * Support for Typescript 5. 96 | * Updated IANA links database. 97 | 98 | 99 | 0.20.4 (2024-01-14) 100 | ------------------- 101 | 102 | * Fix: button imports were broken for ESM. 103 | 104 | 105 | 0.20.3 (2023-09-18) 106 | ------------------- 107 | 108 | * form enhancer script was missing from build 109 | * Update to latest IANA link relation file. 110 | 111 | 112 | 0.20.2 (2023-04-04) 113 | ------------------- 114 | 115 | * Fix import errors in ESM distribution. 116 | 117 | 118 | 0.20.1 (2023-04-04) 119 | ------------------- 120 | 121 | * Assets were not distributed in the npm package. 122 | 123 | 124 | 0.20.0 (2023-02-17) 125 | ------------------- 126 | 127 | * This package now supports ESM and CommonJS modules. 128 | * No longer supports Node 14. Please use Node 16 or higher. 129 | 130 | 131 | 0.19.10 (2022-12-23) 132 | -------------------- 133 | 134 | * 12% more holiday spriit 135 | 136 | 137 | 0.19.9 (2022-11-09) 138 | ------------------- 139 | 140 | * Forms/Actions now recognize a few more verbs in titles to give the submit 141 | button a better label. 142 | 143 | 144 | 0.19.8 (2022-10-31) 145 | ------------------- 146 | 147 | * Show a default icon for `acl` links. 148 | 149 | 150 | 0.19.7 (2022-10-22) 151 | ------------------- 152 | 153 | * #159: Added level3.rest link relationship descriptions (@mattbishop). 154 | * #160: Update rels from the iana link relation registry. 155 | 156 | 157 | 0.19.6 (2022-10-22) 158 | ------------------- 159 | 160 | * We're now guessing the label on submit button for HAL forms based on the verb 161 | used in the form title. 162 | * Fix a bug in `html-form-enhancer.js` 163 | 164 | 165 | 0.19.5 (2022-10-21) 166 | ------------------- 167 | 168 | * `html-form-enhancer.js` was missing from the latest release, which caused 169 | some HAL forms to not work. 170 | 171 | 172 | 0.19.4 (2022-09-29) 173 | ------------------- 174 | 175 | * If a response has a 'description' property, the browser now renders this 176 | paragraph near the top of the screen. 177 | 178 | 179 | 0.19.3 (2022-09-20) 180 | ------------------- 181 | 182 | * #145: Form elements now have `id` attributes and `