jekyll-gis-blogger 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +8 -7
- data/lib/jekyll-gis-blogger/assets.rb +37 -0
- data/lib/jekyll-gis-blogger/tags/lungkui.rb +10 -5
- data/lib/jekyll-gis-blogger/tags/map.rb +3 -1
- data/lib/jekyll-gis-blogger/tags/storymap.rb +10 -5
- data/lib/jekyll-gis-blogger/vendor/assets/gis-blogger/css/lungkui.css +139 -0
- data/lib/jekyll-gis-blogger/vendor/assets/gis-blogger/css/storymap.css +3059 -0
- data/lib/jekyll-gis-blogger/vendor/assets/gis-blogger/js/lungkui.js +652 -0
- data/lib/jekyll-gis-blogger/vendor/assets/gis-blogger/js/storymap-min.js +2 -0
- data/lib/jekyll-gis-blogger.rb +1 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 14d2d7374cb2a5ba1b1eb5ba7a5b059c815b1e73a2812b029bd4c5784348c695
|
|
4
|
+
data.tar.gz: 3be74020a0be66f0e87dd07426660d2ffb46f357f84cced4bfeccfe99c6fe229
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82b168f443558288708b4eeb70b9eec70cf50a8fb72020bd11968798b5def3bb57e8fd750094afc8cbca5041b131744fba70308e62bcd3d70a476c3124022d2d
|
|
7
|
+
data.tar.gz: 101157567275c605066bff5ad0ba9a9e3bbd3b2eb145c47cd6d6256989e957c9857e49433db272b38ed9b8e85428d6caea3d89293af0519c0b2bb26c4f8f4e29
|
data/README.md
CHANGED
|
@@ -85,10 +85,11 @@ Options: `height="600px" width="80%" options='{"calculate_zoom":false}'`
|
|
|
85
85
|
| `overview` | `1`/`true` for intro/title slide |
|
|
86
86
|
| `lat`, `lon` | CSV only; GeoJSON uses Point geometry |
|
|
87
87
|
|
|
88
|
-
####
|
|
88
|
+
#### Bundled assets
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
- `assets/js/storymap-min.js
|
|
90
|
+
Shipped with the gem and copied into your build automatically at
|
|
91
|
+
`assets/gis-blogger/css/storymap.css` and `assets/gis-blogger/js/storymap-min.js`.
|
|
92
|
+
Nothing to copy by hand.
|
|
92
93
|
|
|
93
94
|
### `{% lungkui %}` — lungkui.js MapLibre scrollytelling
|
|
94
95
|
|
|
@@ -112,11 +113,11 @@ Mode `deck` (default) uses arrow keys/click — no page scroll needed. Mode `scr
|
|
|
112
113
|
|
|
113
114
|
Same GeoJSON field names as storymap (`headline`, `text`, `media`, `caption`, `credit`, `zoom`, `order`, `overview`). Coordinates from Point geometry.
|
|
114
115
|
|
|
115
|
-
####
|
|
116
|
+
#### Bundled assets
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
`assets/gis-blogger/js/lungkui.js` and `assets/gis-blogger/css/lungkui.css` ship
|
|
119
|
+
with the gem and are copied into your build automatically. MapLibre GL JS is
|
|
120
|
+
loaded from CDN. Nothing to copy by hand.
|
|
120
121
|
|
|
121
122
|
### `{% gpx_track %}` — GPX track + elevation profile
|
|
122
123
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module GisBlogger
|
|
5
|
+
# Ships the gem's own JS/CSS with every Jekyll build.
|
|
6
|
+
#
|
|
7
|
+
# The storymap and lungkui tags need local assets (StoryMapJS + lungkui.js).
|
|
8
|
+
# Rather than making each project copy them by hand, we register them as
|
|
9
|
+
# Jekyll static files so they are emitted into <dest>/assets/gis-blogger/...
|
|
10
|
+
# automatically. Namespaced under /assets/gis-blogger/ so a site's own
|
|
11
|
+
# /assets/ is never touched.
|
|
12
|
+
module Assets
|
|
13
|
+
# <gem>/lib/jekyll-gis-blogger/vendor
|
|
14
|
+
VENDOR_ROOT = File.expand_path("vendor", __dir__)
|
|
15
|
+
|
|
16
|
+
# dir (URL path, mirrored on disk under VENDOR_ROOT) => filenames
|
|
17
|
+
FILES = {
|
|
18
|
+
"/assets/gis-blogger/js" => %w[storymap-min.js lungkui.js],
|
|
19
|
+
"/assets/gis-blogger/css" => %w[storymap.css lungkui.css]
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# ponytail: always copies all four (~370KB) even if a page uses none —
|
|
23
|
+
# usage isn't known at :post_read. Browsers only fetch what a tag references,
|
|
24
|
+
# so the cost is disk in _site, not bandwidth. Fine until it isn't.
|
|
25
|
+
Jekyll::Hooks.register :site, :post_read do |site|
|
|
26
|
+
FILES.each do |dir, names|
|
|
27
|
+
names.each do |name|
|
|
28
|
+
already = site.static_files.any? { |f| f.relative_path == File.join(dir, name) }
|
|
29
|
+
next if already
|
|
30
|
+
|
|
31
|
+
site.static_files << Jekyll::StaticFile.new(site, VENDOR_ROOT, dir, name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -42,14 +42,16 @@ module Jekyll
|
|
|
42
42
|
div_id = "lungkui-map-#{count}"
|
|
43
43
|
|
|
44
44
|
config_js = build_config_arg(site, baseurl)
|
|
45
|
-
|
|
45
|
+
return warn_html("needs one of: geojson= | config=") unless config_js
|
|
46
|
+
|
|
47
|
+
assets = inject_assets_once(page, baseurl)
|
|
46
48
|
|
|
47
49
|
[
|
|
48
50
|
assets,
|
|
49
51
|
%(<div id="#{div_id}" class="lk-root lk-embed" ) +
|
|
50
52
|
%(style="position:relative;width:#{width};height:#{height};"></div>),
|
|
51
53
|
%(<script type="module">),
|
|
52
|
-
%(import { Lungkui } from '#{baseurl}/assets/js/lungkui.js';),
|
|
54
|
+
%(import { Lungkui } from '#{baseurl}/assets/gis-blogger/js/lungkui.js';),
|
|
53
55
|
%(new Lungkui('##{div_id}', #{config_js});),
|
|
54
56
|
%(</script>)
|
|
55
57
|
].join("\n")
|
|
@@ -62,11 +64,14 @@ module Jekyll
|
|
|
62
64
|
inline(parse_config(site, @attrs["config"]))
|
|
63
65
|
elsif @attrs["geojson"]
|
|
64
66
|
inline(build_config(site, baseurl))
|
|
65
|
-
else
|
|
66
|
-
raise ArgumentError, "{% lungkui %} needs one of: geojson= | config="
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def warn_html(reason)
|
|
71
|
+
Jekyll.logger.warn "lungkui:", reason
|
|
72
|
+
"<!-- lungkui: #{reason} -->"
|
|
73
|
+
end
|
|
74
|
+
|
|
70
75
|
def build_config(site, baseurl)
|
|
71
76
|
geojson_path = @attrs["geojson"]
|
|
72
77
|
slides = slides_from_geojson(read_source(site, geojson_path))
|
|
@@ -159,7 +164,7 @@ module Jekyll
|
|
|
159
164
|
page["lungkui_assets_injected"] = true
|
|
160
165
|
[
|
|
161
166
|
%(<link rel="stylesheet" href="#{MAPLIBRE_CSS}">),
|
|
162
|
-
%(<link rel="stylesheet" href="#{baseurl}/assets/css/lungkui.css">),
|
|
167
|
+
%(<link rel="stylesheet" href="#{baseurl}/assets/gis-blogger/css/lungkui.css">),
|
|
163
168
|
%(<script src="#{MAPLIBRE_JS}"></script>)
|
|
164
169
|
].join("\n")
|
|
165
170
|
end
|
|
@@ -91,7 +91,9 @@ module Jekyll
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def escape_js(text)
|
|
94
|
-
|
|
94
|
+
# Block form avoids gsub's replacement-string backreferences
|
|
95
|
+
# (a literal "\\'" replacement means post-match, not an escaped quote).
|
|
96
|
+
text.to_s.gsub(/[\\'\r\n]/) { |c| c == "\n" || c == "\r" ? " " : "\\#{c}" }
|
|
95
97
|
end
|
|
96
98
|
end
|
|
97
99
|
end
|
|
@@ -33,7 +33,9 @@ module Jekyll
|
|
|
33
33
|
js_var = "storymapMap#{count}"
|
|
34
34
|
|
|
35
35
|
data_arg = build_data_arg(site, baseurl)
|
|
36
|
-
|
|
36
|
+
return warn_html("needs one of: data= | geojson= | csv=") unless data_arg
|
|
37
|
+
|
|
38
|
+
assets = inject_assets_once(page, baseurl)
|
|
37
39
|
|
|
38
40
|
[
|
|
39
41
|
assets,
|
|
@@ -55,11 +57,14 @@ module Jekyll
|
|
|
55
57
|
elsif @attrs["data"]
|
|
56
58
|
url = absolute?(@attrs["data"]) ? @attrs["data"] : "#{baseurl}#{@attrs['data']}"
|
|
57
59
|
"'#{url}'"
|
|
58
|
-
else
|
|
59
|
-
raise ArgumentError, "{% storymap %} needs one of: data= | geojson= | csv="
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def warn_html(reason)
|
|
64
|
+
Jekyll.logger.warn "storymap:", reason
|
|
65
|
+
"<!-- storymap: #{reason} -->"
|
|
66
|
+
end
|
|
67
|
+
|
|
63
68
|
def build_object(slides)
|
|
64
69
|
{ "storymap" => { "slides" => slides } }
|
|
65
70
|
end
|
|
@@ -119,8 +124,8 @@ module Jekyll
|
|
|
119
124
|
|
|
120
125
|
page["storymap_assets_injected"] = true
|
|
121
126
|
[
|
|
122
|
-
"<link rel=\"stylesheet\" href=\"#{baseurl}/assets/css/storymap.css\">",
|
|
123
|
-
"<script type=\"text/javascript\" src=\"#{baseurl}/assets/js/storymap-min.js\"></script>"
|
|
127
|
+
"<link rel=\"stylesheet\" href=\"#{baseurl}/assets/gis-blogger/css/storymap.css\">",
|
|
128
|
+
"<script type=\"text/javascript\" src=\"#{baseurl}/assets/gis-blogger/js/storymap-min.js\"></script>"
|
|
124
129
|
].join("\n")
|
|
125
130
|
end
|
|
126
131
|
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--lk-accent: #e4572e;
|
|
3
|
+
--lk-panel-bg: #ffffff;
|
|
4
|
+
--lk-text: #111111;
|
|
5
|
+
--lk-font: Georgia, serif;
|
|
6
|
+
--lk-panel-width: 360px;
|
|
7
|
+
}
|
|
8
|
+
.lk-root { position: relative; }
|
|
9
|
+
|
|
10
|
+
/* ponytail: fixed map fills the viewport for full-page stories; an embedded
|
|
11
|
+
(non-full-page) mount needs a sticky wrapper instead — add when needed. */
|
|
12
|
+
.lk-map { position: fixed; inset: 0; }
|
|
13
|
+
.lk-map .maplibregl-map { position: absolute; inset: 0; }
|
|
14
|
+
.lk-slides { position: relative; z-index: 1; }
|
|
15
|
+
|
|
16
|
+
/* Scroll mode: panels flow over a fixed map. Side padding keeps panels
|
|
17
|
+
clear of the edge arrows. */
|
|
18
|
+
.lk-slide {
|
|
19
|
+
min-height: 100vh;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
padding: 0 80px;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
}
|
|
26
|
+
.lk-slide--left { justify-content: flex-start; }
|
|
27
|
+
.lk-slide--right { justify-content: flex-end; }
|
|
28
|
+
.lk-slide--center { justify-content: center; }
|
|
29
|
+
.lk-slide--full { justify-content: center; }
|
|
30
|
+
|
|
31
|
+
.lk-slide__panel {
|
|
32
|
+
pointer-events: auto;
|
|
33
|
+
width: var(--lk-panel-width);
|
|
34
|
+
max-width: 90vw;
|
|
35
|
+
padding: 1.5rem 1.75rem;
|
|
36
|
+
background: var(--lk-panel-bg);
|
|
37
|
+
color: var(--lk-text);
|
|
38
|
+
font-family: var(--lk-font);
|
|
39
|
+
border-radius: 6px;
|
|
40
|
+
box-shadow: 0 10px 34px rgba(0, 0, 0, 0.22);
|
|
41
|
+
line-height: 1.6;
|
|
42
|
+
}
|
|
43
|
+
.lk-slide--full .lk-slide__panel { width: 620px; text-align: center; }
|
|
44
|
+
.lk-slide__panel h2 {
|
|
45
|
+
margin: 0 0 0.6rem;
|
|
46
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
47
|
+
font-size: 1.7rem;
|
|
48
|
+
font-weight: 700;
|
|
49
|
+
line-height: 1.15;
|
|
50
|
+
color: var(--lk-accent);
|
|
51
|
+
}
|
|
52
|
+
.lk-slide__panel p { margin: 0.6rem 0; }
|
|
53
|
+
.lk-slide__panel a { color: var(--lk-accent); }
|
|
54
|
+
.lk-media { margin: 1rem 0 0; }
|
|
55
|
+
.lk-media img { width: 100%; border-radius: 4px; }
|
|
56
|
+
.lk-media figcaption { font-size: 0.8rem; opacity: 0.75; margin-top: 0.35rem; }
|
|
57
|
+
|
|
58
|
+
/* Deck mode: one active panel at a time */
|
|
59
|
+
.lk-deck .lk-slide { display: none; }
|
|
60
|
+
.lk-deck .lk-slide.is-active { display: flex; }
|
|
61
|
+
|
|
62
|
+
/* Edge navigation arrows (StoryMapJS-style dark tabs) */
|
|
63
|
+
.lk-controls {
|
|
64
|
+
position: fixed;
|
|
65
|
+
top: 50%;
|
|
66
|
+
left: 0;
|
|
67
|
+
right: 0;
|
|
68
|
+
transform: translateY(-50%);
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
z-index: 3;
|
|
73
|
+
}
|
|
74
|
+
.lk-arrow {
|
|
75
|
+
pointer-events: auto;
|
|
76
|
+
width: 54px;
|
|
77
|
+
height: 92px;
|
|
78
|
+
border: none;
|
|
79
|
+
background: rgba(20, 20, 20, 0.55);
|
|
80
|
+
color: #fff;
|
|
81
|
+
font-size: 1.9rem;
|
|
82
|
+
line-height: 1;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: background 0.15s ease;
|
|
85
|
+
}
|
|
86
|
+
.lk-arrow--prev { border-radius: 0 6px 6px 0; }
|
|
87
|
+
.lk-arrow--next { border-radius: 6px 0 0 6px; }
|
|
88
|
+
.lk-arrow:hover { background: rgba(20, 20, 20, 0.8); }
|
|
89
|
+
|
|
90
|
+
/* Top toolbar */
|
|
91
|
+
.lk-toolbar {
|
|
92
|
+
position: fixed;
|
|
93
|
+
top: 0;
|
|
94
|
+
left: 0;
|
|
95
|
+
display: flex;
|
|
96
|
+
z-index: 4;
|
|
97
|
+
}
|
|
98
|
+
.lk-toolbar__btn {
|
|
99
|
+
border: none;
|
|
100
|
+
border-right: 1px solid rgba(255, 255, 255, 0.15);
|
|
101
|
+
background: rgba(20, 20, 20, 0.78);
|
|
102
|
+
color: #fff;
|
|
103
|
+
font: 0.85rem/1 system-ui, sans-serif;
|
|
104
|
+
padding: 0.7rem 1rem;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
}
|
|
107
|
+
.lk-toolbar__btn:hover,
|
|
108
|
+
.lk-toolbar__btn.is-active { background: var(--lk-accent); }
|
|
109
|
+
|
|
110
|
+
/* Mini-map overview inset */
|
|
111
|
+
.lk-overview {
|
|
112
|
+
position: fixed;
|
|
113
|
+
top: 44px;
|
|
114
|
+
left: 12px;
|
|
115
|
+
width: 220px;
|
|
116
|
+
height: 150px;
|
|
117
|
+
border: 3px solid #fff;
|
|
118
|
+
border-radius: 4px;
|
|
119
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
z-index: 4;
|
|
122
|
+
}
|
|
123
|
+
.lk-overview .maplibregl-map { position: absolute; inset: 0; }
|
|
124
|
+
|
|
125
|
+
/* ─────────────────────────── embedded mode ───────────────────────────
|
|
126
|
+
Full-page stories position the map + chrome against the viewport (fixed).
|
|
127
|
+
When embedding inside a fixed-height box (e.g. a blog post), add .lk-embed
|
|
128
|
+
on the root: everything re-anchors to the box instead of the viewport. */
|
|
129
|
+
.lk-embed { overflow: hidden; }
|
|
130
|
+
.lk-embed .lk-map { position: absolute; }
|
|
131
|
+
.lk-embed .lk-slides { position: absolute; inset: 0; pointer-events: none; }
|
|
132
|
+
.lk-embed .lk-controls,
|
|
133
|
+
.lk-embed .lk-toolbar,
|
|
134
|
+
.lk-embed .lk-overview { position: absolute; }
|
|
135
|
+
/* Deck panels fill the box height (not 100vh). */
|
|
136
|
+
.lk-embed.lk-deck .lk-slide { min-height: 100%; height: 100%; }
|
|
137
|
+
/* Scroll mode inside a box scrolls the slides layer, not the page. */
|
|
138
|
+
.lk-embed:not(.lk-deck) .lk-slides { overflow-y: auto; pointer-events: auto; }
|
|
139
|
+
.lk-embed:not(.lk-deck) .lk-slide { min-height: 100%; }
|