diff --git a/docs/1-Using-Fleet/1-Fleet-UI.md b/docs/1-Using-Fleet/1-Fleet-UI.md
index a5536d77a2..562cf0a65e 100644
--- a/docs/1-Using-Fleet/1-Fleet-UI.md
+++ b/docs/1-Using-Fleet/1-Fleet-UI.md
@@ -59,3 +59,5 @@ Let's say you have two teams in Fleet. One team is named "Workstations" and the
To configure team agent options, head to **Settings > Teams > `Team-name-here` > Agent options**.

+
+
diff --git a/website/api/controllers/docs/view-basic-documentation.js b/website/api/controllers/docs/view-basic-documentation.js
index 5c5a961d0d..769474f0ad 100644
--- a/website/api/controllers/docs/view-basic-documentation.js
+++ b/website/api/controllers/docs/view-basic-documentation.js
@@ -7,23 +7,60 @@ module.exports = {
description: 'Display "Basic documentation" page.',
- exits: {
+ urlWildcardSuffix: 'pageUrlSuffix',
- success: {
- viewTemplatePath: 'pages/docs/basic-documentation'
+
+ inputs: {
+ pageUrlSuffix : {
+ description: 'The relative path to the doc page from within this route. (i.e. the URL wildcard suffix)',
+ example: 'using-fleet/supported-browsers',
+ type: 'string',
+ defaultsTo: ''
}
-
},
- fn: async function () {
+ exits: {
+ success: { viewTemplatePath: 'pages/docs/basic-documentation' },
+ badConfig: { responseType: 'badConfig' },
+ notFound: { responseType: 'notFound' },
+ redirect: { responseType: 'redirect' },
+ },
- // Serve appropriate doc page content.
+
+ fn: async function ({pageUrlSuffix}) {
+
+ if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.markdownPages) || !sails.config.builtStaticContent.compiledPagePartialsAppPath) {
+ throw {badConfig: 'builtStaticContent.markdownPages'};
+ }
+
+ let SECTION_URL_PREFIX = '/docs';
+
+ // Serve appropriate page content.
// > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js
- // TODO
+ let thisPage = _.find(sails.config.builtStaticContent.markdownPages, {
+ url: _.trimRight(SECTION_URL_PREFIX + '/' + _.trim(pageUrlSuffix, '/'), '/')
+ });
+ // console.log('pageUrlSuffix:',pageUrlSuffix);
+ // console.log('SECTION_URL_PREFIX + "/" + _.trim(pageUrlSuffix, "/"):',SECTION_URL_PREFIX + '/' + _.trim(pageUrlSuffix, '/'));
+ // console.log('thisPage:',thisPage);
+ if (!thisPage) {
+ throw 'notFound';
+ }
+
+ if (false) {
+ // TODO: add "redirect" exit and handle mismatched capitalization / extra slashes by redirecting to the correct URL. e.g. "http://localhost:2024/docs//usiNG-fleet///" Partial example of this: https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js#L161-L166
+ let revisedUrl = 'todo';
+ throw {redirect: revisedUrl};
+ }
// Respond with view.
- return {};
+ return {
+ path: require('path'),
+ thisPage: thisPage,
+ markdownPages: sails.config.builtStaticContent.markdownPages,
+ compiledPagePartialsAppPath: sails.config.builtStaticContent.compiledPagePartialsAppPath
+ };
}
diff --git a/website/api/controllers/download-sitemap.js b/website/api/controllers/download-sitemap.js
index 102af7ccf4..3a82744bf8 100644
--- a/website/api/controllers/download-sitemap.js
+++ b/website/api/controllers/download-sitemap.js
@@ -16,7 +16,6 @@ module.exports = {
exits: {
success: { outputFriendlyName: 'Sitemap (XML)', outputType: 'string' },
badConfig: { responseType: 'badConfig' },
- notFound: { responseType: 'notFound' }// « TODO: delete this and the code that calls it below when all pages are ready
},
@@ -27,11 +26,6 @@ module.exports = {
// and for the real thing to be served in production, while explicitly preventing the "whoops,
// i deployed staging and search engine crawlers got fixated on the wrong sitemap" dilemma.
throw new Error('Since this is the staging environment, prevented sitemap.xml from being served to avoid search engine accidents.');
- }//•
-
- if (sails.config.environment === 'production') {// TODO: Remove this once the pages are ready.
- // Don't serve a sitemap until the pages actually work.
- throw 'notFound';
}
if (!_.isObject(sails.config.builtStaticContent)) {
diff --git a/website/api/controllers/handbook/view-basic-handbook.js b/website/api/controllers/handbook/view-basic-handbook.js
index 85f5fc2672..5d73eb8312 100644
--- a/website/api/controllers/handbook/view-basic-handbook.js
+++ b/website/api/controllers/handbook/view-basic-handbook.js
@@ -7,23 +7,57 @@ module.exports = {
description: 'Display "Basic handbook" page.',
- exits: {
+ urlWildcardSuffix: 'pageUrlSuffix',
- success: {
- viewTemplatePath: 'pages/handbook/basic-handbook'
+
+ inputs: {
+ pageUrlSuffix : {
+ description: 'The relative path to the doc page from within this route. (i.e. the URL wildcard suffix)',
+ example: 'handbook/release-process',
+ type: 'string',
+ defaultsTo: ''
}
-
},
- fn: async function () {
+ exits: {
+ success: { viewTemplatePath: 'pages/handbook/basic-handbook' },
+ badConfig: { responseType: 'badConfig' },
+ notFound: { responseType: 'notFound' },
+ redirect: { responseType: 'redirect' },
+ },
- // Serve appropriate handbook page content.
+
+ fn: async function ({pageUrlSuffix}) {
+
+ if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.markdownPages) || !sails.config.builtStaticContent.compiledPagePartialsAppPath) {
+ throw {badConfig: 'builtStaticContent.markdownPages'};
+ }
+
+ let SECTION_URL_PREFIX = '/handbook';
+
+ // Serve appropriate page content.
// > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js
- // TODO
+ let thisPage = _.find(sails.config.builtStaticContent.markdownPages, {
+ url: _.trimRight(SECTION_URL_PREFIX + '/' + _.trim(pageUrlSuffix, '/'), '/')
+ });
+ if (!thisPage) {
+ throw 'notFound';
+ }
+
+ if (false) {
+ // TODO: add "redirect" exit and handle mismatched capitalization / extra slashes by redirecting to the correct URL. e.g. "http://localhost:2024/docs//usiNG-fleet///" Partial example of this: https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js#L161-L166
+ let revisedUrl = 'todo';
+ throw {redirect: revisedUrl};
+ }
// Respond with view.
- return {};
+ return {
+ path: require('path'),
+ thisPage: thisPage,
+ markdownPages: sails.config.builtStaticContent.markdownPages,
+ compiledPagePartialsAppPath: sails.config.builtStaticContent.compiledPagePartialsAppPath
+ };
}
diff --git a/website/api/controllers/view-docs-template.js b/website/api/controllers/view-docs-template.js
deleted file mode 100644
index 80890b8a01..0000000000
--- a/website/api/controllers/view-docs-template.js
+++ /dev/null
@@ -1,119 +0,0 @@
-module.exports = {
-
-
- friendlyName: 'View docs template',
-
-
- description: 'Display "Docs template" page.',
-
-
- exits: {
-
- success: {
- viewTemplatePath: 'pages/docs-template'
- }
-
- },
-
-
- fn: async function () {
-
- // Respond with view.
- return {
- outline: {
- sections: [
- {
- title: 'Get started',
- topics: [], // NOTE this array is empty bc this was not stubbed out
- },
- {
- title: 'Using Fleet',
- topics: [
- {
- title: 'Fleet UI',
- subtopics: ['Running queries', 'Scheduling queries'],
- relatedTopics: ['Osquery queries', 'Osquery packs'],
- },
- {
- title: 'fleetctl',
- subtopics: [], // NOTE this array is empty bc this was not stubbed out
- relatedTopics: [], // NOTE this array is empty bc this was not stubbed out
- },
- {
- title: 'REST API',
- subtopics: [], // NOTE this array is empty bc this was not stubbed out
- relatedTopics: [], // NOTE this array is empty bc this was not stubbed out
- },
- ],
- },
- {
- title: 'Adding endpoints',
- topics: [],
- },
- {
- title: 'Deployment',
- topics: [],
- },
- {
- title: 'Contributing',
- topics: [],
- },
- ],
- },
- currentPage: {
- section: 'Using Fleet',
- topic: 'Fleet UI',
- },
- body: [
- {
- type: 'subtopic',
- content: 'Running queries',
- },
- {
- type: 'text',
- content: 'The Fleet application allows you to query hosts that you have installed osquery on. To run a new query, navigate to "Queries" from the top nav, and then hit the "Create new query" button from the Queries page. From here, you can compose your query, view SQL table documentation via the sidebar, select arbitrary hosts (or groups of hosts), and execute your query. As results are returned, they will populate the interface in real time. You can use the integrated filtering tool to perform useful initial analytics and easily export the entire dataset for offline analysis.',
- },
- {
- type: 'image',
- content: '/images/fleetctl-900x580@2x.png',
- altText: 'An image of Fleet ctl'
- },
- {
- type: 'text',
- content: 'After you\'ve composed a query that returns the information you were looking for, you may choose to save the query. You can still continue to execute the query on whatever set of hosts you would like after you have saved the query.'
- },
- {
- type: 'note',
- content: 'To learn more about scheduling queries so that they run on an on-going basis, see the scheduling queries guide below.'
- },
- {
- type: 'subtopic',
- content: 'Scheduling queries',
- },
- {
- type: 'text',
- content: 'As discussed in the running queries documentation, you can use the Fleet application to create, execute, and save osquery queries. You can organize these queries into "Query Packs". To view all saved packs and perhaps create a new pack, select "Packs" from the top nav. Packs are usually organized by the general class of instrumentation that you\'re trying to perform.',
- },
- {
- type: 'text',
- content: 'To add queries to a pack, use the right-hand sidebar. You can take an existing scheduled query and add it to the pack. You must also define a few key details such as:',
- },
- {
- type: 'bullets',
- content: {
- intro: 'To add queries to a pack, use the right-hand sidebar. You can take an existing scheduled query and add it to the pack. You must also define a few key details such as:',
- bullets: [
- 'interval: how often should the query be executed?',
- 'logging: which osquery logging format would you like to use?',
- 'platform: which operating system platforms should execute this query?',
- 'minimum osquery version: if the table was introduced in a newer version of osquery, you may want to ensure that only sufficiently recent version of osquery execute the query.',
- 'shard: from 0 to 100, what percent of hosts should execute this query?'
- ],
- }
- }
- ]
- };
-
- }
-
-};
diff --git a/website/api/controllers/view-documentation.js b/website/api/controllers/view-documentation.js
deleted file mode 100644
index 5d3bbfcaab..0000000000
--- a/website/api/controllers/view-documentation.js
+++ /dev/null
@@ -1,27 +0,0 @@
-module.exports = {
-
-
- friendlyName: 'View documentation',
-
-
- description: 'Display "Documentation" page.',
-
-
- exits: {
-
- success: {
- viewTemplatePath: 'pages/documentation'
- }
-
- },
-
-
- fn: async function () {
-
- // Respond with view.
- return {};
-
- }
-
-
-};
diff --git a/website/api/helpers/compile-markdown-content.js b/website/api/helpers/compile-markdown-content.js
deleted file mode 100644
index 3f65e3f96e..0000000000
--- a/website/api/helpers/compile-markdown-content.js
+++ /dev/null
@@ -1,206 +0,0 @@
-module.exports = {
-
-
- friendlyName: 'Compile markdown content',
-
-
- // TODO: Make this explanation better or refactor, because actually this does a lot more than just that, including cloning the source git repo
- description: 'Compile documentation templates from markdown.',
- // Also, FUTURE: dissect some of the code from here https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L16-L22
- // and use those building blocks directly instead of depending on doctemplater later and thus unnecessarily duplicating work. Also the other related code in sailsjs docs mentioned in https://github.com/fleetdm/fleet/issues/706
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // Clone repo
- // let topLvlCachePath = path.resolve(sails.config.paths.tmp, `built-static-content/`);
- // await sails.helpers.fs.rmrf(topLvlCachePath);
- // let repoCachePath = path.join(topLvlCachePath, `cloned-repo-${Date.now()}-${Math.round(Math.random()*100)}`);
- // await sails.helpers.process.executeCommand(`git clone git://github.com/fleetdm/fleet.git ${repoCachePath}`);
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- inputs: {
-
- repoPath: {
- description: 'The path of the subdirectory in the Git repo to compile from.',
- type: 'string',
- example: 'docs/',
- required: true
- },
-
- repoBranch: {
- description: 'The name of the branch to compile from.',
- type: 'string',
- defaultsTo: 'master'
- },
-
- repoUrl: {
- description: 'The git:// URL of the remote Git repo to compile from.',
- type: 'string',
- defaultsTo: 'git://github.com/fleetdm/fleet.git',
- }
-
- },
-
-
- exits: {
- success: { outputDescription: 'A list of metadata about all generated HTML files.', outputType: [{}] }
- },
-
-
- fn: async function ({repoPath, repoBranch, repoUrl}) {
-
- let path = require('path');
- let cheerio = require('cheerio');
- let DocTemplater = {};// require('doc-templater');
- if (true) {
- throw new Error('This helper has been retired. TODO: delete it.');
- }
-
- sails.log.info('Compiling `%s` docs from the `%s` branch of `%s`...', repoPath, repoBranch, repoUrl);
-
- // Relative path within this app where output will be written.
- let htmlOutputPath = path.join('views/partials/built-from-markdown/', _.kebabCase(repoPath));
-
- // Relative path within this app where temporary menu data will be written.
- // (This is consumed later in this file.)
- let jsMenuOutputPath = path.join('.tmp/doc-templater/menus/', `${_.kebabCase(repoPath)}.tmp-menu.json`);// fka "the .jsmenu file"
-
- // Delete existing output from previous runs, if any.
- await sails.helpers.fs.rmrf(path.resolve(sails.config.appPath, htmlOutputPath));
- await sails.helpers.fs.rmrf(path.resolve(sails.config.appPath, jsMenuOutputPath));
-
- // Compile the markdown into HTML files and a JSON file (aka "jsmenu") representing
- // the manifest of all compiled HTML files and their hierarchy.
- await new Promise((resolve, reject)=>{
- DocTemplater().build([{
- remote: repoUrl,
- branch: repoBranch,
- remoteSubPath: repoPath,
- outputExtension: 'ejs',//« the file extension for resulting HTML files
- htmlDirPath: htmlOutputPath,
- jsMenuPath: jsMenuOutputPath,
- beforeConvert: (mdString, proceed)=>{// This function is applied to each template before the markdown is converted to markup
- // Based on the github-flavored markdown's language annotation, (e.g. ```js```) add a temporary marker to code blocks that can be parsed post-md-compilation by the `afterConvert()` lifecycle hook
- // Note: This is an HTML comment because it is easy to over-match and "accidentally" add it underneath each code block as well (being an HTML comment ensures it doesn't show up or break anything)
- let LANG_MARKER_PREFIX = '';
- let modifiedMd = mdString.replace(/(```)([a-zA-Z0-9\-]*)(\s*\n)/g, '$1\n' + LANG_MARKER_PREFIX + '$2' + LANG_MARKER_SUFFIX + '$3');
-
- // FUTURE: implement a way of skipping non-markdown files, such as pngs (maybe here, but prbly just make it built-in)
-
- return proceed(undefined, modifiedMd);
- },
- afterConvert: (html, proceed)=>{// This function is applied to each template after the markdown is converted to markup
-
- let modifiedHtml = html;
-
- // Replace github emoji with unicode emojis
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // TODO: actually sub unicode, instead of the following (there's probably an open source lib out there to do it)
- // modifiedHtml = html.replace(/\:white_check_mark\:/g, '');
- // modifiedHtml = modifiedHtml.replace(/\:white_large_square\:/g, '');
- // modifiedHtml = modifiedHtml.replace(/\:heavy_multiplication_x\:/g, '');
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- // TODO: As equivalent concepts are identified in the Fleet docs (e.g. in the API reference), maybe bring this back:
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // // Replace ((bubble))s with HTML
- // modifiedHtml = modifiedHtml.replace(/\(\(([^())]*)\)\)/g, '
` based on the temporary marker that was added in the `beforeConvert` function above
- // console.log('RAN AFTER HOOK, found: ',modifiedHtml.match(/(]*)(>\s*)(\<!--\s*__LANG=\%[^\%]*\%__\s*--\>)/g));
- modifiedHtml = modifiedHtml.replace(// Interpret `js` as `javascript`
- // $1 $2 $3 $4
- /(]*)(>\s*)(\<!-- __LANG=\%js\%__ --\>)\s*/gm,
- '$1 class="javascript"$2$3'
- );
- modifiedHtml = modifiedHtml.replace(// Interpret `sh` and `bash` as `bash`
- // $1 $2 $3 $4
- /(]*)(>\s*)(\<!-- __LANG=\%(bash|sh)\%__ --\>)\s*/gm,
- '$1 class="bash"$2$3'
- );
- modifiedHtml = modifiedHtml.replace(// When unspecified, default to `text`
- // $1 $2 $3 $4
- /(]*)(>\s*)(\<!-- __LANG=\%\%__ --\>)\s*/gm,
- '$1 class="nohighlight"$2$3'
- );
- modifiedHtml = modifiedHtml.replace(// Finally, nab the rest, leaving the code language as-is.
- // $1 $2 $3 $4 $5 $6
- /(]*)(>\s*)(\<!-- __LANG=\%)([^%]+)(\%__ --\>)\s*/gm,
- '$1 class="$5"$2$3'
- );
-
- return proceed(undefined, modifiedHtml);
- },
- }], (err)=>{
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });//_∏_
- });
-
- // Now return the menu data, to provide a nicer way of programmatically working with it.
- // But first, we'll clean it up a bit.
- let filesGenerated = await sails.helpers.fs.readJson(jsMenuOutputPath);
-
- // TODO: bring in the other cleanup in marshal-doc-page-metadata from sailsjs.com repo
-
- // Since the format from the doc-templater package can be a little bit misleading,
- // we'll also munge the resulting data a little bit.
- for (let fileInfo of filesGenerated) {
-
- fileInfo.path = fileInfo.fullPathAndFileName;// « for clarity (it's not technically the full path)
- delete fileInfo.fullPathAndFileName;
-
- fileInfo.fallbackTitle = sails.helpers.strings.toSentenceCase(path.basename(fileInfo.templateTitle, '.ejs'));// « for clarity (the page isn't a template, necessarily, and this title is just a guess. Display title will, more likely than not, come from a tag -- see the bottom of the original, raw unformatted markdown of any page in the sailsjs docs for an example of how to use docmeta tags)
- delete fileInfo.templateTitle;
-
- delete fileInfo.data.lastModified;// « for clarity (this isn't the timestamp you're expecting, so we delete it)
- }//∞
-
- return filesGenerated;
-
- }
-
-
-};
diff --git a/website/api/helpers/strings/to-html.js b/website/api/helpers/strings/to-html.js
new file mode 100644
index 0000000000..09bfabc6df
--- /dev/null
+++ b/website/api/helpers/strings/to-html.js
@@ -0,0 +1,110 @@
+module.exports = {
+
+
+ friendlyName: 'To HTML',
+
+
+ description: 'Compile a Markdown string into an HTML string.',
+
+
+ extendedDescription:
+ 'Expects GitHub-flavored Markdown syntax. Uses [`marked`](https://github.com/chjj/marked)@v0.3.5. '+
+ 'Inspired by https://github.com/mikermcneil/machinepack-markdown/tree/5d8cee127e8ce45c702ec9bbb2b4f9bc4b7fafac',
+
+
+ moreInfoUrl: 'https://help.github.com/articles/basic-writing-and-formatting-syntax/',
+
+
+ sideEffects: 'cacheable',
+
+
+ inputs: {
+
+ mdString: {
+ description: 'Markdown string to convert',
+ example: '# hello world\n it\'s me, some markdown string \n\n ```js\n//but maybe i have code snippets too...\n```',
+ required: true
+ },
+
+ allowHtml: {
+ friendlyName: 'Allow HTML?',
+ description: 'Whether or not to allow HTML tags in the Markdown input. Defaults to `true`.',
+ extendedDescription: 'If `false`, any input that contains HTML tags will trigger the `unsafeMarkdown` exit.',
+ example: true,
+ defaultsTo: true
+ },
+
+ addIdsToHeadings: {
+ friendlyName: 'Add IDs to headings?',
+ description: 'Whether or not to add an ID attribute to rendered heading tags like ',
+ extendedDescription: 'This is not part of the Markdown specification (see http://daringfireball.net/projects/markdown/dingus), but it is the default behavior for the `marked` module. Defaults to `true`.',
+ example: true,
+ defaultsTo: true
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ outputFriendlyName: 'HTML',
+ outputExample: 'hello world
\n
it's me, some markdown string
\n//but maybe i have code snippets too...
\n'
+ },
+
+ unsafeMarkdown: {
+ friendlyName: 'Unsafe Markdown detected',
+ description: 'The provided input contained unsafe content (like HTML tags).'
+ }
+
+ },
+
+
+ fn: function(inputs, exits) {
+ var marked = require('marked');
+
+ // For full list of options, see:
+ // • https://github.com/chjj/marked
+ var markedOpts = {
+ gfm: true,
+ tables: true,
+ breaks: false,
+ pedantic: false,
+ smartLists: true,
+ smartypants: false,
+ };
+
+ if (inputs.addIdsToHeadings === false) {
+ var renderer = new marked.Renderer();
+ renderer.heading = function (text, level) {
+ return ''+text+' ';
+ };
+ markedOpts.renderer = renderer;
+ }
+
+ // Now actually compile the markdown to HTML.
+ marked(inputs.mdString, markedOpts, function afterwards (err, htmlString) {
+ if (err) { return exits.error(err); }
+
+ // If we're not allowing HTML, compile the input again with the `sanitize` option on.
+ if (inputs.allowHtml === false) {
+ markedOpts.sanitize = true;
+ marked(inputs.mdString, markedOpts, function sanitized (err, sanitizedHtmlString) {
+ if (err) { return exits.error(err); }
+
+ // Now compare the unsanitized and the sanitized output, and if they're not the same,
+ // leave through the `unsafeMarkdown` exit since it means that HTML tags were detected.
+ if (htmlString !== sanitizedHtmlString) {
+ return exits.unsafeMarkdown();
+ }
+ return exits.success(htmlString);
+ });
+ }
+ else {
+ return exits.success(htmlString);
+ }
+ });
+ }
+
+
+};
diff --git a/website/api/helpers/strings/to-sentence-case.js b/website/api/helpers/strings/to-sentence-case.js
index 12926fe3dd..4ec83dcbcc 100644
--- a/website/api/helpers/strings/to-sentence-case.js
+++ b/website/api/helpers/strings/to-sentence-case.js
@@ -21,11 +21,22 @@ module.exports = {
fn: function ({ text }) {
- // TODO: make this smarter about: "Fleet REST API" => "Fleet rEST aPI")
+
+ let KNOWN_ACRONYMS = ['JSON', 'REST', 'CLI', 'API', 'FAQ', 'QA', 'UI', 'README']; // « helps make this smarter about things like: "Fleet rEST aPI" => "Fleet REST API")
+ let KNOWN_PROPER_NOUNS = ['Fleet'];// « helps make this smarter about things like: "Deploying fleet" => "Deploying Fleet"
+
return text
.split(/[\s-_]+/)
- .filter((word, idx) => !(idx === 0 && word.match(/[0-9]+/))) // « strip off any leading numbers so first word is actually capitalized
- .map((word, idx) => (idx === 0? word[0].toUpperCase() : word[0].toLowerCase())+word.slice(1))
+ .filter((word, idx) => !(idx === 0 && word.match(/^[0-9]+$/))) // « disregard first word if it contains only numbers (this helps capitalization work as expected)
+ .map((word, idx)=>{
+ if (KNOWN_ACRONYMS.includes(word.toUpperCase())) {
+ return word.toUpperCase();
+ } else if (idx === 0 || KNOWN_PROPER_NOUNS.includes(word[0].toUpperCase() + word.slice(1).toLowerCase())) {
+ return word[0].toUpperCase() + word.slice(1).toLowerCase();
+ } else {
+ return word.toLowerCase();
+ }
+ })
.join(' ');
}
diff --git a/website/assets/dependencies/highlight.min.js b/website/assets/dependencies/highlight.min.js
new file mode 100644
index 0000000000..1a91f04583
--- /dev/null
+++ b/website/assets/dependencies/highlight.min.js
@@ -0,0 +1,2 @@
+/*! highlight.js v9.8.0 | BSD3 License | git.io/hljslicense */
+!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""+t(e)+">"}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/
/g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C=" ",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b:/,e:/(\/\w+|\w+\/)>/,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\._]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/,r:0,c:[{cN:"attr",b:e,r:0},{b:/=\s*/,r:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"?",e:"/?>",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});
\ No newline at end of file
diff --git a/website/assets/dependencies/parasails.js b/website/assets/dependencies/parasails.js
index 01f83b862e..be2d801bdb 100644
--- a/website/assets/dependencies/parasails.js
+++ b/website/assets/dependencies/parasails.js
@@ -2,7 +2,7 @@
* parasails.js
* (lightweight structures for apps with more than one page)
*
- * v0.9.2
+ * v0.9.3
*
* Copyright 2014-present, Mike McNeil (@mikermcneil)
* MIT License
@@ -167,7 +167,7 @@
// > This is particularly useful for catching loose top-level properties
// > that were intended to be within `data` or `methods`, etc.)
if (currentModuleEntityNoun === 'page script' || currentModuleEntityNoun === 'component') {
- // FUTURE: don't allow page-script only things on components
+ // FUTURE: don't allow page script only things on components
var LEGAL_TOP_LVL_KEYS = [
// Everyday page script stuff:
@@ -723,8 +723,22 @@
if (!pageName) { throw new Error('1st argument (page name) is required'); }
if (!def) { throw new Error('2nd argument (page script definition) is required'); }
+ // Don't look for a matching DOM element (by "id") within anything that has `parasails-has-no-page-script`
+ // FUTURE: Move this check a bit further below, probably without defining the variable, and just add another avast (aka early return)
+ var isWithinIgnoredElements;
+ if ($) {
+ // Note that, luckily, this works even without waiting for the DOM to be ready according to jQuery. (i.e. $(()=>{ … }))
+ isWithinIgnoredElements = (
+ $('#'+pageName).parents().filter('[parasails-has-no-page-script]')
+ ).length >= 1;
+ } else {
+ // For simplicity, this check is skipped if jQuery is not available.
+ // FUTURE: Implement with vanilla JS here
+ isWithinIgnoredElements = false;
+ }
+
// Only actually build+load this page script if it is relevant for the current contents of the DOM.
- if (!document.getElementById(pageName)) { return; }//eslint-disable-line no-undef
+ if (!window.document.getElementById(pageName) || isWithinIgnoredElements) { return; }
// Spinlock
if (didAlreadyLoadPageScript) { throw new Error('Cannot load page script (`'+pageName+') because a page script has already been loaded on this page.'); }
diff --git a/website/assets/js/pages/498.page.js b/website/assets/js/pages/498.page.js
deleted file mode 100644
index 602fc8df14..0000000000
--- a/website/assets/js/pages/498.page.js
+++ /dev/null
@@ -1,25 +0,0 @@
-parasails.registerPage('[id="498"]', {
- // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
- // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
- // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
- data: {
- //…
- },
-
- // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
- // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
- // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
- beforeMount: function() {
- //…
- },
- mounted: async function(){
- //…
- },
-
- // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
- // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
- // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
- methods: {
- //…
- }
-});
diff --git a/website/assets/js/pages/docs-template.page.js b/website/assets/js/pages/docs-template.page.js
deleted file mode 100644
index c584f3b19e..0000000000
--- a/website/assets/js/pages/docs-template.page.js
+++ /dev/null
@@ -1,60 +0,0 @@
-parasails.registerPage('docs-template', {
- // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
- // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
- // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
- data: {
- inputTextValue: '',
- inputTimers: {},
- searchString: '',
- showDocsNav: false,
- },
-
- // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
- // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
- // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
- beforeMount: function() {
- //…
- },
- mounted: async function() {
- //…
- },
-
- // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
- // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
- // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
- methods: {
- toggleDocsNav: function () {
- this.showDocsNav = !this.showDocsNav;
- },
-
- delayInput: function (callback, ms, label) {
- let inputTimers = this.inputTimers;
- return function () {
- label = label || 'defaultTimer';
- _.has(inputTimers, label) ? clearTimeout(inputTimers[label]) : 0;
- inputTimers[label] = setTimeout(callback, ms);
- };
- },
-
- setSearchString: function () {
- this.searchString = this.inputTextValue;
- },
-
- getSubtopics: function () {
- return this.body.filter((item) => item.type === 'subtopic')
- .map((item) => item.content);
- },
-
- getRelatedTopics: function () {
- try {
- const sectionIndex = this.outline.sections.findIndex((section) => section.title === this.currentPage.section);
- const topicIndex = this.outline.sections[sectionIndex].topics.findIndex((topic) => topic.title === this.currentPage.topic);
- return this.outline.sections[sectionIndex].topics[topicIndex].relatedTopics;
- } catch (error) {
- console.log(error);
- return [];
- }
- },
-
- }
-});
diff --git a/website/assets/js/pages/docs/basic-documentation.page.js b/website/assets/js/pages/docs/basic-documentation.page.js
index cfcd797288..23d4573095 100644
--- a/website/assets/js/pages/docs/basic-documentation.page.js
+++ b/website/assets/js/pages/docs/basic-documentation.page.js
@@ -3,23 +3,214 @@ parasails.registerPage('basic-documentation', {
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
- //…
+
+ isDocsLandingPage: false,
+
+ inputTextValue: '',
+ inputTimers: {},
+ searchString: '',
+ showDocsNav: false,
+
+ breadcrumbs: [],
+ pages: [],
+ pagesBySectionSlug: {},
+ subtopics: [],
+ relatedTopics: [],
+
+ },
+
+ computed: {
+ currentLocation: function () {
+ return window.location.href;
+ }
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
- //…
+ if (this.thisPage.url === '/docs') {
+ this.isDocsLandingPage = true;
+ }
+
+ this.breadcrumbs = _.trim(this.thisPage.url, /\//).split(/\//);
+
+ this.pages = _.sortBy(this.markdownPages, 'htmlId');
+
+ this.pagesBySectionSlug = (() => {
+ const DOCS_SLUGS = ['using-fleet', 'deploying', 'contributing'];
+
+ let sectionSlugs = _.uniq(_.pluck(this.pages, 'url').map((url) => url.split(/\//).slice(-2)[0]));
+
+ let pagesBySectionSlug = {};
+
+ for (let sectionSlug of sectionSlugs) {
+ pagesBySectionSlug[sectionSlug] = _
+ .chain(this.pages)
+ .filter((page) => {
+ return sectionSlug === page.url.split(/\//).slice(-2)[0];
+ })
+ .sortBy((page) => {
+ // custom sort function is needed because simple sort of alphanumeric htmlIds strings
+ // does not appropriately handle double-digit strings
+ try {
+ // attempt to split htmlId and parse out its ordinal value (e.g., `docs--10-teams--xxxxxxxxxx`)
+ let sortValue = page.htmlId.split(/--/)[1].split(/-/)[0];
+ return parseInt(sortValue) || sortValue;
+ } catch (error) {
+ // something unexpected happened so just return the htmlId and continue sort
+ console.log(error);
+ return page.htmlId;
+ }
+ })
+ .value();
+ }
+
+ // We need to re-sort the top-level sections because their htmlIds do not reflect the correct order
+ pagesBySectionSlug['docs'] = DOCS_SLUGS.map((slug) => {
+ return pagesBySectionSlug['docs'].find((page) => slug === _.kebabCase(page.title));
+ });
+
+ // We need to move any FAQs to the end of its array
+ for (let slug of DOCS_SLUGS) {
+ let pages = pagesBySectionSlug[slug];
+ let index = pages.findIndex((page) => page.title === 'FAQ');
+ if (index === -1 || index === pages.length - 1) {
+ break;
+ } else {
+ let removedPage = _.pullAt(pages, index);
+ pages.push(...removedPage);
+ pagesBySectionSlug[slug] = pages;
+ }
+ }
+
+ return pagesBySectionSlug;
+ })();
},
+
mounted: async function() {
- //…
+
+ // // Alternative jQuery approach to grab `on this page` links from top of markdown files
+ // let subtopics = $('#body-content').find('h1 + ul').children().map((_, el) => el.innerHTML);
+ // subtopics = $.makeArray(subtopics);
+ // console.log(subtopics);
+
+ this.subtopics = (() => {
+ let subtopics = $('#body-content').find('h2').map((_, el) => el.innerHTML);
+ subtopics = $.makeArray(subtopics).map((title) => {
+ return {
+ title,
+ url: '#' + _.kebabCase(title),
+ };
+ });
+ return subtopics;
+ })();
+
+ // https://github.com/sailshq/sailsjs.com/blob/7a74d4901dcc1e63080b502492b03fc971d3d3b2/assets/js/functions/sails-website-actions.js#L177-L239
+ (function highlightThatSyntax(){
+ $('pre code').each((i, block) => {
+ window.hljs.highlightBlock(block);
+ });
+
+ // Make sure the tags whose code isn't being highlighted
+ // has that nice muted look we like.
+ $('.nohighlight').each(function() {
+ var $codeBlock = $(this);
+ $codeBlock.closest('pre').addClass('muted');
+ });
+ // Also make sure the 'usage' (and 'usage-*') code blocks have special styles.
+ $('.usage,.usage-exec').each(function() {
+ var $codeBlock = $(this);
+ $codeBlock.closest('pre').addClass('usage-wrapper');
+ });
+
+ // Now let's make the `function` keywords blue like in sublime.
+ $('.hljs-keyword').each(function() {
+ var $highlightedKeyword = $(this);
+ if($highlightedKeyword.text() === 'function') {
+ $highlightedKeyword.removeClass('hljs-keyword');
+ $highlightedKeyword.addClass('hljs-function-keyword');
+ }
+ });
+
+ $('.hljs-built_in').each(function() {
+ var $builtIn = $(this);
+ var $parentCode = $builtIn.closest('code');
+ var isJavascriptSyntax = $parentCode.hasClass('javascript');
+ var isBashSyntax = $parentCode.hasClass('bash');
+ // ...and make the `require()`s not yellow, also like in sublime.
+ if(isJavascriptSyntax && $builtIn.text() === 'require') {
+ $builtIn.removeClass('hljs-built_in');
+ }
+ // And don't highlight the word 'test' in the bash examples, e.g. for ('sails new test-project')
+ if(isBashSyntax && $builtIn.text() === 'test') {
+ $builtIn.removeClass('hljs-built_in');
+ }
+ });
+ })();
+
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
- //…
+
+ clickCTA: function (slug) {
+ window.location = slug;
+ },
+
+ isCurrentSection: function (section) {
+ if (_.trim(this.thisPage.url, (/\//)).split(/\//).includes(_.last(_.trimRight(section.url, (/\//)).split(/\//)))) {
+ return true;
+ }
+ return false;
+ },
+
+ findPagesByUrl: function (url='') {
+ let slug;
+ // if no url is passed, use the base url as the slug (e.g., 'docs' or 'handbook')
+ if (!url) {
+ slug = _.trim(this.thisPage.url, /\//).split(/\//)[0];
+ } else {
+ slug = _.last(url.split(/\//));
+ }
+
+ return this.pagesBySectionSlug[slug];
+ },
+
+ getActiveSubtopicClass: function (currentLocation, url) {
+ return _.last(currentLocation.split(/#/)) === _.last(url.split(/#/)) ? 'active' : '';
+ },
+
+ getTitleFromUrl: function (url) {
+ return _
+ .chain(url.split(/\//))
+ .last()
+ .split(/-/)
+ .map((str) => str === 'fleet' ? 'Fleet' : str)
+ .join(' ')
+ .capitalize()
+ .value();
+ },
+
+ toggleDocsNav: function () {
+ this.showDocsNav = !this.showDocsNav;
+ },
+
+ delayInput: function (callback, ms, label) {
+ let inputTimers = this.inputTimers;
+ return function () {
+ label = label || 'defaultTimer';
+ _.has(inputTimers, label) ? clearTimeout(inputTimers[label]) : 0;
+ inputTimers[label] = setTimeout(callback, ms);
+ };
+ },
+
+ setSearchString: function () {
+ this.searchString = this.inputTextValue;
+ },
+
}
+
});
diff --git a/website/assets/js/pages/documentation.page.js b/website/assets/js/pages/documentation.page.js
deleted file mode 100644
index c32e1d9e51..0000000000
--- a/website/assets/js/pages/documentation.page.js
+++ /dev/null
@@ -1,102 +0,0 @@
-parasails.registerPage('documentation', {
- // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
- // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
- // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
- data: {
- inputTextValue: '',
- inputTimers: {},
- searchString: '',
- showDocsNav: false,
- tree: [
- {
- title: 'Using Fleet',
- children: [
- 'Fleet UI',
- 'fleetctl',
- 'REST API',
- 'Osquery logs',
- 'Monitoring Fleet',
- 'Security best practices',
- 'Updating Fleet',
- 'FAQ - Using Fleet'
- ]
- },
- {
- title: 'Deploying',
- children: [
- 'Installation',
- 'Configuration',
- 'Adding hosts',
- 'Osquery logs',
- 'Example deployment scenarios',
- 'Self-managed agent updates',
- 'FAQ - Deploying'
- ]
- },
- {
- title: 'Contributing',
- children: [
- 'Building Fleet',
- 'Testing',
- 'Migrations',
- 'Committing changes',
- 'Releasing Fleet',
- 'FAQ - Contributing'
- ]
- }
- ]
- },
-
- // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
- // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
- // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
- beforeMount: function() {
- //…
- },
- mounted: async function() {
- //…
- },
-
- // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
- // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
- // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
- methods: {
- toggleDocsNav: function () {
- this.showDocsNav = !this.showDocsNav;
- },
-
- clickCTA: function (slug) {
- window.location = slug;
- },
-
- delayInput: function (callback, ms, label) {
- let inputTimers = this.inputTimers;
- return function () {
- label = label || 'defaultTimer';
- _.has(inputTimers, label) ? clearTimeout(inputTimers[label]) : 0;
- inputTimers[label] = setTimeout(callback, ms);
- };
- },
-
- setSearchString: function () {
- this.searchString = this.inputTextValue;
- },
-
- getSubtopics: function () {
- return this.body.filter((item) => item.type === 'subtopic')
- .map((item) => item.content);
- },
-
- getRelatedTopics: function () {
- try {
- const sectionIndex = this.outline.sections.findIndex((section) => section.title === this.currentpage.section);
- const topicIndex = this.outline.sections[sectionIndex].topics.findIndex((topic) => topic.title === this.currentpage.topic);
- return this.outline.sections[sectionIndex].topics[topicIndex].relatedTopics;
- } catch (error) {
- console.log(error);
- return [];
- }
- },
-
- }
-});
diff --git a/website/assets/styles/importer.less b/website/assets/styles/importer.less
index 1a9b53b8b6..5e033b845f 100644
--- a/website/assets/styles/importer.less
+++ b/website/assets/styles/importer.less
@@ -48,7 +48,4 @@
@import 'pages/docs/basic-documentation.less';
@import 'pages/handbook/basic-handbook.less';
-@import 'pages/documentation.less';
-@import 'pages/docs-template.less';
-
@import 'pages/transparency.less';
diff --git a/website/assets/styles/pages/docs-template.less b/website/assets/styles/pages/docs-template.less
deleted file mode 100644
index 90d35cc7dc..0000000000
--- a/website/assets/styles/pages/docs-template.less
+++ /dev/null
@@ -1,145 +0,0 @@
-#docs-template {
-
- h3 {
- font-size: 28px;
- line-height: 36px;
- }
-
- h6 {
- font-family: 'Nunito';
- }
-
- a {
- color: @core-vibrant-blue;
- }
-
- ul {
- list-style: none;
- padding-left: 8px;
- li {
- padding-bottom: 8px;
- }
- .topic {
- &.active {
- color: @core-vibrant-blue;
- }
- }
- }
-
- input {
- padding-top: 6px;
- padding-bottom: 6px;
- &::placeholder {
- font-size: 16px;
- line-height: 24px;
- }
- }
-
- .input-group-text {
- color: #8b8fa2;
- border-color: #c5c7d1;
- border-top-left-radius: 8px;
- border-bottom-left-radius: 8px;
- }
-
- .form-control {
- height: 40px;
- font-size: 16px;
- border-color: #c5c7d1;
- border-top-right-radius: 8px;
- border-bottom-right-radius: 8px;
- &:focus {
- border: 1px solid #c5c7d1;
- }
- }
-
- .subtopic {
- color: @core-vibrant-blue;
- }
-
- .left-sidebar {
- border-right: 1px solid @core-fleet-black-25;
- a {
- color: @core-fleet-black;
- text-decoration: none;
- &.btn-primary {
- color: #fff;
- }
- }
- .left-nav {
- border-bottom: 1px solid @core-fleet-black-25;
- }
- }
-
- .btn-primary {
- color: #fff;
- background-color: #ff5c83;
- border-color: #ff5c83;
- &:hover {
- background-color: darken(#ff5c83, 10%);
- border-color: darken(#ff5c83, 10%);
- }
- }
-
- .content {
- ul {
- list-style-type: disc;
- padding-left: 32px;
- }
-
- .note {
- background-color: @core-vibrant-blue-10;
- border-radius: 12px;
- text-decoration: none;
- }
- }
-
- .docs-nav-button {
- color: @core-fleet-black;
- background-color: transparent;
- font-family: 'Nunito';
- font-weight: 400;
- font-size: 16px;
- padding: 0;
- padding-top: 12px;
- padding-bottom: 12px;
- border-bottom: 1px solid @core-vibrant-blue-15;
- border-radius: 0px;
- &:focus {
- box-shadow: none;
- }
- }
-
- .mobile-docs-nav {
- a {
- color: @core-fleet-black;
- text-decoration: none;
- }
- }
-
- @media (min-width: 993px) {
- ul {
- .topic {
- padding-left: 8px;
- }
- }
- .subtopic {
- color: @core-fleet-black;
- padding-top: 4px;
- padding-bottom: 4px;
- border-left: 1px solid @core-vibrant-blue-25;
- .active {
- position: absolute;
- height: 32px;
- transform: translate(-2px, -4px);
- border-left: 3px solid @core-vibrant-blue;
- border-radius: 2px;
- }
- }
- .input-group {
- min-width: 400px;
- }
- }
-
-
-}
diff --git a/website/assets/styles/pages/docs/basic-documentation.less b/website/assets/styles/pages/docs/basic-documentation.less
index fa4dc02b5f..07eb26a5f3 100644
--- a/website/assets/styles/pages/docs/basic-documentation.less
+++ b/website/assets/styles/pages/docs/basic-documentation.less
@@ -1,13 +1,466 @@
#basic-documentation {
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // For a starting point on how to approach styling this generated HTML, please see @RachelElysia's draft in:
- // https://github.com/fleetdm/fleet/commit/bfb758d9dc81c423538f75fc86244b06cc810c1a
- // https://github.com/fleetdm/fleet/commit/e4c77b981627bde0e6225c805c89143467a9bca1
- //
- // Note that handbook and documentation can probably share many of the same styles. (Consider a dedicated mixin.)
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ h1 {
+ font-size: 36px;
+ line-height: 48px;
+ }
- //…
+ h3 {
+ font-size: 22px;
+ line-height: 36px;
+ }
+
+ h4 {
+ font-size: 18px;
+ padding-top: 24px;
+ }
+
+ h6 {
+ font-family: 'Nunito';
+ }
+
+ a {
+ color: @core-vibrant-blue;
+ }
+
+ ul {
+ list-style: none;
+ padding-left: 8px;
+
+ li {
+ padding-bottom: 8px;
+ }
+
+ }
+
+ [purpose='docs-landing-page'] {
+
+ h3 {
+ font-size: 24px;
+ line-height: 32px;
+ }
+
+ a {
+ color: @core-vibrant-blue;
+ }
+
+ [purpose='search'] {
+
+ input {
+ padding-top: 6px;
+ padding-bottom: 6px;
+
+ &::placeholder {
+ font-size: 16px;
+ line-height: 24px;
+
+ }
+
+ }
+
+ .input-group-text {
+ color: @core-fleet-black-50;
+ border-color: @core-fleet-black-25;
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ }
+
+ .form-control {
+ height: 48px;
+ font-size: 16px;
+ border-color: @core-fleet-black-25;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+
+ &:focus {
+ border: 1px solid @core-fleet-black-25;
+ }
+
+ }
+
+ }
+
+ [purpose='cta-cards'] {
+
+ .cta-card {
+ cursor: pointer;
+ border: 1px solid @ui-gray;
+ border-radius: 8px;
+ box-shadow: 1px 2px 2px rgba(197, 199, 209, 0.2);
+
+ .cta-image {
+ max-height: 60px;
+ width: auto;
+ }
+
+ .cta-text {
+
+ a {
+ color: @core-fleet-black;
+ text-decoration: none;
+ }
+
+ img {
+ padding-left: 8px;
+ transition: 0.2s ease-in-out;
+ -o-transition: 0.2s ease-in-out;
+ -ms-transition: 0.2s ease-in-out;
+ -moz-transition: 0.2s ease-in-out;
+ -webkit-transition: 0.2s ease-in-out;
+ }
+
+ &:hover {
+
+ .arrow {
+
+ img {
+ padding-left: 11px;
+ transition: 0.2s ease-in-out;
+ -o-transition: 0.2s ease-in-out;
+ -ms-transition: 0.2s ease-in-out;
+ -moz-transition: 0.2s ease-in-out;
+ -webkit-transition: 0.2s ease-in-out;
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ [purpose='docs-tree'] {
+
+ a {
+ color: @core-fleet-black;
+ text-decoration: none;
+
+ &:hover {
+ color: @core-vibrant-blue;
+ }
+
+ }
+
+ }
+
+ @media (min-width: 768px) {
+
+ .cta-card {
+ max-width: 437px;
+ min-height: 160px;
+
+ .cta-image {
+ max-height: 72px;
+ }
+
+ }
+
+ }
+
+ }
+
+
+ [purpose='docs-template'] {
+
+ h3 {
+
+ code {
+ color: @core-fleet-black;
+ }
+
+ }
+
+ [purpose='search'] {
+
+ input {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ border-radius: 6px;
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+
+ &::placeholder {
+ font-size: 16px;
+ line-height: 24px;
+ }
+
+ }
+
+ .input-group-prepend {
+ border-radius: 6px;
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+
+ }
+
+ .input-group-text {
+ color: #8b8fa2;
+ border-color: #c5c7d1;
+ border-top-left-radius: 8px;
+ border-bottom-left-radius: 8px;
+ }
+
+ .form-control {
+ height: 40px;
+ font-size: 16px;
+ border-color: #c5c7d1;
+ border-top-right-radius: 8px;
+ border-bottom-right-radius: 8px;
+
+ &:focus {
+ border: 1px solid #c5c7d1;
+ }
+
+ }
+
+ }
+
+ [purpose='breadcrumbs'] {
+
+ p, a {
+ font-size: 14px;
+ }
+
+ }
+
+ [purpose='mobile-docs-nav'] {
+
+ a {
+ color: @core-fleet-black;
+ text-decoration: none;
+
+ &.active {
+ color: @core-vibrant-blue;
+ }
+
+ }
+
+ [purpose='docs-nav-button'] {
+ color: @core-fleet-black;
+ background-color: transparent;
+ font-family: 'Nunito';
+ font-weight: 400;
+ font-size: 16px;
+ padding: 0;
+ padding-top: 12px;
+ padding-bottom: 12px;
+
+ &:focus {
+ box-shadow: none;
+ }
+
+ }
+
+ }
+
+ [purpose='left-sidebar'] {
+ font-size: 14px;
+ border-right: 1px solid @core-fleet-black-25;
+
+ a {
+ color: @core-fleet-black;
+ text-decoration: none;
+
+ &:hover {
+ color: #6A67FE;
+ }
+
+ &.btn-primary {
+ color: #fff;
+ }
+
+ .btn-primary {
+ color: #fff;
+ background-color: #ff5c83;
+ border-color: #ff5c83;
+
+ &:hover {
+ background-color: darken(#ff5c83, 10%);
+ border-color: darken(#ff5c83, 10%);
+ }
+
+ }
+
+ }
+
+ .left-nav {
+ border-bottom: 1px solid @core-fleet-black-25;
+ }
+
+ .topic {
+
+ &.active {
+ color: @core-vibrant-blue;
+ }
+
+ }
+
+ }
+
+ [purpose='right-sidebar'] {
+
+ p, a {
+ font-size: 14px;
+ }
+
+ a {
+ &:hover {
+ color: #6A67FE;
+ }
+ }
+
+ }
+
+ [purpose='content'] {
+
+ h1 {
+ padding-bottom: 16px;
+ }
+
+ h2 {
+ font-size: 28px;
+ line-height: 36px;
+ padding-top: 24px;
+ padding-bottom: 24px;
+ }
+
+ h3 {
+ font-size: 24px;
+ line-height: 28px;
+ padding-top: 24px;
+ padding-bottom: 24px;
+ margin-bottom: 0px;
+ }
+
+ ul {
+ list-style-type: disc;
+ padding-left: 32px;
+ }
+
+ img {
+ display: flex;
+ width: 100%;
+ height: auto;
+ padding-top: 24px;
+ padding-bottom: 24px;
+ }
+
+ h1 + ul {
+ display: none; // Hides links at top of some markdown files
+ }
+
+ h2 + h3 {
+ padding-top: 8px;
+ }
+
+ .note {
+ background-color: @core-vibrant-blue-10;
+ border-radius: 12px;
+ text-decoration: none;
+ }
+
+ }
+
+ // for smaller screens
+ @media (max-width: 991px) {
+
+ [purpose='right-sidebar'] {
+ width: 100%;
+
+ [purpose='subtopics'] {
+ color: @core-fleet-black;
+
+ a {
+ color: @core-vibrant-blue;
+ }
+
+ }
+
+ }
+
+ [purpose='content'] {
+ width: 100%;
+
+ h1:first-of-type {
+ display: none; // hides on mobile
+ }
+
+ }
+
+ }
+
+ // for larger screens
+ @media (min-width: 992px) {
+
+ ul {
+ .topic {
+ padding-left: 8px;
+ }
+ }
+
+ [purpose='search'] {
+
+ .input-group {
+ min-width: 400px;
+ }
+
+ }
+
+ [purpose='page-title'] {
+
+ h1 {
+ display: block;
+ }
+
+ }
+
+ [purpose='right-sidebar'] {
+ min-width: 190px;
+ max-width: 210px;
+
+ [purpose='subtopics'] {
+ color: @core-fleet-black;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ border-left: 1px solid @core-vibrant-blue-25;
+
+ a {
+ color: @core-fleet-black;
+ text-decoration: none;
+
+ &:hover {
+ color: @core-vibrant-blue;
+ }
+
+ }
+
+ // .active {
+ // color: @core-vibrant-blue;
+ // position: absolute;
+ // height: 32px;
+ // transform: translate(-2px, -4px);
+ // border-left: 3px solid @core-vibrant-blue;
+ // border-radius: 2px;
+ // a {
+ // color: @core-vibrant-blue;
+ // }
+ // }
+
+ }
+
+ }
+
+ [purpose='content'] {
+ min-width: 0px; // in order for elements to shrink properly, the parent flex element needs to override min-width auto
+ }
+
+ }
+
+ }
+
+ @import 'code-blocks.less'; // styles for code blocks and hljs
}
diff --git a/website/assets/styles/pages/docs/code-blocks.less b/website/assets/styles/pages/docs/code-blocks.less
new file mode 100644
index 0000000000..0f310725f9
--- /dev/null
+++ b/website/assets/styles/pages/docs/code-blocks.less
@@ -0,0 +1,103 @@
+// lesshint-disable spaceAroundComma, trailingWhitespace
+
+ pre {
+ background: #282C40;
+ padding: 24px;
+ border: 1px solid @core-fleet-black-25;
+ border-radius: 4px;
+ margin: 16px 0px 32px;
+
+
+ &.muted {
+ color: @core-fleet-black-50;
+ background: @ui-off-white;
+ margin: 16px 0px 32px;
+ }
+ }
+
+ .hljs {
+ background: #282C40;
+ }
+
+ /*
+
+ Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/
+
+ */
+
+ .hljs,
+ .hljs-tag,
+ .hljs-built_in,
+ .hljs-subst {
+ color: #f8f8f2;
+ }
+
+ .hljs-strong,
+ .hljs-emphasis {
+ color: #a8a8a2;
+ }
+
+ .hljs-bullet,
+ .hljs-quote,
+ .hljs-number,
+ .hljs-regexp,
+ .hljs-literal,
+ .hljs-link {
+ color: #ae81ff;
+ }
+
+ .hljs-code,
+ .hljs-title,
+ .hljs-section,
+ .hljs-selector-class {
+ color: #a6e22e;
+ }
+
+ .hljs-strong {
+ font-weight: bold;
+ }
+
+ .hljs-emphasis {
+ font-style: italic;
+ }
+
+ .hljs-keyword,
+ .hljs-selector-tag,
+ .hljs-name {
+ color: #f92672;
+ }
+
+ .hljs-symbol,
+ .hljs-attribute,
+ .hljs-function-keyword {
+ color: #66d9ef;
+ }
+
+ .hljs-attr,
+ .hljs-class .hljs-title {
+ color: #f8f8f2;
+ }
+
+ .hljs-params {
+ color: #fd9720;
+ }
+
+ .hljs-string,
+ .hljs-type,
+ .hljs-builtin-name,
+ .hljs-selector-id,
+ .hljs-selector-attr,
+ .hljs-selector-pseudo,
+ .hljs-addition,
+ .hljs-variable,
+ .hljs-template-variable {
+ color: #e6db74;
+ }
+
+ .hljs-comment,
+ .hljs-deletion,
+ .hljs-meta {
+ color: #75715e;
+ }
+
+
diff --git a/website/assets/styles/pages/documentation.less b/website/assets/styles/pages/documentation.less
deleted file mode 100644
index db6144a59a..0000000000
--- a/website/assets/styles/pages/documentation.less
+++ /dev/null
@@ -1,97 +0,0 @@
-#documentation {
-
- h3 {
- font-size: 24px;
- line-height: 32px;
- }
-
- a {
- color: @core-vibrant-blue;
- }
-
- input {
- padding-top: 6px;
- padding-bottom: 6px;
- &::placeholder {
- font-size: 16px;
- line-height: 24px;
- }
- }
-
- .input-group-text {
- color: @core-fleet-black-50;
- border-color: @core-fleet-black-25;
- border-top-left-radius: 6px;
- border-bottom-left-radius: 6px;
- }
-
- .form-control {
- height: 48px;
- font-size: 16px;
- border-color: @core-fleet-black-25;
- border-top-right-radius: 6px;
- border-bottom-right-radius: 6px;
- &:focus {
- border: 1px solid @core-fleet-black-25;
- }
- }
-
- .cta-card {
- cursor: pointer;
- border: 1px solid @ui-gray;
- border-radius: 8px;
- box-shadow: 1px 2px 2px rgba(197, 199, 209, 0.2);
- .cta-image {
- max-height: 60px;
- width: auto;
- }
- .cta-text {
- a {
- color: @core-fleet-black;
- text-decoration: none;
- }
- img {
- padding-left: 8px;
- transition: 0.2s ease-in-out;
- -o-transition: 0.2s ease-in-out;
- -ms-transition: 0.2s ease-in-out;
- -moz-transition: 0.2s ease-in-out;
- -webkit-transition: 0.2s ease-in-out;
- }
- &:hover {
- .arrow {
- img {
- padding-left: 11px;
- transition: 0.2s ease-in-out;
- -o-transition: 0.2s ease-in-out;
- -ms-transition: 0.2s ease-in-out;
- -moz-transition: 0.2s ease-in-out;
- -webkit-transition: 0.2s ease-in-out;
- }
- }
- }
-
- }
- }
-
- .tree {
- a {
- color: @core-fleet-black;
- text-decoration: none;
- }
- }
-
- @media (min-width: 768px) {
- .cta-card {
- max-width: 437px;
- min-height: 160px;
- .cta-image {
- max-height: 72px;
- }
- }
- }
-
-
-
-
-}
diff --git a/website/config/env/development.js b/website/config/env/development.js
index cb012901eb..022b6ad2e1 100644
--- a/website/config/env/development.js
+++ b/website/config/env/development.js
@@ -17,10 +17,7 @@ module.exports = {
// Add any dev-only routes for local development of not-yet-released pages.
// e.g. http://localhost:2024/sandbox/example-query
routes: {
- 'GET /sandbox/docs/*': { skipAssets: false, action: 'docs/view-basic-documentation' },// « to see it, check out http://localhost:2024/sandbox/documentation/adsg
- 'GET /sandbox/handbook/*': { skipAssets: false, action: 'handbook/view-basic-handbook' },// « to see it, check out http://localhost:2024/sandbox/handbook/adsg
- 'GET /sandbox/documentation': { action: 'view-documentation' },
- 'GET /sandbox/docs-template': { action: 'view-docs-template' },
+
},
};
diff --git a/website/config/policies.js b/website/config/policies.js
index a50833a15c..ae68f5ba0b 100644
--- a/website/config/policies.js
+++ b/website/config/policies.js
@@ -29,8 +29,6 @@ module.exports.policies = {
'docs/*': true,
'handbook/*': true,
'download-sitemap': true,
- 'view-docs-template': true,
- 'view-documentation': true,
'view-transparency': true,
};
diff --git a/website/config/routes.js b/website/config/routes.js
index 2d1355be0b..e1158c4a96 100644
--- a/website/config/routes.js
+++ b/website/config/routes.js
@@ -18,24 +18,37 @@ module.exports.routes = {
'GET /get-started': { action: 'view-pricing' },
'GET /install': 'https://github.com/fleetdm/fleet/blob/main/README.md', // « FUTURE: When ready, bring back { action: 'view-get-started' }
- '/docs': 'https://github.com/fleetdm/fleet/tree/main/docs',
'/hall-of-fame': 'https://github.com/fleetdm/fleet/pulse',
'/company/about': '/handbook', // FUTURE: brief "about" page explaining the origins of the company
- '/handbook': 'https://github.com/fleetdm/fleet/tree/main/handbook',
'GET /queries': { action: 'view-query-library' },
'GET /queries/:slug': { action: 'view-query-detail' },
- '/contribute': 'https://github.com/fleetdm/fleet/tree/main/docs/3-Contributing',
+ 'GET /docs/?*': { skipAssets: false, action: 'docs/view-basic-documentation' },// handles /docs and /docs/foo/bar
+ // 'GET /handbook/?*': { skipAssets: false, action: 'handbook/view-basic-handbook' },// handles /handbook and /handbook/foo/bar
+ 'GET /handbook': 'https://github.com/fleetdm/fleet/tree/main/handbook',// TODO: Bring back the above when styles are ready
+
+ '/contribute': '/docs/contribute',
'/company/stewardship': 'https://github.com/fleetdm/fleet', // FUTURE: page about how we approach open source and our commitments to the community
'/legal/terms': 'https://docs.google.com/document/d/1OM6YDVIs7bP8wg6iA3VG13X086r64tWDqBSRudG4a0Y/edit',
'/security': 'https://github.com/fleetdm/fleet/security/policy',
'GET /transparency': { action: 'view-transparency' },
- 'GET /apply': 'https://fleet-device-management.breezy.hr',
+ 'GET /apply': 'https://fleet-device-management.breezy.hr',
+ // ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗
+ // ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗
+ // ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝
+ // ┌─ ┌─┐┌─┐┬─┐ ┌┐ ┌─┐┌─┐┬┌─┬ ┬┌─┐┬─┐┌┬┐┌─┐ ┌─┐┌─┐┌┬┐┌─┐┌─┐┌┬┐ ─┐
+ // │ ├┤ │ │├┬┘ ├┴┐├─┤│ ├┴┐│││├─┤├┬┘ ││└─┐ │ │ ││││├─┘├─┤ │ │
+ // └─ └ └─┘┴└─ └─┘┴ ┴└─┘┴ ┴└┴┘┴ ┴┴└──┴┘└─┘ └─┘└─┘┴ ┴┴ ┴ ┴ ┴o ─┘
+ // Use these redirects for deprecated/legacy links, so that they go to an appropriate new place instead of just being broken when docs/etc move or get renamed.
+ // > Note that these redirects take precedence over less specific wildcard routes like '/docs/*' and '/handbook/*'
+
+ 'GET /docs/using-fleet/some-deprecated-link-like-this': '/docs/using-fleet/supported-browsers',// « this is just an example to show how
+
// ╔╦╗╦╔═╗╔═╗ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ ┬ ╔╦╗╔═╗╦ ╦╔╗╔╦ ╔═╗╔═╗╔╦╗╔═╗
// ║║║║╚═╗║ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ ┌┼─ ║║║ ║║║║║║║║ ║ ║╠═╣ ║║╚═╗
// ╩ ╩╩╚═╝╚═╝ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝ └┘ ═╩╝╚═╝╚╩╝╝╚╝╩═╝╚═╝╩ ╩═╩╝╚═╝
diff --git a/website/package.json b/website/package.json
index f6e68ff0d6..bd68cd3de8 100644
--- a/website/package.json
+++ b/website/package.json
@@ -9,19 +9,19 @@
"@sailshq/lodash": "^3.10.3",
"@sailshq/socket.io-redis": "^5.2.0",
"machinepack-github": "^5.0.0",
- "sails": "^1.4.3",
- "sails-hook-apianalytics": "^2.0.3",
- "sails-hook-organics": "^2.0.0",
- "sails-hook-orm": "^2.1.1",
- "sails-hook-sockets": "^2.0.0",
+ "sails": "^1.4.4",
+ "sails-hook-apianalytics": "^2.0.5",
+ "sails-hook-organics": "^2.2.0",
+ "sails-hook-orm": "^3.0.2",
+ "sails-hook-sockets": "^2.0.1",
"sails-postgresql": "^2.0.0"
},
"devDependencies": {
- "cheerio": "0.18.0",
"eslint": "5.16.0",
"grunt": "1.0.4",
"htmlhint": "0.11.0",
"lesshint": "6.3.6",
+ "marked": "0.3.5",
"sails-hook-grunt": "^4.0.0",
"yaml": "1.10.2"
},
diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js
index 5f53d8a24f..075d235b84 100644
--- a/website/scripts/build-static-content.js
+++ b/website/scripts/build-static-content.js
@@ -24,7 +24,7 @@ module.exports = {
let builtStaticContent = {};
await sails.helpers.flow.simultaneously([
- async()=>{// Parse query library from YAML and bake them into the Sails app's configuration.
+ async()=>{// Parse query library from YAML and prepare to bake them into the Sails app's configuration.
let RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO = 'docs/1-Using-Fleet/standard-query-library/standard-query-library.yml';
let yaml = await sails.helpers.fs.read(path.join(topLvlRepoPath, RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO));
@@ -72,7 +72,10 @@ module.exports = {
// Talk to GitHub and get additional information about each contributor.
let githubDataByUsername = {};
await sails.helpers.flow.simultaneouslyForEach(githubUsernames, async(username)=>{
- githubDataByUsername[username] = await sails.helpers.http.get('https://api.github.com/users/' + encodeURIComponent(username), {}, { 'User-Agent': 'Fleet-Standard-Query-Library', Accept: 'application/vnd.github.v3+json' });
+ githubDataByUsername[username] = await sails.helpers.http.get.with({
+ url: 'https://api.github.com/users/' + encodeURIComponent(username),
+ headers: { 'User-Agent': 'Fleet-Standard-Query-Library', Accept: 'application/vnd.github.v3+json' }
+ });
});//∞
// Now expand queries with relevant profile data for the contributors.
@@ -90,23 +93,24 @@ module.exports = {
query.contributors = contributorProfiles;
}
- // Attach to Sails app configuration.
+ // Attach to what will become configuration for the Sails app.
builtStaticContent.queries = queries;
builtStaticContent.queryLibraryYmlRepoPath = RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO;
},
- async()=>{// Parse markdown pages, compile & generate HTML files, and bake documentation's directory tree into the Sails app's configuration.
+ async()=>{// Parse markdown pages, compile & generate HTML files, and prepare to bake directory trees into the Sails app's configuration.
+ let APP_PATH_TO_COMPILED_PAGE_PARTIALS = 'views/partials/built-from-markdown';
- // Note:
- // • path maths inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L107-L132
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // // Original way that works: (versus new stuff below)
- // builtStaticContent.markdownPages = await sails.helpers.compileMarkdownContent('docs/'); // TODO remove this and helper once everything works again
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Delete existing HTML output from previous runs, if any.
+ await sails.helpers.fs.rmrf(path.resolve(sails.config.appPath, APP_PATH_TO_COMPILED_PAGE_PARTIALS));
builtStaticContent.markdownPages = [];// « dir tree representation that will be injected into Sails app's configuration
- let SECTION_REPO_PATHS = ['docs/', 'handbook/'];
- for (let sectionRepoPath of SECTION_REPO_PATHS) {
+ let SECTION_INFOS_BY_SECTION_REPO_PATHS = {
+ 'docs/': { urlPrefix: '/docs', },
+ // 'handbook/': { urlPrefix: '/handbook', }, // TODO: Bring this back when styles are complete (removed from build in the meantime so that sitemap.xml is not incorrect)
+ };
+ let rootRelativeUrlPathsSeen = [];
+ for (let sectionRepoPath of Object.keys(SECTION_INFOS_BY_SECTION_REPO_PATHS)) {// FUTURE: run this in parallel
let thinTree = await sails.helpers.fs.ls.with({
dir: path.join(topLvlRepoPath, sectionRepoPath),
depth: 100,
@@ -114,15 +118,39 @@ module.exports = {
includeSymlinks: false,
});
- let rootRelativeUrlPathsSeen = [];
- for (let pageSourcePath of thinTree) {
+ for (let pageSourcePath of thinTree) {// FUTURE: run this in parallel
- // Perform path maths (determine this using sectionRepoPath, etc)
+ // Crunch some paths (used for determining the URL, etc below.)
// > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L308-L313
// > And https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L107-L132
- let rootRelativeUrlPath = `/todo-${_.trimRight(sectionRepoPath,'/')}-${pageSourcePath.slice(-30).replace(/[^a-z0-9\-]/ig,'')}-${Math.floor(Math.random()*10000000)}`;
- sails.log.verbose(`Building page ${rootRelativeUrlPath} from ${pageSourcePath} (${sectionRepoPath})`);
- // ^^TODO: replace that with the actual desired root relative URL path
+ let pageRelSourcePath = path.relative(path.join(topLvlRepoPath, sectionRepoPath), path.resolve(pageSourcePath));
+ let pageUnextensionedLowercasedRelPath = (
+ pageRelSourcePath
+ .replace(/(^|\/)([^/]+)\.[^/]*$/, '$1$2')
+ .split(/\//).map((fileOrFolderName) => fileOrFolderName.toLowerCase()).join('/')
+ );
+ let RX_README_FILENAME = /\/?readme\.?m?d?$/i;// « for matching `readme` or `readme.md` (case-insensitive) at the end of a file path
+
+ // Determine this page's default (fallback) display title.
+ // (README pages use their folder name as their fallback title.)
+ let fallbackPageTitle;
+ if (pageSourcePath.match(RX_README_FILENAME)) {
+ // console.log(pageRelSourcePath.split(/\//).slice(-2)[0], path.basename(pageRelSourcePath), pageRelSourcePath);
+ fallbackPageTitle = sails.helpers.strings.toSentenceCase(pageRelSourcePath.split(/\//).slice(-2)[0]);
+ } else {
+ fallbackPageTitle = sails.helpers.strings.toSentenceCase(path.basename(pageSourcePath, path.extname(pageSourcePath)));
+ }
+
+ // Determine URL for this page
+ let rootRelativeUrlPath = (
+ (
+ SECTION_INFOS_BY_SECTION_REPO_PATHS[sectionRepoPath].urlPrefix +
+ '/' + (
+ pageUnextensionedLowercasedRelPath
+ .split(/\//).map((fileOrFolderName) => encodeURIComponent(fileOrFolderName.replace(/^[0-9]+[\-]+/,''))).join('/')// « Get URL-friendly by encoding characters and stripping off ordering prefixes (like the "1-" in "1-Using-Fleet") for all folder and file names in the path.
+ )
+ ).replace(RX_README_FILENAME, '')// « Interpret README files as special and map it to the URL representing its containing folder.
+ );
// Assert uniqueness of URL paths.
if (rootRelativeUrlPathsSeen.includes(rootRelativeUrlPath)) {
@@ -130,51 +158,155 @@ module.exports = {
}//•
rootRelativeUrlPathsSeen.push(rootRelativeUrlPath);
- // Get last modified timestamp using git
- // > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L265-L273
- let lastModifiedAt = Date.now();// TODO
+ if (path.extname(pageSourcePath) !== '.md') {// If this file doesn't end in `.md`: skip it (we won't create a page for it)
+ // > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L275-L276
+ sails.log.verbose(`Skipping ${pageSourcePath}`);
+ } else {// Otherwise, this is markdown, so: Compile to HTML, parse docpage metadata, and build+track it as a page
+ sails.log.verbose(`Building page ${rootRelativeUrlPath} (from ${pageSourcePath})`);
- let fallbackTitle = sails.helpers.strings.toSentenceCase(path.basename(pageSourcePath, '.ejs'));// « for clarity (the page isn't a template, necessarily, and this title is just a guess. Display title will, more likely than not, come from a tag -- see the bottom of the original, raw unformatted markdown of any page in the sailsjs docs for an example of how to use docmeta tags)
+ // Compile markdown to HTML.
+ // > This includes build-time enablement of:
+ // > • syntax highlighting
+ // > • data type bubbles
+ // > • transforming relative markdown links to their fleetdm.com equivalents
+ // >
+ // > For more info about how these additional features work, see: https://github.com/fleetdm/fleet/issues/706#issuecomment-884622252
+ // >
+ // > • What about images referenced in markdown files? :: They need to be referenced using an absolute URL src-- e.g.  See also https://github.com/fleetdm/fleet/issues/706#issuecomment-884641081 for reasoning.
+ // > • What about GitHub-style emojis like `:white_check_mark:`? :: Use actual unicode emojis instead. Need to revisit this? Visit https://github.com/fleetdm/fleet/pull/1380/commits/19a6e5ffc70bf41569293db44100e976f3e2bda7 for more info.
+ let mdString = await sails.helpers.fs.read(pageSourcePath);
+ mdString = mdString.replace(/(```)([a-zA-Z0-9\-]*)(\s*\n)/g, '$1\n' + '' + '$3'); // « Based on the github-flavored markdown's language annotation, (e.g. ```js```) add a temporary marker to code blocks that can be parsed post-md-compilation when this is HTML. Note: This is an HTML comment because it is easy to over-match and "accidentally" add it underneath each code block as well (being an HTML comment ensures it doesn't show up or break anything). For more information, see https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L198-L202
+ let htmlString = await sails.helpers.strings.toHtml(mdString);
+ htmlString = (// « Add the appropriate class to the `` based on the temporary "LANG" markers that were just added above
+ htmlString
+ .replace(// Interpret `js` as `javascript`
+ // $1 $2 $3 $4
+ /(]*)(>\s*)(\<!-- __LANG=\%js\%__ --\>)\s*/gm,
+ '$1 class="javascript"$2$3'
+ )
+ .replace(// Interpret `sh` and `bash` as `bash`
+ // $1 $2 $3 $4
+ /(]*)(>\s*)(\<!-- __LANG=\%(bash|sh)\%__ --\>)\s*/gm,
+ '$1 class="bash"$2$3'
+ )
+ .replace(// When unspecified, default to `text`
+ // $1 $2 $3 $4
+ /(]*)(>\s*)(\<!-- __LANG=\%\%__ --\>)\s*/gm,
+ '$1 class="nohighlight"$2$3'
+ )
+ .replace(// Finally, nab the rest, leaving the code language as-is.
+ // $1 $2 $3 $4 $5 $6
+ /(]*)(>\s*)(\<!-- __LANG=\%)([^%]+)(\%__ --\>)\s*/gm,
+ '$1 class="$5"$2$3'
+ )
+ );
+ htmlString = htmlString.replace(/\(\(([^())]*)\)\)/g, ' ');// « Replace ((bubble))s with HTML. For more background, see https://github.com/fleetdm/fleet/issues/706#issuecomment-884622252
+ htmlString = htmlString.replace(/(href="(\.\/[^"]+|\.\.\/[^"]+)")/g, (hrefString)=>{// « Modify path-relative links like `./…` and `../…` to make them absolute. (See https://github.com/fleetdm/fleet/issues/706#issuecomment-884641081 for more background)
+ let oldRelPath = hrefString.match(/href="(\.\/[^"]+|\.\.\/[^"]+)"/)[1];
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // Note: This approach won't work as far as linking between handbook and docs
+ // FUTURE: improve it so that it does. This may involve pulling out URL determination as a separate, first step, then looking up the appropriate URL.
+ // Currently this is a kinda duplicative hack, that just determines the appropriate URL in a similar way to all the code above...
+ // -mikermcneil 2021-07-27
+ // ```
+ let referencedPageSourcePath = path.resolve(path.join(topLvlRepoPath, sectionRepoPath, pageRelSourcePath), '../', oldRelPath);
+ let referencedPageNewUrl = 'https://fleetdm.com/' + (
+ (path.relative(topLvlRepoPath, referencedPageSourcePath).replace(/(^|\/)([^/]+)\.[^/]*$/, '$1$2').split(/\//).map((fileOrFolderName) => fileOrFolderName.toLowerCase()).join('/'))
+ .split(/\//).map((fileOrFolderName) => encodeURIComponent(fileOrFolderName.replace(/^[0-9]+[\-]+/,''))).join('/')
+ ).replace(RX_README_FILENAME, '');
+ // console.log(pageRelSourcePath, '»» '+hrefString+' »»»» href="'+referencedPageNewUrl+'"');
+ // ```
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ return `href="${referencedPageNewUrl}"`;
+ });
+ htmlString = htmlString.replace(/(href="https?:\/\/([^"]+)")/g, (hrefString)=>{// « Modify links that are potentially external
+ // Check if this is an external link (like https://google.com) but that is ALSO not a link
+ // to some page on the destination site where this will be hosted, like `(*.)?fleetdm.com`.
+ // If external, add target="_blank" so the link will open in a new tab.
+ let isExternal = ! hrefString.match(/^href=\"https?:\/\/([^\.]+\.)*fleetdm\.com/g);// « FUTURE: make this smarter with sails.config.baseUrl + _.escapeRegExp()
+ if (isExternal) {
+ return hrefString.replace(/(href="https?:\/\/([^"]+)")/g, '$1 target="_blank"');
+ } else {
+ // Otherwise, change the link to be web root relative.
+ // (e.g. 'href="http://sailsjs.com/documentation/concepts"'' becomes simply 'href="/documentation/concepts"'')
+ // > Note: See the Git version history of "compile-markdown-content.js" in the sailsjs.com website repo for examples of ways this can work across versioned subdomains.
+ return hrefString.replace(/href="https?:\/\//, '').replace(/^fleetdm\.com/, 'href="');
+ }
- // If markdown: Compile to HTML and parse docpage metadata
- // > Parsing docmeta tags (consider renaming them to just - or by now there's probably a more standard way of embedding semantics in markdown files; prefer to use that): https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L180-L183
- // > Compiling: https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L198-L202
- // TODO
+ });//∞
- // Skip this page, if appropriate
- // > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L275-L276
- // TODO
+ // Extract metadata from markdown.
+ // > • Parsing meta tags (consider renaming them to just - or by now there's probably a more standard way of embedding semantics in markdown files; prefer to use that): https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L180-L183
+ // > See also https://github.com/mikermcneil/machinepack-markdown/blob/5d8cee127e8ce45c702ec9bbb2b4f9bc4b7fafac/machines/parse-docmeta-tags.js#L42-L47
+ // >
+ // > e.g. referring to stuff like:
+ // > ```
+ // >
+ // >
+ // > ```
+ let embeddedMetadata = {};
+ for (let tag of (mdString.match(/]*>/igm)||[])) {
+ let name = tag.match(/name="([^">]+)"/i)[1];
+ let value = tag.match(/value="([^">]+)"/i)[1];
+ embeddedMetadata[name] = value;
+ }//∞
+ if (Object.keys(embeddedMetadata).length >= 1) {
+ sails.log.silly(`Parsed ${Object.keys(embeddedMetadata).length} tags:`, embeddedMetadata);
+ }//fi
- // Generate HTML file
- let htmlOutputPath = '';//TODO
- if (dry) {
- sails.log('Dry run: Would have generated file:', htmlOutputPath);
- } else {
- // TODO
+ // Get last modified timestamp using git, and represent it as a JS timestamp.
+ // > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L265-L273
+ let lastModifiedAt = (new Date((await sails.helpers.process.executeCommand.with({
+ command: `git log -1 --format="%ai" '${path.relative(topLvlRepoPath, pageSourcePath)}'`,
+ dir: topLvlRepoPath,
+ })).stdout)).getTime();
+
+ // Determine display title (human-readable title) to use for this page.
+ let pageTitle;
+ if (embeddedMetadata.title) {// Attempt to use custom title, if one was provided.
+ if (embeddedMetadata.title.length > 40) {
+ throw new Error(`Failed compiling markdown content: Invalid custom title () embedded in "${path.join(topLvlRepoPath, sectionRepoPath)}". To resolve, try changing the title to a different, valid value, then rebuild.`);
+ }//•
+ pageTitle = embeddedMetadata.title;
+ } else {// Otherwise use the automatically-determined fallback title.
+ pageTitle = fallbackPageTitle;
+ }
+
+ // Determine unique HTML id
+ // > • This will become the filename of the resulting HTML.
+ // > • And it will be attached to menu data for use in sorting pages within their bottom-level sections.
+ let htmlId = (
+ sectionRepoPath.slice(0,10)+
+ '--'+
+ _.last(pageUnextensionedLowercasedRelPath.split(/\//)).slice(0,20)+
+ '--'+
+ sails.helpers.strings.random.with({len:10})// if two files in different folders happen to have the same filename, there is a 1/16^10 chance of a collision (this is small enough- worst case, the build fails at the uniqueness check and we rerun it.)
+ ).replace(/[^a-z0-9\-]/ig,'');
+
+ // Generate HTML file
+ let htmlOutputPath = path.resolve(sails.config.appPath, path.join(APP_PATH_TO_COMPILED_PAGE_PARTIALS, htmlId+'.ejs'));
+ if (dry) {
+ sails.log('Dry run: Would have generated file:', htmlOutputPath);
+ } else {
+ await sails.helpers.fs.write(htmlOutputPath, htmlString);
+ }
+
+ // Append to what will become configuration for the Sails app.
+ builtStaticContent.markdownPages.push({
+ url: rootRelativeUrlPath,
+ title: pageTitle,
+ lastModifiedAt: lastModifiedAt,
+ htmlId: htmlId,
+ meta: _.omit(embeddedMetadata, 'title')
+ });
}
-
- // TODO: Figure out what to do about embedded images (they'll get cached by CDN so probably ok to point at github, but markdown img srcs will break if relative. Also GitHub could just change image URLs whenever.)
-
- // Append to Sails app configuration.
- builtStaticContent.markdownPages.push({
- url: rootRelativeUrlPath,
- title: '' || fallbackTitle,// TODO use metadata title if available
- lastModifiedAt: lastModifiedAt
- });
}//∞
}//∞
- // Decorate markdownPages tree with easier-to-use properties related to metadata embedded in the markdown and parent/child relationships.
- // Note: Maybe skip the parent/child relationships.
- // > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/marshal-doc-page-metadata.js
- // > And https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/build-jsmenu.js
- // TODO
+ // Attach partials dir path in what will become configuration for the Sails app.
+ // (This is for easier access later, without defining this constant in more than one place.)
+ builtStaticContent.compiledPagePartialsAppPath = APP_PATH_TO_COMPILED_PAGE_PARTIALS;
- // Sort siblings in the markdownPages tree so it's ready to use in menus.
- // > Note: consider doing this on the frontend-- though there's a reason it was here. See:
- // > • https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/compare-doc-page-metadatas.js
- // > • https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/marshal-doc-page-metadata.js#L191-L208
- // TODO
},
]);
diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs
index 7294e65693..c512e0fe00 100644
--- a/website/views/layouts/layout.ejs
+++ b/website/views/layouts/layout.ejs
@@ -87,8 +87,8 @@
Queries
@@ -111,8 +111,8 @@
@@ -140,12 +140,12 @@
- Try Fleet
- Documentation
+ Try Fleet
+ Documentation
Pricing
- Contribute
- Blog
- Hall of fame
+ Contribute
+ Blog
+ Hall of fame
@@ -213,6 +213,7 @@
+
@@ -223,15 +224,12 @@
-
-
-
diff --git a/website/views/pages/docs-template.ejs b/website/views/pages/docs-template.ejs
deleted file mode 100644
index e2c30812c8..0000000000
--- a/website/views/pages/docs-template.ejs
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
-
-
-
- {{currentPage.topic}}
-
-
-
-
-
-
-
-
-
- {{currentPage.topic}}
-
-
- {{item.content}}
- {{item.content}}
-
-
-
- Note:
- {{item.content}}
-
-
-
- {{item.content.intro}}
-
- - {{bullet}}
-
-
-
-
-
- Is there something missing?
-
- If you notice something we've missed or could be improved on, please follow this link and submit a pull request to the Fleet repo.
-
-
-
-
-
-
-
-
-
-
-<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
diff --git a/website/views/pages/docs/basic-documentation.ejs b/website/views/pages/docs/basic-documentation.ejs
index c5e85726ba..425b86d7a2 100644
--- a/website/views/pages/docs/basic-documentation.ejs
+++ b/website/views/pages/docs/basic-documentation.ejs
@@ -1,11 +1,214 @@
- TODO: Implement this page.
- (See also assets/styles/pages/docs/basic-documentation.less, assets/js/pages/docs/basic-documentation.page.js, and api/controllers/docs/view-basic-documentation.js.)
+
+
+
+
+ Fleet documentation
+ Welcome to the documentation for Fleet, an open-source osquery management server.
+
+
+
+
+
+
+
+
+
+
+
+
+ Install osquery and Fleet
+
+ Get started
+
+
+
+
+
+
+
+ Can't find what you need?
+
+ Support
+
+
+
+
+
+
+
+
+
+
+
+ {{page.title}}
+
+ -
+ {{subpage.title}}
+
+
+
+
+
+
+
+
- This paragraph, and the content above it, are just here for reference. The HTML generated from markdown is below.
+
+
- <%- partial('../../partials/built-from-markdown/docs/1-Using-Fleet/1-Fleet-UI.ejs') %>
+
+
+ {{thisPage.title}}
+
+
+
+
+
+
+
+
+ <%- partial(
+ path.relative(
+ path.dirname(__filename),
+ path.resolve(
+ sails.config.appPath,
+ path.join(
+ sails.config.builtStaticContent.compiledPagePartialsAppPath,
+ thisPage.htmlId
+ )
+ )
+ )
+ ) %>
+
+
+ Is there something missing?
+
+ If you notice something we've missed or could be improved on, please follow this link and submit a pull request to the Fleet repo.
+
+
+
+
+
+
+
+
+
+
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
diff --git a/website/views/pages/documentation.ejs b/website/views/pages/documentation.ejs
deleted file mode 100644
index a6ee36155c..0000000000
--- a/website/views/pages/documentation.ejs
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
- Fleet documentation
- Welcome to the documentation for the Fleet osquery fleet manager.
- v4.0.1 changelog
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Install osquery and Fleet
-
- Get started
-
-
-
-
-
-
-
- Can't find what you need?
-
- Support
-
-
-
-
-
-
-
-
-
-
-
- {{item.title}}
-
- -
- {{child}}
-
-
-
-
-
-
-
-
-
-<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
diff --git a/website/views/pages/handbook/basic-handbook.ejs b/website/views/pages/handbook/basic-handbook.ejs
index bdcf88c9b5..423af5ee50 100644
--- a/website/views/pages/handbook/basic-handbook.ejs
+++ b/website/views/pages/handbook/basic-handbook.ejs
@@ -5,7 +5,7 @@
This paragraph, and the content above it, are just here for reference. The HTML generated from markdown is below.
- <%- partial('../../partials/built-from-markdown/handbook/README.ejs') %>
+ <%- partial(path.relative(path.dirname(__filename), path.resolve(sails.config.appPath, path.join(sails.config.builtStaticContent.compiledPagePartialsAppPath, thisPage.htmlId)))) %>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>