@knight-lab/timelinejs 3.9.0 → 3.9.2
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.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +5 -0
- package/dist/css/fonts/font.bitter-raleway.css +1 -1
- package/dist/js/locale/en-gb.json +69 -0
- package/dist/js/locale/es.json +1 -1
- package/dist/js/locale/hi.json +0 -1
- package/dist/js/timeline.js +1 -1
- package/dist/js/timeline.js.map +1 -1
- package/package.json +4 -3
- package/src/js/date/TLDate.js +32 -1
- package/src/js/date/__tests__/TLDate.test.js +10 -0
- package/src/js/dom/DOM.js +7 -1
- package/src/js/language/Language.js +1 -0
- package/src/js/language/locale/en-gb.json +69 -0
- package/src/js/language/locale/es.json +1 -1
- package/src/js/language/locale/hi.json +0 -1
- package/src/js/media/MediaType.js +21 -8
- package/src/js/media/__tests__/MediaType.test.js +47 -0
- package/src/js/media/types/WikipediaImage.js +159 -0
- package/src/js/media/types/__tests__/WikipediaImage.test.js +48 -0
- package/src/js/net/Net.js +1 -1
- package/src/js/slider/SlideNav.js +1 -1
- package/src/js/timenav/TimeNav.js +3 -1
- package/src/js/ui/MenuBar.js +6 -10
- package/src/template/all-media-types.json +29 -3
- package/src/template/cosmo.json +113 -0
- package/src/template/index.html +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knight-lab/timelinejs",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.2",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "TimelineJS v3: A Storytelling Timeline built in JavaScript, made by Northwestern University Knight Lab.",
|
|
6
6
|
"dependencies": {
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"stage_dev": "npm run dist && node tasks/stage.js dev",
|
|
60
60
|
"prepublishOnly": "npm run dist"
|
|
61
61
|
},
|
|
62
|
-
"contributors": [
|
|
62
|
+
"contributors": [
|
|
63
|
+
{
|
|
63
64
|
"name": "Zach Wise",
|
|
64
65
|
"email": "wise@northwestern.edu",
|
|
65
66
|
"url": "https://github.com/zachwise"
|
|
@@ -70,4 +71,4 @@
|
|
|
70
71
|
"url": "https://github.com/JoeGermuska"
|
|
71
72
|
}
|
|
72
73
|
]
|
|
73
|
-
}
|
|
74
|
+
}
|
package/src/js/date/TLDate.js
CHANGED
|
@@ -178,13 +178,24 @@ export const TLDate = TLClass.extend({
|
|
|
178
178
|
this._createDateObj();
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
if (data.format && !format) {
|
|
182
|
+
format = data.format
|
|
183
|
+
}
|
|
181
184
|
this._setFormat(format, format_short);
|
|
182
185
|
},
|
|
183
186
|
|
|
184
187
|
setDateFormat: function(format) {
|
|
185
188
|
this.data.format = format;
|
|
186
189
|
},
|
|
187
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Return a string representation of this date. If this date has been created with a `display_date` property,
|
|
192
|
+
* that value is always returned, regardless of arguments to the method invocation. Otherwise,
|
|
193
|
+
* the given `Language` is asked to create a string representation based on this Date's data, passing through the provided
|
|
194
|
+
* `format` String, or, if that is undefined, this date's default format string.
|
|
195
|
+
* @param {Language} language
|
|
196
|
+
* @param {String} format
|
|
197
|
+
* @returns {String} formattedDate
|
|
198
|
+
*/
|
|
188
199
|
getDisplayDate: function(language, format) {
|
|
189
200
|
if (this.data.display_date) {
|
|
190
201
|
return this.data.display_date;
|
|
@@ -345,6 +356,14 @@ export const TLDate = TLClass.extend({
|
|
|
345
356
|
} else if (!this.data.format_short) {
|
|
346
357
|
this.data.format_short = this.findBestFormat(true);
|
|
347
358
|
}
|
|
359
|
+
},
|
|
360
|
+
/**
|
|
361
|
+
* Get the year-only representation of this date. Ticks need this to layout
|
|
362
|
+
* the time axis, and this needs to work isomorphically for TLDate and BigDate
|
|
363
|
+
* @returns {Number}
|
|
364
|
+
*/
|
|
365
|
+
getFullYear: function() {
|
|
366
|
+
return this.data.date_obj.getFullYear()
|
|
348
367
|
}
|
|
349
368
|
});
|
|
350
369
|
|
|
@@ -477,6 +496,10 @@ export const BigDate = TLDate.extend({
|
|
|
477
496
|
this._createDateObj();
|
|
478
497
|
}
|
|
479
498
|
|
|
499
|
+
if (data.format && !format) {
|
|
500
|
+
format = data.format
|
|
501
|
+
}
|
|
502
|
+
|
|
480
503
|
this._setFormat(format, format_short);
|
|
481
504
|
},
|
|
482
505
|
|
|
@@ -497,5 +520,13 @@ export const BigDate = TLDate.extend({
|
|
|
497
520
|
}
|
|
498
521
|
|
|
499
522
|
throw new TLError("invalid_scale_err", scale);
|
|
523
|
+
},
|
|
524
|
+
/**
|
|
525
|
+
* Get the year-only representation of this date. Ticks need this to layout
|
|
526
|
+
* the time axis, and this needs to work isomorphically for TLDate and BigDate
|
|
527
|
+
* @returns {Number}
|
|
528
|
+
*/
|
|
529
|
+
getFullYear: function() {
|
|
530
|
+
return this.data.date_obj.getTime()
|
|
500
531
|
}
|
|
501
532
|
});
|
|
@@ -55,6 +55,16 @@ test("display_text overrides other formatting", () => {
|
|
|
55
55
|
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
+
test("date constructor format overrides other formatting", () => {
|
|
59
|
+
var cdate = new TLDate({ year: 2014, month: 12 }, 'yyyy')
|
|
60
|
+
expect(cdate.getDisplayDate()).toBe('2014')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("format in constructor data param overrides other formatting", () => {
|
|
64
|
+
var cdate = new TLDate({ year: 2014, month: 12, format: 'yyyy' })
|
|
65
|
+
expect(cdate.getDisplayDate()).toBe('2014')
|
|
66
|
+
})
|
|
67
|
+
|
|
58
68
|
test("handle years in the first century CE correctly", () => {
|
|
59
69
|
var date = makeDate({ year: 75 });
|
|
60
70
|
expect(date.getDisplayDate()).toBe("75")
|
package/src/js/dom/DOM.js
CHANGED
|
@@ -19,6 +19,12 @@ function create(tagName, className, container) {
|
|
|
19
19
|
return el;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function createButton(className, container) {
|
|
23
|
+
var el = create('button', className, container);
|
|
24
|
+
el.type = 'button';
|
|
25
|
+
return el;
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
function createText(content, container) {
|
|
23
29
|
var el = document.createTextNode(content);
|
|
24
30
|
if (container) {
|
|
@@ -78,4 +84,4 @@ let TRANSFORM = testProp(['transformProperty', 'WebkitTransform', 'OTransform',
|
|
|
78
84
|
let TRANSLATE_OPEN = 'translate' + (Browser.webkit3d ? '3d(' : '(')
|
|
79
85
|
let TRANSLATE_CLOSE = Browser.webkit3d ? ',0)' : ')'
|
|
80
86
|
|
|
81
|
-
export { get, create, getPosition }
|
|
87
|
+
export { get, create, createButton, getPosition }
|
|
@@ -318,6 +318,7 @@ var LANGUAGES = {
|
|
|
318
318
|
twitter_load_err: "Unable to load Tweet",
|
|
319
319
|
twitterembed_invalidurl_err: "Invalid Twitter Embed url",
|
|
320
320
|
wikipedia_load_err: "Unable to load Wikipedia entry",
|
|
321
|
+
wikipedia_image_load_err: "Unable to load Wikipedia image data",
|
|
321
322
|
spotify_invalid_url: "Invalid Spotify URL",
|
|
322
323
|
invalid_rgb_err: "Invalid RGB argument",
|
|
323
324
|
time_scale_scale_err: "Don't know how to get date from time for scale",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lang": "en-gb",
|
|
3
|
+
"date": {
|
|
4
|
+
"month_abbr": [
|
|
5
|
+
"Jan",
|
|
6
|
+
"Feb",
|
|
7
|
+
"Mar",
|
|
8
|
+
"Apr",
|
|
9
|
+
"May",
|
|
10
|
+
"Jun",
|
|
11
|
+
"Jul",
|
|
12
|
+
"Aug",
|
|
13
|
+
"Sep",
|
|
14
|
+
"Oct",
|
|
15
|
+
"Nov",
|
|
16
|
+
"Dec"
|
|
17
|
+
],
|
|
18
|
+
"day_abbr": [
|
|
19
|
+
"Sun",
|
|
20
|
+
"Mon",
|
|
21
|
+
"Tue",
|
|
22
|
+
"Wed",
|
|
23
|
+
"Thu",
|
|
24
|
+
"Fri",
|
|
25
|
+
"Sat"
|
|
26
|
+
],
|
|
27
|
+
"day": [
|
|
28
|
+
"Sunday",
|
|
29
|
+
"Monday",
|
|
30
|
+
"Tuesday",
|
|
31
|
+
"Wednesday",
|
|
32
|
+
"Thursday",
|
|
33
|
+
"Friday",
|
|
34
|
+
"Saturday"
|
|
35
|
+
],
|
|
36
|
+
"month": [
|
|
37
|
+
"January",
|
|
38
|
+
"February",
|
|
39
|
+
"March",
|
|
40
|
+
"April",
|
|
41
|
+
"May",
|
|
42
|
+
"June",
|
|
43
|
+
"July",
|
|
44
|
+
"August",
|
|
45
|
+
"September",
|
|
46
|
+
"October",
|
|
47
|
+
"November",
|
|
48
|
+
"December"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"api": {
|
|
52
|
+
"wikipedia": "en"
|
|
53
|
+
},
|
|
54
|
+
"messages": {
|
|
55
|
+
"comment": "There is no need to repeat message keys because the fallback is English and this file only exists to control date formatting."
|
|
56
|
+
},
|
|
57
|
+
"dateformats": {
|
|
58
|
+
"full_long": "d mmm yyyy 'at' HH:MM TT",
|
|
59
|
+
"full_short": "d mmm",
|
|
60
|
+
"full": "d mmmm yyyy",
|
|
61
|
+
"month_short": "mmm",
|
|
62
|
+
"time_no_seconds_small_date": "HH:MM TT'<br/><small>'d mmmm yyyy'</small>'",
|
|
63
|
+
"month": "mmmm yyyy",
|
|
64
|
+
"time_no_seconds_short": "HH:MM TT",
|
|
65
|
+
"time_short": "HH:MM:ss",
|
|
66
|
+
"year": "yyyy",
|
|
67
|
+
"full_long_small_date": "HH:MM TT'<br/><small>d mmm yyyy'</small>'"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"messages": {
|
|
55
55
|
"loading": "cargando",
|
|
56
56
|
"return_to_title": "Volver al título",
|
|
57
|
-
"
|
|
57
|
+
"swipe_to_navigate": "Desliza para ver<br><span class='tl-button'>OK</span>",
|
|
58
58
|
"wikipedia": "Desde Wikipedia, la enciclopedia libre",
|
|
59
59
|
"loading_content": "cargando",
|
|
60
60
|
"loading_timeline": "La cronología esta cargando"
|
|
@@ -15,6 +15,7 @@ import YouTube from "./types/YouTube"
|
|
|
15
15
|
import GoogleMap from "./types/GoogleMap"
|
|
16
16
|
import Blockquote from "./types/Blockquote"
|
|
17
17
|
import Wikipedia from "./types/Wikipedia"
|
|
18
|
+
import WikipediaImage from "./types/WikipediaImage"
|
|
18
19
|
import SoundCloud from "./types/SoundCloud"
|
|
19
20
|
import Vimeo from "./types/Vimeo"
|
|
20
21
|
import DailyMotion from "./types/DailyMotion"
|
|
@@ -58,7 +59,7 @@ export function lookupMediaType(m, image_only) {
|
|
|
58
59
|
media_types = [{
|
|
59
60
|
type: "youtube",
|
|
60
61
|
name: "YouTube",
|
|
61
|
-
match_str: "^(https?:)?\/*(www.)?youtube|youtu\.be",
|
|
62
|
+
match_str: "^(https?:)?\/*(www.|m.)?youtube|youtu\.be",
|
|
62
63
|
cls: YouTube
|
|
63
64
|
},
|
|
64
65
|
{
|
|
@@ -133,6 +134,24 @@ export function lookupMediaType(m, image_only) {
|
|
|
133
134
|
match_str: /documentcloud.org\//,
|
|
134
135
|
cls: DocumentCloud
|
|
135
136
|
},
|
|
137
|
+
{
|
|
138
|
+
type: "wikipedia-image",
|
|
139
|
+
name: "WikipediaImage",
|
|
140
|
+
match_str: "^https:\/\/.+\.wiki[mp]edia\.org.+#/media/.+\.(jpg|jpeg|png|gif|svg|webp)",
|
|
141
|
+
cls: WikipediaImage
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: "wikipedia-image",
|
|
145
|
+
name: "WikipediaImage",
|
|
146
|
+
match_str: "^https:\/\/commons.wikimedia.org/wiki/File:.+\.(jpg|jpeg|png|gif|svg|webp)",
|
|
147
|
+
cls: WikipediaImage
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: "wikipedia",
|
|
151
|
+
name: "Wikipedia",
|
|
152
|
+
match_str: "^(https?:)?\/.+.wikipedia\.org",
|
|
153
|
+
cls: Wikipedia
|
|
154
|
+
},
|
|
136
155
|
{
|
|
137
156
|
type: "image",
|
|
138
157
|
name: "Image",
|
|
@@ -148,7 +167,7 @@ export function lookupMediaType(m, image_only) {
|
|
|
148
167
|
{
|
|
149
168
|
type: "googledocs",
|
|
150
169
|
name: "Google Doc",
|
|
151
|
-
match_str: "^(https?:)?\/*[^.]*.google.com\/[^\/]*\/d\/[^\/]*\/[^\/]*\?usp=
|
|
170
|
+
match_str: "^(https?:)?\/*[^.]*.google.com\/[^\/]*\/d\/[^\/]*\/[^\/]*\?usp=shar.*|^(https?:)?\/*drive.google.com\/open\?id=[^\&]*\&authuser=0|^(https?:)?\/\/*drive.google.com\/open\\?id=[^\&]*|^(https?:)?\/*[^.]*.googledrive.com\/host\/[^\/]*\/",
|
|
152
171
|
cls: GoogleDoc
|
|
153
172
|
},
|
|
154
173
|
{
|
|
@@ -157,12 +176,6 @@ export function lookupMediaType(m, image_only) {
|
|
|
157
176
|
match_str: /^.*\.pdf(\?.*)?(\#.*)?/,
|
|
158
177
|
cls: PDF
|
|
159
178
|
},
|
|
160
|
-
{
|
|
161
|
-
type: "wikipedia",
|
|
162
|
-
name: "Wikipedia",
|
|
163
|
-
match_str: "^(https?:)?\/*(www.)?wikipedia\.org|^(https?:)?\/*([a-z][a-z].)?wikipedia\.org",
|
|
164
|
-
cls: Wikipedia
|
|
165
|
-
},
|
|
166
179
|
{
|
|
167
180
|
type: "spotify",
|
|
168
181
|
name: "spotify",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { lookupMediaType } from "../MediaType";
|
|
2
|
+
import { test, expect } from "@jest/globals"
|
|
3
|
+
|
|
4
|
+
const TEST_CASES = [
|
|
5
|
+
// tuples: [URL, expected type, optional note which isn't used.]
|
|
6
|
+
['https://www.youtube.com/watch?v=pi2v1m6gmD8&t=5m21s', 'youtube', 'YouTube with time stamp'],
|
|
7
|
+
['//www.youtube.com/watch?v=pi2v1m6gmD8', 'youtube', 'YouTube with no protocol'],
|
|
8
|
+
['youtu.be/pi2v1m6gmD8', 'youtube', 'YouTu.be short url'],
|
|
9
|
+
['http://vimeo.com/20839673', 'vimeo'],
|
|
10
|
+
['http://www.dailymotion.com/video/x2fo0e8_the-history-of-advertising-in-60-seconds_lifestyle', 'dailymotion'],
|
|
11
|
+
['https://vine.co/v/Og5Ai71WHdD', 'vine'],
|
|
12
|
+
['https://soundcloud.com/usher-raymond-music/usher-i-dont-mind-feat-juicy-j', 'soundcloud'],
|
|
13
|
+
['https://twitter.com/NASASpaceflight/status/562327074384654336', 'twitter'],
|
|
14
|
+
['<blockquote class="twitter-tweet" lang="en"><p>Rough vs. smooth: the Anuket and Anubis regions on <a href="https://twitter.com/hashtag/67P?src=hash">#67P</a> <a href="http://t.co/kOyAiOKlma">http://t.co/kOyAiOKlma</a> <a href="https://twitter.com/hashtag/CometWatch?src=hash">#CometWatch</a> <a href="http://t.co/YmQ8bP5WbS">pic.twitter.com/YmQ8bP5WbS</a></p>— ESA Rosetta Mission (@ESA_Rosetta) <a href="https://twitter.com/ESA_Rosetta/status/563722810397560832">February 6, 2015</a></blockquote>', 'twitterembed'],
|
|
15
|
+
['https://www.google.com/maps/@42.032147,-87.6689625,15z', 'googlemaps', 'Google Maps with lat/long'],
|
|
16
|
+
['https://www.google.com/maps/search/target/@41.8747339,-87.6481257,13z?hl=en-US', 'googlemaps', 'Google Maps with search'],
|
|
17
|
+
['https://www.google.com/maps/place/Northwestern+University/@42.056459,-87.675267,17z/data=!3m1!4b1!4m2!3m1!1s0x880fd00b703e4c39:0x2c37b567fad56106', 'googlemaps', 'Google Maps with place'],
|
|
18
|
+
['https://www.google.com/maps/dir/W+Adams+St+%26+S+Clark+St,+Chicago,+IL/Northwestern+University,+633+Clark+Street,+Evanston,+IL+60208/@41.9672743,-87.7225481,12z/data=!3m1!4b1!4m13!4m12!1m5!1m1!1s0x880e2cbc8bcec53b:0x72d2c7372d97283d!2m2!1d-87.6308023!2d41.8794067!1m5!1m1!1s0x880fd00b703e4c39:0x2c37b567fad56106!2m2!1d-87.675267!2d42.056459', 'googlemaps', 'Google Maps with directions'],
|
|
19
|
+
['http://instagram.com/p/ymwL5JAsw5/', 'instagram'],
|
|
20
|
+
['http://instagram.com/lukerague/', 'profile'],
|
|
21
|
+
['https://www.flickr.com/photos/critterseeker/16420145375', 'flickr'],
|
|
22
|
+
['https://flic.kr/p/u7SSxw', 'flickr'],
|
|
23
|
+
['https://www.documentcloud.org/documents/1377371-folketinget.html', 'documentcloud'],
|
|
24
|
+
['http://www.kidzone.ws/images-changed/sharks/head.jpg', 'image', 'JPG'],
|
|
25
|
+
['http://usatlife.files.wordpress.com/2014/06/groundhog-day-bill-murray-winter-never-going-to-end.gif', 'image', 'GIF'],
|
|
26
|
+
['http://pngimg.com/upload/banana_PNG842.png', 'image', 'PNG'],
|
|
27
|
+
['http://upload.wikimedia.org/wikipedia/commons/c/c2/Rocky_Mountains.jpeg', 'image', 'JPEG'],
|
|
28
|
+
['https://docs.google.com/document/d/1RvKYxHuwweIP8zRrnjad-0exVoZOUsSVgDYPp0J1mzY/edit?usp=sharing', 'googledocs'],
|
|
29
|
+
['http://stlab.adobe.com/wiki/images/d/d3/Test.pdf', 'pdf'],
|
|
30
|
+
['https://en.wikipedia.org/wiki/1997_International_Tennis_Championships_%E2%80%93_Doubles', 'wikipedia'],
|
|
31
|
+
['<iframe src="https://embed.spotify.com/?uri=https://play.spotify.com/artist/2iE18Oxc8YSumAU232n4rW" width="300" height="380" frameborder="0" allowtransparency="true"></iframe>', 'spotify', 'artist'],
|
|
32
|
+
['https://play.spotify.com/user/edvard_m/playlist/4xFSdiuP4gpR4wq2OghlOs', 'spotify', 'playlist'],
|
|
33
|
+
['https://play.spotify.com/track/5SdB3onMcO9ZBoKrdvCqhR', 'spotify', 'track'],
|
|
34
|
+
["<iframe src='https://cdn.knightlab.com/libs/timeline/latest/embed/index.html?source=10fFZXg4kioMz8uTVDZfawiJkgrWZxfJuziK1i1AaCrs&font=Bevan-PotanoSans&maptype=toner&lang=en&height=650' width='100%' height='650' frameborder='0'></iframe>", 'iframe'],
|
|
35
|
+
['<blockquote>This is a block quote.</blockquote>', 'blockquote'],
|
|
36
|
+
['http://2.bp.blogspot.com/-dxJbW0CG8Zs/TmkoMA5-cPI/AAAAAAAAAqw/fQpsz9GpFdo/s1600/voyage-dans-la-lune-1902-02-g.jpg', 'image'],
|
|
37
|
+
['https://de.wikipedia.org/wiki/Beryllium#/media/Datei:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg', 'wikipedia-image', 'regular wikipedia link'],
|
|
38
|
+
['https://commons.wikimedia.org/wiki/File:Hyperspace_Mountain_(27766070223).jpg', 'wikipedia-image', 'Wikimedia Commons link'],
|
|
39
|
+
['https://commons.wikimedia.org/wiki/Category:Airdancers#/media/File:Sky-dancer-japan.jpg', 'wikipedia-image', 'Wikimedia Commons link'],
|
|
40
|
+
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
test.each(TEST_CASES)("Ensure test URLs yield expected type", (url, expected_type) => {
|
|
44
|
+
const result = lookupMediaType({ url: url })
|
|
45
|
+
expect(result.type).toBe(expected_type)
|
|
46
|
+
|
|
47
|
+
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import { getJSON, fetchJSON } from "../../net/Net";
|
|
3
|
+
import TLError from "../../core/TLError";
|
|
4
|
+
import { unhtmlify } from "../../core/Util";
|
|
5
|
+
|
|
6
|
+
export function computeMediaId(url) {
|
|
7
|
+
|
|
8
|
+
if (url.match(/^.+#\/media\/.+/)) {
|
|
9
|
+
let parts = url.split('#')
|
|
10
|
+
let file_parts = parts[1].split(':') // the prefix is File in English but different on other language WPs
|
|
11
|
+
return `File:${file_parts[1]}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (url.match(/^.*commons.wikimedia.org\/wiki\/File:.+/)) {
|
|
15
|
+
let parts = url.split('/')
|
|
16
|
+
return parts[parts.length - 1]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Given a JSON response from a Wikimedia Commons API call, extract the base thumbnail URL
|
|
24
|
+
* and the page ID, which can be used to get alt text later.
|
|
25
|
+
* We assume that there is only one meaningful result in the object.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} j
|
|
28
|
+
*/
|
|
29
|
+
export function processImageInfoAPIJSON(j) {
|
|
30
|
+
let response = {}
|
|
31
|
+
if (j.query && j.query.pages) {
|
|
32
|
+
let page_ids = Object.keys(j.query.pages)
|
|
33
|
+
response['page_id'] = page_ids[0]
|
|
34
|
+
let data = j.query.pages[response['page_id']]
|
|
35
|
+
response['url'] = data.imageinfo[0].thumburl
|
|
36
|
+
if (data.entityterms && data.entityterms.label) {
|
|
37
|
+
response['label'] = data.entityterms.label[0]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return response
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default class WikipediaImage extends Media {
|
|
44
|
+
|
|
45
|
+
_loadMedia() {
|
|
46
|
+
var api_url,
|
|
47
|
+
image_width = this.options.width || 1000,
|
|
48
|
+
language_code = this.getLanguage().lang.toLowerCase(),
|
|
49
|
+
self = this;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Get Media ID
|
|
53
|
+
this.establishMediaID();
|
|
54
|
+
|
|
55
|
+
// API URL
|
|
56
|
+
api_url = `https://commons.wikimedia.org/w/api.php?action=query&titles=${this.media_id}&prop=imageinfo|entityterms&iiprop=url&&iiurlwidth=${image_width}&format=json&origin=*&wbetlanguage=${language_code}`
|
|
57
|
+
// API Call
|
|
58
|
+
getJSON(api_url, function(d) {
|
|
59
|
+
let response = processImageInfoAPIJSON(d)
|
|
60
|
+
if (response.url) {
|
|
61
|
+
self.base_image_url = response.url
|
|
62
|
+
self.page_id = response.page_id
|
|
63
|
+
if (!(self.data.alt) && response.label) {
|
|
64
|
+
self.data.alt = response.label
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!self.options.background) {
|
|
68
|
+
self.createMedia();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
self.onLoaded();
|
|
72
|
+
} else {
|
|
73
|
+
self.loadErrorDisplay(self._("wikipedia_image_load_err"));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} catch (e) {
|
|
77
|
+
self.loadErrorDisplay(self._(e.message_key));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Analyze the URL provided in the data object passed to the constructor to extract
|
|
83
|
+
* the Wikimedia commons "page" name
|
|
84
|
+
*/
|
|
85
|
+
establishMediaID() {
|
|
86
|
+
let media_id = computeMediaId(this.data.url)
|
|
87
|
+
if (media_id) {
|
|
88
|
+
this.media_id = media_id
|
|
89
|
+
} else {
|
|
90
|
+
throw new TLError(`Invalid Wikipedia Image URL`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
createMedia() {
|
|
95
|
+
var self = this;
|
|
96
|
+
|
|
97
|
+
// Photo
|
|
98
|
+
this._el.content_item = this.domCreate("img", "tl-media-item tl-media-image tl-media-wikipedia-image tl-media-shadow", this._el.content);
|
|
99
|
+
|
|
100
|
+
if (this.data.alt) {
|
|
101
|
+
this._el.content_item.alt = this.data.alt;
|
|
102
|
+
} else if (this.page_id) {
|
|
103
|
+
let wikibase_id = `M${this.page_id}`
|
|
104
|
+
let wikibase_url = `https://commons.wikimedia.org/w/api.php?action=wbgetentities&format=json&ids=${wikibase_id}&format=json&origin=*`
|
|
105
|
+
fetchJSON(wikibase_url).then(j => {
|
|
106
|
+
if (j.entities && j.entities[wikibase_id]) {
|
|
107
|
+
let labels = j.entities[wikibase_id].labels
|
|
108
|
+
let language_code = self.getLanguage().lang.toLowerCase()
|
|
109
|
+
let label = null
|
|
110
|
+
if (labels[language_code]) {
|
|
111
|
+
label = labels[language_code]
|
|
112
|
+
} else if (language_code.length > 2 && labels[language_code.substr(0, 2)]) {
|
|
113
|
+
label = labels[language_code.substr(0, 2)]
|
|
114
|
+
} else if (labels['en']) {
|
|
115
|
+
label = labels['en']
|
|
116
|
+
}
|
|
117
|
+
if (label) {
|
|
118
|
+
console.log(`wikibase_id: ${self.media_id} alt ${label.value}`)
|
|
119
|
+
self.data.alt = label.value
|
|
120
|
+
self._el.content_item.alt = self.data.alt
|
|
121
|
+
} else {
|
|
122
|
+
console.log(`wikibase_id: ${self.media_id} ain't got no alt`)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this.data.title) {
|
|
129
|
+
this._el.content_item.title = this.data.title;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Media Loaded Event
|
|
133
|
+
this._el.content_item.addEventListener('load', function(e) {
|
|
134
|
+
self.onMediaLoaded();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Set Image Source
|
|
138
|
+
this._el.content_item.src = this.getImageURL(this.options.width, this.options.height);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_getImageURL(w, h) {
|
|
142
|
+
if (w && this.base_image_url) {
|
|
143
|
+
let match = this.base_image_url.match(/(\/\d+px-)/)
|
|
144
|
+
if (match) {
|
|
145
|
+
return this.base_image_url.replace(match[1], `/${w}px-`) // Wikipedia will autoscale the image for us
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// they don't always have that pattern!
|
|
149
|
+
return this.base_image_url
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
*
|
|
156
|
+
*
|
|
157
|
+
* https://commons.wikimedia.org/w/api.php?action=query&titles=File:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg&prop=imageinfo&iiprop=url&&iiurlwidth=1000
|
|
158
|
+
* https://commons.wikimedia.org/wiki/File:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg
|
|
159
|
+
*/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { computeMediaId, processImageInfoAPIJSON } from "../WikipediaImage"
|
|
2
|
+
import { test, expect } from "@jest/globals"
|
|
3
|
+
|
|
4
|
+
const TEST_CASES = [
|
|
5
|
+
['https://commons.wikimedia.org/wiki/https://commons.wikimedia.org/wiki/File:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg', 'File:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg'],
|
|
6
|
+
['https://en.wikipedia.org/wiki/Main_Page#/media/File:Symphyotrichum_kentuckiense_233619783_(inflor).jpg', 'File:Symphyotrichum_kentuckiense_233619783_(inflor).jpg'],
|
|
7
|
+
['https://es.wikipedia.org/wiki/Portal:M%C3%BAsica#/media/Archivo:David-Bowie_Chicago_2002-08-08_photoby_Adam-Bielawski-cropped.jpg', 'File:David-Bowie_Chicago_2002-08-08_photoby_Adam-Bielawski-cropped.jpg'],
|
|
8
|
+
['https://commons.wikimedia.org/wiki/Category:Airdancers#/media/File:Sky-dancer-japan.jpg', 'File:Sky-dancer-japan.jpg']
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const IMAGE_INFO_JSON_RESPONSE = {
|
|
12
|
+
"batchcomplete": "",
|
|
13
|
+
"query": {
|
|
14
|
+
"normalized": [{
|
|
15
|
+
"from": "File:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg",
|
|
16
|
+
"to": "File:Beryl-Quartz-Emerald-Zambia-33mm 0885.jpg"
|
|
17
|
+
}],
|
|
18
|
+
"pages": {
|
|
19
|
+
"10450749": {
|
|
20
|
+
"pageid": 10450749,
|
|
21
|
+
"ns": 6,
|
|
22
|
+
"title": "File:Beryl-Quartz-Emerald-Zambia-33mm 0885.jpg",
|
|
23
|
+
"imagerepository": "local",
|
|
24
|
+
"imageinfo": [{
|
|
25
|
+
"thumburl": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg/100px-Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg",
|
|
26
|
+
"thumbwidth": 100,
|
|
27
|
+
"thumbheight": 121,
|
|
28
|
+
"thumbmime": "image/jpeg",
|
|
29
|
+
"responsiveUrls": { "1.5": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg/150px-Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg", "2": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg/200px-Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg" },
|
|
30
|
+
"url": "https://upload.wikimedia.org/wikipedia/commons/d/df/Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg",
|
|
31
|
+
"descriptionurl": "https://commons.wikimedia.org/wiki/File:Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg",
|
|
32
|
+
"descriptionshorturl": "https://commons.wikimedia.org/w/index.php?curid=10450749",
|
|
33
|
+
"mime": "image/jpeg"
|
|
34
|
+
}]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test.each(TEST_CASES)("Ensure correct file identifier is extracted", (url, file_id) =>
|
|
41
|
+
expect(computeMediaId(url)).toBe(file_id)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
test("get image info data", () => {
|
|
45
|
+
let response = processImageInfoAPIJSON(IMAGE_INFO_JSON_RESPONSE)
|
|
46
|
+
expect(response.url).toBe("https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg/100px-Beryl-Quartz-Emerald-Zambia-33mm_0885.jpg")
|
|
47
|
+
expect(response.page_id).toBe("10450749")
|
|
48
|
+
})
|
package/src/js/net/Net.js
CHANGED
|
@@ -1477,7 +1477,7 @@ window.$ === undefined && (window.$ = Zepto)
|
|
|
1477
1477
|
|
|
1478
1478
|
/**
|
|
1479
1479
|
* Add a promisified option to better handle cases where we need the data
|
|
1480
|
-
*
|
|
1480
|
+
* asynchronously.
|
|
1481
1481
|
* @param {String} url
|
|
1482
1482
|
*/
|
|
1483
1483
|
$.fetchJSON = function(url) {
|
|
@@ -39,7 +39,7 @@ export class SlideNav {
|
|
|
39
39
|
mergeData(this.data, data);
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
this._el.container = DOM.
|
|
42
|
+
this._el.container = DOM.createButton("tl-slidenav-" + this.options.direction);
|
|
43
43
|
|
|
44
44
|
if (Browser.mobile) {
|
|
45
45
|
this._el.container.setAttribute("ontouchstart"," ");
|
package/src/js/ui/MenuBar.js
CHANGED
|
@@ -97,10 +97,10 @@ export class MenuBar {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
const firstTick = minor_ticks[0];
|
|
100
|
-
const firstYear =
|
|
100
|
+
const firstYear = firstTick.date.getFullYear();
|
|
101
101
|
|
|
102
102
|
const lastTick = minor_ticks[minor_ticks.length - 1];
|
|
103
|
-
const lastYear =
|
|
103
|
+
const lastYear = lastTick.date.getFullYear();
|
|
104
104
|
|
|
105
105
|
this.data.visible_ticks_dates = {
|
|
106
106
|
start: firstYear,
|
|
@@ -110,10 +110,6 @@ export class MenuBar {
|
|
|
110
110
|
this._updateZoomAriaLabels()
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
_getTickYear(tick) {
|
|
114
|
-
return tick.date.data.date_obj.getFullYear();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
113
|
setSticky(y) {
|
|
118
114
|
this.options.menubar_default_y = y;
|
|
119
115
|
}
|
|
@@ -158,10 +154,10 @@ export class MenuBar {
|
|
|
158
154
|
_initLayout() {
|
|
159
155
|
|
|
160
156
|
// Create Layout
|
|
161
|
-
this._el.button_zoomin = DOM.
|
|
162
|
-
this._el.button_zoomout = DOM.
|
|
163
|
-
this._el.button_forwardtoend = DOM.
|
|
164
|
-
this._el.button_backtostart = DOM.
|
|
157
|
+
this._el.button_zoomin = DOM.createButton('tl-menubar-button', this._el.container);
|
|
158
|
+
this._el.button_zoomout = DOM.createButton('tl-menubar-button', this._el.container);
|
|
159
|
+
this._el.button_forwardtoend = DOM.createButton('tl-menubar-button', this._el.container);
|
|
160
|
+
this._el.button_backtostart = DOM.createButton('tl-menubar-button', this._el.container);
|
|
165
161
|
|
|
166
162
|
if (Browser.mobile) {
|
|
167
163
|
this._el.container.setAttribute("ontouchstart", " ");
|