diff --git a/src/model/structure.js b/src/model/structure.js index 64990b9..a33b321 100644 --- a/src/model/structure.js +++ b/src/model/structure.js @@ -1,5 +1,6 @@ const path = require('path') const urljoin = require('url-join') +const url = require('./url') const BROWSER_SUPPORTED_EXT = /(jpg|jpeg|png|gif)$/i @@ -51,8 +52,8 @@ function photoExtension (filepath) { } function join (prefix, filepath) { - if (prefix.match(/^https?:\/\//)) { - return urljoin(prefix, filepath) + if (prefix.match(/^(http|https|file):\/\//)) { + return urljoin(prefix, url.fromPath(filepath)) } else { return path.join(prefix, filepath) } diff --git a/src/model/url.js b/src/model/url.js index 52a5641..953e38d 100644 --- a/src/model/url.js +++ b/src/model/url.js @@ -1,9 +1,19 @@ +const path = require('path') const process = require('process') exports.fromPath = function (filepath) { + // already a URL (typically provided as a CLI argument, e.g. link prefix) + if (filepath.match(/^(http|https|file):\/\//)) { + return filepath + } + // absolute paths should start with file:// + const prefix = path.isAbsolute(filepath) ? 'file://' : '' + // convert \ to / but only on Windows if (process.platform === 'win32') { filepath = filepath.replace(/\\/g, '/') } - const encoded = encodeURIComponent(filepath) - return encoded.replace(/%2F/g, '/') + // encode URLs, but decode overly-encoded slashes + filepath = encodeURIComponent(filepath).replace(/%2F/g, '/') + // prepend the prefix if needed + return prefix + filepath } diff --git a/src/steps/step-process.js b/src/steps/step-process.js index d5ce2a9..e23e052 100644 --- a/src/steps/step-process.js +++ b/src/steps/step-process.js @@ -36,7 +36,9 @@ exports.create = function (files, opts, problems) { const destDate = modifiedDate(dest) const action = actionMap[f.output[out].rel] // ignore output files that don't have an action (e.g. existing links) - debug(`Comparing ${f.path} (${f.date}) and ${f.output[out].path} (${destDate})`) + if (action) { + debug(`Comparing ${f.path} (${f.date}) and ${f.output[out].path} (${destDate})`) + } if (action && f.date > destDate) { sourceFiles.add(f.path) tasks[dest] = { diff --git a/src/website/theme-base/helpers/relative.js b/src/website/theme-base/helpers/relative.js index c845cf4..6eddf23 100644 --- a/src/website/theme-base/helpers/relative.js +++ b/src/website/theme-base/helpers/relative.js @@ -1,6 +1,10 @@ const path = require('path') module.exports = (target, options) => { + // if already an absolute URL, do nothing + if (target.match(/^(http|https|file):\/\//)) { + return target + } const albumPath = options.data.root.album.path const relative = path.relative(path.dirname(albumPath), target) const url = relative.replace(/\\/g, '/') diff --git a/test/model/structure.spec.js b/test/model/structure.spec.js index d587f73..5fe4dff 100644 --- a/test/model/structure.spec.js +++ b/test/model/structure.spec.js @@ -71,11 +71,31 @@ describe('Structure', () => { should(folders('holidays/IMG_0001.mp4', 'video:poster')).endWith(ospath('IMG_0001.jpg')) }) - it('can use a file system link', () => { + it('can use a relative file system link', () => { const res = folders('holidays/IMG_0001.jpg', 'fs:link', { linkPrefix: '../..' }) should(res).eql(ospath('../../holidays/IMG_0001.jpg')) }) + itLinux('can use an absolute file system link', () => { + const res = folders('holidays/IMG_0001.jpg', 'fs:link', { linkPrefix: '/media' }) + should(res).eql('/media/holidays/IMG_0001.jpg') + }) + + itLinux('can use a file:// system link', () => { + const res = folders('holidays/IMG_0001.jpg', 'fs:link', { linkPrefix: 'file:///media' }) + should(res).eql('file:///media/holidays/IMG_0001.jpg') + }) + + itWindows('can use an absolute file system link', () => { + const res = folders('holidays/IMG_0001.jpg', 'fs:link', { linkPrefix: 'C:\\media' }) + should(res).eql('C:\\media\\holidays\\IMG_0001.jpg') + }) + + itWindows('can use a file:// system link', () => { + const res = folders('holidays/IMG_0001.jpg', 'fs:link', { linkPrefix: 'file://C:/media' }) + should(res).eql('file://C:/media/holidays/IMG_0001.jpg') + }) + it('can use a remote HTTP link', () => { const res = folders('holidays/IMG_0001.jpg', 'fs:link', { linkPrefix: 'http://test.com' }) should(res).eql('http://test.com/holidays/IMG_0001.jpg') diff --git a/test/model/url.spec.js b/test/model/url.spec.js index f62733a..9417153 100644 --- a/test/model/url.spec.js +++ b/test/model/url.spec.js @@ -15,21 +15,36 @@ describe('URLs', () => { }) }) - itLinux('converts standard Linux paths', function () { + it('doesnt escape a file:// prefix', function () { + const res = url.fromPath('file:///media/photos/IMG_001.jpg') + should(res).eql('file:///media/photos/IMG_001.jpg') + }) + + itLinux('converts relative Linux paths', function () { const res = url.fromPath('photos/holidays/IMG_001.jpg') should(res).eql('photos/holidays/IMG_001.jpg') }) + itLinux('converts absolute Linux paths', function () { + const res = url.fromPath('/photos/holidays/IMG_001.jpg') + should(res).eql('file:///photos/holidays/IMG_001.jpg') + }) + itLinux('encodes backslash in Linux paths', function () { const res = url.fromPath('photos/my\\holidays.jpg') should(res).eql('photos/my%5Cholidays.jpg') }) - itWindows('converts standard Windows paths', function () { + itWindows('converts relative Windows paths', function () { const res = url.fromPath('photos\\holidays\\IMG_001.jpg') should(res).eql('photos/holidays/IMG_001.jpg') }) + itWindows('converts absolute Windows paths', function () { + const res = url.fromPath('C:\\photos\\holidays\\IMG_001.jpg') + should(res).eql('file://C:/photos/holidays/IMG_001.jpg') + }) + itWindows('slashes are also valid path separators on Windows', function () { const res = url.fromPath('photos/holidays/IMG_001.jpg') should(res).eql('photos/holidays/IMG_001.jpg') diff --git a/test/themes/helpers/relative.spec.js b/test/themes/helpers/relative.spec.js index 7e1d0fd..15e3dd0 100644 --- a/test/themes/helpers/relative.spec.js +++ b/test/themes/helpers/relative.spec.js @@ -25,6 +25,14 @@ describe('Handlebars helpers: relative', () => { should(res).eql('') }) + it('does not do anything if the path is an absolute URL', () => { + // This can happen when using --link-prefix + const url = 'http://example.com/photo.jpg' + const template = handlebars.compile(``) + const res = template({}) + should(res).eql(``) + }) + // TODO: this should not be needed anymore because all URLs are already escaped it('escapes single quotes so they can be used in CSS background-image', () => { const template = handlebars.compile(`background-image('{{relative url}}')`)