diff --git a/app.yaml b/app.yaml deleted file mode 100644 index 1f880c8d..00000000 --- a/app.yaml +++ /dev/null @@ -1,2 +0,0 @@ -runtime: nodejs18 -instance_class: F4 \ No newline at end of file diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index 28a0021e..d9464c45 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -1,15 +1,15 @@ - <%- include('partials/head') %> + <%- include('partials/head', {formatUrl, pathPrefix}) %>

Default template

- <%- include('partials/header') %> - <%- include('partials/nav') %> + <%- include('partials/header', {formatUrl}) %> + <%- include('partials/nav', {formatUrl}) %>
<% if (locals.sections && locals.sections.length) { %> - <%- include('partials/sectionList') %> + <%- include('partials/sectionList', {formatUrl}) %> <% } %> <% parentLinks.forEach((link) => { %> <% }) %> <% }) %> diff --git a/layouts/partials/folderList.ejs b/layouts/partials/folderList.ejs index 414b69e0..007836b3 100644 --- a/layouts/partials/folderList.ejs +++ b/layouts/partials/folderList.ejs @@ -10,9 +10,9 @@ <% if (parents.includes(folder.id)) { %> <%= folder.prettyName %> <% } else { %> - <%= folder.prettyName %> + <%= folder.prettyName %> <% } %> - <%- include('./folderList', {folders: folder.children, id, parents}) %> + <%- include('./folderList', {folders: folder.children, id, parents, formatUrl}) %> <% }) %> diff --git a/layouts/partials/footer.ejs b/layouts/partials/footer.ejs index 1ccff568..dadfa43c 100644 --- a/layouts/partials/footer.ejs +++ b/layouts/partials/footer.ejs @@ -2,7 +2,7 @@
<% if (locals.pager) { %> <% pager.forEach((page, i) => { %> - <%= i + 1 %> + <%= i + 1 %> <% }) %> <% } %> <% if (locals.editLink) { %> @@ -37,13 +37,13 @@ // get the userinfo then fire a pageview (can't cache in the page) $.ajax({ method: 'GET', - url: '/whoami.json' + url: '<%= formatUrl("/whoami.json") %>' }).always(function (data) { var userId = (data || {}).analyticsUserId; if (userId) { ga('set', 'userId', userId) - if(window.location.pathname === '/') { + if(window.location.pathname === '<%= formatUrl("/") %>') { $(document).ready(function() { personalizeHomepage(userId) }) @@ -71,7 +71,7 @@ if (!lastFilenameFetch || now - lastFilenameFetch.lastFetched > 600000) { // 10 min $.ajax({ method: 'GET', - url: '/filename-listing.json', + url: '<%= formatUrl("/filename-listing.json") %>', json: true }).then(function(data, textStatus, xhr) { if (xhr.status !== 200) return; // if ajax fails, continue with old listing diff --git a/layouts/partials/head.ejs b/layouts/partials/head.ejs index ae4aaf0a..a53c212a 100644 --- a/layouts/partials/head.ejs +++ b/layouts/partials/head.ejs @@ -15,9 +15,16 @@ <% if (locals.inlineCSS) { %> <% } else { %> - + - + + <% } %> diff --git a/layouts/partials/header.ejs b/layouts/partials/header.ejs index 7c76c5e3..8760a274 100644 --- a/layouts/partials/header.ejs +++ b/layouts/partials/header.ejs @@ -1,7 +1,7 @@ diff --git a/layouts/partials/landingModule.ejs b/layouts/partials/landingModule.ejs index 62fcc113..289f230e 100644 --- a/layouts/partials/landingModule.ejs +++ b/layouts/partials/landingModule.ejs @@ -8,7 +8,7 @@ const itemsLimited = items.slice(0, 6) -
\ No newline at end of file +
diff --git a/layouts/partials/nav.ejs b/layouts/partials/nav.ejs index cd81cc5d..e31164e5 100644 --- a/layouts/partials/nav.ejs +++ b/layouts/partials/nav.ejs @@ -1,8 +1,8 @@
- <%- include('search') %> + <%- include('search', {formatUrl}) %> <% if (locals.title) { %> <% if (locals.duplicates) { %> - <%- include('partials/warning', {message: template('warning.duplicate', locals.duplicates, locals.parentId) }) %> + <%- include('partials/warning', {message: template('warning.duplicate', locals.duplicates, locals.parentId), formatUrl}) %> <% } %>

<%= title %>

diff --git a/layouts/partials/search.ejs b/layouts/partials/search.ejs index 1a45c59d..d4f0fd49 100644 --- a/layouts/partials/search.ejs +++ b/layouts/partials/search.ejs @@ -1,5 +1,5 @@
-
+ <% const placeholder = template('search.placeholder')%> <% const focus = locals.focus || '' %> <% const msgOnFocus = placeholder || '' %> @@ -9,7 +9,7 @@ <% const style = locals.style || 'plaintext' %> diff --git a/layouts/partials/siblingList.ejs b/layouts/partials/siblingList.ejs index 96e20878..e665ce5c 100644 --- a/layouts/partials/siblingList.ejs +++ b/layouts/partials/siblingList.ejs @@ -10,7 +10,7 @@ <% if (link.isCurrent) { %> <%= link.name %> <% } else { %> - <%= link.name %> + <%= link.name %> <% } %> <% }) %> diff --git a/layouts/playlists/index.ejs b/layouts/playlists/index.ejs index 613432a6..bb6d4c10 100644 --- a/layouts/playlists/index.ejs +++ b/layouts/playlists/index.ejs @@ -1,18 +1,18 @@ - <%- include ../partials/head %> + <%- include('../partials/head', {formatUrl, pathPrefix}) %>

