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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd212331e34487d24a2802da86fa54d28bffa6f26c5f0440f207af631c1ce903
4
- data.tar.gz: 1dbdd9262bd39aca7ed6606e0dd3f173d6a749bd3d8e6fb51e4c5611d4301dee
3
+ metadata.gz: 14d2d7374cb2a5ba1b1eb5ba7a5b059c815b1e73a2812b029bd4c5784348c695
4
+ data.tar.gz: 3be74020a0be66f0e87dd07426660d2ffb46f357f84cced4bfeccfe99c6fe229
5
5
  SHA512:
6
- metadata.gz: 7e26717d20db332f2d524a5a9af47dff8ed36171b274224d915022655e958adc7f4d07b68ae639132beb3453590a049aa70e3326883f4e813844c948911e28c8
7
- data.tar.gz: f6292967d9c18128a78040eede0df12f6b716135b71052a4eecb8513363ea0d70bfd57e4d7cfefa1056e60278710be8f1fff642d03590665ecf4b25f4b3d67fc
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
- #### Required assets
88
+ #### Bundled assets
89
89
 
90
- - `assets/css/storymap.css`
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
- #### Required assets
116
+ #### Bundled assets
116
117
 
117
- - `assets/js/lungkui.js`
118
- - `assets/css/lungkui.css`
119
- - MapLibre GL JS loaded from CDN automatically
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
- assets = inject_assets_once(page, baseurl)
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
- text.to_s.gsub("\\", "\\\\").gsub("'", "\\'").gsub("\n", " ")
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
- assets = inject_assets_once(page, baseurl)
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%; }