diff --git a/src/share/content_renderer.js b/src/share/content_renderer.js index cc456bc2e..8c1dbee10 100644 --- a/src/share/content_renderer.js +++ b/src/share/content_renderer.js @@ -44,7 +44,8 @@ function renderIndex(result) { const rootNote = shaca.getNote(shareRoot.SHARE_ROOT_NOTE_ID); for (const childNote of rootNote.getChildNotes()) { - result.content += `
  • ${childNote.escapedTitle}
  • `; + const href = childNote.getLabelValue("shareExternal") ?? `./${childNote.shareId}`; + result.content += `
  • ${childNote.escapedTitle}
  • `; } result.content += ''; @@ -84,7 +85,8 @@ function renderText(result, note) { const noteId = notePathSegments[notePathSegments.length - 1]; const linkedNote = shaca.getNote(noteId); if (linkedNote) { - linkEl.setAttribute("href", linkedNote.shareId); + const href = linkedNote.getLabelValue("shareExternal") ?? `./${linkedNote.shareId}`; + linkEl.setAttribute("href", href); linkEl.classList.add(`type-${linkedNote.type}`); } else { linkEl.removeAttribute("href"); diff --git a/src/share/routes.js b/src/share/routes.js index 462ad1aeb..80fa3423f 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -1,6 +1,7 @@ const express = require('express'); const path = require('path'); const safeCompare = require('safe-compare'); +const ejs = require("ejs"); const shaca = require("./shaca/shaca"); const shacaLoader = require("./shaca/shaca_loader"); @@ -8,6 +9,9 @@ const shareRoot = require("./share_root"); const contentRenderer = require("./content_renderer"); const assetPath = require("../services/asset_path"); const appPath = require("../services/app_path"); +const searchService = require("../services/search/services/search"); +const SearchContext = require("../services/search/search_context"); +const log = require("../services/log"); /** * @param {SNote} note @@ -128,18 +132,42 @@ function register(router) { } const {header, content, isEmpty} = contentRenderer.getContent(note); - const subRoot = getSharedSubTreeRoot(note); + const opts = {note, header, content, isEmpty, subRoot, assetPath, appPath}; + let useDefaultView = true; - res.render("share/page", { - note, - header, - content, - isEmpty, - subRoot, - assetPath, - appPath - }); + // Check if the user has their own template + if (note.hasRelation('shareTemplate')) { + // Get the template note and content + const templateId = note.getRelation('shareTemplate').value; + const templateNote = shaca.getNote(templateId); + + // Make sure the note type is correct + if (templateNote.type === 'code' && templateNote.mime === 'application/x-ejs') { + + // EJS caches the result of this so we don't need to pre-cache + const includer = (path) => { + const childNote = templateNote.children.find(n => path === n.title); + if (!childNote) return null; + if (childNote.type !== 'code' || childNote.mime !== 'application/x-ejs') return null; + return { template: childNote.getContent() }; + }; + + // Try to render user's template, w/ fallback to default view + try { + const ejsResult = ejs.render(templateNote.getContent(), opts, {includer}); + res.send(ejsResult); + useDefaultView = false; // Rendering went okay, don't use default view + } + catch (e) { + log.error(`Rendering user provided share template (${templateId}) threw exception ${e.message} with stacktrace: ${e.stack}`); + } + } + } + + if (useDefaultView) { + res.render('share/page', opts); + } } router.get('/share/', (req, res, next) => { @@ -303,6 +331,37 @@ function register(router) { res.send(note.getContent()); }); + + // Used for searching, require noteId so we know the subTreeRoot + router.get('/share/api/search/:noteId', (req, res, next) => { + shacaLoader.ensureLoad(); + + let note; + + if (!(note = checkNoteAccess(req.params.noteId, req, res))) { + return; + } + + const {query} = req.query; + + if (!query?.trim()) { + return res.status(400).json({ message: "'query' parameter is mandatory." }); + } + + const subRootPath = getSharedSubTreeRoot(note); + const subRoot = subRootPath.note; + const searchContext = new SearchContext({ancestorNoteId: subRoot.noteId}); + const searchResults = searchService.findResultsWithQuery(query, searchContext); + const filteredResults = searchResults.map(sr => { + const fullNote = shaca.notes[sr.noteId]; + const startIndex = sr.notePathArray.indexOf(subRoot.noteId); + const localPathArray = sr.notePathArray.slice(startIndex + 1); + const pathTitle = localPathArray.map(id => shaca.notes[id].title).join(" / "); + return { id: fullNote.noteId, title: fullNote.title, score: sr.score, path: pathTitle }; + }); + + res.json({ results: filteredResults }); + }); } module.exports = {