Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autocomplete for search #51

Merged
merged 20 commits into from
Oct 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion layouts/partials/footer.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<p class="help-text"><%- template('footer.helpText') %></p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.jquery.min.js"></script>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like all the other Library scripts are in the head (and we just haven't audited this in a while). I know it's beyond the scope of this particular change, but I think it would be worthwhile for us to be consistent about where scripts are included. This script seems like it is closer to the right place than the existing ones. What do you think about moving the other includes so they can all be together? My hunch is that this wouldn't break anything but would be curious to see what you find.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah — I initially had it in the head but since it's technically not necessary for the page to paint I wanted to make sure it wasn't blocking. That said, CDNJS has extremely aggressive caching, so I wouldn't be too worried about putting it back

<script>
// Google Analytics tracking
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
Expand All @@ -25,7 +26,7 @@
<% if (process.env.GA_TRACKING_ID) { %>
ga('create', '<%= process.env.GA_TRACKING_ID %>', 'auto');
<% } %>

<% if(locals.pageType) { %>
ga('set', 'contentGroup1', <%- JSON.stringify(locals.pageType) %>);
<% } %>
Expand Down Expand Up @@ -65,6 +66,28 @@
$('.btn-user-initial').text(initials.join(''));
}

// ajax for latest filenames, req will 304 and serve cache if unchanged
var lastFilenameFetch = getFilenameStorage()
var now = new Date().getTime()
if (!lastFilenameFetch || now - lastFilenameFetch.lastFetched > 600000) { // 10 min
$.ajax({
method: 'GET',
url: '/filename-listing.json',
json: true
}).then(function(data, textStatus, xhr) {
if (xhr.status !== 200) return; // if ajax fails, continue with old listing

var lastFetched = new Date().getTime()
var uniqueNames = new $.unique(data.filenames)
var data = {lastFetched: lastFetched, filenames: uniqueNames}
localStorage.setItem('filenames', JSON.stringify(data))
})
}

function getFilenameStorage() {
return JSON.parse(localStorage.getItem('filenames'))
}

