feat: Embedded Microsoft Office Viewer and Google Docs Viewer web services and no longer download as default action when viewer is supported when loading directory with file attachment that doesn't automatically render.

This commit is contained in:
David Ball 2024-06-24 01:02:26 -04:00
parent e0b93cf355
commit 1f1071f5a9
6 changed files with 104 additions and 44 deletions

View File

@ -186,6 +186,14 @@ const renderArchive = (html: string, paths: string[]) => {
return html; return html;
} }
const isMsOfficeViewerSupported = (file: string) => {
return path.extname(file).search(/^((?:.pptx)|(?:.docx)|(?:.xlsx)|(?:.ppt)|(?:.doc)|(?:.xls))$/ig) != -1;
}
const isGoogleDocsViewerSupported = (file: string) => {
return isMsOfficeViewerSupported(file) || path.extname(file).search(/^((?:.pdf))$/ig) != -1;
}
export default { export default {
leftTrimFirstDirectory, leftTrimFirstDirectory,
trimSlashes, trimSlashes,
@ -205,4 +213,6 @@ export default {
inspect, inspect,
md, md,
moment, moment,
isMsOfficeViewerSupported,
isGoogleDocsViewerSupported,
}; };

View File

@ -78,7 +78,7 @@ export default function () {
} }
}); });
const renderData = { breadcrumbs, content, filePath, fullFilePath, paths, req, route, ...fmData }; const renderData = { breadcrumbs, content, filePath, fullFilePath, paths, req, route, ...fmData };
res.render("page", { h: helpers, ...renderData }); res.render("page", { h: helpers, path, config, ...renderData });
}); });
}); });
@ -122,7 +122,7 @@ export default function () {
console.log(`Setting route for ${route}`); console.log(`Setting route for ${route}`);
pageRouter.get(route, async (req, res) => { pageRouter.get(route, async (req, res) => {
const html = fs.readFileSync(fullFilePath).toString(); const html = fs.readFileSync(fullFilePath).toString();
const renderData = { route, filePath, fullFilePath, req, paths, html }; const renderData = { route, filePath, fullFilePath, req, paths, html, path, config };
res.render("archive", { h: helpers, ...renderData }); res.render("archive", { h: helpers, ...renderData });
}); });
}); });
@ -183,7 +183,7 @@ export default function () {
} }
}); });
const renderData = { breadcrumbs, route, filePath, fullFilePath, req, paths, directory: path.join('public', directory), videoURL, subtitleURL, subtitleVTT, info }; const renderData = { breadcrumbs, route, filePath, fullFilePath, req, paths, directory: path.join('public', directory), videoURL, subtitleURL, subtitleVTT, info };
res.render("video-player", { h: helpers, ...renderData }); res.render("video-player", { h: helpers, path, config, ...renderData });
} }
}); });
}); });

View File

