piccle 0.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|