Default Playlist template

- <%- include ../partials/header %> - <%- include ../partials/nav %> + <%- include('../partials/header', {formatUrl}) %> + <%- include('../partials/nav', {formatUrl}) %>
<% if (locals.children && children.length) { %> - <%- include('../partials/childrenList', {children, kicker: template('playlist.childrenList.kicker', title)}) %> + <%- include('../partials/childrenList', {children, kicker: template('playlist.childrenList.kicker', title), formatUrl}) %> <% } %>
- <%- include('../partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %> + <%- include('../partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1], formatUrl }) %>
diff --git a/layouts/playlists/leaf.ejs b/layouts/playlists/leaf.ejs index ed0cf29a..ba8e8bce 100644 --- a/layouts/playlists/leaf.ejs +++ b/layouts/playlists/leaf.ejs @@ -1,23 +1,23 @@ -<%- include('../partials/head', {title}) %> +<%- include('../partials/head', {title, formatUrl, pathPrefix}) %>

Default template

- <%- include('../partials/header', {title}) %> - <%- include ../partials/nav %> + <%- include('../partials/header', {title, formatUrl}) %> + <%- include('../partials/nav', {formatUrl}) %>

- -

- <%- include('../partials/siblingList', {kicker: template('playlist.siblingList.heading', playlistName)}) %> + <%- include('../partials/siblingList', {kicker: template('playlist.siblingList.heading', playlistName, formatUrl)}) %>
<% if (locals.content) { %> @@ -25,12 +25,12 @@ <% } %>

- - + +

