@schukai/monster 4.150.8 → 4.151.0
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/package.json +1 -1
- package/source/components/content/style/viewer.pcss +46 -1
- package/source/components/content/stylesheet/viewer.mjs +1 -1
- package/source/components/content/viewer/html.mjs +2 -2
- package/source/components/content/viewer/message.mjs +2 -2
- package/source/components/content/viewer/sanitize-html.mjs +194 -0
- package/source/components/content/viewer.mjs +237 -192
- package/source/components/form/select.mjs +413 -53
- package/source/components/form/style/select.pcss +40 -1
- package/source/components/form/stylesheet/select.mjs +2 -2
- package/test/cases/components/content/viewer.mjs +250 -3
- package/test/cases/components/form/select.mjs +141 -0
|
@@ -29,12 +29,16 @@ import "./viewer/message.mjs";
|
|
|
29
29
|
import { getLocaleOfDocument } from "../../dom/locale.mjs";
|
|
30
30
|
import { Button } from "../form/button.mjs";
|
|
31
31
|
import { findTargetElementFromEvent } from "../../dom/events.mjs";
|
|
32
|
-
import {
|
|
32
|
+
import { sanitizeViewerHtml } from "./viewer/sanitize-html.mjs";
|
|
33
33
|
|
|
34
34
|
export { Viewer };
|
|
35
35
|
|
|
36
|
-
const
|
|
37
|
-
"
|
|
36
|
+
const SAFE_VIEWER_LINK_PROTOCOLS = new Set([
|
|
37
|
+
"http:",
|
|
38
|
+
"https:",
|
|
39
|
+
"mailto:",
|
|
40
|
+
"tel:",
|
|
41
|
+
]);
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
44
|
* @private
|
|
@@ -42,6 +46,24 @@ const BLOCKED_HTML_RESOURCE_DATA_URL =
|
|
|
42
46
|
*/
|
|
43
47
|
const viewerElementSymbol = Symbol("viewerElement");
|
|
44
48
|
|
|
49
|
+
/** @private @type {symbol} */
|
|
50
|
+
const linkConfirmationElementSymbol = Symbol("linkConfirmationElement");
|
|
51
|
+
|
|
52
|
+
/** @private @type {symbol} */
|
|
53
|
+
const linkHostElementSymbol = Symbol("linkHostElement");
|
|
54
|
+
|
|
55
|
+
/** @private @type {symbol} */
|
|
56
|
+
const linkUrlElementSymbol = Symbol("linkUrlElement");
|
|
57
|
+
|
|
58
|
+
/** @private @type {symbol} */
|
|
59
|
+
const pendingLinkSymbol = Symbol("pendingLink");
|
|
60
|
+
|
|
61
|
+
/** @private @type {symbol} */
|
|
62
|
+
const linkClickHandlerSymbol = Symbol("linkClickHandler");
|
|
63
|
+
|
|
64
|
+
/** @private @type {symbol} */
|
|
65
|
+
const linkKeydownHandlerSymbol = Symbol("linkKeydownHandler");
|
|
66
|
+
|
|
45
67
|
/**
|
|
46
68
|
* @private
|
|
47
69
|
* @type {symbol}
|
|
@@ -74,6 +96,16 @@ class Viewer extends CustomElement {
|
|
|
74
96
|
return Symbol.for("@schukai/monster/components/content/viewer@@instance");
|
|
75
97
|
}
|
|
76
98
|
|
|
99
|
+
connectedCallback() {
|
|
100
|
+
super.connectedCallback();
|
|
101
|
+
if (this[linkClickHandlerSymbol]) {
|
|
102
|
+
this.addEventListener("click", this[linkClickHandlerSymbol], true);
|
|
103
|
+
}
|
|
104
|
+
if (this[linkKeydownHandlerSymbol]) {
|
|
105
|
+
this.addEventListener("keydown", this[linkKeydownHandlerSymbol], true);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
77
109
|
/**
|
|
78
110
|
* To set the options via the HTML tag, the attribute `data-monster-options` must be used.
|
|
79
111
|
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
|
|
@@ -127,6 +159,14 @@ class Viewer extends CustomElement {
|
|
|
127
159
|
*/
|
|
128
160
|
disconnectedCallback() {
|
|
129
161
|
super.disconnectedCallback?.();
|
|
162
|
+
if (this[linkClickHandlerSymbol]) {
|
|
163
|
+
this.removeEventListener("click", this[linkClickHandlerSymbol], true);
|
|
164
|
+
this[linkClickHandlerSymbol] = null;
|
|
165
|
+
}
|
|
166
|
+
if (this[linkKeydownHandlerSymbol]) {
|
|
167
|
+
this.removeEventListener("keydown", this[linkKeydownHandlerSymbol], true);
|
|
168
|
+
this[linkKeydownHandlerSymbol] = null;
|
|
169
|
+
}
|
|
130
170
|
if (this[downloadHandlerSymbol]) {
|
|
131
171
|
this.removeEventListener("click", this[downloadHandlerSymbol]);
|
|
132
172
|
this[downloadHandlerSymbol] = null;
|
|
@@ -736,7 +776,7 @@ class Viewer extends CustomElement {
|
|
|
736
776
|
if (data instanceof Blob) {
|
|
737
777
|
blobToText(data)
|
|
738
778
|
.then((html) => {
|
|
739
|
-
this.setOption("content",
|
|
779
|
+
this.setOption("content", sanitizeViewerHtml(html));
|
|
740
780
|
})
|
|
741
781
|
.catch((error) => {
|
|
742
782
|
this.dispatchEvent(
|
|
@@ -758,7 +798,7 @@ class Viewer extends CustomElement {
|
|
|
758
798
|
return response.text();
|
|
759
799
|
})
|
|
760
800
|
.then((html) => {
|
|
761
|
-
this.setOption("content",
|
|
801
|
+
this.setOption("content", sanitizeViewerHtml(html));
|
|
762
802
|
})
|
|
763
803
|
.catch((error) => {
|
|
764
804
|
this.dispatchEvent(
|
|
@@ -775,7 +815,7 @@ class Viewer extends CustomElement {
|
|
|
775
815
|
throw new Error("HTMLElement or string expected");
|
|
776
816
|
}
|
|
777
817
|
|
|
778
|
-
this.setOption("content",
|
|
818
|
+
this.setOption("content", sanitizeViewerHtml(data));
|
|
779
819
|
}
|
|
780
820
|
|
|
781
821
|
/**
|
|
@@ -904,217 +944,203 @@ function blobToText(blob) {
|
|
|
904
944
|
});
|
|
905
945
|
}
|
|
906
946
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
947
|
+
/**
|
|
948
|
+
* @private
|
|
949
|
+
* @return {Select}
|
|
950
|
+
* @throws {Error} no shadow-root is defined
|
|
951
|
+
*/
|
|
952
|
+
function initControlReferences() {
|
|
953
|
+
if (!this.shadowRoot) {
|
|
954
|
+
throw new Error("no shadow-root is defined");
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
|
|
958
|
+
this[linkConfirmationElementSymbol] = this.shadowRoot.querySelector(
|
|
959
|
+
'[data-monster-role="external-link-confirmation"]',
|
|
960
|
+
);
|
|
961
|
+
this[linkHostElementSymbol] = this.shadowRoot.querySelector(
|
|
962
|
+
'[data-monster-role="external-link-host"]',
|
|
963
|
+
);
|
|
964
|
+
this[linkUrlElementSymbol] = this.shadowRoot.querySelector(
|
|
965
|
+
'[data-monster-role="external-link-url"]',
|
|
966
|
+
);
|
|
967
|
+
}
|
|
911
968
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
969
|
+
/**
|
|
970
|
+
* @private
|
|
971
|
+
*/
|
|
972
|
+
function initEventHandler() {
|
|
973
|
+
this[linkClickHandlerSymbol] = (event) => {
|
|
974
|
+
const path = event.composedPath?.() || [];
|
|
975
|
+
const actionElement = path.find(
|
|
976
|
+
(element) =>
|
|
977
|
+
element instanceof Element &&
|
|
978
|
+
element.hasAttribute?.("data-monster-link-action"),
|
|
979
|
+
);
|
|
916
980
|
|
|
917
|
-
if (
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
981
|
+
if (actionElement) {
|
|
982
|
+
event.preventDefault();
|
|
983
|
+
event.stopPropagation();
|
|
984
|
+
handleLinkConfirmationAction.call(
|
|
985
|
+
this,
|
|
986
|
+
actionElement.getAttribute("data-monster-link-action"),
|
|
987
|
+
);
|
|
988
|
+
return;
|
|
924
989
|
}
|
|
925
|
-
});
|
|
926
990
|
|
|
927
|
-
|
|
928
|
-
|
|
991
|
+
const link = path.find(
|
|
992
|
+
(element) => element instanceof Element && element.localName === "a",
|
|
993
|
+
);
|
|
994
|
+
if (!link) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
929
997
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
998
|
+
const href = link.getAttribute("href")?.trim();
|
|
999
|
+
if (!href || href.startsWith("#")) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
935
1002
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
.
|
|
941
|
-
.filter((entry) => {
|
|
942
|
-
const [candidateUrl] = entry.split(/\s+/, 1);
|
|
943
|
-
return isSameOriginResourceUrl(candidateUrl);
|
|
944
|
-
});
|
|
945
|
-
|
|
946
|
-
if (sameOriginEntries.length > 0) {
|
|
947
|
-
element.setAttribute(attributeName, sameOriginEntries.join(", "));
|
|
1003
|
+
let url;
|
|
1004
|
+
try {
|
|
1005
|
+
url = new URL(href, document.baseURI);
|
|
1006
|
+
} catch {
|
|
1007
|
+
event.preventDefault();
|
|
948
1008
|
return;
|
|
949
1009
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1010
|
+
if (!SAFE_VIEWER_LINK_PROTOCOLS.has(url.protocol.toLowerCase())) {
|
|
1011
|
+
event.preventDefault();
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
event.preventDefault();
|
|
1016
|
+
event.stopPropagation();
|
|
1017
|
+
showLinkConfirmation.call(this, link, url);
|
|
1018
|
+
};
|
|
953
1019
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1020
|
+
this[linkKeydownHandlerSymbol] = (event) => {
|
|
1021
|
+
if (event.key !== "Escape" || this[linkConfirmationElementSymbol]?.hidden) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
event.preventDefault();
|
|
1025
|
+
closeLinkConfirmation.call(this, true);
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
this.addEventListener("click", this[linkClickHandlerSymbol], true);
|
|
1029
|
+
this.addEventListener("keydown", this[linkKeydownHandlerSymbol], true);
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function showLinkConfirmation(link, url) {
|
|
1034
|
+
const confirmation = this[linkConfirmationElementSymbol];
|
|
1035
|
+
if (!confirmation) {
|
|
961
1036
|
return;
|
|
962
1037
|
}
|
|
963
1038
|
|
|
964
|
-
|
|
965
|
-
|
|
1039
|
+
this[pendingLinkSymbol] = { link, url };
|
|
1040
|
+
this[linkHostElementSymbol].textContent = url.hostname || url.protocol;
|
|
1041
|
+
this[linkUrlElementSymbol].textContent = url.href;
|
|
1042
|
+
confirmation.hidden = false;
|
|
966
1043
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
return isSameOriginResourceUrl(cleanedUrl) ? match : "";
|
|
1044
|
+
queueMicrotask(() => {
|
|
1045
|
+
positionLinkConfirmation(confirmation, link);
|
|
1046
|
+
confirmation.querySelector('[data-monster-link-action="cancel"]')?.focus();
|
|
971
1047
|
});
|
|
972
1048
|
}
|
|
973
1049
|
|
|
974
|
-
function
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1050
|
+
function positionLinkConfirmation(confirmation, link) {
|
|
1051
|
+
const linkRect = link.getBoundingClientRect();
|
|
1052
|
+
const width = confirmation.offsetWidth || 360;
|
|
1053
|
+
const height = confirmation.offsetHeight || 220;
|
|
1054
|
+
const viewportWidth =
|
|
1055
|
+
document.documentElement.clientWidth || window.innerWidth;
|
|
1056
|
+
const viewportHeight =
|
|
1057
|
+
document.documentElement.clientHeight || window.innerHeight;
|
|
1058
|
+
const edge = 16;
|
|
1059
|
+
const gap = 8;
|
|
1060
|
+
const left = Math.max(
|
|
1061
|
+
edge,
|
|
1062
|
+
Math.min(linkRect.left, viewportWidth - width - edge),
|
|
1063
|
+
);
|
|
1064
|
+
const below = linkRect.bottom + gap;
|
|
1065
|
+
const top =
|
|
1066
|
+
below + height <= viewportHeight - edge
|
|
1067
|
+
? below
|
|
1068
|
+
: Math.max(edge, linkRect.top - height - gap);
|
|
1069
|
+
|
|
1070
|
+
confirmation.style.left = `${left}px`;
|
|
1071
|
+
confirmation.style.top = `${top}px`;
|
|
1072
|
+
}
|
|
983
1073
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
url.protocol === "data:" ||
|
|
989
|
-
url.protocol === "blob:"
|
|
990
|
-
);
|
|
991
|
-
} catch {
|
|
992
|
-
return true;
|
|
1074
|
+
function handleLinkConfirmationAction(action) {
|
|
1075
|
+
if (action === "cancel") {
|
|
1076
|
+
closeLinkConfirmation.call(this, true);
|
|
1077
|
+
return;
|
|
993
1078
|
}
|
|
994
|
-
}
|
|
995
1079
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
* @throws {Error} no shadow-root is defined
|
|
1000
|
-
*/
|
|
1001
|
-
function initControlReferences() {
|
|
1002
|
-
if (!this.shadowRoot) {
|
|
1003
|
-
throw new Error("no shadow-root is defined");
|
|
1080
|
+
const pendingLink = this[pendingLinkSymbol];
|
|
1081
|
+
if (!pendingLink || (action !== "open" && action !== "new-window")) {
|
|
1082
|
+
return;
|
|
1004
1083
|
}
|
|
1005
1084
|
|
|
1006
|
-
|
|
1085
|
+
const target = action === "new-window" ? "_blank" : "_self";
|
|
1086
|
+
window.open(pendingLink.url.href, target, "noopener,noreferrer");
|
|
1087
|
+
closeLinkConfirmation.call(this, false);
|
|
1007
1088
|
}
|
|
1008
1089
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1090
|
+
function closeLinkConfirmation(restoreFocus) {
|
|
1091
|
+
const pendingLink = this[pendingLinkSymbol];
|
|
1092
|
+
this[linkConfirmationElementSymbol].hidden = true;
|
|
1093
|
+
delete this[pendingLinkSymbol];
|
|
1094
|
+
if (restoreFocus) {
|
|
1095
|
+
pendingLink?.link?.focus();
|
|
1096
|
+
}
|
|
1014
1097
|
}
|
|
1015
1098
|
|
|
1016
1099
|
function getLabels() {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
case "mr": // Marathi
|
|
1064
|
-
return {
|
|
1065
|
-
download: "डाउनलोड करा",
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
case "fr": // French
|
|
1069
|
-
return {
|
|
1070
|
-
download: "Télécharger",
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
case "it": // Italian
|
|
1074
|
-
return {
|
|
1075
|
-
download: "Scarica",
|
|
1076
|
-
};
|
|
1077
|
-
|
|
1078
|
-
case "nl": // Dutch
|
|
1079
|
-
return {
|
|
1080
|
-
download: "Downloaden",
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
case "sv": // Swedish
|
|
1084
|
-
return {
|
|
1085
|
-
download: "Ladda ner",
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
case "pl": // Polish
|
|
1089
|
-
return {
|
|
1090
|
-
download: "Pobierz",
|
|
1091
|
-
};
|
|
1092
|
-
|
|
1093
|
-
case "da": // Danish
|
|
1094
|
-
return {
|
|
1095
|
-
download: "Hent",
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
|
-
case "fi": // Finnish
|
|
1099
|
-
return {
|
|
1100
|
-
download: "Lataa",
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
|
-
case "no": // Norwegian
|
|
1104
|
-
return {
|
|
1105
|
-
download: "Last ned",
|
|
1106
|
-
};
|
|
1107
|
-
|
|
1108
|
-
case "cs": // Czech
|
|
1109
|
-
return {
|
|
1110
|
-
download: "Stáhnout",
|
|
1111
|
-
};
|
|
1112
|
-
|
|
1113
|
-
default:
|
|
1114
|
-
return {
|
|
1115
|
-
download: "Download",
|
|
1116
|
-
};
|
|
1117
|
-
}
|
|
1100
|
+
const language = getLocaleOfDocument().language;
|
|
1101
|
+
const downloadLabels = {
|
|
1102
|
+
en: "Download",
|
|
1103
|
+
de: "Herunterladen",
|
|
1104
|
+
es: "Descargar",
|
|
1105
|
+
zh: "下载",
|
|
1106
|
+
hi: "डाउनलोड करें",
|
|
1107
|
+
bn: "ডাউনলোড",
|
|
1108
|
+
pt: "Baixar",
|
|
1109
|
+
ru: "Скачать",
|
|
1110
|
+
ja: "ダウンロード",
|
|
1111
|
+
pa: "ਡਾਊਨਲੋਡ ਕਰੋ",
|
|
1112
|
+
mr: "डाउनलोड करा",
|
|
1113
|
+
fr: "Télécharger",
|
|
1114
|
+
it: "Scarica",
|
|
1115
|
+
nl: "Downloaden",
|
|
1116
|
+
sv: "Ladda ner",
|
|
1117
|
+
pl: "Pobierz",
|
|
1118
|
+
da: "Hent",
|
|
1119
|
+
fi: "Lataa",
|
|
1120
|
+
no: "Last ned",
|
|
1121
|
+
cs: "Stáhnout",
|
|
1122
|
+
};
|
|
1123
|
+
const linkLabels =
|
|
1124
|
+
language === "de"
|
|
1125
|
+
? {
|
|
1126
|
+
externalLinkTitle: "Externen Link öffnen?",
|
|
1127
|
+
externalLinkMessage: "Dieser Link führt zu:",
|
|
1128
|
+
externalLinkCancel: "Abbrechen",
|
|
1129
|
+
externalLinkOpen: "Öffnen",
|
|
1130
|
+
externalLinkNewWindow: "In neuem Fenster öffnen",
|
|
1131
|
+
}
|
|
1132
|
+
: {
|
|
1133
|
+
externalLinkTitle: "Open external link?",
|
|
1134
|
+
externalLinkMessage: "This link leads to:",
|
|
1135
|
+
externalLinkCancel: "Cancel",
|
|
1136
|
+
externalLinkOpen: "Open",
|
|
1137
|
+
externalLinkNewWindow: "Open in new window",
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
return {
|
|
1141
|
+
download: downloadLabels[language] || "Download",
|
|
1142
|
+
...linkLabels,
|
|
1143
|
+
};
|
|
1118
1144
|
}
|
|
1119
1145
|
|
|
1120
1146
|
/**
|
|
@@ -1127,7 +1153,26 @@ function getTemplate() {
|
|
|
1127
1153
|
<div id="viewer" data-monster-role="viewer" part="viewer"
|
|
1128
1154
|
data-monster-replace="path:content"
|
|
1129
1155
|
data-monster-attributes="class path:classes.viewer">
|
|
1130
|
-
</div
|
|
1156
|
+
</div>
|
|
1157
|
+
<section data-monster-role="external-link-confirmation" part="external-link-confirmation"
|
|
1158
|
+
role="dialog" aria-labelledby="external-link-title"
|
|
1159
|
+
aria-describedby="external-link-message external-link-url" hidden>
|
|
1160
|
+
<strong id="external-link-title" data-monster-replace="path:labels.externalLinkTitle"></strong>
|
|
1161
|
+
<p id="external-link-message" data-monster-replace="path:labels.externalLinkMessage"></p>
|
|
1162
|
+
<strong data-monster-role="external-link-host"></strong>
|
|
1163
|
+
<code id="external-link-url" data-monster-role="external-link-url"></code>
|
|
1164
|
+
<div data-monster-role="external-link-actions">
|
|
1165
|
+
<button type="button" class="monster-button-outline-secondary"
|
|
1166
|
+
data-monster-link-action="cancel"
|
|
1167
|
+
data-monster-replace="path:labels.externalLinkCancel"></button>
|
|
1168
|
+
<button type="button" class="monster-button-primary"
|
|
1169
|
+
data-monster-link-action="open"
|
|
1170
|
+
data-monster-replace="path:labels.externalLinkOpen"></button>
|
|
1171
|
+
<button type="button" class="monster-button-outline-primary"
|
|
1172
|
+
data-monster-link-action="new-window"
|
|
1173
|
+
data-monster-replace="path:labels.externalLinkNewWindow"></button>
|
|
1174
|
+
</div>
|
|
1175
|
+
</section>`;
|
|
1131
1176
|
}
|
|
1132
1177
|
|
|
1133
1178
|
registerCustomElement(Viewer);
|