$(document).on('click', '#edit-button', function () {
$.ajax({
method: 'GET',
Expand Down
41 changes: 11 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
"build": "node-sass --include-path custom/styles --include-path styles/partials --source-map public/css/style.css.map --source-map-contents styles/style.scss public/css/style.css",
"debug": "node -r dotenv/config --inspect --debug-brk server/index.js",
"watch": "npm run build && concurrently \"nodemon --inspect=0.0.0.0 -r dotenv/config -e js,ejs server/index.js\" \"nodemon -e scss --watch styles --watch custom/styles -x npm run build\"",
"test": "NODE_ENV=test mocha test/**/*.test.js -r ./test/utils/bootstrap.js --recursive --exit",
"test:cover": "NODE_ENV=test nyc --include=server/** --reporter=html --reporter=text mocha test/**/*.test.js -r ./test/utils/bootstrap.js --recursive --exit",
"test": "NODE_ENV=test PORT=3001 mocha test/**/*.test.js -r ./test/utils/bootstrap.js --recursive --exit",
"test:cover": "NODE_ENV=test PORT=3001 nyc --include=server/** --reporter=html --reporter=text mocha test/**/*.test.js -r ./test/utils/bootstrap.js --recursive --exit",
"lint": "eslint ./server",
"gcp-build": "./bin/install_customizations && npm run build && ./bin/update_gae_pkg"
},
Expand Down
13 changes: 13 additions & 0 deletions public/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ $(document).ready(function() {
$target.append(fullSection);
}

function filenameMatcher(q, cb) {
const substrRegex = new RegExp(q, 'i')
const filenames = getFilenameStorage().filenames
cb(filenames.filter((str) => substrRegex.test(str)))
}

// setup typeahead
$('#search-box').typeahead({
hilight: true
}, {
name: 'documents',
source: filenameMatcher
})
})

function personalizeHomepage(userId) {
Expand Down
30 changes: 20 additions & 10 deletions server/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ const driveType = process.env.DRIVE_TYPE
const driveId = process.env.DRIVE_ID

let currentTree = null // current route data by slug
let currentFilenames = null // current list of filenames for typeahead
let docsInfo = {} // doc info by id
let tags = {} // tags to doc id
let driveBranches = {} // map of id to nodes
const playlistInfo = {} // playlist info by id

// normally return the cached tree data
// if it does not exist yet, return a promise for the new tree
exports.getTree = () => currentTree || updateTree()
exports.getTree = () => currentTree || updateTree().then(({tree}) => tree)
exports.getFilenames = () => currentFilenames || updateTree().then(({filenames}) => filenames)

// exposes docs metadata
exports.getMeta = (id) => {
Expand Down Expand Up @@ -68,15 +70,18 @@ async function updateTree() {
const drive = google.drive({version: 'v3', auth: authClient})
const files = await fetchAllFiles({drive, driveType})

currentTree = produceTree(files, driveId)
const updatedData = produceTree(files, driveId)
const { tree, filenames } = updatedData
currentTree = tree
currentFilenames = filenames

const count = Object.values(docsInfo)
.filter((f) => f.resourceType !== 'folder')
.length

log.debug(`Current file count in drive: ${count}`)

return currentTree
return updatedData
})
}

Expand Down Expand Up @@ -152,8 +157,8 @@ function produceTree(files, firstParent) {
// maybe group into folders first?
// then build out tree, by traversing top down
// keep in mind that files can have multiple parents
const [byParent, byId, tagIds] = files.reduce(([byParent, byId, tagIds], resource) => {
const {parents, id, name} = resource
const [byParent, byId, tagIds, fileNames] = files.reduce(([byParent, byId, tagIds, fileNames], resource) => {
const {parents, id, name, mimeType} = resource

// prepare data for the individual file and store later for reference
// FIXME: consider how to remove circular dependency here.
Expand All @@ -164,10 +169,12 @@ function produceTree(files, firstParent) {
.map((t) => t.trim().toLowerCase())
.filter((t) => t.length > 0)

if (!mimeType.includes('folder')) fileNames.push(prettyName)

byId[id] = Object.assign({}, resource, {
prettyName,
tags,
resourceType: cleanResourceType(resource.mimeType),
resourceType: cleanResourceType(mimeType),
sort: determineSort(name),
slug,
isTrashCan: slug === 'trash' && parents.includes(driveId)
Expand Down Expand Up @@ -199,25 +206,28 @@ function produceTree(files, firstParent) {
byParent[parentId] = parent
})

return [byParent, byId, tagIds]
}, [{}, {}, {}])
return [byParent, byId, tagIds, fileNames]
}, [{}, {}, {}, []])

const oldInfo = docsInfo
const oldBranches = driveBranches
tags = tagIds
docsInfo = addPaths(byId) // update our outer cache w/ data including path information
driveBranches = byParent
return buildTreeFromData(firstParent, {info: oldInfo, tree: oldBranches})
const tree = buildTreeFromData(firstParent, {info: oldInfo, tree: oldBranches})
return {tree: tree, filenames: fileNames}
}

// do we care about parent ids? maybe not?
function buildTreeFromData(rootParent, previousData, breadcrumb) {
const {children, home} = driveBranches[rootParent] || {}
const {children, home, homePrettyName} = driveBranches[rootParent] || {}
const parentInfo = docsInfo[rootParent] || {}

const parentNode = {
nodeType: children ? 'branch' : 'leaf',
prettyName: parentInfo.prettyName,
home,
homePrettyName,
id: rootParent,
breadcrumb,
sort: parentInfo ? determineSort(parentInfo.name) : Infinity // some number here that could be used to sort later
Expand Down
8 changes: 7 additions & 1 deletion server/routes/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ const move = require('../move')

const router = require('express-promise-router')()

const { getTree, getMeta, getTagged } = require('../list')
const { getTree, getFilenames, getMeta, getTagged } = require('../list')
const { getTemplates, sortDocs, stringTemplate, getConfig } = require('../utils')

router.get('/', handlePage)
router.get('/:page', handlePage)

router.get('/filename-listing.json', async (req, res) => {
res.header('Cache-Control', 'public, must-revalidate') // override no-cache
const filenames = await getFilenames()
res.json({filenames: filenames})
})

module.exports = router

const pages = getTemplates('pages')
Expand Down
Loading