mirror of https://github.com/thumbsup/thumbsup
Compare commits
No commits in common. 'master' and 'v2.12.0' have entirely different histories.
@ -1,6 +0,0 @@
|
||||
ignores:
|
||||
# dynamically loaded themes
|
||||
- "@thumbsup/theme-cards"
|
||||
- "@thumbsup/theme-classic"
|
||||
- "@thumbsup/theme-mosaic"
|
||||
- "@thumbsup/theme-flow"
|
@ -0,0 +1,40 @@
|
||||
|
||||
<!--
|
||||
|
||||
Hi!
|
||||
|
||||
Thanks for taking the time to submit a feature request or bug report. Please try to fill out the relevant fields, and remove any irrelevant section from this template.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
## Feature request
|
||||
|
||||
Please describe the new feature or behaviour you'd like to see.
|
||||
If possible, give a concrete use case.
|
||||
|
||||
## Bug report
|
||||
|
||||
If running as an npm package:
|
||||
|
||||
```
|
||||
Thumbsup version: __________
|
||||
Node version: __________
|
||||
NPM version: __________
|
||||
Operating system: __________
|
||||
```
|
||||
|
||||
If running as a Docker container:
|
||||
|
||||
```
|
||||
Thumbsup image tag: __________
|
||||
Docker version: __________
|
||||
Operating system: __________
|
||||
```
|
||||
|
||||
About the bug...
|
||||
|
||||
- Are you getting an error message?
|
||||
- Have you tried running `DEBUG="*" thumbsup <args>` to get more troubleshooting info?
|
||||
- Is the behavior different from expected?
|
||||
- Can you provide steps to reproduce the issue?
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Something went wrong? Create a bug report to help us improve.
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for submitting a bug report.
|
||||
Please fill out the sections below.
|
||||
-->
|
||||
|
||||
## Bug description
|
||||
|
||||
<!--
|
||||
Are you getting an error message?
|
||||
Is the behavior different from expected?
|
||||
Have you tried running `thumbsup <args> --log debug`?
|
||||
-->
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
<!--
|
||||
Please provide minimal steps to reproduce the issue.
|
||||
-->
|
@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Documentation update
|
||||
url: https://github.com/thumbsup/docs
|
||||
about: Something missing in the documentation? Please open a pull-request over there.
|
||||
- name: Ideas and questions
|
||||
url: https://github.com/thumbsup/thumbsup/discussions
|
||||
about: Have questions about Thumbsup? Want to discuss ideas for the project?
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a new feature for Thumbsup.
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
## Feature name
|
||||
|
||||
<!--
|
||||
Please describe the new feature or behaviour you'd like to see.
|
||||
If possible, give concrete use cases.
|
||||
|
||||
If this is a high-level idea or a question like "how do I..."
|
||||
then a Discussion might be a better choice.
|
||||
-->
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
name: Installation issues
|
||||
about: Any problem installing Thumbsup?
|
||||
labels: installation
|
||||
---
|
||||
|
||||
If trying to install Thumbsup locally:
|
||||
|
||||
```
|
||||
Thumbsup version: __________
|
||||
Node version: __________
|
||||
NPM version: __________
|
||||
Operating system: __________
|
||||
```
|
||||
|
||||
If trying to run the Docker image:
|
||||
|
||||
```
|
||||
Thumbsup image tag: __________
|
||||
Docker version: __________
|
||||
Operating system: __________
|
||||
```
|
||||
|
||||
What are the error messages?
|
@ -1,7 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 1
|
||||
schedule:
|
||||
interval: "monthly"
|
@ -1,38 +0,0 @@
|
||||
name: Publish Docker base images
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
nodejs: [18, 20]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
- name: Publish thumbsup/runtime
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: docker
|
||||
file: ./docker/Dockerfile.runtime
|
||||
build-args: NODE_VERSION=${{ matrix.nodejs }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
tags: ghcr.io/thumbsup/runtime:node-${{ matrix.nodejs }}
|
||||
push: true
|
||||
- name: Publish thumbsup/build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: docker
|
||||
file: ./docker/Dockerfile.build
|
||||
build-args: NODE_VERSION=${{ matrix.nodejs }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
tags: ghcr.io/thumbsup/build:node-${{ matrix.nodejs }}
|
||||
push: true
|
@ -1,35 +0,0 @@
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_run:
|
||||
workflows: ["Publish NPM package"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
- id: version
|
||||
name: Fetch latest NPM version
|
||||
run: echo ::set-output name=version::$(npm show thumbsup version)
|
||||
- name: Publish Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: docker
|
||||
file: ./docker/Dockerfile.release
|
||||
build-args: PACKAGE_VERSION=${{ steps.version.outputs.version }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
tags: |
|
||||
ghcr.io/thumbsup/thumbsup:${{ steps.version.outputs.version }}
|
||||
ghcr.io/thumbsup/thumbsup:latest
|
||||
push: true
|
@ -1,23 +0,0 @@
|
||||
name: Publish NPM package
|
||||
|
||||
# This workflow runs every time a version tag is pushed
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
|
||||
# Publish the npm package
|
||||
publish:
|
||||
name: Publish package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- run: npm publish --ignore-scripts
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
@ -1,19 +0,0 @@
|
||||
name: Test on Linux
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile.test
|
@ -1,21 +0,0 @@
|
||||
name: Test on Windows
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
- run: choco install exiftool graphicsmagick
|
||||
# GraphicsMagick must be manually added to the path
|
||||
- run: |
|
||||
$installPath = Get-ChildItem 'C:\Program Files\GraphicsMagick*' | Select-Object -First 1 | % { $_.FullName }
|
||||
echo "$installPath" | Out-file -Append -FilePath $env:GITHUB_PATH -Encoding utf8
|
||||
- run: npm install
|
||||
- run: npm test
|
@ -1,6 +0,0 @@
|
||||
recursive: true
|
||||
file:
|
||||
- test/helpers.js
|
||||
- test/log.js
|
||||
reporter: list
|
||||
parallel: false
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"all": true,
|
||||
"src": ["src"],
|
||||
"reporter": ["html", "text-summary"]
|
||||
}
|
@ -0,0 +1 @@
|
||||
ignore-extra=@thumbsup/theme-cards,@thumbsup/theme-classic,@thumbsup/theme-mosaic,@thumbsup/theme-flow
|
@ -0,0 +1,36 @@
|
||||
# Build steps need access to the Docker agent
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
|
||||
jobs:
|
||||
include:
|
||||
|
||||
# Run all the tests inside Docker
|
||||
|
||||
- stage: Test
|
||||
script: docker build -f Dockerfile.test .
|
||||
|
||||
|
||||
# If this is a tagged commit, publish the package to npm
|
||||
|
||||
- stage: Release npm
|
||||
script: echo "Deploying to npm"
|
||||
deploy:
|
||||
provider: npm
|
||||
email: asyncadventures@gmail.com
|
||||
api_key: $NPM_TOKEN
|
||||
on:
|
||||
tags: true
|
||||
condition: $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+
|
||||
|
||||
# If this is a tagged commit, publish a new Docker image
|
||||
|
||||
- stage: Release Docker
|
||||
script: echo "Deploying to DockerHub"
|
||||
deploy:
|
||||
provider: script
|
||||
script: scripts/travis-release-docker
|
||||
on:
|
||||
tags: true
|
||||
condition: $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+
|
@ -0,0 +1,35 @@
|
||||
const Insight = require('insight')
|
||||
const path = require('path')
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'))
|
||||
|
||||
// Google Analytics tracking code
|
||||
const TRACKING_CODE = 'UA-110087713-3'
|
||||
|
||||
class Analytics {
|
||||
constructor ({ enabled }) {
|
||||
this.enabled = enabled
|
||||
this.insight = new Insight({ trackingCode: TRACKING_CODE, pkg })
|
||||
this.insight.optOut = !enabled
|
||||
}
|
||||
|
||||
// report that the gallery has started building
|
||||
start (done) {
|
||||
this.insight.track('start')
|
||||
}
|
||||
|
||||
// report that the gallery has finished building + some stats
|
||||
finish (stats, done) {
|
||||
this.insight.track('finish')
|
||||
this.insight.trackEvent({ category: 'gallery', action: 'albums', label: 'Album count', value: stats.albums })
|
||||
this.insight.trackEvent({ category: 'gallery', action: 'photos', label: 'Photo count', value: stats.photos })
|
||||
this.insight.trackEvent({ category: 'gallery', action: 'videos', label: 'Video count', value: stats.videos })
|
||||
}
|
||||
|
||||
// report that an error happened
|
||||
// but don't report the contents (might contain file paths etc)
|
||||
error (done) {
|
||||
this.insight.track('error')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Analytics
|
@ -1,11 +0,0 @@
|
||||
# -------------------------------------------------
|
||||
# This Docker image is used to speed up the builds
|
||||
# -------------------------------------------------
|
||||
|
||||
ARG NODE_VERSION
|
||||
|
||||
# Node.js + runtime dependencies
|
||||
FROM ghcr.io/thumbsup/runtime:node-${NODE_VERSION}
|
||||
|
||||
# Standard build dependencies for npm install
|
||||
RUN apk add --no-cache git make g++ python3 bash
|
@ -1,17 +0,0 @@
|
||||
# -------------------------------------------------
|
||||
# This Docker image contains all the typical
|
||||
# runtime dependencies for thumbsup, including
|
||||
# exiftool, imagemagick, ffmpeg, gifsicle...
|
||||
# -------------------------------------------------
|
||||
|
||||
ARG NODE_VERSION
|
||||
FROM node:${NODE_VERSION}-alpine as base
|
||||
|
||||
# Metadata
|
||||
LABEL org.opencontainers.image.source https://github.com/thumbsup/thumbsup
|
||||
|
||||
# Add libraries
|
||||
RUN apk add --update --no-cache libgomp zlib libpng libjpeg-turbo libwebp tiff lcms2 x265 libde265 libheif
|
||||
|
||||
# Add external programs
|
||||
RUN apk add --update --no-cache ffmpeg imagemagick graphicsmagick exiftool gifsicle zip
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
PATH=$PATH:./node_modules/.bin
|
||||
|
||||
function cli {
|
||||
echo "\`\`\`"
|
||||
node bin/thumbsup.js --help
|
||||
echo "\`\`\`"
|
||||
}
|
||||
|
||||
echo "$(cli)" | node-injectmd -t cli -i README.md
|
@ -0,0 +1,20 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
if [ -z "${TRAVIS_TAG}" ]; then
|
||||
echo "This script releases a Docker image corresponding to the npm package version"
|
||||
echo "It should only be run on Travis CI for tagged commits"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the image
|
||||
|
||||
DOCKER_IMAGE="thumbsupgallery/thumbsup"
|
||||
PACKAGE_VERSION="${TRAVIS_TAG//v}"
|
||||
docker build -f Dockerfile.release -t "${DOCKER_IMAGE}:${PACKAGE_VERSION}" --build-arg "PACKAGE_VERSION=${PACKAGE_VERSION}" .
|
||||
|
||||
# Pushes both <thumbsup:x.y.z> and <thumbsup:latest>
|
||||
|
||||
docker login -u "${DOCKER_USERNAME}" -p "${DOCKER_PASSWORD}"
|
||||
docker tag "${DOCKER_IMAGE}:${PACKAGE_VERSION}" "${DOCKER_IMAGE}:latest"
|
||||
docker push "${DOCKER_IMAGE}:${PACKAGE_VERSION}"
|
||||
docker push "${DOCKER_IMAGE}:latest"
|
@ -1,11 +0,0 @@
|
||||
const fs = require('node:fs')
|
||||
const child = require('node:child_process')
|
||||
|
||||
// get latest CLI help text
|
||||
const output = child.execSync('node bin/thumbsup.js --help')
|
||||
const codeblock = '```' + output + '```'
|
||||
|
||||
// update README file
|
||||
const readme = fs.readFileSync('README.md', 'utf-8')
|
||||
const updated = readme.replace(/<!--STARTCLI-->[\s\S]*?<!--ENDCLI-->/, `<!--STARTCLI-->\n${codeblock}\n<!--ENDCLI-->`)
|
||||
fs.writeFileSync('README.md', updated)
|
@ -1,74 +0,0 @@
|
||||
# listr-work-queue
|
||||
|
||||
> Drop-in [`Listr`](https://github.com/SamVerschueren/listr) subclass for tasks that need to be picked from a queue concurrently
|
||||
|
||||
------
|
||||
|
||||
:information_source: *About this package*
|
||||
|
||||
*The main difference with the standard `concurrent: <count>` option is that tasks are only rendered as they get picked up, and disappear once processed.*
|
||||
|
||||
*This component will not be necessary anymore if the following `Listr` feature request is implemented: [#53 Display only the task that is running](https://github.com/SamVerschueren/listr/issues/53).
|
||||
When it is, we can simply rely on the `concurrent: <count>` option and let Listr look after the scheduling / execution of the tasks.*
|
||||
|
||||
------
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const Listr = require('listr')
|
||||
const ListrWorkQueue = require('./listr-work-queue/index.js')
|
||||
|
||||
const tasks = new Listr([{
|
||||
title: 'Running jobs',
|
||||
task: () => new ListrWorkQueue(/* tasks */, {
|
||||
concurrent: WORKER_COUNT,
|
||||
exitOnError: false
|
||||
})
|
||||
}])
|
||||
|
||||
tasks.run().then(() => console.log('Done'))
|
||||
```
|
||||
|
||||
## Jobs
|
||||
|
||||
Every job is an object with 2 properties, similar to the standard `Listr` tasks.
|
||||
The `task` property **must** be a Promise. Observables and streams are not supported.
|
||||
|
||||
```js
|
||||
{
|
||||
title: 'Job A',
|
||||
task: () => new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `concurrent`: the number of workers getting tasks from the queue.
|
||||
|
||||
```js
|
||||
{
|
||||
concurrent: os.cpus().length
|
||||
}
|
||||
```
|
||||
|
||||
- `exitOnError`: whether to stop processing new jobs when one of the jobs fails (default is `true`). Note that pending tasks will still complete before the process is stopped.
|
||||
|
||||
```js
|
||||
{
|
||||
exitOnError: false
|
||||
}
|
||||
```
|
||||
|
||||
- `update()`: a callback to report on the overall progress. This can be used for example to update the parent task title.
|
||||
|
||||
```js
|
||||
{
|
||||
title: 'Running jobs',
|
||||
task: (ctx, parent) => new ListrWorkQueue(/* tasks */, {
|
||||
update: (completed, total) => {
|
||||
parent.title = `Running jobs (${completed}/${total})`
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
@ -1,19 +0,0 @@
|
||||
const path = require('node:path')
|
||||
const process = require('node: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, '/')
|
||||
}
|
||||
// encode URLs, but decode overly-encoded slashes
|
||||
filepath = encodeURIComponent(filepath).replace(/%2F/g, '/')
|
||||
// prepend the prefix if needed
|
||||
return prefix + filepath
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
exports.create = function (album, opts, themeSettings) {
|
||||
const baseModel = {
|
||||
gallery: Object.assign({}, opts, { home: album }),
|
||||
settings: themeSettings,
|
||||
home: album
|
||||
}
|
||||
return createPages(baseModel, album, opts.albumPageSize, [])
|
||||
}
|
||||
|
||||
function createPages (baseModel, album, pageSize, breadcrumbs) {
|
||||
// HTML pages for the current album
|
||||
const slicedAlbums = createSlicedAlbums(album, pageSize)
|
||||
const pages = slicedAlbums.map((album, index) => {
|
||||
const pagination = createPagination(slicedAlbums, index)
|
||||
const model = Object.assign({}, baseModel, {
|
||||
path: pagination[index].path,
|
||||
breadcrumbs,
|
||||
album,
|
||||
pagination: (pageSize ? pagination : [])
|
||||
})
|
||||
return model
|
||||
})
|
||||
// and all nested albums
|
||||
album.albums.forEach(function (nested) {
|
||||
const crumbs = breadcrumbs.concat([album])
|
||||
const nestedPages = createPages(baseModel, nested, pageSize, crumbs)
|
||||
Array.prototype.push.apply(pages, nestedPages)
|
||||
})
|
||||
return pages
|
||||
}
|
||||
|
||||
function createSlicedAlbums (album, pageSize) {
|
||||
if (!pageSize) return [album]
|
||||
if (album.files.length < pageSize) return [album]
|
||||
const pagedFiles = _.chunk(album.files, pageSize)
|
||||
return pagedFiles.map(page => {
|
||||
return Object.assign({}, album, { files: page })
|
||||
})
|
||||
}
|
||||
|
||||
function createPagination (albums, currentIndex) {
|
||||
return albums.map((album, index) => {
|
||||
return {
|
||||
index: index + 1,
|
||||
current: (index === currentIndex),
|
||||
path: injectPageNumber(album.path, index),
|
||||
url: injectPageNumber(album.url, index)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function injectPageNumber (filepath, index) {
|
||||
if (index === 0) return filepath
|
||||
const base = filepath.slice(0, -5)
|
||||
return `${base}${index + 1}.html`
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
class SEO {
|
||||
constructor (output, seoLocation, rootAlbum) {
|
||||
this.output = output
|
||||
this.seoPrefix = seoLocation + (seoLocation.endsWith('/') ? '' : '/')
|
||||
this.album = rootAlbum
|
||||
}
|
||||
|
||||
robots () {
|
||||
return `User-Agent: *\nDisallow:\nSitemap: ${this.seoPrefix}sitemap.xml\n`
|
||||
}
|
||||
|
||||
sitemap () {
|
||||
const now = new Date().toISOString()
|
||||
const prefix = this.seoPrefix
|
||||
// gather all album pages
|
||||
const urls = []
|
||||
addAlbumUrls(urls, this.album)
|
||||
// create one <url> section per album
|
||||
const xml = urls.map(url => `
|
||||
<url>
|
||||
<loc>${prefix}${url}</loc>
|
||||
<lastmod>${now}</lastmod>
|
||||
</url>`)
|
||||
// return the full document
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${xml.join('')}
|
||||
</urlset>
|
||||
`
|
||||
}
|
||||
|
||||
writeFiles () {
|
||||
const robotsFile = path.join(this.output, 'robots.txt')
|
||||
const sitemapFile = path.join(this.output, 'sitemap.xml')
|
||||
fs.writeFileSync(robotsFile, this.robots())
|
||||
fs.writeFileSync(sitemapFile, this.sitemap())
|
||||
}
|
||||
}
|
||||
|
||||
function addAlbumUrls (list, album) {
|
||||
list.push(album.url)
|
||||
album.albums.forEach(subAlbum => addAlbumUrls(list, subAlbum))
|
||||
}
|
||||
|
||||
module.exports = SEO
|
@ -1,14 +1,8 @@
|
||||
const path = require('node:path')
|
||||
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 backToGalleryRoot = path.relative(path.dirname(albumPath), '.')
|
||||
const relative = path.join(backToGalleryRoot, target)
|
||||
const url = relative.replace(/\\/g, '/')
|
||||
const relative = path.relative(path.dirname(albumPath), target)
|
||||
// Escape single/double quotes
|
||||
return url.replace(/'/g, '%27').replace(/"/g, '%22')
|
||||
return relative.replace(/'/g, '%27').replace(/"/g, '%22')
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
{{#if gallery.googleAnalytics}}
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{gallery.googleAnalytics}}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{gallery.googleAnalytics}}');
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '{{gallery.googleAnalytics}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
|
||||
{{/if}}
|
||||
|
@ -1,21 +0,0 @@
|
||||
{{!--
|
||||
This theme renders useful information as a YAML file
|
||||
For reliable integration tests
|
||||
--}}
|
||||
|
||||
title: {{ album.title }}
|
||||
|
||||
albums:
|
||||
{{#each album.albums}}
|
||||
- title: {{title}}
|
||||
url: {{relative url}}
|
||||
{{/each}}
|
||||
|
||||
files:
|
||||
{{#each album.files}}
|
||||
- name: {{filename}}
|
||||
caption: {{meta.caption}}
|
||||
thumbnail: {{relative urls.thumbnail}}
|
||||
preview: {{relative urls.large}}
|
||||
download: {{relative urls.download}}
|
||||
{{/each}}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "@thumbsup/test-theme",
|
||||
"thumbsup": {
|
||||
"themeRoot": "."
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
@color: #333;
|
||||
|
||||
h1 {
|
||||
border: @color;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
const should = require('should/as-function')
|
||||
const messages = require('../../bin/messages.js')
|
||||
|
||||
describe('messages', function () {
|
||||
['SUCCESS', 'GREETING', 'SORRY'].forEach(type => {
|
||||
it(`wraps ${type} messages in a box`, () => {
|
||||
const success = messages[type]({})
|
||||
should(success.indexOf('┌───')).above(-1)
|
||||
should(success.indexOf('───┐')).above(-1)
|
||||
should(success.indexOf('└───')).above(-1)
|
||||
should(success.indexOf('───┘')).above(-1)
|
||||
should(success.split('\n').length).above(4)
|
||||
})
|
||||
})
|
||||
|
||||
it('lists mandatory binary dependencies', () => {
|
||||
const required = messages.BINARIES_REQUIRED(['bin1', 'bin2'])
|
||||
should(required.indexOf('bin1\n')).above(-1)
|
||||
should(required.indexOf('bin2\n')).above(-1)
|
||||
})
|
||||
|
||||
it('can print one or more problem', () => {
|
||||
should(messages.PROBLEMS(1).indexOf('with 1 file.')).above(-1)
|
||||
should(messages.PROBLEMS(2).indexOf('with 2 files.')).above(-1)
|
||||
})
|
||||
})
|
@ -1,39 +0,0 @@
|
||||
const should = require('should/as-function')
|
||||
const messages = require('../../src/cli/messages.js')
|
||||
|
||||
describe('messages', function () {
|
||||
it('shows SUCCESS in a box', () => {
|
||||
const stats = { albums: 1, photos: 1, videos: 1 }
|
||||
const success = messages.SUCCESS(stats)
|
||||
assertInABox(success)
|
||||
})
|
||||
|
||||
it('shows GREETING in a box', () => {
|
||||
const greeting = messages.GREETING()
|
||||
assertInABox(greeting)
|
||||
})
|
||||
|
||||
it('shows SORRY in a box', () => {
|
||||
const sorry = messages.SORRY('thumbsup.log')
|
||||
assertInABox(sorry)
|
||||
})
|
||||
|
||||
it('lists mandatory binary dependencies', () => {
|
||||
const required = messages.BINARIES_REQUIRED(['bin1', 'bin2'])
|
||||
should(required.indexOf('bin1\n')).above(-1)
|
||||
should(required.indexOf('bin2\n')).above(-1)
|
||||
})
|
||||
|
||||
it('can print one or more problem', () => {
|
||||
should(messages.PROBLEMS(1).indexOf('with 1 file.')).above(-1)
|
||||
should(messages.PROBLEMS(2).indexOf('with 2 files.')).above(-1)
|
||||
})
|
||||
})
|
||||
|
||||
function assertInABox (result) {
|
||||
should(result.indexOf('┌───')).above(-1)
|
||||
should(result.indexOf('───┐')).above(-1)
|
||||
should(result.indexOf('└───')).above(-1)
|
||||
should(result.indexOf('───┘')).above(-1)
|
||||
should(result.split('\n').length).above(4)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
const should = require('should/as-function')
|
||||
const IntegrationTest = require('./integration-test')
|
||||
const fixtures = require('../fixtures')
|
||||
|
||||
describe('Integration: picasa', function () {
|
||||
this.slow(5000)
|
||||
this.timeout(5000)
|
||||
|
||||
beforeEach(IntegrationTest.before)
|
||||
afterEach(IntegrationTest.after)
|
||||
|
||||
it('reads a picasa.ini file', function (done) {
|
||||
const integration = new IntegrationTest({
|
||||
'input/folder/IMG_0001.jpg': fixtures.fromDisk('photo.jpg'),
|
||||
'input/folder/picasa.ini': '[IMG_0001.jpg]\ncaption=Beach'
|
||||
})
|
||||
const customOpts = []
|
||||
integration.run(customOpts, () => {
|
||||
integration.assertExist(['index.html', 'folder.html'])
|
||||
const res = integration.parseYaml('folder.html')
|
||||
should(res.files[0].caption).eql('Beach')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
@ -1,108 +0,0 @@
|
||||
const should = require('should/as-function')
|
||||
const IntegrationTest = require('./integration-test')
|
||||
const fixtures = require('../fixtures')
|
||||
|
||||
describe('Integration: scan modes', function () {
|
||||
this.slow(5000)
|
||||
this.timeout(5000)
|
||||
|
||||
const image = fixtures.fromDisk('photo.jpg')
|
||||
|
||||
beforeEach(IntegrationTest.before)
|
||||
afterEach(IntegrationTest.after)
|
||||
|
||||
function newIntegrationTest () {
|
||||
return new IntegrationTest({
|
||||
'input/london/IMG_0001.jpg': image,
|
||||
'input/london/IMG_0002.jpg': image,
|
||||
'input/newyork/day 1/IMG_0003.jpg': image,
|
||||
'input/newyork/day 2/IMG_0004.jpg': image
|
||||
})
|
||||
}
|
||||
|
||||
describe('Full', () => {
|
||||
it('removes files that no longer exist in the source', function (done) {
|
||||
const integration = newIntegrationTest()
|
||||
integration.run(['--scan-mode', 'full'], () => {
|
||||
const london1 = integration.parseYaml('london.html')
|
||||
should(london1.files).have.length(2)
|
||||
// delete a file and run again
|
||||
integration.deleteInputFile('input/london/IMG_0002.jpg')
|
||||
integration.run(['--scan-mode', 'full', '--cleanup', 'true'], () => {
|
||||
const london2 = integration.parseYaml('london.html')
|
||||
// the deleted file was removed
|
||||
should(london2.files).have.length(1)
|
||||
integration.assertNotExist([
|
||||
'media/thumbs/london/IMG_0002.jpg',
|
||||
'media/small/london/IMG_0002.jpg',
|
||||
'media/large/london/IMG_0002.jpg'
|
||||
])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("removes files that don't match the include filter", function (done) {
|
||||
const integration = newIntegrationTest()
|
||||
integration.run(['--scan-mode', 'full'], () => {
|
||||
// first run, there's 2 albums (London + New York)
|
||||
const index1 = integration.parseYaml('index.html')
|
||||
should(index1.albums).have.length(2)
|
||||
// run again, only including New York
|
||||
integration.run(['--scan-mode', 'full', '--include', 'newyork/**', '--cleanup', 'true'], () => {
|
||||
const index2 = integration.parseYaml('index.html')
|
||||
// the London album is no longer there
|
||||
should(index2.albums).have.length(1)
|
||||
should(index2.albums[0].title).eql('newyork')
|
||||
integration.assertNotExist([
|
||||
'media/thumbs/london/IMG_0001.jpg',
|
||||
'media/small/london/IMG_0001.jpg',
|
||||
'media/large/london/IMG_0001.jpg'
|
||||
])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Partial', () => {
|
||||
it('ignores changes outside the include pattern', function (done) {
|
||||
const integration = newIntegrationTest()
|
||||
integration.run(['--scan-mode', 'full'], () => {
|
||||
const london1 = integration.parseYaml('london.html')
|
||||
should(london1.files).have.length(2)
|
||||
integration.deleteInputFile('input/london/IMG_0002.jpg')
|
||||
// run again, with only processing New York
|
||||
integration.run(['--scan-mode', 'partial', '--include', 'newyork/**', '--cleanup', 'true'], () => {
|
||||
// the London album still exists
|
||||
const index2 = integration.parseYaml('index.html')
|
||||
should(index2.albums).have.length(2)
|
||||
// and it still has 2 files
|
||||
const london2 = integration.parseYaml('london.html')
|
||||
should(london2.files).have.length(2)
|
||||
// and the excluded thumbnails are not deleted
|
||||
integration.assertExist(['media/thumbs/london/IMG_0002.jpg'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Incremental', () => {
|
||||
it('does not remove deleted files', function (done) {
|
||||
const integration = newIntegrationTest()
|
||||
integration.run(['--scan-mode', 'full'], () => {
|
||||
const london1 = integration.parseYaml('london.html')
|
||||
should(london1.files).have.length(2)
|
||||
// run again after deleting a file
|
||||
integration.deleteInputFile('input/london/IMG_0002.jpg')
|
||||
integration.run(['--scan-mode', 'incremental'], () => {
|
||||
const london2 = integration.parseYaml('london.html')
|
||||
should(london2.files).have.length(2)
|
||||
integration.assertExist(['media/thumbs/london/IMG_0002.jpg'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,57 +0,0 @@
|
||||
const IntegrationTest = require('./integration-test')
|
||||
const fixtures = require('../fixtures')
|
||||
|
||||
describe('Integration: media files', function () {
|
||||
this.slow(5000)
|
||||
this.timeout(5000)
|
||||
|
||||
beforeEach(IntegrationTest.before)
|
||||
afterEach(IntegrationTest.after)
|
||||
|
||||
const image = fixtures.fromDisk('photo.jpg')
|
||||
const integration = new IntegrationTest({
|
||||
'input/london/IMG_0001.jpg': image,
|
||||
'input/london/IMG_0002.jpg': image,
|
||||
'input/newyork/day 1/IMG_0003.jpg': image,
|
||||
'input/newyork/day 2/IMG_0004.jpg': image
|
||||
})
|
||||
|
||||
it('builds the gallery from scratch', function (done) {
|
||||
const customOpts = []
|
||||
integration.run(customOpts, () => {
|
||||
// Database
|
||||
integration.assertExist([
|
||||
'thumbsup.db'
|
||||
])
|
||||
// Albums
|
||||
integration.assertExist([
|
||||
'index.html',
|
||||
'london.html',
|
||||
'newyork-day-1.html',
|
||||
'newyork-day-2.html'
|
||||
])
|
||||
// Thumbnails
|
||||
integration.assertExist([
|
||||
'media/thumbs/london/IMG_0001.jpg',
|
||||
'media/thumbs/london/IMG_0002.jpg',
|
||||
'media/thumbs/newyork/day 1/IMG_0003.jpg',
|
||||
'media/thumbs/newyork/day 2/IMG_0004.jpg'
|
||||
])
|
||||
// Large versions
|
||||
integration.assertExist([
|
||||
'media/large/london/IMG_0001.jpg',
|
||||
'media/large/london/IMG_0002.jpg',
|
||||
'media/large/newyork/day 1/IMG_0003.jpg',
|
||||
'media/large/newyork/day 2/IMG_0004.jpg'
|
||||
])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('builds the gallery a second time', function (done) {
|
||||
const customOpts = []
|
||||
integration.run(customOpts, () => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
@ -1,84 +0,0 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const debug = require('debug')
|
||||
const glob = require('glob')
|
||||
const YAML = require('yaml')
|
||||
const should = require('should/as-function')
|
||||
const fixtures = require('../fixtures')
|
||||
const options = require('../../src/cli/options')
|
||||
const index = require('../../src/index')
|
||||
|
||||
class IntegrationTest {
|
||||
constructor (structure) {
|
||||
this.tmpdir = fixtures.createTempStructure(structure)
|
||||
this.input = path.join(this.tmpdir, 'input')
|
||||
this.output = path.join(this.tmpdir, 'output')
|
||||
this.actualFiles = []
|
||||
}
|
||||
|
||||
run (customOptions, done) {
|
||||
const defaultOptions = [
|
||||
'--input', this.input,
|
||||
'--output', this.output,
|
||||
'--theme-path', 'test-fixtures/theme',
|
||||
'--log', 'info'
|
||||
]
|
||||
const allOptions = defaultOptions.concat(customOptions)
|
||||
const opts = options.get(allOptions)
|
||||
index.build(opts, err => {
|
||||
// Reset the logger ASAP to print the test status
|
||||
console.log = console.logOld
|
||||
should(err).eql(null)
|
||||
debug.assertNotContains('thumbsup:error')
|
||||
this.actualFiles = glob.sync('**/*', {
|
||||
cwd: this.output,
|
||||
nodir: true,
|
||||
nonull: false
|
||||
})
|
||||
setImmediate(done)
|
||||
})
|
||||
}
|
||||
|
||||
deleteInputFile (filepath) {
|
||||
fixtures.deleteTempFile(this.tmpdir, filepath)
|
||||
}
|
||||
|
||||
assertExist (expected) {
|
||||
const missing = expected.filter(f => this.actualFiles.indexOf(f) === -1)
|
||||
should(missing).eql([])
|
||||
}
|
||||
|
||||
assertNotExist (expected) {
|
||||
const present = expected.filter(f => this.actualFiles.indexOf(f) !== -1)
|
||||
should(present).eql([])
|
||||
}
|
||||
|
||||
parse (filepath) {
|
||||
const fullpath = path.join(this.output, filepath)
|
||||
return fs.readFileSync(fullpath, { encoding: 'utf8' })
|
||||
}
|
||||
|
||||
parseYaml (filepath) {
|
||||
const contents = this.parse(filepath)
|
||||
return YAML.parse(contents)
|
||||
}
|
||||
|
||||
getPath (structurePath) {
|
||||
return path.join(this.tmpdir, structurePath)
|
||||
}
|
||||
}
|
||||
|
||||
IntegrationTest.before = function () {
|
||||
// Listr uses control.log() to print progress
|
||||
// But so does Mocha to print test results
|
||||
// So we override it for the duration of the integration test
|
||||
console.logOld = console.log
|
||||
console.log = debug('thumbsup:info')
|
||||
debug.reset()
|
||||
}
|
||||
|
||||
IntegrationTest.after = function () {
|
||||
console.log = console.logOld
|
||||
}
|
||||
|
||||
module.exports = IntegrationTest
|
@ -1,38 +0,0 @@
|
||||
const should = require('should/as-function')
|
||||
const IntegrationTest = require('./integration-test')
|
||||
const fixtures = require('../fixtures')
|
||||
|
||||
describe('Integration: themes', function () {
|
||||
this.slow(5000)
|
||||
this.timeout(5000)
|
||||
|
||||
beforeEach(IntegrationTest.before)
|
||||
afterEach(IntegrationTest.after)
|
||||
|
||||
const integration = new IntegrationTest({
|
||||
'input/IMG_0001.jpg': fixtures.fromDisk('photo.jpg'),
|
||||
'custom.less': '@color: #444;'
|
||||
})
|
||||
|
||||
it('processes LESS variables', function (done) {
|
||||
const customOpts = []
|
||||
integration.run(customOpts, () => {
|
||||
integration.assertExist(['public/theme.css'])
|
||||
const res = integration.parse('public/theme.css')
|
||||
should(res.includes('border: #333')).eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can customise LESS variables', function (done) {
|
||||
const customOpts = [
|
||||
'--theme-style', integration.getPath('custom.less')
|
||||
]
|
||||
integration.run(customOpts, () => {
|
||||
integration.assertExist(['public/theme.css'])
|
||||
const res = integration.parse('public/theme.css')
|
||||
should(res.includes('border: #444')).eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
@ -1,38 +0,0 @@
|
||||
const should = require('should/as-function')
|
||||
const IntegrationTest = require('./integration-test')
|
||||
const fixtures = require('../fixtures')
|
||||
|
||||
describe('Integration: urls', function () {
|
||||
this.slow(5000)
|
||||
this.timeout(5000)
|
||||
|
||||
beforeEach(IntegrationTest.before)
|
||||
afterEach(IntegrationTest.after)
|
||||
|
||||
const integration = new IntegrationTest({
|
||||
'input/IMG_0001.jpg': fixtures.fromDisk('photo.jpg')
|
||||
})
|
||||
|
||||
it('uses relative URLs by default', function (done) {
|
||||
const customOpts = []
|
||||
integration.run(customOpts, () => {
|
||||
integration.assertExist(['index.html'])
|
||||
const res = integration.parseYaml('index.html')
|
||||
should(res.files[0].thumbnail).eql('media/thumbs/IMG_0001.jpg')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can use an external link prefix', function (done) {
|
||||
const customOpts = [
|
||||
'--photo-preview', 'link',
|
||||
'--link-prefix', 'http://example.com'
|
||||
]
|
||||
integration.run(customOpts, () => {
|
||||
integration.assertExist(['index.html'])
|
||||
const res = integration.parseYaml('index.html')
|
||||
should(res.files[0].preview).eql('http://example.com/IMG_0001.jpg')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
@ -0,0 +1,108 @@
|
||||
const debug = require('debug')
|
||||
const glob = require('glob')
|
||||
const path = require('path')
|
||||
const should = require('should/as-function')
|
||||
const fixtures = require('../fixtures')
|
||||
const options = require('../../bin/options')
|
||||
const index = require('../../src/index')
|
||||
|
||||
describe('Full integration', function () {
|
||||
this.slow(5000)
|
||||
this.timeout(5000)
|
||||
|
||||
var tmpdir = null
|
||||
var opts = null
|
||||
|
||||
before(() => {
|
||||
const image = fixtures.fromDisk('photo.jpg')
|
||||
tmpdir = fixtures.createTempStructure({
|
||||
'input/london/IMG_0001.jpg': image,
|
||||
'input/london/IMG_0002.jpg': image,
|
||||
'input/newyork/day 1/IMG_0003.jpg': image,
|
||||
'input/newyork/day 2/IMG_0004.jpg': image
|
||||
})
|
||||
opts = options.get([
|
||||
'--input', path.join(tmpdir, 'input'),
|
||||
'--output', path.join(tmpdir, 'output'),
|
||||
'--title', 'Photo album',
|
||||
'--homeAlbumName', 'Home',
|
||||
'--theme', 'classic',
|
||||
'--log', 'info'
|
||||
])
|
||||
})
|
||||
|
||||
// Listr uses control.log() to print progress
|
||||
// But so does Mocha to print test results
|
||||
// So we override it for the duration of the integration test
|
||||
beforeEach(() => {
|
||||
console.logOld = console.log
|
||||
console.log = debug('thumbsup:info')
|
||||
debug.reset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
console.log = console.logOld
|
||||
})
|
||||
|
||||
it('builds the gallery from scratch', function (testDone) {
|
||||
index.build(opts, err => {
|
||||
// Reset the logger ASAP to print the test status
|
||||
console.log = console.logOld
|
||||
// Check for any errors
|
||||
console.log(err)
|
||||
should(err).eql(null)
|
||||
debug.assertNotContains('thumbsup:error')
|
||||
// Check the contents of the output folder
|
||||
const actualFiles = actualStructure(opts.output)
|
||||
// Database
|
||||
assertExist(actualFiles, [
|
||||
'thumbsup.db'
|
||||
])
|
||||
// Albums
|
||||
assertExist(actualFiles, [
|
||||
'index.html',
|
||||
'london.html',
|
||||
'newyork-day-1.html',
|
||||
'newyork-day-2.html'
|
||||
])
|
||||
// Thumbnails
|
||||
assertExist(actualFiles, [
|
||||
'media/thumbs/london/IMG_0001.jpg',
|
||||
'media/thumbs/london/IMG_0002.jpg',
|
||||
'media/thumbs/newyork/day 1/IMG_0003.jpg',
|
||||
'media/thumbs/newyork/day 2/IMG_0004.jpg'
|
||||
])
|
||||
// Large versions
|
||||
assertExist(actualFiles, [
|
||||
'media/large/london/IMG_0001.jpg',
|
||||
'media/large/london/IMG_0002.jpg',
|
||||
'media/large/newyork/day 1/IMG_0003.jpg',
|
||||
'media/large/newyork/day 2/IMG_0004.jpg'
|
||||
])
|
||||
testDone()
|
||||
})
|
||||
})
|
||||
|
||||
it('builds the gallery a second time (nothing to do)', function (testDone) {
|
||||
index.build(opts, err => {
|
||||
// Reset the logger ASAP to print the test status
|
||||
console.log = console.logOld
|
||||
should(err).eql(null)
|
||||
testDone()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function actualStructure (dir) {
|
||||
return glob.sync('**/*', {
|
||||
cwd: dir,
|
||||
ignore: 'public',
|
||||
nodir: true,
|
||||
nonull: false
|
||||
})
|
||||
}
|
||||
|
||||
function assertExist (actual, expected) {
|
||||
const missing = expected.filter(f => actual.indexOf(f) === -1)
|
||||
should([]).eql(missing)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
--recursive
|
||||
--require test/helpers.js
|
||||
--require test/log.js
|
@ -1,113 +0,0 @@
|
||||
const _ = require('lodash')
|
||||
const should = require('should/as-function')
|
||||
const Album = require('../../src/model/album')
|
||||
const fixtures = require('../fixtures')
|
||||
|
||||
function arrayOfFiles (count) {
|
||||
const base = new Array(count)
|
||||
return Array.from(base, (_, index) => fixtures.photo(`${index}`))
|
||||
}
|
||||
|
||||
function outputName (file) {
|
||||
const thumb = file.urls.thumbnail
|
||||
return thumb.substring(thumb.lastIndexOf('/') + 1, thumb.lastIndexOf('.'))
|
||||
}
|
||||
|
||||
describe('Album', function () {
|
||||
this.slow(200)
|
||||
|
||||
describe('previews', function () {
|
||||
it('picks the first 10 photos by default', function () {
|
||||
const album = new Album({ files: arrayOfFiles(100) })
|
||||
album.finalize()
|
||||
should(album.previews).have.length(10)
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs).eql(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
|
||||
})
|
||||
|
||||
it('adds <missing> thumbnails to fill', function () {
|
||||
const album = new Album({ files: arrayOfFiles(5) })
|
||||
album.finalize()
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs.slice(0, 5)).eql(['0', '1', '2', '3', '4'])
|
||||
for (let i = 5; i < 10; ++i) {
|
||||
should(album.previews[i].urls.thumbnail).eql('public/missing.png')
|
||||
}
|
||||
})
|
||||
|
||||
it('uses files from nested albums too', function () {
|
||||
const album = new Album({
|
||||
title: 'a',
|
||||
files: [fixtures.photo('a1'), fixtures.photo('a2')],
|
||||
albums: [
|
||||
new Album({
|
||||
title: 'b',
|
||||
files: [fixtures.photo('b1'), fixtures.photo('b2')]
|
||||
}),
|
||||
new Album({
|
||||
title: 'c',
|
||||
files: [fixtures.photo('c1'), fixtures.photo('c2')]
|
||||
})
|
||||
]
|
||||
})
|
||||
album.finalize()
|
||||
should(album.previews).have.length(10)
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs.slice(0, 6)).eql(['a1', 'a2', 'b1', 'b2', 'c1', 'c2'])
|
||||
for (let i = 6; i < 10; ++i) {
|
||||
should(album.previews[i].urls.thumbnail).eql('public/missing.png')
|
||||
}
|
||||
})
|
||||
|
||||
describe('preview modes', () => {
|
||||
it('can pick the first 10 photos', function () {
|
||||
const album = new Album({ files: arrayOfFiles(100) })
|
||||
album.finalize({ albumPreviews: 'first' })
|
||||
should(album.previews).have.length(10)
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs).eql(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
|
||||
})
|
||||
|
||||
it('can randomize the previews', function () {
|
||||
const album = new Album({ files: arrayOfFiles(100) })
|
||||
album.finalize({ albumPreviews: 'random' })
|
||||
should(album.previews).have.length(10)
|
||||
should(_.uniq(album.previews)).have.length(10)
|
||||
})
|
||||
|
||||
it('can spread the previews', function () {
|
||||
const album = new Album({ files: arrayOfFiles(50) })
|
||||
album.finalize({ albumPreviews: 'spread' })
|
||||
should(album.previews).have.length(10)
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs).eql(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45'])
|
||||
})
|
||||
|
||||
it('ignores the extra photos when spreading on un-even counts', function () {
|
||||
const album = new Album({ files: arrayOfFiles(58) })
|
||||
album.finalize({ albumPreviews: 'spread' })
|
||||
should(album.previews).have.length(10)
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs).eql(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45'])
|
||||
})
|
||||
|
||||
it('picks the first 10 when trying to spread under 10 photos', function () {
|
||||
const album = new Album({ files: arrayOfFiles(5) })
|
||||
album.finalize({ albumPreviews: 'spread' })
|
||||
should(album.previews).have.length(10)
|
||||
const thumbs = album.previews.map(outputName)
|
||||
should(thumbs.slice(0, 5)).eql(['0', '1', '2', '3', '4'])
|
||||
for (let i = 5; i < 10; ++i) {
|
||||
should(album.previews[i].urls.thumbnail).eql('public/missing.png')
|
||||
}
|
||||
})
|
||||
|
||||
it('throws an error if the preview type is not supported', function () {
|
||||
const album = new Album({ files: arrayOfFiles(5) })
|
||||
should.throws(function () {
|
||||
album.finalize({ albumPreviews: 'test' })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue