diff --git a/.drone.yml b/.drone.yml index 81a13a72efa..0958e7597fe 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,7 +18,13 @@ pipeline: repo: rancher/docs context: . tag: dev - secrets: [docker_username, docker_password] + secrets: [docker_username, docker_password, algolia_app_id, algolia_admin_key] + environment: + - ALGOLIA_INDEX_NAME="dev_docs" + - ALGOLIA_INDEX_FILE="public/algolia.json" + build_args_from_env: + - ALGOLIA_APP_ID + - ALGOLIA_ADMIN_KEY when: branch: master event: push @@ -30,7 +36,13 @@ pipeline: repo: rancher/docs context: . tag: staging - secrets: [docker_username, docker_password] + secrets: [docker_username, docker_password, algolia_app_id, algolia_admin_key] + environment: + - ALGOLIA_INDEX_NAME="dev_docs" + - ALGOLIA_INDEX_FILE="public/algolia.json" + build_args_from_env: + - ALGOLIA_APP_ID + - ALGOLIA_ADMIN_KEY when: branch: master event: push @@ -42,7 +54,13 @@ pipeline: repo: rancher/docs context: . tag: latest - secrets: [docker_username, docker_password] + secrets: [docker_username, docker_password, algolia_app_id, algolia_admin_key] + environment: + - ALGOLIA_INDEX_NAME="dev_docs" + - ALGOLIA_INDEX_FILE="public/algolia.json" + build_args_from_env: + - ALGOLIA_APP_ID + - ALGOLIA_ADMIN_KEY when: branch: master event: push diff --git a/Dockerfile.dev b/Dockerfile.dev index 26ce67c09b6..30b14aee341 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,8 @@ FROM rancher/docs:build +ARG ALGOLIA_APP_ID +ARG ALGOLIA_INDEX_NAME +ARG ALGOLIA_INDEX_FILE +ARG ALGOLIA_ADMIN_KEY VOLUME ["/site"] WORKDIR /run @@ -21,6 +25,11 @@ COPY gulpfile.babel.js /run/ ADD https://github.com/rancherlabs/website-theme/archive/master.tar.gz master.tar.gz RUN mkdir -p /run/node_modules/rancher-website-theme && tar -xzf master.tar.gz --strip 1 -C /run/node_modules/rancher-website-theme && rm master.tar.gz +ENV ALGOLIA_APP_ID=$ALGOLIA_APP_ID +ENV ALGOLIA_INDEX_NAME=$ALGOLIA_INDEX_NAME +ENV ALGOLIA_INDEX_FILE=$ALGOLIA_INDEX_FILE +ENV ALGOLIA_ADMIN_KEY=$ALGOLIA_ADMIN_KEY + # Expose default hugo port EXPOSE 9001 diff --git a/Dockerfile.prod b/Dockerfile.prod index 8cde8c68f49..9869a6412e5 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,4 +1,8 @@ FROM rancher/docs:build as build +ARG ALGOLIA_APP_ID +ARG ALGOLIA_INDEX_NAME +ARG ALGOLIA_INDEX_FILE +ARG ALGOLIA_ADMIN_KEY WORKDIR /run @@ -17,6 +21,13 @@ ENV HUGO_ENV production ADD https://github.com/rancherlabs/website-theme/archive/master.tar.gz master.tar.gz RUN tar -xzf master.tar.gz --strip 1 -C /run/node_modules/rancher-website-theme && rm master.tar.gz + + +ENV ALGOLIA_APP_ID=$ALGOLIA_APP_ID +ENV ALGOLIA_INDEX_NAME=$ALGOLIA_INDEX_NAME +ENV ALGOLIA_INDEX_FILE=$ALGOLIA_INDEX_FILE +ENV ALGOLIA_ADMIN_KEY=$ALGOLIA_ADMIN_KEY + RUN gulp build # Make sure something got built diff --git a/Dockerfile.staging b/Dockerfile.staging index 93247e0817a..d400d2f4be5 100644 --- a/Dockerfile.staging +++ b/Dockerfile.staging @@ -1,4 +1,8 @@ FROM rancher/docs:build as build +ARG ALGOLIA_APP_ID +ARG ALGOLIA_INDEX_NAME +ARG ALGOLIA_INDEX_FILE +ARG ALGOLIA_ADMIN_KEY WORKDIR /run @@ -17,6 +21,12 @@ ENV HUGO_ENV production ADD https://github.com/rancherlabs/website-theme/archive/master.tar.gz master.tar.gz RUN tar -xzf master.tar.gz --strip 1 -C /run/node_modules/rancher-website-theme && rm master.tar.gz + +ENV ALGOLIA_APP_ID=$ALGOLIA_APP_ID +ENV ALGOLIA_INDEX_NAME=$ALGOLIA_INDEX_NAME +ENV ALGOLIA_INDEX_FILE=$ALGOLIA_INDEX_FILE +ENV ALGOLIA_ADMIN_KEY=$ALGOLIA_ADMIN_KEY + RUN gulp build-staging # Make sure something got built diff --git a/config.toml b/config.toml index 152b97f73bf..7f02944b155 100644 --- a/config.toml +++ b/config.toml @@ -18,19 +18,19 @@ pygmentsCodeFencesGuessSyntax=true ### Edit content repo (set to automatically enter "edit" mode; this is good for "improve this page" links) ghdocsrepo = "https://github.com/rancher/docs" -[outputFormats.Search] -baseName = "search" +[outputFormats.Algolia] +baseName = "algolia" isPlaintext = true mediaType = "application/json" notAlternative = true [outputs] -home = ["HTML", "RSS", "Search"] -page = ["HTML", "Search"] +home = ["HTML", "RSS", "Algolia"] +# page = ["HTML", "Algolia"] -[params.search] -vars = ["title","summary","date","publishdate","expirydate","permalink"] -params = ["categories","tags"] +[params.algolia] +vars = ["title", "summary", "date", "publishdate", "expirydate", "permalink"] +params = ["categories", "tags"] [[menu.main]] identifier = "what-is-rancher" diff --git a/gulpfile.babel.js b/gulpfile.babel.js index ce333e9fff1..bd13759d2f1 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -13,13 +13,10 @@ import buffer from 'vinyl-buffer'; import babelify from 'babelify'; import watch from 'gulp-watch'; -const $ = gulpLoadPlugins(); -const browserSync = require('browser-sync').create(); +const $ = gulpLoadPlugins(); +const browserSync = require('browser-sync').create(); const isProduction = process.env.NODE_ENV === 'production'; - -require('hugo-search-index/gulp')(gulp); - -const onError = (err) => { +const onError = (err) => { console.log(err); } @@ -34,19 +31,19 @@ gulp.task('dev', ['build-dev'], () => { }); gulp.task('build', (cb) => { - runSequence('pub-delete', 'sass', 'build:vendor', 'build:app', 'fonts', 'img', 'hugo' , 'hugo-search-index', () => { + runSequence('pub-delete', 'sass', 'build:vendor', 'build:app', 'fonts', 'img', 'hugo', 'build:search-index', () => { cb(); }); }); gulp.task('build-staging', (cb) => { - runSequence('pub-delete', 'sass', 'build:vendor', 'build:app', 'fonts', 'img', 'hugo-staging' , 'hugo-search-index', () => { + runSequence('pub-delete', 'sass', 'build:vendor', 'build:app', 'fonts', 'img', 'hugo-staging', 'build:search-index', () => { cb(); }); }); gulp.task('build-dev', (cb) => { - runSequence('pub-delete', 'sass', 'build:vendor', 'build:app', 'fonts', 'img', 'hugo-dev' , /* 'hugo-search-index', */ () => { + runSequence('pub-delete', 'sass', 'build:vendor', 'build:app', 'fonts', 'img', 'hugo-dev', () => { cb(); }); }); @@ -83,7 +80,17 @@ gulp.task('init-watch', () => { watch([ 'src/sass/**/*.scss', 'node_modules/rancher-website-theme/**/*.scss' ], () => runSequence('sass', 'hugo-dev')); watch([ 'src/js/**/*.js', 'node_modules/rancher-website-theme/static/js/base.js' ], () => runSequence('build:app', 'hugo-dev')); watch('src/img/**/*', () => runSequence('img', 'hugo-dev')); - watch(['archetypes/**/*', 'data/**/*', 'content/**/*', 'layouts/**/*', 'node_modules/rancher-website-theme/layouts/**/*', 'node_modules/rancher-website-theme/archetypes/**/*', 'node_modules/rancher-website-theme/data/**/*', 'node_modules/rancher-website-theme/content/**/*', 'themes/**/*', 'config.toml'], () => gulp.start('hugo-dev')); + watch([ + 'archetypes/**/*', + 'data/**/*', + 'content/**/*', + 'layouts/**/*', + 'node_modules/rancher-website-theme/layouts/**/*', + 'node_modules/rancher-website-theme/archetypes/**/*', + 'node_modules/rancher-website-theme/data/**/*', + 'node_modules/rancher-website-theme/content/**/*', + 'themes/**/*', + 'config.toml'], () => gulp.start('hugo-dev')); }); @@ -103,7 +110,7 @@ gulp.task('sass', () => { .pipe(gulp.dest('static/css')); }); -const vendors = [/* 'zoom.ts', */'ml-stack-nav', 'lory.js', 'tingle.js', 'moment', 'jquery']; +const vendors = [/* 'zoom.ts', */'instantsearch.js', 'ml-stack-nav', 'lory.js', 'tingle.js', 'moment', 'jquery']; gulp.task('build:vendor', () => { const b = browserify(); @@ -158,3 +165,20 @@ gulp.task('cms-delete', () => { gulp.task('pub-delete', () => { return del(['public/**', '!public']); }); + + +gulp.task('build:search-index', (cb) => { + const env = process.env; + + env.ALGOLIA_APP_ID = '30NEY6C9UY'; + env.ALGOLIA_INDEX_NAME = isProduction ? 'prod_docs' : 'dev_docs'; + env.ALGOLIA_INDEX_FILE = 'public/algolia.json'; + + const opts = { + stdio: 'inherit', + env: env + }; + return spawn(process.cwd()+'/scripts/build-algolia.js', opts).on('close', (/* code */) => { + cb(); + }); +}); diff --git a/package.json b/package.json index 0cb2c49bb74..c710263ef49 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,11 @@ "dev": "gulp dev", "server": "gulp server", "server:with-drafts": "gulp server:with-drafts", - "cms:delete": "gulp cms-delete" + "cms:delete": "gulp cms-delete", + "algolia": "node build-algolia.js" }, "dependencies": { + "atomic-algolia": "^0.3.15", "babel-core": "^6.25.0", "babel-preset-es2015": "^6.24.1", "babelify": "^8.0.0", @@ -29,7 +31,10 @@ "gulp-size": "^3.0.0", "gulp-uglify": "^3.0.0", "gulp-watch": "^5.0.0", + "instantsearch.js": "^2.8.0", "jquery": "^3.3.1", + "jsdom": "^11.11.0", + "md5": "^2.2.1", "moment": "^2.20.1", "node-sass-tilde-importer": "^1.0.0", "rancher-website-theme": "https://github.com/rancherlabs/website-theme.git", @@ -44,7 +49,6 @@ }, "devDependencies": { "gulp": "^3.9.1", - "hugo-search-index": "^0.4.1", "lory.js": "^2.4.1", "ml-stack-nav": "^1.1.2", "tingle.js": "^0.13.2" diff --git a/scripts/build-algolia.js b/scripts/build-algolia.js new file mode 100755 index 00000000000..c2104929f2f --- /dev/null +++ b/scripts/build-algolia.js @@ -0,0 +1,88 @@ +#! /usr/bin/env node +'use strict'; +const jsdom = require("jsdom"); +const { + JSDOM +} = jsdom; +const md5 = require('md5'); +const atomicalgolia = require("atomic-algolia"); +const fs = require('fs'); +const isProduction = process.env.NODE_ENV === 'production'; +const indexName = isProduction ? "prod_docs" : "dev_docs"; +const nue = []; +const rawdata = fs.readFileSync('public/algolia.json'); +const nodes = JSON.parse(rawdata); + +nodes.forEach(node => { + const dom = new JSDOM(node.content); + const content = dom.window.document.body; //post content wrapped in a body tag + const contentChildren = content.children; // all the children of the body tag + const paragraphOut = { + anchor: '#', + title: '', + content: '', + postref: node.objectID, + objectID: md5(node.permalink), + permalink: node.permalink + }; + + let childCount = contentChildren.length - 1; // how many children + + // loop over the content until the next h2 heading -> this is the paragraph of searchable text + while(childCount >= 0) { + const child = contentChildren[childCount]; + + if (child.tagName === "H2") { + //this is our header + paragraphOut.anchor = `#${child.id}`; + paragraphOut.title = child.textContent; + + let next = child.nextElementSibling; + + while(next && next.tagName !== 'H2') { + if (next) { + paragraphOut.content += next.textContent; + } + next = next.nextElementSibling; + } + + } + + childCount--; + } + + // a post without headers + if (paragraphOut.title === '') { + // Set the title to the page title + paragraphOut.title = node.title; + + // pass along the content + paragraphOut.content = content.textContent; + } + + // limit the content to 10k so we dont blow up just incase someone decides to make a 40k blog post in one paragraph ¯\_(ツ)_/¯ + paragraphOut.content = paragraphOut.content.substr(0, 8000); + + // objectID is not quite unique yet so hash the entire object + paragraphOut.objectID = md5(JSON.stringify(paragraphOut)); + + + nue.push(paragraphOut); + + // remove potentially large content (see size limits) and replace with teh summary so that we don't get results with zero highlightable results + node.content = node.summary; + + // remove summary for dedup + delete node.summary; + +}); + +const merged = [...nodes, ...nue]; + +// fs.writeFileSync('public/combined.algolia.json', JSON.stringify(merged)); +// process.exit(0); +atomicalgolia(indexName, merged, (err, result) => { + if (err) throw err; + console.log(result); + process.exit(0); +}); diff --git a/src/js/app.js b/src/js/app.js index 6bc24dcd01d..88c7c994ad4 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,2 +1,66 @@ +import $ from 'jquery'; +import instantsearch from 'instantsearch.js'; + // This is for any custom JS that may need to be added to individual apps. // Main JS is located in Rancher Website Theme +const bootstrapDocsSearch = function() { + const search = instantsearch({ + appId: '30NEY6C9UY', + apiKey: 'b7f43c16886fec97b87981e9e62ef1a5', + indexName: window.location.host === 'rancher.com' ? 'prod_docs' : 'dev_docs', + routing: true, + }); + + search.addWidget( + instantsearch.widgets.searchBox({ + container: '#search-box', + placeholder: 'Search Docs...', + magnifier: false, + reset: true, + }) + ); + + search.addWidget( + instantsearch.widgets.infiniteHits({ + container: '#hits', + templates: { + empty: '