mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 13:14:16 +08:00
Rewrite quickpreview to use strategy
concept, add PrismJS for code file types
This commit is contained in:
parent
b7939d38bd
commit
80dcc9af73
6 changed files with 351 additions and 61 deletions
|
@ -131,6 +131,7 @@ export class AttachmentItem extends Component {
|
|||
_onAttachmentKeyDown = event => {
|
||||
if (event.key === SPACE && this.props.filePreviewPath) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
Actions.quickPreviewFile(this.props.filePath);
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
|
|
|
@ -11,8 +11,7 @@ const ThumbnailWidth = 320 * (11 / 8.5);
|
|||
const QuicklookIsAvailable = process.platform === 'darwin';
|
||||
const PDFJSRoot = path.join(__dirname, 'pdfjs-2.0.943');
|
||||
|
||||
// TODO make this list more exhaustive
|
||||
const QuicklookNonPreviewableExtensions = [
|
||||
const QuicklookBlacklist = [
|
||||
'jpg',
|
||||
'bmp',
|
||||
'gif',
|
||||
|
@ -27,31 +26,75 @@ const QuicklookNonPreviewableExtensions = [
|
|||
'ics',
|
||||
];
|
||||
|
||||
const CrossplatformPreviewableExtensions = [
|
||||
// pdfjs
|
||||
'pdf',
|
||||
const CrossplatformStrategies = {
|
||||
pdfjs: ['pdf'],
|
||||
mammoth: ['docx'],
|
||||
snarkdown: ['md'],
|
||||
xlsx: [
|
||||
'xls',
|
||||
'xlsx',
|
||||
'csv',
|
||||
'eth',
|
||||
'ods',
|
||||
'fods',
|
||||
'uos1',
|
||||
'uos2',
|
||||
'dbf',
|
||||
'txt',
|
||||
'prn',
|
||||
'xlw',
|
||||
'xlsb',
|
||||
],
|
||||
prism: [
|
||||
'html',
|
||||
'svg',
|
||||
'xml',
|
||||
'css',
|
||||
'c',
|
||||
'cc',
|
||||
'cpp',
|
||||
'js',
|
||||
'jsx',
|
||||
'tsx',
|
||||
'ts',
|
||||
'go',
|
||||
'cs',
|
||||
'patch',
|
||||
'swift',
|
||||
'java',
|
||||
'json',
|
||||
'jsonp',
|
||||
'tex',
|
||||
'mm',
|
||||
'm',
|
||||
'h',
|
||||
'py',
|
||||
'rb',
|
||||
'rs',
|
||||
'sql',
|
||||
'yaml',
|
||||
'txt',
|
||||
'log',
|
||||
],
|
||||
};
|
||||
|
||||
// Mammoth
|
||||
'docx',
|
||||
const CrossplatformStrategiesBetterThanQuicklook = ['snarkdown', 'prism'];
|
||||
|
||||
// Snarkdown
|
||||
'md',
|
||||
function strategyForPreviewing(ext) {
|
||||
if (ext.startsWith('.')) ext = ext.substr(1);
|
||||
|
||||
// XLSX
|
||||
'xls',
|
||||
'xlsx',
|
||||
'csv',
|
||||
'eth',
|
||||
'ods',
|
||||
'fods',
|
||||
'uos1',
|
||||
'uos2',
|
||||
'dbf',
|
||||
'txt',
|
||||
'prn',
|
||||
'xlw',
|
||||
'xlsb',
|
||||
];
|
||||
const strategy = Object.keys(CrossplatformStrategies).find(strategy =>
|
||||
CrossplatformStrategies[strategy].includes(ext)
|
||||
);
|
||||
|
||||
if (QuicklookIsAvailable && !QuicklookBlacklist.includes(ext)) {
|
||||
if (!strategy || !CrossplatformStrategiesBetterThanQuicklook.includes(strategy)) {
|
||||
return 'quicklook';
|
||||
}
|
||||
}
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
const PreviewWindowMenuTemplate = [
|
||||
{
|
||||
|
@ -146,25 +189,19 @@ export function canPossiblyPreviewExtension(file) {
|
|||
if (file.size > FileSizeLimit) {
|
||||
return false;
|
||||
}
|
||||
// On macOS, we try to quicklook basically everything because it supports
|
||||
// a large number of formats and plugins (adding support for Sketch files, etc).
|
||||
// On other platforms, we only preview a specific set of formats we support.
|
||||
if (QuicklookIsAvailable) {
|
||||
return !QuicklookNonPreviewableExtensions.includes(file.displayExtension());
|
||||
} else {
|
||||
return CrossplatformPreviewableExtensions.includes(file.displayExtension());
|
||||
}
|
||||
return !!strategyForPreviewing(file.displayExtension());
|
||||
}
|
||||
|
||||
export function displayQuickPreviewWindow(filePath) {
|
||||
if (QuicklookIsAvailable) {
|
||||
const isPDF = filePath.endsWith('.pdf');
|
||||
const strategy = strategyForPreviewing(path.extname(filePath));
|
||||
|
||||
if (strategy === 'quicklook') {
|
||||
const currentWin = AppEnv.getCurrentWindow();
|
||||
currentWin.previewFile(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
const isPDF = filePath.endsWith('.pdf');
|
||||
|
||||
if (quickPreviewWindow === null) {
|
||||
quickPreviewWindow = new remote.BrowserWindow({
|
||||
width: 800,
|
||||
|
@ -192,28 +229,28 @@ export function displayQuickPreviewWindow(filePath) {
|
|||
});
|
||||
} else {
|
||||
quickPreviewWindow.loadFile(path.join(__dirname, 'renderer.html'), {
|
||||
search: JSON.stringify({ mode: 'display', filePath }),
|
||||
search: JSON.stringify({ mode: 'display', filePath, strategy }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function generatePreview(...args) {
|
||||
if (QuicklookIsAvailable) {
|
||||
return await _generateQuicklookPreview(...args);
|
||||
export async function generatePreview({ file, filePath, previewPath }) {
|
||||
const strategy = strategyForPreviewing(file.displayExtension());
|
||||
|
||||
if (strategy === 'quicklook') {
|
||||
return await _generateQuicklookPreview({ file, filePath, previewPath });
|
||||
} else if (strategy) {
|
||||
return await _generateCrossplatformPreview({ file, filePath, previewPath, strategy });
|
||||
} else {
|
||||
return await _generateCrossplatformPreview(...args);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
async function _generateCrossplatformPreview({ file, filePath, previewPath }) {
|
||||
if (!CrossplatformPreviewableExtensions.includes(file.displayExtension())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function _generateCrossplatformPreview({ file, filePath, previewPath, strategy }) {
|
||||
return new Promise(resolve => {
|
||||
captureQueue.push({ file, filePath, previewPath, resolve });
|
||||
captureQueue.push({ file, filePath, previewPath, strategy, resolve });
|
||||
|
||||
if (!captureWindow || captureWindow.isDestroyed()) {
|
||||
captureWindow = _createCaptureWindow();
|
||||
|
@ -255,11 +292,11 @@ function _generateNextCrossplatformPreview() {
|
|||
return;
|
||||
}
|
||||
|
||||
const { filePath, previewPath, resolve } = captureQueue.pop();
|
||||
const { strategy, filePath, previewPath, resolve } = captureQueue.pop();
|
||||
|
||||
// Start the thumbnail generation
|
||||
captureWindow.loadFile(path.join(__dirname, 'renderer.html'), {
|
||||
search: JSON.stringify({ mode: 'capture', filePath, previewPath }),
|
||||
search: JSON.stringify({ strategy, mode: 'capture', filePath, previewPath }),
|
||||
});
|
||||
|
||||
// Race against a timer to complete the preview. We don't want this to hang
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PDFJSRoot = path.join(__dirname, 'pdfjs-2.0.943');
|
||||
global.PDFJSRoot = path.join(__dirname, 'pdfjs-2.0.943');
|
||||
global.PrismRoot = path.join(__dirname, 'prism-1.15.0');
|
||||
|
||||
global.nodePDFForFile = async filePath => {
|
||||
global.pdfjs = require(`${PDFJSRoot}/build/pdf.js`);
|
||||
global.pdfjs.GlobalWorkerOptions.workerSrc = path.join(PDFJSRoot, 'build', 'pdf.worker.js');
|
||||
global.pdfjs = require(`${global.PDFJSRoot}/build/pdf.js`);
|
||||
global.pdfjs.GlobalWorkerOptions.workerSrc = path.join(
|
||||
global.PDFJSRoot,
|
||||
'build',
|
||||
'pdf.worker.js'
|
||||
);
|
||||
|
||||
return await global.pdfjs.getDocument(filePath, {
|
||||
cMapUrl: path.join(PDFJSRoot, 'web', 'cmaps'),
|
||||
cMapUrl: path.join(global.PDFJSRoot, 'web', 'cmaps'),
|
||||
cMapPacked: true,
|
||||
});
|
||||
};
|
||||
|
||||
global.nodeStringForFile = (filePath, { truncate } = {}) => {
|
||||
let raw = fs.readFileSync(filePath).toString();
|
||||
if (truncate) raw = raw.substr(0, 1000);
|
||||
return raw;
|
||||
};
|
||||
|
||||
global.nodeXLSXForFile = filepath => {
|
||||
global.xlsx = require('xlsx');
|
||||
return fs.readFileSync(filepath);
|
||||
|
@ -47,10 +58,16 @@ global.finishWithWindowCapture = (previewPath, startedAt = Date.now()) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const win = require('electron').remote.getCurrentWindow();
|
||||
win.capturePage(img => {
|
||||
fs.writeFileSync(previewPath, img.toPNG());
|
||||
document.title = 'Finished';
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
const win = require('electron').remote.getCurrentWindow();
|
||||
win.capturePage(img => {
|
||||
fs.writeFileSync(previewPath, img.toPNG());
|
||||
document.title = 'Finished';
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
125
app/src/quickpreview/prism-1.15.0/prism.css
Normal file
125
app/src/quickpreview/prism-1.15.0/prism.css
Normal file
|
@ -0,0 +1,125 @@
|
|||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+c+csharp+cpp+ruby+diff+docker+go+java+json+latex+makefile+objectivec+sql+python+jsx+typescript+rust+swift+yaml+tsx */
|
||||
/**
|
||||
* okaidia theme for JavaScript, CSS and HTML
|
||||
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||
* @author ocodia
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #f8f8f2;
|
||||
background: none;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #272822;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.number {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
27
app/src/quickpreview/prism-1.15.0/prism.js
Normal file
27
app/src/quickpreview/prism-1.15.0/prism.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -124,6 +124,87 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function runPrism({ filePath, previewPath, mode }) {
|
||||
const ext = filePath.split('.').pop();
|
||||
const str = await nodeStringForFile(filePath, {
|
||||
truncate: mode === 'capture',
|
||||
});
|
||||
|
||||
const grammars = {
|
||||
html: 'html',
|
||||
xml: 'xml',
|
||||
svg: 'svg',
|
||||
css: 'css',
|
||||
js: 'javascript',
|
||||
c: 'c',
|
||||
h: 'c',
|
||||
cs: 'csharp',
|
||||
cc: 'cpp',
|
||||
cpp: 'cpp',
|
||||
patch: 'diff',
|
||||
go: 'go',
|
||||
java: 'java',
|
||||
json: 'json',
|
||||
jsonp: 'json',
|
||||
tex: 'latex',
|
||||
m: 'objectivec',
|
||||
mm: 'objectivec',
|
||||
py: 'python',
|
||||
jsx: 'jsx',
|
||||
tsx: 'tsx',
|
||||
rb: 'ruby',
|
||||
rs: 'rust',
|
||||
sql: 'sql',
|
||||
swift: 'swift',
|
||||
ts: 'typescript',
|
||||
yaml: 'yaml',
|
||||
txt: 'clike',
|
||||
log: 'clike',
|
||||
};
|
||||
|
||||
const grammar = grammars[ext] || 'clike';
|
||||
|
||||
// Attach Prism CSS
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = './prism-1.15.0/prism.css';
|
||||
document.body.appendChild(link);
|
||||
document.body.style.margin = '0';
|
||||
|
||||
// Attach <pre><code class="language-XXX">{text}</code></pre> to the DOM
|
||||
div.innerHTML = '';
|
||||
if (mode !== 'capture') {
|
||||
div.style.opacity = 0;
|
||||
}
|
||||
const preEl = document.createElement('pre');
|
||||
preEl.style = 'min-height: 100%; margin: 0; border-radius: 0;';
|
||||
div.appendChild(preEl);
|
||||
|
||||
const codeEl = document.createElement('code');
|
||||
codeEl.textContent = str;
|
||||
codeEl.classList.add(`language-${grammar}`);
|
||||
preEl.appendChild(codeEl);
|
||||
|
||||
// Load the Prism script and capture/show the DOM when it's completed
|
||||
const finish = () => {
|
||||
const stylesReady = window.getComputedStyle(preEl).fontFamily.includes('Monaco');
|
||||
if (!stylesReady) {
|
||||
setTimeout(finish, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
div.style.opacity = 1;
|
||||
if (mode === 'capture') {
|
||||
window.finishWithWindowCapture(previewPath);
|
||||
}
|
||||
};
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.onload = finish;
|
||||
script.src = './prism-1.15.0/prism.js';
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
async function runMammoth({ filePath, previewPath, mode }) {
|
||||
div.innerHTML = await nodeMammothHTMLForFile(filePath);
|
||||
if (mode === 'capture') {
|
||||
|
@ -140,22 +221,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
const { mode, filePath, previewPath } = JSON.parse(
|
||||
const { mode, filePath, previewPath, strategy } = JSON.parse(
|
||||
decodeURIComponent(window.location.search.substr(1))
|
||||
);
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
if (filePath.endsWith('.pdf')) {
|
||||
if (strategy === 'pdfjs') {
|
||||
if (mode === 'capture') {
|
||||
runPDFCapture({ filePath, previewPath });
|
||||
} else {
|
||||
// To view PDFs we use the separate pdfviewer included with pdfjs
|
||||
}
|
||||
} else if (filePath.endsWith('.docx')) {
|
||||
} else if (strategy === 'mammoth') {
|
||||
runMammoth({ mode, filePath, previewPath });
|
||||
} else if (filePath.endsWith('.md')) {
|
||||
} else if (strategy === 'snarkdown') {
|
||||
runSnarkdown({ mode, filePath, previewPath });
|
||||
} else {
|
||||
} else if (strategy === 'prism') {
|
||||
runPrism({ mode, filePath, previewPath });
|
||||
} else if (strategy === 'xlsx') {
|
||||
runXLSX({ mode, filePath, previewPath });
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue