piccle 0.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/NOTES.md +69 -0
- data/README.md +175 -0
- data/Rakefile +8 -0
- data/agpl-3.0.md +660 -0
- data/assets/css/default.css +397 -0
- data/assets/css/normalize.css +427 -0
- data/assets/icons/android-chrome-192x192.png +0 -0
- data/assets/icons/android-chrome-512x512.png +0 -0
- data/assets/icons/apple-touch-icon.png +0 -0
- data/assets/icons/favicon-16x16.png +0 -0
- data/assets/icons/favicon-32x32.png +0 -0
- data/assets/icons/favicon.ico +0 -0
- data/bin/console +14 -0
- data/bin/piccle +355 -0
- data/bin/setup +8 -0
- data/db/migrations/001_create_photos.rb +15 -0
- data/db/migrations/002_update_photos.rb +14 -0
- data/db/migrations/003_create_keywords_and_join_table.rb +14 -0
- data/db/migrations/004_add_focal_length.rb +7 -0
- data/db/migrations/005_create_locations.rb +20 -0
- data/js-renderer/handlebars.min-v4.7.6.js +29 -0
- data/js-renderer/renderer.js +93 -0
- data/lib/piccle.rb +52 -0
- data/lib/piccle/config.rb +136 -0
- data/lib/piccle/database.rb +33 -0
- data/lib/piccle/dstk_service.rb +64 -0
- data/lib/piccle/extractor.rb +128 -0
- data/lib/piccle/js_renderer.rb +37 -0
- data/lib/piccle/models/keyword.rb +6 -0
- data/lib/piccle/models/location.rb +11 -0
- data/lib/piccle/models/photo.rb +211 -0
- data/lib/piccle/parser.rb +230 -0
- data/lib/piccle/quilt_generator.rb +30 -0
- data/lib/piccle/renderer.rb +175 -0
- data/lib/piccle/streams.rb +2 -0
- data/lib/piccle/streams/base_stream.rb +56 -0
- data/lib/piccle/streams/camera_stream.rb +35 -0
- data/lib/piccle/streams/date_stream.rb +95 -0
- data/lib/piccle/streams/event_stream.rb +73 -0
- data/lib/piccle/streams/keyword_stream.rb +24 -0
- data/lib/piccle/streams/location_stream.rb +57 -0
- data/lib/piccle/template_helpers.rb +79 -0
- data/lib/piccle/version.rb +3 -0
- data/lib/tasks/development.rake +38 -0
- data/piccle.gemspec +43 -0
- data/templates/_breadcrumbs.handlebars.slim +16 -0
- data/templates/_footer.handlebars.slim +2 -0
- data/templates/_header.handlebars.slim +36 -0
- data/templates/_navigation.handlebars.slim +16 -0
- data/templates/_substream.handlebars.slim +17 -0
- data/templates/feed.atom.slim +29 -0
- data/templates/index.html.handlebars.slim +36 -0
- data/templates/show.html.handlebars.slim +64 -0
- metadata +340 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Browse photos by keyword.
|
4
|
+
class Piccle::Streams::KeywordStream < Piccle::Streams::BaseStream
|
5
|
+
def namespace
|
6
|
+
"by-topic"
|
7
|
+
end
|
8
|
+
|
9
|
+
# Standard method called by the parser object. Returns a hash that contains the data to merge for the given photo.
|
10
|
+
def data_for(photo)
|
11
|
+
result = { namespace => {
|
12
|
+
:friendly_name => "By Topic",
|
13
|
+
:interesting => true
|
14
|
+
}}
|
15
|
+
photo.keywords.each do |kw|
|
16
|
+
result[namespace][slugify(kw.name)] = { friendly_name: kw.name, interesting: true, photos: [photo.md5] }
|
17
|
+
end
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
def metadata_for(photo)
|
22
|
+
photo.keywords.map { |kw| { friendly_name: kw.name, type: :keyword, selector: [namespace, slugify(kw.name)] } }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Enables browsing photos by location.
|
4
|
+
class Piccle::Streams::LocationStream < Piccle::Streams::BaseStream
|
5
|
+
def namespace
|
6
|
+
"by-location"
|
7
|
+
end
|
8
|
+
|
9
|
+
def data_for(photo)
|
10
|
+
data = {}
|
11
|
+
if photo.country
|
12
|
+
data = { namespace => {
|
13
|
+
:friendly_name => "By Location",
|
14
|
+
:interesting => false,
|
15
|
+
photo.country.downcase => {
|
16
|
+
:friendly_name => photo.country,
|
17
|
+
:interesting => true,
|
18
|
+
:photos => [photo.md5]
|
19
|
+
},
|
20
|
+
}}
|
21
|
+
if photo.state
|
22
|
+
data[namespace][photo.country.downcase][photo.state.downcase] = {
|
23
|
+
:friendly_name => photo.state,
|
24
|
+
:interesting => false,
|
25
|
+
:photos => [photo.md5]
|
26
|
+
}
|
27
|
+
|
28
|
+
if photo.city
|
29
|
+
data[namespace][photo.country.downcase][photo.state.downcase][photo.city.downcase] = {
|
30
|
+
:friendly_name => photo.city,
|
31
|
+
:interesting => false,
|
32
|
+
:photos => [photo.md5]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
data
|
39
|
+
end
|
40
|
+
|
41
|
+
def metadata_for(photo)
|
42
|
+
metadata = []
|
43
|
+
|
44
|
+
if photo.country
|
45
|
+
metadata << { friendly_name: photo.country, type: :location_country, selector: [namespace, photo.country.downcase] }
|
46
|
+
|
47
|
+
if photo.state
|
48
|
+
metadata << { friendly_name: photo.state, type: :location_state, selector: [namespace, photo.country.downcase, photo.state.downcase] }
|
49
|
+
|
50
|
+
if photo.city
|
51
|
+
metadata << { friendly_name: photo.city, type: :location_city, selector: [namespace, photo.country.downcase, photo.state.downcase, photo.city.downcase] }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
metadata
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'handlebars'
|
3
|
+
|
4
|
+
# Rendering functions for templates.
|
5
|
+
# * For most files, we use Slim to render a Handlebars template (ie. HTML with embedded Handlebars). Handlebars then
|
6
|
+
# generates the final output. This lets us use the same templates for backend and frontend rendering.
|
7
|
+
# * Partials don't have variable interpolation, because they're rendered inline into the main template. Their variables
|
8
|
+
# will be interpolated when the overall template is rendered.
|
9
|
+
# * RSS feeds are generated via Slim only, as we don't need to generate those server-side.
|
10
|
+
#
|
11
|
+
# We don't cache Handlebars templates, because it actually results in WORSE performance than compiling a fresh instance
|
12
|
+
# each time.
|
13
|
+
|
14
|
+
class Piccle::TemplateHelpers
|
15
|
+
@@handlebars = nil
|
16
|
+
@@slim_pages = {}
|
17
|
+
@@slim_partials = {}
|
18
|
+
|
19
|
+
# Renders a partial template. Partial templates do NOT have their variables interpolated via Handlebars.
|
20
|
+
def self.render_partial(template_name, args = {})
|
21
|
+
@@slim_partials[template_name] ||= Tilt['slim'].new { File.read(Piccle.config.gem_root_join("templates/_#{template_name}.handlebars.slim")) }
|
22
|
+
@@slim_partials[template_name].render(Object.new, args)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Renders an entire template out
|
26
|
+
def self.render(template_name, data = {})
|
27
|
+
options = { code_attr_delims: { '(' => ')', '[' => ']'}, attr_list_delims: { '(' => ')', '[' => ']' } }
|
28
|
+
@@slim_pages[template_name] ||= Tilt['slim'].new(options) { File.read(Piccle.config.gem_root_join("templates/#{template_name}.html.handlebars.slim")) }.render
|
29
|
+
template = handlebars.compile(@@slim_pages[template_name])
|
30
|
+
template.call(data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.render_rss(template_name, data = {})
|
34
|
+
@@slim_pages["rss_#{template_name}"] ||= Tilt['slim'].new { File.read(Piccle.config.gem_root_join("templates/#{template_name}.atom.slim")) }
|
35
|
+
@@slim_pages["rss_#{template_name}"].render(Object.new, data)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Gets a Handlebars version of the template. No variable replacement!
|
39
|
+
def self.compile_template(name)
|
40
|
+
slim_template = Tilt['slim'].new { File.read(Piccle.config.gem_root_join("templates/#{name}.html.handlebars.slim")) }
|
41
|
+
slim_template.render(Object.new, {})
|
42
|
+
end
|
43
|
+
|
44
|
+
# Given a "selector" (an array of string path components), returns an "include prefix" (a relative path that
|
45
|
+
# gets us back to the top level).
|
46
|
+
# eg. ["by-date", "2017", "03"] → "../../../"
|
47
|
+
def self.include_prefix(selector)
|
48
|
+
if selector.any?
|
49
|
+
"#{(['..'] * selector.length).join('/')}/"
|
50
|
+
else
|
51
|
+
""
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a block of content, escape its HTML.
|
56
|
+
def self.escape_html(&block)
|
57
|
+
CGI::escape_html(yield)
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def self.handlebars
|
63
|
+
unless @@handlebars
|
64
|
+
@@handlebars = Handlebars::Context.new
|
65
|
+
@@handlebars.register_helper(:ifEqual) do |context, arg1, arg2, block|
|
66
|
+
if arg1 == arg2
|
67
|
+
block.fn(context)
|
68
|
+
else
|
69
|
+
block.inverse(context)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
@@handlebars.register_helper(:join) do |context, arg1, arg2, block|
|
74
|
+
arg1.join(arg2)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
@@handlebars
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
|
3
|
+
namespace :db do
|
4
|
+
desc "Create a database for photo data"
|
5
|
+
task :initialise do
|
6
|
+
puts "This task is deprecated; run sequel -m db/migrations sqlite://photo_data.db instead."
|
7
|
+
# # If a DB already exists, do nothing.
|
8
|
+
# if File.exist?(Piccle::Database::PHOTO_DATABASE_FILENAME)
|
9
|
+
# puts "Database #{Piccle::Database::PHOTO_DATABASE_FILENAME} already exists; exiting."
|
10
|
+
# else
|
11
|
+
# puts "Creating an empty DB..."
|
12
|
+
# db = SQLite3::Database.new(Piccle::Database::PHOTO_DATABASE_FILENAME)
|
13
|
+
# puts " ... and writing a schema to it..."
|
14
|
+
# db.execute <<-SQL
|
15
|
+
# CREATE TABLE photos(
|
16
|
+
# id INTEGER PRIMARY KEY,
|
17
|
+
# file_name text NOT NULL,
|
18
|
+
# path text NOT NULL,
|
19
|
+
# md5 varchar(128) NOT NULL,
|
20
|
+
# width integer NOT NULL,
|
21
|
+
# height integer NOT NULL,
|
22
|
+
# camera_name text,
|
23
|
+
# taken_at datetime,
|
24
|
+
# created_at datetime
|
25
|
+
# );
|
26
|
+
# SQL
|
27
|
+
# puts " ... Done."
|
28
|
+
# end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Drop the database"
|
32
|
+
task :drop do
|
33
|
+
File.delete("photo_data.db")
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Recreate the database"
|
37
|
+
task recreate: [:drop, :initialise]
|
38
|
+
end
|
data/piccle.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'piccle/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "piccle"
|
8
|
+
spec.version = Piccle::VERSION
|
9
|
+
spec.authors = ["Alex Pounds"]
|
10
|
+
spec.email = ["piccle@alexpounds.com"]
|
11
|
+
spec.executables = ["piccle"]
|
12
|
+
|
13
|
+
spec.summary = "A static site generator for photographers"
|
14
|
+
spec.description = "Piccle uses the EXIF data present in your photographs and uses it to build a website that lets
|
15
|
+
visitors browse by camera, tag, location, and more."
|
16
|
+
spec.homepage = "https://piccle.alexpounds.com/"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = "bin"
|
22
|
+
spec.executables = ["piccle"]
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0" # Testing library
|
27
|
+
spec.add_development_dependency "simplecov" # Code coverage calculator
|
28
|
+
spec.add_development_dependency "simplecov-console" # Output stats on the console
|
29
|
+
spec.add_development_dependency "pry-byebug", "~> 3.5" # Debugging aid; only needed by developers.
|
30
|
+
spec.add_development_dependency "ruby-prof" # Performance profiling.
|
31
|
+
|
32
|
+
spec.add_dependency "exifr", "~> 1.3" # EXIF reading library
|
33
|
+
spec.add_dependency "handlebars" # Templating engine, usable both backend and frontend.
|
34
|
+
spec.add_dependency "httparty" # Simple HTTP library
|
35
|
+
spec.add_dependency "rake" # Ruby task runner
|
36
|
+
spec.add_dependency "recursive-open-struct", "~> 1.0" # Blesses database results into objects compatible with flavour-saver
|
37
|
+
spec.add_dependency "rmagick", "~> 2.0" # Image processing library
|
38
|
+
spec.add_dependency "sequel", "~> 5" # DB access in a structured way
|
39
|
+
spec.add_dependency "slim", "~> 3.0" # Templating language, so we don't have to write longhand HTML
|
40
|
+
spec.add_dependency "sqlite3", "~> 1.3" # Simple file-based database
|
41
|
+
spec.add_dependency "thor" # Nice Ruby CLI builder
|
42
|
+
spec.add_dependency "xmp", "~> 0.2" # Read XMP info from files
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
|
2
|
+
{{#if breadcrumbs}}
|
3
|
+
<ol>
|
4
|
+
{{#each breadcrumbs as |crumb|}}
|
5
|
+
{{#if @last}}
|
6
|
+
<li>{{crumb.friendly_name}}</li>
|
7
|
+
{{else}}
|
8
|
+
{{#if crumb.link}}
|
9
|
+
<li><a href="{{../include_prefix}}{{crumb.link}}">{{crumb.friendly_name}}</a></li>
|
10
|
+
{{else}}
|
11
|
+
<li>{{crumb.friendly_name}}</li>
|
12
|
+
{{/if}}
|
13
|
+
{{/if}}
|
14
|
+
{{/each}}
|
15
|
+
</ol>
|
16
|
+
{{/if}}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
head
|
2
|
+
meta charset="utf-8"
|
3
|
+
meta name="viewport" content="width=device-width, initial-scale=1"
|
4
|
+
title Photography by {{site_metadata.author_name}}
|
5
|
+
link rel="stylesheet" href="{{include_prefix}}css/normalize.css"
|
6
|
+
link rel="stylesheet" href="{{include_prefix}}css/default.css"
|
7
|
+
link rel="icon" href="{{include_prefix}}icons/favicon.ico"
|
8
|
+
link rel="icon" sizes="32x32" type="image/png" href="{{include_prefix}}icons/favicon-32x32.png"
|
9
|
+
link rel="icon" sizes="16x16" type="image/png" href="{{include_prefix}}icons/favicon-16x16.png"
|
10
|
+
link rel="icon" sizes="192x192" type="image/png" href="{{include_prefix}}icons/android-chrome-192x192.png"
|
11
|
+
link rel="icon" sizes="512x512" type="image/png" href="{{include_prefix}}icons/android-chrome-512x512.png"
|
12
|
+
link rel="apple-touch-icon" sizes="180x180" type="image/png" href="{{include_prefix}}icons/apple-touch-icon.png"
|
13
|
+
|
|
14
|
+
{{#if canonical}}
|
15
|
+
<link rel="canonical" href="{{include_prefix}}{{canonical}}" />
|
16
|
+
{{/if}}
|
17
|
+
{{#if prev_link}}
|
18
|
+
<link rel="prev" href="{{include_prefix}}{{prev_link}}" />
|
19
|
+
{{/if}}
|
20
|
+
{{#if next_link}}
|
21
|
+
<link rel="next" href="{{include_prefix}}{{next_link}}" />
|
22
|
+
{{/if}}
|
23
|
+
{{#if site_url}}
|
24
|
+
<link rel="alternate" type="application/atom+xml" href="{{include_prefix}}/feed.atom" title="All photos">
|
25
|
+
{{/if}}
|
26
|
+
{{#if open_graph}}
|
27
|
+
<meta property="og:type" content="website" />
|
28
|
+
<meta property="og:title" content="{{open_graph.title}}" />
|
29
|
+
<meta property="og:description" content="{{open_graph.description}}" />
|
30
|
+
<meta property="og:image" content="{{open_graph.image.url}}" />
|
31
|
+
<meta property="og:image:type" content="image/jpeg" />
|
32
|
+
{{#if open_graph.image.alt}}<meta property="og:image:alt" content="{{open_graph.image.alt}}" />{{/if}}
|
33
|
+
<meta property="og:image:width" content="{{open_graph.image.width}}" />
|
34
|
+
<meta property="og:image:height" content="{{open_graph.image.height}}" />
|
35
|
+
<meta property="og:url" content="{{open_graph.url}}" />
|
36
|
+
{{/if}}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
nav
|
2
|
+
|
|
3
|
+
<input type="checkbox" id="menu_toggle" name="menu_toggle" />
|
4
|
+
<label for="menu_toggle">Menu</label>
|
5
|
+
{{#each nav_items as |nav_item|}}
|
6
|
+
{{#if nav_item.entries}}
|
7
|
+
<section>
|
8
|
+
<h2>{{nav_item.friendly_name}}</h2>
|
9
|
+
<ul>
|
10
|
+
{{#each nav_item.entries as |entry|}}
|
11
|
+
<li><a href="{{../../include_prefix}}{{entry.link}}" title="{{entry.photo_count}} {{#ifEqual entry.photo_count 1}}photo{{else}}photos{{/ifEqual}}">{{entry.friendly_name}}</a></li>
|
12
|
+
{{/each}}
|
13
|
+
</ul>
|
14
|
+
</section>
|
15
|
+
{{/if}}
|
16
|
+
{{/each}}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
|
2
|
+
{{#if stream.previous}}
|
3
|
+
<a class="navigation_arrow left" href="{{../include_prefix}}{{stream.selector_path}}{{stream.previous.hash}}.html#photo">«</a>
|
4
|
+
{{/if}}
|
5
|
+
<div class="stamps">
|
6
|
+
{{#each stream.photos as |substream_photo|}}
|
7
|
+
<a href="{{../../include_prefix}}{{stream.selector_path}}{{substream_photo.hash}}.html#photo">
|
8
|
+
<img
|
9
|
+
class="stamp{{#ifEqual substream_photo.hash ../../photo.hash}} current{{/ifEqual}}"
|
10
|
+
src="{{../../include_prefix}}images/thumbnails/{{substream_photo.hash}}.{{substream_photo.file_name}}"
|
11
|
+
alt="{{substream_photo.title}}" />
|
12
|
+
</a>
|
13
|
+
{{/each}}
|
14
|
+
</div>
|
15
|
+
{{#if stream.next}}
|
16
|
+
<a class="navigation_arrow right" href="{{../include_prefix}}{{stream.selector_path}}{{stream.next.hash}}.html#photo">»</a>
|
17
|
+
{{/if}}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
doctype xml
|
2
|
+
feed xmlns="http://www.w3.org/2005/Atom"
|
3
|
+
title Photography by #{site_metadata[:author_name]}
|
4
|
+
author
|
5
|
+
name #{site_metadata[:author_name]}
|
6
|
+
link rel="self" href=URI::join(Piccle.config.home_url, joined_selector, "feed.atom")
|
7
|
+
link rel="alternate" href=URI::join(Piccle.config.home_url, joined_selector, "index.html")
|
8
|
+
icon = URI::join(Piccle.config.home_url, "icons", "android-chrome-192x192.png")
|
9
|
+
updated = feed_update_time.strftime("%FT%T%:z")
|
10
|
+
id = URI::join(Piccle.config.home_url, joined_selector, "feed.atom")
|
11
|
+
|
12
|
+
- photos.map do |hash, photo|
|
13
|
+
entry
|
14
|
+
title = photo[:title] || "A photo by #{site_metadata[:author_name]}"
|
15
|
+
link rel="alternate" href=URI::join(Piccle.config.home_url, joined_selector, "#{photo[:hash]}.html")
|
16
|
+
id = URI::join(Piccle.config.home_url, joined_selector, "#{photo[:hash]}.html")
|
17
|
+
updated = photo[:created_at].strftime("%FT%T%:z")
|
18
|
+
content type="html"
|
19
|
+
== Piccle::TemplateHelpers.escape_html
|
20
|
+
- if photo[:title]
|
21
|
+
h1 = photo[:title]
|
22
|
+
img src=URI::join(Piccle.config.home_url, "/images/photos/", "#{photo[:hash]}.#{URI::DEFAULT_PARSER.escape(photo[:file_name])}")
|
23
|
+
- if photo[:description]
|
24
|
+
p = photo[:description]
|
25
|
+
- if photo[:has_location]
|
26
|
+
p #{[photo[:city], photo[:state], photo[:country]].compact.join(", ")}.
|
27
|
+
- if photo[:taken_at]
|
28
|
+
p Taken #{photo[:taken_at].strftime("%-d %B, %Y.")}
|
29
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
== Piccle::TemplateHelpers.render_partial "header"
|
4
|
+
|
5
|
+
body.photos_index
|
6
|
+
header
|
7
|
+
|
|
8
|
+
{{#if selector}}
|
9
|
+
<h1><a href="{{include_prefix}}index.html">Photography by {{site_metadata.author_name}}</a></h1>
|
10
|
+
{{else}}
|
11
|
+
<h1>Photography by {{site_metadata.author_name}}</a></h1>
|
12
|
+
{{/if}}
|
13
|
+
== Piccle::TemplateHelpers.render_partial "breadcrumbs"
|
14
|
+
main
|
15
|
+
== Piccle::TemplateHelpers.render_partial "navigation"
|
16
|
+
|
17
|
+
.photos
|
18
|
+
|
|
19
|
+
{{#each photos as |photo|}}
|
20
|
+
{{#if (lookup ../event_ends photo.hash)}}
|
21
|
+
{{#with (lookup ../event_ends hash)}}
|
22
|
+
<p class="event_block event_end"><a href="{{../include_prefix}}{{join selector '/'}}/index.html">{{name}}</a></p>
|
23
|
+
{{/with}}
|
24
|
+
{{/if}}
|
25
|
+
<a href="{{../include_prefix}}{{../selector_path}}{{photo.hash}}.html#photo">
|
26
|
+
<img class="thumbnail" src="{{../include_prefix}}images/thumbnails/{{photo.hash}}.{{photo.file_name}}" alt="{{photo.title}}" />
|
27
|
+
</a>
|
28
|
+
{{#if (lookup ../event_starts photo.hash)}}
|
29
|
+
{{#with (lookup ../event_starts hash)}}
|
30
|
+
<p class="event_block event_start"><a href="{{../include_prefix}}{{join selector '/'}}/index.html">{{name}}</a></p>
|
31
|
+
{{/with}}
|
32
|
+
{{/if}}
|
33
|
+
{{/each}}
|
34
|
+
|
35
|
+
== Piccle::TemplateHelpers.render_partial "footer"
|
36
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
== Piccle::TemplateHelpers.render_partial "header"
|
2
|
+
|
3
|
+
body.photo_show
|
4
|
+
header
|
5
|
+
h1
|
6
|
+
a href="{{include_prefix}}index.html" Photography by {{site_metadata.author_name}}
|
7
|
+
== Piccle::TemplateHelpers.render_partial "breadcrumbs"
|
8
|
+
main#photo
|
9
|
+
h2
|
10
|
+
| {{photo.title}}
|
11
|
+
.photo_with_pagination
|
12
|
+
| {{#if prev_link}}
|
13
|
+
<a class="navigation_arrow" href="{{include_prefix}}{{prev_link}}#photo">«</a>
|
14
|
+
{{else}}
|
15
|
+
<span class="navigation_arrow"> </span>
|
16
|
+
{{/if}}
|
17
|
+
img src="{{include_prefix}}images/photos/{{photo.hash}}.{{photo.file_name}}"
|
18
|
+
| {{#if next_link}}
|
19
|
+
<a class="navigation_arrow" href="{{include_prefix}}{{next_link}}#photo">»</a>
|
20
|
+
{{else}}
|
21
|
+
<span class="navigation_arrow"> </span>
|
22
|
+
{{/if}}
|
23
|
+
|
24
|
+
p.description
|
25
|
+
| {{photo.description}}
|
26
|
+
p.settings
|
27
|
+
| {{#if camera_link}}
|
28
|
+
<a href="{{include_prefix}}{{camera_link.link}}">{{camera_link.friendly_name}}</a>{{/if}}{{#if photo.focal_length}}, {{photo.focal_length}}{{/if}}{{#if photo.aperture}}, f/{{photo.aperture}}{{/if}}{{#if photo.shutter_speed}}, {{photo.shutter_speed}}{{/if}}{{#if photo.iso}}, ISO {{photo.iso}}{{/if}}.
|
29
|
+
| {{#if photo.taken_at}}
|
30
|
+
<p class="date">
|
31
|
+
{{#if day_link}}<a href="{{include_prefix}}{{day_link.link}}">{{day_link.friendly_name}}</a>{{/if}}
|
32
|
+
{{#if month_link}}<a href="{{include_prefix}}{{month_link.link}}">{{month_link.friendly_name}}</a>, {{/if}}
|
33
|
+
{{#if year_link}}<a href="{{include_prefix}}{{year_link.link}}">{{year_link.friendly_name}}</a>.{{/if}}
|
34
|
+
</p>
|
35
|
+
{{/if}}
|
36
|
+
{{#if photo.has_location}}
|
37
|
+
<ul class="location">
|
38
|
+
{{#if city_link}}<li><a href="{{include_prefix}}{{city_link.link}}">{{city_link.friendly_name}}</a></li>{{/if}}
|
39
|
+
{{#if state_link}}<li><a href="{{include_prefix}}{{state_link.link}}">{{state_link.friendly_name}}</a></li>{{/if}}
|
40
|
+
{{#if country_link}}<li><a href="{{include_prefix}}{{country_link.link}}">{{country_link.friendly_name}}</a></li>{{/if}}
|
41
|
+
</ul>
|
42
|
+
{{/if}}
|
43
|
+
|
44
|
+
| {{#if keywords}}
|
45
|
+
<div class="keywords">
|
46
|
+
{{#each keywords as |keyword|}}
|
47
|
+
<a href="{{../include_prefix}}{{keyword.link}}">{{keyword.friendly_name}}</a>
|
48
|
+
{{/each}}
|
49
|
+
</div>
|
50
|
+
{{/if}}
|
51
|
+
|
52
|
+
.streams
|
53
|
+
|
|
54
|
+
{{#each substreams as |stream|}}
|
55
|
+
<section>
|
56
|
+
<h2>{{stream.title}}</h2>
|
57
|
+
<div class="stream">
|
58
|
+
== Piccle::TemplateHelpers.render_partial "substream"
|
59
|
+
|
|
60
|
+
</div>
|
61
|
+
</section>
|
62
|
+
{{/each}}
|
63
|
+
|
64
|
+
== Piccle::TemplateHelpers.render_partial "footer"
|