@ -81,15 +81,15 @@ export interface ServeHandlerOptions {
* current working directory. * current working directory.
* *
* For example, if serving a Jekyll app, it would look like this: * For example, if serving a Jekyll app, it would look like this:
* { * {
* "public": "_site" * "public": "_site"
* } * }
* *
* Using absolute path: * Using absolute path:
* *
* { * {
* "public": "/path/to/your/_site" * "public": "/path/to/your/_site"
* } * }
* *
* NOTE: The path cannot contain globs or regular expressions. * NOTE: The path cannot contain globs or regular expressions.
*/ */
@ -101,17 +101,17 @@ export interface ServeHandlerOptions {
* with status code 301 to the same path, but with the extension dropped. * with status code 301 to the same path, but with the extension dropped.
* *
* You can disable the feature like follows: * You can disable the feature like follows:
* { * {
* "cleanUrls": false * "cleanUrls": false
* } * }
* *
* However, you can also restrict it to certain paths: * However, you can also restrict it to certain paths:
* { * {
* "cleanUrls": [ * "cleanUrls": [
* "/app/**", * "/app/**",
* "/!components/**" * "/!components/**"
* ] * ]
* } * }
* *
* NOTE: The paths can only contain globs that are matched using minimatch. * NOTE: The paths can only contain globs that are matched using minimatch.
*/ */
@ -121,19 +121,19 @@ export interface ServeHandlerOptions {
* different one behind the curtains, this option is what you need. * different one behind the curtains, this option is what you need.
* *
* It's perfect for single page applications (SPAs), for example: * It's perfect for single page applications (SPAs), for example:
* { * {
* "rewrites": [ * "rewrites": [
* { "source": "app/**", "destination": "/index.html" }, * { "source": "app/**", "destination": "/index.html" },
* { "source": "projects/edit", "destination": "/edit-project.html" } * { "source": "projects/edit", "destination": "/edit-project.html" }
* ] * ]
* } * }
* *
* You can also use so-called "routing segments" as follows: * You can also use so-called "routing segments" as follows:
* { * {
* "rewrites": [ * "rewrites": [
* { "source": "/projects/:id/edit", "destination": "/edit-project-:id.html" }, * { "source": "/projects/:id/edit", "destination": "/edit-project-:id.html" },
* ] * ]
* } * }
* *
* Now, if a visitor accesses /projects/123/edit, it will respond with the file /edit-project-123.html. * Now, if a visitor accesses /projects/123/edit, it will respond with the file /edit-project-123.html.
* *
@ -184,8 +184,9 @@ export const directoryTemplate = (vals: ServeDirectoryTemplateParameters) => {
}); });
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ejs.renderFile(path.join(config.viewsPath, 'directory.ejs'), { breadcrumbs, h: helpers, ...vals }, (err, str) => { ejs.renderFile(path.join(config.viewsPath, 'directory.ejs'), { h: helpers, path, config, breadcrumbs, ...vals }, (err, str) => {
if (err) { if (err) {
console.error(err);
reject(err); reject(err);
} else { } else {
resolve(str); resolve(str);
@ -196,8 +197,9 @@ export const directoryTemplate = (vals: ServeDirectoryTemplateParameters) => {
export const errorTemplate = (vals: ServeErrorTemplateParameters) => { export const errorTemplate = (vals: ServeErrorTemplateParameters) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ejs.renderFile(path.join(config.viewsPath, 'error.ejs'), { h: helpers, ...vals }, (err, str) => { ejs.renderFile(path.join(config.viewsPath, 'error.ejs'), { h: helpers, path, config, ...vals }, (err, str) => {
if (err) { if (err) {
console.error(err);
reject(err); reject(err);
} else { } else {
resolve(str); resolve(str);

View File

@ -4,18 +4,13 @@
<title><%=h.getDirectoryTitle(directory)%></title> <title><%=h.getDirectoryTitle(directory)%></title>
<%- include('./includes/common-head.ejs') %> <%- include('./includes/common-head.ejs') %>
</head> </head>
<body onload="initPage()"> <body onload="initPage()">
<%- include('./includes/top-navbar.ejs') %> <%- include('./includes/top-navbar.ejs') %>
<%- include('./includes/no-trash-svg.ejs') %> <%- include('./includes/no-trash-svg.ejs') %>
<main class="container"> <main class="container">
<header> <header>
<%- include('./includes/breadcrumbs.ejs') %> <%- include('./includes/breadcrumbs.ejs') %>
</header> </header>
<% if (h.directoryContainsReadme(directory)) {%> <% if (h.directoryContainsReadme(directory)) {%>
<div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg"> <div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg">
<div class="col-lg-12 p-3 p-lg-5 pt-lg-3"> <div class="col-lg-12 p-3 p-lg-5 pt-lg-3">
@ -27,13 +22,69 @@
<b>Document Date:</b> <%= h.moment(h.readmeFm(directory).docDate).format('MMMM D, YYYY') %> <b>Document Date:</b> <%= h.moment(h.readmeFm(directory).docDate).format('MMMM D, YYYY') %>
<% } %> <% } %>
<%if (typeof h.readmeFm(directory).file !== 'undefined') { %> <%if (typeof h.readmeFm(directory).file !== 'undefined') { %>
<b>Attached Document:</b> <a href="<%- encodeURI(h.readmeFm(directory).file) %>"><%- h.readmeFm(directory).file %></a> <b>Document:</b> <a href="<%- encodeURI(path.basename(h.readmeFm(directory).file)) %>"><%- h.readmeFm(directory).file %></a>
<% } %> <% } %>
</small> </small>
</p> </p>
<% } %> <% } %>
<% if (typeof h.readmeFm(directory).file !== 'undefined') { %> <% if (typeof h.readmeFm(directory).file !== 'undefined') { %>
<iframe src="<%- encodeURI(h.readmeFm(directory).file) %>" style="width: 100%; height: 90vh;"></iframe> <ul class="nav nav-tabs" id="readerTab" role="tablist">
<% if (h.isMsOfficeViewerSupported(h.readmeFm(directory).file)) { %>
<li class="nav-item" role="presentation">
<button class="nav-link active" id="ms-office-viewer-tab" data-bs-toggle="tab" data-bs-target="#ms-office-viewer" type="button" role="tab" aria-controls="ms-office-viewer" aria-selected="true">Microsoft Office Web Viewer</button>
</li>
<% } %>
<% if (h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) { %>
<li class="nav-item" role="presentation">
<button class="nav-link<%=h.isMsOfficeViewerSupported(h.readmeFm(directory).file) ? '' : ' active'%>" id="google-docs-viewer-tab" data-bs-toggle="tab" data-bs-target="#google-docs-viewer" type="button" role="tab" aria-controls="google-docs-viewer" aria-selected="false">Google Docs Viewer</button>
</li>
<% } %>
<li class="nav-item" role="presentation">
<button class="nav-link" id="view-download-tab" data-bs-toggle="tab" data-bs-target="#view-download" type="button" role="tab" aria-controls="view-download" aria-selected="false">View/Download</button>
</li>
</ul>
<div class="tab-content">
<% if (h.isMsOfficeViewerSupported(h.readmeFm(directory).file)) { %>
<div class="tab-pane active" id="ms-office-viewer" role="tabpanel" aria-labelledby="ms-office-viewer-tab" tabindex="0">
<iframe src="https://view.officeapps.live.com/op/embed.aspx?src=<%- encodeURIComponent(h.trimSlashes(config.siteUrl) + path.posix.join(breadcrumbs[breadcrumbs.length-1].url, (h.readmeFm(directory).file.replaceAll('\\', '/')))) %>" style="width: 100%; height: 85vh; border: solid 1px #dfd7ca; border-top: 0px; border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem;" frameborder="0"></iframe>
</div>
<% } %>
<% if (h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) { %>
<div class="tab-pane<%=h.isMsOfficeViewerSupported(h.readmeFm(directory).file) ? '' : ' active'%>" id="google-docs-viewer" role="tabpanel" aria-labelledby="google-docs-viewer-tab" tabindex="0">
<iframe src="https://docs.google.com/gview?embedded=true&url=<%- encodeURIComponent(h.trimSlashes(config.siteUrl) + path.posix.join(breadcrumbs[breadcrumbs.length-1].url, (h.readmeFm(directory).file.replaceAll('\\', '/')))) %>" style="width: 100%; height: 85vh; border: solid 1px #dfd7ca; border-top: 0px; border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem;" frameborder="0"></iframe>
</div>
<% } %>
<div class="tab-pane<%=(h.isMsOfficeViewerSupported(h.readmeFm(directory).file) || h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) ? '' : ' active'%>" id="view-download" role="tabpanel" aria-labelledby="view-download-tab" tabindex="0">
<iframe src="about:blank" style="width: 100%; height: 85vh; border: solid 1px #dfd7ca; border-top: 0px; border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem;" frameborder="0"></iframe>
</div>
<script>
$(document).ready(function () {
var viewDownloadTabButton = $('button#view-download-tab');
var viewDownloadTab = new bootstrap.Tab(viewDownloadTabButton);
var viewDownloadTabPaneIFrame = $('#view-download iframe');
viewDownloadTabButton.on('click', function (event) {
event.preventDefault();
viewDownloadTab.show();
if (viewDownloadTabPaneIFrame.attr('src') == 'about:blank') {
console.log('View/Download tab clicked. Loading iframe.');
viewDownloadTabPaneIFrame.attr('src', '<%- encodeURI(h.readmeFm(directory).file) %>');
} else {
console.log('View/Download tab clicked. iframe previously loaded.');
}
})
})
</script>
<% if (!h.isMsOfficeViewerSupported(h.readmeFm(directory).file) && !h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) { %>
<script>
$(document).ready(function () {
setTimeout(function () {
var viewDownloadTabButton = $('button#view-download-tab');
viewDownloadTabButton.click();
}, 100);
});
</script>
<% } %>
</div>
<% } else { %> <% } else { %>
<%- h.printReadme(directory) %> <%- h.printReadme(directory) %>
<% } %> <% } %>
@ -54,7 +105,6 @@
</div> </div>
<% } %> <% } %>
<% } %> <% } %>
<ul id="files" class="list-group shadow-lg"> <ul id="files" class="list-group shadow-lg">
<% files.forEach(function(value, index) { %> <% files.forEach(function(value, index) { %>
<li class="list-group-item list-group-item-action flex-column align-items-start"> <li class="list-group-item list-group-item-action flex-column align-items-start">
@ -65,7 +115,6 @@
<% }); %> <% }); %>
</ul> </ul>
</main> </main>
<%- include('./includes/bottom-navbar.ejs') %> <%- include('./includes/bottom-navbar.ejs') %>
<%- include('./includes/bottom-scripts.ejs') %> <%- include('./includes/bottom-scripts.ejs') %>
</body> </body>

View File

@ -1,5 +1,4 @@
<!-- Bootstrap JS (optional, if you need Bootstrap JS features) --> <!-- Bootstrap JS (optional, if you need Bootstrap JS features) -->
<script src="https://daball.me/vendor/jquery/jquery.min.js"></script>
<script src="https://daball.me/vendor/popper.js/dist/popper.min.js"></script> <script src="https://daball.me/vendor/popper.js/dist/popper.min.js"></script>
<script src="https://daball.me/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="https://daball.me/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="https://daball.me/vendor/jquery-easing/jquery.easing.min.js"></script> <script src="https://daball.me/vendor/jquery-easing/jquery.easing.min.js"></script>

View File

@ -10,7 +10,6 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8937572456576531" crossorigin="anonymous"></script> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8937572456576531" crossorigin="anonymous"></script>
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link href="https://daball.me/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://daball.me/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Saira+Extra+Condensed:100,200,300,400,500,600,700,800,900" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Saira+Extra+Condensed:100,200,300,400,500,600,700,800,900" rel="stylesheet" />
@ -26,3 +25,4 @@
<link href="https://daball.me/vendor/simple-line-icons/css/simple-line-icons.css" rel="stylesheet" /> <link href="https://daball.me/vendor/simple-line-icons/css/simple-line-icons.css" rel="stylesheet" />
<link href="https://daball.me/layouts/blog/css/blog.min.css" rel="stylesheet" /> <link href="https://daball.me/layouts/blog/css/blog.min.css" rel="stylesheet" />
<link href="/css/nm3clol.css" rel="stylesheet" /> <link href="/css/nm3clol.css" rel="stylesheet" />
<script src="https://daball.me/vendor/jquery/jquery.min.js"></script>