Repository: crisp-oss/chappe Branch: master Commit: a269f1e3a96c Files: 102 Total size: 424.3 KB Directory structure: gitextract_j_mdcei_/ ├── .babelrc ├── .banner ├── .bowerrc ├── .github/ │ └── workflows/ │ ├── build.yml │ └── test.yml ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .npmignore ├── .pug-lintrc ├── .stylelintrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin/ │ └── chappe.js ├── bower.json ├── examples/ │ └── acme-docs/ │ ├── config.json │ ├── data/ │ │ ├── changes/ │ │ │ ├── 2020.json │ │ │ └── 2021.json │ │ ├── guides/ │ │ │ ├── hello-world/ │ │ │ │ ├── index.md │ │ │ │ └── quickstart/ │ │ │ │ └── index.md │ │ │ ├── index.md │ │ │ └── markdown-syntax/ │ │ │ ├── index.md │ │ │ ├── section-example/ │ │ │ │ ├── index.md │ │ │ │ ├── sub-section-one/ │ │ │ │ │ └── index.md │ │ │ │ └── sub-section-two/ │ │ │ │ └── index.md │ │ │ └── syntax/ │ │ │ └── index.md │ │ └── references/ │ │ ├── rest-api/ │ │ │ ├── _private.md │ │ │ └── v1.md │ │ └── rtm-api/ │ │ └── v1.md │ └── package.json ├── gulpfile.js ├── package.json ├── res/ │ ├── config/ │ │ ├── common.json │ │ └── user.json │ └── plugins/ │ ├── gulp/ │ │ ├── minisearch.js │ │ └── pug-templates.js │ └── marked/ │ ├── extensions/ │ │ ├── embed.js │ │ ├── emphasis.js │ │ ├── figcaption.js │ │ ├── navigation-item.js │ │ └── navigation.js │ └── renderers/ │ ├── code.js │ └── heading.js └── src/ ├── javascripts/ │ └── common/ │ └── common.js ├── locales/ │ └── en.json ├── stylesheets/ │ ├── _colors.scss │ ├── _config.scss │ ├── _functions.scss │ ├── _globals.scss │ ├── _mixins.scss │ ├── _variables.scss │ ├── changes/ │ │ └── changes.scss │ ├── common/ │ │ ├── _appearance.scss │ │ ├── _badges.scss │ │ ├── _base.scss │ │ ├── _buttons.scss │ │ ├── _code.scss │ │ ├── _content.scss │ │ ├── _fonts.scss │ │ ├── _footer.scss │ │ ├── _header.scss │ │ ├── _highlight.scss │ │ ├── _markdown.scss │ │ ├── _search.scss │ │ ├── _viewer.scss │ │ └── common.scss │ ├── guides/ │ │ ├── _common.scss │ │ ├── _content.scss │ │ ├── _sidebar_left.scss │ │ └── guides.scss │ ├── home/ │ │ └── home.scss │ ├── not_found/ │ │ └── not_found.scss │ └── references/ │ ├── _content.scss │ ├── _sidebar_left.scss │ └── references.scss └── templates/ ├── __base.pug ├── _body_footer.pug ├── _body_header.pug ├── _body_search.pug ├── _head_favicon.pug ├── _head_http.pug ├── _head_includes.pug ├── _head_metas.pug ├── _head_screen.pug ├── _head_theme.pug ├── _mixins.pug ├── changes/ │ └── index.pug ├── guides/ │ ├── _content.pug │ ├── _sidebar_left.pug │ └── index.pug ├── home/ │ └── index.pug ├── not_found/ │ └── index.pug └── references/ ├── _blueprint.pug ├── _blueprint_content.pug ├── _blueprint_sidebar_left.pug ├── _markdown.pug ├── _markdown_content.pug ├── _markdown_sidebar_left.pug ├── _mixins_blueprint.pug ├── _mixins_common.pug └── index.pug ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { presets: ["es2015"] } ================================================ FILE: .banner ================================================ ___ ___ ___ ___ ___ ___ / /\ /__/\ / /\ / /\ / /\ / /\ / /:/ \ \:\ / /::\ / /::\ / /::\ / /:/_ / /:/ \__\:\ / /:/\:\ / /:/\:\ / /:/\:\ / /:/ /\ / /:/ ___ ___ / /::\ / /:/~/::\ / /:/~/:// /:/~/:// /:/ /:/_ /__/:/ / /\/__/\ /:/\:\/__/:/ /:/\:\/__/:/ /://__/:/ /://__/:/ /:/ /\ \ \:\ / /:/\ \:\/:/__\/\ \:\/:/__\/\ \:\/:/ \ \:\/:/ \ \:\/:/ /:/ \ \:\ /:/ \ \::/ \ \::/ \ \::/ \ \::/ \ \::/ /:/ \ \:\/:/ \ \:\ \ \:\ \ \:\ \ \:\ \ \:\/:/ \ \::/ \ \:\ \ \:\ \ \:\ \ \:\ \ \::/ \__\/ \__\/ \__\/ \__\/ \__\/ \__\/ — {{bundle}} by Crisp ================================================ FILE: .bowerrc ================================================ { "registry": "https://registry.bower.io" } ================================================ FILE: .github/workflows/build.yml ================================================ on: push: tags: - "v*.*.*" permissions: id-token: write name: Build and Release jobs: release: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Install NodeJS uses: actions/setup-node@v1 with: node-version: 24.x registry-url: https://registry.npmjs.org - name: Verify versions run: node --version && npm --version && node -p process.versions.v8 - name: Release package run: npm publish --ignore-scripts --provenance ================================================ FILE: .github/workflows/test.yml ================================================ on: [push, pull_request] name: Test and Build jobs: test: strategy: matrix: os: [ubuntu-latest] node-version: [20.x, 22.x, 24.x] fail-fast: false runs-on: ${{ matrix.os }} steps: - name: Checkout code uses: actions/checkout@v2 - name: Install NodeJS uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: Verify versions run: node --version && npm --version && node -p process.versions.v8 - name: Cache build artifacts id: cache-node uses: actions/cache@v4 with: path: | ~/.npm .chappe node_modules key: test-${{ runner.os }}-node-${{ matrix.node-version }} - name: Install dependencies run: npm install - name: Run tests run: npm test - name: Build 'acme-docs' example run: npm run build ================================================ FILE: .gitignore ================================================ .DS_Store Thumbs.db npm-debug.log package-lock.json node_modules/ .chappe/ dist/ ================================================ FILE: .jscsrc ================================================ { "maximumLineLength": 80, "validateIndentation": 2, "validateQuoteMarks": "\"", "disallowTrailingComma": true, "disallowTrailingWhitespace": true, "disallowNewlineBeforeBlockStatements": true, "disallowMixedSpacesAndTabs": true, "disallowMultipleLineStrings": true, "disallowNamedUnassignedFunctions": true, "disallowQuotedKeysInObjects": true, "disallowSpacesInsideParentheses": true, "disallowKeywordsOnNewLine": ["else"], "disallowIdentifierNames": ["console"], "requireCurlyBraces": true, "requireDotNotation": true, "requireSemicolons": true, "requireSpaceBeforeObjectValues": true, "requireSpaceBetweenArguments": true, "requireSpacesInForStatement": true, "requireSpaceAfterObjectKeys": true, "requireSpaceBeforeBinaryOperators": true, "requireSpaceBeforeBlockStatements": true, "requireSpacesInConditionalExpression": true, "requireBlocksOnNewline": true, "requireCommaBeforeLineBreak": true, "requireLineFeedAtFileEnd": true, "requirePaddingNewLineAfterVariableDeclaration": true, "requirePaddingNewLinesAfterUseStrict": true, "requirePaddingNewLinesBeforeExport": true, "requirePaddingNewLinesInObjects": true } ================================================ FILE: .jshintrc ================================================ { "camelcase": false, "esversion": 6, "node": true, "predef": [ "require", "define", "escape", "Buffer", "module" ] } ================================================ FILE: .npmignore ================================================ package-lock.json .github/** .chappe/** dist/** examples/** ================================================ FILE: .pug-lintrc ================================================ { "requireLowerCaseTags": true, "requireLowerCaseAttributes": true, "requireLineFeedAtFileEnd": true, "requireIdLiteralsBeforeAttributes": true, "requireClassLiteralsBeforeIdLiterals": true, "requireClassLiteralsBeforeAttributes": true, "requireStrictEqualityOperators": true, "validateSelfClosingTags": true, "validateIndentation": 2, "validateDivTags": true, "validateAttributeQuoteMarks": "\"", "disallowStringConcatenation": true, "disallowDuplicateAttributes": true, "disallowSpecificAttributes": [ "xmlns", { "br": "role" }, { "hr": "role" }, { "nav": "role" } ], "disallowSpecificTags": [ "b", "hgroup", "i", "s", "u" ], "requireSpecificAttributes": [ { "html": [ "lang" ] }, { "a": [ "href" ] }, { "img": [ "src", "alt" ] }, { "form": [ "action", "method" ] }, { "input": [ "type", "name" ] }, { "textarea": [ "name", "cols", "rows" ] } ] } ================================================ FILE: .stylelintrc.yml ================================================ extends: - stylelint-config-standard-scss rules: selector-id-pattern: null selector-class-pattern: null at-rule-empty-line-before: null no-descending-specificity: null scss/comment-no-empty: null scss/load-no-partial-leading-underscore: null scss/dollar-variable-empty-line-before: null ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 1.16.0 (2026-05-03) ### New Features * Added support for footnotes in guides. ## 1.15.2 (2026-03-31) ### Bug Fixes * Fixed category icon overflow in guides details section. ## 1.15.1 (2026-02-02) ### Bug Fixes * Fixed generated slug for anchor links in Markdown articles (in some cases). ## 1.15.0 (2026-01-28) ### Changes * Migrate `.jade` files to `.pug` syntax (and update the syntax to Pug 3). ## 1.14.0 (2026-01-27) ### Changes * Migrate `.sass` files to `.scss` syntax. * Re-instate linting of stylesheet files with `gulp-stylelint-esm`. ## 1.13.0 (2026-01-27) ### Changes * Migrate Sass syntax away from `@import` to `@use`. ## 1.12.0 (2025-11-11) ### Changes * Migrate `gulp-cssmin` to `gulp-clean-css` for Node.js 24 compatibility. ## 1.11.0 (2025-10-16) ### Changes * Migrate to NPMJS OIDC publishing tokens (since Classic Tokens will be removed in November 2025). ## 1.10.2 (2025-07-15) ### Bug Fixes * Fixed dropped enumeration members with empty values (such as `0` and `false`). * Fixed enumeration descriptions, that were not showing at all. ## 1.10.1 (2025-06-22) ### New Features * Added a way to configure navigation link targets (`self` or `blank`, defaults to `self`). ## 1.10.0 (2025-06-22) ### New Features * Added a way to disable customer support CTAs with the `features.support` feature flag option (enabled by default). ## 1.9.8 (2025-05-17) ### Bug Fixes * Fixed concurrency issues on the copying of images while the `sass` is also running. ## 1.9.7 (2025-05-17) ### New Features * Added a way to customize `async` and `defer` attributes in the `includes.scripts.urls` option. ### Bug Fixes * The warnings thrown by `sass` are now hidden (until they are fixed). ## 1.9.6 (2025-05-16) ### Changes * Replaced deprecated `node-sass` dependency with `sass`. ## 1.9.5 (2023-11-06) ### Changes * Added provenance information upon building NPM package over GitHub Actions. ## 1.9.4 (2023-08-02) ### Bug Fixes * Fixed performance issues when installing Chappe with NPM v9. ## 1.9.3 (2023-01-06) ### New Features * Automated the package release process via GitHub Actions (ie. `npm publish`). ## 1.9.2 (2022-12-02) ### Changes * Improved detection of page titles when generating Open Graph previews. ## 1.9.1 (2022-12-02) ### New Features * Added support for the `twitter:image:src` and `twitter:card` tags. ## 1.9.0 (2022-12-02) ### ⚠️ Breaking Changes * The Open Graph images are now auto-generated from the page content, using the provided `opengraph` configuration property; since this option already existed before, you will need to make sure to update your Open Graph image so that it can contain inserted text in the foreground (ie. you need to clear any text from your existing Open Graph image). ## 1.8.1 (2022-11-03) ### Changes * Added a background hover effect on table rows. ## 1.8.0 (2022-06-24) ### New Features * Added a "copy to clipboard" button in all code blocks. ## 1.7.1 (2022-05-06) ### Changes * The generated platform changes RSS feed now uses the configured title. ## 1.7.0 (2022-05-06) ### ⚠️ Breaking Changes * Changed how the theme accent color gets configured with the `theme` configuration property (`light` and `dark` mode variants must now be set). ### New Features * Support for Crisp Status (aside from Vigil). ## 1.6.4 (2022-02-13) ### Changes * Moved the search engine opening shortcut from ⌘F to ⌘K (after gathering user feedback). ## 1.6.3 (2022-01-12) ### Bug Fixes * Fixed an issue with non-highlighted code blocks in Dark Mode, where code text would appear black-on-black. * Fixed an issue with inline code blocks in Dark Mode, where selected text would appear white-on-white. ## 1.6.2 (2022-01-11) ### Changes * Improved management of CSS colors. ## 1.6.1 (2022-01-11) ### Changes * Improved Dark Mode colors. * Moved logos to embedded images (this prevents visual glitches when loading docs). ## 1.6.0 (2022-01-10) ### New Features * Implemented Dark Mode. * Added the ability to configure the theme accent color with the `theme` configuration property. ## 1.5.1 (2022-01-07) ### Changes * Improved the performance of watching for changes in `data/` while using `chappe serve` or `chappe watch`. ### Bug Fixes * Fixed Gulp meta-events showing in the Chappe CLI output when using `--verbose` (they are now hidden). ## 1.5.0 (2022-01-06) ### New Features * The Chappe CLI now embeds a preview server, used to ease with writing and previewing docs (via `chappe serve`). ### Changes * Improved logging in the Chappe CLI. ### Bug Fixes * Fixed the abrupt stopping of the Chappe CLI whenever a resource build failed while using `chappe watch`. ## 1.4.1 (2022-01-06) ### Changes * Refactored the README to add the Chappe logo. ## 1.4.0 (2022-01-05) ### Changes * Moved the project to a dedicated GitHub organization: [Crisp OSS](https://github.com/crisp-oss). ## 1.3.2 (2022-01-05) ### Changes * Moved the Chappe CLI from ES5 to ES6. ## 1.3.1 (2022-01-05) ### Changes * Refactored Chappe CLI terminal outputs. * Improved the `acme-docs` example. ## 1.3.0 (2022-01-05) ### Changes * Reworked the lint pipeline to reduce the number of dependencies. ### Bug Fixes * Fixed an issue where 404 and private pages appeared in the sitemap. * Fixed the configuration path for the SASS linter, which could not read its configuration file in some cases. ## 1.2.1 (2022-01-05) ### Bug Fixes * Fixed the normalization of paths passed to the Chappe CLI via `--config`. ## 1.2.0 (2022-01-05) ### New Features * The Chappe CLI `--config` argument now accepts multiple configuration files (comma-separated, merged together). * Added the ability to override certain Chappe internal values with the `overrides` configuration property. ### Bug Fixes * Fixed an issue in Chappe CLI, where a build failure could cause the process to hang indefinitely. ## 1.1.2 (2022-01-04) ### Bug Fixes * Fixed the NPMJS distribution package, that was missing some more hidden files (such as `.babelrc`). ## 1.1.1 (2022-01-04) ### Bug Fixes * Fixed the NPMJS distribution package, that was missing the `.banner` file. ## 1.1.0 (2022-01-04) ### Changes * Moved the build pipeline to Gulp 4. * Improved the Chappe CLI with terminal spinners. ## 1.0.3 (2022-01-04) ### Changes * Now showing a Chappe logo when calling the Chappe CLI. * Better temporary files management. ### Bug Fixes * All internal paths are now absolute (this fixes some build environments). ## 1.0.2 (2022-01-03) ### Bug Fixes * Fix dependencies for published `chappe` package. ## 1.0.1 (2022-01-03) ### Bug Fixes * Fix the path of Chappe CLI. ## 1.0.0 (2022-01-03) ### New Features * Initial release. ================================================ FILE: LICENSE ================================================ Copyright (c) 2021 Crisp IM SAS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Chappe [![Test and Build](https://github.com/crisp-oss/chappe/actions/workflows/test.yml/badge.svg)](https://github.com/crisp-oss/chappe/actions/workflows/test.yml) [![Build and Release](https://github.com/crisp-oss/chappe/actions/workflows/build.yml/badge.svg)](https://github.com/crisp-oss/chappe/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/chappe.svg)](https://www.npmjs.com/package/chappe) [![Downloads](https://img.shields.io/npm/dt/chappe.svg)](https://www.npmjs.com/package/chappe) **Developer Docs builder. Write guides in Markdown and references in API Blueprint. Comes with a built-in search engine.** Chappe is a Developer Docs builder, that produces static assets. No runtime, just lightweight static files. It was built to address SaaS companies needs, and can serve as a first-class modern alternative to hosted services such as [ReadMe](https://readme.com/). The reason behind why we made Chappe is the following: while looking for a Developer Docs builder at [Crisp](https://crisp.chat/en/), all that we could find were either outdated open-source projects, or commercial documentation builders. We wanted a modern Developer Docs website hosted on our premises, as pure-static assets. The latter is especially important, as we do not want to rely on a plethora of external services that can go down anytime. **Using Chappe is as easy as:** 1. Writing all your docs in Markdown; 2. Building your docs in a single command; 3. Finally, deploying static build assets to your Web servers (or GitHub Pages, Cloudflare Pages, etc. — _this can be automated via GitHub Actions_); **😘 Maintainer**: [@valeriansaliou](https://github.com/valeriansaliou) ## Screenshots & Demo **👉 See a live demo of Chappe on the [Crisp Developer Hub](https://docs.crisp.chat/).** 1️⃣ Chappe can generate your REST API reference: [![Chappe References](https://crisp-oss.github.io/chappe/images/screenshot-references.gif)](https://docs.crisp.chat/references/rest-api/v1/) 2️⃣ It also generates Markdown-based developer guides: [![Chappe Guides](https://crisp-oss.github.io/chappe/images/screenshot-guides.gif)](https://docs.crisp.chat/guides/rest-api/rate-limits/) 3️⃣ Oh, and it also lets your users search anything in your Developer Docs: [![Chappe Search](https://crisp-oss.github.io/chappe/images/screenshot-search.gif)](https://docs.crisp.chat/) _👉 Note that the search engine feature is 100% local. This means that it does not run on an external service like [Algolia](https://www.algolia.com/), though it does provides similar search performance and results. The search index is generated at build time as a JSON file, which gets loaded on-demand when the search box gets opened._ ## Who uses it?
Crisp Meowtel
_👋 You use Chappe and you want to be listed there? [Open an issue](https://github.com/crisp-oss/chappe/issues)._ ## Last changes The version history can be found in the [CHANGELOG.md](https://github.com/crisp-oss/chappe/blob/master/CHANGELOG.md) file. ## Features * **Simple & fast**: generate a Developer Docs with optimized static assets. No runtime * **Guides**: write developer guides in Markdown (rich content support: images, videos, tables, etc.) * **References**: document your HTTP REST API specification using API Blueprint * **Changes**: maintain a changelog of your platform (eg. your REST API, your SDKs) * **RSS feed**: users can subscribe to your changelog over RSS * **Beautiful Markdown rendering**: all content that you write gets rendered with a clear and modern style * **Syntax highlighting**: coloring for your code examples in 100+ programming languages * **Built-in search engine**: the index is generated during build and is hosted locally * **Fully responsive**: full support of desktop, tablet and phone screens * **Dark Mode**: read your docs either in light mode or dark mode * **Customizable theme**: configure an accent color for your docs theme * **SEO-friendly**: a deep sitemap is generated for search engines * **Sharing-friendly**: full support of the Open Graph protocol, with the auto-generation of preview images * **Private pages support**: mark any guide or reference as private or unlisted (prefix its name with `_`) * **Local preview server**: skip setting up a local Web server to preview your docs while writing them, Chappe embeds a preview server that can be started in a single command _The following optional features can also be enabled:_ * **Chatbox**: integrate with the [Crisp Chatbox](https://crisp.chat/en/livechat/) to handle tech support and collect user feedback * **Status page**: integrate with your [Vigil](https://github.com/valeriansaliou/vigil) status page to show live system status (can also be [Crisp Status](https://crisp.chat/en/status/)) ## How to use it? ### Installation & Overview To install and use Chappe, please follow those steps: 1. Create a new, empty Git repository; 2. Copy the `examples/acme-docs/` folder contents from the Chappe repository into your project root; 3. Run: `npm install` (make sure that you have a recent NodeJS version installed); 4. Run: `npx chappe serve` to build the docs and serve them over a local Web server (it will also watch for changes); 5. Open: [http://localhost:8080](http://localhost:8080/) in your Web browser to access your docs; 6. Write your Markdown guides and references in the `data/` directory (changes will be hot-reloaded in your browser); Please refer to sections below for more details on how to write docs, customize Chappe, and deploy your final docs to your Web server. _👉 If Chappe fails to install on your Mac with an Apple Silicon chip, please refer to the [Common questions](#-common-questions) section below._ ### Configuration The configuration of your Chappe docs is stored in a single JSON file, usually named `config.json`. Your configuration file will make references to images, such as your docs logo, which are stored in the `assets/` folder. An empty definition of the Chappe configuration file is available in: [res/config/user.json](https://github.com/crisp-oss/chappe/blob/master/res/config/user.json), although you may rather want to see a filled example: [examples/acme-docs/config.json](https://github.com/crisp-oss/chappe/blob/master/examples/acme-docs/config.json) (if you copy-paste it, **make sure** to change all of its contents). _👇 Notes on certain configuration rules can be found in the [Advanced settings](#%EF%B8%8F-advanced-settings) section._ ### Chappe CLI usage Chappe provides you with the `chappe` command, that builds your docs. It supports the following actions, defaulting to `build` if none is specified: * `build` to build docs * `clean` to clean `dist/` and all temporary files * `watch` to watch for changes and re-build (useful while writing docs) * `serve` to serve built assets on your local/development computer (useful while testing and writing docs, **not used for production**) * `lint` to run lints on Chappe internal resources It supports the following parameters, with a default value if not set: * `--config` (paths to the configuration files, comma-separated, _default value:_ `./config.json`) * `--assets` (path to the assets directory, _default value:_ `./assets`) * `--data` (path to the data directory, _default value:_ `./data`) * `--dist` (path where to write built resources, _default value:_ `./dist`) * `--temp` (path where to write temporary files, _default value:_ `./.chappe`) * `--env` (environment, either `development` or `production`, _default value:_ `production`) If you are running with the `serve` action, it accepts additional parameters: * `--host` (hostname or IP address to use for the local/development server, _default value:_ `localhost`) * `--port` (port number to use for the local/development server, _default value:_ `8080`) Some special parameters are also available: * `--quiet` (show less output when performing task) * `--verbose` (show more output when performing task) * `--example` (name of the Chappe docs example to build, useful for Chappe developers and quick tests, eg. `acme-docs`) To build your docs, you can call the Chappe CLI as such: ```bash npx chappe build --config=./config.json --assets=./assets --data=./data --dist=./dist ``` You can also call the Chappe CLI without any argument, in which case defaults will be used: ```bash npx chappe build ``` By default, docs are built for a `production` target, meaning that all assets produced are optimized for speed and size. In most use cases, you will never need to set it to `development`, unless you are trying to extend or modify the Chappe core and therefore need to see uncompressed assets output. To create a local development server on [http://localhost:8080](http://localhost:8080/), used to write and preview your docs, use: ```bash npx chappe serve ``` _👉 If the `chappe` command is not found, make sure to add `chappe` to your `package.json` and call `npm install`._ ### Writing docs Docs can be either: `guides`, `references` or `changes`. The corresponding folders are stored in the `data/` directory, which is passed to the Chappe CLI whenever building your docs. * `guides` are articles that walk your users through using your systems ([example here](https://docs.crisp.chat/guides/rest-api/rate-limits/)). They are written in Markdown, and are organized in sub-folders if deep nesting of guides in several sections is required. Chappe will auto-generate the navigation sidebar for you, based on this folder hierarchy. * `references` are formal specifications of your systems (examples of: [API Blueprint](https://docs.crisp.chat/references/rest-api/v1/) and [Markdown](https://docs.crisp.chat/references/rtm-api/v1/)). They are written in [API Blueprint](https://apiblueprint.org/) for your HTTP REST API (a pseudo-Markdown format), or traditional Markdown for other systems (eg. a WebSocket server). * `changes` is a timeline of updates that you made to your systems ([example here](https://docs.crisp.chat/changes/)). They are defined in a JSON format. In addition to the timeline, an RSS feed also gets generated at the `/changes.rss` URL. ### Deploying your docs To deploy your docs: 1. First, create a Virtual Host on your Web server, using a dedicated domain, eg. `docs.acme.com`; 2. Then, build Chappe with `npx chappe build` (on your local computer or a CI/CD runner such as GitHub Actions); 3. Finally, copy the contents of the `dist/` folder to your server folder for your docs Virtual Host (eg. `/var/www/docs.acme.com`); ⚠️ Chappe **must** be hosted at the root of your docs domain — it **will not** work if hosted in a sub-directory! Here is an example configuration file for NGINX on the Virtual Host `docs.acme.com`: ``` server { listen 443 ssl http2; server_name docs.acme.com; root /var/www/docs.acme.com; error_page 404 /not_found/; } ``` _👉 Note that if possible, you should make sure that you have a rule to catch 404 errors and show the `not_found` page (as the NGINX configuration file above shows)._ ## Syntax guide ### How to write guides? Guides are stored within `guides/` in your data directory. A guide is stored as a Markdown file named `index.md` in a sub-directory with the guide name eg. `hello-world`. The sub-directory structure directly maps to the final URL that you get: for instance `guides/hello-world/index.md` results in eg. `http://docs.acme.com/guides/hello-world/`. #### Structure of a guide file Each guide Markdown file **must** start with a meta-data header, which holds information on: * `TITLE`: The guide article name * Example: `TITLE: Hello World` * `INDEX`: Number used to position the article relative to others in the navigation sidebar * Example: `INDEX: 1` * `UPDATED`: The date at which the guide article has been updated * Example: `UPDATED: 2021-09-22` * `LINK`: Additional navigation links to be added in the navigation sidebar * _Optional_, _Multiple possible_ * Example: `LINK: Reference -> /references/rest-api/v1/` Right after the header is defined, you can start writing Markdown for your guide, as normal. An example of a full Markdown code for a guide is available at: [examples/acme-docs/data/guides/hello-world/index.md](https://raw.githubusercontent.com/crisp-oss/chappe/master/examples/acme-docs/data/guides/hello-world/index.md) #### Adding icons to guide sections Each guide main section can have its icon shown in the navigation sidebar (first-level sections only). Section icons are defined in the `config.json` configuration file, within `images.categories.guides`. The section folder name, eg. `hello-world`, should be added to the `guides` object, associated to an SVG icon image from your `assets/` folder. For example: ```json { "images" : { "categories" : { "guides" : { "hello-world" : "images/categories/guides/hello-world.svg" } } } } ``` #### List of special Markdown syntax While the [Markdown specification](https://daringfireball.net/projects/markdown/syntax) defines most of the syntax that we need to build a full-featured Developer Docs (text formatting, images, tables, etc.), some non-standard elements had to be defined in Chappe. --- ##### Video embeds To embed a video in a page, use the following Markdown syntax: ```markdown ${provider}[Video Title](video-id) ``` Supported providers: `youtube` Example: ```markdown ${youtube}[In-depth Introduction to the Crisp RTM API](vS-h6k2ML6M) ``` --- ##### Text emphasis (notice, info or warning blocks) To insert text in an emphasis block, use one of the following Markdown syntaxes: ```markdown ! This is a notice text. !! This is an info text. !!! This is a warning text. ``` --- ##### Image with caption To insert an image with a caption, use the following Markdown syntax: ```markdown $[Caption Text](![Image Title](image-path.png)) ``` Example: ```markdown $[Copy your Website ID](![](copy-website-id.png)) ``` --- ##### Navigation links To insert a navigation block, with one or multiple links to other pages, use the following Markdown syntax: ```markdown + Navigation | Link Title 1: Link Description -> ./link/target/1/ | Link Title 2: Link Description -> http://external-url.com/target/page/ [blank] ``` Example: ```markdown + Navigation | Quickstart: Learn how to use the REST API in minutes. -> ./quickstart/ | Authentication: Read how to authenticate to the REST API. -> ./authentication/ | Rate-Limits: Learn about request rate-limits. -> ./rate-limits/ | API Libraries: Libraries for your programming language. -> ./api-libraries/ ``` --- ##### Interact with the Crisp Chatbox If you need to interact with the Crisp Chatbox from your Markdown code, you can include a traditional Markdown link with an URL pointing to special anchors. The following anchors are available: * Pop open the chatbox: `#crisp-chat-open` * Prompt to submit feedback on the current page: `#crisp-chat-feedback` Example: ```markdown If you have any question on this guide, please [contact our chat support](#crisp-chat-open). ``` _👉 Note that this only works if you are using the Crisp Chatbox integration, and if the Crisp Chatbox is appearing on your docs._ --- ### How to write references? References are stored within `references/` in your data directory. A reference is stored either as an API Blueprint or Markdown file named for example `v1.md` for the API version, in a sub-directory corresponding to the name of the API, eg. `rest-api`. The sub-directory structure directly maps to the final URL that you get: for instance `references/rest-api/v1.md` results in eg. `http://docs.acme.com/references/rest-api/v1/`. #### API Blueprint references API Blueprint-formatted references are used to specify an HTTP REST API. Each reference written with API Blueprint **must** start with a meta-data header, which holds information on: * `TYPE`: The type of the reference * Value: `API Blueprint` * `TITLE`: The reference title (with its version number) * Example: `TITLE: REST API Reference (V1)` * `UPDATED`: The date at which the reference has been updated * Example: `UPDATED: 2021-12-22` Immediately following, come API Blueprint meta-datas: * `FORMAT`: The API Blueprint format (_do not change this_) * Value: `1A` * `HOST`: The HTTP REST API host URL * Example: `https://api.crisp.chat/v1` Then, a main title with the following mandatory content: ```markdown # Reference ``` After that, you can specify all your HTTP REST API routes in API Blueprint as normal. Also, note that as done with guides above, reference sections can have their own icon images. Section icons are defined in the `config.json` configuration file, within `images.categories.references`. An example of a full API Blueprint code for a reference is available at: [examples/acme-docs/data/references/rest-api/v1.md](https://raw.githubusercontent.com/crisp-oss/chappe/master/examples/acme-docs/data/references/rest-api/v1.md) #### Markdown references Markdown-formatted references are used to specify anything that is not an HTTP REST API. For instance, a WebSocket endpoint, a network protocol or a programmatic interface. Each reference Markdown file **must** start with a meta-data header, which holds information on: * `TYPE`: The type of the reference * Value: `Markdown` * `TITLE`: The reference title (with its version number) * Example: `TITLE: RTM API Reference (V1)` * `UPDATED`: The date at which the reference has been updated * Example: `UPDATED: 2021-09-22` After that, you can write the specification contents in Markdown. Also, note that as done with guides above, reference sections can have their own icon images. Section icons are defined in the `config.json` configuration file, within `images.categories.references`. An example of a full Markdown code for a reference is available at: [examples/acme-docs/data/references/rtm-api/v1.md](https://raw.githubusercontent.com/crisp-oss/chappe/master/examples/acme-docs/data/references/rtm-api/v1.md) ### How to write changelogs? Changes are stored within `changes/` in your data directory. They are organized in JSON files for each year, eg. `2021.json`. #### Structure of a changelog file A changelog file for a year contains an array of all individual change entries. Think of it as a yearly feed of all dated changes. For instance, a `2021.json` file with a single change would contain: ```json [ { "group" : "rest_api", "type" : "change", "date" : "2021-12-03", "text" : "Markdown-formatted text for this change on the REST API." } ] ``` An example of a full changelog file is available at: [examples/acme-docs/data/changes/2021.json](https://raw.githubusercontent.com/crisp-oss/chappe/master/examples/acme-docs/data/changes/2021.json) #### Allowed values for a change A change is structured as such: * `group`: the category of this change — _define your custom categories labels in `texts.changes.groups` and colors in `colors.changes.groups` within your `config.json`_; * `type`: the type of the change (either: `change` or `deprecation`); * `date`: a date for the change (formatted as: `YYYY-MM-DD`); * `text`: the description text for the change, Markdown-formatted — _make sure that any URL you define there is a full URL, as this is also used in RSS feeds_ ## ⚛️ Advanced settings ### Available code coloring Code coloring rules for programming languages must be added manually, for each syntax that you intend use. As the rules are quite heavy for each syntax, Chappe includes none by default. For instance, if you need to show examples of Java code, you'd need to add the `java` code coloring rule in `plugins.code.syntaxes` in your `config.json`. Chappe runs on [Prism](https://github.com/PrismJS/prism) for code coloring. Most often used syntaxes are listed below (pick yours!): ``` markup markup-templating css clike c javascript bash go java groovy json objectivec php python ruby rust swift objectivec ``` All available Prism rules can be found [here](https://github.com/PrismJS/prism/tree/master/components). _👉 Note that some rules depend on others. For instance, `objectivec` requires the `c` rule to be also included. If you do not get code coloring for a certain syntax after including it, then it probably means that one of its dependency is missing. Please refer to the list of Prism components for more details._ ### Check file sizes during build Once Chappe is done building your docs, it checks for all built files sizes against maximum build size rules. This is done to ensure that you do not get bad surprises about your Developer Docs users experiencing slow load times, especially when including a lot of heavy images in guides. In the event a build size rule threshold is reached, the Chappe CLI will error out, informing you which file is over-sized. To adjust size thresholds or disable this checker rule, open your `config.json` file and refer to the `rules.build_size` property: * To circumvent build failure when a file is over-sized, set the `fail` property to `false`; * Maximum sizes can be adjusted where relevant with the `sizes` property (note that sizes are in bytes, so 10KB is about `10000`); ## 🙋 Common questions ### The installation of Chappe fails on my Mac with Apple Silicon Chappe relies on the `gulp-ogimage` dependency to auto-generate Open Graph images, which itself uses a library named `canvas`. Unfortunately, as of December 2022, `canvas` does not provide any pre-built binary for the `arm64` CPU architecture, leading to Chappe failing to install on Macs with Apple Silicon chips. In order to install Chappe on `arm64` architectures, you will need to ensure that [Homebrew](https://brew.sh/) is setup on your system, then run: ```bash brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman ``` Once those tools are installed, try installing Chappe again. ### How can I customize my docs style? In order to customize your docs style — _ie. override the default Chappe style past what can already be customized in the `config.json` configuration file_ — open `config.json` and look for the `includes` property (that contains `stylesheets`, that contains `urls` and `inline`). You can easily deploy your own custom stylesheet on your docs domain, along with Chappe-generated `dist/` assets, with CSS classes overriding Chappe default styles: ```json { "includes" : { "stylesheets" : { "urls" : [ "/overrides/style.css" ], "inline" : [] } } } ``` ### How can I add scripts like Google Tag Manager? To add inline scripts such as Google Tag Manager, open your `config.json` configuration file for Chappe, and look for the `includes` property (that contains `scripts`, that contains `urls` and `inline`). Add a new entry to the `urls` and `inline` array, separately, giving eg.: ```json { "includes" : { "scripts" : { "urls" : [ "https://www.googletagmanager.com/gtag/js?id={YOUR_GTM_ID}", { "src" : "https://scripts.simpleanalyticscdn.com/latest.js", "async" : true } ], "inline" : [ "window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag(\"js\", new Date());\ngtag(\"config\", \"{YOUR_GTM_ID}\");" ] } } } ``` The `urls` property will include the JavaScript at the provided URL on all pages, while the `inline` property will append the inline JavaScript in a `script` element on all pages. ### How can I deploy my docs to GitHub Pages? To build your docs to GitHub Pages, you will first need to host your docs project as a GitHub repository. Then, make sure that GitHub Actions is configured and running for your project. You can then use the [deploy-to-github-pages](https://github.com/marketplace/actions/deploy-to-github-pages) action to proceed with building your docs via `npx chappe build` and then deploying the `dist/` folder to GitHub Pages. ### Where does the Chappe name come from? Chappe was named after [Claude Chappe](https://en.wikipedia.org/wiki/Claude_Chappe), a French inventor, pioneer in long-distance communications. He invented the [optical telegraph](https://en.wikipedia.org/wiki/Optical_telegraph) (a.k.a. semaphore telegraph), later replaced by the [electrical telegraph](https://en.wikipedia.org/wiki/Electrical_telegraph). Those technologies were the founding blocks of what took over the world next: analog and digital telecommunications. Quoting from his page on Wikipedia: > This [the optical telegraph] was the first practical telecommunications system of the industrial age, and was used until the 1850s when electric telegraph systems replaced it. _Credits to [Baptiste Jamin](https://github.com/baptistejamin) for the name idea._ ================================================ FILE: bin/chappe.js ================================================ #!/usr/bin/env node /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ "use strict"; // Suppress all warnings from Node, as this is a CLI script process.removeAllListeners("warning"); var fs = require("fs"); var path = require("path"); var ora = require("ora"); var version = require("../package.json").version; var args = require("yargs").argv; /** * Chappe CLI * @class * @classdesc Chappe CLI class. */ class ChappeCLI { /** * Constructor */ constructor() { // Constants this.__context_defaults = { default : { config : "./config.json", assets : "./assets", data : "./data", dist : "./dist", temp : "./.chappe", env : "production", host : "localhost", port : 8080 }, example : { config : "./examples/{{target}}/config.json", assets : "./examples/{{target}}/assets", data : "./examples/{{target}}/data", dist : "./dist", temp : "./.chappe", env : "development", host : "localhost", port : 8080 } }; this.__actions_available = [ "build", "clean", "watch", "serve", "lint" ]; this.__actions_logging = [ "watch", "serve" ]; this.__actions_no_aborts = [ "watch", "serve" ]; this.__spinner_successes = { default : { method : "succeed", text : "Success!" }, clean : { method : "succeed", text : "Cleaned up." }, build : { method : "succeed", text : "Build done!" }, lint : { method : "succeed", text : "Lint passed." }, serve : { method : "start", text : "Now listening..." }, watch : { method : "start", text : "Watching...\n" } }; this.__path_expand_keys = [ "config", "assets", "data", "dist", "temp" ]; this.__env_available = [ "development", "production" ]; // Storage this.__has_setup_gulp_logging = false; } // jscs:disable disallowIdentifierNames /** * Runs the class * @public * @return {undefined} */ run() { // Run help? if (args.help) { return this.__run_help(); } // Run version? if (args.version) { return this.__run_version(); } // Run default (clean or build) this.__run_default(); } /** * Runs help * @private * @return {undefined} */ __run_help() { console.log( "Builds given Chappe documentation resources into static assets.\n\n" + "Available actions:\n" + (this.__format_help_actions(this.__actions_available) + "\n\n") + "Available arguments:\n" + (this.__format_help_argument("config") + "\n") + (this.__format_help_argument("assets") + "\n") + (this.__format_help_argument("data") + "\n") + (this.__format_help_argument("dist") + "\n") + (this.__format_help_argument("temp") + "\n") + (this.__format_help_argument("env") + "\n") + (this.__format_help_argument("host") + "\n") + (this.__format_help_argument("port") + "\n\n") + "Other arguments:\n" + (this.__format_help_argument("quiet") + "\n") + (this.__format_help_argument("verbose") + "\n") + this.__format_help_argument("example") ); process.exit(0); } /** * Runs version * @private * @return {undefined} */ __run_version() { console.log("Chappe CLI v" + version); process.exit(0); } /** * Runs default * @private * @return {undefined} */ __run_default() { let _has_output = ( (args.quiet && !args.verbose) ? false : true ); // Acquire task from action let _task = this.__acquire_action(); // Dump banner? if (_has_output === true) { console.log(this.__dump_banner()); } // Acquire context global.CONTEXT = this.__acquire_context(_task); if (_has_output === true) { console.log( ("Chappe will " + _task + " docs with context:\n") + (this.__dump_context(global.CONTEXT) + "\n") ); } // Setup spinner // Throttle down spinner refresh interval, to ease w/ terminal CPU usage \ // when performing long operations eg. 'watch'. let _spinner = ora({ text : "Working...\n", color : "cyan", interval : 350 }); // Import Gulp instance let _gulp = require("gulp"); // Setup error traps this.__setup_error_traps(_gulp, _spinner, _task); // Setup Gulp logging? (pre-task mode, only if verbose) if (args.verbose) { this.__setup_gulp_logging(_gulp, _spinner); } // Import the Gulpfile let _gulpfile = require("../gulpfile.js"); // Start spinner _spinner.start(); // Build docs _gulpfile[_task]((error) => { // Any error occured? if (error) { // Throw error and stop spinner _spinner.fail("Error:"); console.log(error); _spinner.stop(); process.exit(1); } else { // Show success spinner (depending on task success rule) let _success_rules = ( this.__spinner_successes[_task] || this.__spinner_successes.default ); _spinner[_success_rules.method](_success_rules.text); // Setup Gulp logging? (post-task mode, only for certain actions) if (this.__actions_logging.includes(_task) === true) { this.__setup_gulp_logging(_gulp, _spinner); } } }); } /** * Formats help actions * @private * @param {object} actions * @return {string} Formatted help actions */ __format_help_actions(actions) { let _actions = actions.map((action) => { return (" " + action); }); return _actions.join("\n"); } /** * Formats help argument * @private * @param {string} name * @return {string} Formatted help argument */ __format_help_argument(name) { let _argument = (" --" + name); // Append default value? (if any) let _default_value = this.__context_defaults.default[name]; if (typeof _default_value !== "undefined") { _argument += (" (defaults to: '" + _default_value + "')"); } return _argument; } /** * Dumps the banner * @private * @return {string} Dumped banner */ __dump_banner() { // Generate bundle name let _bundle_name = ("Chappe v" + version); // Read banner file let _buffer = ( fs.readFileSync(path.join(__dirname, "../.banner")) ); // Convert banner to string and inject bundle name let _banner = _buffer.toString().replace("{{bundle}}", _bundle_name); return _banner; } /** * Dumps the context * @private * @param {object} context * @return {string} Dumped context */ __dump_context(context) { let _context = Object.keys(context).map((key) => { return (" " + key + " -> " + context[key]); }); return _context.join("\n"); } /** * Acquires current action * @private * @return {string} Current action */ __acquire_action() { let _action; for (let _i = 0; _i < this.__actions_available.length; _i++) { let _cur_action = this.__actions_available[_i]; if (process.argv.includes(_cur_action) === true) { _action = _cur_action; break; } } return (_action || "build"); } /** * Acquires current context * @private * @param {string} task * @return {object} Current context */ __acquire_context(task) { // Acquire defaults let _defaults = ( this.__context_defaults[(args.example ? "example" : "default")] ); // Inject target in defaults? if (args.example) { _defaults = this.__inject_defaults_target( _defaults, args.example ); } // Generate context // Notice: the values are temporarily represented as arrays, to ease with \ // data normalization steps. let _context = { config : (args.config || _defaults.config).split(","), assets : [(args.assets || _defaults.assets)], data : [(args.data || _defaults.data)], dist : [(args.dist || _defaults.dist)], temp : [(args.temp || _defaults.temp)], env : [(args.env || _defaults.env)] }; // Append serve-related context? if (task === "serve") { _context.host = [(args.host || _defaults.host)]; _context.port = [parseInt((args.port || _defaults.port), 10)]; } // Expand context paths let _base_path = process.cwd(); this.__path_expand_keys.forEach((key) => { let _context_values = _context[key]; for (let _i = 0; _i < _context_values.length; _i++) { // Path is not already in absolute format? (convert to absolute) if (path.isAbsolute(_context_values[_i]) !== true) { _context_values[_i] = ( path.join(_base_path, _context_values[_i]) ); } } }); // Re-join context values as bare strings (from lists) for (let _key in _context) { // Notice, this retains non-string types untouched if (_context[_key].length === 1) { _context[_key] = _context[_key][0]; } else { _context[_key] = _context[_key].join(","); } } // Validate final context if (this.__env_available.includes(_context.env) !== true) { throw new Error( "Environment value not recognized: " + _context.env ); } return _context; } /** * Inject target into defaults * @private * @param {object} defaults * @param {string} target * @return {object} Defaults w/ injections */ __inject_defaults_target(defaults, target) { // Important: create a new defaults object, as not to alter the given one, \ // which could be re-used later. let _injected_defaults = {}; for (let _key in defaults) { let _cur_default = defaults[_key]; if (typeof _cur_default === "string") { _cur_default = _cur_default.replace("{{target}}", target); } _injected_defaults[_key] = _cur_default; } return _injected_defaults; } /** * Setups error traps * @private * @param {object} gulp * @param {object} spinner * @param {string} task * @return {undefined} */ __setup_error_traps(gulp, spinner, task) { // Check if should crash on error let _crash_on_error = ( (this.__actions_no_aborts.includes(task) === true) ? false : true ); // Setup process events process.once("exit", (code) => { if (code > 0) { // Self-kill, because apparently even if calling process.exit(1), the \ // process stays active and sticky in certain cases (due to \ // registered listeners and file descriptors in some Gulp libraries). // Warning: this is a bit hacky! process.exitCode = code; process.kill(process.pid, "SIGKILL"); } }); process.on("uncaughtException", (error) => { // Throw error and stop spinner spinner.fail("Unexpected failure:"); console.log( (error && error.context) ? error.context : error ); spinner.stop(); process.exit(1); }); // Important: setup Gulp error listener, otherwise any error will get \ // uncaught and be handled by 'uncaughtException' at the process-level. gulp.on("error", (event) => { // Freeze spinner w/ failure spinner.fail( "Error in '" + event.name + "':" ); if (event.error) { console.log(event.error); } // Restart the spinner spinner.start(); if (_crash_on_error === true) { process.exit(1); } }); } /** * Setups Gulp logging * @private * @param {object} gulp * @param {object} spinner * @return {undefined} */ __setup_gulp_logging(gulp, spinner) { // Not quiet and not already setup? if (!args.quiet && this.__has_setup_gulp_logging !== true) { this.__has_setup_gulp_logging = true; gulp.on("start", (event) => { // Freeze spinner w/ information // Notice: do not log meta-events eg. '' if (event.name.startsWith("<") !== true) { spinner.info( "Starting '" + event.name + "'..." ); } }); gulp.on("stop", (event) => { // Freeze spinner w/ success // Notice: do not log meta-events eg. '' if (event.name.startsWith("<") !== true) { spinner.succeed( "Finished '" + event.name + "'" ); // Restart the spinner spinner.start(); } }); } } // jscs:enable disallowIdentifierNames } (new ChappeCLI()).run(); ================================================ FILE: bower.json ================================================ { "name": "chappe", "dependencies": { "reset.css": "https://github.com/shannonmoeller/reset-css.git#d8bfbea7095dd54700fdb7ff05953b27862c5363", "console": "https://github.com/valeriansaliou/console.js.git#fe138ee08caba80aadf7f2794e40131f40988755", "cookies": "https://github.com/crisp-dev/Cookies.git#ba57f18775e726a80bab9aaad35ee479c6520963", "cash": "https://github.com/fabiospampinato/cash.git#f7b4fc2ce0fc02eb367ecc6e5092d27e7c9809ba", "minisearch": "https://github.com/lucaong/minisearch.git#917cf84b2ff79f3ba5612f4a5d5f542b74f78bbf", "prism.js": "https://github.com/PrismJS/prism.git#61221218f1773ef5d4d8c60b02ed02c2d2e976ac" } } ================================================ FILE: examples/acme-docs/config.json ================================================ { "identity" : { "title" : "Acme Developer Hub", "copyright" : "Acme Inc." }, "theme" : { "accent" : { "light" : { "base" : "#2275f1", "active" : "#0f69ef" }, "dark" : { "base" : "#e0e7ed", "active" : "#d5dde4" } } }, "urls" : { "base" : "https://docs.acme.com" }, "favicons" : { "main" : "favicons/favicon.ico", "sizes" : { "default" : "favicons/favicon.png", "512x512" : "favicons/favicon-512x512.png", "256x256" : "favicons/favicon-256x256.png", "128x128" : "favicons/favicon-128x128.png", "32x32" : "favicons/favicon-32x32.png" } }, "images" : { "illustrations" : { "home" : "images/illustrations/home.png" }, "logos" : { "header_full" : "images/logos/logo-header-full.svg", "header_short" : "images/logos/logo-header-short.svg", "footer" : "images/logos/logo-footer.svg" }, "metas" : { "opengraph" : "images/metas/opengraph.png" }, "categories" : { "guides" : { "hello-world" : "images/categories/guides/hello-world.svg", "markdown-syntax" : "images/categories/guides/markdown-syntax.svg" }, "references" : { "rest-api" : "images/categories/references/rest-api.svg", "rtm-api" : "images/categories/references/rtm-api.svg", "team" : "images/categories/references/team.svg" } } }, "dimensions" : { "logos" : { "header_full" : { "width" : 136 }, "header_short" : { "width" : 46 }, "footer" : { "width" : 84 } } }, "colors" : { "changes" : { "groups" : { "rest_api" : "#d34040", "rtm_api" : "#dc9b37", "web_hooks" : "#1a71f5" } } }, "texts" : { "home" : { "title" : "Welcome to the Acme Developer Hub", "label" : "Build apps for 10M+ Acme app users." }, "changes" : { "groups" : { "rest_api" : "REST API", "rtm_api" : "RTM API", "web_hooks" : "Web Hooks" }, "titles" : { "feed" : "Acme Platform Changes", "latest" : "Latest Platform Changes", "year" : "Platform Changes In" }, "notice" : "**The Acme Platform is updated every day with improvements and new features.** This page is a ledger of all notable changes that occured over time, and which may impact your integrations.\n\n**Note that breaking changes appear in red.**\n\nIf you would like to monitor those changes, an RSS feed is available at: [`changes.rss`](/changes.rss)" } }, "links" : { "header" : { "navigation" : [ { "route" : ["guides"], "label" : "Guides" }, { "route" : ["references"], "label" : "References", "dropdown" : [ { "route" : ["rest-api", "v1"], "label" : "REST API Reference" }, { "route" : ["rtm-api", "v1"], "label" : "RTM API Reference" } ] } ], "actions" : [ { "label" : "Sign Up", "dropdown" : [ { "title" : "Sign Up to Acme", "subtitle" : "Create your Acme user account.", "target" : "https://dashboard.acme.com/" }, { "title" : "Sign Up to Developer Center", "subtitle" : "Start building Acme integrations.", "target" : "https://developers.acme.com/" } ] } ] }, "footer" : { "navigation" : [ { "label" : "Acme Website", "target" : "https://acme.com/" }, { "label" : "Terms of Use", "target" : "https://acme.com/terms/" }, { "label" : "Privacy Policy", "target" : "https://acme.com/privacy/" }, { "label" : "About Us", "target" : "https://acme.com/about/" }, { "label" : "Read our Blog", "target" : "https://blog.acme.com/" } ] } }, "actions" : { "home" : [ { "route" : ["guides"], "label" : "Build your First Acme App" } ] }, "bulletpoints" : { "home" : { "quickstart" : { "description" : "Start building on the top of the Acme Platform in a matter of minutes.", "actions" : [ { "route" : ["guides"], "label" : "Start building" } ] }, "guides" : { "description" : "Guides & tutorials on how to integrate Acme with your systems.", "actions" : [ { "route" : ["guides", "hello-world"], "label" : "Introduction" }, { "route" : ["guides", "markdown-syntax", "syntax"], "label" : "Markdown Syntax" } ] }, "references" : { "description" : "In-depth technical references of all available Acme APIs.", "actions" : [ { "route" : ["references", "rest-api", "v1"], "label" : "REST API" }, { "route" : ["references", "rtm-api", "v1"], "label" : "RTM API" } ] } } }, "includes" : { "scripts" : { "urls" : [], "inline" : [] }, "stylesheets" : { "urls" : [], "inline" : [] } }, "tokens" : { "crisp_website_id" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }, "plugins" : { "code" : { "syntaxes" : [ "markup", "markup-templating", "css", "clike", "c", "javascript", "bash", "go", "java", "groovy", "json", "php", "python", "ruby", "rust", "swift", "objectivec" ] } }, "features" : { "support" : true }, "rules" : { "build_size" : { "fail" : true, "sizes" : { "references" : { "pages" : { "gzip_maximum" : 80000 } }, "guides" : { "pages" : { "gzip_maximum" : 25000 }, "images" : { "maximum" : 500000 } }, "data" : { "objects" : { "maximum" : 140000, "gzip_maximum" : 25000 } } } } } } ================================================ FILE: examples/acme-docs/data/changes/2020.json ================================================ [ { "group" : "rest_api", "type" : "change", "date" : "2020-12-08", "text" : "The REST API now rejects all requests without an `application/json` `Content-Type` header. Please update your apps!" }, { "group" : "web_hooks", "type" : "change", "date" : "2020-05-28", "text" : "The Web Hooks delivery user-agent has been updated to: `Acme-Hooks-Deliver (1.0)`" } ] ================================================ FILE: examples/acme-docs/data/changes/2021.json ================================================ [ { "group" : "rest_api", "type" : "change", "date" : "2021-12-03", "text" : "New official REST API libraries have been added for Golang and Java." }, { "group" : "web_hooks", "type" : "deprecation", "date" : "2021-05-23", "text" : "The Web Hooks retry system **has been phased out**. If your server does not handle Web Hooks from us immediately, we will not attempt to deliver them again anymore." }, { "group" : "rtm_api", "type" : "change", "date" : "2021-01-04", "text" : "The RTM API now **auto-closes the socket channel** if the authentication payload is not received in a timely manner (20 seconds maximum)." } ] ================================================ FILE: examples/acme-docs/data/guides/hello-world/index.md ================================================ TITLE: Hello World INDEX: 1 UPDATED: 2021-09-22 LINK: REST API Reference -> /references/rest-api/v1/ LINK: RTM API Reference -> /references/rtm-api/v1/ Hello World! We advise that you start with the [Quickstart guide](./quickstart/), which will get you running in a few minutes. + Navigation | Quickstart: Learn how to use our systems in minutes. -> ./quickstart/ ================================================ FILE: examples/acme-docs/data/guides/hello-world/quickstart/index.md ================================================ TITLE: Quickstart INDEX: 1 UPDATED: 2021-09-22 **To retrieve and send data on behalf of Acme teams, you need to use the REST API. The REST API can be connected to over HTTPS.** This guide walks you through using the REST API. Note that the REST API can be used aside to the RTM API which lets you receive real-time events associated to any action that you take with the REST API. # Overview Schematic ![Overview schematic](schematic-overview.png) _👉 The REST API lets your integration access features that regular Acme operator users have access to, such as sending messages. Note that the RTM API, not covered in this guide, lets you receive real-time events, and can be used side by side with the REST API._ --- # In-Depth Video Tutorial ${youtube}[In-depth Introduction to the Acme REST API](4A37k4KAHfo) --- # How To Use ## Use a library The most convenient way to use the REST API is to use a REST API-compatible library that we provide. Let's provide an example on how you can make requests to the REST API in your code. #### 1. Import and configure the library ```javascript var Acme = require("acme-api"); // Create the Acme client (it lets you access the REST API and RTM API at once) var AcmeClient = new Acme(); // Configure your Acme authentication tokens ('app' token) AcmeClient.authenticateTier("app", "", ""); ``` #### 2. Make requests to the API ```javascript // // Send text message for `team_id` and `session_id` // Note: replace '' and '' with your values AcmeClient.team.sendMessageInConversation( "", "", { type : "text", from : "operator", origin : "chat", content : "This was sent from the REST API!" } ) .then(() => { console.info("Message has been sent."); }) .catch((error) => { console.error("Message could not be sent, because:", error); }); ``` --- ## Direct usage (no library) **In case no library is available, it is still easy to use the REST API using an HTTP library** available for your programming language. As long as you are able to make requests in all available HTTP methods, set body content, and configure request headers, then you are good to go. **The HTTP endpoint base you will need to use is:** `https://api.acme.com/v1/`. Note that all requests must be done via HTTPS (HTTP over TLS), we do not support HTTP (plain text HTTP). **The full list of HTTP routes available to you**, as well as the data input that they expect, is available on the [REST API Reference](/references/rest-api/v1/). ! The examples that follow **will use cURL over the command-line**, as API users that do not rely on a library are most-likely using the command-line instead. !! Make sure to replace `{identifier}:{key}` with your token keypair (keep the middle `:` separator). Also, replace `{team_id}` with your team identifier, and any other value with `{brackets}`. #### Example 1: Get team details ```bash curl https://api.acme.com/v1/team/{team_id} \ --get \ --user "{identifier}:{key}" \ --header "X-Acme-Tier: app" ``` #### Example 2: Send a text message ```bash curl https://api.acme.com/v1/team/{team_id}/conversation/{session_id}/message \ --user "{identifier}:{key}" \ --header "Content-Type: application/json" \ --header "X-Acme-Tier: app" \ --data '{ "type": "text", "from": "operator", "origin": "chat", "content": "This was sent with cURL!" }' ``` _Note that `--post` does not need to be passed there, as cURL assumes it implicitly if `--data` is being used._ --- # Usage Considerations ## Rate-limits (quotas) **The REST API is subject to rate-limits and daily quotas** to protect our systems against abuse. It guarantees that the REST API stays as fast and responsive as possible for all Acme users. --- ## Submitted data **Whenever you submit data to the API using `POST`, `PUT` or `PATCH`, it gets validated against a data schema.** The [REST API Reference](/references/rest-api/v1/) specifies the data format that schemas enforce, within the Data Structure section, for each request. Whenever you submit data to the REST API, if it gets rejected by schema validators, **you will receive the following response error: `invalid_data`**. A message may also be provided, with explanations of what you did wrong. ================================================ FILE: examples/acme-docs/data/guides/index.md ================================================ TITLE: Guides INDEX: 1 UPDATED: 2021-09-22 **👋 Welcome to the Acme Developer Hub!** We have written extensive guides to help you build powerful integrations with Acme. This ranges from our REST API to our RTM API. Guides are classified in categories, for each system available to you. Aside from guides, you may also find references. References are more technical, as they serve as full specifications of our systems. You may want to use guides and references aside, switching between both of them. + Navigation | Hello World: Guides on how to start using our systems. -> ./hello-world/ [blank] | Markdown Syntax: Guides on the Markdown syntax you can use. -> ./markdown-syntax/ ! If you have any technical question while building your integration with Acme, feel free to [chat with our support team](#crisp-chat-open). Our technical support team will gladly help you fix any issue you encounter. ================================================ FILE: examples/acme-docs/data/guides/markdown-syntax/index.md ================================================ TITLE: Markdown Syntax INDEX: 2 UPDATED: 2022-01-05 This is the Markdown syntax guide, it contains two sections: + Navigation | Syntax: Guide of all available Markdown elements. -> ./syntax/ | Section Example: Examples of sub-sections. -> ./section-example/ ================================================ FILE: examples/acme-docs/data/guides/markdown-syntax/section-example/index.md ================================================ TITLE: Section Example INDEX: 2 UPDATED: 2022-01-05 This section contains two example sub-sections. **Pick one in the sidebar**, or the navigation menu below. + Navigation | Sub-Section One: First example sub-section. -> ./sub-section-one/ | Sub-Section Two: Second example sub-section. -> ./sub-section-two/ ================================================ FILE: examples/acme-docs/data/guides/markdown-syntax/section-example/sub-section-one/index.md ================================================ TITLE: Sub-Section One INDEX: 1 UPDATED: 2022-01-05 ! This is an example of a sub-section! **(number one)** ================================================ FILE: examples/acme-docs/data/guides/markdown-syntax/section-example/sub-section-two/index.md ================================================ TITLE: Sub-Section Two INDEX: 1 UPDATED: 2022-01-05 !! This is an example of a sub-section! **(number two)** ================================================ FILE: examples/acme-docs/data/guides/markdown-syntax/syntax/index.md ================================================ TITLE: Syntax INDEX: 1 UPDATED: 2022-01-05 # Navigation Links You can easily insert navigation links anywhere as such: + Navigation | Hello World Quickstart: Read the Hello World guide. -> /guides/hello-world/quickstart/ | Example Sub-Section One: First example sub-section. -> /guides/markdown-syntax/section-example/sub-section-one/ | Example Sub-Section Two: Second example sub-section. -> /guides/markdown-syntax/section-example/sub-section-two/ --- # Text Samples Sem integer vitae justo eget magna. Euismod lacinia at quis risus. Tellus cras adipiscing enim eu turpis egestas. Fringilla urna porttitor rhoncus dolor purus non. Commodo viverra maecenas accumsan lacus vel. **Feugiat in fermentum posuere urna nec tincidunt praesent semper feugiat**. **Turpis egestas integer eget aliquet nibh.** Pharetra convallis posuere morbi leo urna molestie at. Metus aliquam eleifend mi in. Scelerisque varius morbi enim nunc faucibus a. Condimentum id venenatis a condimentum vitae sapien. Neque [ornare aenean](#) euismod elementum nisi quis. Lacus laoreet non curabitur gravida arcu ac tortor. Vestibulum lectus mauris ultrices eros in! Leo urna molestie at _elementum_ eu **_facilisis_** sed, eg. `team:conversation:messages` Nam aliquam sem et tortor consequat id porta nibh. Sagittis vitae et leo duis ut. **Suspendisse faucibus interdum** posuere lorem ipsum dolor sit amet. Nisl tincidunt eget nullam non nisi est sit amet. Urna nunc id cursus metus aliquam eleifend. 😀 Turpis massa tincidunt dui ut ornare lectus sit. Eget dolor morbi non arcu risus quis varius quam quisque. Maecenas sed enim ut sem viverra. Etiam tempor orci eu lobortis elementum nibh. **Tristique senectus et netus et malesuada fames.** !!! Commodo viverra maecenas accumsan lacus vel! Eget egestas purus viverra accumsan in nisl nisi. Blandit aliquam etiam erat velit scelerisque in dictum non consectetur. Nam at lectus urna duis convallis. 😅 !! Mattis molestie a **iaculis at erat** pellentesque adipiscing. Posuere lorem ipsum dolor sit amet. Nec tincidunt praesent semper feugiat nibh sed. Vestibulum lorem sed risus ultricies tristique nulla aliquet enim. ! Id diam maecenas ultricies mi eget mauris pharetra et ultrices. Pretium lectus quam id leo in vitae. Posuere ac ut consequat semper viverra. Massa massa ultricies mi quis hendrerit. Nullam ac tortor vitae purus faucibus. In tellus integer feugiat scelerisque varius morbi enim nunc: > “Id diam maecenas ultricies mi eget mauris pharetra et ultrices. Pretium lectus quam id leo in vitae. Posuere ac ut consequat semper viverra. Massa massa ultricies mi quis hendrerit. Nullam ac tortor vitae purus faucibus.” > > Valerian. --- # Footnote Samples This sentence has two[^foo] footnotes[^bar]. --- # List Samples Nunc lobortis mattis aliquam faucibus purus in massa. Risus feugiat in ante metus dictum at tempor. Felis eget velit aliquet sagittis id consectetur. **Vestibulum lectus mauris ultrices eros in:** * Access to buckets: `bucket:url` * Access to availability information: `team:availability` * Access to conversation initiate: `team:conversation:initiate` * Access to conversation sessions: `team:conversation:sessions` * Access to conversation messages: `team:conversation:messages` * Access to a people profiles: `team:people:profiles` * Access to a people conversations: `team:people:conversations` * Access to a people events: `team:people:events` **Vestibulum lectus mauris ultrices eros in:** 1. Access to buckets: `bucket:url` 2. Access to availability information: `team:availability` 3. Access to conversation initiate: `team:conversation:initiate` --- # Code Sample Inline code: `echo "hello world"` Block code: ```javascript var dump = { error : false }; console.log("Hello World", dump); ``` --- # Video Embed Sample YouTube videos can be included in Markdown, as such: ${youtube}[An Architect's Own House Situated on a Remote Beach](LildjJAG0fk) --- # Full-Width Image Sample Full-width images can be included as such: ![](image-caption.jpg) --- # Caption Image Sample Images can be included with a caption, as such: $[Sample Mountain Image](![](image-caption.jpg)) --- # Table Sample Est ullamcorper eget nulla facilisi etiam dignissim diam quis enim. **Sit amet dictum sit amet justo donec enim diam vulputate.** Elit eget gravida cum sociis natoque penatibus et. Ut faucibus pulvinar elementum integer enim neque: | Route Name | API Reference | Associated Scopes | Comments | | --- | --- | --- | --- | | Check If Conversation Exists | [Read reference](/references/rest-api/v1/) | `team:conversation:sessions` | — | | Initiate Conversation With Session | [Read reference](/references/rest-api/v1/) | `team:conversation:initiate` | Write permission required | | Send Message In Conversation | [Read reference](/references/rest-api/v1/) | `team:conversation:messages` | Write permission required | | Assign Conversation Routing | [Read reference](/references/rest-api/v1/) | `team:conversation:routing` | Write permission required | | List Conversation Pages | [Read reference](/references/rest-api/v1/) | `team:conversation:pages` | — | | Get Conversation State | [Read reference](/references/rest-api/v1/) | `team:conversation:states` | — | | Request Email Transcript | [Read reference](/references/rest-api/v1/) | `team:conversation:actions` | Write permission required | Ac tortor vitae purus faucibus ornare suspendisse. Egestas sed tempus urna et pharetra. Euismod nisi porta lorem mollis aliquam ut porttitor! 👏 --- # Title Samples ## Level 2 Title ### Level 3 Title #### Level 4 Title ##### Level 5 Title ###### Level 6 Title --- [^foo]: This is an example footnote. [^bar]: Note that it has to be defined **on a single line**, but using `
`
you can still make it multiline if needed. ================================================ FILE: examples/acme-docs/data/references/rest-api/_private.md ================================================ TYPE: API Blueprint TITLE: REST API Reference (Private) UPDATED: 2021-11-03 FORMAT: 1A HOST: https://api.acme.com/v1 # Reference This reference documents private Acme API routes. ☢️ **Those routes are solely used by Acme systems to perform eg. account management and other tasks. This may not be shared to public users, although routes are still available for them to access through the same API endpoint than the public one.** # Group Email Manages Acme emails. ## Subscription [/email/{email_hash}/subscription] Manages email subscriptions. Used to subscribe or unsubscribe from Acme notification emails. ### Get Subscription Status [GET /email/{email_hash}/subscription/{key}{?team_id}] Resolves current subscription status (subscribed or unsubscribed). + Attributes + error (boolean) + reason (string) + data (object) + subscribed (boolean) - Whether email is subscribed or not + Parameters + email_hash (string) - Email secure hash + key (string) - Private security for given email + team_id (string, optional) - Team identifier for email + Request Get Subscription Status (application/json) + Body + Response 200 (application/json) + Body ``` { "error": false, "reason": "resolved", "data": { "subscribed": true } } ``` + Response 404 (application/json) + Body ``` { "error": true, "reason": "email_not_found", "data": {} } ``` ================================================ FILE: examples/acme-docs/data/references/rest-api/v1.md ================================================ TYPE: API Blueprint TITLE: REST API Reference (V1) UPDATED: 2021-12-22 FORMAT: 1A HOST: https://api.acme.com/v1 # Reference The Acme REST API offers access and control over all Acme data. All resources that you will most likely use are prefixed with a star symbol (⭐). **While integrating the REST API, you may be interested in the following guides:** + Navigation | Quickstart: Get started in minutes. -> /guides/hello-world/quickstart/ # Group Team Manages Acme teams. ## Base [/team] Manages teams. ### Check If Team Exists [HEAD /team{?domain}] Checks if given team exists (by domain). + Parameters + domain (string, required) - The team domain to check against + Request Check If Team Exists (application/json) + Tiers: `user` `app` + Body + Response 200 (application/json) + Response 404 (application/json) ### Create Team [POST /team] Creates a new team. + Attributes + name (string, required) - Team name + domain (string, required) - Team domain + Request Create Team (application/json) + Tiers: `user` + Body ``` { "name": "Acme, Inc.", "domain": "acme-inc.com" } ``` + Response 201 (application/json) + Body ``` { "error": false, "reason": "added", "data": { "team_id": "e2efddb0-d1ce-47fd-99f5-d3a5b69f1def" } } ``` + Response 423 (application/json) + Body ``` { "error": true, "reason": "quota_limit_exceeded", "data": {} } ``` ### Get A Team [GET /team/{team_id}] Resolves an existing team information. + Attributes + error (boolean) + reason (string) + data (object) + team_id (string) - Team identifier + name (string) - Team name + domain (string) - Team domain + Parameters + team_id (string) - The team identifier + Request Get Team Information (application/json) + Tiers: `user` `app` + Body + Response 200 (application/json) + Body ``` { "error": false, "reason": "resolved", "data": { "team_id": "8c842203-7ed8-4e29-a608-7cf78a7d2fcc", "name": "Acme", "domain": "acme.com" } } ``` + Response 403 (application/json) + Body ``` { "error": true, "reason": "not_allowed", "data": {} } ``` + Response 404 (application/json) + Body ``` { "error": true, "reason": "not_subscribed", "data": {} } ``` ### Delete A Team [DELETE /team/{team_id}] Deletes an existing team. + Attributes + verify (string, required) - User password (used to double-authenticate deletion) + Parameters + team_id (string) - The team identifier + Request Delete A Team (application/json) + Tiers: `user` + Body ``` { "verify": "MySuperSecurePassword" } ``` + Response 200 (application/json) + Body ``` { "error": false, "reason": "deleted", "data": {} } ``` + Response 403 (application/json) + Body ``` { "error": true, "reason": "not_allowed", "data": {} } ``` + Response 404 (application/json) + Body ``` { "error": true, "reason": "team_not_found", "data": {} } ``` + Response 423 (application/json) + Body ``` { "error": true, "reason": "password_unverified", "data": {} } ``` ## Conversations [/team/{team_id}/conversations] Manages multiple team conversations. ### ⭐ List Conversations [GET /team/{team_id}/conversations/{page_number}{?search_query}{&search_type}{&search_operator}] Lists conversations for team. + Attributes + error (boolean) + reason (string) + data (array) + (object) + session_id (string) - Session identifier + team_id (string) - Team identifier + people_id (string) - People identifier + state (enum[string]) - Conversation state + Members + `pending` + `unresolved` + `resolved` + status (enum[number]) - Conversation status (an alias of state; useful for sorting conversations) + Members + `0` - Numeric code for pending status + `1` - Numeric code for unresolved status + `2` - Numeric code for resolved status + is_verified (boolean) - Whether session is verified or not (user email ownership is authenticated) + is_blocked (boolean) - Whether session is blocked or not (block messages from visitor) + availability (enum[string]) - Visitor availability + Members + `online` + `offline` + active (object) - User activity statistics + now (boolean) - Whether user is considered active right now or not + last (number) - Timestamp at which the user was last active + last_message (string) - Last message excerpt + participants (array) - External participants for this conversation + (object) + type (enum[string]) - External participant type + Members + `email` + target (string) - External participant target (ie. email address, identifier, etc.) + mentions (array[string]) - Mentioned user identifiers (from conversation messages) + created_at (number) - Conversation creation timestamp + updated_at (number) - Conversation update timestamp + compose (object) - Compose states + operator (object) - Compose state for operator + type (enum[string]) - Compose state type + Members + `start` + `stop` + excerpt (string) - Message excerpt for compose state + timestamp (number) - Timestamp for compose state + user (object) - Compose user information + user_id (string) - Compose user identifier + nickname (string) - Compose user nickname + avatar (string) - Compose user avatar + visitor (object) - Compose state for visitor + type (enum[string]) - Compose state type + Members + `start` + `stop` + excerpt (string) - Message excerpt for compose state + timestamp (number) - Timestamp for compose state + unread (object) - Unread messages counters + operator (number) - Unread messages counter for operator + visitor (number) - Unread messages counter for visitor + assigned (object) - Assigned operator (if any) + user_id (string) - Operator user identifier + meta (object) - Meta-data for conversation + nickname (string) - Visitor nickname + email (string) - Visitor email + phone (string) - Visitor phone + address (string) - Visitor address + ip (string) - Visitor IP address + data (object) - Visitor data + avatar (string) - Visitor avatar + device (object) - Device information + capabilities (array) - Visitor device capabilities + (enum[string]) + Members + `browsing` + `call` + geolocation (object) - Geolocation information for visitor device + country (string) - Country code + region (string) - Region code + city (string) - City name + coordinates (object) - Location coordinates + latitude (number) - Latitude coordinate + longitude (number) - Longitude coordinate + system (object) - Visitor device system information + os (object) - Operating system information + version (string) - OS version + name (string) - OS name + engine (object) - Rendering engine information + version (string) - Engine version + name (string) - Engine name + browser (object) - Browser information + major (string) - Browser major version (eg: version 8.1 has a major of 8) + version (string) - Browser version + name (string) - Browser name + useragent (string) - Visitor user agent + timezone (number) - Visitor device timezone offset (UTC) + locales (array[string]) - Visitor device locales + segments (array[string]) - Segments attributed to conversation + Parameters + team_id (string) - The team identifier + page_number (number, optional) - Page number for conversations paging + search_query (string, optional) - Search query in all conversations (text if type is `text` or `segment`, filter if type is `filter`) + search_type (string, optional) - Search type (either `text`, `segment` or `filter`) + search_operator (string, optional) - Search operator if search type is `filter` (`or` or `and` respectful to boolean algebra, defaults to `and` if not set) + Request List Conversations (application/json) + Tiers: `user` `app` + Scopes: `team:conversation:sessions` + Body + Response 206 (application/json) + Body ``` { "error": false, "reason": "listed", "data": [ { "session_id": "session_aaea8e1d-d6e3-4238-9252-d8c2e5579f5c", "team_id": "8c842203-7ed8-4e29-a608-7cf78a7d2fcc", "people_id": "0cb89450-34fb-4d51-8905-040c1d14a594", "status": 1, "state": "unresolved", "is_verified": false, "is_blocked": false, "availability": "offline", "active": { "now": false }, "last_message": "All right, thanks.", "mentions": [], "participants": [ { "type": "email", "target": "jane.doe@acme-inc.com" } ], "updated_at": 1468401603070, "created_at": 1468341857826, "unread": { "operator": 0, "visitor": 1 }, "assigned": { "user_id": "a4c32c68-be91-4e29-8a05-976e93abbe3f" }, "meta": { "nickname": "Dan Boy", "email": "dan.boy@acme-inc.com", "ip": "104.236.186.68", "avatar": null, "device": { "capabilities": [ "call" ], "geolocation": { "country": "US", "region": "CA", "city": "San Francisco", "coordinates": { "latitude": 37.7749, "longitude": -122.4194 } }, "system": { "os": { "version": "10.11.5", "name": "Mac OS" }, "engine": { "name": "WebKit", "version": "537.36" }, "browser": { "major": "51", "version": "51.0.2683.0", "name": "Chrome" }, "useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2683.0 Safari/537.36" }, "timezone": -120, "locales": [ "en", "fr" ] }, "segments": [ "customer", "friend" ] } } ] } ``` + Response 400 (application/json) + Body ``` { "error": true, "reason": "search_query_too_long", "data": {} } ``` + Response 402 (application/json) + Body ``` { "error": true, "reason": "subscription_upgrade_required", "data": {} } ``` + Response 403 (application/json) + Body ``` { "error": true, "reason": "not_allowed", "data": {} } ``` + Response 404 (application/json) + Body ``` { "error": true, "reason": "not_subscribed", "data": {} } ``` + Response 423 (application/json) + Body ``` { "error": true, "reason": "quota_limit_exceeded", "data": {} } ``` ================================================ FILE: examples/acme-docs/data/references/rtm-api/v1.md ================================================ TYPE: Markdown TITLE: RTM API Reference (V1) UPDATED: 2021-09-22 Events are sent on the RTM Events API WebSocket channel that you can open alongside your REST API channel, which allows you to receive asynchronous replies and events for some of your actions via the REST API. + Navigation | Quickstart: Get started in minutes. -> /guides/hello-world/quickstart/ # Endpoint You may subscribe to events by opening a [Socket.IO](https://socket.io/) connection to the WebSocket endpoint. **The RTM API endpoint URL is:** `wss://rtm.acme.com/` ! **There is a limit on the maximum number of connections that can be simultaneously open** with the RTM API (per-token, per-team and per-IP). This limit is quite high and can be revised at any time. Please make sure to teardown any unused connection before opening a new one. _Most use cases require a single RTM API connection._ --- # Namespaces Available RTM event namespaces are listed below. !! Note that if your API token is a `app` tier token, then you will **only have access to the RTM events that your token scopes allow**, based on the API routes that you have access to. Required scopes and tiers are listed for each event below. # Events ## Session Events ### Session Update Availability * **Event**: `session:update_availability` * **Description:** session availability changed _(eg. online to offline)_ * **Tiers:** `user` `app` * **Scopes:** `team:conversation:sessions` + `read` ```json { "team_id": "42286ab3-b29a-4fde-8538-da0ae501d825", "session_id": "session_36ba3566-9651-4790-afc8-ffedbccc317f", "availability": "online" } ``` ### Session Update Verify * **Event**: `session:update_verify` * **Description:** session verification status changed * **Tiers:** `user` `app` * **Scopes:** `team:conversation:sessions` + `read` ```json { "team_id": "42286ab3-b29a-4fde-8538-da0ae501d825", "session_id": "session_36ba3566-9651-4790-afc8-ffedbccc317f", "is_verified": true } ``` --- ## Message Events ### Message Updates * **Event**: `message:updated` * **Description:** message has been updated * **Tiers:** `user` `app` * **Scopes:** `team:conversation:messages` + `read` ```json { "team_id": "42286ab3-b29a-4fde-8538-da0ae501d825", "session_id": "session_36ba3566-9651-4790-afc8-ffedbccc317f", "fingerprint": 163240180126629, "content": "This is an edited message!" } ``` ================================================ FILE: examples/acme-docs/package.json ================================================ { "dependencies": { "chappe": "1.x.x" } } ================================================ FILE: gulpfile.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var fs = require("fs"); var path = require("path"); var lodash = require("lodash"); var del = require("del"); var glob = require("glob"); var merge = require("merge-stream"); var marked = require("marked").marked; var marked_footnote = require("marked-footnote"); var marked_gfm_heading_id = require("marked-gfm-heading-id").gfmHeadingId; var marked_mangle = require("marked-mangle").mangle; var remove_markdown = require("@tommoor/remove-markdown"); var MiniSearch = require("minisearch"); var Feed = require("feed").Feed; var gulp = require("gulp"); var gulp_connect = require("gulp-connect"); var gulp_file = require("gulp-file"); var gulp_bower = require("gulp-bower"); var gulp_pug = require("gulp-pug"); var gulp_sass = require("gulp-sass")(require("sass")); var gulp_inline_image = require("gulp-inline-image"); var gulp_ogimage = require("gulp-ogimage"); var gulp_sass_variables = require("gulp-sass-variables"); var gulp_concat = require("gulp-concat"); var gulp_babel = require("gulp-babel"); var gulp_clean_css = require("gulp-clean-css"); var gulp_uglify = require("gulp-uglify"); var gulp_rename = require("gulp-rename"); var gulp_replace = require("gulp-replace"); var gulp_header = require("gulp-header"); var gulp_pug_lint = require("gulp-pug-lint"); var gulp_stylelint = require("gulp-stylelint-esm").default; var gulp_jshint = require("gulp-jshint"); var gulp_jscs = require("gulp-jscs"); var gulp_sizereport = require("gulp-sizereport"); var gulp_sitemap = require("gulp-sitemap"); var gulp_notify = require("gulp-notify"); var gulp_noop = require("gulp-noop"); var package = require("./package.json"); var gulp_pug_templates = require("./res/plugins/gulp/pug-templates"); var gulp_minisearch = require("./res/plugins/gulp/minisearch"); var marked_renderers = { heading : require("./res/plugins/marked/renderers/heading"), code : require("./res/plugins/marked/renderers/code") }; var marked_extensions = { navigation : require("./res/plugins/marked/extensions/navigation"), navigation_item : require("./res/plugins/marked/extensions/navigation-item"), emphasis : require("./res/plugins/marked/extensions/emphasis"), figcaption : require("./res/plugins/marked/extensions/figcaption"), embed : require("./res/plugins/marked/extensions/embed") }; // Global context var PARENT_CONTEXT = ( (typeof global.CONTEXT !== "undefined") ? global.CONTEXT : {} ); var CONTEXT = { // Data CONFIG : {}, SEARCH_INDEX : null, // Configurations PATH_CONFIG : ( PARENT_CONTEXT.config ? PARENT_CONTEXT.config.split(",") : null ), PATH_ASSETS : (PARENT_CONTEXT.assets || null), PATH_DATA : (PARENT_CONTEXT.data || null), PATH_TEMP : (PARENT_CONTEXT.temp || null), PATH_DIST : (PARENT_CONTEXT.dist || null), SERVE_HOST : (PARENT_CONTEXT.host || null), SERVE_PORT : (PARENT_CONTEXT.port || null), PATH_CHAPPE : null, PATH_SOURCES : null, PATH_LIBRARIES : null, PATH_BUILD_PAGES : null, PATH_BUILD_ASSETS : null, IS_PRODUCTION : ((PARENT_CONTEXT.env === "production") ? true : false), IS_WATCH : false }; // Initializers marked.use({ renderer : { heading : marked_renderers.heading, code : marked_renderers.code }, extensions : [ marked_extensions.navigation, marked_extensions.navigation_item, marked_extensions.emphasis, marked_extensions.figcaption, marked_extensions.embed ] }); marked.use( marked_footnote(), marked_gfm_heading_id(), marked_mangle() ); // Tasks /* Acquires the configuration */ var get_configuration = function(next) { // Assert that all paths are set if (CONTEXT.PATH_CONFIG === null || CONTEXT.PATH_ASSETS === null || CONTEXT.PATH_DATA === null || CONTEXT.PATH_TEMP === null || CONTEXT.PATH_DIST === null) { throw new Error( "A build path was not passed properly, please pass them as globals" ); } // Make sure that the config and data paths exist // Notice: do not check the 'temp' and 'dist' path, as they will get \ // auto-created. if (fs.existsSync(CONTEXT.PATH_ASSETS) !== true) { throw new Error("The assets path provided does not exist!"); } if (fs.existsSync(CONTEXT.PATH_DATA) !== true) { throw new Error("The data path provided does not exist!"); } CONTEXT.PATH_CONFIG.forEach(function(config_path) { if (fs.existsSync(config_path) !== true) { throw new Error( "One of the configuration path provided does not exist!" ); } }); // Initialize final source paths CONTEXT.PATH_CHAPPE = __dirname; CONTEXT.PATH_SOURCES = path.join(CONTEXT.PATH_CHAPPE, "./src"); CONTEXT.PATH_LIBRARIES = path.join(CONTEXT.PATH_TEMP, "./lib"); // Initialize final build paths CONTEXT.PATH_BUILD_PAGES = CONTEXT.PATH_DIST; CONTEXT.PATH_BUILD_ASSETS = (CONTEXT.PATH_DIST + "/static"); // Read atomic configurations (merge them together) var _merge_pipeline = [ // #1: Common configuration (project static) require("./res/config/common.json"), // #2: Site configuration (project defaults) { SITE : require("./res/config/user.json") } ]; // Read vector configurations CONTEXT.PATH_CONFIG.forEach(function(config_path) { // #3: Site configuration (user-provided) _merge_pipeline.push({ SITE : require(config_path) }); }); // Unwind merge pipeline _merge_pipeline.forEach(function(merge_object) { CONTEXT.CONFIG = lodash.merge(CONTEXT.CONFIG, merge_object); }); // Assign contextual values CONTEXT.CONFIG.ENVIRONMENT = ( (CONTEXT.IS_PRODUCTION === true) ? "production" : "development" ); CONTEXT.CONFIG.REVISION = ( "v" + (package.version || "0.0.0") ); next(); }; /* Installs bower packages */ var bower = function() { return gulp_bower({ directory : CONTEXT.PATH_LIBRARIES, cwd : CONTEXT.PATH_CHAPPE, verbosity : 1 }); }; /* Copies all user assets */ var copy_user_assets = function() { return gulp.src( CONTEXT.PATH_ASSETS + "/**/*" ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/user" ) ); }; /* Copies images (base images) */ var copy_images_base = function() { return gulp.src( CONTEXT.PATH_SOURCES + "/images/**/*" ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/images" ) ) .pipe( gulp_connect.reload() ); }; /* Copies images (guides images) */ var copy_images_guides = function() { return gulp.src( CONTEXT.PATH_DATA + "/guides/**/*.{jpg,jpeg,png,gif}" ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/images/guides/content" ) ) .pipe( gulp_connect.reload() ); }; /* Copies fonts */ var copy_fonts = function() { return gulp.src( CONTEXT.PATH_SOURCES + "/fonts/**/*" ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/fonts" ) ) .pipe( gulp_connect.reload() ); }; /* Concats javascript libraries */ var concat_libraries_javascripts = function() { // Acquire targets // Notice: append user-defined syntaxes for code coloring (from 'prism.js') var _targets = CONTEXT.CONFIG.LIBRARIES.JAVASCRIPTS.map(function(library) { return (CONTEXT.PATH_LIBRARIES + "/" + library); }); CONTEXT.CONFIG.SITE.plugins.code.syntaxes.forEach(function(syntax) { var _syntax_path = ( CONTEXT.PATH_LIBRARIES + "/prism.js/components/prism-" + syntax + ".js" ); if (fs.existsSync(_syntax_path) !== true) { throw new Error( "Unsupported code syntax plugin provided: " + syntax + " at path: " + _syntax_path ); } _targets.push(_syntax_path); }); return gulp.src(_targets) .pipe( gulp_concat("common/libs.js") ) .pipe( gulp_replace( "manual: _self.Prism && _self.Prism.manual,", "manual: true," ) ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/javascripts" ) ); }; /* Concats stylesheet libraries */ var concat_libraries_stylesheets = function() { return gulp.src( CONTEXT.CONFIG.LIBRARIES.STYLESHEETS.map(function(library) { return (CONTEXT.PATH_LIBRARIES + "/" + library); }) ) .pipe( gulp_concat("common/libs.css") ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets" ) ); }; /* Prepares Minisearch search index (before it gets populated) */ var minisearch_prepare = function(next) { // Initialize search index CONTEXT.SEARCH_INDEX = new MiniSearch( CONTEXT.CONFIG.SEARCH.OPTIONS.BASE ); next(); }; /* Consolidates Minisearch search index (after it was populated) */ var minisearch_consolidate = function() { // Generate search index data return gulp_file( ("./" + CONTEXT.CONFIG.SEARCH.INDEX), JSON.stringify(CONTEXT.SEARCH_INDEX), { src : true } ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/data" ) ); }; /* Compiles Pug templates (base templates) */ var pug_templates_base = function() { var _stream = merge(); gulp_pug_templates.list_config_locales(CONTEXT, package, marked) .forEach(function(pug_data) { _stream.add( gulp_pug_templates.pipe_commons(CONTEXT, pug_data.locale, function() { return gulp.src( CONTEXT.CONFIG.SOURCES.TEMPLATES.map(function(template) { return (CONTEXT.PATH_SOURCES + "/templates/" + template); }), { base : (CONTEXT.PATH_SOURCES + "/templates") } ) .pipe( gulp_pug(pug_data.config) ); }) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ) .pipe( gulp_connect.reload() ) ); }); return _stream; }; /* Compiles Pug templates (guides templates) */ var pug_templates_guides = function() { var _stream = merge(); // Acquire the guides meta-data // Format: { \ // segments, id, title, index, links, \ // updated, markdown, parent, subtrees \ // } // Output: [tree, linear] var _guide_meta_data = ( gulp_pug_templates.traverse_guides_tree(CONTEXT.PATH_DATA + "/guides") ); var _guide_meta_tree = _guide_meta_data[0], _guide_meta_linear = _guide_meta_data[1]; // Append all guides to search index var _categories = _guide_meta_tree[0]; if (_categories) { gulp_minisearch.insert_categories( CONTEXT, "guides", [_categories.title], (_categories.subtrees || []) ); } // Compile all guide pages gulp_pug_templates.list_config_locales(CONTEXT, package, marked) .forEach(function(pug_data) { _guide_meta_linear.forEach(function(guide_level) { // Generate current guide data (for locale) var pug_level_config = lodash.cloneDeep(pug_data.config); pug_level_config.data.guides = _guide_meta_tree; pug_level_config.data.guide = guide_level; // Forbid indexing? (if any entry segment starts with an underscore) var _segment_private_index = guide_level.segments.findIndex( function(segment) { return ((segment[0] === "_") ? true : false); } ); if (_segment_private_index !== -1) { pug_level_config.data.INDEXING = false; } _stream.add( gulp_pug_templates.pipe_commons( CONTEXT, pug_data.locale, function() { return gulp.src( (CONTEXT.PATH_SOURCES + "/templates/guides/index.pug"), { base : (CONTEXT.PATH_SOURCES + "/templates") } ) .pipe( gulp_pug(pug_level_config) ) .pipe( gulp_rename(function(file_path) { file_path.basename = path.join.apply(this, [].concat( guide_level.segments, ["index"] )); }) ); } ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ) ); }); }); return _stream; }; /* Compiles Pug templates (references templates) */ var pug_templates_references = function() { var _stream = merge(); // Acquire the parsed references content // Format: {segments, type, data} // Output: linear var _reference_meta_data = ( gulp_pug_templates.traverse_references_linear( CONTEXT, (CONTEXT.PATH_DATA + "/references") ) ); gulp_pug_templates.list_config_locales(CONTEXT, package, marked) .forEach(function(pug_data) { _reference_meta_data.forEach(function(reference_entry) { // Generate current reference data (for locale) var pug_entry_config = lodash.cloneDeep(pug_data.config); pug_entry_config.data.reference = reference_entry; // Forbid indexing? (if any entry segment starts with an underscore) var _segment_private_index = reference_entry.segments.findIndex( function(segment) { return ((segment[0] === "_") ? true : false); } ); if (_segment_private_index !== -1) { pug_entry_config.data.INDEXING = false; } // Append all reference anchors to search index // Important: only if reference indexing is not forbidden if (reference_entry.categories && pug_entry_config.data.INDEXING !== false) { // Acquire reference path, as well as reference path title (with \ // its common suffix extracted) // Notice: 'REST API Reference (V1)' becomes 'REST API' + 'V1' var _reference_path = ["References"], _title_split_regex = /^(.+) Reference \(([^\(\)]+)\)$/; var _title_matcher = ( reference_entry.title.match(_title_split_regex) ); var _reference_title_path = ( (_title_matcher && _title_matcher[1] && _title_matcher[2]) ? [_title_matcher[1], _title_matcher[2]] : [reference_entry.title] ); // Insert reference base gulp_minisearch.insert_categories( CONTEXT, "references", _reference_path, [reference_entry] ); // Insert reference sub-categories gulp_minisearch.insert_categories( CONTEXT, "references", [].concat(_reference_path, _reference_title_path), reference_entry.categories, reference_entry.segments ); } _stream.add( gulp_pug_templates.pipe_commons( CONTEXT, pug_data.locale, function() { return gulp.src( (CONTEXT.PATH_SOURCES + "/templates/references/index.pug"), { base : (CONTEXT.PATH_SOURCES + "/templates") } ) .pipe( gulp_pug(pug_entry_config) ) .pipe( gulp_rename(function(file_path) { file_path.basename = path.join.apply(this, [].concat( reference_entry.segments, ["index"] )); }) ); } ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ) .pipe( gulp_connect.reload() ) ); }); }); return _stream; }; /* Compiles Pug templates (changes templates) */ var pug_templates_changes = function() { var _stream = merge(); // Acquire the change years data // Format: [year, data] (ordered by most recent year first) var _change_years = ( glob.sync( (CONTEXT.PATH_DATA + "/changes/*.json") ) .sort() .reverse() .map(function(file_path) { var _file_path_parts = path.parse(file_path); return [ parseInt(_file_path_parts.name, 10), JSON.parse(fs.readFileSync(file_path)) ]; }) .map(function(year_data) { // Classify each change in its month (in an orderly manner) // Notice: the Map object is used as it retains natural ordering, \ // where last months come first. var _year_months = new Map(); year_data[1].forEach(function(entry) { // Validate entry data if (!entry.group || !entry.type || !entry.date || !entry.text) { throw new Error( "Invalid entry data in changes for year: " + year_data[0] ); } // Acquire entry date var _entry_date = (new Date(entry.date)); if (!_entry_date || isNaN(_entry_date.getTime())) { throw new Error( "Invalid date in changes for year: " + year_data[0] ); } // Acquire entry month key var _entry_month_key = ("" + (_entry_date.getMonth() + 1)); if (_entry_month_key.length === 1) { _entry_month_key = ("0" + _entry_month_key); } // Make sure entries object for month is initialized if (_year_months.has(_entry_month_key) === false) { _year_months.set(_entry_month_key, []); } // Append entry to month entries object _year_months.get(_entry_month_key).push(entry); }); // Assign new per-month data // Important: convert back all ordered map entries to an array, which \ // we can iterate on right away from templates. year_data[1] = Array.from( _year_months.entries() ); return year_data; }) ); // Map available change years (as bare numbers) // Important: before prepending latest year data var _available_bare_years = _change_years.map(function(change_year) { return change_year[0]; }); // Prepend list of years with the 'latest' year (ie. corresponding to the \ // 'index'), and push only the first 3 months (ie. latest) var _first_change_year = (_change_years[0] || []); _change_years.unshift([ (_first_change_year[0] || -1), (_first_change_year[1] || []).slice(0, 3), "index" ]); gulp_pug_templates.list_config_locales(CONTEXT, package, marked) .forEach(function(pug_data) { _change_years.forEach(function(change_year) { // Generate current year data (for locale) var pug_year_config = lodash.cloneDeep(pug_data.config); pug_year_config.data.years = _available_bare_years; pug_year_config.data.changes = { year : change_year[0], current : ( (change_year[2] === "index") ? "latest" : change_year[0] ), timeline : change_year[1] }; _stream.add( gulp_pug_templates.pipe_commons( CONTEXT, pug_data.locale, function() { return gulp.src( (CONTEXT.PATH_SOURCES + "/templates/changes/index.pug"), { base : (CONTEXT.PATH_SOURCES + "/templates") } ) .pipe( gulp_pug(pug_year_config) ) .pipe( gulp_rename(function(file_path) { file_path.basename = ( change_year[2] ? change_year[2] : ( path.join(("" + change_year[0]), "index") ) ); }) ); } ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ) .pipe( gulp_connect.reload() ) ); }); }); return _stream; }; /* Compiles all Pug templates (shell task to aggregate sub-tasks) */ var pug_templates_all = function() { return gulp.parallel( pug_templates_base, pug_templates_guides, pug_templates_references, pug_templates_changes ); }(); /* Compiles SCSS stylesheets */ var scss = function() { return gulp.src( CONTEXT.CONFIG.SOURCES.STYLESHEETS.map(function(stylesheet) { return (CONTEXT.PATH_SOURCES + "/stylesheets/" + stylesheet); }), { base : (CONTEXT.PATH_SOURCES + "/stylesheets") } ) .pipe( gulp_sass_variables({ "$use-inline-images" : CONTEXT.IS_PRODUCTION }) ) .pipe( gulp_sass({ outputStyle : "expanded" }) ) .on("error", gulp_notify.onError({ title : "scss", message : "Error compiling", emitError : true })) .on("error", function(error) { if (CONTEXT.IS_WATCH === true) { // Handle compile errors (used for development ease w/ `gulp watch`) console.error(error.toString()); } else { throw error; } }) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets" ) ); }; /* Imports inline images in stylesheets */ var css_inline_images = function() { return gulp.src(( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets/**/*.css" ), { base : (CONTEXT.PATH_BUILD_ASSETS + "/images") }) .pipe( gulp_inline_image() ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets" ) ) .pipe( gulp_connect.reload() ); }; /* Compiles Babel templates */ var babel = function() { return gulp.src( CONTEXT.CONFIG.SOURCES.JAVASCRIPTS.map(function(javascript) { return (CONTEXT.PATH_SOURCES + "/javascripts/" + javascript); }), { base : (CONTEXT.PATH_SOURCES + "/javascripts") } ) .pipe( gulp_babel() ) .on("error", gulp_notify.onError({ title : "babel", message : "Error compiling", emitError : true })) .on("error", function(error) { if (CONTEXT.IS_WATCH === true) { // Handle compile errors (used for development ease w/ `gulp watch`) console.error(error.toString()); } else { throw error; } }) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/javascripts" ) ); }; /* Replaces values from template files (guides templates) */ var replace_templates_guides = function() { return gulp.src( CONTEXT.PATH_BUILD_PAGES + "/guides/**/*.html" ) .pipe( gulp_replace( /src="(?:\.\/)?([^\.\/"']+\.(?:jpg|jpeg|png|gif))"/g, function(_, file_name) { // Acquire base path segments var _image_base_segments = this.file.relative.split("/"); _image_base_segments.pop(); // Build final path segments var _image_final_segments = ( [].concat( ["", "static", "images", "guides", "content"], _image_base_segments, [file_name] ) ); // Replace with final path to image file within static assets return ( "src=\"" + _image_final_segments.join("/") + "\"" ); } ) ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES + "/guides" ) ) .pipe( gulp_connect.reload() ); }; /* Replaces values from javascript files */ var replace_javascripts = function() { return gulp.src( CONTEXT.PATH_BUILD_ASSETS + "/javascripts/**/*.js" ) .pipe( gulp_replace( "@:revision", CONTEXT.CONFIG.REVISION ) ) .pipe( gulp_replace( "\"@:url_status\"", JSON.stringify({ provider : ( CONTEXT.CONFIG.SITE.urls.vigil ? "vigil" : ( CONTEXT.CONFIG.SITE.urls.crisp_status ? "crisp" : null ) ), target : ( CONTEXT.CONFIG.SITE.urls.vigil || CONTEXT.CONFIG.SITE.urls.crisp_status || null ) }) ) ) .pipe( gulp_replace( "@:search_index", CONTEXT.CONFIG.SEARCH.INDEX ) ) .pipe( gulp_replace( "\"@:search_options_base\"", JSON.stringify(CONTEXT.CONFIG.SEARCH.OPTIONS.BASE) ) ) .pipe( gulp_replace( "\"@:search_options_query\"", JSON.stringify(CONTEXT.CONFIG.SEARCH.OPTIONS.QUERY) ) ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/javascripts" ) ) .pipe( gulp_connect.reload() ); }; /* Replaces values from stylesheet files */ const replace_stylesheets = function() { return gulp.src( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets/**/*.css" ) .pipe( gulp_replace( "@@revision", CONTEXT.CONFIG.REVISION ) ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets" ) ); }; /* Compiles feeds (changes templates) */ var feed_changes = function() { var _title_maximum_length = 80; // Acquire paths for the last 3 years (sort by last year first) var _feed_years_back = 3; var _last_year_paths = glob.sync( (CONTEXT.PATH_DATA + "/changes/*.json") ) .sort() .reverse() .splice(0, _feed_years_back); // Acquire the change years data var _change_years = ( _last_year_paths.map(function(file_path) { return JSON.parse( fs.readFileSync(file_path) ); }) ); // Concatenate all changes together var _changes_flat = [].concat.apply( [], _change_years ); // Acquire feed title var _feed_title = ( CONTEXT.CONFIG.SITE.texts.changes.titles.feed || "Platform Changes" ); // Construct feed object var _feed = new Feed({ title : _feed_title, description : ("Feed of " + _feed_title + "."), link : CONTEXT.CONFIG.SITE.urls.base, language : "en", generator : package.name, author : { name : package.author.name }, favicon : ( CONTEXT.CONFIG.SITE.urls.base + "/favicon.ico" ), updated : (new Date()) }); _changes_flat.forEach(function(change) { // Strip Markdown from text var _text_barebone = ( remove_markdown(change.text || "") ); // Split text into a title var _title; if (_text_barebone.length > _title_maximum_length) { _title = ( _text_barebone.substr(0, _title_maximum_length) + " (..)" ); } else { _title = _text_barebone; } // Add entry _feed.addItem({ title : _title, content : marked(change.text), link : (CONTEXT.CONFIG.SITE.urls.base + "/changes/"), date : (new Date(change.date)) }); }); // Generate feed data return gulp_file( "changes.rss", _feed.rss2(), { src : true } ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ); }; /* Generates sitemap */ var sitemap = function() { // Scan all templates, while ignoring the 'not_found' page and all private \ // pages (ie. those starting w/ '_') return gulp.src([ (CONTEXT.PATH_BUILD_PAGES + "/**/*.html"), ("!" + CONTEXT.PATH_BUILD_PAGES + "/not_found/index.html"), ("!" + CONTEXT.PATH_BUILD_PAGES + "/**/_*/index.html") ]) .pipe( gulp_sitemap({ siteUrl : CONTEXT.CONFIG.SITE.urls.base, getLoc : function(site_url, location) { // Nuke file name and extension from URL? (if found) // Notice: built files are stored in directories, corresponding to \ // their path, ending in a file named eg. 'index.html'. var _file_index = location.lastIndexOf("index.html"); // Rewrite location? if (_file_index > 0) { location = location.substring(0, _file_index); } return location; } }) ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ); }; /* Generates robots */ var robots = function() { return gulp_file( "robots.txt", ( "User-Agent: *\n" + "Allow: /\n" + ("Sitemap: " + CONTEXT.CONFIG.SITE.urls.base + "/sitemap.xml\n") ), { src : true } ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ); }; /* Generates Open Graph images */ var ogimage = function() { // Generate Open Graph images (if any background is configured) return gulp.src( CONTEXT.PATH_BUILD_PAGES + "/**/*.html" ) .pipe(function() { if (CONTEXT.CONFIG.SITE.images.metas.opengraph) { return gulp_ogimage({ base : function(file) { // Acquire base path segments // Notice: pop the last two items var _page_base_segments = file.relative.split("/"); _page_base_segments.splice( (_page_base_segments.length - 2), 2 ); if (_page_base_segments.length > 0) { _page_base_segments.unshift(""); } // Generate base path for generated image, which will be output to \ // the final HTML file. return ( CONTEXT.CONFIG.SITE.urls.base + "/static/images/opengraph" + _page_base_segments.join("/") ); }, directory : function(file) { // Acquire base path segments // Notice: pop the last two items var _page_base_segments = file.relative.split("/"); _page_base_segments.splice( (_page_base_segments.length - 2), 2 ); // Build final path segments var _page_final_segments = ( [].concat( ["", "images", "opengraph"], _page_base_segments ) ); // Return full directory path (except from the first directory, \ // which becomes the image name); this will get used to output \ // the generated image file on the file system. return ( CONTEXT.PATH_BUILD_ASSETS + _page_final_segments.join("/") ); }, name : function(file) { // Acquire base path segments // Notice: pop the last item var _page_base_segments = file.relative.split("/"); _page_base_segments.pop(); // Return last directory name (if there is none, this means this \ // is the 'index' page); this will get used to output the \ // generated image file on the file system. return ( _page_base_segments[(_page_base_segments.length - 1)] || "index" ); }, backgroundImage : function() { // Return base background image, which will get used as a baseline \ // to generate all Open Graph images (ie. as their background \ // image). return ( CONTEXT.PATH_ASSETS + "/" + CONTEXT.CONFIG.SITE.images.metas.opengraph ); }, title : function(_, $) { return ( $("head title").first().text() || "" ); }, description : function(_, $) { return ( $("head meta[name=\"description\"]").first().attr("content") || "" ); } }) } // No Open Graph background image configured, warn and skip console.warn( "⚠️ No Open Graph background image configured, skipping the " + "auto-generation of 'og:image' metadata.\n " + "You can configure this with the 'images.metas.opengraph' " + "configuration namespace.\n" ); return gulp_noop(); }()) .pipe( gulp.dest( CONTEXT.PATH_BUILD_PAGES ) ); }; /* Minifies stylesheet files */ var cssmin = function() { return gulp.src( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets/**/*.css" ) .pipe( gulp_clean_css() ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/stylesheets" ) ); }; /* Minifies javascript files */ var uglify = function() { return gulp.src( CONTEXT.PATH_BUILD_ASSETS + "/javascripts/**/*.js" ) .pipe( gulp_uglify() ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS + "/javascripts" ) ); }; /* Insert banner in build files */ var build_banner = function() { var date = new Date(); var today = ( (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear() ); var banner = [ "/**", " * <%= pkg.name %> - <%= pkg.description %>", " * @version v<%= pkg.version %>", " * @author <%= pkg.author.name %> <%= pkg.author.url %>", " * @date <%= today %>", " */", "" ].join("\n"); return gulp.src( CONTEXT.PATH_BUILD_ASSETS + "/**/*.{css,js}" ) .pipe( gulp_header(banner, { pkg : package, today : today }) ) .pipe( gulp.dest( CONTEXT.PATH_BUILD_ASSETS ) ); }; /* Shows final build size */ var build_size = function() { var _stream = merge(); // Acquire sizes var _fail = (CONTEXT.CONFIG.SITE.rules.build_size.fail || false), _sizes = CONTEXT.CONFIG.SITE.rules.build_size.sizes; // Map all sources and options var _sources = [ { glob : ( CONTEXT.PATH_BUILD_PAGES + "/references/**/*.html" ), options : { production : { gzip : true, total : true, fail : _fail, "*" : { maxGzippedSize : _sizes.references.pages.gzip_maximum } } } }, { glob : ( CONTEXT.PATH_BUILD_PAGES + "/guides/**/*.html" ), options : { production : { gzip : true, total : true, fail : _fail, "*" : { maxGzippedSize : _sizes.guides.pages.gzip_maximum } } } }, { glob : ( CONTEXT.PATH_BUILD_ASSETS + "/images/guides/content/**/" + "*.{jpg,jpeg,png,gif}" ), options : { production : { total : true, fail : _fail, "*" : { maxSize : _sizes.guides.images.maximum } } } }, { glob : ( CONTEXT.PATH_BUILD_ASSETS + "/data/**/*.json" ), options : { production : { gzip : true, total : true, fail : _fail, "*" : { maxSize : _sizes.data.objects.maximum, maxGzippedSize : _sizes.data.objects.gzip_maximum } } } } ]; // Compute size report for each source _sources.forEach(function(source) { _stream.add( gulp.src(source.glob) .pipe( gulp_sizereport( (CONTEXT.IS_PRODUCTION === true) ? source.options.production : ( source.options.production.default || { total : true } ) ) ) ); }); return _stream; }; /* Builds all resources */ var build_resources = function() { // Generate main series var _series = [ gulp.parallel( robots, feed_changes, copy_user_assets, copy_images_guides, gulp.series( bower, gulp.parallel( concat_libraries_stylesheets, gulp.series( gulp.parallel( concat_libraries_javascripts, babel ), replace_javascripts ) ) ), gulp.series( gulp.parallel( copy_images_base, copy_fonts ), scss, replace_stylesheets, css_inline_images ), gulp.series( pug_templates_all, gulp.parallel( replace_templates_guides, minisearch_consolidate, sitemap ), ogimage ) ) ]; // Append production final tasks? if (CONTEXT.IS_PRODUCTION === true) { _series.push( gulp.parallel( cssmin, uglify ), build_banner, build_size ); } return gulp.series(_series); }(); /* Cleans all build files */ var build_clean = function() { return del([ (CONTEXT.PATH_BUILD_ASSETS + "/*"), (CONTEXT.PATH_BUILD_PAGES + "/*") ]); }; /* Starts connect server */ var connect_server = function(next) { gulp_connect.server( { name : "Server", root : CONTEXT.PATH_DIST, host : CONTEXT.SERVE_HOST, port : CONTEXT.SERVE_PORT, silent : true, livereload : { hostname : CONTEXT.SERVE_HOST, port : (CONTEXT.SERVE_PORT + 1), } }, function() { console.log( "\n🔥 Preview server started on: http://" + CONTEXT.SERVE_HOST + ":" + CONTEXT.SERVE_PORT + "\n" ); } ); next(); }; /* Watches for changes on resources */ var watch_resources = function(next) { CONTEXT.IS_WATCH = true; // Internal files (Chappe files) // Notice: only if not 'production', ie. in 'development' mode, as this is \ // used by Chappe developers only. if (CONTEXT.IS_PRODUCTION !== true) { gulp.watch("bower.json", bower); gulp.watch("res/config/*", get_configuration); gulp.watch("src/images/**/*", copy_images_base); gulp.watch("src/fonts/**/*", copy_fonts); gulp.watch( "src/locales/**/*", gulp.series( pug_templates_all, replace_templates_guides ) ); gulp.watch( "src/templates/**/*", gulp.series( pug_templates_all, gulp.parallel( replace_templates_guides, sitemap ) ) ); gulp.watch( "src/stylesheets/**/*", gulp.series( scss, replace_stylesheets, css_inline_images ) ); gulp.watch( "src/javascripts/**/*", gulp.series( babel, replace_javascripts ) ); } // External files (user files) CONTEXT.PATH_CONFIG.forEach(function(config_path) { gulp.watch(config_path, get_configuration); }); gulp.watch( (CONTEXT.PATH_DATA + "/guides/**/*.{jpg,jpeg,png,gif}"), copy_images_guides ); gulp.watch( (CONTEXT.PATH_DATA + "/guides/**/*.md"), gulp.series( pug_templates_guides, replace_templates_guides ) ); gulp.watch( (CONTEXT.PATH_DATA + "/references/**/*.md"), pug_templates_references ); gulp.watch( (CONTEXT.PATH_DATA + "/changes/**/*.json"), gulp.parallel( pug_templates_changes, feed_changes ) ); next(); }; /* Lints Pug templates */ var lint_pug_templates = function() { return gulp.src( CONTEXT.PATH_SOURCES + "/templates/**/*.pug" ) .pipe( gulp_pug_lint({ defaultFile : path.join( CONTEXT.PATH_CHAPPE, ".pug-lintrc" ) }) ); }; /* Lints SCSS stylesheets */ var lint_scss_stylesheets = function() { return gulp.src( CONTEXT.PATH_SOURCES + "/stylesheets/**/*.scss" ) .pipe( gulp_stylelint({ failAfterError : true, configFile : path.join( CONTEXT.PATH_CHAPPE, ".stylelintrc.yml" ), reporters : [ { formatter : "verbose", console : true } ] }) ); }; /* Lints JS scripts (with JSHint) */ var lint_js_scripts_jshint = function() { return gulp.src([ CONTEXT.PATH_SOURCES + "/javascripts/**/*.js", CONTEXT.PATH_CHAPPE + "/bin/*.js" ]) .pipe( gulp_jshint({ defaultFile : path.join( CONTEXT.PATH_CHAPPE, ".jshintrc" ) }) ) .pipe( gulp_jshint.reporter("fail") ); }; /* Lints JS scripts (with JSCS) */ var lint_js_scripts_jscs = function() { return gulp.src([ CONTEXT.PATH_SOURCES + "/javascripts/**/*.js", CONTEXT.PATH_CHAPPE + "/bin/*.js" ]) .pipe( gulp_jscs({ configPath : path.join( CONTEXT.PATH_CHAPPE, ".jscsrc" ) }) ) .pipe( gulp_jscs.reporter("fail") ); }; /* Lints project built code */ var lint = function() { return gulp.series( get_configuration, gulp.parallel( lint_pug_templates, lint_scss_stylesheets, lint_js_scripts_jshint, lint_js_scripts_jscs ) ); }(); /* Serves project */ var serve = function() { return gulp.series( get_configuration, minisearch_prepare, build_clean, build_resources, watch_resources, connect_server ); }(); /* Cleans all build files */ var clean = function() { return gulp.series( get_configuration, build_clean ) }(); /* Builds project */ var build = function() { return gulp.series( get_configuration, minisearch_prepare, build_resources ); }(); /* Watches for project changes */ var watch = function() { return gulp.series( get_configuration, minisearch_prepare, watch_resources ) }(); /* Export all public tasks */ exports.lint = lint; exports.serve = serve; exports.clean = clean; exports.watch = watch; exports.build = build; exports.default = build; ================================================ FILE: package.json ================================================ { "name": "chappe", "description": "Developer Docs builder. Write guides in Markdown and references in API Blueprint. Comes with a built-in search engine.", "version": "1.16.0", "homepage": "https://github.com/crisp-oss/chappe", "license": "MIT", "bin": { "chappe": "bin/chappe.js" }, "author": { "name": "Valerian Saliou", "email": "valerian@valeriansaliou.name", "url": "https://valeriansaliou.name/" }, "contributors": [ { "name": "Baptiste Jamin", "url": "https://jam.in/" } ], "repository": { "type": "git", "url": "git://github.com/crisp-oss/chappe.git" }, "bugs": { "url": "https://github.com/crisp-oss/chappe/issues" }, "licenses": [ { "type": "MIT", "url": "https://github.com/crisp-oss/chappe/blob/master/LICENSE" } ], "engines": { "node": ">= 20.0.0" }, "scripts": { "dev": "./bin/chappe.js serve --example=acme-docs", "build": "./bin/chappe.js build --example=acme-docs", "test": "./bin/chappe.js lint --example=acme-docs" }, "dependencies": { "@tommoor/remove-markdown": "0.3.x", "babel-preset-es2015": "6.9.x", "del": "6.0.x", "drafter.js": "3.2.x", "escape-html": "1.0.x", "feed": "4.2.x", "glob": "7.2.x", "gulp": "4.0.x", "gulp-babel": "6.1.x", "gulp-bower": "0.0.15", "gulp-clean-css": "4.3.x", "gulp-concat": "2.6.x", "gulp-connect": "5.7.x", "gulp-file": "0.4.x", "gulp-header": "2.0.x", "gulp-inline-image": "1.0.x", "gulp-jscs": "4.1.x", "gulp-jshint": "2.1.x", "gulp-noop": "1.0.x", "gulp-notify": "4.0.x", "gulp-ogimage": "2.0.x", "gulp-pug": "5.0.x", "gulp-pug-lint": "0.1.x", "gulp-rename": "2.0.x", "gulp-replace": "1.1.x", "gulp-sass": "6.0.x", "gulp-sass-variables": "1.2.x", "gulp-sitemap": "8.0.x", "gulp-sizereport": "1.2.x", "gulp-stylelint-esm": "3.0.x", "gulp-uglify": "3.0.x", "http-status-codes": "2.2.x", "jshint": "2.13.x", "lodash": "4.17.x", "markdown-toc": "1.2.x", "marked": "7.0.x", "marked-footnote": "1.4.x", "marked-gfm-heading-id": "3.2.x", "marked-mangle": "1.1.x", "merge-stream": "2.0.x", "minisearch": "3.1.x", "ora": "5.4.x", "sass": "1.89.x", "slug": "5.2.x", "stylelint-config-standard-scss": "16.0.x", "trunc-text": "1.0.x", "yargs": "4.8.x" }, "keywords": [ "docs", "documentation", "api-documentation", "documentation-tool", "docs-generator", "developer-documentation", "developer-tool", "markdown", "blueprint", "developer", "api-rest", "github-pages", "cloudflare-pages" ] } ================================================ FILE: res/config/common.json ================================================ { "LANGS" : [ "en" ], "URLS" : { "CRISP_WEB" : "https://crisp.chat/" }, "LIBRARIES" : { "JAVASCRIPTS" : [ "console/console.js", "cookies/src/cookies.js", "cash/dist/cash.js", "minisearch/dist/umd/index.js", "prism.js/prism.js" ], "STYLESHEETS" : [ "reset.css/reset.css" ] }, "SOURCES" : { "TEMPLATES" : [ "home/index.pug", "not_found/index.pug" ], "JAVASCRIPTS" : [ "common/common.js" ], "STYLESHEETS" : [ "common/common.scss", "home/home.scss", "guides/guides.scss", "references/references.scss", "changes/changes.scss", "not_found/not_found.scss" ] }, "FORMAT" : { "DESCRIPTION" : { "TRUNCATE" : 140 }, "DATES" : { "LOCALE_DATE_STRING" : { "AREA" : "en-US", "OPTIONS" : { "year" : "numeric", "month" : "long", "day" : "numeric" } } } }, "COLORS" : { "BADGES" : { "HTTP" : { "head" : "blue", "get" : "blue", "post" : "green", "put" : "yellow", "patch" : "yellow", "delete" : "red" } } }, "SEARCH" : { "INDEX" : "search/index.json", "OPTIONS" : { "BASE" : { "fields" : [ "title", "path", "summary" ], "storeFields" : [ "id", "path", "title", "summary" ] }, "QUERY" : { "searchOptions" : { "prefix" : true, "fuzzy" : 0.15, "boost" : { "title" : 6, "path" : 4, "summary" : 1 }, "weights" : { "fuzzy" : 0.5, "prefix" : 0.25 } } } } } } ================================================ FILE: res/config/user.json ================================================ { "identity" : { "title" : "", "copyright" : "" }, "theme" : { "accent" : { "light" : { "base" : "#2275f1", "active" : "#0f69ef" }, "dark" : { "base" : "#e0e7ed", "active" : "#d5dde4" } } }, "urls" : { "base" : "", "vigil" : "", "crisp_status" : "" }, "favicons" : { "main" : "", "sizes" : { "default" : "", "512x512" : "", "256x256" : "", "128x128" : "", "32x32" : "" } }, "images" : { "illustrations" : { "home" : "", "not_found" : "" }, "logos" : { "header_full" : "", "header_short" : "", "footer" : "" }, "metas" : { "opengraph" : "" }, "categories" : { "guides" : {}, "references" : {} } }, "dimensions" : { "logos" : { "header_full" : { "width" : 160 }, "header_short" : { "width" : 50 }, "footer" : { "width" : 120 } } }, "colors" : { "changes" : { "groups" : {} } }, "texts" : { "home" : { "title" : "", "label" : "" }, "changes" : { "groups" : {}, "titles" : { "feed" : "", "latest" : "", "year" : "" }, "notice" : "" } }, "links" : { "header" : { "navigation" : [], "actions" : [] }, "footer" : { "navigation" : [] } }, "actions" : { "home" : [] }, "bulletpoints" : { "home" : { "quickstart" : { "description" : "", "actions" : [] }, "guides" : { "description" : "", "actions" : [] }, "references" : { "description" : "", "actions" : [] } } }, "includes" : { "scripts" : { "urls" : [], "inline" : [] }, "stylesheets" : { "urls" : [], "inline" : [] } }, "tokens" : { "crisp_website_id" : "" }, "plugins" : { "code" : { "syntaxes" : [] } }, "features" : { "support" : true }, "rules" : { "build_size" : { "fail" : true, "sizes" : { "references" : { "pages" : { "gzip_maximum" : 100000 } }, "guides" : { "pages" : { "gzip_maximum" : 50000 }, "images" : { "maximum" : 1000000 } }, "data" : { "objects" : { "maximum" : 200000, "gzip_maximum" : 50000 } } } } }, "overrides" : { "crisp_chatbox_url" : "" } } ================================================ FILE: res/plugins/gulp/minisearch.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var remove_markdown = require("@tommoor/remove-markdown"); module.exports = { insert_categories : function( context, type, path_origin, categories, override_segments ) { var _self = this; // Define a maximum length for summaries var _summary_maximum_length = 100; categories.forEach(function(category) { // Generate target page URL var _target_segments = ( override_segments || category.segments || [] ); var _target_url = ( "/" + type + "/" + _target_segments.join("/") + ((_target_segments.length > 0) ? "/" : "") ); // Append page anchor? (if not a category with segments) if (!category.segments && category.id) { _target_url += ("#" + category.id); } // Acquire summary (based on available data) var _summary_full = ""; if (category.data) { switch (category.type) { case "API Blueprint": { if (category.data.content) { var _blueprint_first_line; // Acquire first line from content for (var _i = 0; _i < category.data.content.length; _i++) { var _entry = category.data.content[_i]; if (_entry.element === "copy" && _entry.content) { _blueprint_first_line = _entry.content; break; } } // Strip Markdown from first line? if (_blueprint_first_line) { _summary_full = remove_markdown(_blueprint_first_line); } } break; } case "Markdown": { // Strip Markdown from data _summary_full = remove_markdown(category.data); break; } } // Use title as summary? (fallback) if (!_summary_full) { _summary_full = category.title; } } else if (category.markdown) { // Strip Markdown from text _summary_full = remove_markdown(category.markdown); } // Generate short summary var _summary; if (_summary_full.length > _summary_maximum_length) { _summary = ( _summary_full.substr(0, _summary_maximum_length) + "." ); } else { _summary = _summary_full; } // Insert entry to index context.SEARCH_INDEX.add({ id : _target_url, path : path_origin.join(" > "), title : category.title, summary : (_summary || "") }); // Recurse to subtrees? if ((category.subtrees || []).length > 0) { // Generate current path origin var _parent_path_origin = [].concat(path_origin); _parent_path_origin.push(category.title); // Insert all child categories to the index _self.insert_categories( context, type, _parent_path_origin, category.subtrees, override_segments ); } }); } }; ================================================ FILE: res/plugins/gulp/pug-templates.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var fs = require("fs"); var path = require("path"); var url = require("url"); var lodash = require("lodash"); var glob = require("glob"); var slug = require("slug"); var drafter = require("drafter.js"); var toc = require("markdown-toc"); var truncate_text = require("trunc-text"); var remove_markdown = require("@tommoor/remove-markdown"); var http_status_codes = require("http-status-codes"); var gulp_notify = require("gulp-notify"); var gulp_rename = require("gulp-rename"); module.exports = { list_config_locales : function(context, package, marked) { var pug_config = { data : lodash.merge( lodash.clone(context.CONFIG), { PACKAGE : package, DATE : { YEAR : (new Date()).getUTCFullYear() }, METHODS : { marked : marked, remove_markdown : remove_markdown, truncate_text : truncate_text, slug : slug } } ) }; if (context.IS_PRODUCTION) { pug_config.pretty = false; } else { pug_config.pretty = true; } return context.CONFIG.LANGS.map(function(lang) { var pug_config_lang = lodash.cloneDeep(pug_config); // Assign locale code pug_config_lang.data.LOCALE = { CODE : lang, DIRECTION : "ltr" }; // Assign locale strings pug_config_lang.data.$_ = JSON.parse( fs.readFileSync(context.PATH_SOURCES + "/locales/" + lang + ".json") ); return { locale : lang, config : pug_config_lang }; }); }, pipe_commons : function(context, locale, fn_pipeline) { return fn_pipeline() .on("error", gulp_notify.onError({ title : "pug_templates_base", message : "Error compiling", emitError : true })) .on("error", function(error) { if (context.IS_WATCH === true) { // Handle compile errors (used for development ease w/ `gulp watch`) console.error(error.toString()); // jscs:ignore } else { throw error; } }) .pipe( gulp_rename(function(file_path) { // Append locale code to base name (only if not default locale, ie. \ // 'en') if (locale !== context.CONFIG.LANGS[0]) { file_path.basename += ("." + locale); } // Map home directory to base directory? (as 'home' must be set at \ // the root, ie. as the entry 'index.html') if (file_path.dirname === "home") { file_path.dirname = ""; } }) ); }, traverse_guides_tree : function( root_path, base_path, linear_output, parent_tree ) { var _self = this; // Apply defaults base_path = (base_path || root_path); linear_output = (linear_output || []); parent_tree = (parent_tree || null); var _tree_output = glob.sync("./index.md", { cwd : base_path }) .map(function(file_path) { // Acquire file paths (full paths from build root, and page paths) var _file_path_full = path.join(base_path, file_path), _file_path_pages = path.relative(root_path, _file_path_full); // Split file path into its final URL segments var _path_segments = ( _file_path_pages.split("/") .filter(function(file_path_segment) { // Ignore segment? if (file_path_segment === "." || file_path_segment === "index.md") { return false; } // Accept segment return true; }) ); // Read guide metas: title, order and others (and then, Markdown) var _file_data = fs.readFileSync(_file_path_full, "utf-8"), _file_lines = _file_data.split(/\r?\n/); var _guide_title = null, _guide_index = null, _guide_updated = null, _guide_links = [], _guide_markdown = ""; // Read content line-by-line var _has_scanned_metas = false; for (var _i = 0; _i < _file_lines.length; _i++) { // Match on meta line? (if has not already scanned all metas) if (_has_scanned_metas !== true) { var _match_meta_line = _file_lines[_i].match(/^([A-Z]+):(.+)$/); if (_match_meta_line) { var _match_meta_value = ( (_match_meta_line[2] || "").trim() || null ); switch (_match_meta_line[1]) { case "TITLE": { _guide_title = _match_meta_value; break; } case "INDEX": { if (!isNaN(_match_meta_value)) { _guide_index = parseInt(_match_meta_value, 10); } break; } case "UPDATED": { _guide_updated = (new Date(_match_meta_value)); break; } case "LINK": { var _link_parts = _match_meta_value.split("->"), _link_name = (_link_parts[0] || "").trim(), _link_url = (_link_parts[1] || "").trim(); if (!_link_name || !_link_url || _link_parts.length !== 2) { throw new Error( "Guide link value is invalid: " + _match_meta_value ); } _guide_links.push({ name : _link_name, url : _link_url }); break; } default: { throw new Error( "Guide meta-data value is unsupported: " + _match_meta_line[1] ); } } continue; } // Non-meta line found, consider as having scanned them all. _has_scanned_metas = true; } // Handle Markdown line _guide_markdown += _file_lines[_i]; _guide_markdown += "\n"; } // Perform a final trim of the parsed Markdown (as extra end-lines may \ // be held) _guide_markdown = _guide_markdown.trim(); // Validate acquired values if (!_guide_title || !_guide_updated || isNaN(_guide_updated.getTime()) || isNaN(_guide_index) || _guide_index < 1) { throw new Error("Guide meta-data is invalid at: " + _file_path_full); } // Generate tree entry var _entry = { segments : _path_segments, id : _path_segments.join("_"), title : _guide_title, index : _guide_index, links : _guide_links, updated : _guide_updated, markdown : _guide_markdown, parent : parent_tree, subtrees : [] }; // List sub-trees (traverse tree recursively) var _sub_tree_parts = ( glob.sync("./*/", { cwd : base_path }) .map(function(directory_path) { var _sub_tree_data = _self.traverse_guides_tree( root_path, path.join(base_path, directory_path), linear_output, _entry ); // Take tree output, drop the linear output aggregator return _sub_tree_data[0]; }) .filter(function(sub_tree) { // Filter-out empty sub-trees return (sub_tree.length > 0 ? true : false); }) ); // Reduce sub-trees into a single array _sub_tree_parts.forEach(function(sub_tree_part) { _entry.subtrees = _entry.subtrees.concat(sub_tree_part); }); // Sort sub-trees by lower index first _entry.subtrees = _entry.subtrees.sort(function(previous, next) { return (previous.index - next.index); }); // Append this entry to the linear output (w/ the same object reference \ // as the tree-based output) linear_output.push(_entry); return _entry; }) .sort(function(previous, next) { // Sort main-tree by lower index first return (previous.index - next.index); }); return [_tree_output, linear_output]; }, traverse_references_linear : function(context, root_path) { var _self = this; return glob.sync("./**/*.md", { cwd : root_path }) .map(function(file_path) { // Acquire file paths (full paths from build root, and page paths) var _file_path_full = path.join(root_path, file_path); // Split file path into its final URL segments var _path_segments = ( file_path.split("/") .filter(function(file_path_segment) { // Ignore segment? if (file_path_segment === ".") { return false; } // Accept segment return true; }) .map(function(file_path_segment) { // Remove all file extensions in all segments return file_path_segment.split(".")[0]; }) ); // Read reference metas: type and others (and then, content) var _file_data = fs.readFileSync(_file_path_full, "utf-8"), _file_lines = _file_data.split(/\r?\n/); var _reference_type = null, _reference_title = null, _reference_updated = null, _reference_content = ""; // Read content line-by-line var _has_scanned_metas = false; for (var _i = 0; _i < _file_lines.length; _i++) { // Match on meta line? (if has not already scanned all metas) if (_has_scanned_metas !== true) { // Only scan on certain meta keys, as some keys may be used for \ // eg. API Blueprint. var _match_meta_line = ( _file_lines[_i].match(/^(TYPE|TITLE|UPDATED):(.+)$/) ); if (_match_meta_line) { var _match_meta_value = ( (_match_meta_line[2] || "").trim() || null ); switch (_match_meta_line[1]) { case "TYPE": { _reference_type = _match_meta_value; break; } case "TITLE": { _reference_title = _match_meta_value; break; } case "UPDATED": { _reference_updated = (new Date(_match_meta_value)); break; } default: { throw new Error( "Reference meta-data value is unsupported: " + _match_meta_line[1] ); } } continue; } // Non-meta line found, consider as having scanned them all. _has_scanned_metas = true; } // Handle content line _reference_content += _file_lines[_i]; _reference_content += "\n"; } // Perform a final trim of the parsed content (as extra end-lines may \ // be held) _reference_content = _reference_content.trim(); // Validate acquired values if (!_reference_type || !_reference_title || !_reference_updated || isNaN(_reference_updated.getTime())) { throw new Error( "Reference meta-data is invalid at: " + _file_path_full ); } // Return entry data return [ { segments : _path_segments, id : _path_segments.join("_"), type : _reference_type, title : _reference_title, updated : _reference_updated, }, _reference_content ]; }) .map(function(entry_items) { var _entry = entry_items[0]; switch (_entry.type) { case "API Blueprint": { var _parse_result = drafter.parseSync(entry_items[1], { requireBlueprintName : true, generateSourceMap : false, generateMessageBody : false, generateMessageBodySchema : false }); // Blueprint has error? (likely invalid) if (!_parse_result || _parse_result.content.length === 0 || _parse_result.content[0].element !== "category") { throw new Error( "Reference API Blueprint could not be parsed for: " + _entry.segments.join("/") ); } // Blueprint has warnings? (we want to be strict there and abort \ // build, as ignored warnings can result in unseen issues in the \ // final built reference document) // Notice: render errors in a readable format. var _warns_count = (_parse_result.content.length - 1); if (_warns_count > 0) { var _warns_limit = 50; throw new Error( "Reference API Blueprint has warnings for: " + _entry.segments.join("/") + "\n\n" + "Warnings to be resolved:" + "\n" + ( _parse_result.content.splice(1, _warns_limit) .map(function(warning) { var _text = ( (typeof warning.content === "string") ? (warning.content || "[no text]") : "[wrong type]" ); return (" |- (" + warning.element + ") -> " + _text); }) .join("\n") ) + ( (_warns_count <= _warns_limit) ? "" : ( "\n" + ( " \\+ (" + (_warns_count - _warns_limit) + " more)" ) ) ) ); } // Acquire final entry data var _data = _parse_result.content[0]; // Find API host URL from parent attributes var _host_url = null; (_data.attributes.metadata.content || []).forEach( function(metadata) { if (!_host_url && metadata.element === "member" && metadata.content && metadata.content.key.content === "HOST") { _host_url = metadata.content.value.content; } } ); // Still did not find host URL? This is unexpected. if (!_host_url) { throw new Error( "Reference API Blueprint HOST value could not be found" ); } // Assign parsed Blueprint result tree (first entry contains the \ // whole result tree) _entry.data = _data; _entry.baseline = ( _self.map_references_blueprint_baseline(_host_url) ); _entry.categories = ( _self.map_references_blueprint_categories(context, _data.content) ); _entry.examples = ( _self.map_references_blueprint_examples(_data.content) ); break; } case "Markdown": { // Acquire final entry data var _data = entry_items[1]; // Assign parsed Markdown data _entry.data = _data; _entry.categories = ( _self.map_references_markdown_categories(_data) ); break; } default: { throw new Error( "Reference type: " + _entry.type + " is not recognized on: " + _entry.segments.join("/") ); } } return _entry; }); }, map_references_blueprint_baseline : function(host_url) { var _baseline = {}; // Map host URL parts _baseline.host = { url : host_url, path : (url.parse(host_url).pathname || "/") }; return _baseline; }, map_references_blueprint_categories : function(context, entries, parent_id) { var _self = this; var _categories = []; entries.forEach(function(entry) { // Match on category groups only if (entry.element === "category" || entry.element === "resource" || entry.element === "transition") { // Acquire HTTP method associated to 'transition' element (ie. request) var _badge = null; if (entry.element === "transition") { var _http_method = ( _self.find_references_blueprint_http_method(entry) ); if (_http_method) { // Acquire badge color for HTTP method var _badge_color = ( context.CONFIG.COLORS.BADGES.HTTP[_http_method] || null ); // Create final badge object _badge = [_http_method, _badge_color]; } } // Generate category identifier (prepend parent identifier, if any) var _id = slug(entry.meta.title.content); if (parent_id) { _id = (parent_id + "-" + _id); } // Push current category to the level of categories being scanned _categories.push({ id : _id, title : entry.meta.title.content, badge : _badge, subtrees : ( _self.map_references_blueprint_categories( context, entry.content, ((entry.element === "category") ? _id : null) //-[parent_id] ) ) }); } }); return _categories; }, map_references_blueprint_examples : function(entries, parent_map) { var _self = this; parent_map = (parent_map || {}); entries.forEach(function(entry) { switch (entry.element) { case "category": case "resource": { // Recurse on children (parent groups of transition group) _self.map_references_blueprint_examples( entry.content, parent_map ); break; } case "transition": { // Acquire HTTP method var _http_method = ( _self.find_references_blueprint_http_method(entry) ); // Acquire restrictions var _restrictions = ( _self.find_references_blueprint_restrictions(entry) ); // Parse URL parts (if any) var _url_parts = ( _self.find_references_blueprint_url_parts(entry) ); // Parse available request-response flows var _flows = ( _self.find_references_blueprint_flows(entry) ); // Validate acquired data if (!_http_method || Object.keys(_flows).length === 0) { throw new Error( "Reference API Blueprint transition entry has no example request" ); } // Generate example data var _example_data = { method : _http_method, url : { parts : _url_parts }, tiers : (_restrictions.tiers || []), scopes : (_restrictions.scopes || []), flows : _flows }; // Assign example data to parent map var _id = slug(entry.meta.title.content); parent_map[_id] = _example_data; break; } } }); return parent_map; }, map_references_markdown_categories : function(data) { var _self = this; // Generate navigation menu from Markdown var _navigation = toc(data, { maxdepth : 3, firsth1 : true }); // Generate navigation tree (based on indent levels) return ( _self.transduce_references_markdown_categories_tree(_navigation.json) ); }, transduce_references_markdown_categories_tree : function(items, level) { var _self = this; level = (level || 1); // Append entries to tree var _tree = [], _current_entry = null, _children_stack = []; for (var _i = 0; _i < (items.length + 1); _i++) { var _item = (items[_i] || null); // Scanning is finished as we have overflown? Or are we still scanning? if (_item === null || _item.lvl === level) { // Push last current entry before, if any if (_current_entry !== null) { // Transduce eventual children from the stack _current_entry.subtrees = ( _self.transduce_references_markdown_categories_tree( _children_stack, (level + 1) ) ); // Append current entry to tree (with its eventual children) _tree.push(_current_entry); // Reset children stack back to empty state _children_stack = []; } // Start a new current entry? if (_item !== null) { _current_entry = { id : slug(_item.content), title : _item.content, subtrees : [] }; } } else { // Append raw child entry to children stack _children_stack.push(_item); } } return _tree; }, find_references_blueprint_http_method : function(transition) { var _http_method = null; transition.content.forEach(function(entry) { if (_http_method === null && entry.element === "httpTransaction") { entry.content.forEach(function(transaction) { if (_http_method === null && transaction.element === "httpRequest") { if (transaction.attributes && transaction.attributes.method) { // Read HTTP method _http_method = ( transaction.attributes.method.content.toLowerCase() ); } } }); } }); return _http_method; }, find_references_blueprint_restrictions : function(transition) { var _restrictions = null; // Iterate on each 'copy' text line found transition.content.forEach(function(entry) { if (_restrictions === null && entry.element === "httpTransaction") { entry.content.forEach(function(transaction) { if (_restrictions === null && transaction.element === "httpRequest") { transaction.content.forEach(function(sub_entry) { if (sub_entry.element === "copy") { sub_entry.content.split("\n").forEach(function(line) { var _match = line.trim().match(/^\+ (Tiers|Scopes):(.+)$/); if (_match && _match[1] && _match[2]) { var _key = _match[1].toLowerCase(); // Initialize restrictions map or key array (if needed) _restrictions = (_restrictions || {}); _restrictions[_key] = (_restrictions[_key] || []); // Iterate on parts sub-matches var _part_regex = /(?:^| )`([^`]+)`/g, _part_match; while (_part_match = _part_regex.exec(_match[2])) { // Assign found restriction type and values _restrictions[_key].push( _part_match[1].trim() ); } } }); } }); } }); } }); return (_restrictions || {}); }, find_references_blueprint_url_parts : function(transition) { // Notice: this is a simple and (relatively) hacky way to parse URL \ // parts into tokens, w/o using more complex yet complete ways \ // eg. backtracking. This does the job in our use-case. var _url_parts = []; if (transition.attributes && transition.attributes.href) { var _href = (transition.attributes.href.content || ""), _current_part = null; for (var _i = 0; _i < _href.length; _i++) { var _slice = _href[_i]; // Initialize current part? var _was_initiated = false; if (_current_part === null) { _current_part = { type : ((_slice === "{") ? "parameter" : "segment"), value : "" }; _was_initiated = true; } // Push current character? (ignore '{' and '}' enclosure characters) if (_slice !== "{" && _slice !== "}") { _current_part.value += _slice; } // Push current part? (closure or opener character detected, or \ // end-of-string, or next character opens a different enclosed \ // type) if (_current_part !== null && ((_i === (_href.length - 1)) || ((_slice === "{" || _slice === "}" || _slice === "/") && (_was_initiated !== true || _href[_i + 1] === "{")))) { var _first_slice = _current_part.value[0]; // Ignore argument parameters if (_first_slice !== "?" && _first_slice !== "&") { _url_parts.push(_current_part); } _current_part = null; } } } return _url_parts; }, find_references_blueprint_flows : function(transition) { var _self = this; var _flows_map = {}; transition.content.forEach(function(entry) { if (entry.element === "httpTransaction") { // Map entry contents as a direct-access map var _entry_contents = {}; entry.content.forEach(function(sub_entry) { if (sub_entry.element.startsWith("http") === true) { _entry_contents[sub_entry.element] = sub_entry; } }); if (_entry_contents.httpRequest && _entry_contents.httpResponse) { var _request_name = ( ((_entry_contents.httpRequest.meta || {}).title || {}).content || "?" ); var _request_id = slug(_request_name); // Assign first flow request data? (if not already set) if (!_flows_map[_request_id]) { var _request_params = ( _self.parse_references_blueprint_flow_direction( _request_name, _entry_contents.httpRequest ) ); // Assign new flow data _flows_map[_request_id] = { request : _request_params, responses : [] }; } // Assign current flow response data var _http_status_name = ( _entry_contents.httpResponse.attributes.statusCode.content ); // Attempt to resolve reason text from status code? if (!isNaN(_http_status_name)) { // Notice: 'http_status_codes.getReasonPhrase()' throws if it \ // cannot find the reason for a status code, therefore we need \ // to wrap this in a try/catch block. try { var _http_status_reason = http_status_codes.getReasonPhrase( parseInt(_http_status_name, 10) ); if (_http_status_reason) { _http_status_name += (" " + _http_status_reason); } } catch (error) { // Ignore error (this will skip appending the reason to the \ // status name) } } var _response_params = ( _self.parse_references_blueprint_flow_direction( _http_status_name, _entry_contents.httpResponse ) ); _flows_map[_request_id].responses.push(_response_params); } } }); return _flows_map; }, parse_references_blueprint_flow_direction : function(name, entry) { // Look for direction content type and data var _direction_type = null, _direction_data = null; entry.content.forEach(function(direction_entry) { if (!_direction_type && direction_entry.element === "asset" && (direction_entry.meta.classes.content[0].content === "messageBody")) { // Read type // Notice: if type is a MIME? Extract last chunk _direction_type = direction_entry.attributes.contentType.content; if (_direction_type.includes("/") === true) { var _type_chunks = _direction_type.split("/"); _direction_type = ( (_type_chunks[_type_chunks.length - 1] || "").toUpperCase() ); } // Read data _direction_data = (direction_entry.content || "").trim(); // Collapse tabulations from 4 spaces to 2 spaces (this improves \ // legibility) _direction_data = _direction_data.replace(/^( +)\1/gm, "$1"); } }); return { name : name, type : (_direction_type || null), data : (_direction_data || null) }; } }; ================================================ FILE: res/plugins/marked/extensions/embed.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var _s = require("escape-html"); // Format: `${type}[title](target)` module.exports = { name : "embed", level : "inline", start : function(source) { var _match = source.match( /^\${(?:[^{}]+)}\[(?:[^\[\]]*)\]\((?:[^\(\)]+)\)/ ); if (_match) { return _match.index; } }, tokenizer : function(source) { var _match = /^\${([^{}]+)}\[([^\[\]]*)\]\(([^\(\)]+)\)$/.exec(source); if (_match) { return { type : "embed", raw : _match[0], injector : _match[1], title : _match[2], target : _match[3] }; } }, renderer : function(token) { // Generate preview code var _preview_url; switch (token.injector) { case "youtube": { // Generate YouTube preview image URL _preview_url = ( "https://img.youtube.com/vi/" + token.target + "/maxresdefault.jpg" ); break; } default: { throw new Error( "Unsupported embed injector: " + token.injector ); } } // Generate caption code var _caption_code = ( !token.title ? "" : ( "
" + _s(token.title) + "
" ) ); // Generate final embed code return ( "
" + "
" + "
" + "
" + "
" + "
" + _caption_code + "
" ); } }; ================================================ FILE: res/plugins/marked/extensions/emphasis.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ // Format: `!!! text`, `!! text` or `! text` module.exports = { name : "emphasis", level : "block", start : function(source) { var _match = source.match(/^(?:[!]{1,3})(?:[ ]{1,})(?:[^\n]*)/); if (_match) { return _match.index; } }, tokenizer : function(source) { var _match = /^([!]{1,3})(?:[ ]{1,})([^\n]+)/.exec(source); if (_match) { // Parse level var _level; switch (_match[1].trim()) { case "!!!": { _level = "warning"; break; } case "!!": { _level = "info"; break; } default: { _level = "notice"; } } return { type : "emphasis", raw : _match[0], level : _level, text : this.lexer.inlineTokens(_match[2].trim()) }; } }, renderer : function(token) { return ( "
" + this.parser.parseInline(token.text) + "
" ); } }; ================================================ FILE: res/plugins/marked/extensions/figcaption.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var _s = require("escape-html"); // Format: `$[caption]()` module.exports = { name : "figcaption", level : "block", start : function(source) { var _match = source.match(/^(?:\$\[[^\[\]\n]+\]\([^\n]+)\)/); if (_match) { return _match.index; } }, tokenizer : function(source) { var _match = /^\$\[([^\[\]\n]+)\]\(([^\n]+)\)/.exec(source); if (_match) { return { type : "figcaption", raw : _match[0], caption : _match[1], code : this.lexer.inlineTokens(_match[2].trim()) }; } }, renderer : function(token) { return ( "
" + this.parser.parseInline(token.code) + "
" + _s(token.caption) + "
" + "
" ); } }; ================================================ FILE: res/plugins/marked/extensions/navigation-item.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var _s = require("escape-html"); // Format: `* Title: Description -> URL` for child var RULE = ( /^(?:[ ]*\|[ ]?([^:\n]+):[ ]?([^:\n]+)[ ]?->[ ]([^>\n\[\]]+)(?: \[(blank|self)\])?(?:\n|$))/ ); module.exports = { name : "navigation-item", level : "inline", start : function(source) { var _match = source.match(RULE); if (_match) { return _match.index; } }, tokenizer : function(source) { var _match = RULE.exec(source); if (_match) { return { type : "navigation-item", raw : _match[0], title : _match[1].trim(), description : _match[2].trim(), url : _match[3].trim(), target : (_match[4] || "self").trim() }; } }, renderer : function(token) { return ( "
  • " + "" + "" + "" + _s(token.title) + "" + "" + _s(token.description) + "" + "" + "Read" + "" + "
  • " ); } }; ================================================ FILE: res/plugins/marked/extensions/navigation.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ // Format: `+ Navigation` for parent var RULE = ( /^(?:\+ Navigation[ ]*\n{1,2})((?:(?:[ ]*\|[ ]?(?:[^:\n]+):[ ]?(?:[^:\n]+)[ ]?->[ ](?:[^>\n]+))(?:\n|$))+)/ ); module.exports = { name : "navigation", level : "block", start : function(source) { var _match = source.match(RULE); if (_match) { return _match.index; } }, tokenizer : function(source) { var _match = RULE.exec(source); if (_match) { var _token = { type : "navigation", raw : _match[0], text : _match[1], tokens : [] }; this.lexer.inline( _token.text, _token.tokens ); return _token; } }, renderer : function(token) { return ( "
      " + this.parser.parseInline(token.tokens) + "
    " ); } }; ================================================ FILE: res/plugins/marked/renderers/code.js ================================================ /* * chappe * * Copyright 2022, Crisp IM SAS * Author: Valerian Saliou */ var _s = require("escape-html"); module.exports = function(code, infostring, escaped) { // Acquire language name and its associated class (if any) var _language = (infostring || "").trim(); var _class = ( _language ? (this.options.langPrefix + _s(_language)) : null ); return ( "" + "" + "" + (escaped ? code : _s(code)) + "" + "" + "\n" ); }; ================================================ FILE: res/plugins/marked/renderers/heading.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ var slug = require("slug"); module.exports = function(text, level) { var _id = slug(text); return ( "" + "" + text + "" + "" ); }; ================================================ FILE: src/javascripts/common/common.js ================================================ /* * chappe * * Copyright 2021, Crisp IM SAS * Author: Valerian Saliou */ /** * Common * @class * @classdesc Common class. */ class Common { /** * Constructor */ constructor() { let fn = "constructor"; try { this.ns = "Common"; this._$ = $; // Selectors this._document_sel = this._$(document); // Configuration this.__revision = "@:revision"; this.__url_status = "@:url_status"; this.__search_index_path = "@:search_index"; this.__search_index_options_base = "@:search_options_base"; this.__search_index_options_query = "@:search_options_query"; this.__second_in_milliseconds = 1000; // 1 second this.__copy_state_confirm_delay = 2000; // 2 seconds this.__content_anchor_viewed_delay = 100; // 1/10 second this.__status_poll_interval = 5000; // 5 seconds this.__status_poll_refresh = 90000; // 90 seconds this.__chatbox_z_index = 120; this.__search_results_limit = 12; this.__cookie_prefix = "chappe/"; this.__status_known_health = [ "healthy", "sick", "dead" ]; // Instances this.__escape_html_text_rules = { "&" : /&/g, "<" : //g, """ : /"/g }; // Storage this.__crisp_chat_feedback_shown = false; this.__search_opened = false; this.__appearance_mode = "light"; this.__content_anchor_viewed_timeout = null; this.__status_poll_scheduler = null; this.__search_index_responder = null; this.__search_index_load_pending = null; this.__search_field_value_last = ""; this.__status_poll_health = null; this.__status_poll_last_check = 0; } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Initializes the class * @public * @return {undefined} */ init() { let fn = "init"; try { this.__chatbox(); this.__options(); this.__events(); this.__schedules(); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Configures the chatbox * @private * @return {undefined} */ __chatbox() { let fn = "__chatbox"; try { // Adjust chatbox z-index? (if chatbox is included) if (typeof window.$crisp !== "undefined") { window.$crisp.push([ "config", "container:index", this.__chatbox_z_index ]); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Setups all user options * @private * @return {undefined} */ __options() { let fn = "__options"; try { // Restore appearance options this.__toggle_appearance( this.__detect_appearance_preference() //-[mode] ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds all events * @private * @return {undefined} */ __events() { let fn = "__events"; try { // Bind common events this.__bind_copy_click(); // Bind search events this.__bind_search_open_keydown(); this.__bind_search_open_click(); this.__bind_search_close_click(); this.__bind_search_field_keyup(); // Bind appearance events this.__bind_appearance_toggle_click(); // Bind sidebar events this.__bind_sidebar_toggler_click(); this.__bind_sidebar_nest_level_toggle_click(); // Bind content events this.__bind_content_anchor_viewed(); // Bind code events this.__bind_code_metas_picker_change(); this.__bind_code_block_viewed(); // Bind Markdown events this.__bind_markdown_embed_click(); // Bind chatbox events this.__bind_crisp_chat_open_click(); this.__bind_crisp_chat_feedback_click(); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds all schedules * @private * @return {undefined} */ __schedules() { let fn = "__schedules"; try { // Bind all schedules this.__bind_status_poll_schedule(); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Reads cookie * @private * @param {string} cookie_key * @return {string} Cookie value (if any) */ __read_cookie(cookie_key) { let fn = "__read_cookie"; let _cookie_value; try { _cookie_value = Cookies.get( (this.__cookie_prefix + cookie_key) ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } finally { return _cookie_value; } } /** * Writes cookie * @private * @param {string} cookie_key * @param {string} cookie_value * @return {undefined} */ __write_cookie(cookie_key, cookie_value) { let fn = "__write_cookie"; try { Cookies.set( (this.__cookie_prefix + cookie_key), cookie_value, { domain : location.hostname, expires : Infinity, sameSite : "strict" } ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Copies text (from element) * @private * @param {object} text_el * @return {undefined} */ __copy_text(text_el) { let fn = "__copy_text"; let _was_copied = false; try { // Select text (clear out existing selections first) let _range = document.createRange(); _range.selectNode(text_el); window.getSelection().removeAllRanges(); window.getSelection().addRange(_range); // Copy text document.execCommand("copy"); window.getSelection().removeAllRanges(); _was_copied = true; } catch (error) { Console.error(`${this.ns}.${fn}`, error); } finally { return _was_copied; } } /** * Escapes text (to be included in HTML) * @private * @param {string} text_raw * @return {string} Escaped text */ __escape_html_text(text_raw) { let fn = "__escape_html_text"; let _text_safe = ""; try { let _text_buffer = text_raw; // Apply all escape rules to text for (let _value in this.__escape_html_text_rules) { let _rule = this.__escape_html_text_rules[_value]; _text_buffer = _text_buffer.replace(_rule, _value); } _text_safe = _text_buffer; } catch (error) { Console.error(`${this.ns}.${fn}`, error); } finally { return _text_safe; } } /** * Opens up search * @private * @return {undefined} */ __open_search() { let fn = "__open_search"; try { if (this.__search_opened !== true) { this.__search_opened = true; // Open search let _search_sel = this._$("#search"), _input_sel = _search_sel.find(".spotlight-input"); _search_sel.css("display", "block"); // Focus on search input? if (_input_sel.length > 0) { _input_sel[0].focus(); } // Ensure that search index is loaded? (if not already loaded, or not \ // already loading) this.__ensure_load_search_index(_search_sel); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Closes search * @private * @param {boolean} [do_reset] * @return {undefined} */ __close_search(do_reset=false) { let fn = "__close_search"; try { if (this.__search_opened === true) { this.__search_opened = false; // Close search let _search_sel = this._$("#search"); _search_sel.css("display", "none"); // Reset search value and results? if (do_reset === true) { let _spotlight_sel = _search_sel.find(".spotlight"), _input_sel = _spotlight_sel.find(".spotlight-input"), _entries_sel = _spotlight_sel.find(".spotlight-entries"); // Reset input value this.__search_field_value_last = ""; _input_sel.val(""); // Reset search results _entries_sel.empty(); _spotlight_sel.attr("data-has-results", "false"); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Toggles selected search result * @private * @param {object} results_all_sel * @param {number} [increment] * @return {undefined} */ __toggle_search_result_selected(results_all_sel, increment=1) { let fn = "__toggle_search_result_selected"; try { // Any result? if (results_all_sel.length > 0) { // Find the index of the active result let _index = -1; for (let _i = 0; _i < results_all_sel.length; _i++) { if (results_all_sel[_i].getAttribute("data-selected") === "true") { _index = _i; break; } } // Process the index of the next element to activate _index += increment; if (_index < 0) { _index = (results_all_sel.length - 1); } else if (_index >= results_all_sel.length) { _index = 0; } // Select target element? if (_index > -1) { let _result_selected_el = results_all_sel[_index]; // Set selected state to selected element results_all_sel.removeAttr("data-selected"); this._$(_result_selected_el).attr("data-selected", "true"); // Scroll to selected element _result_selected_el.scrollIntoView({ behavior : "smooth", block : "nearest" }); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Proceeds search query * @private * @param {object} spotlight_sel * @param {object} results_sel * @param {object} entries_sel * @param {string} [search_query] * @return {undefined} */ __proceed_search_query( spotlight_sel, results_sel, entries_sel, search_query="" ) { let fn = "__proceed_search_query"; try { // Initialize 'has results' state let _has_results = false; // Acquire search results if (search_query) { // Responder is not available? Throw an error, as this should \ // never happen. if (this.__search_index_responder === null) { throw new Error( "Search index responder is not available (yet?)" ); } // Obtain search results from index responder let _search_results = ( this.__search_index_responder.search(search_query) ); if (_search_results.length > 0) { _has_results = true; // Limit results to N first results if (_search_results.length > this.__search_results_limit) { _search_results = _search_results.slice( 0, this.__search_results_limit ); } // Inject search results this.__inject_search_results( results_sel, entries_sel, _search_results ); } } // Mark as having results (or not) spotlight_sel.attr( "data-has-results", ((_has_results === true) ? "true" : "false") ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Ensures that search index is loaded * @private * @param {object} search_sel * @return {undefined} */ __ensure_load_search_index(search_sel) { let fn = "__ensure_load_search_index"; try { // Not already loaded, and load not already pending? if (this.__search_index_responder === null && this.__search_index_load_pending === null) { // Select spotlight element let _spotlight_sel = search_sel.find(".spotlight"); // Mark as pending (initialize pending operations stack) this.__search_index_load_pending = []; // Reset any previously-set error state _spotlight_sel.removeAttr("data-index-error"); // Load index data from network fetch( `/static/data/${this.__search_index_path}?${this.__revision}`, { mode : "same-origin" } ) .then((response) => { // Non-success response? if (response.status >= 400) { return Promise.reject(null); } // Examine the text in the response return response.text(); }) .then((index_data) => { // Build search index responder (this parses from text JSON) this.__search_index_responder = MiniSearch.loadJSON( index_data, Object.assign( {}, this.__search_index_options_base, this.__search_index_options_query ) ); // Handle all pending operations while (this.__search_index_load_pending.length > 0) { this.__search_index_load_pending.shift()(); } return Promise.resolve(); }) .catch(() => { // Show index loading error _spotlight_sel.attr("data-index-error", "true"); // Pass-through (ignore for this session) return Promise.resolve(); }) .then(() => { // Hide load spinner (as all deferred actions have been fired, \ // if index was successfully loaded) _spotlight_sel.removeAttr("data-index-loading"); // Mark as not pending anymore this.__search_index_load_pending = null; }); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Toggles sidebar nest level * @private * @param {object} target_sel * @param {boolean} [is_anchor] * @return {undefined} */ __toggle_sidebar_nest_level(target_sel, is_anchor=false) { let fn = "__toggle_sidebar_nest_level"; try { let _parent_sel = ( target_sel.parents(".nest-navigate-level") ); if (_parent_sel) { let _new_state = ( (_parent_sel.attr("data-expanded") === "true") ? "false" : "true" ); // Unexpand all other expanded levels first? (only if new state is \ // expanded) // Notice #1: we do not want to allow multiple levels to be expanded \ // at the same time, as this can consume quite a large amount of \ // browser height and clutter the UI. // Notice #2: only when click originates from a viewed anchor. if (_new_state === "true" && is_anchor === true) { let _expanded_all_sel = this._$( "#content .sidebar .nest .nest-navigate-level" + "[data-expanded=\"true\"]" ); _expanded_all_sel.attr("data-expanded", "false"); } // Toggle expanded state on selected level _parent_sel.attr("data-expanded", _new_state); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Toggles appearance mode * @private * @param {string} [new_mode] * @return {undefined} */ __toggle_appearance(new_mode=null) { let fn = "__toggle_appearance"; try { if (new_mode !== null && this.__appearance_mode !== new_mode) { // Store new appearance mode (now current mode) this.__appearance_mode = new_mode; // Update current appearance mode (in toggle) this._$("#header .coloring").attr("data-mode", new_mode); // Update dark mode in document document.body.setAttribute("data-appearance", new_mode); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Detects appearance preference * @private * @return {string} Detected appearance preference */ __detect_appearance_preference() { let fn = "__detect_appearance_preference"; let _mode = null; try { // Attempt to detect mode from cookies? (user override) let _mode_cookies = ( this.__read_cookie("appearance-mode") || null ); if (_mode_cookies !== null) { _mode = _mode_cookies; // Abort detection there. return; } // Attempt to detect mode from media query? (operating system) if (typeof window.matchMedia === "function" && (window.matchMedia("(prefers-color-scheme: dark)").matches === true)) { _mode = "dark"; // Abort detection there. return; } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } finally { return (_mode || "light"); } } /** * Refreshes code metas picker section * @private * @param {string} direction * @param {object} code_box_sel * @param {object} section_sel * @param {string} selected_value * @param {object} code_chunk * @return {undefined} */ __refresh_code_metas_picker_section( direction, code_box_sel, section_sel, selected_value, code_chunk ) { let fn = "__refresh_code_metas_picker_section"; try { if (section_sel.length > 0 && selected_value) { // Acquire selectors let _type_sel = section_sel.find(".code-meta-type"), _content_sel = section_sel.find(".code-content"); if (_type_sel.length > 0 && _content_sel.length > 0) { // Update code type _type_sel.text(code_chunk.type); // Inject code block? if (code_chunk.data) { // Generate the list of classes on code element let _class_list = []; if (code_chunk.type) { _class_list.push(`language-${code_chunk.type.toLowerCase()}`); } if (direction === "request") { _class_list.push("copy-value"); } let _code_class_spaced = ( (_class_list.length === 0) ? "" : ` class="${_class_list.join(" ")}"` ); _content_sel.html( `
    ` ); // Select newly-injected code block let _code_block_sel = _content_sel.find("code"); if (_code_block_sel.length > 0) { // Update code content _code_block_sel.text(code_chunk.data); // Re-apply code coloring _code_block_sel[0].setAttribute("data-viewed", ""); window.Prism.highlightElement(_code_block_sel[0]); } } else { // Replace w/ empty placeholder _content_sel.html("—"); } // Update 'has request' marker? (only if direction is 'request') if (direction === "request") { code_box_sel.attr( "data-has-request", (code_chunk.data ? "true" : "false") ); } } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Schedules the handling of content anchor viewed event * @private * @param {object} link_all_sel * @param {object} sidebar_left_sel * @param {string} anchor_id * @param {number} [header_height] * @return {undefined} */ __schedule_handle_content_anchor_viewed( link_all_sel, sidebar_left_sel, anchor_id, header_height=0 ) { let fn = "__schedule_handle_content_anchor_viewed"; try { // Cancel last scheduled update? if (this.__content_anchor_viewed_timeout !== null) { clearTimeout(this.__content_anchor_viewed_timeout); } // Schedule next update (defer) this.__content_anchor_viewed_timeout = setTimeout(() => { this.__content_anchor_viewed_timeout = null; // Trigger content anchor viewed event handler this.__handle_content_anchor_viewed( link_all_sel, sidebar_left_sel, anchor_id, header_height ); }, this.__content_anchor_viewed_delay); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Handles content anchor viewed event * @private * @param {object} link_all_sel * @param {object} sidebar_left_sel * @param {string} anchor_id * @param {number} [header_height] * @return {undefined} */ __handle_content_anchor_viewed( link_all_sel, sidebar_left_sel, anchor_id, header_height=0 ) { let fn = "__handle_content_anchor_viewed"; try { let _anchor_active_el = ( (link_all_sel.filter(`[data-anchor="#${anchor_id}"]`) || [])[0] || null ); if (_anchor_active_el !== null) { let _anchor_active_sel = this._$(_anchor_active_el); // Swap active attributes to active anchor link link_all_sel.removeAttr("data-active"); _anchor_active_sel.attr("data-active", "true"); // If active link belongs to a slice parent, then auto-expand this \ // slice let _level_active_sel = ( _anchor_active_sel.parents(".nest-navigate-level--first").first() ); if (_level_active_sel.length > 0 && _level_active_sel.attr("data-expanded") !== "true") { // Trigger a virtual click event to toggle visibility let _toggle_active_sel = _level_active_sel.find( ".nest-navigate-link--slice .nest-navigate-toggle" ); if (_toggle_active_sel.length > 0) { // Toggle sidebar nest level this.__toggle_sidebar_nest_level( _toggle_active_sel, true //-[is_anchor] ); } } // Ensure that active link is into view (otherwise, scroll to link) // Important: do not use 'smooth' scrolling, as we need the scroll \ // position change to be instant. _anchor_active_el.scrollIntoView({ behavior : "auto", block : "nearest" }); // Element is hidden below header? Scroll a bit up once more (note \ // that this only applies when scrolling upwards) let _bounding_box = _anchor_active_el.getBoundingClientRect(), _bounding_top = Math.floor(_bounding_box.top || 0.0); if (_bounding_top < header_height) { sidebar_left_sel[0].scrollTop -= (header_height - _bounding_top); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Handles code metas picker change event * @private * @param {object} code_box_sel * @return {undefined} */ __handle_code_metas_picker_change(code_box_sel) { let fn = "__handle_code_metas_picker_change"; try { // Acquire code data let _code_data = ( JSON.parse(code_box_sel.find(".code-data").val() || "{}") ); // Select request and response targets let _section_request_sel = ( code_box_sel.find(".code-section.code-section--request") ); let _section_response_sel = ( code_box_sel.find(".code-section.code-section--response") ); // Acquire selected values let _selected_request = ( _section_request_sel.find(".code-meta-picker select").val() ); let _selected_response = ( _section_response_sel.find(".code-meta-picker select").val() ); // Acquire code chunk associated to selected request let _code_chunk = (_code_data[_selected_request] || null); // Re-compute code section for request? if (_code_chunk !== null) { this.__refresh_code_metas_picker_section( "request", code_box_sel, _section_request_sel, _selected_request, _code_chunk.request ); // Acquire code chunk associated to selected response let _code_response = ( _code_chunk.responses[parseInt(_selected_response, 10)] || null ); // Re-compute code section for response? if (_code_response !== null) { this.__refresh_code_metas_picker_section( "response", code_box_sel, _section_response_sel, _selected_response, _code_response ); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Handles code block viewed event * @private * @param {object} target_sel * @return {undefined} */ __handle_code_block_viewed(target_sel) { let fn = "__handle_code_block_viewed"; try { // Load full code box parent element? (if any) // Notice: only if it was not already previously loaded, also note that \ // this only applies to 'pre' element wrapped in a '.code' box \ // wrapper. Not all 'pre' are being wrapped as such, therefore this \ // is not necessary in all cases. let _code_box_sel = ( target_sel.parents(".code[data-was-loaded=\"false\"]").first() ); if (_code_box_sel.length > 0) { _code_box_sel[0].setAttribute("data-was-loaded", "true"); // Trigger the first code box picker 'change' event to load default \ // code into view. this.__handle_code_metas_picker_change(_code_box_sel); } // Highlight code? Do not re-apply if already applied before. let _code_el = ( target_sel.find("code[class*=\"language-\"]")[0] || null ); if (_code_el !== null && _code_el.hasAttribute("data-viewed") !== true) { _code_el.setAttribute("data-viewed", ""); window.Prism.highlightElement(_code_el); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Handles Crisp chat open click event * @private * @return {boolean} False value (prevents default click event) */ __handle_crisp_chat_open_click() { let fn = "__handle_crisp_chat_open_click"; try { if (typeof window.$crisp !== "undefined") { window.$crisp.do("chat:open"); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } finally { return false; } } /** * Handles Crisp chat feedback click event * @private * @return {boolean} False value (prevents default click event) */ __handle_crisp_chat_feedback_click() { let fn = "__handle_crisp_chat_feedback_click"; try { if (typeof window.$crisp !== "undefined") { // Open chatbox window.$crisp.do("chat:open"); // Show feedback message? (if not already shown in current session) if (this.__crisp_chat_feedback_shown !== true) { this.__crisp_chat_feedback_shown = true; window.$crisp.push([ "do", "message:show", [ "text", ( `Do you have feedback on this page? ` + `Please reply to this message with your comments! :)` + `\n\n` + `➡️ ${document.location.href}` ) ] ]); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } finally { return false; } } /** * Injects search results * @private * @param {object} results_sel * @param {object} entries_sel * @param {object} results * @return {undefined} */ __inject_search_results(results_sel, entries_sel, results) { let fn = "__inject_search_results"; try { // Generate results HTML let _results_html = ""; for (let _i = 0; _i < results.length; _i++) { let _result = results[_i]; // Generate result path HTML let _path_html = "", _path_chunks = _result.path.split(" > "); for (let _j = 0; _j < _path_chunks.length; _j++) { _path_html += ( this.__escape_html_text(_path_chunks[_j]) ); // Append separator? if (_path_chunks.length > 1 && _j < (_path_chunks.length - 1)) { _path_html += ( "" ); } } // Acquire entry type let _type = ( (_result.id.includes("#") === true) ? "anchor" : "page" ); // Append whole result HTML to results HTML _results_html += ( `
  • ` + `` + `` + _path_html + `` + `
    ` + this.__escape_html_text(_result.title) + `
    ` + ( _result.summary ? ( `

    ` + this.__escape_html_text(_result.summary) + `

    ` ) : "" ) + `
    ` + `
  • ` ); } // Inject generated HTML entries_sel.html(_results_html); // Select results let _results_sel = entries_sel.find(".spotlight-entry"); // Select first result (if any) _results_sel.first().attr("data-selected", "true"); // Make sure that scroll position is restored to top of scroll area if (results_sel.length > 0) { results_sel[0].scrollTop = 0; } // Bind mouse enter + mouse leave events on new results _results_sel.on("mouseenter", (event) => { try { _results_sel.removeAttr("data-selected"); this._$(event.target).attr("data-selected", "true"); } catch (_error) { Console.error(`${this.ns}.${fn}:mouseenter`, _error); } }); // Bind click event on new results (just to auto-hide search in case an \ // anchor gets clicked) _results_sel.on("click", () => { try { // Close search immediately this.__close_search( true //-[do_reset] ); } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Fetches status health (based on provider) * @private * @param {string} provider * @param {string} target * @return {undefined} */ __fetch_status_health(provider, target) { switch (provider) { case "vigil": { // Vigil provider return fetch(`${target}/status/text/`, { mode : "cors" }) .then((response) => { // Non-success response? if (response.status !== 200) { return Promise.reject(null); } // Examine the text in the response return response.text(); }); } case "crisp": { // Crisp Status provider return fetch(`${target}/includes/report/`, { mode : "cors" }) .then((response) => { // Non-success response? if (response.status !== 200) { return Promise.reject(null); } // Examine the JSON in the response return response.json(); }) .then((report) => { // Extract health from report return Promise.resolve(report.health); }); } default: { // Provider unknown, hard-fail return Promise.reject(null); } } } /** * Refreshes status indicator * @private * @param {object} status_sel * @param {object} seconds_sel * @return {undefined} */ __refresh_status_indicator(status_sel, seconds_sel) { let fn = "__refresh_status_indicator"; try { // Refresh status status_sel.attr( "data-status", (this.__status_poll_health || "none") ); // Refresh 'last checked since' seconds seconds_sel.text( `${(this.__status_poll_last_check || 1)}` ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds search open keydown event * @private * @return {undefined} */ __bind_search_open_keydown() { let fn = "__bind_search_open_keydown"; try { const _KEY_CODE_K = 75, _KEY_CODE_ESCAPE = 27, _KEY_CODE_ENTER = 13, _KEY_CODE_ARROW_UP = 38, _KEY_CODE_ARROW_DOWN = 40; window.addEventListener("keydown", (event) => { switch (event.keyCode) { // 'K' pressed? case _KEY_CODE_K: { // Check if 'CTRL' is pressed (or 'COMMAND' on Mac computers) let _has_control = ( ((navigator.platform || "").toLowerCase().indexOf("mac") !== -1) ? event.metaKey : event.ctrlKey ); if (_has_control === true) { if (this.__search_opened !== true) { // Block 'CTRL + K' event default behavior (otherwise, browser \ // URL search prompt will be shown) event.preventDefault(); // Open search this.__open_search(); } else { // Close search // Notice: this will show the browser default URL search \ // prompt, letting the users press 'CTRL + K' twice quickly \ // if they want to perform a regular browser URL search. this.__close_search(); } } break; } // 'ESCAPE' pressed? case _KEY_CODE_ESCAPE: { if (this.__search_opened === true) { // Block event default behavior event.preventDefault(); // Close search this.__close_search(); } break; } // 'ENTER' pressed? case _KEY_CODE_ENTER: { if (this.__search_opened === true) { // Block event default behavior event.preventDefault(); // Select selected result let _result_selected_sel = this._$( "#search .spotlight-entry[data-selected=\"true\"] " + ".spotlight-entry-link" ); // Open selected search result? if (_result_selected_sel.length > 0) { let _target_url = _result_selected_sel.first().attr("href"); if (_target_url) { // Close search immediately this.__close_search( true //-[do_reset] ); // Split hash part from target URL? let _target_url_parts = _target_url.split("#"); // Navigate to URL? (if different than current one) if (_target_url_parts[0] !== document.location.pathname) { // Navigate to new full URL document.location.href = _target_url; } else if (_target_url_parts[1]) { // Navigate to new hash (or same) document.location.hash = `#${_target_url_parts[1]}`; } else if ((document.location.hash || "").length > 1) { // Reset current hash document.location.hash = ""; } } } } break; } // 'ARROW UP' or 'ARROW DOWN' pressed? case _KEY_CODE_ARROW_UP: case _KEY_CODE_ARROW_DOWN: { if (this.__search_opened === true) { // Block event default behavior event.preventDefault(); // Select search results let _results_all_sel = this._$("#search .spotlight-entry"); // Compute increment let _increment = ( (event.keyCode === _KEY_CODE_ARROW_DOWN) ? 1 : -1 ); // Select next or previous search result this.__toggle_search_result_selected( _results_all_sel, _increment ); } break; } } }); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds search open click event * @private * @return {undefined} */ __bind_search_open_click() { let fn = "__bind_search_open_click"; try { this._$("#header .search").on( "click", this.__open_search.bind(this) ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds search close click event * @private * @return {undefined} */ __bind_search_close_click() { let fn = "__bind_search_close_click"; try { this._$("#search").on("click", (event) => { try { if (event.target) { let _target_sel = this._$(event.target); // Click away from spotlight box? Close search. if (_target_sel.is("#search .spotlight") === true) { this.__close_search(); } } } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds search field keyup event * @private * @return {undefined} */ __bind_search_field_keyup() { let fn = "__bind_search_field_keyup"; try { // Select all targets let _spotlight_sel = this._$("#search .spotlight"), _input_sel = _spotlight_sel.find(".spotlight-input"), _results_sel = _spotlight_sel.find(".spotlight-results"), _entries_sel = _results_sel.find(".spotlight-entries"); // Initialize last search value _input_sel.on("keyup", (event) => { try { // Acquire normalized search query value let _field_value = ( (event.target.value || "").trim().toLowerCase() ); if (_field_value !== this.__search_field_value_last) { // Retain last search value this.__search_field_value_last = _field_value; // Define proceed function let _fnProceed = () => { this.__proceed_search_query( _spotlight_sel, _results_sel, _entries_sel, _field_value ); }; // Proceed immediately? (pending stack is not defined) if (this.__search_index_load_pending === null) { // Proceed immediately _fnProceed(); } else { // Show load spinner (as search was deferred) _spotlight_sel.attr("data-index-loading", "true"); // Stack for later (note: reset stack, as we do not want any \ // previous operation from being processed, only the last one) this.__search_index_load_pending = [_fnProceed]; } } } catch (_error) { Console.error(`${this.ns}.${fn}:keyup`, _error); } }); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds copy click event * @private * @return {undefined} */ __bind_copy_click() { let fn = "__bind_copy_click"; try { // Select all copy targets let _click_all_sel = this._$(".copy-button"); // Bind all click events _click_all_sel.each((click_el) => { let _state_confirm_timeout = null; // Notice: use 'addEventListener' instead of '.on' as this is ~500% \ // faster, which avoids blocking page rendering for ~10ms when there \ // are a lot of copy elements. click_el.addEventListener("click", () => { try { // Find click parent (from clicked basis) let _click_parent_sel = this._$(click_el); if (_click_parent_sel.is(".copy") === false) { _click_parent_sel = _click_parent_sel.parents(".copy").first(); } // Select clicked value let _value_sel = _click_parent_sel.find(".copy-value"); if (_click_parent_sel.length > 0 && _value_sel.length > 0) { // Select text and copy it let _was_copied = this.__copy_text(_value_sel[0]); // Change state to 'copied'? if (_was_copied === true) { // Clear out state clear timeout (if any) if (_state_confirm_timeout !== null) { clearTimeout(_state_confirm_timeout); } // Change state to 'copied' _click_parent_sel.attr("data-copy-state", "copied"); // Schedule state clear timeout _state_confirm_timeout = setTimeout(() => { _state_confirm_timeout = null; // Change state back to 'none' _click_parent_sel.attr("data-copy-state", "none"); }, this.__copy_state_confirm_delay); } } } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); }); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds appearance toggle click event * @private * @return {undefined} */ __bind_appearance_toggle_click() { let fn = "__bind_appearance_toggle_click"; try { this._$("#header .coloring").on("click", () => { try { // Acquire new appearance mode let _new_mode = ( (this.__appearance_mode === "light") ? "dark" : "light" ); // Toggle appearance mode this.__toggle_appearance(_new_mode); // Remember choice (with a cookie) this.__write_cookie( "appearance-mode", _new_mode ); } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds sidebar toggler click event * @private * @return {undefined} */ __bind_sidebar_toggler_click() { let fn = "__bind_sidebar_toggler_click"; try { let _sidebar_left_sel = ( this._$("#content .sidebar.sidebar--left") ); if (_sidebar_left_sel.length > 0) { // Bind sidebar toggler click events let _sidebar_togglers_sel = this._$( "#content .sidebar-toggler, #content .sidebar-toggler-retract" ); _sidebar_togglers_sel.on("click", () => { try { // Compute new sidebar visibility state let _new_state = ( (_sidebar_left_sel.attr("data-visible") === "true") ? "false" : "true" ); // Toggle visible state on sidebar _sidebar_left_sel.attr("data-visible", _new_state); } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); // Bind sidebar link click events (anchor links) let _sidebar_link_click = _sidebar_left_sel.find( ".nest-navigate-link[href^=\"#\"], .nest-navigate-slice[href^=\"#\"]" ); if (_sidebar_link_click.length > 0) { _sidebar_link_click.on("click", () => { try { // Forcibly hide sidebar _sidebar_left_sel.attr("data-visible", "false"); } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds sidebar nest level toggle click event * @private * @return {undefined} */ __bind_sidebar_nest_level_toggle_click() { let fn = "__bind_sidebar_nest_level_toggle_click"; try { this._$("#content .sidebar .nest .nest-navigate-toggle").on( "click", (event) => { // Toggle sidebar nest level? if (event.target) { this.__toggle_sidebar_nest_level( this._$(event.target) ); } } ); } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds content anchor viewed event * @private * @return {undefined} */ __bind_content_anchor_viewed() { let fn = "__bind_content_anchor_viewed"; try { let _sidebar_left_sel = ( this._$("#content .sidebar.sidebar--left") || [] ); let _anchor_all_sel = ( this._$("#content .panes .content [id]") || [] ); let _link_all_sel = ( (_sidebar_left_sel.length > 0) ? _sidebar_left_sel.find(".nest-navigate-link[data-anchor^=\"#\"]") : [] ); // Anchors exist on page? Bind viewed listeners. if (_sidebar_left_sel.length > 0 && _anchor_all_sel.length > 0 && _link_all_sel.length > 0) { // Bind intersection observer? (if browser API is available, otherwise \ // ignore, as this is only a nice-to-have feature) // Notice: this will apply anchor viewed markers when the anchor \ // comes into view. if (typeof window.IntersectionObserver !== "undefined") { // Acquire header height (used to compute root margins, ie. \ // accounting for the fact that an element that went into view but \ // is still sliding below the header, is not really into view yet) let _header_sel = (this._$("#header") || []), _header_height = 0; if (_header_sel.length > 0) { _header_height = ( _header_sel[0].offsetHeight || 0 ); } // Allocate viewed context let _viewed_context = { active : null, visible : {} }; // Create intersection observer let _observer = new IntersectionObserver( (entries) => { for (let _i = 0; _i < entries.length; _i++) { let _entry = entries[_i]; if (_entry.target && _entry.target.id && (_link_all_sel.filter( `[data-anchor="#${_entry.target.id}"]` ).length > 0)) { // Entry intersecting? if (_entry.isIntersecting === true) { _viewed_context.visible[_entry.target.id] = true; } else { delete _viewed_context.visible[_entry.target.id]; } } } // Schedule content anchor viewed event? (only if active anchor \ // has changed; we need to find first active anchor from all \ // available anchors, ie. this is an ordered list) let _active_anchor = null; for (let _i = 0; _i < _anchor_all_sel.length; _i++) { let _cur_anchor_el = _anchor_all_sel[_i]; // This anchor is visible? Stop there, as we found the first \ // visible anchor. if (_viewed_context.visible[_cur_anchor_el.id] === true) { _active_anchor = _cur_anchor_el; break; } } if (_active_anchor !== null && _active_anchor !== _viewed_context.active) { this.__schedule_handle_content_anchor_viewed( _link_all_sel, _sidebar_left_sel, _active_anchor.id, _header_height ); } // Apply new active item? (or retain last active if stack is \ // empty) if (_active_anchor !== null) { _viewed_context.active = _active_anchor; } }, { threshold : 0.0, rootMargin : ( `${(-1 * _header_height)}px 0px 0px 0px` ) } ); // Bind intersection observer on all elements for (let _i = 0; _i < _anchor_all_sel.length; _i++) { _observer.observe(_anchor_all_sel[_i]); } } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds code metas picker change event * @private * @return {undefined} */ __bind_code_metas_picker_change() { let fn = "__bind_code_metas_picker_change"; try { let _code_meta_picker_all_sel = ( this._$(".code .code-meta-picker select") || [] ); // Code boxes exist on page? Bind change listener on select. if (_code_meta_picker_all_sel.length > 0) { // Bind all change events _code_meta_picker_all_sel.each((picker_el) => { // Notice: use 'addEventListener' instead of '.on' as this is ~200% \ // faster, which avoids blocking page rendering for ~5ms when \ // there are a lot of select elements. picker_el.addEventListener("change", (event) => { try { let _code_box_sel = ( this._$(event.target).parents(".code").first() ); if (_code_box_sel.length > 0) { this.__handle_code_metas_picker_change(_code_box_sel); } } catch (_error) { Console.error(`${this.ns}.${fn}:change`, _error); } }); }); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds code block viewed event * @private * @return {undefined} */ __bind_code_block_viewed() { let fn = "__bind_code_block_viewed"; try { let _pre_all_sel = (this._$("pre") || []); // Code blocks exist on page? Bind code block listener. if (_pre_all_sel.length > 0) { // Bind intersection observer? (if browser API is available) // Notice: this will apply code highlighting when the code block \ // enters into view, or will load code boxes when they enter into \ // view. if (typeof window.IntersectionObserver !== "undefined") { // Create intersection observer let _observer = new IntersectionObserver( (entries) => { // Code blocks entered into view? for (let _i = 0; _i < entries.length; _i++) { let _entry = entries[_i]; // Entry intersecting? if (_entry.isIntersecting === true && _entry.target) { // Handle code block viewed event this.__handle_code_block_viewed( this._$(_entry.target) ); } } }, { threshold : 0.0 } ); // Bind intersection observer on all elements for (let _i = 0; _i < _pre_all_sel.length; _i++) { _observer.observe(_pre_all_sel[_i]); } } else { Console.warn( `${this.ns}.${fn}`, ( "Optimized code block formatting is disabled, as the browser " + "does not support the IntersectionObserver API." ) ); // Highlight all code elements, and load all code boxes, as the \ // browser does not support intersection observing (this is \ // obviously much slower and can degrade performances on page \ // first draw) for (let _i = 0; _i < _pre_all_sel.length; _i++) { // Trigger code block viewed event for block (virtual event, as \ // the code block is probably not even in view) this.__handle_code_block_viewed( this._$(_pre_all_sel[_i]) ); } } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds Markdown embed click event * @private * @return {undefined} */ __bind_markdown_embed_click() { let fn = "__bind_markdown_embed_click"; try { let _embed_preview_all_sel = ( this._$(".embed .embed-preview") || [] ); // Embeds exist on page? Bind click listener (this injects embed content) if (_embed_preview_all_sel.length > 0) { _embed_preview_all_sel.on("click", (event) => { try { // Acquire embed parent let _embed_sel = this._$(event.target).parents(".embed").first(); if (_embed_sel.length > 0) { let _injector = _embed_sel.attr("data-injector"), _target = _embed_sel.attr("data-target"); if (_injector && _target) { // Generate injector code let _inject_code; switch (_injector) { case "youtube": { // Generate YouTube embed URL let _youtube_url = ( "https://www.youtube.com/embed/" + _target + "?hl=en&rel=0&modestbranding=1&autoplay=1" ); _inject_code = ( "" ); break; } default: { // Injector is not supported. _inject_code = null; } } // Inject embedded content? if (_inject_code !== null) { // Perform injection _embed_sel.find(".embed-frame").html(_inject_code); // Mark as loaded _embed_sel.attr("data-loaded", "true"); } } } } catch (_error) { Console.error(`${this.ns}.${fn}:click`, _error); } }); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds Crisp chat open click event * @private * @return {undefined} */ __bind_crisp_chat_open_click() { let fn = "__bind_crisp_chat_open_click"; try { // Bind chat open click listener? (if chatbox is included) if (typeof window.$crisp !== "undefined") { // Opens the Crisp chatbox let _chat_open_sel = ( this._$("a[href='#crisp-chat-open']") || [] ); for (let _i = 0; _i < _chat_open_sel.length; _i++) { _chat_open_sel[_i].onclick = ( this.__handle_crisp_chat_open_click.bind(this) ); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds Crisp chat feedback click event * @private * @return {undefined} */ __bind_crisp_chat_feedback_click() { let fn = "__bind_crisp_chat_feedback_click"; try { // Bind chat open click listener? (if chatbox is included) if (typeof window.$crisp !== "undefined") { // Binds to the feedback action link let _chat_feedback_sel = ( this._$("a[href='#crisp-chat-feedback']") || [] ); for (let _i = 0; _i < _chat_feedback_sel.length; _i++) { _chat_feedback_sel[_i].onclick = ( this.__handle_crisp_chat_feedback_click.bind(this) ); } } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } /** * Binds status poll schedule * @private * @return {undefined} */ __bind_status_poll_schedule() { let fn = "__bind_status_poll_schedule"; try { // Pre-select all status targets let _status_sel = ( this._$("#footer .status") || [] ); if (this.__url_status.provider && this.__url_status.target && _status_sel.length > 0) { let _seconds_sel = _status_sel.find(".status-time-seconds"); // Stop any previously-set scheduler? if (this.__status_poll_scheduler !== null) { clearInterval(this.__status_poll_scheduler); } // Start new scheduler this.__status_poll_scheduler = setInterval(() => { // Increment last checked counter? (if we have an health status \ // defined) if (this.__status_poll_health !== null) { this.__status_poll_last_check += ( this.__status_poll_interval / this.__second_in_milliseconds ); } // Should we refresh health now? if (this.__status_poll_last_check === 0 || ((this.__status_poll_last_check * this.__second_in_milliseconds) >= this.__status_poll_refresh)) { // Fetch latest status page health this.__fetch_status_health( this.__url_status.provider, this.__url_status.target ) .then((text) => { // Acquired status text health is unknown? if (this.__status_known_health.includes(text) !== true) { return Promise.reject(null); } // Store acquired status text? (if health is known) this.__status_poll_health = text; this.__status_poll_last_check = 0; return Promise.resolve(); }) .catch(() => { // Mark health refresh as failed? this.__status_poll_health = "failure"; this.__status_poll_last_check = 0; return Promise.resolve(); }) .then(() => { // Refresh the UI (both health and 'last checked' time counter) this.__refresh_status_indicator(_status_sel, _seconds_sel); }); } else { // Refresh the UI (just the 'last checked' time counter) this.__refresh_status_indicator(_status_sel, _seconds_sel); } }, this.__status_poll_interval); } } catch (error) { Console.error(`${this.ns}.${fn}`, error); } } } window.Common = new Common(); window.Common._document_sel.ready(function() { window.Common.init(); }); ================================================ FILE: src/locales/en.json ================================================ { "COMMON" : { "HEADER" : { "EXTRAS" : { "CHANGES" : "Last Changes" }, "SEARCH" : { "PLACEHOLDER" : "Search docs" } }, "FOOTER" : { "ENGINE" : { "GENERATED_BY" : "Documentation generated by", "PROJECT_FROM" : "an open-source project from" }, "STATUS" : { "LABEL_FAILURE" : "Failed checking status", "LABEL_HEALTHY" : "All systems are healthy", "LABEL_SICK" : "Service slowdown ongoing", "LABEL_DEAD" : "Service outage ongoing", "TIME_LAST" : "Last checked", "TIME_AGO" : "ago" } }, "SEARCH" : { "FIELD" : { "INPUT_PLACEHOLDER" : "Find anything in the docs…" }, "LEGEND" : { "SHORTCUT_NAVIGATE" : "Navigate", "SHORTCUT_GO" : "Go To", "SHORTCUT_CLOSE" : "Close", "ERROR_INDEX" : "Search unavailable" } }, "CODE" : { "META_NAME_REQUEST" : "Request", "META_NAME_RESPONSE" : "Response", "CONTENT_LOADING" : "(loading)" }, "METAS" : { "SITE_NAME" : "Developer Hub" } }, "HOME" : { "PAGE" : "Developer Hub", "TITLE" : "Welcome to the Developer Hub", "LABEL" : "Read guides and browse references.", "BULLETPOINTS" : { "QUICKSTART" : "Get Started", "GUIDES" : "Guides", "REFERENCES" : "References" }, "SUPPORT" : { "QUESTION" : "Anything that you cannot find in the documentation?", "LABEL" : "Ask our tech support team, anytime.", "ACTION" : "Chat with Support" } }, "GUIDES" : { "DETAILS" : { "FEEDBACK" : "Give Feedback", "UPDATED" : "Updated on", "TITLE_INDEX" : "Introduction" } }, "REFERENCES" : { "BLUEPRINT" : { "GROUP" : { "ORIGIN_IN" : "in" }, "SPECIFICATION" : { "REQUEST_FORMAT_TITLES" : { "DATA_REQUEST" : "Request Body", "DATA_RESPONSE" : "Response Data", "URI_PARAMETERS" : "URI Parameters" }, "REQUEST_FORMAT_REQUIRED" : "Required", "REQUEST_FORMAT_OPTIONAL" : "Optional", "REQUEST_FORMAT_MEMBERS" : "Values", "REQUEST_FORMAT_TOGGLE_SHOW": "Show child parameters", "REQUEST_FORMAT_TOGGLE_HIDE": "Hide child parameters" }, "EXAMPLES" : { "DETAILS" : { "TIERS" : "Tiers", "SCOPES" : "Scopes" } } }, "MARKDOWN" : { "DETAILS" : { "UPDATED" : "Updated on" } } }, "CHANGES" : { "PAGE" : "Last Changes", "TITLE_FEED" : "Platform Changes", "TITLE_LATEST" : "Latest Platform Changes", "TITLE_YEAR" : "Platform Changes In", "NOTICE" : "This page is a ledger of all notable changes that occured over time.\n\n**Note that breaking changes appear in red.**\n\nIf you would like to monitor those changes, an RSS feed is available at: [`changes.rss`](/changes.rss)", "NAVIGATE" : { "LATEST" : "Latest" }, "MONTHS" : { "01" : "January", "02" : "February", "03" : "March", "04" : "April", "05" : "May", "06" : "June", "07" : "July", "08" : "August", "09" : "September", "10" : "October", "11" : "November", "12" : "December" } }, "NOT_FOUND" : { "PAGE" : "Page Not Found", "TITLE" : "Oops. We cannot find this page!", "LABEL_FIRST" : "You have hit a page that does not exist.", "LABEL_SECOND" : "It may have been removed.", "ACTION" : "Go to Homepage" } } ================================================ FILE: src/stylesheets/_colors.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou // Palette $color-white: #fff; $color-black: #000; $color-grey: #e0e7ef; $color-grey-light: #dee6ee; $color-grey-dark: #dfe5ec; $color-blue-black: #44566b; $color-blue-grey: #738aa3; $color-blue-grey-light: #c1ccd8; $color-blue-grey-dark: #607282; $color-green: #009d24; $color-red: #c21717; // Base $color-base: #f5f6f8; $color-base-background: #f8f9fa; // Selection $color-selection-default: #bfe2ff; $color-selection-code: $color-white; // Badges $color-badge-black: $color-black; $color-badge-white: $color-white; $color-badge-blue: #0299e5; $color-badge-green: #86a546; $color-badge-yellow: #b2a64e; $color-badge-red: #c14a75; $color-badge-special-light: #dde4e8; $color-badge-special-dark: #e8f5fa; // Methods $color-method-get-head-light: #ddf1fc; $color-method-get-head-dark: #0099e5; $color-method-post-light: #e4f2c8; $color-method-post-dark: #85a546; $color-method-put-patch-light: #f7f2c3; $color-method-put-patch-dark: #b1a74e; $color-method-delete-light: #f2d8e1; $color-method-delete-dark: #c14a74; // Markdown $color-markdown-code-inline: #a9c6dd; $color-markdown-code-inline-light: #e8f5fa; $color-markdown-code-inline-dark: #275190; $color-markdown-code-block: #1f2224; $color-markdown-blockquote: #c0cedd; $color-markdown-emphasis-warning-light: #fff4e1; $color-markdown-emphasis-warning-dark: #f49c0b; $color-markdown-emphasis-info-light: #fffcd9; $color-markdown-emphasis-info-dark: #f4e200; $color-markdown-emphasis-notice-light: #e2ffdd; $color-markdown-emphasis-notice-dark: #48cc30; // Status $color-status-default: #787b84; $color-status-healthy: #0db033; $color-status-sick: #f18000; $color-status-dead: #e10000; // Code $color-code-header-light: #151517; $color-code-header-dark: #0c0c0d; $color-code-metas: #1e1e20; $color-code-content: #222628; // Highlight $color-highlight-main: #f7f7f7; $color-highlight-entity-light: #a09f93; $color-highlight-entity-dark: #393939; $color-highlight-comment: #a2a2a2; $color-highlight-punctuation: #f7f7f7; $color-highlight-variable: #f2777a; $color-highlight-number: #ff8c4d; $color-highlight-class: #fc6; $color-highlight-property: #c2ee65; $color-highlight-regex: #6cc; $color-highlight-function: #88c4ff; $color-highlight-keyword: #ff9eff; $color-highlight-doctype: #d27b53; // Search $color-search-lock: #495368; $color-search-spotlight-shortcut: #929fac; // Header $color-header-search-border-clear: #d5dadf; $color-header-search-shortcut-text-dark: #a2aeb9; $color-header-search-shortcut-text-light: #e4e9ed; $color-header-coloring-toggle-icon-light: #e0a142; $color-header-coloring-toggle-icon-dark: #5162b7; // Content $color-content-sidebar-right: #2d3134; // Footer $color-footer-text: #9fb0c2; $color-footer-link-target: #677789; $color-footer-copyright: #859ab0; $color-footer-status-time: #93a0ad; // References $color-references-request-format-required: #eb4e3a; $color-references-request-format-optional: #a2a8b0; $color-references-request-format-label: #717982; $color-references-examples-detail-value: #858b8e; $color-references-examples-detail-segment: #dde4e8; $color-references-examples-detail-parameter: #c2ee65; // Changes $color-changes-month-event-deprecated: #fdeaee; ================================================ FILE: src/stylesheets/_config.scss ================================================ // chappe // // Copyright 2026, Crisp IM SAS // Author: Valerian Saliou $inlining: false !default; ================================================ FILE: src/stylesheets/_functions.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "sass:math"; @use "_variables" as vars; @function background-image-width($image) { $image-width: image-width("1x/#{$image}"); @return $image-width; } @function background-image-height($image) { $image-height: image-height("1x/#{$image}"); @return $image-height; } @function font-size-calculate-px($font-size) { $calculated-font-size: (vars.$font-resize-ratio * $font-size); @return $calculated-font-size; } @function font-size-calculate-percent($font-size) { $calculated-font-size: (vars.$font-resize-ratio * math.div($font-size, vars.$font-size-base)) * 100%; @return $calculated-font-size; } @function font-size-calculate-em($font-size) { $calculated-font-size: (vars.$font-resize-ratio * math.div($font-size, vars.$font-size-base)) * 1em; @return $calculated-font-size; } @function font-size-calculate-rem($font-size, $relative-font-size) { $calculated-font-size: (vars.$font-resize-ratio * math.div($font-size, $relative-font-size)) * 1rem; @return $calculated-font-size; } ================================================ FILE: src/stylesheets/_globals.scss ================================================ // chappe // // Copyright 2026, Crisp IM SAS // Author: Valerian Saliou @forward "_mixins"; @forward "_functions"; @forward "_variables"; @forward "_colors"; ================================================ FILE: src/stylesheets/_mixins.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "_config" as config; @use "_functions" as functions; @use "_variables" as vars; /* stylelint-disable property-no-vendor-prefix, selector-no-vendor-prefix */ @mixin font-size($font-size) { font-size: functions.font-size-calculate-px($font-size); } @mixin font-face-include($font-family, $font-path, $font-weight, $font-style) { @font-face { font-family: $font-family; src: url('/static/fonts/#{$font-path}.woff2?#{vars.$cache-buster-hash}') format('woff2'), url('/static/fonts/#{$font-path}.woff?#{vars.$cache-buster-hash}') format('woff'); font-weight: $font-weight; font-style: $font-style; font-display: swap; } } @mixin background-image-internal($image) { @if config.$inlining == true { background-image: inline-image($image); } @else { background-image: url('/static/images/#{$image}?#{vars.$cache-buster-hash}'); } } @mixin background-image-fill($display: true) { background-size: contain; background-repeat: no-repeat; background-position: center; @if $display == true { display: inline-block; } } @mixin background-image-external($image) { background-image: url('/static/images/#{$image}?#{vars.$cache-buster-hash}'); } @mixin background-image-internal-fill($image, $display: true) { @include background-image-internal($image); @include background-image-fill($display); } @mixin background-image-external-fill($image, $display: true) { @include background-image-external($image); @include background-image-fill($display); } @mixin mask-image-internal($image) { @if config.$inlining == true { @include mask-image(inline-image($image)); } @else { @include mask-image(url('/static/images/#{$image}?#{vars.$cache-buster-hash}')); } } @mixin mask-image-fill($display: true) { @include mask-size(contain); @include mask-repeat(no-repeat); @include mask-position(center); @if $display == true { display: inline-block; } } @mixin mask-image-internal-fill($image, $display: true) { @include mask-image-internal($image); @include mask-image-fill($display); } @mixin placeholder { &::placeholder { @content; } &:-moz-placeholder { @content; } &::-moz-placeholder { @content; } &:-ms-input-placeholder { @content; } &::-webkit-input-placeholder { @content; } } @mixin input-placeholder { @include placeholder { @content; } } @mixin textarea-placeholder { @include placeholder { @content; } } @mixin input-search { &::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-search-results-button, &::-webkit-search-results-decoration { @content; } } @mixin selection { ::selection { @content; } ::-moz-selection { @content; } } @mixin appearance($value) { -webkit-appearance: $value; -moz-appearance: $value; appearance: $value; } @mixin backdrop-filter($rules) { -webkit-backdrop-filter: $rules; backdrop-filter: $rules; } @mixin mask-image($value) { --mask-image: #{$value}; -webkit-mask-image: var(--mask-image); mask-image: var(--mask-image); } @mixin mask-size($value) { -webkit-mask-size: $value; mask-size: $value; } @mixin mask-repeat($value) { -webkit-mask-repeat: $value; mask-repeat: $value; } @mixin mask-position($value) { -webkit-mask-position: $value; mask-position: $value; } @mixin font-smoothing($value: antialiased) { @if $value == antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @else { -webkit-font-smoothing: subpixel-antialiased; -moz-osx-font-smoothing: auto; } } @mixin keyframes-fix($name) { @keyframes #{$name} { @content; } } /* stylelint-enable property-no-vendor-prefix, selector-no-vendor-prefix */ ================================================ FILE: src/stylesheets/_variables.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou $cache-buster-hash: "@@revision"; $font-size-base: 15px; $font-resize-ratio: 1; $screen-large-width-breakpoint: 1140px; $screen-medium-width-breakpoint: 1020px; $screen-small-width-breakpoint: 880px; $screen-tiny-width-breakpoint: 640px; $screen-lilliput-width-breakpoint: 400px; $wrapper-default-padding-sides: 48px; $wrapper-medium-padding-sides: 28px; $wrapper-small-padding-sides: 20px; $wrapper-tiny-padding-sides: 16px; $wrapper-lilliput-padding-sides: 12px; $header-height: 58px; $header-border-size: 1px; $header-height-outer: ($header-height + $header-border-size); $content-wrap-max-width: 1200px; $content-sidebar-left-width: 248px; $content-sidebar-right-default-width: 460px; $content-sidebar-right-large-width: 360px; $button-default-height: 40px; $button-small-height: 34px; $badge-default-height: 18px; $badge-small-height: 14px; $badge-special-value-height: 18px; ================================================ FILE: src/stylesheets/changes/changes.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_config" with ( $inlining: $use-inline-images ); @use "../_globals" as *; #changes { position: relative; .navigate { user-select: none; position: absolute; right: 0; top: 70px; z-index: 1; &::before { content: ""; background-color: var(--color-grey-light); width: 2px; position: absolute; top: 0; bottom: 0; left: 0; z-index: 2; } .navigate-item { margin-bottom: 4px; &:last-of-type { margin-bottom: 0; } &.navigate-item--active { &, &:hover, &:active { .navigate-link { color: var(--color-white); background-color: var(--color-accent-base); z-index: 3; } } } &, .navigate-link { display: block; } .navigate-link { background-color: transparent; color: var(--color-black); letter-spacing: -.12px; text-align: left; line-height: 24px; padding-left: 17px; padding-right: 12px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; transition: background-color linear 75ms; position: relative; z-index: 1; @include font-size(14px); &:hover { background-color: var(--color-changes-navigate-link-background-hover); } &:active { background-color: var(--color-changes-navigate-link-background-active); } } } } .content { width: 100%; max-width: 860px; margin: 0 auto; padding-top: 30px; padding-bottom: 126px; .title { color: var(--color-black); @include font-size(22px); } .notice { background: var(--color-changes-notice-background); margin-top: 18px; padding: 18px 28px; overflow: hidden; position: relative; border-radius: 2px; &::before { content: ""; background-color: var(--color-grey-light); width: 5px; position: absolute; top: 0; bottom: 0; left: 0; } } .timeline { .month { margin-top: 52px; .month-name { color: var(--color-black); @include font-size(22px); } .month-events { margin-top: 25px; .month-event { $event-default-padding-top-bottom: 4px; $event-deprecated-padding-top-bottom: 10px; border-bottom: 1px solid var(--color-changes-month-event-border); margin-bottom: 14px; padding-bottom: 14px; display: flex; flex-direction: row; &.month-event--deprecated { .month-event-group { color: var(--color-red); padding-top: $event-deprecated-padding-top-bottom; } .month-event-text { background-color: var(--color-changes-month-event-deprecated-background); border-color: var(--color-red); padding-top: $event-deprecated-padding-top-bottom; padding-bottom: $event-deprecated-padding-top-bottom; padding-right: 8px; } } &:last-child { border-bottom: 0 none; margin-bottom: 0; padding-bottom: 0; } .month-event-group, .month-event-text { line-height: 20px; @include font-size(14px); } .month-event-group { color: var(--color-changes-month-event-group-text); line-height: 22px; width: 120px; padding-top: $event-default-padding-top-bottom; padding-right: 8px; flex: 0 0 auto; } .month-event-text { border-color: var(--color-grey); border-width: 0 0 0 2px; border-style: solid; padding: $event-default-padding-top-bottom 0 $event-default-padding-top-bottom 14px; flex: 1; } } } } } } } // - Media Queries // For screens below defined widths @media screen and (width <= 1140px) { #changes { .content { .content-aside { padding-right: 80px; } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #changes { .navigate { text-align: center; margin-top: 14px; position: static; &::before { display: none; } .navigate-item { margin-right: 1px; &:last-of-type { margin-right: 0; } &, .navigate-link { display: inline-block; } .navigate-link { padding: 0 10px; border-radius: 3px; } } } .content { padding-top: 22px; padding-bottom: 74px; .content-aside { padding-right: 0; } .title, .timeline .month .month-name { text-align: center; } .notice { padding: 14px 24px; } .timeline { .month { margin-top: 36px; .month-events { .month-event { border-bottom: 0 none; margin-bottom: 20px; padding-bottom: 0; display: block; &, &.month-event--deprecated { .month-event-group { padding: 0 0 2px; } .month-event-text { padding-top: 6px; } } &.month-event--deprecated { .month-event-text { padding-left: 10px; padding-right: 10px; } } .month-event-group { text-align: center; width: 100%; } .month-event-text { border-width: 2px 0 0; padding-left: 2px; padding-right: 2px; } } } } } } } } ================================================ FILE: src/stylesheets/common/_appearance.scss ================================================ // chappe // // Copyright 2022, Crisp IM SAS // Author: Valerian Saliou @use "sass:color"; @use "../_globals" as *; .appearance { // Palette --color-white: #{$color-white}; --color-black: #{$color-black}; --color-grey: #{$color-grey}; --color-grey-light: #{$color-grey-light}; --color-grey-dark: #{$color-grey-dark}; --color-blue-black: #{$color-blue-black}; --color-blue-grey: #{$color-blue-grey}; --color-blue-grey-light: #{$color-blue-grey-light}; --color-blue-grey-dark: #{$color-blue-grey-dark}; --color-green: #{$color-green}; --color-red: #{$color-red}; // Base --color-base: #{$color-base}; --color-base-background: #{$color-base-background}; // Selection --color-selection-default: #{$color-selection-default}; // Buttons --color-button-default: var(--color-accent-base); --color-button-active: var(--color-accent-active); --color-button-shadow: #{rgba($color-black, .1)}; // Badges --color-badge-black: #{$color-badge-black}; --color-badge-white: #{$color-badge-white}; --color-badge-blue: #{$color-badge-blue}; --color-badge-green: #{$color-badge-green}; --color-badge-yellow: #{$color-badge-yellow}; --color-badge-red: #{$color-badge-red}; --color-badge-special-background: #{rgba($color-badge-special-dark, .15)}; --color-badge-special-border: #{rgba($color-white, .25)}; --color-badge-special-text: #{$color-badge-special-light}; // Methods --color-method-get-head-light: #{$color-method-get-head-light}; --color-method-get-head-dark: #{$color-method-get-head-dark}; --color-method-post-light: #{$color-method-post-light}; --color-method-post-dark: #{$color-method-post-dark}; --color-method-put-patch-light: #{$color-method-put-patch-light}; --color-method-put-patch-dark: #{$color-method-put-patch-dark}; --color-method-delete-light: #{$color-method-delete-light}; --color-method-delete-dark: #{$color-method-delete-dark}; // Status --color-status-default-text: #{$color-status-default}; --color-status-healthy-text: #{$color-status-healthy}; --color-status-sick-text: #{$color-status-sick}; --color-status-dead-text: #{$color-status-dead}; // Viewer --color-viewer-panes-footer-border: #{rgba($color-grey, .5)}; // Markdown --color-markdown-title-small-text: var(--color-blue-black); --color-markdown-list-item-bullet: var(--color-blue-black); --color-markdown-code-inline-background: #{$color-markdown-code-inline-light}; --color-markdown-code-inline-border: #{$color-markdown-code-inline}; --color-markdown-code-inline-text: #{$color-markdown-code-inline-dark}; --color-markdown-code-block-background: #{$color-markdown-code-block}; --color-markdown-code-block-text: #{$color-white}; --color-markdown-code-block-clipboard-background-default: #{color.adjust($color-markdown-code-block, $lightness: 3%)}; --color-markdown-code-block-clipboard-background-hover: #{color.adjust($color-markdown-code-block, $lightness: 7%)}; --color-markdown-code-block-clipboard-background-active: #{color.adjust($color-markdown-code-block, $lightness: 5%)}; --color-markdown-code-block-clipboard-border-default: #{rgba($color-blue-grey-light, .25)}; --color-markdown-code-block-clipboard-border-hover: #{rgba($color-blue-grey-light, .5)}; --color-markdown-code-block-clipboard-border-active: #{rgba($color-blue-grey-light, .35)}; --color-markdown-code-block-clipboard-border-copied: #{color.adjust($color-green, $lightness: 7%)}; --color-markdown-code-block-clipboard-icon-background-default: #{$color-blue-grey-light}; --color-markdown-code-block-clipboard-icon-background-copied: var(--color-markdown-code-block-clipboard-border-copied); --color-markdown-code-selection: #{rgba($color-selection-code, .25)}; --color-markdown-figure-text: var(--color-blue-grey); --color-markdown-figure-border: #{rgba($color-grey, .9)}; --color-markdown-table-head-background: var(--color-base); --color-markdown-table-head-text: var(--color-blue-black); --color-markdown-table-body-row-background-hover: #{rgba($color-blue-grey-light, .08)}; --color-markdown-table-cell-border: #{rgba($color-blue-grey-light, .65)}; --color-markdown-blockquote-background: var(--color-base); --color-markdown-blockquote-text: var(--color-blue-black); --color-markdown-blockquote-border: #{$color-markdown-blockquote}; --color-markdown-embed-wrap-background: #{$color-black}; --color-markdown-emphasis-warning-background: #{$color-markdown-emphasis-warning-light}; --color-markdown-emphasis-warning-border: #{$color-markdown-emphasis-warning-dark}; --color-markdown-emphasis-info-background: #{$color-markdown-emphasis-info-light}; --color-markdown-emphasis-info-border: #{$color-markdown-emphasis-info-dark}; --color-markdown-emphasis-notice-background: #{$color-markdown-emphasis-notice-light}; --color-markdown-emphasis-notice-border: #{$color-markdown-emphasis-notice-dark}; // Code --color-code-selection: #{rgba($color-selection-code, .3)}; --color-code-border-hover: #{rgba($color-white, .25)}; --color-code-header-default: #{$color-code-header-light}; --color-code-header-hover: #{$color-code-header-dark}; --color-code-language-text: #{rgba($color-white, .6)}; --color-code-action-background: #{$color-white}; --color-code-metas-background: #{$color-code-metas}; --color-code-meta-name-border: #{rgba($color-white, .06)}; --color-code-meta-name-text: #{rgba($color-white, .8)}; --color-code-meta-type-text: #{rgba($color-white, .4)}; --color-code-content-background: #{$color-code-content}; --color-code-content-text: #{rgba($color-white, .4)}; --color-code-select-text-default: #{rgba($color-white, .5)}; --color-code-select-text-hover: #{rgba($color-white, .8)}; --color-code-select-border-default: #{rgba($color-white, .075)}; --color-code-select-border-hover: #{rgba($color-white, .15)}; // Highlight --color-highlight-main: #{$color-highlight-main}; --color-highlight-entity-light: #{$color-highlight-entity-light}; --color-highlight-entity-dark: #{$color-highlight-entity-dark}; --color-highlight-comment: #{$color-highlight-comment}; --color-highlight-punctuation: #{$color-highlight-punctuation}; --color-highlight-variable: #{$color-highlight-variable}; --color-highlight-number: #{$color-highlight-number}; --color-highlight-class: #{$color-highlight-class}; --color-highlight-property: #{$color-highlight-property}; --color-highlight-regex: #{$color-highlight-regex}; --color-highlight-function: #{$color-highlight-function}; --color-highlight-keyword: #{$color-highlight-keyword}; --color-highlight-doctype: #{$color-highlight-doctype}; // Search --color-search-lock-background: #{rgba($color-search-lock, .45)}; --color-search-spotlight-shadow: #{rgba($color-black, .1)}; --color-search-spotlight-shortcut-text: #{$color-search-spotlight-shortcut}; --color-search-spotlight-field-background: #{rgba($color-base, .65)}; --color-search-spotlight-field-placeholder: var(--color-blue-grey-dark); --color-search-spotlight-field-spinner-border: var(--color-blue-grey-dark); --color-search-spotlight-entry-link-background-default: var(--color-accent-base); --color-search-spotlight-entry-link-background-active: var(--color-accent-active); // Header --color-header-background: #{rgba($color-white, .85)}; --color-header-border: #{rgba($color-grey, .65)}; --color-header-shadow: #{rgba($color-black, .06)}; --color-header-dropdown-border: #{rgba($color-grey, .65)}; --color-header-dropdown-shadow: #{rgba($color-black, .05)}; --color-header-dropdown-link-background-hover: var(--color-accent-base); --color-header-dropdown-link-background-active: var(--color-accent-active); --color-header-search-background-default: var(--color-base); --color-header-search-background-clear: var(--color-white); --color-header-search-border-default: var(--color-grey-dark); --color-header-search-border-hover: #{color.adjust($color-grey-dark, $lightness: -10%)}; --color-header-search-border-active: #{color.adjust($color-grey-dark, $lightness: -14%)}; --color-header-search-border-clear-default: #{$color-header-search-border-clear}; --color-header-search-border-clear-hover: #{color.adjust($color-header-search-border-clear, $lightness: -6%)}; --color-header-search-border-clear-active: #{color.adjust($color-header-search-border-clear, $lightness: -9%)}; --color-header-search-shadow: #{rgba($color-grey-dark, .25)}; --color-header-search-placeholder-text: var(--color-blue-grey-dark); --color-header-search-shortcut-text: #{$color-header-search-shortcut-text-dark}; --color-header-search-shortcut-border: #{$color-header-search-border-clear}; --color-header-search-shortcut-shadow: #{$color-header-search-shortcut-text-light}; --color-header-coloring-background-default: #{color.adjust($color-grey-light, $lightness: 1%)}; --color-header-coloring-background-clear: #{rgba($color-white, .7)}; --color-header-coloring-toggle-border-default: #{rgba(color.adjust($color-grey-dark, $lightness: -50%), .2)}; --color-header-coloring-toggle-border-hover: #{rgba(color.adjust($color-grey-dark, $lightness: -50%), .225)}; --color-header-coloring-toggle-border-active: #{rgba(color.adjust($color-grey-dark, $lightness: -50%), .175)}; --color-header-coloring-toggle-shadow-default: #{rgba($color-black, .04)}; --color-header-coloring-toggle-shadow-hover: #{rgba($color-black, .06)}; --color-header-coloring-toggle-shadow-active: #{rgba($color-black, .02)}; --color-header-coloring-toggle-icon-light-background: #{$color-header-coloring-toggle-icon-light}; --color-header-coloring-toggle-icon-dark-background: #{$color-header-coloring-toggle-icon-dark}; // Content --color-content-navigation-background: #{rgba($color-base, .25)}; --color-content-navigation-border-default: var(--color-blue-grey-light); --color-content-navigation-border-active: #{color.adjust($color-blue-grey-light, $lightness: -5%)}; --color-content-navigation-shadow-outset: #{rgba($color-blue-grey-light, .25)}; --color-content-navigation-shadow-inset: #{rgba($color-white, .75)}; --color-content-navigation-shadow-active: #{rgba($color-blue-grey-light, .4)}; --color-content-navigation-label-text: var(--color-blue-grey); --color-content-bulletpoint-label-text: var(--color-blue-grey); --color-content-sidebar-left-background-default: #{rgba($color-base, .65)}; --color-content-sidebar-left-background-small: #{rgba($color-base, .9)}; --color-content-sidebar-left-background-tiny: var(--color-base); --color-content-sidebar-left-shadow-small: #{rgba($color-black, .04)}; --color-content-sidebar-right-background: #{$color-content-sidebar-right}; --color-content-sidebar-toggler-background: #{rgba($color-white, .85)}; --color-content-sidebar-toggler-shadow-default: #{rgba($color-black, .1)}; --color-content-sidebar-toggler-shadow-hover: #{rgba($color-black, .125)}; --color-content-sidebar-toggler-shadow-active: #{rgba($color-black, .2)}; --color-content-sidebar-toggler-icon-background: var(--color-blue-black); --color-content-sidebar-nest-navigate-link-background-hover: #{color.adjust($color-grey-light, $lightness: 3%)}; --color-content-sidebar-nest-navigate-link-background-active: #{color.adjust($color-grey-light, $lightness: 1%)}; --color-content-sidebar-nest-navigate-link-default-toggle-background-hover: #{color.adjust($color-grey-light, $lightness: -3%)}; --color-content-sidebar-nest-navigate-link-default-toggle-background-active: #{color.adjust($color-grey-light, $lightness: -7%)}; --color-content-sidebar-nest-navigate-link-active-toggle-background-hover: #{rgba($color-white, .15)}; --color-content-sidebar-nest-navigate-link-active-toggle-background-active: #{rgba($color-white, .2)}; --color-content-sidebar-nest-navigate-slice-text: var(--color-blue-black); --color-content-sidebar-nest-navigate-slice-icon-background-default: var(--color-blue-black); --color-content-sidebar-nest-navigate-slice-icon-background-active: var(--color-white); // Footer --color-footer-brand-separator: #{rgba($color-footer-text, .3)}; --color-footer-link-target-text: #{$color-footer-link-target}; --color-footer-copyright-text: #{$color-footer-copyright}; --color-footer-engine-text: #{$color-footer-text}; --color-footer-status-border-default: #{rgba($color-footer-text, .45)}; --color-footer-status-border-hover: #{rgba($color-footer-text, .65)}; --color-footer-status-time-text: #{$color-footer-status-time}; // Guides --color-guides-details-border: #{rgba($color-grey, .65)}; --color-guides-details-inner-text: var(--color-blue-black); // References --color-references-separator-content: #{rgba($color-grey, .65)}; --color-references-separator-sidebar: #{rgba($color-grey, .15)}; --color-references-details-updated-text: var(--color-blue-black); --color-references-group-origin-text: var(--color-blue-black); --color-references-request-target-url-background: var(--color-base); --color-references-request-target-url-border-default: #{color.adjust($color-grey-light, $lightness: -8%)}; --color-references-request-target-url-border-hover: #{color.adjust($color-grey, $lightness: -16%)}; --color-references-request-target-url-border-active: #{color.adjust($color-grey, $lightness: -24%)}; --color-references-request-format-type-text: var(--color-blue-black); --color-references-request-format-required-text: #{$color-references-request-format-required}; --color-references-request-format-optional-text: #{$color-references-request-format-optional}; --color-references-request-format-label-text: #{$color-references-request-format-label}; --color-references-request-format-keys-depth-border-zero: #{color.adjust($color-grey-light, $lightness: 10%)}; --color-references-request-format-keys-depth-border-one: #{color.adjust($color-grey-light, $lightness: 5%)}; --color-references-request-format-keys-depth-border-two: var(--color-grey-light); --color-references-request-format-keys-depth-border-three: #{color.adjust($color-grey-light, $lightness: -5%)}; --color-references-request-format-keys-depth-border-four: #{color.adjust($color-grey-light, $lightness: -10%)}; --color-references-request-format-keys-depth-border-infinity: #{color.adjust($color-grey-light, $lightness: -15%)}; --color-references-request-format-toggle-count-text: #{color.adjust($color-black, $lightness: 12.5%)}; --color-references-request-format-toggle-count-background: #{color.adjust($color-grey-light, $lightness: 2%)}; --color-references-request-format-toggle-button-border: #{color.adjust($color-grey-dark, $lightness: -8%)}; --color-references-request-format-toggle-button-shadow: #{rgba($color-grey-dark, .25)}; --color-references-examples-selection: #{rgba($color-selection-code, .2)}; --color-references-examples-detail-value-text: #{$color-references-examples-detail-value}; --color-references-examples-detail-segment-text: #{$color-references-examples-detail-segment}; --color-references-examples-detail-parameter-text: #{$color-references-examples-detail-parameter}; // Changes --color-changes-notice-background: var(--color-base); --color-changes-navigate-link-background-hover: #{color.adjust($color-grey-light, $lightness: 6%)}; --color-changes-navigate-link-background-active: #{color.adjust($color-grey-light, $lightness: 4%)}; --color-changes-month-event-border: #{rgba($color-grey, .65)}; --color-changes-month-event-group-text: var(--color-blue-grey-dark); --color-changes-month-event-deprecated-background: #{$color-changes-month-event-deprecated}; &[data-appearance="dark"] { // Overrides (Dark Mode) $color-white: #10131c; $color-black: #fff; $color-grey: #474b5e; $color-grey-light: #4c4f62; $color-grey-dark: #3e4255; $color-blue-black: #cdd3db; $color-blue-grey: #a3a5ae; $color-blue-grey-light: $color-grey; $color-blue-grey-dark: #71757d; $color-green: #21fb52; $color-red: #ff7e7e; $color-base: #1e202b; $color-base-background: #191c25; $color-markdown-code-block: #2a2d39; $color-markdown-blockquote: #516071; $color-markdown-emphasis-warning-light: #ffb300; $color-markdown-emphasis-warning-dark: #c87c00; $color-markdown-emphasis-info-light: #ffef00; $color-markdown-emphasis-info-dark: #c4b500; $color-markdown-emphasis-notice-light: #87ff73; $color-markdown-emphasis-notice-dark: #30bb17; $color-code-header-light: #16171c; $color-code-header-dark: #121317; $color-code-metas: #1e1f24; $color-code-content: #24252b; $color-search-lock: #434959; $color-header-coloring-background: #5161b7; $color-header-coloring-toggle-border: $color-header-coloring-background; $color-content-sidebar-right: #292a31; $color-references-request-format-required: #ff6d5a; $color-references-request-format-optional: #8f99a6; $color-references-request-format-label: #8d98a4; $color-changes-month-event-deprecated: #ff7b98; // Palette (Dark Mode) --color-white: #{$color-white}; --color-black: #{$color-black}; --color-grey: #{$color-grey}; --color-grey-light: #{$color-grey-light}; --color-grey-dark: #{$color-grey-dark}; --color-blue-black: #{$color-blue-black}; --color-blue-grey: #{$color-blue-grey}; --color-blue-grey-light: #{$color-blue-grey-light}; --color-blue-grey-dark: #{$color-blue-grey-dark}; --color-green: #{$color-green}; --color-red: #{$color-red}; // Base (Dark Mode) --color-base: #{$color-base}; --color-base-background: #{$color-base-background}; // Selection (Dark Mode) --color-selection-default: #{rgba($color-black, .25)}; // Badges (Dark Mode) --color-badge-black: #{color.adjust($color-badge-black, $lightness: 10%)}; --color-badge-blue: #{color.adjust($color-badge-blue, $lightness: 10%)}; --color-badge-green: #{color.adjust($color-badge-green, $lightness: 10%)}; --color-badge-yellow: #{color.adjust($color-badge-yellow, $lightness: 10%)}; --color-badge-red: #{color.adjust($color-badge-red, $lightness: 10%)}; // Methods (Dark Mode) --color-method-get-head-light: #{rgba($color-method-get-head-light, .3)}; --color-method-get-head-dark: #{color.adjust($color-method-get-head-dark, $lightness: 6%)}; --color-method-post-light: #{rgba($color-method-post-light, .3)}; --color-method-post-dark: #{color.adjust($color-method-post-dark, $lightness: 6%)}; --color-method-put-patch-light: #{rgba($color-method-put-patch-light, .3)}; --color-method-put-patch-dark: #{color.adjust($color-method-put-patch-dark, $lightness: 6%)}; --color-method-delete-light: #{rgba($color-method-delete-light, .3)}; --color-method-delete-dark: #{color.adjust($color-method-delete-dark, $lightness: 6%)}; // Viewer (Dark Mode) --color-viewer-panes-footer-border: #{rgba($color-black, .1)}; // Markdown (Dark Mode) --color-markdown-code-inline-background: #{rgba($color-black, .15)}; --color-markdown-code-inline-border: #{rgba($color-black, .35)}; --color-markdown-code-inline-text: #{$color-black}; --color-markdown-figure-border: #{rgba($color-grey, .9)}; --color-markdown-table-body-row-background-hover: #{rgba($color-blue-grey-light, .12)}; --color-markdown-table-cell-border: var(--color-grey); --color-markdown-blockquote-border: #{$color-markdown-blockquote}; --color-markdown-emphasis-warning-background: #{rgba($color-markdown-emphasis-warning-light, .25)}; --color-markdown-emphasis-warning-border: #{$color-markdown-emphasis-warning-dark}; --color-markdown-emphasis-info-background: #{rgba($color-markdown-emphasis-info-light, .25)}; --color-markdown-emphasis-info-border: #{$color-markdown-emphasis-info-dark}; --color-markdown-emphasis-notice-background: #{rgba($color-markdown-emphasis-notice-light, .25)}; --color-markdown-emphasis-notice-border: #{$color-markdown-emphasis-notice-dark}; // Code (Dark Mode) --color-code-header-default: #{$color-code-header-light}; --color-code-header-hover: #{$color-code-header-dark}; --color-code-metas-background: #{$color-code-metas}; --color-code-content-background: #{$color-code-content}; // Search (Dark Mode) --color-search-lock-background: #{rgba($color-search-lock, .4)}; --color-search-spotlight-field-background: #{rgba($color-base, .65)}; // Header (Dark Mode) --color-header-background: #{rgba($color-white, .9)}; --color-header-border: #{rgba($color-black, .075)}; --color-header-dropdown-border: #{rgba($color-grey, .65)}; --color-header-search-background-clear: var(--color-header-search-background-default); --color-header-search-border-hover: #{color.adjust($color-grey-dark, $lightness: 10%)}; --color-header-search-border-active: #{color.adjust($color-grey-dark, $lightness: 14%)}; --color-header-search-border-clear-default: var(--color-header-search-border-default); --color-header-search-border-clear-hover: var(--color-header-search-border-hover); --color-header-search-border-clear-active: var(--color-header-search-border-active); --color-header-search-shadow: var(--color-white); --color-header-search-shortcut-text: var(--color-blue-grey-dark); --color-header-search-shortcut-border: var(--color-blue-grey-dark); --color-header-search-shortcut-shadow: var(--color-white); --color-header-coloring-background-default: #{color.adjust($color-header-coloring-background, $lightness: -16%)}; --color-header-coloring-background-clear: var(--color-header-coloring-background-default); --color-header-coloring-toggle-border-default: #{rgba($color-header-coloring-toggle-border, .85)}; --color-header-coloring-toggle-border-hover: #{$color-header-coloring-toggle-border}; --color-header-coloring-toggle-border-active: #{rgba($color-header-coloring-toggle-border, .95)}; // Content (Dark Mode) --color-content-navigation-background: var(--color-base); --color-content-navigation-border-default: var(--color-grey); --color-content-navigation-border-active: #{color.adjust($color-grey, $lightness: 6%)}; --color-content-navigation-shadow-outset: #{rgba($color-white, .25)}; --color-content-navigation-shadow-inset: #{rgba($color-black, .05)}; --color-content-navigation-shadow-active: #{rgba($color-white, .4)}; --color-content-sidebar-left-background-default: var(--color-base); --color-content-sidebar-left-background-small: var(--color-base); --color-content-sidebar-right-background: #{$color-content-sidebar-right}; --color-content-sidebar-toggler-background: #{rgba($color-base, .9)}; --color-content-sidebar-nest-navigate-link-background-hover: #{color.adjust($color-grey-light, $lightness: -3%)}; --color-content-sidebar-nest-navigate-link-background-active: #{color.adjust($color-grey-light, $lightness: -1%)}; --color-content-sidebar-nest-navigate-link-default-toggle-background-hover: #{color.adjust($color-grey-light, $lightness: 3%)}; --color-content-sidebar-nest-navigate-link-default-toggle-background-active: #{color.adjust($color-grey-light, $lightness: 7%)}; // Guides (Dark Mode) --color-guides-details-border: #{rgba($color-grey, .45)}; // References (Dark Mode) --color-references-separator-content: #{rgba($color-grey, .45)}; --color-references-separator-sidebar: #{rgba($color-grey, .35)}; --color-references-request-target-url-border-default: transparent; --color-references-request-target-url-border-hover: #{rgba($color-black, .45)}; --color-references-request-target-url-border-active: #{rgba($color-black, .6)}; --color-references-request-format-required-text: #{$color-references-request-format-required}; --color-references-request-format-optional-text: #{$color-references-request-format-optional}; --color-references-request-format-label-text: #{$color-references-request-format-label}; --color-references-request-format-keys-depth-border-zero: #{color.adjust($color-grey-light, $lightness: -10%)}; --color-references-request-format-keys-depth-border-one: #{color.adjust($color-grey-light, $lightness: -5%)}; --color-references-request-format-keys-depth-border-three: #{color.adjust($color-grey-light, $lightness: 5%)}; --color-references-request-format-keys-depth-border-four: #{color.adjust($color-grey-light, $lightness: 10%)}; --color-references-request-format-keys-depth-border-infinity: #{color.adjust($color-grey-light, $lightness: 15%)}; --color-references-request-format-toggle-count-text: #{color.adjust($color-black, $lightness: -12.5%)}; --color-references-request-format-toggle-count-background: #{color.adjust($color-grey-light, $lightness: -2%)}; --color-references-request-format-toggle-button-border: #{color.adjust($color-grey-dark, $lightness: 8%)}; --color-references-request-format-toggle-button-shadow: #{rgba($color-white, .25)}; // Changes (Dark Mode) --color-changes-navigate-link-background-hover: #{color.adjust($color-grey-light, $lightness: -6%)}; --color-changes-navigate-link-background-active: #{color.adjust($color-grey-light, $lightness: -4%)}; --color-changes-month-event-border: #{rgba($color-grey, .65)}; --color-changes-month-event-deprecated-background: #{rgba($color-changes-month-event-deprecated, .3)}; // Images (Dark Mode) .logo { .logo-image { filter: grayscale(1) invert(1) brightness(10); } } .illustration { filter: saturate(.15) invert(1); } } } ================================================ FILE: src/stylesheets/common/_badges.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; .badge { border: 1px solid var(--color-badge-black); color: var(--color-badge-black); user-select: none; text-transform: uppercase; line-height: $badge-default-height; height: $badge-default-height; padding: 0 5px; display: inline-block; border-radius: 1px; @include font-size(10.5px); &.badge--special-value { background-color: var(--color-badge-special-background); border-color: var(--color-badge-special-border); color: var(--color-badge-special-text); user-select: text; text-transform: lowercase; line-height: $badge-special-value-height; height: $badge-special-value-height; border-radius: 2px; @include font-size(11.5px); } &.badge--small { line-height: $badge-small-height; height: $badge-small-height; padding: 0 3px; @include font-size(10px); } &.badge--white { border-color: var(--color-badge-white); color: var(--color-badge-white); } &.badge--blue { border-color: var(--color-badge-blue); color: var(--color-badge-blue); } &.badge--green { border-color: var(--color-badge-green); color: var(--color-badge-green); } &.badge--yellow { border-color: var(--color-badge-yellow); color: var(--color-badge-yellow); } &.badge--red { border-color: var(--color-badge-red); color: var(--color-badge-red); } } ================================================ FILE: src/stylesheets/common/_base.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; body { font-family: "Chappe Proxima Nova Regular", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: $font-size-base; background: var(--color-base-background) !important; color: var(--color-black); } * { font-weight: normal !important; margin: 0; padding: 0; @include font-smoothing(subpixel-antialiased); } .wrapper { padding: 0 $wrapper-default-padding-sides; } a { text-decoration: none; } @include selection { background-color: var(--color-selection-default); } .text-ellipsis { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } .title-group-anchor:hover .title-anchor, .title-anchor:hover { text-decoration: underline !important; text-decoration-color: var(--color-grey-dark) !important; } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-medium-width-breakpoint) { .wrapper { padding-left: $wrapper-medium-padding-sides; padding-right: $wrapper-medium-padding-sides; } } @media screen and (max-width: $screen-small-width-breakpoint) { .wrapper { padding-left: $wrapper-small-padding-sides; padding-right: $wrapper-small-padding-sides; } } @media screen and (max-width: $screen-tiny-width-breakpoint) { .wrapper { padding-left: $wrapper-tiny-padding-sides; padding-right: $wrapper-tiny-padding-sides; } } @media screen and (max-width: $screen-lilliput-width-breakpoint) { .wrapper { padding-left: $wrapper-lilliput-padding-sides; padding-right: $wrapper-lilliput-padding-sides; } } ================================================ FILE: src/stylesheets/common/_buttons.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; .button { color: var(--color-white); background-color: var(--color-button-default); user-select: none; line-height: $button-default-height; height: $button-default-height; padding: 0 20px; display: inline-block; box-shadow: 0 1px 0 0 var(--color-button-shadow); border-radius: 3px; transition: all linear 100ms; transition-property: background-color, color, transform, box-shadow; @include font-size(13px); &:hover, &:active { background-color: var(--color-button-active); } &:active { transform: translateY(1px); box-shadow: none; } &.button--icon { display: inline-flex; align-items: center; &::before, &::after { flex: 0 0 auto; } .button-text { flex: 1; } } &.button--reverse { color: var(--color-button-default); background-color: var(--color-white); border: 1px solid var(--color-button-default); transition-duration: 75ms; &:hover { color: var(--color-white); background-color: var(--color-button-default); } &.button--large { border-width: 1.5px; } } &.button--small { line-height: $button-small-height; height: $button-small-height; } &.button--large { padding: 0 24px; @include font-size(14px); } &, .button-text { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } } ================================================ FILE: src/stylesheets/common/_code.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; .code { border: 1px solid transparent; margin: 24px; user-select: none; overflow: hidden; border-radius: 6px; transition: border-color linear 75ms; &[data-copy-state="copied"] { .code-header { .code-actions { .code-action { &.code-action--copy { width: 13px; height: 16px; margin-top: 1px; opacity: 1; @include mask-image-internal("common/code/header-action-copied.svg"); } } } } } &[data-has-request="false"] { .code-header { .code-actions { .code-action { &.code-action--copy { display: none; } } } } } &:hover { border-color: var(--color-code-border-hover); .code-header { background-color: var(--color-code-header-hover); } } .code-header, .code-section .code-metas, .code-section .code-content { padding: 0 14px; } .code-data { display: none; } .code-header { background-color: var(--color-code-header-default); height: 40px; display: flex; transition: background-color linear 75ms; .code-language, .code-actions { display: flex; align-items: center; } .code-language { color: var(--color-code-language-text); flex: 1; @include font-size(12.5px); } .code-actions { flex: 0 0 auto; padding-left: 8px; .code-action { margin-right: 15px; opacity: .65; cursor: pointer; transition: opacity linear 100ms; @include mask-image-fill; &:last-of-type { margin-right: 0; } &:hover { opacity: .85; } &:active { opacity: 1; } &.code-action--copy { background-color: var(--color-code-action-background); width: 13px; height: 15px; @include mask-image-internal("common/code/header-action-copy.svg"); } } } } .code-section { .code-metas { background-color: var(--color-code-metas-background); line-height: 16px; height: 33px; display: flex; flex-direction: row; align-items: center; .code-meta-name, .code-meta-picker, .code-meta-type { margin-top: -1px; } .code-meta-name, .code-meta-type { letter-spacing: .1px; min-width: 80px; flex: 0 0 auto; @include font-size(12.5px); } .code-meta-name, .code-meta-picker { border-right: 1px solid var(--color-code-meta-name-border); } .code-meta-name { color: var(--color-code-meta-name-text); text-align: left; padding-right: 8px; margin-right: 10px; } .code-meta-picker { flex: 1; padding-right: 10px; margin-right: 8px; } .code-meta-type { color: var(--color-code-meta-type-text); text-align: right; } } .code-content { background-color: var(--color-code-content-background); color: var(--color-code-content-text); line-height: 16px; white-space: pre; user-select: none; overflow: auto; max-height: 640px; padding-top: 10px; padding-bottom: 12px; @include font-size(11px); code { color: var(--color-white); user-select: text; } } @include selection { background-color: var(--color-code-selection); } } select { $select-padding-sides: 8px; background: transparent; color: var(--color-code-select-text-default); border: 1px solid var(--color-code-select-border-default); background-repeat: no-repeat; background-position-x: calc(100% - 8px); background-position-y: 50%; line-height: 23px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; width: 100%; padding-left: $select-padding-sides; padding-right: ($select-padding-sides + 8px); outline: none; cursor: pointer; border-radius: 3px; transition: all linear 75ms; transition-property: color, border-color; @include background-image-internal("common/code/select-arrow.svg"); @include font-size(13px); @include appearance(none); &:hover { color: var(--color-code-select-text-hover); border-color: var(--color-code-select-border-hover); option { color: initial; } } option { color: initial; } } } ================================================ FILE: src/stylesheets/common/_content.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #content { background-color: var(--color-white); min-height: 120px; padding-top: $header-height-outer; .main { .main-title, .main-label { display: block; margin: 0 auto; } .main-title { color: var(--color-black); letter-spacing: 0; text-align: center; @include font-size(28px); } .main-label { color: var(--color-blue-grey); line-height: 22px; letter-spacing: 0; text-align: center; max-width: 340px; margin-top: 16px; @include font-size(17px); .main-label-line { display: block; } } } .panes { min-height: 100vh; margin-top: (-1 * $header-height-outer); display: flex; flex-direction: row; .content, .sidebar { overflow: hidden; min-height: 200px; padding-bottom: 100px; } .content { flex: 1; padding-top: ($header-height-outer + 34px); &.content--padded { padding-left: 36px; padding-right: $wrapper-default-padding-sides; } .content-wrap { width: 100%; max-width: $content-wrap-max-width; margin: 0 auto; &.content-wrap--full { max-width: none; } } [id] { &:not([data-footnote-ref], .footnotes *) { $anchor-offset-top: ($header-height-outer + 16px); pointer-events: none; &::before { content: ""; width: 0; height: $anchor-offset-top; margin-top: (-1 * $anchor-offset-top); display: block; position: relative; } a { &[href^="#"] { pointer-events: all; } } } } } .sidebar { $sidebar-item-padding-left-semi: 26px; $sidebar-item-padding-right-semi: 14px; $sidebar-item-icon-margin-right: 6px; $sidebar-item-icon-size: ($sidebar-item-padding-left-semi - $sidebar-item-icon-margin-right); flex: 0 0 auto; position: relative; &.sidebar--left { background-color: var(--color-content-sidebar-left-background-default); width: $content-sidebar-left-width; padding-top: $header-height-outer; padding-bottom: 30px; overflow: hidden scroll; position: fixed; left: 0; top: 0; bottom: 0; } &.sidebar--right { background-color: var(--color-content-sidebar-right-background); width: $content-sidebar-right-default-width; } .sidebar-toggler-retract { background-color: var(--color-black); width: 11px; height: 11px; margin-right: ($sidebar-item-padding-right-semi + 2px); cursor: pointer; opacity: .65; position: absolute; top: 0; right: 0; display: none; transition: opacity linear 100ms; @include mask-image-internal-fill("common/content/sidebar-toggler-retract.svg", $display: false); &:hover { opacity: 1; } } .context { border-bottom: 1px solid var(--color-grey); padding: 16px $sidebar-item-padding-right-semi 16px $wrapper-default-padding-sides; .context-name { color: var(--color-black); line-height: 18px; letter-spacing: -.1px; @include font-size(15px); .context-icon { vertical-align: middle; width: $sidebar-item-icon-size; height: $sidebar-item-icon-size; margin-left: (-1 * $sidebar-item-padding-left-semi); margin-right: $sidebar-item-icon-margin-right; margin-top: -2px; display: inline-block; } } } .nest { user-select: none; padding: 24px $sidebar-item-padding-right-semi 0 $wrapper-default-padding-sides; &:last-of-type { padding-bottom: 10px; } .nest-category { letter-spacing: -.1px; @include font-size(15px); &, .nest-category-link { color: var(--color-black); } &.nest-category--active { &, .nest-category-link { color: var(--color-accent-base); } } .nest-icon { vertical-align: middle; width: $sidebar-item-icon-size; height: $sidebar-item-icon-size; margin-top: -3px; margin-left: (-1 * $sidebar-item-padding-left-semi); margin-right: $sidebar-item-icon-margin-right; display: inline-block; } } .nest-navigate { margin-top: 10px; .nest-navigate-level { $level-item-margin-bottom-base: 3px; position: relative; &[data-expanded="false"] { .nest-navigate-link { &.nest-navigate-link--slice { .nest-navigate-toggle { &::after { transform: rotate(-90deg); } } } } .nest-navigate-level { &.nest-navigate-level--second { display: none; } } } &.nest-navigate-level--first { margin-bottom: $level-item-margin-bottom-base; } &.nest-navigate-level--second { $level-item-second-padding-left-base: 13px; margin-top: 4px; margin-bottom: 10px; padding-left: $level-item-second-padding-left-base; &::before { content: ""; background-color: var(--color-grey-light); width: 2px; position: absolute; top: 0; bottom: 0; left: 0; z-index: 2; } .nest-navigate-link { margin-bottom: $level-item-margin-bottom-base; margin-left: (-1 * $level-item-second-padding-left-base); padding-left: $level-item-second-padding-left-base; border-top-left-radius: 0; border-bottom-left-radius: 0; position: relative; z-index: 1; &[data-active="true"] { z-index: 3; } } } .nest-navigate-link { $link-item-padding-top-bottom: 3px; $link-item-padding-right: 7px; letter-spacing: -.1px; line-height: 17px; margin-left: (-1 * $sidebar-item-padding-left-semi); padding: $link-item-padding-top-bottom $link-item-padding-right $link-item-padding-top-bottom $sidebar-item-padding-left-semi; display: flex; align-items: center; border-radius: 3px; transition: all linear 75ms; transition-property: background-color, color; @include font-size(13.5px); &:hover { background-color: var(--color-content-sidebar-nest-navigate-link-background-hover); } &:active { background-color: var(--color-content-sidebar-nest-navigate-link-background-active); } &, .nest-navigate-slice { color: var(--color-content-sidebar-nest-navigate-slice-text); } &[data-active="true"] { background-color: var(--color-accent-base); &, .nest-navigate-slice { color: var(--color-white); } .nest-navigate-link-text { font-family: "Chappe Proxima Nova Semibold", sans-serif; } .nest-navigate-link-objects { .badge { border-color: var(--color-white); color: var(--color-white); } } } &[data-starred="true"] { .nest-navigate-link-text { font-family: "Chappe Proxima Nova Semibold", sans-serif; } } &.nest-navigate-link--external { color: var(--color-accent-base); &::after { content: ""; background-color: var(--color-accent-base); width: 11px; height: 11px; margin-left: 6px; @include mask-image-internal-fill("common/content/sidebar-link-external-icon.svg"); } } &.nest-navigate-link--slice { align-items: center; display: flex; .nest-navigate-toggle { cursor: pointer; margin-left: 3px; margin-right: (-1 * ($link-item-padding-right - $link-item-padding-top-bottom)); padding: 0 5px; flex: 0 0 auto; border-radius: 2px; transition: background-color linear 75ms; &::after { content: ""; background-color: var(--color-content-sidebar-nest-navigate-slice-icon-background-default); vertical-align: middle; width: 9px; height: 6px; margin-top: -1px; opacity: .5; transition: all linear 100ms; transition-property: opacity, transform; @include mask-image-internal-fill("common/content/sidebar-link-slice-icon.svg"); } &:hover { background-color: var(--color-content-sidebar-nest-navigate-link-default-toggle-background-hover); &::after { opacity: 1; } } &:active { background-color: var(--color-content-sidebar-nest-navigate-link-default-toggle-background-active); } } &[data-active="true"] { .nest-navigate-toggle { &::after { background-color: var(--color-content-sidebar-nest-navigate-slice-icon-background-active); } &:hover { background-color: var(--color-content-sidebar-nest-navigate-link-active-toggle-background-hover); } &:active { background-color: var(--color-content-sidebar-nest-navigate-link-active-toggle-background-active); } } } .nest-navigate-slice { flex: 1; } } .nest-navigate-link-text { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; flex: 1; } .nest-navigate-link-objects { margin-left: 4px; display: flex; align-items: center; } } } } } } .sidebar-toggler { $sidebar-toggler-size: 36px; background-color: var(--color-content-sidebar-toggler-background); line-height: $sidebar-toggler-size; text-align: center; width: $sidebar-toggler-size; height: $sidebar-toggler-size; margin-left: $wrapper-default-padding-sides; cursor: pointer; display: none; position: fixed; top: ($header-height + $header-border-size); left: 0; z-index: 50; box-shadow: 0 1px 5px 0 var(--color-content-sidebar-toggler-shadow-default); border-radius: 100%; transition: box-shadow linear 150ms; &:hover { box-shadow: 0 2px 6px 0 var(--color-content-sidebar-toggler-shadow-hover); &::after { opacity: .9; } } &:active { box-shadow: 0 1px 3px 0 var(--color-content-sidebar-toggler-shadow-active); &::after { opacity: 1; } } &::after { content: ""; background-color: var(--color-content-sidebar-toggler-icon-background); vertical-align: middle; width: 16px; height: 16px; margin-top: -1px; opacity: .7; transition: opacity linear 150ms; @include mask-image-internal-fill("common/content/sidebar-toggler-icon.svg"); } @include backdrop-filter(blur(4px) saturate(160%) contrast(45%) brightness(140%)); } } .bulletpoints .bulletpoint, .navigation .navigation-link { background-color: var(--color-content-navigation-background); border: 1px solid var(--color-content-navigation-border-default); text-align: left; box-shadow: 0 2px 1px 0 var(--color-content-navigation-shadow-outset), inset 0 1px 0 0 var(--color-content-navigation-shadow-inset); border-radius: 2px; transition: all linear 100ms; transition-property: border-color, box-shadow, transform; } .bulletpoints { display: grid; gap: 18px 24px; grid-template-columns: 1fr 1fr 1fr; .bulletpoint { padding: 20px 30px 18px 26px; &::before { content: ""; background-color: var(--color-accent-base); height: 32px; width: 32px; margin-bottom: 16px; @include mask-image-fill; } .bulletpoint-title { color: var(--color-black); @include font-size(18.5px); } .bulletpoint-label { color: var(--color-content-bulletpoint-label-text); line-height: 18px; margin-top: 9px; @include font-size(14px); } .bulletpoint-actions { margin-top: 18px; margin-bottom: -1px; .bulletpoint-action { color: var(--color-accent-base); line-height: 18px; margin-right: 22px; @include font-size(14px); &:last-of-type { margin-right: 0; } &:hover { text-decoration: underline; } &.bulletpoint-action--single { &:hover { &::after { transform: translateX(1px); } } &, &:active { &::after { transform: translateX(0); } } &::after { content: ""; background-color: var(--color-accent-base); width: 13px; height: 9px; margin-left: 4px; vertical-align: middle; transition: transform linear 100ms; @include mask-image-internal-fill("common/content/bulletpoint-action-arrow.svg"); } } } } } } .navigation { user-select: none; margin-top: 20px; display: grid; gap: 7px 18px; grid-template-columns: 1fr 1fr; .navigation-link { &:hover { border-color: var(--color-content-navigation-border-active); } &:active { transform: translateY(1px); box-shadow: 0 1px 1px 0 var(--color-content-navigation-shadow-active); } } .navigation-item { margin: 0; padding: 0 0 3px; overflow: hidden; &::before { display: none; } .navigation-link { padding: 10px 14px 10px 20px; text-decoration: none; display: flex; align-items: center; &:hover { .navigation-action { text-decoration: underline; &::after { transform: translateX(1px); } } } &, &:active { .navigation-action { &::after { transform: translateX(0); } } } .navigation-text { flex: 1; &, .navigation-title, .navigation-label { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; margin: 0; } .navigation-title, .navigation-label { display: block; } .navigation-title { color: var(--color-black); line-height: 20px; @include font-size(14.5px); } .navigation-label { color: var(--color-content-navigation-label-text); line-height: 18px; margin-top: 3px; @include font-size(13.5px); } } .navigation-action { color: var(--color-accent-base); margin-left: 8px; flex: 0 0 auto; @include font-size(13.5px); &::after { content: ""; background-color: var(--color-accent-base); width: 11px; height: 8px; margin-top: -1px; margin-left: 4px; vertical-align: middle; transition: transform linear 100ms; @include mask-image-internal-fill("common/content/navigation-item-link-arrow.svg"); } } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-large-width-breakpoint) { #content { .panes { .sidebar { &.sidebar--right { width: $content-sidebar-right-large-width; } } } } } @media screen and (max-width: $screen-medium-width-breakpoint) { #content { .panes { .content { &.content--padded { padding-left: 28px; padding-right: $wrapper-medium-padding-sides; } } .sidebar { &.sidebar--right { display: none; } } .sidebar-toggler { margin-left: $wrapper-medium-padding-sides; } } .bulletpoints { column-gap: 16px; } } } @media screen and (max-width: $screen-small-width-breakpoint) { #content { .panes { .content { &.content--padded { padding-left: $wrapper-small-padding-sides; padding-right: $wrapper-small-padding-sides; } } .sidebar { &.sidebar--left { background-color: var(--color-content-sidebar-left-background-small); padding-top: 20px; padding-bottom: 20px; overflow: hidden auto; position: fixed; top: ($header-height + $header-border-size); bottom: 0; left: (-1 * ($content-sidebar-left-width + 10px)); z-index: 75; transition: left ease 200ms; box-shadow: 1px 0 5px 0 var(--color-content-sidebar-left-shadow-small); &[data-visible="true"] { left: 0; } .sidebar-toggler-retract { margin-top: 11px; display: inline-block; } @include backdrop-filter(blur(4px) saturate(160%) contrast(45%) brightness(140%)); } .nest { &:last-of-type { padding-bottom: 40px; } } } .sidebar-toggler { margin-left: $wrapper-small-padding-sides; display: block; } } .bulletpoints { row-gap: 14px; grid-template-columns: 1fr 1fr; } .navigation { row-gap: 5px; grid-template-columns: 1fr; } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #content { .panes { .content { padding-top: ($header-height-outer + 22px); padding-bottom: 74px; &.content--padded { padding-left: $wrapper-tiny-padding-sides; padding-right: $wrapper-tiny-padding-sides; } } .sidebar { &.sidebar--left { background-color: var(--color-content-sidebar-left-background-tiny); visibility: hidden; opacity: 0; width: auto; left: 0; right: 0; transition: opacity linear 150ms; box-shadow: none; &[data-visible="true"] { visibility: visible; opacity: 1; } @include backdrop-filter(none); } } .sidebar-toggler { margin-left: $wrapper-tiny-padding-sides; } } .bulletpoints { row-gap: 12px; grid-template-columns: 1fr; } } } @media screen and (max-width: $screen-lilliput-width-breakpoint) { #content { .panes { .content { &.content--padded { padding-left: $wrapper-lilliput-padding-sides; padding-right: $wrapper-lilliput-padding-sides; } } .sidebar-toggler { margin-left: $wrapper-lilliput-padding-sides; } } } } ================================================ FILE: src/stylesheets/common/_fonts.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; // - Proxima Nova // Regular @include font-face-include("Chappe Proxima Nova Regular", "proxima_nova/proxima_nova_regular", 400, normal); @include font-face-include("Chappe Proxima Nova Regular", "proxima_nova/proxima_nova_regular_italic", 400, italic); // Semibold @include font-face-include("Chappe Proxima Nova Semibold", "proxima_nova/proxima_nova_semibold", 600, normal); @include font-face-include("Chappe Proxima Nova Semibold", "proxima_nova/proxima_nova_semibold_italic", 600, italic); // Bold @include font-face-include("Chappe Proxima Nova Bold", "proxima_nova/proxima_nova_bold", 700, normal); @include font-face-include("Chappe Proxima Nova Bold", "proxima_nova/proxima_nova_bold_italic", 700, italic); // - Hack // Regular @include font-face-include("Chappe Hack Regular", "hack/hack_regular", 400, normal); // - Bindings // - Sans Serif .font-sans-regular { font-family: "Chappe Proxima Nova Regular", sans-serif; } .font-sans-semibold { font-family: "Chappe Proxima Nova Semibold", sans-serif; } .font-sans-bold { font-family: "Chappe Proxima Nova Bold", sans-serif; } .font-code-regular { font-family: "Chappe Hack Regular", sans-serif; } ================================================ FILE: src/stylesheets/common/_footer.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #footer { display: flex; flex-direction: row; .inner { border-top: 1px solid var(--color-grey); padding: 30px 0 40px; flex: 1; } .wrapper { .navigation, .metadata, .left, .right { height: 100%; display: flex; flex-direction: row; align-items: center; } .left { flex: 1; } .right { flex: 0 0 auto; padding-left: 12px; } } .navigation { .links .link .link-target, .copyright { letter-spacing: -.1px; @include font-size(13.5px); } .links { user-select: none; .link { margin-right: 20px; display: inline-block; &:last-of-type { margin-right: 0; } .link-target { color: var(--color-footer-link-target-text); &:hover { text-decoration: underline; } } } } .copyright { color: var(--color-footer-copyright-text); } } .metadata { margin-top: 38px; .brand { display: flex; flex-direction: row; align-items: center; .logo { height: 26px; @include background-image-fill; } .separator { background-color: var(--color-footer-brand-separator); width: 1px; height: 22px; margin: -1px 20px 0; } .engine { letter-spacing: -.11px; @include font-size(14px); &, a { color: var(--color-footer-engine-text); } a { text-decoration: underline; } } } .status { visibility: visible; opacity: 1; transition: opacity linear 100ms; &[data-status="none"] { visibility: hidden; opacity: 0; } &[data-status="none"], &[data-status="failure"] { .status-wrapped { .status-text { .status-state { .status-state-variant { &.status-state-variant--failure { display: block; } } } } } } &[data-status="healthy"] { .status-wrapped { .status-icon { @include background-image-internal("common/footer/status-healthy.svg"); } .status-text { .status-state { color: var(--color-status-healthy-text); .status-state-variant { &.status-state-variant--healthy { display: block; } } } } } } &[data-status="sick"] { .status-wrapped { .status-icon { @include background-image-internal("common/footer/status-sick.svg"); } .status-text { .status-state { color: var(--color-status-sick-text); .status-state-variant { &.status-state-variant--sick { display: block; } } } } } } &[data-status="dead"] { .status-wrapped { .status-icon { @include background-image-internal("common/footer/status-dead.svg"); } .status-text { .status-state { color: var(--color-status-dead-text); .status-state-variant { &.status-state-variant--dead { display: block; } } } } } } .status-wrapped { border: 1px solid var(--color-footer-status-border-default); user-select: none; padding: 5px 30px 5px 18px; border-radius: 1px; display: flex; flex-direction: row; align-items: center; transition: border-color linear 100ms; &:hover { border-color: var(--color-footer-status-border-hover); cursor: pointer; } .status-icon { width: 24px; height: 24px; @include background-image-internal-fill("common/footer/status-failure.svg"); } .status-text { padding-left: 18px; .status-state, .status-time { display: block; } .status-state { color: var(--color-status-default-text); letter-spacing: 0; line-height: 20px; @include font-size(13.5px); .status-state-variant { display: none; } } .status-time { color: var(--color-footer-status-time-text); letter-spacing: -.1px; line-height: 18px; @include font-size(12.5px); } } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-medium-width-breakpoint) { #footer { .wrapper { .navigation, .metadata { display: block; } .navigation { .left, .right { text-align: center; padding: 0; display: block; } .right { margin-top: 20px; } .links { line-height: 19px; .link { margin: 0 8px; } } } .metadata { margin-top: 28px; .left, .right { justify-content: center; padding: 0; } .right { margin-top: 36px; } } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #footer { .metadata { .brand { flex-direction: column; .separator { display: none; } .engine { line-height: 18px; text-align: center; margin-top: 24px; } } } } } ================================================ FILE: src/stylesheets/common/_header.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #header { background-color: var(--color-header-background); border-bottom: $header-border-size solid var(--color-header-border); height: $header-height; position: fixed; top: 0; left: 0; right: 0; z-index: 100; box-shadow: 0 2px 6px 0 var(--color-header-shadow); @include backdrop-filter(blur(4px) saturate(160%) contrast(45%) brightness(140%)); .wrapper { &, .left, .right { height: 100%; display: flex; flex-direction: row; } .left, .right { align-items: center; } .left { flex: 1; } .right { flex: 0 0 auto; padding-left: 12px; } } .logo { color: var(--color-black); margin-right: 60px; .logo-image { height: 24px; @include background-image-fill; &.logo-image--full { display: block; } &.logo-image--short { display: none; } } } .menu, .extras { height: 100%; display: flex; align-items: center; flex-direction: row; } .menu .menu-item, .extras .extras-item { user-select: none; height: 100%; padding: 0 2px; display: flex; align-items: center; flex-direction: row; position: relative; &::after { content: ""; background-color: var(--color-grey); height: 0; position: absolute; left: 0; right: 0; bottom: -1px; transition: none; } } .menu .menu-item .menu-link, .extras .extras-item .extras-link { height: 100%; display: flex; align-items: center; } .menu .menu-item:hover, .menu .menu-item.menu-item--active, .extras .extras-item.extras-item--active { &::after { height: 3px; transition: height linear 100ms; } } .menu .menu-item.menu-item--active, .extras .extras-item.extras-item--active { &::after { background-color: var(--color-accent-base); } } .dropdown { $dropdown-padding-edges: 8px; $dropdown-link-padding-sides: 8px; background-color: var(--color-white); border: 1px solid var(--color-header-dropdown-border); padding: ($header-height - 15px) $dropdown-padding-edges $dropdown-padding-edges; display: none; position: absolute; z-index: 1; top: 7px; left: (-1 * ($dropdown-padding-edges + $dropdown-link-padding-sides)); box-shadow: 0 3px 5px 0 var(--color-header-dropdown-shadow); border-radius: 3px; .dropdown-link { color: var(--color-black); line-height: 28px; letter-spacing: -.1px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; margin-bottom: 2px; padding: 0 32px 0 ($dropdown-link-padding-sides + 1px); display: block; border-radius: 3px; @include font-size(13.5px); &:hover, &:active { color: var(--color-white); } &:hover { background-color: var(--color-header-dropdown-link-background-hover); &:active { transition: background-color linear 100ms; } } &:active { background-color: var(--color-header-dropdown-link-background-active); } &:last-of-type { margin-bottom: 0; } &.dropdown-link--multiple { line-height: 18px; padding-top: 6px; padding-bottom: 6px; .dropdown-link-label { margin-top: 1px; } } &.dropdown-link--external { padding-right: 42px; position: relative; &::after { $icon-size: 12px; content: ""; background-color: var(--color-black); width: $icon-size; height: $icon-size; margin-top: (-1 * ($icon-size * 0.5)); opacity: .3; position: absolute; right: ($dropdown-link-padding-sides + 1px); top: 50%; @include mask-image-internal-fill("common/header/actions-dropdown-external.svg"); } &:hover, &:active { &::after { background-color: var(--color-white); opacity: 1; } } } .dropdown-link-title, .dropdown-link-label { display: block; } .dropdown-link-label { opacity: .8; @include font-size(13px); } } } .menu { .menu-item { margin-right: 28px; position: relative; &:hover { .dropdown { display: block; } } &:last-of-type { margin-right: 0; } .menu-link { color: var(--color-black); @include font-size(14px); &.menu-link--dropdown { position: relative; z-index: 2; &::after { content: ""; background-color: var(--color-black); width: 8px; height: 5px; margin-top: 1px; margin-left: 5px; opacity: .35; @include mask-image-internal-fill("common/header/menu-arrow.svg"); } } } } } .extras { .extras-item { .extras-link { color: var(--color-accent-base); letter-spacing: -.09px; @include font-size(13px); } } } .search { background: var(--color-header-search-background-default); border: 1px solid var(--color-header-search-border-default); height: 32px; margin-left: 18px; padding: 0 10px 0 12px; display: flex; align-items: center; user-select: none; border-radius: 3px; box-shadow: 0 1px 0 0 var(--color-header-search-shadow); transition: border-color linear 100ms; &:hover { border-color: var(--color-header-search-border-hover); cursor: pointer; } &:active { border-color: var(--color-header-search-border-active); } .search-placeholder { color: var(--color-header-search-placeholder-text); letter-spacing: -.1px; @include font-size(13px); &::before { content: ""; background-color: var(--color-blue-grey-dark); width: 14px; height: 14px; margin-top: -2px; margin-right: 10px; vertical-align: middle; @include mask-image-internal-fill("common/header/search-icon.svg"); } } .search-shortcut { background: var(--color-white); border: 1px solid var(--color-header-search-shortcut-border); color: var(--color-header-search-shortcut-text); letter-spacing: -.2px; text-align: center; height: 20px; line-height: 21px; margin-left: 20px; padding: 0 5px; box-shadow: 0 1px 0 0 var(--color-header-search-shortcut-shadow); border-radius: 3px; @include font-size(12px); } } .coloring { $coloring-height: 16px; $coloring-width: 32px; $coloring-toggle-size: 24px; background-color: var(--color-header-coloring-background-default); width: $coloring-width; height: $coloring-height; margin: 0 32px; cursor: pointer; position: relative; border-radius: ($coloring-height * 0.5); &:hover { .toggle { border-color: var(--color-header-coloring-toggle-border-hover); box-shadow: 0 1px 4px 0 var(--color-header-coloring-toggle-shadow-hover); } } &:active { .toggle { border-color: var(--color-header-coloring-toggle-border-active); box-shadow: 0 1px 1px 0 var(--color-header-coloring-toggle-shadow-active); } } &[data-mode="light"] { .toggle { margin-left: (-1 * ($coloring-toggle-size * 0.25)); &::before { background-color: var(--color-header-coloring-toggle-icon-light-background); width: 14px; height: 14px; @include mask-image-internal("common/header/coloring-mode-light-icon.svg"); } } } &[data-mode="dark"] { .toggle { margin-left: ($coloring-width - (($coloring-toggle-size + 2px) - ($coloring-toggle-size * 0.25))); &::before { background-color: var(--color-header-coloring-toggle-icon-dark-background); width: 12px; height: 12px; @include mask-image-internal("common/header/coloring-mode-dark-icon.svg"); } } } .toggle { background-color: var(--color-white); border: 1px solid var(--color-header-coloring-toggle-border-default); height: $coloring-toggle-size; width: $coloring-toggle-size; margin-top: (-1 * (($coloring-toggle-size * 0.5) - ($coloring-height * 0.5) + 1px)); top: 0; left: 0; display: flex; align-items: center; justify-content: center; position: absolute; border-radius: 100%; box-shadow: 0 1px 3px 0 var(--color-header-coloring-toggle-shadow-default); transition: all linear 100ms; transition-property: border-color, margin-left, box-shadow; &::before { content: ""; @include mask-image-fill; } } } .actions { .action { position: relative; &:hover { .dropdown { display: block; } .action-button { &.button { background-color: transparent !important; color: var(--color-black) !important; box-shadow: none !important; transition: none !important; } &::after { background-color: var(--color-black); } } } .dropdown { left: auto; right: -5px; top: -5px; } .action-button { position: relative; z-index: 2; &:active { transform: none; } &::after { content: ""; background-color: var(--color-white); width: 8px; height: 5px; margin-left: 7px; margin-right: -3px; vertical-align: middle; opacity: .55; transition: opacity linear 100ms; @include mask-image-internal-fill("common/header/actions-arrow.svg"); } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-medium-width-breakpoint) { #header { .logo { margin-right: 42px; } .coloring { margin: 0 20px; } } } @media screen and (max-width: $screen-small-width-breakpoint) { #header { .logo { margin-right: 32px; } .menu { .menu-item { margin-right: 20px; &:last-of-type { margin-right: 0; } } } .search { margin-left: 12px; padding: 0 11px; .search-placeholder-text, .search-shortcut { display: none; } .search-placeholder { &::before { margin-right: 0; } } } .coloring { display: none; } .actions { margin-left: 10px; } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #header { .logo { margin-top: 1px; margin-right: 28px; .logo-image { &.logo-image--full { display: none; } &.logo-image--short { display: block; } } } .search { margin-left: 0; } .extras, .actions { display: none; } } } ================================================ FILE: src/stylesheets/common/_highlight.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou pre { code { &[class*="language-"] { color: var(--color-highlight-main); .namespace { opacity: .7; } .token.entity, .language-css .token.string, .style .token.string { color: var(--color-highlight-entity-light); background: var(--color-highlight-entity-dark); } .token { &.comment, &.prolog, &.cdata { color: var(--color-highlight-comment); } &.punctuation { color: var(--color-highlight-punctuation); } &.variable, &.tag, &.operator, &.deleted { color: var(--color-highlight-variable); } &.number, &.boolean, &.constant, &.url { color: var(--color-highlight-number); } &.class-name, &.bold { color: var(--color-highlight-class); } &.property, &.string, &.symbol, &.attr-value, &.inserted, &.atrule { color: var(--color-highlight-property); } &.regex, &.important { color: var(--color-highlight-regex); } &.function, &.attr-name { color: var(--color-highlight-function); } &.keyword, &.selector, &.italic, &.char, &.builtin { color: var(--color-highlight-keyword); } &.doctype { color: var(--color-highlight-doctype); } &.important, &.bold { font-weight: bold; } &.italic { font-style: italic; } &.entity { cursor: help; } } } } } ================================================ FILE: src/stylesheets/common/_markdown.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; .markdown { font-family: "Chappe Proxima Nova Regular", sans-serif; * { &:first-child { margin-top: 0 !important; } &:last-child { margin-bottom: 0 !important; } } h1, h2, h3 { font-family: "Chappe Proxima Nova Bold", sans-serif; } h4, h5, h6 { font-family: "Chappe Proxima Nova Semibold", sans-serif; } h1, h2, h3, h4, h5, h6 { a { font-family: inherit; color: inherit; } } h2, h3, h4, h5, h6 { margin-top: 20px; margin-bottom: 14px; } h3, h4, h5, h6 { color: var(--color-markdown-title-small-text); } h1, h2 { color: var(--color-black); } h1 { margin-top: 42px; margin-bottom: 20px; @include font-size(20px); } h2 { @include font-size(18px); } h3 { @include font-size(17.5px); } h4 { @include font-size(17px); } h5 { @include font-size(16.5px); } h6 { @include font-size(16px); } p, ul, ol, blockquote, .emphasis { line-height: 22px; @include font-size(14.5px); } p { margin-bottom: .9em; } a { color: var(--color-accent-base); &:hover { text-decoration: underline; } } hr { background-color: var(--color-grey-light); border: 0 none; height: 1px; margin: 28px auto 30px; } img { width: 100%; margin: 16px auto; } em { font-style: italic; } strong, a, table thead, figure figcaption, .embed .embed-caption { font-family: "Chappe Proxima Nova Semibold", sans-serif; } ul, ol { list-style-type: none; margin: 18px 0; li { line-height: 20px; margin-bottom: 8px; padding-left: 24px; position: relative; &:last-of-type { margin-bottom: 0; } ul, ol { &, &:first-child, &:last-child { margin-top: 12px !important; margin-bottom: 18px !important; } } ul { li { padding-left: 20px; &::before { background-color: var(--color-black); width: 4px; height: 4px; margin-top: 1px; border-radius: 0; } } } } } ul { li { &::before { content: ""; background-color: var(--color-markdown-list-item-bullet); width: 7px; height: 7px; border-radius: 100%; position: absolute; top: 7px; left: 10px; } } } ol { counter-reset: list-counter; li { counter-increment: list-counter; &::before { content: counter(list-counter) "."; text-align: right; min-width: 19px; position: absolute; top: 0; left: 0; } } } figure, .embed { margin: 36px auto 32px; } figure figcaption, .embed .embed-caption { color: var(--color-markdown-figure-text); text-align: center; line-height: 18px; margin-top: 10px; display: block; @include font-size(14px); } figure { text-align: center; max-width: 70%; display: block; img { border: 1px solid var(--color-markdown-figure-border); width: auto; max-width: 100%; margin: 0 auto; display: block; } } code { font-family: "Chappe Hack Regular", sans-serif; font-size: .8em; background-color: var(--color-markdown-code-inline-background); border: 1px solid var(--color-markdown-code-inline-border); color: var(--color-markdown-code-inline-text); hyphens: auto; word-break: break-all; margin: 0 1px; padding: 2px 6px; display: inline; border-radius: 2px; } pre { margin: 22px auto; position: relative; &.copy { &[data-copy-state="copied"] { .code-clipboard { border-color: var(--color-markdown-code-block-clipboard-border-copied); &::after { background-color: var(--color-markdown-code-block-clipboard-icon-background-copied); width: 16px; height: 12px; @include mask-image-internal("common/markdown/code-clipboard-copied.svg"); } } } &[data-copy-state="copied"], &:hover { .code-clipboard { visibility: visible; opacity: 1; } } } .code-clipboard { $code-clipboard-size: 30px; background-color: var(--color-markdown-code-block-clipboard-background-default); border: 1px solid var(--color-markdown-code-block-clipboard-border-default); width: $code-clipboard-size; height: $code-clipboard-size; display: flex; align-items: center; justify-content: center; visibility: hidden; opacity: 0; cursor: pointer; position: absolute; top: 8px; right: 9px; transition: all linear 75ms; transition-property: background-color, border-color, opacity; border-radius: 5px; &:hover { background-color: var(--color-markdown-code-block-clipboard-background-hover); border-color: var(--color-markdown-code-block-clipboard-border-hover); } &:active { background-color: var(--color-markdown-code-block-clipboard-background-active); border-color: var(--color-markdown-code-block-clipboard-border-active); } &::after { content: ""; background-color: var(--color-markdown-code-block-clipboard-icon-background-default); width: 13px; height: 15px; transition: background-color linear 75ms; @include mask-image-internal("common/markdown/code-clipboard-copy.svg"); } } code { background-color: var(--color-markdown-code-block-background); border: 0 none; color: var(--color-markdown-code-block-text); line-height: 19px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; tab-size: 4; hyphens: none; margin: 0; padding: 15px 18px; overflow: auto hidden; display: block; } @include selection { background-color: var(--color-markdown-code-selection); } } table { border-collapse: collapse; text-align: left; width: 100%; margin: 26px 0 34px; @include font-size(13px); thead { background-color: var(--color-markdown-table-head-background); color: var(--color-markdown-table-head-text); letter-spacing: .1px; user-select: none; } tbody { tr { &:hover { background-color: var(--color-markdown-table-body-row-background-hover); } } } th, td { border: 1px solid var(--color-markdown-table-cell-border); line-height: 18px; word-break: break-word; /* stylelint-disable-line declaration-property-value-keyword-no-deprecated */ hyphens: auto; padding: 6px 10px 6px 16px; } } blockquote, .emphasis { $emphasis-margin-top-bottom-base: 26px; margin: $emphasis-margin-top-bottom-base 0; padding: 14px 20px 14px 28px; position: relative; overflow: hidden; border-radius: 2px; & + blockquote, & + .emphasis { margin-top: ((-1 * $emphasis-margin-top-bottom-base) + 9px); } &::before { content: ""; width: 4px; position: absolute; top: 0; bottom: 0; left: 0; } } blockquote { background-color: var(--color-markdown-blockquote-background); color: var(--color-markdown-blockquote-text); font-style: italic; &::before { background-color: var(--color-markdown-blockquote-border); } } .emphasis { &.emphasis--warning { background-color: var(--color-markdown-emphasis-warning-background); &::before { background-color: var(--color-markdown-emphasis-warning-border); } } &.emphasis--info { background-color: var(--color-markdown-emphasis-info-background); &::before { background-color: var(--color-markdown-emphasis-info-border); } } &.emphasis--notice { background-color: var(--color-markdown-emphasis-notice-background); &::before { background-color: var(--color-markdown-emphasis-notice-border); } } } .embed { &[data-loaded="true"] { .embed-wrap { .embed-frame { opacity: 1; z-index: 2; } .embed-preview { opacity: 0; &::after { display: none; } } } } .embed-wrap { height: 420px; width: 100%; max-width: 680px; margin: 0 auto; position: relative; .embed-preview, .embed-frame, .embed-frame iframe { background-color: var(--color-markdown-embed-wrap-background); } .embed-preview, .embed-frame { width: 100%; height: 100%; transition: opacity ease-in-out 600ms; } .embed-preview { cursor: pointer; opacity: 1; position: relative; z-index: 1; @include background-image-fill($display: false); &:hover { &::after { opacity: 1; } } &:active { &::after { opacity: .95; } } &::after { $icon-size: 68px; content: ""; width: $icon-size; height: $icon-size; position: absolute; top: 50%; left: 50%; opacity: .9; transition: opacity ease-in-out 300ms; transform: translate(-50%, -50%); @include background-image-internal-fill("common/markdown/embed-preview-play.svg"); } } .embed-frame { opacity: 0; position: absolute; top: 0; left: 0; iframe { width: 100%; height: 100%; } } } } sup { &:has([data-footnote-ref]) { display: inline-block; vertical-align: baseline; font-size: smaller; position: relative; top: -0.4em; } } .footnotes { ol { list-style: none; li { counter-increment: footnote-counter; &::before { content: counter(footnote-counter) "."; top: 1px; } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-small-width-breakpoint) { .markdown { figure { max-width: 80%; } .embed { .embed-wrap { height: 380px; } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { .markdown { figure { max-width: none; } .embed { .embed-wrap { height: 280px; } } } } ================================================ FILE: src/stylesheets/common/_search.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #search { display: none; position: fixed; inset: 0; z-index: 200; .spotlight, &::after { position: absolute; inset: 0; } .spotlight { $spotlight-padding-sides: 26px; $spotlight-field-icon-size: 16px; $spotlight-field-spinner-size: 12px; padding-top: 90px; z-index: 2; animation-name: search-spotlight-animation; animation-delay: .1s; animation-duration: .2s; animation-fill-mode: both; &[data-has-results="false"] { .spotlight-results { display: none; } .spotlight-legend { background-color: var(--color-white); } } &[data-index-loading="true"] { .spotlight-field { &::after { display: inline-block; } .spotlight-input { padding-right: ($spotlight-padding-sides + $spotlight-field-spinner-size + 10px); } } } &[data-index-error="true"] { .spotlight-legend { .spotlight-error { display: block; } } } .spotlight-box { background-color: var(--color-white); width: 100%; max-width: 600px; margin: 0 auto; overflow: hidden; display: flex; flex-direction: column; border-radius: 6px; box-shadow: 0 4px 6px 0 var(--color-search-spotlight-shadow); } .spotlight-field, .spotlight-legend { background-color: var(--color-search-spotlight-field-background); flex: 0 0 auto; } .spotlight-field { border-bottom: 1px solid var(--color-grey-dark); height: 58px; position: relative; &::before, &::after { content: ""; position: absolute; top: 50%; } &::before { background-color: var(--color-blue-grey-dark); width: $spotlight-field-icon-size; height: $spotlight-field-icon-size; margin-top: (-1 * ($spotlight-field-icon-size * 0.5)); left: $spotlight-padding-sides; @include mask-image-internal-fill("common/search/field-icon.svg"); } &::after { background: transparent; border-color: var(--color-search-spotlight-field-spinner-border) var(--color-search-spotlight-field-spinner-border) transparent; border-width: 2px; border-style: solid; width: $spotlight-field-spinner-size; height: $spotlight-field-spinner-size; margin-top: (-1 * (($spotlight-field-spinner-size * 0.5) + 2px)); right: $spotlight-padding-sides; opacity: .75; border-radius: 100%; display: none; animation-name: search-spotlight-field-spinner-animation; animation-duration: 1s; animation-timing-function: linear; animation-fill-mode: both; animation-iteration-count: infinite; } .spotlight-input { background: transparent; border: 0 none; outline: 0 none; color: var(--color-black); letter-spacing: .1px; padding-left: ($spotlight-padding-sides + $spotlight-field-icon-size + 14px); height: 100%; width: 100%; @include font-size(15px); @include appearance(none); @include input-placeholder { color: var(--color-search-spotlight-field-placeholder); opacity: 1; } @include input-search { @include appearance(none); display: none; } } } .spotlight-results { $spotlight-result-padding-scroller-top-bottom: 3px; max-height: 380px; padding: (12px - $spotlight-result-padding-scroller-top-bottom) $spotlight-padding-sides (20px - $spotlight-result-padding-scroller-top-bottom); overflow: hidden auto; flex: 1; .spotlight-entries { .spotlight-entry { $spotlight-entry-padding-sides: 12px; $spotlight-entry-padding-top-bottom: 9px; $spotlight-entry-type-icon-size: 14px; margin: 0 (-1 * $spotlight-entry-padding-sides); padding-top: $spotlight-result-padding-scroller-top-bottom; padding-bottom: $spotlight-result-padding-scroller-top-bottom; &[data-selected="true"] { .spotlight-entry-link { background-color: var(--color-search-spotlight-entry-link-background-default); color: var(--color-white); &:active { background-color: var(--color-search-spotlight-entry-link-background-active); transition: background-color linear 100ms; } &::after { background-color: var(--color-white); opacity: 1; } .spotlight-entry-path { opacity: 1; } .spotlight-entry-separator { background-color: var(--color-white); opacity: .55; } } } &.spotlight-entry--page { .spotlight-entry-link { &::after { @include mask-image-internal("common/search/result-type-page.svg"); } } } &.spotlight-entry--anchor { .spotlight-entry-link { &::after { width: ($spotlight-entry-type-icon-size - 2px); height: ($spotlight-entry-type-icon-size - 2px); @include mask-image-internal("common/search/result-type-anchor.svg"); } } } &, .spotlight-entry-link { display: block; } .spotlight-entry-link { color: var(--color-black); letter-spacing: -.1px; padding: $spotlight-entry-padding-top-bottom ($spotlight-entry-padding-sides + $spotlight-entry-type-icon-size + 1px) $spotlight-entry-padding-top-bottom $spotlight-entry-padding-sides; position: relative; border-radius: 3px; &::after { content: ""; background-color: var(--color-black); width: $spotlight-entry-type-icon-size; height: $spotlight-entry-type-icon-size; opacity: .25; position: absolute; top: $spotlight-entry-padding-top-bottom; right: $spotlight-entry-padding-sides; @include mask-image-fill; } .spotlight-entry-path, .spotlight-entry-title, .spotlight-entry-preview { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; display: block; } .spotlight-entry-path { line-height: 14px; opacity: .85; @include font-size(12.5px); } .spotlight-entry-title { line-height: 16px; margin-top: 6px; @include font-size(15px); } .spotlight-entry-preview { line-height: 18px; margin-top: 6px; margin-bottom: -1px; opacity: .8; @include font-size(13.5px); } .spotlight-entry-separator { background-color: var(--color-black); vertical-align: middle; width: 4px; height: 8px; margin: -1px 5px 0; opacity: .35; display: inline-block; @include mask-image-internal-fill("common/search/result-separator.svg"); } } } } } .spotlight-legend { user-select: none; height: 32px; padding: 0 $spotlight-padding-sides; display: flex; align-items: center; flex-direction: row; .spotlight-shortcuts { display: flex; align-items: center; flex: 0 0 auto; .spotlight-shortcut { color: var(--color-search-spotlight-shortcut-text); letter-spacing: .1px; margin-top: -1px; margin-right: 18px; flex: 0 0 auto; @include font-size(11px); &:last-of-type { margin-right: 0; } &.spotlight-shortcut--navigate { &::before { width: 10px; height: 13px; @include mask-image-internal("common/search/legend-icon-navigate.svg"); } } &.spotlight-shortcut--go { &::before { width: 10px; height: 10px; @include mask-image-internal("common/search/legend-icon-go.svg"); } } &.spotlight-shortcut--close { &::before { width: 14px; height: 10px; @include mask-image-internal("common/search/legend-icon-close.svg"); } } &::before { content: ""; background-color: var(--color-search-spotlight-shortcut-text); vertical-align: middle; margin-top: -2px; margin-right: 4px; @include mask-image-fill; } } } .spotlight-error { color: var(--color-red); text-align: right; margin-top: -2px; padding-left: 10px; flex: 1; display: none; @include font-size(12px); } } } &::after { content: ""; background-color: var(--color-search-lock-background); z-index: 1; animation-name: search-lock-animation; animation-duration: .1s; animation-fill-mode: both; } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-medium-width-breakpoint) { #search { .spotlight { padding-top: 76px; } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #search { .spotlight { padding-top: $header-height; } } } // For screens below defined heights @media screen and (height <= 600px) { #search { .spotlight { .spotlight-box { height: 100%; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .spotlight-results { max-height: none; } } } } // - Keyframes @include keyframes-fix(search-lock-animation) { 0% { opacity: 0; } 100% { opacity: 1; } } @include keyframes-fix(search-spotlight-animation) { 0% { opacity: 0; transform: scale3d(.99, .99, .99) translate3d(0, 1%, 0); } 50% { opacity: 1; } } @include keyframes-fix(search-spotlight-field-spinner-animation) { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } ================================================ FILE: src/stylesheets/common/_viewer.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; .viewer { &[data-viewer-panes="2"], &[data-viewer-panes="3"] { #content { .panes { .content { margin-left: $content-sidebar-left-width; } } } #footer { &::before { content: ""; background-color: var(--color-white); width: $content-sidebar-left-width; flex: 0 0 auto; } .inner { border-left: 1px solid var(--color-viewer-panes-footer-border); } } } &[data-viewer-panes="3"] { #header { .search { background: var(--color-header-search-background-clear); border-color: var(--color-header-search-border-clear-default); &:hover { border-color: var(--color-header-search-border-clear-hover); } &:active { border-color: var(--color-header-search-border-clear-active); } } .coloring { background-color: var(--color-header-coloring-background-clear); } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-medium-width-breakpoint) { .viewer { &[data-viewer-panes="3"] { #header { .search { background: var(--color-header-search-background-default); border-color: var(--color-header-search-border-default); &:hover { border-color: var(--color-header-search-border-hover); } &:active { border-color: var(--color-header-search-border-active); } } .coloring { background-color: var(--color-header-coloring-background-default); } } } } } @media screen and (max-width: $screen-small-width-breakpoint) { .viewer { &[data-viewer-panes="2"], &[data-viewer-panes="3"] { #content { .panes { .content { margin-left: 0; } } } #footer { &::before { display: none; } .inner { border-left: 0 none; } } } } } ================================================ FILE: src/stylesheets/common/common.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_config" with ( $inlining: $use-inline-images ); @use "_fonts"; @use "_base"; @use "_buttons"; @use "_badges"; @use "_markdown"; @use "_code"; @use "_highlight"; @use "_header"; @use "_search"; @use "_content"; @use "_footer"; @use "_viewer"; @use "_appearance"; ================================================ FILE: src/stylesheets/guides/_common.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #guides { .nest .nest-category .nest-icon, .details .details-category .details-icon { background-color: var(--color-accent-base); @include mask-image-internal-fill("guides/common/category-icon-others.svg"); } } ================================================ FILE: src/stylesheets/guides/_content.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #guides { .details { border-bottom: 1px solid var(--color-guides-details-border); padding-bottom: 18px; .details-upper, .details-lower { display: flex; flex-direction: row; align-items: center; } .details-upper .details-category, .details-lower .details-title { flex: 1; } .details-upper .details-feedback, .details-lower .details-updated { flex: 0 0 auto; padding-left: 8px; @include font-size(13.5px); } .details-upper .details-category, .details-lower .details-updated { color: var(--color-guides-details-inner-text); } .details-upper { .details-category { line-height: 18px; margin-left: -2px; overflow: visible; @include font-size(16px); .details-icon { width: 19px; height: 19px; margin-top: -3px; margin-right: 4px; vertical-align: middle; @include background-image-fill; } } .details-feedback { color: var(--color-accent-base); user-select: none; &:hover { text-decoration: underline; } } } .details-lower { margin-top: 10px; .details-title { color: var(--color-black); line-height: 24px; @include font-size(23px); } } } .article { padding-top: 40px; } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-small-width-breakpoint) { #guides { .details { .details-upper { padding-left: 48px; } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #guides { .details { padding-left: 40px; padding-right: 10px; .details-upper .details-category, .details-lower .details-title { text-align: center; } .details-upper .details-feedback, .details-lower .details-updated { display: none; } .details-upper { padding-left: 0; } } .article { padding-top: 30px; } } } ================================================ FILE: src/stylesheets/guides/_sidebar_left.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #guides { .panes { .sidebar { &.sidebar--left { .nest { .nest-category { .nest-icon { @include background-image-fill; } } } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-small-width-breakpoint) { #guides { .panes { .sidebar-toggler { margin-top: 24px; } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #guides { .panes { .sidebar-toggler { margin-top: 30px; } } } } ================================================ FILE: src/stylesheets/guides/guides.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_config" with ( $inlining: $use-inline-images ); @use "_common"; @use "_sidebar_left"; @use "_content"; ================================================ FILE: src/stylesheets/home/home.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_config" with ( $inlining: $use-inline-images ); @use "../_globals" as *; #home { text-align: center; padding-top: 62px; padding-bottom: 100px; .illustration { max-width: 560px; width: 100%; height: 280px; margin: 48px auto -30px; @include background-image-fill; } .actions { $actions-button-margin-bottom: 7px; margin-top: 30px; margin-bottom: (-1 * $actions-button-margin-bottom); .button { margin: 0 5px $actions-button-margin-bottom; &:hover { &::after { background-color: var(--color-white); } } &::after { content: ""; background-color: var(--color-accent-base); width: 6px; height: 9px; margin-left: 17px; margin-right: -7px; flex: 0 0 auto; @include mask-image-internal-fill("home/action-quickstart-icon.svg"); } } } .bulletpoints { max-width: 940px; margin: 74px auto 0; .bulletpoint { &.bulletpoint--quickstart { &::before { @include mask-image-internal("home/bulletpoint-icon-quickstart.svg"); } } &.bulletpoint--guides { &::before { @include mask-image-internal("home/bulletpoint-icon-guides.svg"); } } &.bulletpoint--references { &::before { @include mask-image-internal("home/bulletpoint-icon-references.svg"); } } } } .support { margin-top: 62px; display: flex; justify-content: center; .support-wrap { display: flex; flex-direction: row; } .support-text { text-align: left; .support-question { color: var(--color-black); line-height: 20px; margin-top: -2px; @include font-size(15.5px); } .support-label { color: var(--color-blue-grey); line-height: 18px; margin-top: 5px; @include font-size(14.5px); } } .support-action { margin-left: 34px; .button { &:hover { &::before { opacity: 1; } } &::before { content: ""; background-color: var(--color-white); width: 20px; height: 18px; margin-top: -1px; margin-left: -2px; margin-right: 12px; opacity: .55; transition: opacity linear 100ms; @include mask-image-internal-fill("home/support-chat-icon.svg"); } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-small-width-breakpoint) { #home { padding-top: 52px; .illustration { margin-bottom: 0; } .bulletpoints { margin-top: 60px; } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #home { padding-top: 42px; padding-bottom: 80px; .illustration { height: 200px; } .support { .support-wrap { display: block; .support-text { text-align: center; } .support-action { margin-left: 0; margin-top: 18px; } } } } } @media screen and (max-width: $screen-lilliput-width-breakpoint) { #home { .actions .button, .support .support-action .button { display: flex; } .actions { .button { margin-left: 0; margin-right: 0; } } } } ================================================ FILE: src/stylesheets/not_found/not_found.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_config" with ( $inlining: $use-inline-images ); @use "../_globals" as *; #not_found { text-align: center; padding-top: 62px; padding-bottom: 86px; .illustration { max-width: 468px; width: 100%; height: 320px; margin: 64px auto 0; @include background-image-fill; } .actions { margin-top: 30px; .button { &:hover { &::before { opacity: 1; } } &::before { content: ""; background-color: var(--color-white); width: 20px; height: 16px; margin-top: -2px; margin-left: -3px; margin-right: 12px; opacity: .55; transition: opacity linear 100ms; @include mask-image-internal-fill("not_found/action-home-icon.svg"); } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-small-width-breakpoint) { #not_found { padding-top: 52px; } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #not_found { padding-top: 42px; padding-bottom: 64px; .illustration { height: 260px; } } } @media screen and (max-width: $screen-lilliput-width-breakpoint) { #not_found { .actions { .button { display: flex; } } } } ================================================ FILE: src/stylesheets/references/_content.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; $part-chunk-padding-sides: 24px; #references { .content { position: relative; .sidebar { &.sidebar--right { position: absolute; top: 0; right: 0; bottom: 0; z-index: 0; } } } .details, .introduction { position: relative; z-index: 2; } .details { margin-bottom: 32px; &.details--with-sidebar-right { padding-left: $part-chunk-padding-sides; } .details-inner { border-bottom: 1px solid var(--color-references-separator-content); padding-bottom: 18px; display: flex; flex-direction: row; align-items: center; .details-title { color: var(--color-black); line-height: 22px; margin: 0; flex: 1; @include font-size(21px); } .details-updated { color: var(--color-references-details-updated-text); flex: 0 0 auto; padding-left: 8px; @include font-size(13.5px); } } } .group, .introduction, .parts .specification { padding: 0 $part-chunk-padding-sides; } .group, .introduction, .details.details--with-sidebar-right { padding-right: ($content-sidebar-right-default-width + $part-chunk-padding-sides); } .group { margin-bottom: 26px; &, .group-anchor { color: var(--color-black); line-height: 20px; @include font-size(19px); } .group-anchor { .group-origin { color: var(--color-references-group-origin-text); margin-left: 9px; } } } .introduction { margin-bottom: 38px; } .parts { position: relative; z-index: 1; &:last-of-type { .part { &:last-of-type { margin-bottom: 0; padding-bottom: 0; &::before, &::after { display: none; } } } } .part { display: flex; flex-direction: row; margin-bottom: 28px; padding-bottom: 32px; position: relative; &:last-of-type { margin-bottom: 38px; } &.part--method-get, &.part--method-head { .specification { .request-specification { border-color: var(--color-method-get-head-light); .request-target { background-color: var(--color-method-get-head-light); .request-target-method { background-color: var(--color-method-get-head-dark); } } } } } &.part--method-post { .specification { .request-specification { border-color: var(--color-method-post-light); .request-target { background-color: var(--color-method-post-light); .request-target-method { background-color: var(--color-method-post-dark); } } } } } &.part--method-put, &.part--method-patch { .specification { .request-specification { border-color: var(--color-method-put-patch-light); .request-target { background-color: var(--color-method-put-patch-light); .request-target-method { background-color: var(--color-method-put-patch-dark); } } } } } &.part--method-delete { .specification { .request-specification { border-color: var(--color-method-delete-light); .request-target { background-color: var(--color-method-delete-light); .request-target-method { background-color: var(--color-method-delete-dark); } } } } } &::before, &::after { content: ""; height: 1px; position: absolute; bottom: 0; } &::before { background-color: var(--color-references-separator-content); left: $part-chunk-padding-sides; right: $content-sidebar-right-default-width; } &::after { background-color: var(--color-references-separator-sidebar); width: $content-sidebar-right-default-width; right: 0; } .specification { $specification-max-width-outer-offset: ($content-sidebar-right-default-width + (2 * $part-chunk-padding-sides)); max-width: calc(100% - #{$specification-max-width-outer-offset}); flex: 1; .request-title { margin-bottom: 10px; &, .request-title-anchor { color: var(--color-black); @include font-size(17.5px); } } .request-specification { border: 1px solid var(--color-grey); margin-top: 12px; border-radius: 3px; .request-target, .request-format { padding: 0 12px; } .request-target { background-color: var(--color-grey); height: 42px; display: flex; align-items: center; .request-target-method, .request-target-url { line-height: 28px; } .request-target-method { background-color: var(--color-black); color: var(--color-white); letter-spacing: -.1px; user-select: none; padding: 0 10px; border-radius: 2px; flex: 0 0 auto; @include font-size(13px); } .request-target-url { background: var(--color-references-request-target-url-background); border: 1px solid var(--color-references-request-target-url-border-default); margin-left: 12px; padding: 0 11px 0 9px; display: flex; align-items: center; cursor: pointer; overflow: hidden; border-radius: 2px; flex: 1; transition: border-color linear 75ms; &[data-copy-state="copied"] { .request-target-copy { background-color: var(--color-green); width: 14px; height: 11px; margin-right: -1px; @include mask-image-internal("references/content/request-target-copied.svg"); } } &:hover { border-color: var(--color-references-request-target-url-border-hover); .request-target-copy { opacity: .85; } } &:active { border-color: var(--color-references-request-target-url-border-active); .request-target-copy { opacity: 1; } } .request-target-copy { content: ""; background-color: var(--color-black); width: 11px; height: 12px; margin-left: 5px; opacity: .7; flex: 0 0 auto; transition: opacity linear 100ms; @include mask-image-internal-fill("references/content/request-target-copy.svg"); } } .request-target-path { color: var(--color-black); text-overflow: ellipsis; white-space: nowrap; @include font-size(12px); overflow: hidden; flex: 1; } } .request-format { line-height: 18px; padding-top: 14px; padding-bottom: 14px; overflow: hidden; .markdown { hyphens: auto; word-break: break-word; /* stylelint-disable-line declaration-property-value-keyword-no-deprecated */ p { line-height: 20px; } pre { margin: 10px auto; } .emphasis { $emphasis-margin-top-bottom: 12px; margin: $emphasis-margin-top-bottom 0; padding-left: 22px; & + .emphasis { margin-top: ((-1 * $emphasis-margin-top-bottom) + 8px); } } } .request-format-title { border-bottom: 1px solid var(--color-references-separator-content); color: var(--color-black); user-select: none; margin-top: 18px; margin-bottom: 11px; padding-bottom: 6px; @include font-size(15px); } .request-format-keys { .request-format-key { margin-bottom: 6px; @include font-size(13.5px); &:last-of-type { margin-bottom: 0; } .request-format-keys { border-left: 2px solid var(--color-references-request-format-keys-depth-border-two); margin: 7px 0; padding-left: 12px; &[data-depth] { border-left-color: var(--color-references-request-format-keys-depth-border-infinity); } &[data-depth="0"] { border-left-color: var(--color-references-request-format-keys-depth-border-zero); } &[data-depth="1"] { border-left-color: var(--color-references-request-format-keys-depth-border-one); } &[data-depth="2"] { border-left-color: var(--color-references-request-format-keys-depth-border-two); } &[data-depth="3"] { border-left-color: var(--color-references-request-format-keys-depth-border-three); } &[data-depth="4"] { border-left-color: var(--color-references-request-format-keys-depth-border-four); } } } } .request-format-parts { .request-format-type, .request-format-required, .request-format-optional { margin-left: 8px; } .request-format-path, .request-format-type, .request-format-required, .request-format-optional { text-transform: lowercase; flex: 0 0 auto; } .request-format-type { color: var(--color-references-request-format-type-text); user-select: none; min-width: 58px; } .request-format-required, .request-format-optional { margin-left: 4px; } .request-format-required { color: var(--color-references-request-format-required-text); } .request-format-optional { color: var(--color-references-request-format-optional-text); } .request-format-head { display: flex; flex-direction: row; } .request-format-label { color: var(--color-references-request-format-label-text); hyphens: auto; word-break: break-all; margin-top: 1px; flex: 1; &.markdown { * { font-size: inherit; line-height: inherit; } code { padding: 1px 4px; @include font-size(11.5px); } p { margin-bottom: 5px; &:last-of-type { margin-bottom: 0; } } ul { line-height: 14px; margin-top: 6px !important; margin-bottom: 12px !important; padding-left: 5px !important; @include font-size(12px); li { padding-left: 15px; &::before { background-color: var(--color-black); width: 4px; height: 4px; top: 5px; left: 4px; border-radius: 0; } } } } } } .request-format-toggle-button { color: var(--color-black); background-color: var(--color-white); border: 1px solid var(--color-references-request-format-toggle-button-border); line-height: 25px; white-space: nowrap; height: auto; margin-top: 7px; margin-bottom: 5px; padding: 0 10px; cursor: pointer; user-select: none; display: inline-flex; align-items: center; border-radius: 2px; box-shadow: 0 2px 1px 0 var(--color-references-request-format-toggle-button-shadow); &:hover { &::before { opacity: .85; } } &:active { box-shadow: none; } &::before { content: ""; background-color: var(--color-black); width: 8px; height: 6px; margin-right: 9px; opacity: .55; flex: 0 0 auto; transition: all linear .15s; transition-property: transform, opacity; @include mask-image-internal-fill("references/content/request-format-toggle-button-arrow.svg"); } .request-format-toggle-label { text-align: center; text-overflow: ellipsis; overflow: hidden; flex: 1; } .request-format-toggle-count { $toggle-count-footprint: 12px; color: var(--color-references-request-format-toggle-count-text); background-color: var(--color-references-request-format-toggle-count-background); text-align: center; line-height: $toggle-count-footprint; min-width: $toggle-count-footprint; margin-left: 8px; padding: 2px; @include font-size(10px); flex: 0 0 auto; border-radius: 100%; } } .request-format-toggle-checkbox, .request-format-toggle-checkbox:not(:checked) ~ .request-format-keys, .request-format-toggle-checkbox:not(:checked) ~ .request-format-toggle-button .request-format-toggle-label--hide, .request-format-toggle-checkbox:checked ~ .request-format-toggle-button .request-format-toggle-label--show { display: none; } .request-format-toggle-checkbox:checked ~ .request-format-toggle-button { &::before { transform: rotate(-180deg); } } .request-format-toggle-checkbox:checked ~ .request-format-keys { animation-name: references-content-request-format-keys-animation; animation-duration: .35s; animation-fill-mode: both; } } } } .examples { width: $content-sidebar-right-default-width; flex: 0 0 auto; .examples-wrap { padding: 34px 16px 0; } .examples-details { .examples-detail { margin-bottom: 7px; display: flex; flex-direction: row; align-items: center; &:last-of-type { margin-bottom: 0; } @include selection { background-color: var(--color-references-examples-selection); color: var(--color-white); } .examples-detail-label { min-width: 65px; text-align: right; flex: 0 0 auto; } .examples-detail-value { color: var(--color-references-examples-detail-value-text); line-height: 16px; word-break: break-word; /* stylelint-disable-line declaration-property-value-keyword-no-deprecated */ margin-left: 12px; flex: 1; @include font-size(13.5px); .badge { margin-right: 5px; &:last-of-type { margin-right: 0; } } .examples-detail-segment { color: var(--color-references-examples-detail-segment-text); } .examples-detail-parameter { color: var(--color-references-examples-detail-parameter-text); } } } } .code { margin: 24px 0 0; } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-large-width-breakpoint) { #content { .group, .introduction, .details.details--with-sidebar-right { padding-right: ($content-sidebar-right-large-width + $part-chunk-padding-sides); } .parts { .part { .specification { $specification-max-width-outer-offset: ($content-sidebar-right-large-width + (2 * $part-chunk-padding-sides)); max-width: calc(100% - #{$specification-max-width-outer-offset}); } .examples { width: $content-sidebar-right-large-width; } &::before { right: $content-sidebar-right-large-width; } &::after { width: $content-sidebar-right-large-width; } } } } } @media screen and (max-width: $screen-medium-width-breakpoint) { #content { .group, .introduction, .details.details--with-sidebar-right { padding: 0 $part-chunk-padding-sides; } .parts { .part { .specification { $specification-max-width-outer-offset: (2 * $part-chunk-padding-sides); max-width: calc(100% - #{$specification-max-width-outer-offset}); } &::after, .examples { display: none; } &::before { right: $part-chunk-padding-sides; } } } } } @media screen and (max-width: $screen-small-width-breakpoint) { #content { .details { .details-inner { padding-left: 50px; } } .group, .introduction, .details.details--with-sidebar-right, .parts .specification { padding: 0 $wrapper-small-padding-sides; } .parts { .part { &::before { left: $wrapper-small-padding-sides; right: $wrapper-small-padding-sides; } .specification { $specification-max-width-outer-offset: (2 * $wrapper-small-padding-sides); max-width: calc(100% - #{$specification-max-width-outer-offset}); } } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #content { .details { .details-inner { padding-left: 40px; padding-right: 10px; .details-title { text-align: center; } .details-updated { display: none; } } } .parts { .part { .specification { .request-specification { .request-format { .request-format-toggle-button { margin-bottom: 9px; display: flex; } } } } } } } } // - Keyframes @include keyframes-fix(references-content-request-format-keys-animation) { 0% { opacity: 0; } 100% { opacity: 1; } } ================================================ FILE: src/stylesheets/references/_sidebar_left.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_globals" as *; #references { .panes { .sidebar { &.sidebar--left { .context .context-name .context-icon, .nest .nest-category .nest-icon { background-color: var(--color-accent-base); @include mask-image-fill; } .nest { .nest-category { .nest-icon { @include mask-image-internal("references/sidebar/category-icon-default.svg"); } } } } } } } // - Media Queries // For screens below defined widths @media screen and (max-width: $screen-small-width-breakpoint) { #references { .panes { .sidebar-toggler { margin-top: 26px; } } } } @media screen and (max-width: $screen-tiny-width-breakpoint) { #references { .panes { .sidebar-toggler { margin-top: 15px; } } } } ================================================ FILE: src/stylesheets/references/references.scss ================================================ // chappe // // Copyright 2021, Crisp IM SAS // Author: Valerian Saliou @use "../_config" with ( $inlining: $use-inline-images ); @use "_sidebar_left"; @use "_content"; ================================================ FILE: src/templates/__base.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou include _mixins block vars - var page = null; - var panes = 1; mixin head-stylesheet(href) link( rel="stylesheet", href=`${href}.css?${REVISION}`, type="text/css" ) mixin head-javascript(src) script( src=`${src}.js?${REVISION}` ) doctype html html( lang=LOCALE.CODE, dir=LOCALE.DIRECTION, data-environment=ENVIRONMENT ) head include _head_http include _head_screen include _head_theme include _head_includes include _head_favicon include _head_metas block head_metas title block title | #{(SITE.identity.title || $_.COMMON.METAS.SITE_NAME)} block alternates block stylesheets +head-stylesheet("/static/stylesheets/common/libs") +head-stylesheet("/static/stylesheets/common/common") block javascripts +head-javascript("/static/javascripts/common/libs") +head-javascript("/static/javascripts/common/common") body.viewer.appearance( data-viewer-panes=(panes || 1), data-appearance="light" ) include _body_header include _body_search #content block body_content include _body_footer ================================================ FILE: src/templates/_body_footer.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou #footer .inner .wrapper .navigation .left if SITE.links.footer.navigation.length > 0 ul.links each link in SITE.links.footer.navigation li.link a.link-target( href=link.target, rel="noopener" ) | #{link.label} .right span.copyright | © #{DATE.YEAR} if SITE.identity.copyright | #{SITE.identity.copyright} .metadata .left .brand if SITE.images.logos.footer span.logo( style=`background-image: url('/static/user/${SITE.images.logos.footer}'); width: ${SITE.dimensions.logos.footer.width}px;` ) span.separator span.engine | #{$_.COMMON.FOOTER.ENGINE.GENERATED_BY} +text-space a.font-sans-semibold( href=PACKAGE.homepage, target="_blank" ) | Chappe | , #{$_.COMMON.FOOTER.ENGINE.PROJECT_FROM} +text-space a.font-sans-semibold( href=URLS.CRISP_WEB, target="_blank" ) | Crisp | . .right - var status_url = (SITE.urls.vigil || SITE.urls.crisp_status || null); if status_url .status( data-status="none" ) a.status-wrapped( href=status_url, target="_blank" ) span.status-icon span.status-text span.status-state.font-sans-semibold span.status-state-variant.status-state-variant--failure | #{$_.COMMON.FOOTER.STATUS.LABEL_FAILURE} span.status-state-variant.status-state-variant--healthy | #{$_.COMMON.FOOTER.STATUS.LABEL_HEALTHY} span.status-state-variant.status-state-variant--sick | #{$_.COMMON.FOOTER.STATUS.LABEL_SICK} span.status-state-variant.status-state-variant--dead | #{$_.COMMON.FOOTER.STATUS.LABEL_DEAD} span.status-time | #{$_.COMMON.FOOTER.STATUS.TIME_LAST} +text-space span.status-time-seconds | 0 | s #{$_.COMMON.FOOTER.STATUS.TIME_AGO}. ================================================ FILE: src/templates/_body_header.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou #header .wrapper .left a.logo.font-sans-bold( href="/" ) if SITE.images.logos.header_full && SITE.images.logos.header_short each variant in ["full", "short"] span.logo-image( class=`logo-image--${variant}`, style=`background-image: url('/static/user/${SITE.images.logos['header_' + variant]}'); width: ${SITE.dimensions.logos['header_' + variant].width}px;` ) else | #{(SITE.identity.title || $_.COMMON.METAS.SITE_NAME)} if SITE.links.header.navigation.length > 0 ul.menu each item in SITE.links.header.navigation li.menu-item( class=((item.route[0] === page) ? "menu-item--active" : null) ) - var item_href = `/${item.route.join("/")}/`; if (item.dropdown || []).length > 0 span.menu-link.menu-link--dropdown.font-sans-semibold | #{item.label} .dropdown each subitem in item.dropdown a.dropdown-link( href=`${item_href}${subitem.route.join("/")}/` ) | #{subitem.label} else a.menu-link.font-sans-semibold( href=item_href ) | #{item.label} .right ul.extras each extra in ["changes"] li.extras-item( class=((extra === page) ? "extras-item--active" : null) ) a.extras-link( href=`/${extra}/` ) | #{$_.COMMON.HEADER.EXTRAS[extra.toUpperCase()]} .search span.search-placeholder span.search-placeholder-text | #{$_.COMMON.HEADER.SEARCH.PLACEHOLDER} span.search-shortcut | ⌘K .coloring( data-mode="light" ) span.toggle if SITE.links.header.actions.length > 0 .actions each action in SITE.links.header.actions .action .action-button.button.button--small.button--icon span.button-text | #{action.label} .dropdown each subaction in action.dropdown a.dropdown-link.dropdown-link--multiple.dropdown-link--external( href=subaction.target, target="_blank" ) span.dropdown-link-title.font-sans-semibold | #{subaction.title} span.dropdown-link-label | #{subaction.subtitle} ================================================ FILE: src/templates/_body_search.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou #search .spotlight( data-has-results="false" ) .spotlight-box .spotlight-field input.spotlight-input.font-sans-regular( name="search", type="search", placeholder=$_.COMMON.SEARCH.FIELD.INPUT_PLACEHOLDER ) .spotlight-results ul.spotlight-entries .spotlight-legend .spotlight-shortcuts - var shortcuts = [["navigate", $_.COMMON.SEARCH.LEGEND.SHORTCUT_NAVIGATE], ["go", $_.COMMON.SEARCH.LEGEND.SHORTCUT_GO], ["close", $_.COMMON.SEARCH.LEGEND.SHORTCUT_CLOSE]]; each shortcut in shortcuts .spotlight-shortcut( class=`spotlight-shortcut--${shortcut[0]}` ) | #{shortcut[1]} span.spotlight-error.text-ellipsis.font-sans-semibold | #{$_.COMMON.SEARCH.LEGEND.ERROR_INDEX} ================================================ FILE: src/templates/_head_favicon.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou if SITE.favicons.sizes each href, size in SITE.favicons.sizes link( rel="icon", href=`/static/user/${href}`, sizes=((size !== "default") ? size : null), type="image/png" ) if SITE.favicons.main link( rel="shortcut icon", href=`/static/user/${SITE.favicons.main}`, type="image/x-icon" ) ================================================ FILE: src/templates/_head_http.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou meta( http-equiv="Content-Type", content="text/html; charset=utf-8" ) if (INDEXING === false || page === "not_found") meta( name="robots", content="noindex, nofollow" ) ================================================ FILE: src/templates/_head_includes.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou if SITE.tokens.crisp_website_id - var crisp_chatbox_url = (SITE.overrides.crisp_chatbox_url || "https://client.crisp.chat/l.js"); script. window.$crisp = []; window.CRISP_WEBSITE_ID = "#{SITE.tokens.crisp_website_id}"; (function(){d=document;s=d.createElement("script");s.src="#{crisp_chatbox_url}";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})(); if SITE.features.support !== true script. window.$crisp.push(["do", "chat:hide"]); each script_inline in SITE.includes.scripts.inline script. #{script_inline} each script_url in SITE.includes.scripts.urls if typeof script_url === "string" script( src=script_url, defer ) else script( src=script_url.src, async=script_url.async, defer=script_url.defer ) each stylesheet_inline in SITE.includes.stylesheets.inline style. #{stylesheet_inline} each stylesheet_url in SITE.includes.stylesheets.urls link( rel="stylesheet", href=stylesheet_url, type="text/css" ) ================================================ FILE: src/templates/_head_metas.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou meta( name="generator", content=`Chappe v${PACKAGE.version}` ) meta( property="og:locale", content=LOCALE.CODE ) meta( property="og:site_name", content=(SITE.identity.title || $_.COMMON.METAS.SITE_NAME) ) meta( property="og:type", content="website" ) if SITE.images.metas.opengraph - var opengraph_image = `${SITE.urls.base}/static/user/${SITE.images.metas.opengraph}`; meta( property="og:image", content=opengraph_image ) meta( name="twitter:card", content="summary_large_image" ) meta( name="twitter:image:src", content=opengraph_image ) ================================================ FILE: src/templates/_head_screen.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou meta( name="viewport", content="width=device-width, initial-scale=1" ) ================================================ FILE: src/templates/_head_theme.pug ================================================ //- chappe //- //- Copyright 2022, Crisp IM SAS //- Author: Valerian Saliou style. .appearance { --color-accent-base: #{SITE.theme.accent.light.base}; --color-accent-active: #{SITE.theme.accent.light.active}; } .appearance[data-appearance="dark"] { --color-accent-base: #{SITE.theme.accent.dark.base}; --color-accent-active: #{SITE.theme.accent.dark.active}; } ================================================ FILE: src/templates/_mixins.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou mixin text-space | mixin icon-user-override(icon_class, icon_image) span( class=icon_class, style=((icon_image !== null) ? (`-webkit-mask-image: url('/static/user/${icon_image}'); mask-image: url('/static/user/${icon_image}');`) : null) ) mixin sidebar-toggler .sidebar-toggler( data-toggled="false" ) mixin head-metas-page(title, description, url_path) meta( property="og:url", content=`${SITE.urls.base}${url_path}` ) meta( property="og:title", content=title ) meta( property="og:description", content=description ) meta( name="description", content=description ) mixin nest(origin, category, parent_id) .nest .nest-category.font-sans-semibold( class=((category.id === subpage) ? "nest-category--active" : null) ) - var link_icon_category = (SITE.images.categories[origin][category.id] || null); - var link_url_category = (category.segments ? (`/${page}/${category.segments.join("/")}/`) : null); +icon-user-override("nest-icon", link_icon_category) if link_url_category a.nest-category-link( href=link_url_category ) | #{category.title} else span.nest-category-link | #{category.title} .nest-navigate each item in category.subtrees - var is_slice = ((item.subtrees.length > 0) ? true : false); .nest-navigate-level.nest-navigate-level--first( data-expanded=(is_slice ? ((parent_id || "").startsWith(item.id) ? "true" : "false") : null) ) - var link_url_item = (item.segments ? (`/${page}/${item.segments.join("/")}/`) : `#${item.id}`); if is_slice .nest-navigate-link.nest-navigate-link--slice( data-active=((item.id === subpage) ? "true" : null), data-anchor=link_url_item ) a.nest-navigate-slice.font-sans-semibold( href=link_url_item ) | #{item.title} span.nest-navigate-toggle .nest-navigate-level.nest-navigate-level--second.font-sans-regular each slice in item.subtrees - var link_url_slice = (slice.segments ? (`/${page}/${slice.segments.join("/")}/`) : `#${slice.id}`); +nest-navigate-link(link_url_slice, slice.id, slice.title, slice.badge, subpage, false) if item.links each link, index in item.links +nest-navigate-links-external(link, index, item) else +nest-navigate-link(link_url_item, item.id, item.title, item.badge, subpage, false) if category.links each link, index in category.links .nest-navigate-level.nest-navigate-level--first +nest-navigate-links-external(link, index, category) mixin nest-navigate-link(href, id, label, badge, id_active, is_external) - var is_active = ((id === id_active) ? true : false); a.nest-navigate-link( href=href, target=(is_external ? "_blank" : null), class=(is_external ? "nest-navigate-link--external font-sans-semibold" : ""), data-active=(is_active ? "true" : null), data-starred=(label.startsWith("⭐") ? "true" : null), data-anchor=href ) span.nest-navigate-link-text | #{label} if badge && badge[0] span.nest-navigate-link-objects span.badge.badge--small.font-sans-bold( class=`badge--${badge[1] || "black"}` ) | #{badge[0]} mixin nest-navigate-links-external(link, index, item) +nest-navigate-link(link.url, `${item.id}_link_${index}`, link.name, null, null, true) mixin code(language, flows, request_options, response_options) .code.copy( data-was-loaded="false", data-has-request="false" ) input.code-data( name="data", type="hidden", value=JSON.stringify(flows) ) .code-header .code-language | #{language} .code-actions each action in ["copy"] span.code-action( class=`code-action--${action}${(action === "copy") ? " copy-button" : ""}` ) .code-section.code-section--request +code-metas($_.COMMON.CODE.META_NAME_REQUEST, request_options) +code-content .code-section.code-section--response +code-metas($_.COMMON.CODE.META_NAME_RESPONSE, response_options) +code-content mixin code-metas(label, options) .code-metas .code-meta-name.font-sans-semibold | #{label} .code-meta-picker +code-select("group", options, ((options || [])[0] || [])[0]) .code-meta-type mixin code-content pre.code-content.font-code-regular | #{$_.COMMON.CODE.CONTENT_LOADING} mixin code-select(name, options, selected) select.font-sans-regular( name=name, autocomplete="off" ) each option in options option( value=option[0], selected=((selected && option[0] === selected) ? "" : null) ) | #{option[1]} ================================================ FILE: src/templates/changes/index.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou extends ../__base block vars - var page = "changes"; //- Special: generate page title - var page_title = `${$_.CHANGES.PAGE} | ${$_.CHANGES.NAVIGATE[("" + changes.current).toUpperCase()] || changes.current}`; block title | #{page_title} block append stylesheets +head-stylesheet("/static/stylesheets/changes/changes") block head_metas - var page_url = `/changes/${(changes.current !== "latest") ? `${changes.current}/` : ""}`; //- Special: generate page description - var page_description = ((changes.current === "latest") ? (SITE.texts.changes.titles.latest || $_.CHANGES.TITLE_LATEST) : `${SITE.texts.changes.titles.year || $_.CHANGES.TITLE_YEAR} ${changes.year}`); +head-metas-page(page_title, page_description, page_url) block append alternates link( rel="alternate", type="application/rss+xml", title=(SITE.texts.changes.titles.feed || $_.CHANGES.TITLE_FEED), href="/changes.rss" ) block body_content .wrapper #changes ul.navigate - var navigate_years = [].concat(["latest"], years); each navigate_year in navigate_years li.navigate-item( class=((navigate_year === changes.current) ? "navigate-item--active" : null) ) a.navigate-link( href=`/changes/${(navigate_year === "latest") ? "" : `${navigate_year}/`}`, class=((typeof navigate_year === "string" || navigate_year === changes.current) ? "font-sans-semibold" : null) ) if typeof navigate_year === "string" | #{($_.CHANGES.NAVIGATE[navigate_year.toUpperCase()] || navigate_year)} else | #{navigate_year} .content .content-aside h4.title.font-sans-bold if changes.current === "latest" | #{(SITE.texts.changes.titles.latest || $_.CHANGES.TITLE_LATEST)} else | #{(SITE.texts.changes.titles.year || $_.CHANGES.TITLE_YEAR)} #{changes.year} if changes.current === "latest" .notice.markdown - var notice_text = (SITE.texts.changes.notice || $_.CHANGES.NOTICE); != METHODS.marked(notice_text) .timeline each month_entry in changes.timeline .month h6.month-name.font-sans-semibold | #{($_.CHANGES.MONTHS[month_entry[0]] || month_entry[0])} #{changes.year} ul.month-events each month_change in month_entry[1] li.month-event( class=((month_change.type === "deprecation") ? "month-event--deprecated" : null), data-group=month_change.group ) - var group_color = (((month_change.type !== "deprecation") ? SITE.colors.changes.groups[month_change.group] : null) || null); .month-event-group | #{(SITE.texts.changes.groups[month_change.group] || month_change.group)} .month-event-text.markdown( style=((group_color !== null) ? `border-color: ${group_color};` : null) ) != METHODS.marked(month_change.text) ================================================ FILE: src/templates/guides/_content.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou .content.content--padded .content-wrap .details - var category_icon = (SITE.images.categories.guides[guide.segments[0]] || null); - var category_title = ((parent_titles.length > 0) ? parent_titles.join(" | ") : guide.title); .details-upper h6.details-category.text-ellipsis.font-sans-semibold +icon-user-override("details-icon", category_icon) | #{category_title} if SITE.tokens.crisp_website_id && SITE.features.support === true a.details-feedback.font-sans-semibold( href="#crisp-chat-feedback" ) | #{$_.GUIDES.DETAILS.FEEDBACK} .details-lower h1.details-title.text-ellipsis.font-sans-bold | #{((category_title !== guide.title) ? guide.title : $_.GUIDES.DETAILS.TITLE_INDEX)} span.details-updated | #{$_.GUIDES.DETAILS.UPDATED} #{guide.updated.toLocaleDateString(FORMAT.DATES.LOCALE_DATE_STRING.AREA, FORMAT.DATES.LOCALE_DATE_STRING.OPTIONS)} .article.markdown != METHODS.marked(guide.markdown) ================================================ FILE: src/templates/guides/_sidebar_left.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou include ../_mixins .sidebar.sidebar--left span.sidebar-toggler-retract - var guides_base = ((guides[0] || {}).subtrees || []); each category in guides_base +nest("guides", category, guide.id) +sidebar-toggler ================================================ FILE: src/templates/guides/index.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou extends ../__base block vars - var page = "guides"; - var subpage = guide.id; - var panes = 2; //- Special: acquire all parent titles - var parent_titles = []; - var next_parent = guide.parent; while next_parent if next_parent.id - parent_titles.unshift(next_parent.title); - next_parent = next_parent.parent; //- Special: generate page title - var page_title = guide.title; if parent_titles.length > 0 - page_title += ` (${parent_titles.join(" → ")})`; block title | #{page_title} block append stylesheets +head-stylesheet("/static/stylesheets/guides/guides") block head_metas - var page_url = `/guides/${guide.segments.join("/")}${(guide.segments.length > 0) ? "/" : ""}`; //- Special: generate page description - var page_description = METHODS.truncate_text(METHODS.remove_markdown(guide.markdown), FORMAT.DESCRIPTION.TRUNCATE); +head-metas-page(page_title, page_description, page_url) block body_content #guides .panes include _sidebar_left include _content ================================================ FILE: src/templates/home/index.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou extends ../__base block vars - var page = "home"; //- Special: define bulletpoints - var bulletpoints = [["quickstart", SITE.bulletpoints.home.quickstart.description, SITE.bulletpoints.home.quickstart.actions], ["guides", SITE.bulletpoints.home.guides.description, SITE.bulletpoints.home.guides.actions], ["references", SITE.bulletpoints.home.references.description, SITE.bulletpoints.home.references.actions]]; block title | #{(SITE.identity.title || $_.HOME.PAGE)} block append stylesheets +head-stylesheet("/static/stylesheets/home/home") block append javascripts script( type="application/ld+json" ) | { | "@context": "https://schema.org", | "@type": "BreadcrumbList", | "itemListElement": [ - var last_position = 0; each bulletpoint, bulletpoint_index in bulletpoints if bulletpoint[2] && bulletpoint[2].length > 0 each action, action_index in bulletpoint[2] - var is_last = ((bulletpoint_index === (bulletpoints.length - 1)) && (action_index === (bulletpoint[2].length - 1))); | { | "@type": "ListItem", | "position": #{(++last_position)}, | "name": "#{action.label}", | "item": "#{SITE.urls.base}/#{action.route.join('/')}/" | }#{((is_last === true) ? "" : ", ")} | ] | } block head_metas +head-metas-page((SITE.identity.title || $_.HOME.PAGE), (SITE.texts.home.label || $_.HOME.LABEL), "/") block body_content .wrapper #home .main h1.main-title.font-sans-bold | #{(SITE.texts.home.title || $_.HOME.TITLE)} p.main-label | #{(SITE.texts.home.label || $_.HOME.LABEL)} if SITE.actions.home.length > 0 .actions each action in SITE.actions.home a.button.button--large.button--reverse.button--icon.font-sans-semibold( href=`/${action.route.join("/")}/` ) span.button-text | #{action.label} if SITE.images.illustrations.home .illustration( style=`background-image: url('/static/user/${SITE.images.illustrations.home}');` ) ul.bulletpoints each bulletpoint in bulletpoints li.bulletpoint( class=`bulletpoint--${bulletpoint[0]}` ) h6.bulletpoint-title.font-sans-semibold | #{$_.HOME.BULLETPOINTS[bulletpoint[0].toUpperCase()]} if bulletpoint[1] p.bulletpoint-label | #{bulletpoint[1]} if bulletpoint[2] && bulletpoint[2].length > 0 .bulletpoint-actions each action in bulletpoint[2] a.bulletpoint-action.font-sans-semibold( href=`/${action.route.join("/")}/`, class=((bulletpoint[2].length === 1) ? "bulletpoint-action--single" : null) ) | #{action.label} if SITE.tokens.crisp_website_id && SITE.features.support === true .support .support-wrap .support-text h6.support-question.font-sans-regular | #{$_.HOME.SUPPORT.QUESTION} p.support-label | #{$_.HOME.SUPPORT.LABEL} .support-action a.button.button--icon( href="#crisp-chat-open" ) span.button-text | #{$_.HOME.SUPPORT.ACTION} ================================================ FILE: src/templates/not_found/index.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou extends ../__base block vars - var page = "not_found"; block title | #{$_.NOT_FOUND.PAGE} block append stylesheets +head-stylesheet("/static/stylesheets/not_found/not_found") block body_content .wrapper #not_found .main h1.main-title.font-sans-bold | #{$_.NOT_FOUND.TITLE} p.main-label span.main-label-line | #{$_.NOT_FOUND.LABEL_FIRST} span.main-label-line | #{$_.NOT_FOUND.LABEL_SECOND} .actions a.button.button--icon( href="/" ) span.button-text | #{$_.NOT_FOUND.ACTION} if SITE.images.illustrations.not_found .illustration( style=`background-image: url('/static/user/${SITE.images.illustrations.not_found}');` ) ================================================ FILE: src/templates/references/_blueprint.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou include _mixins_common include _mixins_blueprint include _blueprint_sidebar_left include _blueprint_content ================================================ FILE: src/templates/references/_blueprint_content.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou .content .content-wrap.content-wrap--full +references-content-details(reference, true) each entry in reference.data.content case entry.element when "copy" +references-blueprint-introduction(entry.content) when "category" +references-blueprint-group(entry.content, entry.meta, reference.baseline) .sidebar.sidebar--right ================================================ FILE: src/templates/references/_blueprint_sidebar_left.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou include ../_mixins .sidebar.sidebar--left span.sidebar-toggler-retract +references-sidebar-context(reference) each category in reference.categories +nest("references", category) +sidebar-toggler ================================================ FILE: src/templates/references/_markdown.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou include _mixins_common include _markdown_sidebar_left include _markdown_content ================================================ FILE: src/templates/references/_markdown_content.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou .content.content--padded .content-wrap.markdown +references-content-details(reference) != METHODS.marked(reference.data) ================================================ FILE: src/templates/references/_markdown_sidebar_left.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou include ../_mixins .sidebar.sidebar--left span.sidebar-toggler-retract +references-sidebar-context(reference) each category in reference.categories +nest("references", category) +sidebar-toggler ================================================ FILE: src/templates/references/_mixins_blueprint.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou mixin references-blueprint-group(content, meta, baseline) .parts - var id = ""; if meta.title - id = METHODS.slug(meta.title.content); each entry in content if entry.element === "resource" +references-blueprint-group-title(entry.meta, meta, id) each part in entry.content if part.element === "transition" +references-blueprint-part(part.content, part.meta, part.attributes, reference.examples, baseline) mixin references-blueprint-group-title(meta, origin_meta, origin_id) if meta.title - var id = METHODS.slug(meta.title.content); if origin_id - id = `${origin_id}-${id}`; h1.group.font-sans-bold( id=id ) a.group-anchor.title-group-anchor( href=`#${id}` ) span.title-anchor | #{meta.title.content} if origin_meta && origin_meta.title span.group-origin.font-sans-regular | #{$_.REFERENCES.BLUEPRINT.GROUP.ORIGIN_IN} #{origin_meta.title.content} mixin references-blueprint-introduction(content) .introduction.markdown != METHODS.marked(content) mixin references-blueprint-part(content, meta, attrs, examples, baseline) - var id = (meta.title ? METHODS.slug(meta.title.content) : ""); - var http_method = ((examples[id] || {}).method || "none"); .part( class=`part--method-${http_method}` ) .specification if meta.title h2.request-title.font-sans-bold( id=id ) a.request-title-anchor.title-anchor( href=`#${id}` ) | #{meta.title.content} +references-blueprint-part-specification(id, content, meta, attrs, baseline, http_method) +references-blueprint-part-examples(id, examples, baseline) mixin references-blueprint-part-specification(id, content, meta, attrs, baseline, http_method) - var data_source = ((["head", "get"].includes(http_method) === true) ? "response" : "request"); .request-specification .request-target .request-target-method.font-sans-semibold | #{http_method.toUpperCase()} .request-target-url.copy.copy-button span.request-target-path.copy-value.font-code-regular | #{(attrs.href ? `${baseline.host.path}${attrs.href.content}` : "--")} span.request-target-copy .request-format //- Collapse deeper for data coming from response (due to nesting in 'data') - var collapse_depth = ((data_source === "response") ? 1 : 0); each entry in content if entry.element === "copy" .markdown != METHODS.marked(entry.content) if attrs.hrefVariables && attrs.hrefVariables.content .request-format-title.font-sans-bold | #{$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_TITLES.URI_PARAMETERS} +references-blueprint-request-format-keys(id, http_method, 0, attrs.hrefVariables.content, collapse_depth, false) if attrs.data && attrs.data.content && attrs.data.content.content .request-format-title.font-sans-bold | #{$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_TITLES[`DATA_${data_source.toUpperCase()}`]} +references-blueprint-request-format-keys(id, http_method, 0, attrs.data.content.content, collapse_depth, true) mixin references-blueprint-part-examples(id, examples, baseline) - var example = (examples[id] || null); if example .examples .examples-wrap ul.examples-details li.examples-detail span.examples-detail-label span.badge.badge--white.font-sans-semibold | #{(example.method || "none").toUpperCase()} span.examples-detail-value | #{baseline.host.url} each part in example.url.parts span( class=`examples-detail-${part.type}` ) | #{part.value} if example.tiers.length > 0 li.examples-detail span.examples-detail-label span.badge.badge--white.font-sans-semibold | #{$_.REFERENCES.BLUEPRINT.EXAMPLES.DETAILS.TIERS} span.examples-detail-value each tier in example.tiers span.badge.badge--special-value | #{tier} if example.scopes.length > 0 li.examples-detail span.examples-detail-label span.badge.badge--white.font-sans-semibold | #{$_.REFERENCES.BLUEPRINT.EXAMPLES.DETAILS.SCOPES} span.examples-detail-value each scope in example.scopes span.badge.badge--special-value | #{scope} if example.flows - var flow_keys = Object.keys(example.flows); if flow_keys.length > 0 - var first_flow = example.flows[flow_keys[0]]; - var request_options = flow_keys.map(function(key) { return [key, example.flows[key].request.name] }); - var response_options = first_flow.responses.map(function(response, index) { return [("" + index), response.name] }); +code("HTTP", example.flows, request_options, response_options) mixin references-blueprint-request-format-keys(id, http_method, depth, content, collapse_depth, with_children) //- Generate keys tree from API Blueprint 'data' object .request-format-keys( data-depth=depth ) each data_level in content if data_level.element === "member" - var key = {}; //- Push main data - key.key = data_level.content.key.content; - key.type = data_level.content.value.element; //- Parse label? if data_level.meta && data_level.meta.description - key.label = data_level.meta.description.content; //- Begin array content type? if data_level.content.value.element === "array" && data_level.content.value.content - key.type += `[${((data_level.content.value.content[0] || {}).element || "")}`; //- Parse enumeration members? - var enumeration_attributes = data_level.content.value.attributes; if data_level.content.value.element === "array" && data_level.content.value.content //- Flatten enumeration members from array (only for array[enum[*]]) - enumeration_attributes = (data_level.content.value.content[0] || {}).attributes; if enumeration_attributes && enumeration_attributes.enumerations && enumeration_attributes.enumerations.content && enumeration_attributes.enumerations.content.length > 0 - key.type += `[${enumeration_attributes.enumerations.content[0].element}]`; //- Append all enumeration member values to label (using Markdown) - var members = ""; each enumeration in enumeration_attributes.enumerations.content if enumeration.content !== undefined - members += `* \`${enumeration.content}\``; if enumeration.meta && enumeration.meta.description && enumeration.meta.description.content - members += ` ${enumeration.meta.description.content}`; - members += "\n"; if members - key.label = `${key.label || ""}\n\n**${$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_MEMBERS}**\n${members.trim()}`; //- End array content type? if data_level.content.value.element === "array" && data_level.content.value.content - key.type += "]"; //- Parse required and optional attributes? if data_level.attributes && data_level.attributes.typeAttributes && data_level.attributes.typeAttributes.content each type_attribute in data_level.attributes.typeAttributes.content if !key.attribute - key.attribute = type_attribute.content; //- Parse children? (if children parsing is enabled) if with_children === true && data_level.content.value.content //- Unwind array? (first item only for array[object]) if data_level.content.value.element === "array" - key.children = (data_level.content.value.content[0] || {}).content; else - key.children = data_level.content.value.content; .request-format-key +references-blueprint-request-format-parts(key.key, key.type, key.label, key.attribute) if key.children && key.children.length > 0 if with_children === true && depth >= collapse_depth +references-blueprint-request-format-toggle(id, http_method, key.key, key.children.length, depth) +references-blueprint-request-format-keys(id, http_method, (depth + 1), key.children, collapse_depth, with_children) mixin references-blueprint-request-format-parts(key_name, type, label, attribute) .request-format-parts .request-format-head .request-format-path.font-sans-semibold | #{key_name} .request-format-type | #{(type || "?")} case attribute when "required" span.request-format-required.font-sans-semibold | #{$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_REQUIRED} when "optional" span.request-format-optional | #{$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_OPTIONAL} if label .request-format-label.markdown != METHODS.marked(label) mixin references-blueprint-request-format-toggle(id, http_method, key_name, children_count, depth) - var toggle_parts = [http_method, id, `z${depth}`, key_name]; - var toggle_id = `toggle_${toggle_parts.join("-").toLowerCase()}`; input.request-format-toggle-checkbox( type="checkbox", id=toggle_id, name=toggle_id ) label.request-format-toggle-button.button.button--small( for=toggle_id ) span.request-format-toggle-label.request-format-toggle-label--show | #{$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_TOGGLE_SHOW} span.request-format-toggle-label.request-format-toggle-label--hide | #{$_.REFERENCES.BLUEPRINT.SPECIFICATION.REQUEST_FORMAT_TOGGLE_HIDE} span.request-format-toggle-count.font-sans-semibold | #{children_count} ================================================ FILE: src/templates/references/_mixins_common.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou mixin references-sidebar-context(reference) - var context_icon = (SITE.images.categories.references[reference.segments[0]] || null); .context .context-name.font-sans-semibold +icon-user-override("context-icon", context_icon) | #{reference.title} mixin references-content-details(reference, has_sidebar_right) .details( class=(has_sidebar_right ? "details--with-sidebar-right" : null) ) .details-inner h1.details-title.text-ellipsis.font-sans-bold | #{reference.title} span.details-updated | #{$_.REFERENCES.MARKDOWN.DETAILS.UPDATED} #{reference.updated.toLocaleDateString(FORMAT.DATES.LOCALE_DATE_STRING.AREA, FORMAT.DATES.LOCALE_DATE_STRING.OPTIONS)} ================================================ FILE: src/templates/references/index.pug ================================================ //- chappe //- //- Copyright 2021, Crisp IM SAS //- Author: Valerian Saliou extends ../__base block vars - var page = "references"; - var subpage = reference.id; - var panes = ((reference.type === "API Blueprint") ? 3 : 2); block title | #{reference.title} block append stylesheets +head-stylesheet("/static/stylesheets/references/references") block head_metas - var page_url = `/references/${reference.segments.join("/")}${(reference.segments.length > 0) ? "/" : ""}`; //- Special: generate page description - var page_description = ((typeof reference.data === "string") ? METHODS.truncate_text(METHODS.remove_markdown(reference.data), FORMAT.DESCRIPTION.TRUNCATE) : reference.title); +head-metas-page(reference.title, page_description, page_url) block body_content #references .panes case reference.type when "API Blueprint" include _blueprint when "Markdown" include _markdown