ixbrl-viewer 1.4.39__py3-none-any.whl → 1.4.41__py3-none-any.whl
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.
Potentially problematic release.
This version of ixbrl-viewer might be problematic. Click here for more details.
- iXBRLViewerPlugin/__init__.py +25 -22
- iXBRLViewerPlugin/_version.py +2 -2
- iXBRLViewerPlugin/constants.py +86 -1
- iXBRLViewerPlugin/featureConfig.py +4 -1
- iXBRLViewerPlugin/iXBRLViewer.py +28 -14
- iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
- iXBRLViewerPlugin/viewer/i18next-parser.config.js +1 -1
- iXBRLViewerPlugin/viewer/src/html/fact-details.html +69 -38
- iXBRLViewerPlugin/viewer/src/html/footer-logo.html +4 -0
- iXBRLViewerPlugin/viewer/src/html/footnote-details.html +1 -1
- iXBRLViewerPlugin/viewer/src/html/inspector.html +318 -211
- iXBRLViewerPlugin/viewer/src/i18n/cy/balancetypes.json +1 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/currencies.json +13 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/datatypes.json +9 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/labelroles.json +24 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/referenceparts.json +10 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/scale.json +16 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/tooltips.json +17 -0
- iXBRLViewerPlugin/viewer/src/i18n/cy/translation.json +179 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/balancetypes.json +4 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/datatypes.json +10 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/labelroles.json +4 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/scale.json +16 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/tooltips.json +17 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +50 -25
- iXBRLViewerPlugin/viewer/src/i18n/es/balancetypes.json +4 -0
- iXBRLViewerPlugin/viewer/src/i18n/es/datatypes.json +10 -0
- iXBRLViewerPlugin/viewer/src/i18n/es/labelroles.json +24 -0
- iXBRLViewerPlugin/viewer/src/i18n/es/scale.json +16 -0
- iXBRLViewerPlugin/viewer/src/i18n/es/tooltips.json +17 -0
- iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +66 -40
- iXBRLViewerPlugin/viewer/src/icons/dark-mode.svg +4 -0
- iXBRLViewerPlugin/viewer/src/img/arelle-dark.svg +179 -0
- iXBRLViewerPlugin/viewer/src/img/inline-viewer-dark.svg +59 -0
- iXBRLViewerPlugin/viewer/src/js/accordian.js +2 -1
- iXBRLViewerPlugin/viewer/src/js/aspect.js +18 -7
- iXBRLViewerPlugin/viewer/src/js/balance.js +14 -0
- iXBRLViewerPlugin/viewer/src/js/chart.js +10 -6
- iXBRLViewerPlugin/viewer/src/js/concept.js +28 -1
- iXBRLViewerPlugin/viewer/src/js/concept.test.js +23 -2
- iXBRLViewerPlugin/viewer/src/js/datatype.js +20 -0
- iXBRLViewerPlugin/viewer/src/js/datatype.test.js +62 -0
- iXBRLViewerPlugin/viewer/src/js/dialog.js +3 -1
- iXBRLViewerPlugin/viewer/src/js/fact.js +16 -0
- iXBRLViewerPlugin/viewer/src/js/fact.test.js +3 -0
- iXBRLViewerPlugin/viewer/src/js/index.js +11 -3
- iXBRLViewerPlugin/viewer/src/js/inspector.js +499 -121
- iXBRLViewerPlugin/viewer/src/js/inspector.test.js +1 -1
- iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.js +128 -30
- iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.test.js +133 -20
- iXBRLViewerPlugin/viewer/src/js/menu.js +21 -3
- iXBRLViewerPlugin/viewer/src/js/outline.js +2 -2
- iXBRLViewerPlugin/viewer/src/js/report.js +60 -8
- iXBRLViewerPlugin/viewer/src/js/report.test.js +51 -5
- iXBRLViewerPlugin/viewer/src/js/reportset.js +20 -0
- iXBRLViewerPlugin/viewer/src/js/reportset.test.js +3 -3
- iXBRLViewerPlugin/viewer/src/js/search.js +23 -2
- iXBRLViewerPlugin/viewer/src/js/search.test.js +2 -2
- iXBRLViewerPlugin/viewer/src/js/summary.js +14 -0
- iXBRLViewerPlugin/viewer/src/js/tableExport.js +2 -1
- iXBRLViewerPlugin/viewer/src/js/taxonomynamer.js +34 -0
- iXBRLViewerPlugin/viewer/src/js/taxonomynamer.test.js +32 -0
- iXBRLViewerPlugin/viewer/src/js/theme.js +36 -0
- iXBRLViewerPlugin/viewer/src/js/unit.js +17 -2
- iXBRLViewerPlugin/viewer/src/js/util.js +16 -16
- iXBRLViewerPlugin/viewer/src/js/viewer.js +13 -7
- iXBRLViewerPlugin/viewer/src/less/accordian.less +8 -4
- iXBRLViewerPlugin/viewer/src/less/block-list.less +12 -6
- iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +2 -2
- iXBRLViewerPlugin/viewer/src/less/chart.less +8 -5
- iXBRLViewerPlugin/viewer/src/less/colours-dark-mode.less +40 -0
- iXBRLViewerPlugin/viewer/src/less/colours.less +28 -21
- iXBRLViewerPlugin/viewer/src/less/common.less +1 -1
- iXBRLViewerPlugin/viewer/src/less/components.less +3 -3
- iXBRLViewerPlugin/viewer/src/less/core.less +2 -0
- iXBRLViewerPlugin/viewer/src/less/dialog.less +13 -10
- iXBRLViewerPlugin/viewer/src/less/form-controls.less +33 -11
- iXBRLViewerPlugin/viewer/src/less/inspector.less +538 -299
- iXBRLViewerPlugin/viewer/src/less/loader.less +2 -2
- iXBRLViewerPlugin/viewer/src/less/menu.less +33 -15
- iXBRLViewerPlugin/viewer/src/less/summary.less +16 -6
- iXBRLViewerPlugin/viewer/src/less/tabs.less +5 -5
- iXBRLViewerPlugin/viewer/src/less/text-mixins.less +2 -1
- iXBRLViewerPlugin/viewer/src/less/validation-report.less +1 -1
- iXBRLViewerPlugin/viewer/src/less/viewer.less +30 -18
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/METADATA +33 -5
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/RECORD +95 -66
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/WHEEL +1 -1
- tests/puppeteer/framework/page_objects/doc_frame.js +1 -1
- tests/puppeteer/tests/fact_properties.test.js +4 -4
- tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +69 -28
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/LICENSE +0 -0
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/NOTICE +0 -0
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/entry_points.txt +0 -0
- {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.41.dist-info}/top_level.txt +0 -0
|
@@ -159,7 +159,7 @@ describe("Scales filter options", () => {
|
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
test("Scales filter options with only monetary facts", () => {
|
|
162
|
-
|
|
162
|
+
const insp = new TestInspector();
|
|
163
163
|
const reportSet = testReport({
|
|
164
164
|
...monetaryFactData,
|
|
165
165
|
}, ixData);
|
|
@@ -5,11 +5,17 @@ import $ from 'jquery'
|
|
|
5
5
|
import { ReportSet } from "./reportset.js";
|
|
6
6
|
import { Viewer, DocumentTooLargeError } from "./viewer.js";
|
|
7
7
|
import { Inspector } from "./inspector.js";
|
|
8
|
+
import { initializeTheme } from './theme.js';
|
|
9
|
+
import { TaxonomyNamer } from './taxonomynamer.js';
|
|
10
|
+
import {FEATURE_GUIDE_LINK, FEATURE_REVIEW, FEATURE_SUPPORT_LINK, FEATURE_SURVEY_LINK} from "./util";
|
|
11
|
+
|
|
12
|
+
const featureFalsyValues = new Set([undefined, null, '', 'false', false]);
|
|
8
13
|
|
|
9
14
|
export class iXBRLViewer {
|
|
10
15
|
|
|
11
16
|
constructor(options) {
|
|
12
|
-
this.
|
|
17
|
+
this._staticFeatures = {};
|
|
18
|
+
this._dynamicFeatures = {};
|
|
13
19
|
this._plugins = [];
|
|
14
20
|
this.inspector = new Inspector(this);
|
|
15
21
|
this.viewer = null;
|
|
@@ -76,40 +82,68 @@ export class iXBRLViewer {
|
|
|
76
82
|
});
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
setFeatures(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
+
setFeatures(features, queryString) {
|
|
86
|
+
this._staticFeatures = {}
|
|
87
|
+
for (const [key, value] of Object.entries(features)) {
|
|
88
|
+
this._staticFeatures[key] = value;
|
|
89
|
+
}
|
|
85
90
|
|
|
86
|
-
// Enable/disable features based on query string
|
|
87
91
|
const urlParams = new URLSearchParams(queryString);
|
|
92
|
+
this._dynamicFeatures = {}
|
|
88
93
|
urlParams.forEach((value, key) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
if (value === '') {
|
|
95
|
+
if (this._dynamicFeatures[key] === undefined) {
|
|
96
|
+
value = 'true'
|
|
97
|
+
} else {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
93
100
|
}
|
|
101
|
+
this._dynamicFeatures[key] = value;
|
|
94
102
|
});
|
|
103
|
+
}
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
105
|
+
getStaticFeatureValue(featureName) {
|
|
106
|
+
return this._staticFeatures[featureName];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getFeatureValue(featureName) {
|
|
110
|
+
if (this._dynamicFeatures[featureName]) {
|
|
111
|
+
return this._dynamicFeatures[featureName];
|
|
104
112
|
}
|
|
113
|
+
return this.getStaticFeatureValue(featureName);
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
isFeatureEnabled(featureName) {
|
|
108
|
-
return
|
|
117
|
+
return !featureFalsyValues.has(this.getFeatureValue(featureName));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isStaticFeatureEnabled(featureName) {
|
|
121
|
+
return !featureFalsyValues.has(this.getStaticFeatureValue(featureName));
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
isReviewModeEnabled() {
|
|
112
|
-
return this.isFeatureEnabled(
|
|
125
|
+
return this.isFeatureEnabled(FEATURE_REVIEW);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getGuideLinkUrl() {
|
|
129
|
+
if (!this.isStaticFeatureEnabled(FEATURE_GUIDE_LINK)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return this.resolveRelativeUrl(this.getStaticFeatureValue(FEATURE_GUIDE_LINK));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getSupportLinkUrl() {
|
|
136
|
+
if (!this.isStaticFeatureEnabled(FEATURE_SUPPORT_LINK)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return this.resolveRelativeUrl(this.getStaticFeatureValue(FEATURE_SUPPORT_LINK));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getSurveyLinkUrl() {
|
|
143
|
+
if (!this.isStaticFeatureEnabled(FEATURE_SURVEY_LINK)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return this.resolveRelativeUrl(this.getStaticFeatureValue(FEATURE_SURVEY_LINK));
|
|
113
147
|
}
|
|
114
148
|
|
|
115
149
|
isViewerEnabled() {
|
|
@@ -117,16 +151,34 @@ export class iXBRLViewer {
|
|
|
117
151
|
return (urlParams.get('disable-viewer') ?? 'false') === 'false';
|
|
118
152
|
}
|
|
119
153
|
|
|
154
|
+
// Resolves URL relative to the config file
|
|
155
|
+
resolveRelativeUrl(url) {
|
|
156
|
+
if (this.options.configUrl === undefined) {
|
|
157
|
+
return url;
|
|
158
|
+
}
|
|
159
|
+
const resolvedUrl = new URL(url, this.options.configUrl.href);
|
|
160
|
+
return resolvedUrl.href;
|
|
161
|
+
}
|
|
162
|
+
|
|
120
163
|
_loadInspectorHTML() {
|
|
121
164
|
/* Insert HTML and CSS styles into body */
|
|
122
|
-
|
|
165
|
+
const footerLogoHtml = this.runtimeConfig.skin?.footerLogoHtml ?? require("../html/footer-logo.html");
|
|
166
|
+
$(require('../html/inspector.html'))
|
|
167
|
+
.prependTo('body')
|
|
168
|
+
.find("#footer-logo").html(footerLogoHtml);
|
|
123
169
|
const inspector_css = require('../less/inspector.less').toString();
|
|
124
170
|
$('<style id="ixv-style"></style>')
|
|
125
171
|
.prop("type", "text/css")
|
|
126
172
|
.text(inspector_css)
|
|
127
173
|
.appendTo('head');
|
|
174
|
+
if (this.runtimeConfig.skin?.stylesheetUrl !== undefined) {
|
|
175
|
+
$('<link rel="stylesheet" id="ixv-style-skin" />')
|
|
176
|
+
.attr("href", this.resolveRelativeUrl(this.runtimeConfig.skin.stylesheetUrl))
|
|
177
|
+
.appendTo('head');
|
|
178
|
+
}
|
|
179
|
+
const favIconUrl = this.runtimeConfig.skin?.faviconUrl !== undefined ? this.resolveRelativeUrl(this.runtimeConfig.skin.faviconUrl) : require("../img/favicon.ico");
|
|
128
180
|
$('<link id="ixv-favicon" type="image/x-icon" rel="shortcut icon" />')
|
|
129
|
-
.attr('href',
|
|
181
|
+
.attr('href', favIconUrl)
|
|
130
182
|
.appendTo('head');
|
|
131
183
|
|
|
132
184
|
try {
|
|
@@ -140,7 +192,7 @@ export class iXBRLViewer {
|
|
|
140
192
|
_reparentDocument() {
|
|
141
193
|
const iframeContainer = $('#ixv #iframe-container');
|
|
142
194
|
|
|
143
|
-
const iframe = $('<iframe title="iXBRL document view"/>')
|
|
195
|
+
const iframe = $('<iframe title="iXBRL document view" tabindex="0"/>')
|
|
144
196
|
.data("report-index", 0)
|
|
145
197
|
.appendTo(iframeContainer)[0];
|
|
146
198
|
|
|
@@ -160,7 +212,8 @@ export class iXBRLViewer {
|
|
|
160
212
|
$('html').attr("lang", "en-US");
|
|
161
213
|
}
|
|
162
214
|
|
|
163
|
-
$('head')
|
|
215
|
+
$('head')
|
|
216
|
+
.children().not("script, style#ixv-style, link#ixv-style-skin, link#ixv-favicon").appendTo($(iframe).contents().find('head'));
|
|
164
217
|
|
|
165
218
|
$('<title>').text(docTitle).appendTo($('head'));
|
|
166
219
|
|
|
@@ -197,11 +250,41 @@ export class iXBRLViewer {
|
|
|
197
250
|
}
|
|
198
251
|
}
|
|
199
252
|
|
|
253
|
+
_loadRuntimeConfig() {
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
if (this.options.configUrl === undefined) {
|
|
256
|
+
resolve({});
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
fetch(this.options.configUrl)
|
|
260
|
+
.then((resp) => {
|
|
261
|
+
if (resp.status == 404) {
|
|
262
|
+
return Promise.resolve({});
|
|
263
|
+
}
|
|
264
|
+
else if (resp.status != 200) {
|
|
265
|
+
return Promise.reject("Fetch failed: " + resp.status);
|
|
266
|
+
}
|
|
267
|
+
return resp.json();
|
|
268
|
+
})
|
|
269
|
+
.then((data) => {
|
|
270
|
+
resolve(data);
|
|
271
|
+
})
|
|
272
|
+
.catch((err) => {
|
|
273
|
+
console.log(err);
|
|
274
|
+
resolve({});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
200
280
|
load() {
|
|
201
281
|
const iv = this;
|
|
202
282
|
const inspector = this.inspector;
|
|
283
|
+
|
|
284
|
+
this._loadRuntimeConfig().then((runtimeConfig) => {
|
|
285
|
+
this.runtimeConfig = runtimeConfig;
|
|
286
|
+
initializeTheme();
|
|
203
287
|
|
|
204
|
-
setTimeout(function () {
|
|
205
288
|
const stubViewer = $('body').hasClass('ixv-stub-viewer');
|
|
206
289
|
|
|
207
290
|
// If viewer is disabled, but not in stub viewer mode, just abort
|
|
@@ -217,9 +300,25 @@ export class iXBRLViewer {
|
|
|
217
300
|
// We need to parse JSON first so that we can determine feature enablement before loading begins.
|
|
218
301
|
const taxonomyData = iv._getTaxonomyData();
|
|
219
302
|
const parsedTaxonomyData = taxonomyData && JSON.parse(taxonomyData);
|
|
220
|
-
|
|
303
|
+
let features = parsedTaxonomyData && parsedTaxonomyData["features"];
|
|
304
|
+
if (!features) {
|
|
305
|
+
features = {};
|
|
306
|
+
}
|
|
307
|
+
// `features` was previously an array of flag values
|
|
308
|
+
// Support this for backwards compatability
|
|
309
|
+
else if (Array.isArray(features)) {
|
|
310
|
+
features = features.reduce((obj, val) => {
|
|
311
|
+
obj[val] = true;
|
|
312
|
+
return obj;
|
|
313
|
+
}, {});
|
|
314
|
+
}
|
|
315
|
+
if (this.runtimeConfig.features !== undefined) {
|
|
316
|
+
features = {...this.runtimeConfig.features, features};
|
|
317
|
+
}
|
|
318
|
+
iv.setFeatures(features, window.location.search);
|
|
221
319
|
|
|
222
320
|
const reportSet = new ReportSet(parsedTaxonomyData);
|
|
321
|
+
reportSet.taxonomyNamer = new TaxonomyNamer(new Map(Object.entries(this.runtimeConfig.taxonomyNames ?? {})));
|
|
223
322
|
|
|
224
323
|
// Viewer disabled in stub viewer mode => redirect to first iXBRL document
|
|
225
324
|
if (!iv.isViewerEnabled()) {
|
|
@@ -239,7 +338,7 @@ export class iXBRLViewer {
|
|
|
239
338
|
const ds = reportSet.reportFiles();
|
|
240
339
|
let hasExternalIframe = false;
|
|
241
340
|
for (let i = stubViewer ? 0 : 1; i < ds.length; i++) {
|
|
242
|
-
const iframe = $(
|
|
341
|
+
const iframe = $('<iframe tabindex="0" />').attr("src", ds[i].file).data("report-index", ds[i].index).appendTo("#ixv #iframe-container");
|
|
243
342
|
iframes = iframes.add(iframe);
|
|
244
343
|
hasExternalIframe = true;
|
|
245
344
|
}
|
|
@@ -292,7 +391,6 @@ export class iXBRLViewer {
|
|
|
292
391
|
$('#ixv .loader').remove();
|
|
293
392
|
|
|
294
393
|
/* Focus on fact specified in URL fragment, if any */
|
|
295
|
-
inspector.handleFactDeepLink();
|
|
296
394
|
if (iv.options.showValidationWarningOnStart) {
|
|
297
395
|
inspector.showValidationWarning();
|
|
298
396
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// See COPYRIGHT.md for copyright information
|
|
2
2
|
|
|
3
3
|
import {iXBRLViewer} from "./ixbrlviewer";
|
|
4
|
+
import {
|
|
5
|
+
FEATURE_GUIDE_LINK,
|
|
6
|
+
FEATURE_REVIEW,
|
|
7
|
+
FEATURE_SUPPORT_LINK,
|
|
8
|
+
FEATURE_SURVEY_LINK
|
|
9
|
+
} from "./util";
|
|
4
10
|
describe("Feature enablement", () => {
|
|
5
11
|
var viewer = null;
|
|
6
12
|
beforeAll(() => {
|
|
@@ -8,58 +14,99 @@ describe("Feature enablement", () => {
|
|
|
8
14
|
});
|
|
9
15
|
|
|
10
16
|
test("Query parameter with no value results in enablement", () => {
|
|
11
|
-
viewer.setFeatures(
|
|
12
|
-
expect(viewer.
|
|
17
|
+
viewer.setFeatures({}, 'a')
|
|
18
|
+
expect(viewer._staticFeatures).toEqual({});
|
|
19
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'true'});
|
|
13
20
|
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
14
21
|
});
|
|
15
22
|
|
|
16
23
|
test("Excluded from JSON, excluded from query", () => {
|
|
17
|
-
viewer.setFeatures(
|
|
18
|
-
expect(viewer.
|
|
24
|
+
viewer.setFeatures({}, '')
|
|
25
|
+
expect(viewer._staticFeatures).toEqual({});
|
|
26
|
+
expect(viewer._dynamicFeatures).toEqual({});
|
|
19
27
|
expect(viewer.isFeatureEnabled('a')).toBeFalsy();
|
|
20
28
|
});
|
|
21
29
|
|
|
22
30
|
test("Excluded from JSON, enabled in query", () => {
|
|
23
|
-
viewer.setFeatures(
|
|
24
|
-
expect(viewer.
|
|
31
|
+
viewer.setFeatures({}, 'a=true')
|
|
32
|
+
expect(viewer._staticFeatures).toEqual({});
|
|
33
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'true'});
|
|
25
34
|
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
26
35
|
});
|
|
27
36
|
|
|
28
37
|
test("Excluded from JSON, disabled in query", () => {
|
|
29
|
-
viewer.setFeatures(
|
|
30
|
-
expect(viewer.
|
|
38
|
+
viewer.setFeatures({}, 'a=false')
|
|
39
|
+
expect(viewer._staticFeatures).toEqual({});
|
|
40
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'false'});
|
|
31
41
|
expect(viewer.isFeatureEnabled('a')).toBeFalsy();
|
|
32
42
|
});
|
|
33
43
|
|
|
34
44
|
test("Excluded from JSON, enabled and disabled in query", () => {
|
|
35
|
-
viewer.setFeatures(
|
|
36
|
-
expect(viewer.
|
|
45
|
+
viewer.setFeatures({}, 'a=true&a=false&a')
|
|
46
|
+
expect(viewer._staticFeatures).toEqual({});
|
|
47
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'false'});
|
|
37
48
|
expect(viewer.isFeatureEnabled('a')).toBeFalsy();
|
|
38
49
|
});
|
|
39
50
|
|
|
40
51
|
test("Included in JSON, excluded from query", () => {
|
|
41
|
-
viewer.setFeatures(
|
|
42
|
-
expect(viewer.
|
|
52
|
+
viewer.setFeatures({'a': true}, '')
|
|
53
|
+
expect(viewer._staticFeatures).toEqual({'a': true});
|
|
54
|
+
expect(viewer._dynamicFeatures).toEqual({});
|
|
43
55
|
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
44
56
|
});
|
|
45
57
|
|
|
46
58
|
test("Included in JSON, enabled in query", () => {
|
|
47
|
-
viewer.setFeatures(
|
|
48
|
-
expect(viewer.
|
|
59
|
+
viewer.setFeatures({'a': true}, 'a=true')
|
|
60
|
+
expect(viewer._staticFeatures).toEqual({'a': true});
|
|
61
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'true'});
|
|
49
62
|
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
50
63
|
});
|
|
51
64
|
|
|
52
65
|
test("Included in JSON, disabled in query", () => {
|
|
53
|
-
viewer.setFeatures(
|
|
54
|
-
expect(viewer.
|
|
66
|
+
viewer.setFeatures({'a': true}, 'a=false')
|
|
67
|
+
expect(viewer._staticFeatures).toEqual({'a': true});
|
|
68
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'false'});
|
|
55
69
|
expect(viewer.isFeatureEnabled('a')).toBeFalsy();
|
|
56
70
|
});
|
|
57
71
|
|
|
58
72
|
test("Included in JSON, enabled and disabled in query", () => {
|
|
59
|
-
viewer.setFeatures(
|
|
60
|
-
expect(viewer.
|
|
73
|
+
viewer.setFeatures({'a': true}, 'a=true&a=false&a')
|
|
74
|
+
expect(viewer._staticFeatures).toEqual({'a': true});
|
|
75
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'false'});
|
|
61
76
|
expect(viewer.isFeatureEnabled('a')).toBeFalsy();
|
|
62
77
|
});
|
|
78
|
+
|
|
79
|
+
test("Value set in JSON, not included in query", () => {
|
|
80
|
+
viewer.setFeatures({'a': '1'}, '')
|
|
81
|
+
expect(viewer._staticFeatures).toEqual({'a': '1'});
|
|
82
|
+
expect(viewer._dynamicFeatures).toEqual({});
|
|
83
|
+
expect(viewer.getFeatureValue('a')).toEqual('1');
|
|
84
|
+
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("Value not set in JSON, set in query", () => {
|
|
88
|
+
viewer.setFeatures({}, 'a=1')
|
|
89
|
+
expect(viewer._staticFeatures).toEqual({});
|
|
90
|
+
expect(viewer._dynamicFeatures).toEqual({'a': '1'});
|
|
91
|
+
expect(viewer.getFeatureValue('a')).toEqual('1');
|
|
92
|
+
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("Value set in JSON, overwritten in query", () => {
|
|
96
|
+
viewer.setFeatures({'a': '1'}, 'a=2')
|
|
97
|
+
expect(viewer._staticFeatures).toEqual({'a': '1'});
|
|
98
|
+
expect(viewer._dynamicFeatures).toEqual({'a': '2'});
|
|
99
|
+
expect(viewer.getFeatureValue('a')).toEqual('2');
|
|
100
|
+
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("Value set in JSON, overwritten in query with blank", () => {
|
|
104
|
+
viewer.setFeatures({'a': '1'}, 'a')
|
|
105
|
+
expect(viewer._staticFeatures).toEqual({'a': '1'});
|
|
106
|
+
expect(viewer._dynamicFeatures).toEqual({'a': 'true'});
|
|
107
|
+
expect(viewer.getFeatureValue('a')).toEqual('true');
|
|
108
|
+
expect(viewer.isFeatureEnabled('a')).toBeTruthy();
|
|
109
|
+
});
|
|
63
110
|
});
|
|
64
111
|
|
|
65
112
|
describe("Review mode enablement", () => {
|
|
@@ -69,12 +116,78 @@ describe("Review mode enablement", () => {
|
|
|
69
116
|
});
|
|
70
117
|
|
|
71
118
|
test("Review mode enabled", () => {
|
|
72
|
-
viewer.setFeatures([
|
|
119
|
+
viewer.setFeatures({[FEATURE_REVIEW]: true}, '')
|
|
73
120
|
expect(viewer.isReviewModeEnabled()).toBeTruthy();
|
|
74
121
|
});
|
|
75
122
|
|
|
76
123
|
test("Review mode disabled", () => {
|
|
77
|
-
viewer.setFeatures(
|
|
124
|
+
viewer.setFeatures({'a': true}, '')
|
|
78
125
|
expect(viewer.isReviewModeEnabled()).toBeFalsy();
|
|
79
126
|
});
|
|
80
127
|
});
|
|
128
|
+
|
|
129
|
+
describe("Support link enablement", () => {
|
|
130
|
+
var viewer = null;
|
|
131
|
+
beforeAll(() => {
|
|
132
|
+
viewer = new iXBRLViewer({})
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("Support link enabled by JSON", () => {
|
|
136
|
+
viewer.setFeatures({[FEATURE_SUPPORT_LINK]: '/help'}, '')
|
|
137
|
+
expect(viewer.getSupportLinkUrl()).toEqual('/help');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("Support link enabled by query", () => {
|
|
141
|
+
viewer.setFeatures({}, FEATURE_SUPPORT_LINK + '=/help')
|
|
142
|
+
expect(viewer.getSupportLinkUrl()).toEqual(null);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("Support link disabled by query", () => {
|
|
146
|
+
viewer.setFeatures({[FEATURE_SUPPORT_LINK]: '/help'}, FEATURE_SUPPORT_LINK + '=false')
|
|
147
|
+
expect(viewer.getSupportLinkUrl()).toEqual('/help');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("Survey link enablement", () => {
|
|
152
|
+
var viewer = null;
|
|
153
|
+
beforeAll(() => {
|
|
154
|
+
viewer = new iXBRLViewer({})
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("Survey link enabled by JSON", () => {
|
|
158
|
+
viewer.setFeatures({[FEATURE_SURVEY_LINK]: '/survey'}, '')
|
|
159
|
+
expect(viewer.getSurveyLinkUrl()).toEqual('/survey');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("Survey link enabled by query", () => {
|
|
163
|
+
viewer.setFeatures({}, FEATURE_SURVEY_LINK + '=/survey')
|
|
164
|
+
expect(viewer.getSurveyLinkUrl()).toEqual(null);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("Survey link disabled by query", () => {
|
|
168
|
+
viewer.setFeatures({[FEATURE_SURVEY_LINK]: '/survey'}, FEATURE_SURVEY_LINK + '=false')
|
|
169
|
+
expect(viewer.getSurveyLinkUrl()).toEqual('/survey');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("Guide link enablement", () => {
|
|
174
|
+
var viewer = null;
|
|
175
|
+
beforeAll(() => {
|
|
176
|
+
viewer = new iXBRLViewer({})
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("Guide link enabled by JSON", () => {
|
|
180
|
+
viewer.setFeatures({[FEATURE_GUIDE_LINK]: '/guide'}, '')
|
|
181
|
+
expect(viewer.getGuideLinkUrl()).toEqual('/guide');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("Guide link enabled by query", () => {
|
|
185
|
+
viewer.setFeatures({}, FEATURE_GUIDE_LINK + '=/guide')
|
|
186
|
+
expect(viewer.getGuideLinkUrl()).toEqual(null);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("Guide link disabled by query", () => {
|
|
190
|
+
viewer.setFeatures({[FEATURE_GUIDE_LINK]: '/guide'}, FEATURE_GUIDE_LINK + '=false')
|
|
191
|
+
expect(viewer.getGuideLinkUrl()).toEqual('/guide');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// See COPYRIGHT.md for copyright information
|
|
2
2
|
|
|
3
3
|
import $ from 'jquery'
|
|
4
|
+
import i18next from "i18next";
|
|
4
5
|
|
|
5
6
|
export class Menu {
|
|
6
7
|
constructor(elt, attr) {
|
|
@@ -65,14 +66,14 @@ export class Menu {
|
|
|
65
66
|
$('<input type="checkbox"></input>')
|
|
66
67
|
.prop("checked", onByDefault)
|
|
67
68
|
.change(function () {
|
|
68
|
-
callback($(this).prop("checked"));
|
|
69
|
+
callback($(this).prop("checked"), true);
|
|
69
70
|
menu.close();
|
|
70
71
|
})
|
|
71
72
|
)
|
|
72
73
|
.append($("<span></span>").addClass("checkmark"));
|
|
73
74
|
this._add(item, after);
|
|
74
75
|
if (onByDefault) {
|
|
75
|
-
callback(true);
|
|
76
|
+
callback(true, false);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -91,7 +92,6 @@ export class Menu {
|
|
|
91
92
|
.attr({ "name": name, "value": v})
|
|
92
93
|
.on("change", function () {
|
|
93
94
|
callback($(this).val())
|
|
94
|
-
menu.close();
|
|
95
95
|
})
|
|
96
96
|
)
|
|
97
97
|
.append($("<span></span>").addClass("checkmark"))
|
|
@@ -102,4 +102,22 @@ export class Menu {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
addLabel(name) {
|
|
107
|
+
const item = $('<div></div>')
|
|
108
|
+
.addClass("label")
|
|
109
|
+
.text(name)
|
|
110
|
+
this._add(item);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
addLink(label, href) {
|
|
114
|
+
const item = $("<a></a>")
|
|
115
|
+
.attr("href", href)
|
|
116
|
+
.attr("target", "_blank")
|
|
117
|
+
.attr("title", label)
|
|
118
|
+
.attr("aria-label", label)
|
|
119
|
+
.addClass("item")
|
|
120
|
+
.text(label);
|
|
121
|
+
this._add(item);
|
|
122
|
+
}
|
|
105
123
|
}
|
|
@@ -171,9 +171,9 @@ export class DocumentOutline {
|
|
|
171
171
|
sortedSections() {
|
|
172
172
|
const sections = Object.keys(this.sections);
|
|
173
173
|
const re = /\(parenthetical\)\s*$/i;
|
|
174
|
-
const filteredSections = sections.filter(s => !re.test(this.report.
|
|
174
|
+
const filteredSections = sections.filter(s => !re.test(this.report.getRoleLabelOrURI(s)));
|
|
175
175
|
return filteredSections
|
|
176
|
-
.sort((a, b) => this.report.
|
|
176
|
+
.sort((a, b) => this.report.getRoleLabelOrURI(a).localeCompare(this.report.getRoleLabelOrURI(b)))
|
|
177
177
|
.map(elr => ({ report: this.report, fact: this.sections[elr], elr: elr }));
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -165,7 +165,39 @@ export class XBRLReport {
|
|
|
165
165
|
return label;
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
return
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getRoleLabelOrURI(rolePrefix) {
|
|
172
|
+
return this.getRoleLabel(rolePrefix) ?? this.reportSet.roleMap()[rolePrefix];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getLabelRoleLabel(rolePrefix) {
|
|
176
|
+
const roleURI = this.reportSet.roleMap()[rolePrefix];
|
|
177
|
+
if (roleURI === undefined) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Built-in label roles don't have a definition in the taxonomy. Do an
|
|
182
|
+
// i18n look-up on the last part of the URI. For "en" the camel-case
|
|
183
|
+
// splitter will do what we want for everything except standard label
|
|
184
|
+
// and documentation label
|
|
185
|
+
const suffix = roleURI.split("/").pop();
|
|
186
|
+
if (roleURI.startsWith("http://www.xbrl.org/2003/role/") && i18next.exists(`labelRoles:${suffix}`)) {
|
|
187
|
+
return i18next.t(`labelRoles:${suffix}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Attempt to get a label from the role definition
|
|
191
|
+
const label = this.getRoleLabel(rolePrefix);
|
|
192
|
+
if (label !== undefined) {
|
|
193
|
+
return label;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Fall back on de-camel-casing the last part of the URI
|
|
197
|
+
return suffix
|
|
198
|
+
.replaceAll(/([A-Z][a-z]+)/g, ' $1')
|
|
199
|
+
.trim()
|
|
200
|
+
.replace(/^./, s => s.toUpperCase());
|
|
169
201
|
}
|
|
170
202
|
|
|
171
203
|
localDocuments() {
|
|
@@ -180,7 +212,7 @@ export class XBRLReport {
|
|
|
180
212
|
}
|
|
181
213
|
|
|
182
214
|
getScaleLabel(scale, unit) {
|
|
183
|
-
let label = i18next.t(`scale
|
|
215
|
+
let label = i18next.t(`scale:${scale}`, {defaultValue:"noName"});
|
|
184
216
|
if (unit && unit.isMonetary() && scale === -2) {
|
|
185
217
|
let measure = unit.value() ?? '';
|
|
186
218
|
if (measure) {
|
|
@@ -199,35 +231,47 @@ export class XBRLReport {
|
|
|
199
231
|
}
|
|
200
232
|
|
|
201
233
|
getLabel(c, rolePrefix, showPrefix) {
|
|
234
|
+
return this.getLabelAndLang(c, rolePrefix, showPrefix).label;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getLabelAndLang(c, rolePrefix, showPrefix) {
|
|
202
238
|
rolePrefix = rolePrefix || 'std';
|
|
203
239
|
const lang = this.reportSet.viewerOptions.language;
|
|
204
240
|
const concept = this._reportData.concepts[c];
|
|
205
241
|
if (concept === undefined) {
|
|
206
242
|
console.log("Attempt to get label for undefined concept: " + c);
|
|
207
|
-
return "<no label>";
|
|
243
|
+
return { label: "<no label>" };
|
|
208
244
|
}
|
|
209
245
|
const labels = concept.labels[rolePrefix]
|
|
210
246
|
if (labels === undefined || Object.keys(labels).length == 0) {
|
|
211
|
-
return undefined;
|
|
247
|
+
return { label: undefined };
|
|
212
248
|
}
|
|
213
249
|
else {
|
|
214
250
|
let label;
|
|
251
|
+
let actualLang;
|
|
215
252
|
if (lang && labels[lang]) {
|
|
216
253
|
label = labels[lang];
|
|
254
|
+
actualLang = lang;
|
|
217
255
|
}
|
|
218
256
|
else {
|
|
219
257
|
// Fall back on English, then any label deterministically.
|
|
220
|
-
|
|
258
|
+
for (const l of ["en", "en-us", Object.keys(labels).sort()[0]]) {
|
|
259
|
+
if (labels[l] !== undefined) {
|
|
260
|
+
label = labels[l];
|
|
261
|
+
actualLang = l;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
221
265
|
}
|
|
222
266
|
if (label === undefined) {
|
|
223
|
-
return undefined;
|
|
267
|
+
return {label: undefined};
|
|
224
268
|
}
|
|
225
269
|
let s = '';
|
|
226
270
|
if (showPrefix && this.reportSet.viewerOptions.showPrefixes) {
|
|
227
|
-
s = "(" + this.qname(c).prefix + ") ";
|
|
271
|
+
s = "(" + this.reportSet.taxonomyNamer.fromQName(this.qname(c)).prefix + ") ";
|
|
228
272
|
}
|
|
229
273
|
s += label;
|
|
230
|
-
return s;
|
|
274
|
+
return { label: s, lang: actualLang };
|
|
231
275
|
}
|
|
232
276
|
}
|
|
233
277
|
|
|
@@ -239,6 +283,14 @@ export class XBRLReport {
|
|
|
239
283
|
return label;
|
|
240
284
|
}
|
|
241
285
|
|
|
286
|
+
getLabelOrNameAndLang(c, rolePrefix, showPrefix) {
|
|
287
|
+
const labelLang = this.getLabelAndLang(c, rolePrefix, showPrefix);
|
|
288
|
+
if (labelLang.label === undefined) {
|
|
289
|
+
return { label: c };
|
|
290
|
+
}
|
|
291
|
+
return labelLang;
|
|
292
|
+
}
|
|
293
|
+
|
|
242
294
|
isCalculationContributor(c) {
|
|
243
295
|
if (this._calculationContributors === undefined) {
|
|
244
296
|
if (this._reportData.rels?.calc) {
|