@projektemacher/storymapjs 0.10.5 → 0.10.7
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 +11 -0
- package/dist/assets/{demo-CVQ7kFek.js → demo-D6cTmzn6.js} +1 -1
- package/dist/assets/{harness-C-VZV-ou.js → harness-BPFlzutp.js} +1 -1
- package/dist/assets/main-Y-MYUSJ4.js +17 -0
- package/dist/css/fonts/font.oldstandard.css +14 -32
- package/dist/demo.html +2 -2
- package/dist/docs/migration.html +1 -1
- package/dist/embed/index.html +129 -4
- package/dist/harness.html +2 -2
- package/dist/index.html +17 -0
- package/dist/js/storymap.d.ts +126 -7
- package/dist/js/storymap.js +4518 -3613
- package/dist/js/storymap.js.map +1 -1
- package/docs/migration-from-knightlab.md +139 -0
- package/docs/storymap-as-iiif-manifest.md +510 -0
- package/package.json +6 -3
- package/src/animation/easings.ts +18 -0
- package/src/animation/morpheus.d.ts +41 -0
- package/src/core/Browser.ts +19 -0
- package/src/core/Load.ts +180 -0
- package/src/core/Util.ts +354 -0
- package/src/core/mixins.ts +171 -0
- package/src/dom/Dom.ts +31 -0
- package/src/dom/DomEvent.ts +61 -0
- package/src/globals.d.ts +16 -0
- package/src/language/Language.ts +107 -0
- package/src/language/locale/be.json +18 -0
- package/src/language/locale/bg.json +18 -0
- package/src/language/locale/cs.json +18 -0
- package/src/language/locale/de.json +20 -0
- package/src/language/locale/el.json +18 -0
- package/src/language/locale/en.json +28 -0
- package/src/language/locale/es.json +18 -0
- package/src/language/locale/et.json +18 -0
- package/src/language/locale/fr.json +18 -0
- package/src/language/locale/he.json +18 -0
- package/src/language/locale/hu.json +18 -0
- package/src/language/locale/is.json +18 -0
- package/src/language/locale/it.json +18 -0
- package/src/language/locale/jp.json +18 -0
- package/src/language/locale/ko.json +18 -0
- package/src/language/locale/nl.json +18 -0
- package/src/language/locale/nn.json +18 -0
- package/src/language/locale/no.json +18 -0
- package/src/language/locale/pl.json +18 -0
- package/src/language/locale/pt.json +18 -0
- package/src/language/locale/ru.json +18 -0
- package/src/language/locale/sk.json +18 -0
- package/src/language/locale/sr.json +17 -0
- package/src/language/locale/sv.json +18 -0
- package/src/language/locale/tr.json +18 -0
- package/src/language/locale/uk.json +18 -0
- package/src/language/locale/ur.json +18 -0
- package/src/language/locale/zh-cn.json +18 -0
- package/src/language/locale/zh-tw.json +18 -0
- package/src/main.ts +51 -0
- package/src/map/Map.ts +717 -0
- package/src/map/MapMarker.ts +149 -0
- package/src/map/openlayers/Map.OpenLayers.ts +1879 -0
- package/src/map/openlayers/MapMarker.OpenLayers.ts +176 -0
- package/src/map/openlayers/zoomifyTiles.ts +28 -0
- package/src/map/types.ts +17 -0
- package/src/media/EmbedUtil.ts +168 -0
- package/src/media/Media.ts +409 -0
- package/src/media/MediaType.ts +163 -0
- package/src/media/types/Audio.ts +87 -0
- package/src/media/types/Blockquote.ts +40 -0
- package/src/media/types/DailyMotion.ts +58 -0
- package/src/media/types/DocumentCloud.ts +41 -0
- package/src/media/types/Facebook.ts +41 -0
- package/src/media/types/Flickr.ts +115 -0
- package/src/media/types/GoogleDoc.ts +59 -0
- package/src/media/types/IFrame.ts +51 -0
- package/src/media/types/Image.ts +49 -0
- package/src/media/types/Juxtapose.ts +32 -0
- package/src/media/types/SoundCloud.ts +77 -0
- package/src/media/types/Text.ts +172 -0
- package/src/media/types/Twitter.ts +96 -0
- package/src/media/types/Video.ts +84 -0
- package/src/media/types/Vimeo.ts +65 -0
- package/src/media/types/Website.ts +36 -0
- package/src/media/types/Wikipedia.ts +124 -0
- package/src/media/types/YouTube.ts +192 -0
- package/src/scss/Typography.scss +237 -0
- package/src/scss/VCO.StoryMap.Dark.scss +138 -0
- package/src/scss/VCO.StoryMap.scss +164 -0
- package/src/scss/Variables.Dark.scss +56 -0
- package/src/scss/Variables.scss +47 -0
- package/src/scss/core/Mixins.scss +112 -0
- package/src/scss/core/Reset.scss +206 -0
- package/src/scss/fonts/_font.base.scss +113 -0
- package/src/scss/fonts/font.abril-droidsans.scss +75 -0
- package/src/scss/fonts/font.amatic-andika.scss +73 -0
- package/src/scss/fonts/font.bitter-raleway.scss +86 -0
- package/src/scss/fonts/font.clicker-garamond.scss +92 -0
- package/src/scss/fonts/font.dancing-ledger.scss +74 -0
- package/src/scss/fonts/font.default.scss +9 -0
- package/src/scss/fonts/font.fjalla-average.scss +80 -0
- package/src/scss/fonts/font.georgia-helvetica.scss +58 -0
- package/src/scss/fonts/font.knightlab.scss +97 -0
- package/src/scss/fonts/font.lustria-lato.scss +80 -0
- package/src/scss/fonts/font.medula-lato.scss +73 -0
- package/src/scss/fonts/font.oldstandard.scss +88 -0
- package/src/scss/fonts/font.opensans-gentiumbook.scss +80 -0
- package/src/scss/fonts/font.playfair-faunaone.scss +86 -0
- package/src/scss/fonts/font.playfair.scss +85 -0
- package/src/scss/fonts/font.pt.scss +90 -0
- package/src/scss/fonts/font.roboto-megrim.scss +73 -0
- package/src/scss/fonts/font.rufina-sintony.scss +73 -0
- package/src/scss/fonts/font.ubuntu.scss +78 -0
- package/src/scss/fonts/font.unicaone-vollkorn.scss +85 -0
- package/src/scss/icons/Icons.scss +239 -0
- package/src/scss/map/VCO.ImageMarker.scss +23 -0
- package/src/scss/map/VCO.Map.scss +21 -0
- package/src/scss/map/VCO.MapMarker.scss +147 -0
- package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +126 -0
- package/src/scss/media/VCO.Media.scss +222 -0
- package/src/scss/media/types/VCO.Media.Blockquote.scss +76 -0
- package/src/scss/media/types/VCO.Media.IFrame.scss +11 -0
- package/src/scss/media/types/VCO.Media.Image.scss +13 -0
- package/src/scss/media/types/VCO.Media.Instagram.scss +7 -0
- package/src/scss/media/types/VCO.Media.Profile.scss +5 -0
- package/src/scss/media/types/VCO.Media.Text.scss +90 -0
- package/src/scss/media/types/VCO.Media.Twitter.scss +87 -0
- package/src/scss/media/types/VCO.Media.Wikipedia.scss +64 -0
- package/src/scss/site/site.scss +748 -0
- package/src/scss/slider/VCO.Slide.scss +447 -0
- package/src/scss/slider/VCO.SlideNav.scss +338 -0
- package/src/scss/slider/VCO.StorySlider.scss +64 -0
- package/src/scss/snapmap/VCO.SnapMap.scss +103 -0
- package/src/scss/ui/VCO.Consent.scss +77 -0
- package/src/scss/ui/VCO.MenuBar.Button.scss +99 -0
- package/src/scss/ui/VCO.MenuBar.scss +20 -0
- package/src/scss/ui/VCO.Message.scss +104 -0
- package/src/site/site.ts +87 -0
- package/src/slider/Slide.ts +460 -0
- package/src/slider/SlideNav.ts +212 -0
- package/src/slider/StorySlider.ts +722 -0
- package/src/storymap/Consent.ts +146 -0
- package/src/storymap/StoryMap.ts +1108 -0
- package/src/storymap/iiif.ts +386 -0
- package/src/storymap/validate.ts +184 -0
- package/src/types.ts +297 -0
- package/src/ui/Draggable.ts +374 -0
- package/src/ui/MenuBar.ts +323 -0
- package/src/ui/Message.ts +118 -0
- package/src/ui/Swipable.ts +388 -0
- package/dist/assets/main-DwbXrrYn.js +0 -17
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
import { ratio } from "../../core/Util";
|
|
5
|
+
|
|
6
|
+
/* Media.DailyMotion
|
|
7
|
+
================================================== */
|
|
8
|
+
|
|
9
|
+
export default class DailyMotion extends Media {
|
|
10
|
+
declare "media_id": string;
|
|
11
|
+
|
|
12
|
+
/* Load the media
|
|
13
|
+
================================================== */
|
|
14
|
+
_loadMedia() {
|
|
15
|
+
// Loading Message
|
|
16
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
17
|
+
|
|
18
|
+
// Create Dom element
|
|
19
|
+
this._el.content_item = Dom.create(
|
|
20
|
+
"div",
|
|
21
|
+
"vco-media-item vco-media-iframe vco-media-dailymotion",
|
|
22
|
+
this._el.content,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Get Media ID
|
|
26
|
+
const id = this.data.url.match("video")
|
|
27
|
+
? this.data.url.split("video/")[1]
|
|
28
|
+
: this.data.url.split("embed/")[1];
|
|
29
|
+
if (!id) {
|
|
30
|
+
throw new Error("Invalid DailyMotion URL");
|
|
31
|
+
}
|
|
32
|
+
this.media_id = id.split(/[?&]/)[0];
|
|
33
|
+
|
|
34
|
+
// API URL
|
|
35
|
+
const api_url =
|
|
36
|
+
"https://www.dailymotion.com/embed/video/" + this.media_id + "?api=postMessage";
|
|
37
|
+
|
|
38
|
+
// API Call
|
|
39
|
+
this._el.content_item.innerHTML =
|
|
40
|
+
"<iframe autostart='false' frameborder='0' width='100%' height='100%' src='" +
|
|
41
|
+
api_url +
|
|
42
|
+
"'></iframe>";
|
|
43
|
+
|
|
44
|
+
// After Loaded
|
|
45
|
+
this.onLoaded();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Update Media Display
|
|
49
|
+
_updateMediaDisplay() {
|
|
50
|
+
this._el.content_item.style.height =
|
|
51
|
+
ratio.r16_9({ w: this._el.content_item.offsetWidth }) + "px";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_stopMedia() {
|
|
55
|
+
const iframe = this._el.content_item?.querySelector("iframe") as HTMLIFrameElement | null;
|
|
56
|
+
iframe?.contentWindow?.postMessage('{"command":"pause","parameters":[]}', "*");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
import { validateWebURL } from "../EmbedUtil";
|
|
5
|
+
|
|
6
|
+
/* Media.DocumentCloud
|
|
7
|
+
Embeds a DocumentCloud document viewer (issue #437)
|
|
8
|
+
================================================= */
|
|
9
|
+
|
|
10
|
+
export default class DocumentCloud extends Media {
|
|
11
|
+
/* Load the media
|
|
12
|
+
================================================== */
|
|
13
|
+
_loadMedia() {
|
|
14
|
+
// Loading Message
|
|
15
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
16
|
+
|
|
17
|
+
// Create Dom element
|
|
18
|
+
this._el.content_item = Dom.create(
|
|
19
|
+
"div",
|
|
20
|
+
"vco-media-item vco-media-iframe vco-media-documentcloud",
|
|
21
|
+
this._el.content,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// the canonical document URL renders the standalone viewer; rebuilt
|
|
25
|
+
// from a validated src to keep stored XSS out of the storymap JSON
|
|
26
|
+
const src = validateWebURL(this.data.url);
|
|
27
|
+
if (!src) {
|
|
28
|
+
this.loadErrorDisplay("Invalid URL.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const iframe = document.createElement("iframe");
|
|
32
|
+
iframe.setAttribute("src", src);
|
|
33
|
+
this._el.content_item.appendChild(iframe);
|
|
34
|
+
this.onLoaded();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Update Media Display
|
|
38
|
+
_updateMediaDisplay() {
|
|
39
|
+
this._el.content_item.style.height = this.options.height + "px";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
|
|
5
|
+
/* Media.Facebook
|
|
6
|
+
Embeds a Facebook post, video or reel via the plugins endpoint
|
|
7
|
+
(issue #272)
|
|
8
|
+
================================================== */
|
|
9
|
+
|
|
10
|
+
export default class Facebook extends Media {
|
|
11
|
+
/* Load the media
|
|
12
|
+
================================================== */
|
|
13
|
+
_loadMedia() {
|
|
14
|
+
// Loading Message
|
|
15
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
16
|
+
|
|
17
|
+
// Create Dom element
|
|
18
|
+
this._el.content_item = Dom.create(
|
|
19
|
+
"div",
|
|
20
|
+
"vco-media-item vco-media-iframe vco-media-facebook",
|
|
21
|
+
this._el.content,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const url = this.data.url;
|
|
25
|
+
const href = encodeURIComponent(url);
|
|
26
|
+
const embed =
|
|
27
|
+
/\/videos?\//.test(url) || /\/watch/.test(url) || /\/reel\//.test(url)
|
|
28
|
+
? `https://www.facebook.com/plugins/video.php?href=${href}&show_text=true`
|
|
29
|
+
: `https://www.facebook.com/plugins/post.php?href=${href}&show_text=true`;
|
|
30
|
+
this._el.content_item.innerHTML =
|
|
31
|
+
`<iframe src="${embed}" scrolling="no" frameborder="0" ` +
|
|
32
|
+
`allowfullscreen="true" allow="autoplay; clipboard-write; encrypted-media; ` +
|
|
33
|
+
`picture-in-picture; web-share"></iframe>`;
|
|
34
|
+
this.onLoaded();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Update Media Display
|
|
38
|
+
_updateMediaDisplay() {
|
|
39
|
+
this._el.content_item.style.height = this.options.height + "px";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
|
|
5
|
+
/* Media.Flickr
|
|
6
|
+
|
|
7
|
+
================================================== */
|
|
8
|
+
|
|
9
|
+
export default class Flickr extends Media {
|
|
10
|
+
declare "media_id": string;
|
|
11
|
+
|
|
12
|
+
/* Load the media
|
|
13
|
+
================================================== */
|
|
14
|
+
_loadMedia() {
|
|
15
|
+
// Loading Message
|
|
16
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
17
|
+
|
|
18
|
+
// Create Dom element
|
|
19
|
+
this._el.content_item = Dom.create(
|
|
20
|
+
"img",
|
|
21
|
+
"vco-media-item vco-media-image vco-media-flickr vco-media-shadow",
|
|
22
|
+
this._el.content,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Media Loaded Event
|
|
26
|
+
this._el.content_item.addEventListener("load", (e) => {
|
|
27
|
+
this.onMediaLoaded();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Get Media ID
|
|
31
|
+
this.establishMediaID();
|
|
32
|
+
|
|
33
|
+
const api_url =
|
|
34
|
+
"https://api.flickr.com/services/rest/?method=flickr.photos.getSizes&api_key=" +
|
|
35
|
+
this.options.api_key_flickr +
|
|
36
|
+
"&photo_id=" +
|
|
37
|
+
this.media_id +
|
|
38
|
+
"&format=json&nojsoncallback=1";
|
|
39
|
+
|
|
40
|
+
void this._fetchSizes(api_url);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async _fetchSizes(api_url: string) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(api_url);
|
|
46
|
+
const d = (await response.json()) as { stat: string };
|
|
47
|
+
if (d.stat === "ok") {
|
|
48
|
+
this.createMedia(d);
|
|
49
|
+
} else {
|
|
50
|
+
this.loadErrorDisplay("Photo not found or private.");
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
this.loadErrorDisplay("Photo not found or private.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
establishMediaID() {
|
|
58
|
+
const marker = "flickr.com/photos/";
|
|
59
|
+
const idx = this.data.url.indexOf(marker);
|
|
60
|
+
if (idx === -1) {
|
|
61
|
+
throw "Invalid Flickr URL";
|
|
62
|
+
}
|
|
63
|
+
const pos = idx + marker.length;
|
|
64
|
+
this.media_id = this.data.url.slice(pos).split("/")[1];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
createMedia(d: unknown) {
|
|
68
|
+
const data = d as { sizes: { size: { label: string; source: string }[] } };
|
|
69
|
+
const best_size = this.sizes(this.options.height);
|
|
70
|
+
const sizes = data?.sizes?.size;
|
|
71
|
+
if (!sizes || !sizes.length) {
|
|
72
|
+
this.loadErrorDisplay("Photo not found or private.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let size = sizes[Math.max(0, sizes.length - 2)].source;
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
78
|
+
if (sizes[i].label === best_size) {
|
|
79
|
+
size = sizes[i].source;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Set Image Source
|
|
84
|
+
(this._el.content_item as HTMLImageElement).src = size;
|
|
85
|
+
|
|
86
|
+
// After Loaded
|
|
87
|
+
this.onLoaded();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
sizes(s: number): string {
|
|
91
|
+
let _size;
|
|
92
|
+
|
|
93
|
+
if (s <= 75) {
|
|
94
|
+
if (s <= 0) {
|
|
95
|
+
_size = "Large";
|
|
96
|
+
} else {
|
|
97
|
+
_size = "Thumbnail";
|
|
98
|
+
}
|
|
99
|
+
} else if (s <= 180) {
|
|
100
|
+
_size = "Small";
|
|
101
|
+
} else if (s <= 240) {
|
|
102
|
+
_size = "Small 320";
|
|
103
|
+
} else if (s <= 375) {
|
|
104
|
+
_size = "Medium";
|
|
105
|
+
} else if (s <= 480) {
|
|
106
|
+
_size = "Medium 640";
|
|
107
|
+
} else if (s <= 600) {
|
|
108
|
+
_size = "Large";
|
|
109
|
+
} else {
|
|
110
|
+
_size = "Large";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return _size;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
import { validateWebURL } from "../EmbedUtil";
|
|
5
|
+
|
|
6
|
+
/* Media.GoogleDoc
|
|
7
|
+
|
|
8
|
+
================================================= */
|
|
9
|
+
|
|
10
|
+
export default class GoogleDoc extends Media {
|
|
11
|
+
declare "media_id": string;
|
|
12
|
+
|
|
13
|
+
/* Load the media
|
|
14
|
+
================================================== */
|
|
15
|
+
_loadMedia() {
|
|
16
|
+
// Loading Message
|
|
17
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
18
|
+
|
|
19
|
+
// Create Dom element
|
|
20
|
+
this._el.content_item = Dom.create(
|
|
21
|
+
"div",
|
|
22
|
+
"vco-media-item vco-media-iframe",
|
|
23
|
+
this._el.content,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Get Media ID
|
|
27
|
+
this.media_id = this.data.url;
|
|
28
|
+
|
|
29
|
+
// Rebuild a clean iframe from a validated src: injecting the raw
|
|
30
|
+
// URL into markup would allow stored XSS via the storymap JSON
|
|
31
|
+
const src = validateWebURL(this.media_id);
|
|
32
|
+
if (!src) {
|
|
33
|
+
this.loadErrorDisplay("Invalid URL.");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const iframe = document.createElement("iframe");
|
|
37
|
+
iframe.className = "doc";
|
|
38
|
+
iframe.setAttribute("frameborder", "0");
|
|
39
|
+
iframe.setAttribute("width", "100%");
|
|
40
|
+
iframe.setAttribute("height", "100%");
|
|
41
|
+
if (this.media_id.match(/docs.google.com/i)) {
|
|
42
|
+
iframe.setAttribute("src", src + "&embedded=true");
|
|
43
|
+
} else {
|
|
44
|
+
iframe.setAttribute(
|
|
45
|
+
"src",
|
|
46
|
+
"http://docs.google.com/viewer?url=" + encodeURIComponent(src) + "&embedded=true",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
this._el.content_item.appendChild(iframe);
|
|
50
|
+
|
|
51
|
+
// After Loaded
|
|
52
|
+
this.onLoaded();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update Media Display
|
|
56
|
+
_updateMediaDisplay() {
|
|
57
|
+
this._el.content_item.style.height = this.options.height + "px";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
import { buildIframe } from "../EmbedUtil";
|
|
5
|
+
|
|
6
|
+
/* Media.IFrame
|
|
7
|
+
================================================== */
|
|
8
|
+
|
|
9
|
+
export default class IFrame extends Media {
|
|
10
|
+
declare "media_id": string;
|
|
11
|
+
|
|
12
|
+
/* Load the media
|
|
13
|
+
================================================== */
|
|
14
|
+
_loadMedia() {
|
|
15
|
+
// Loading Message
|
|
16
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
17
|
+
|
|
18
|
+
// Create Dom element
|
|
19
|
+
this._el.content_item = Dom.create(
|
|
20
|
+
"div",
|
|
21
|
+
"vco-media-item vco-media-iframe",
|
|
22
|
+
this._el.content,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Get Media ID
|
|
26
|
+
this.media_id = this.data.url;
|
|
27
|
+
|
|
28
|
+
// The url field holds a user-pasted embed snippet. Rebuild a
|
|
29
|
+
// clean iframe from its src instead of injecting the raw markup,
|
|
30
|
+
// which would allow stored XSS via the storymap JSON.
|
|
31
|
+
const iframe = buildIframe(this.media_id);
|
|
32
|
+
|
|
33
|
+
if (!iframe) {
|
|
34
|
+
this.loadErrorDisplay(
|
|
35
|
+
"Invalid embed code. Paste an iframe embed code with an http(s) source URL.",
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// API Call
|
|
41
|
+
this._el.content_item.appendChild(iframe);
|
|
42
|
+
|
|
43
|
+
// After Loaded
|
|
44
|
+
this.onLoaded();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Update Media Display
|
|
48
|
+
_updateMediaDisplay() {
|
|
49
|
+
this._el.content_item.style.height = this.options.height + "px";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
|
|
5
|
+
/* Media.Image
|
|
6
|
+
Produces image assets.
|
|
7
|
+
Takes a data object and populates a dom object
|
|
8
|
+
================================================== */
|
|
9
|
+
|
|
10
|
+
export default class Image extends Media {
|
|
11
|
+
/* Load the media
|
|
12
|
+
================================================== */
|
|
13
|
+
_loadMedia() {
|
|
14
|
+
// Loading Message
|
|
15
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
16
|
+
|
|
17
|
+
// Link
|
|
18
|
+
if (this.data.link) {
|
|
19
|
+
this._el.content_link = Dom.create("a", "", this._el.content);
|
|
20
|
+
const content_link = this._el.content_link as HTMLAnchorElement;
|
|
21
|
+
content_link.href = this.data.link;
|
|
22
|
+
content_link.target = "_blank";
|
|
23
|
+
this._el.content_item = Dom.create(
|
|
24
|
+
"img",
|
|
25
|
+
"vco-media-item vco-media-image vco-media-shadow",
|
|
26
|
+
this._el.content_link,
|
|
27
|
+
);
|
|
28
|
+
} else {
|
|
29
|
+
this._el.content_item = Dom.create(
|
|
30
|
+
"img",
|
|
31
|
+
"vco-media-item vco-media-image vco-media-shadow",
|
|
32
|
+
this._el.content,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Media Loaded Event
|
|
37
|
+
this._el.content_item.addEventListener("load", (e) => {
|
|
38
|
+
this.onMediaLoaded();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
(this._el.content_item as HTMLImageElement).src = this.data.url;
|
|
42
|
+
|
|
43
|
+
this.onLoaded();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_updateMediaDisplay(layout?: string) {
|
|
47
|
+
// modern browsers size media correctly without engine-specific fixes
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
|
|
5
|
+
/* Media.Juxtapose
|
|
6
|
+
Embeds a JuxtaposeJS before/after slider (issue #360)
|
|
7
|
+
================================================== */
|
|
8
|
+
|
|
9
|
+
export default class Juxtapose extends Media {
|
|
10
|
+
/* Load the media
|
|
11
|
+
================================================== */
|
|
12
|
+
_loadMedia() {
|
|
13
|
+
// Loading Message
|
|
14
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
15
|
+
|
|
16
|
+
// Create Dom element
|
|
17
|
+
this._el.content_item = Dom.create(
|
|
18
|
+
"div",
|
|
19
|
+
"vco-media-item vco-media-iframe vco-media-juxtapose",
|
|
20
|
+
this._el.content,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// published Juxtapose URLs are iframe-ready embeds
|
|
24
|
+
this._el.content_item.innerHTML = `<iframe src="${this.data.url}" />`;
|
|
25
|
+
this.onLoaded();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Update Media Display
|
|
29
|
+
_updateMediaDisplay() {
|
|
30
|
+
this._el.content_item.style.height = this.options.height + "px";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Media } from "../Media";
|
|
2
|
+
import Dom from "../../dom/Dom";
|
|
3
|
+
import { Language } from "../../language/Language";
|
|
4
|
+
|
|
5
|
+
/* Media.SoundCloud
|
|
6
|
+
================================================== */
|
|
7
|
+
|
|
8
|
+
/* Minimal surface of the SoundCloud widget API used here. */
|
|
9
|
+
interface SoundCloudWidget {
|
|
10
|
+
pause: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default class SoundCloud extends Media {
|
|
14
|
+
declare "media_id": string;
|
|
15
|
+
declare "soundCloudCreated": boolean;
|
|
16
|
+
declare "widget": SoundCloudWidget;
|
|
17
|
+
|
|
18
|
+
/* Load the media
|
|
19
|
+
================================================== */
|
|
20
|
+
async _loadMedia() {
|
|
21
|
+
// Loading Message
|
|
22
|
+
this.message.updateMessage(Language.messages.loading + " " + this.options.media_name);
|
|
23
|
+
|
|
24
|
+
// Create Dom element
|
|
25
|
+
this._el.content_item = Dom.create(
|
|
26
|
+
"div",
|
|
27
|
+
"vco-media-item vco-media-iframe vco-media-soundcloud vco-media-shadow",
|
|
28
|
+
this._el.content,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// Get Media ID
|
|
32
|
+
this.media_id = this.data.url;
|
|
33
|
+
|
|
34
|
+
// API URL
|
|
35
|
+
const api_url = "https://soundcloud.com/oembed?url=" + this.media_id + "&format=json";
|
|
36
|
+
|
|
37
|
+
// API Call
|
|
38
|
+
try {
|
|
39
|
+
const r = await fetch(api_url);
|
|
40
|
+
const d = await r.json();
|
|
41
|
+
await this.loadScript("https://w.soundcloud.com/player/api.js");
|
|
42
|
+
//load soundcloud api for pausing.
|
|
43
|
+
this.createMedia(d);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// aborted loads stay silent (the visitor navigated away);
|
|
46
|
+
// real failures show the media error display
|
|
47
|
+
if ((err as DOMException)?.name !== "AbortError") {
|
|
48
|
+
this.loadErrorDisplay("Unable to load this track.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
createMedia(d: unknown) {
|
|
54
|
+
const data = d as { html: string };
|
|
55
|
+
if (!data?.html) {
|
|
56
|
+
this.loadErrorDisplay("Unable to load this track.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this._el.content_item.innerHTML = data.html;
|
|
60
|
+
|
|
61
|
+
this.soundCloudCreated = true;
|
|
62
|
+
|
|
63
|
+
const sc = SC as { Widget: (iframe: Element | null) => SoundCloudWidget };
|
|
64
|
+
// per-instance widget: a global would be overwritten by every
|
|
65
|
+
// SoundCloud slide and _stopMedia would pause the wrong one
|
|
66
|
+
this.widget = sc.Widget(this._el.content_item.querySelector("iframe"));
|
|
67
|
+
|
|
68
|
+
// After Loaded
|
|
69
|
+
this.onLoaded();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_stopMedia() {
|
|
73
|
+
if (this.soundCloudCreated) {
|
|
74
|
+
this.widget?.pause();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|