- <%- include('../partials/footer', { pageType: 'playlist' }) %> + <%- include('../partials/footer', { pageType: 'playlist', formatUrl }) %>
- \ No newline at end of file + diff --git a/package-lock.json b/package-lock.json index c60f6a70..763baf7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,8 +55,8 @@ "valid-url": "^1.0.9" }, "engines": { - "node": ">=10.x", - "npm": ">=6.5.x" + "node": "16.x", + "npm": "8.3.x" } }, "node_modules/@babel/code-frame": { diff --git a/public/scripts/main.js b/public/scripts/main.js index 372748e8..ef710107 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -75,7 +75,7 @@ $(document).ready(function() { var items = data.map(function(el) { var item = el.doc; var folder = (item.folder || {}).prettyName || ''; // lets not try to show a folder if there isn't one - var path = item.path ? item.path : '#'; + var path = item.path ? formatUrl(item.path) : '#'; return [ '
  • ', '', @@ -136,7 +136,7 @@ function personalizeHomepage(userId) { // kill existing elements that on the mostViewed list to avoid dupes $('ul.teams-cat-list li[data-team-id="' + el.team.id + '"]').detach() - return '
  • ' + el.team.prettyName + '
  • ' + return '
  • ' + el.team.prettyName + '
  • ' }).join('') $('ul.teams-cat-list').prepend(items) diff --git a/server/formatter.js b/server/formatter.js index 6e3c4d22..1539f63c 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -4,6 +4,7 @@ const qs = require('querystring') const unescape = require('unescape') const hljs = require('highlight.js') const list = require('./list') +const {formatUrl} = require('./utils') /* Your one stop shop for all your document processing needs. */ @@ -89,7 +90,7 @@ function normalizeHtml(html) { const {path: libraryPath} = isDoc ? list.getMeta(docId) || {} : {} const libraryDeepLink = deepLink && libraryPath ? `${libraryPath}#${deepLink}` : libraryPath - $(el).attr('href', libraryDeepLink || decoded) + $(el).attr('href', libraryDeepLink ? formatUrl(libraryDeepLink) : decoded) } return el diff --git a/server/index.js b/server/index.js index 1fac976a..e60d6088 100644 --- a/server/index.js +++ b/server/index.js @@ -6,7 +6,7 @@ const csp = require('helmet-csp') const {middleware: cache} = require('./cache') const {getMeta} = require('./list') -const {allMiddleware, requireWithFallback} = require('./utils') +const {formatUrl, allMiddleware, requireWithFallback} = require('./utils') const userInfo = require('./routes/userInfo') const pages = require('./routes/pages') const categories = require('./routes/categories') @@ -32,26 +32,26 @@ if ((process.env.TRUST_PROXY || '').toUpperCase() === 'TRUE') { app.set('view engine', 'ejs') app.set('views', [path.join(__dirname, '../custom/layouts'), path.join(__dirname, '../layouts')]) -app.get('/healthcheck', (req, res) => { +app.get(formatUrl('/healthcheck'), (req, res) => { res.send('OK') }) app.use(csp({directives: customCsp})) -app.use(userAuth) +app.use(formatUrl('/'), userAuth) preload.forEach((middleware) => app.use(middleware)) -app.use(userInfo) +app.use(formatUrl('/'), userInfo) // serve all files in the public folder -app.use('/assets', express.static(path.join(__dirname, '../public'))) +app.use(formatUrl('/assets'), express.static(path.join(__dirname, '../public'))) // strip trailing slashes from URLs app.get(/(.+)\/$/, (req, res, next) => { res.redirect(req.params[0]) }) -app.get('/view-on-site/:docId', (req, res, next) => { +app.get(formatUrl('/view-on-site/:docId'), (req, res, next) => { const {docId} = req.params const doc = getMeta(docId) @@ -78,20 +78,20 @@ app.use((req, res, next) => { next() }) -app.use(pages) -app.use(cache) +app.use(formatUrl('/'), pages) +app.use(formatUrl('/'), cache) // category pages will be cache busted when their last updated timestamp changes -app.use(categories) -app.use(playlists) +app.use(formatUrl('/'), categories) +app.use(formatUrl('/'), playlists) postload.forEach((middleware) => app.use(middleware)) // if no page has been served, check for a redirect before erroring -app.use(redirects) +app.use(formatUrl('/'), redirects) // error handler for rendering the 404 and 500 pages, must go last -app.use(errorPages) +app.use(formatUrl('/'), errorPages) // If we are called directly, listen on port 3000, otherwise don't diff --git a/server/routes/categories.js b/server/routes/categories.js index f714faaf..461ae995 100644 --- a/server/routes/categories.js +++ b/server/routes/categories.js @@ -5,7 +5,7 @@ const router = require('express-promise-router')() const log = require('../logger') const {getMeta} = require('../list') const {fetchDoc, cleanName} = require('../docs') -const {getTemplates, sortDocs, stringTemplate} = require('../utils') +const {getTemplates, sortDocs, stringTemplate, formatUrl, pathPrefix} = require('../utils') const {parseUrl} = require('../urlParser') router.get('*', handleCategory) @@ -43,7 +43,9 @@ async function handleCategory(req, res) { editLink: meta.mimeType === 'text/html' ? meta.folder.webViewLink : meta.webViewLink, id, template: stringTemplate, - duplicates + duplicates, + formatUrl, + pathPrefix }) // if this is a folder, just render from the generic data @@ -71,7 +73,9 @@ async function handleCategory(req, res) { content: html, byline, createdBy, - sections + sections, + formatUrl, + pathPrefix }) res.format({ diff --git a/server/routes/errors.js b/server/routes/errors.js index 5e85a9de..b286c8e1 100644 --- a/server/routes/errors.js +++ b/server/routes/errors.js @@ -1,7 +1,7 @@ 'use strict' const log = require('../logger') -const {assetDataURI, readFileAsync, stringTemplate} = require('../utils') +const {assetDataURI, readFileAsync, stringTemplate, formatUrl, pathPrefix} = require('../utils') let assetCache @@ -70,7 +70,9 @@ module.exports = async (err, req, res, next) => { res.render(`errors/${code}`, { inlineCSS: inlined.css, err, - template: inlined.stringTemplate + template: inlined.stringTemplate, + formatUrl, + pathPrefix }) }, diff --git a/server/routes/pages.js b/server/routes/pages.js index 9acd77f2..28382834 100644 --- a/server/routes/pages.js +++ b/server/routes/pages.js @@ -5,7 +5,7 @@ const search = require('../search') const router = require('express-promise-router')() const {getTree, getFilenames, getMeta, getTagged} = require('../list') -const {getTemplates, sortDocs, stringTemplate, getConfig} = require('../utils') +const {getTemplates, formatUrl, pathPrefix, sortDocs, stringTemplate, getConfig} = require('../utils') router.get('/', handlePage) router.get('/:page', handlePage) @@ -35,12 +35,12 @@ async function handlePage(req, res) { if (autocomplete) { // filter here first to make sure only _one_ document exists with this exact name const exactMatches = results.filter((i) => i.prettyName === q) - if (exactMatches.length === 1) return res.redirect(exactMatches[0].path) + if (exactMatches.length === 1) return res.redirect(formatUrl(exactMatches[0].path)) } res.format({ html: () => { - res.render(template, {q, results, template: stringTemplate}) + res.render(template, {q, results, template: stringTemplate, formatUrl, pathPrefix}) }, json: () => { @@ -65,7 +65,7 @@ async function handlePage(req, res) { const categories = buildDisplayCategories(tree) res.format({ html: () => { - res.render(template, {...categories, template: stringTemplate}) + res.render(template, {...categories, template: stringTemplate, formatUrl, pathPrefix}) }, json: () => { @@ -83,7 +83,7 @@ async function handlePage(req, res) { return } - res.render(template, {template: stringTemplate}) + res.render(template, {template: stringTemplate, formatUrl, pathPrefix}) } function buildDisplayCategories(tree) { diff --git a/server/routes/playlists.js b/server/routes/playlists.js index 48ae0218..29576327 100644 --- a/server/routes/playlists.js +++ b/server/routes/playlists.js @@ -5,7 +5,7 @@ const router = require('express-promise-router')() const log = require('../logger') const {getMeta, getPlaylist} = require('../list') const {fetchDoc, cleanName} = require('../docs') -const {stringTemplate} = require('../utils') +const {stringTemplate, formatUrl, pathPrefix} = require('../utils') const {parseUrl} = require('../urlParser') router.get('*', handlePlaylist) @@ -66,7 +66,9 @@ function preparePlaylistOverview(playlistMeta, values, breadcrumb) { modifiedAt: playlistMeta.modifiedTime, lastUpdatedBy: (playlistMeta.lastModifyingUser || {}).displayName, createdAt: playlistMeta.createdTime, - editLink: playlistMeta.mimeType === 'text/html' ? playlistMeta.folder.webViewLink : playlistMeta.webViewLink + editLink: playlistMeta.mimeType === 'text/html' ? playlistMeta.folder.webViewLink : playlistMeta.webViewLink, + formatUrl, + pathPrefix }) return renderData @@ -115,7 +117,9 @@ async function preparePlaylistPage(data, url, parent) { parentLinks, previous, next, - playlistName: parent.prettyName + playlistName: parent.prettyName, + formatUrl, + pathPrefix } } diff --git a/server/userAuth.js b/server/userAuth.js index cacd5a91..228c8ad4 100644 --- a/server/userAuth.js +++ b/server/userAuth.js @@ -7,7 +7,7 @@ const GoogleStrategy = require('passport-google-oauth20') const SlackStrategy = require('passport-slack-oauth2').Strategy const log = require('./logger') -const {stringTemplate: template} = require('./utils') +const {stringTemplate: template, formatUrl} = require('./utils') const router = require('express-promise-router')() const domains = new Set(process.env.APPROVED_DOMAINS.split(/,\s?/g)) @@ -15,7 +15,7 @@ const domains = new Set(process.env.APPROVED_DOMAINS.split(/,\s?/g)) const authStrategies = ['google', 'Slack'] let authStrategy = process.env.OAUTH_STRATEGY -const callbackURL = process.env.REDIRECT_URL || '/auth/redirect' +const callbackURL = process.env.REDIRECT_URL || formatUrl('/auth/redirect') if (!authStrategies.includes(authStrategy)) { log.warn(`Invalid oauth strategy ${authStrategy} specific, defaulting to google auth`) authStrategy = 'google' @@ -77,8 +77,8 @@ router.get('/logout', (req, res) => { res.redirect('/') }) -router.get('/auth/redirect', passport.authenticate(authStrategy, {failureRedirect: '/login'}), (req, res) => { - res.redirect(req.session.authRedirect || '/') +router.get('/auth/redirect', passport.authenticate(authStrategy, {failureRedirect: formatUrl('/login')}), (req, res) => { + res.redirect(req.session.authRedirect || formatUrl('/')) }) router.use((req, res, next) => { @@ -94,8 +94,8 @@ router.use((req, res, next) => { } log.info('User not authenticated') - req.session.authRedirect = req.path - res.redirect('/login') + req.session.authRedirect = formatUrl(req.path) + res.redirect(formatUrl('/login')) }) function isAuthorized(user) { diff --git a/server/utils.js b/server/utils.js index fef9978c..66398ae9 100644 --- a/server/utils.js +++ b/server/utils.js @@ -136,3 +136,11 @@ exports.assetDataURI = async (filePath) => { const src = `data:${mimeType};base64,${data}` return src } + +exports.pathPrefix = process.env.PATH_PREFIX || '/' + +exports.formatUrl = (url) => { + if (url.match(/^https?:\/\//)) return url + if (url.startsWith('/')) return exports.pathPrefix + url.slice(1) + return exports.pathPrefix + url +}