ixbrl-viewer 1.4.21__py3-none-any.whl → 1.4.49__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.

Files changed (114) hide show
  1. iXBRLViewerPlugin/__init__.py +77 -49
  2. iXBRLViewerPlugin/_version.py +2 -2
  3. iXBRLViewerPlugin/constants.py +86 -1
  4. iXBRLViewerPlugin/featureConfig.py +4 -1
  5. iXBRLViewerPlugin/iXBRLViewer.py +202 -131
  6. iXBRLViewerPlugin/plugin.py +7 -0
  7. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
  8. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js.LICENSE.txt +9 -2
  9. iXBRLViewerPlugin/viewer/i18next-parser.config.js +1 -1
  10. iXBRLViewerPlugin/viewer/src/data/utr.json +1 -0
  11. iXBRLViewerPlugin/viewer/src/html/fact-details.html +69 -38
  12. iXBRLViewerPlugin/viewer/src/html/footer-logo.html +4 -0
  13. iXBRLViewerPlugin/viewer/src/html/footnote-details.html +1 -1
  14. iXBRLViewerPlugin/viewer/src/html/inspector.html +324 -211
  15. iXBRLViewerPlugin/viewer/src/i18n/cy/balancetypes.json +1 -0
  16. iXBRLViewerPlugin/viewer/src/i18n/cy/currencies.json +13 -0
  17. iXBRLViewerPlugin/viewer/src/i18n/cy/datatypes.json +9 -0
  18. iXBRLViewerPlugin/viewer/src/i18n/cy/labelroles.json +24 -0
  19. iXBRLViewerPlugin/viewer/src/i18n/cy/referenceparts.json +10 -0
  20. iXBRLViewerPlugin/viewer/src/i18n/cy/scale.json +16 -0
  21. iXBRLViewerPlugin/viewer/src/i18n/cy/tooltips.json +17 -0
  22. iXBRLViewerPlugin/viewer/src/i18n/cy/translation.json +179 -0
  23. iXBRLViewerPlugin/viewer/src/i18n/en/balancetypes.json +4 -0
  24. iXBRLViewerPlugin/viewer/src/i18n/en/datatypes.json +10 -0
  25. iXBRLViewerPlugin/viewer/src/i18n/en/labelroles.json +4 -0
  26. iXBRLViewerPlugin/viewer/src/i18n/en/scale.json +16 -0
  27. iXBRLViewerPlugin/viewer/src/i18n/en/tooltips.json +17 -0
  28. iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +56 -24
  29. iXBRLViewerPlugin/viewer/src/i18n/es/balancetypes.json +4 -0
  30. iXBRLViewerPlugin/viewer/src/i18n/es/datatypes.json +10 -0
  31. iXBRLViewerPlugin/viewer/src/i18n/es/labelroles.json +24 -0
  32. iXBRLViewerPlugin/viewer/src/i18n/es/scale.json +16 -0
  33. iXBRLViewerPlugin/viewer/src/i18n/es/tooltips.json +17 -0
  34. iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +70 -37
  35. iXBRLViewerPlugin/viewer/src/icons/dark-mode.svg +4 -0
  36. iXBRLViewerPlugin/viewer/src/img/arelle-dark.svg +179 -0
  37. iXBRLViewerPlugin/viewer/src/img/inline-viewer-dark.svg +59 -0
  38. iXBRLViewerPlugin/viewer/src/js/accordian.js +3 -2
  39. iXBRLViewerPlugin/viewer/src/js/aspect.js +18 -10
  40. iXBRLViewerPlugin/viewer/src/js/aspect.test.js +2 -2
  41. iXBRLViewerPlugin/viewer/src/js/balance.js +14 -0
  42. iXBRLViewerPlugin/viewer/src/js/calculation.js +45 -34
  43. iXBRLViewerPlugin/viewer/src/js/calculationInspector.js +4 -7
  44. iXBRLViewerPlugin/viewer/src/js/chart.js +23 -21
  45. iXBRLViewerPlugin/viewer/src/js/concept.js +27 -2
  46. iXBRLViewerPlugin/viewer/src/js/concept.test.js +23 -2
  47. iXBRLViewerPlugin/viewer/src/js/datatype.js +20 -0
  48. iXBRLViewerPlugin/viewer/src/js/datatype.test.js +62 -0
  49. iXBRLViewerPlugin/viewer/src/js/dialog.js +6 -4
  50. iXBRLViewerPlugin/viewer/src/js/fact.js +40 -7
  51. iXBRLViewerPlugin/viewer/src/js/fact.test.js +3 -0
  52. iXBRLViewerPlugin/viewer/src/js/index.js +11 -3
  53. iXBRLViewerPlugin/viewer/src/js/inspector.js +560 -160
  54. iXBRLViewerPlugin/viewer/src/js/inspector.test.js +1 -2
  55. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.js +129 -31
  56. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.test.js +133 -20
  57. iXBRLViewerPlugin/viewer/src/js/ixnode.js +1 -1
  58. iXBRLViewerPlugin/viewer/src/js/menu.js +25 -7
  59. iXBRLViewerPlugin/viewer/src/js/number-matcher.js +2 -2
  60. iXBRLViewerPlugin/viewer/src/js/outline.js +2 -4
  61. iXBRLViewerPlugin/viewer/src/js/period.js +0 -1
  62. iXBRLViewerPlugin/viewer/src/js/report.js +68 -13
  63. iXBRLViewerPlugin/viewer/src/js/report.test.js +77 -6
  64. iXBRLViewerPlugin/viewer/src/js/reportset.js +33 -3
  65. iXBRLViewerPlugin/viewer/src/js/reportset.test.js +32 -6
  66. iXBRLViewerPlugin/viewer/src/js/search.js +61 -35
  67. iXBRLViewerPlugin/viewer/src/js/search.test.js +8 -5
  68. iXBRLViewerPlugin/viewer/src/js/summary.js +22 -0
  69. iXBRLViewerPlugin/viewer/src/js/summary.test.js +50 -13
  70. iXBRLViewerPlugin/viewer/src/js/tableExport.js +3 -3
  71. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.js +34 -0
  72. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.test.js +32 -0
  73. iXBRLViewerPlugin/viewer/src/js/theme.js +49 -0
  74. iXBRLViewerPlugin/viewer/src/js/unit.js +73 -2
  75. iXBRLViewerPlugin/viewer/src/js/unit.test.js +14 -3
  76. iXBRLViewerPlugin/viewer/src/js/util.js +21 -18
  77. iXBRLViewerPlugin/viewer/src/js/util.test.js +1 -0
  78. iXBRLViewerPlugin/viewer/src/js/utr.js +27 -0
  79. iXBRLViewerPlugin/viewer/src/js/viewer.js +40 -29
  80. iXBRLViewerPlugin/viewer/src/js/viewerOptions.js +0 -2
  81. iXBRLViewerPlugin/viewer/src/less/accordian.less +8 -4
  82. iXBRLViewerPlugin/viewer/src/less/block-list.less +12 -6
  83. iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +2 -2
  84. iXBRLViewerPlugin/viewer/src/less/chart.less +8 -5
  85. iXBRLViewerPlugin/viewer/src/less/colours-dark-mode.less +40 -0
  86. iXBRLViewerPlugin/viewer/src/less/colours.less +28 -21
  87. iXBRLViewerPlugin/viewer/src/less/common.less +1 -1
  88. iXBRLViewerPlugin/viewer/src/less/components.less +3 -3
  89. iXBRLViewerPlugin/viewer/src/less/core.less +2 -0
  90. iXBRLViewerPlugin/viewer/src/less/dialog.less +13 -10
  91. iXBRLViewerPlugin/viewer/src/less/form-controls.less +33 -11
  92. iXBRLViewerPlugin/viewer/src/less/inspector.less +556 -300
  93. iXBRLViewerPlugin/viewer/src/less/loader.less +2 -2
  94. iXBRLViewerPlugin/viewer/src/less/menu.less +33 -15
  95. iXBRLViewerPlugin/viewer/src/less/summary.less +16 -6
  96. iXBRLViewerPlugin/viewer/src/less/tabs.less +5 -5
  97. iXBRLViewerPlugin/viewer/src/less/text-mixins.less +2 -1
  98. iXBRLViewerPlugin/viewer/src/less/validation-report.less +1 -1
  99. iXBRLViewerPlugin/viewer/src/less/viewer.less +30 -18
  100. iXBRLViewerPlugin/viewer/webpack.common.js +19 -9
  101. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/METADATA +41 -14
  102. ixbrl_viewer-1.4.49.dist-info/RECORD +197 -0
  103. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/WHEEL +1 -1
  104. tests/puppeteer/framework/page_objects/doc_frame.js +3 -3
  105. tests/puppeteer/framework/page_objects/fact_details_panel.js +28 -0
  106. tests/puppeteer/puppeteer_test_run_via_intellij.jpg +0 -0
  107. tests/puppeteer/tests/fact_properties.test.js +10 -4
  108. tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +117 -51
  109. iXBRLViewerPlugin/viewer/src/js/interact.min.js +0 -6
  110. ixbrl_viewer-1.4.21.dist-info/RECORD +0 -166
  111. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/LICENSE +0 -0
  112. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/NOTICE +0 -0
  113. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/entry_points.txt +0 -0
  114. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@
3
3
  import $ from 'jquery'
4
4
  import i18next from 'i18next';
5
5
  import jqueryI18next from 'jquery-i18next';
6
- import { formatNumber, wrapLabel, truncateLabel, runGenerator, SHOW_FACT, HIGHLIGHT_COLORS, viewerUniqueId } from "./util.js";
6
+ import {formatNumber, wrapLabel, truncateLabel, runGenerator, SHOW_FACT, HIGHLIGHT_COLORS, viewerUniqueId, GLOSSARY_URL, FEATURE_HOME_LINK_URL, FEATURE_HOME_LINK_LABEL, FEATURE_SEARCH_ON_STARTUP, FEATURE_HIGHLIGHT_FACTS_ON_STARTUP, STORAGE_HIGHLIGHT_FACTS, STORAGE_HOME_LINK_QUERY} from "./util.js";
7
7
  import { ReportSearch } from "./search.js";
8
8
  import { IXBRLChart } from './chart.js';
9
9
  import { ViewerOptions } from './viewerOptions.js';
@@ -16,13 +16,23 @@ import { Footnote } from './footnote.js';
16
16
  import { ValidationReportDialog } from './validationreport.js';
17
17
  import { TextBlockViewerDialog } from './textblockviewer.js';
18
18
  import { MessageBox } from './messagebox.js';
19
- import { Interval } from './interval.js';
20
19
  import { Calculation } from "./calculation.js";
21
20
  import { CalculationInspector } from './calculationInspector.js';
22
21
  import { ReportSetOutline } from './outline.js';
23
22
  import { DIMENSIONS_KEY, DocumentSummary, MEMBERS_KEY, PRIMARY_ITEMS_KEY, TOTAL_KEY } from './summary.js';
23
+ import { toggleTheme } from './theme.js';
24
24
 
25
25
  const SEARCH_PAGE_SIZE = 100
26
+ const SEARCH_FILTER_MULTISELECTS = {
27
+ periodFilter: "search-filter-period",
28
+ dimensionTypeFilter: "search-filter-dimension-type",
29
+ namespacesFilter: "search-filter-namespaces",
30
+ targetDocumentFilter: "search-filter-target-document",
31
+ scalesFilter:"search-filter-scales",
32
+ unitsFilter: "search-filter-units",
33
+ calculationsFilter: "search-filter-calculations",
34
+ dataTypesFilter: "search-filter-datatypes",
35
+ };
26
36
 
27
37
  export class Inspector {
28
38
  constructor(iv) {
@@ -33,24 +43,30 @@ export class Inspector {
33
43
  }
34
44
 
35
45
  i18nInit() {
46
+ const langs = ["en", "cy", "es"];
47
+ const bundles = [
48
+ "translation",
49
+ "referenceParts",
50
+ "currencies",
51
+ "dataTypes",
52
+ "labelRoles",
53
+ "scale",
54
+ "balanceTypes",
55
+ "tooltips"
56
+ ];
36
57
  return i18next.init({
37
58
  lng: this.preferredLanguages()[0],
59
+ reloadOnLanguageChange: true,
38
60
  // Do not apply translations that are present but with an empty string
39
61
  returnEmptyString: false,
40
62
  fallbackLng: 'en',
41
63
  debug: false,
42
- resources: {
43
- en: {
44
- translation: require('../i18n/en/translation.json'),
45
- referenceParts: require('../i18n/en/referenceparts.json'),
46
- currencies: require('../i18n/en/currencies.json')
47
- },
48
- es: {
49
- translation: require('../i18n/es/translation.json'),
50
- referenceParts: require('../i18n/es/referenceparts.json'),
51
- currencies: require('../i18n/es/currencies.json')
52
- }
53
- }
64
+ resources:
65
+ Object.fromEntries(
66
+ langs.map(l => [l, Object.fromEntries(
67
+ bundles.map(n => [n, require(`../i18n/${l}/${n.toLowerCase()}.json`)]))
68
+ ])
69
+ )
54
70
  }).then((t) => {
55
71
  jqueryI18next.init(i18next, $, {
56
72
  tName: 't', // --> appends $.t = i18next.t
@@ -72,7 +88,7 @@ export class Inspector {
72
88
  inspector._reportSet = reportSet;
73
89
  inspector.i18nInit().then((t) => {
74
90
 
75
- $(".collapsible-header").on("click", function () {
91
+ $(".collapsible-header button:first-of-type").on("click", function () {
76
92
  const d = $(this).closest(".collapsible-section");
77
93
  d.toggleClass("collapsed");
78
94
  if (d.hasClass("collapsed")) {
@@ -89,28 +105,29 @@ export class Inspector {
89
105
  }
90
106
  }
91
107
  });
92
- $("#inspector .controls .search-button").on("click", function () {
93
- $(this).closest("#inspector").removeClass(["summary-mode", "outline-mode"]).toggleClass("search-mode");
94
- });
95
- $("#inspector .controls .summary-button").on("click", function () {
96
- $(this).closest("#inspector").removeClass(["outline-mode", "search-mode"]).toggleClass("summary-mode");
97
- });
98
- $("#inspector .controls .outline-button").on("click", function () {
99
- $(this).closest("#inspector").removeClass(["summary-mode", "search-mode"]).toggleClass("outline-mode");
100
- });
101
- $("#inspector-head .back").on("click", function () {
102
- $(this).closest("#inspector").removeClass(["summary-mode", "outline-mode", "search-mode"]);
108
+ $("#inspector .controls .search-button").on("click", () => inspector.inspectorMode("search-mode", true));
109
+ $("#inspector .controls .summary-button").on("click", () => inspector.inspectorMode("summary-mode", true));
110
+ $("#inspector .controls .outline-button").on("click", () => inspector.inspectorMode("outline-mode", true));
111
+ $("#inspector .back").on("click", () => inspector.popInspectorMode());
112
+ $(".popup-trigger").on("mouseenter", function () {
113
+ $(this).find(".popup-content").show()
114
+ }).on("mouseleave", function () {
115
+ $(this).find(".popup-content").hide()
103
116
  });
104
- $(".popup-trigger").hover(function () { $(this).find(".popup-content").show() }, function () { $(this).find(".popup-content").hide() });
105
117
  $("#inspector").on("click", ".clipboard-copy", function () {
106
118
  navigator.clipboard.writeText($(this).data("cb-text"));
107
119
  });
120
+
121
+ inspector.initializeTooltips();
122
+
108
123
  inspector._toolbarMenu = new Menu($("#toolbar-highlight-menu"));
109
124
  inspector.buildToolbarHighlightMenu();
110
125
 
111
126
  inspector._optionsMenu = new Menu($("#display-options-menu"));
112
127
  inspector.buildDisplayOptionsMenu();
113
128
 
129
+ inspector.buildHomeLink()
130
+
114
131
  $("#ixv").localize();
115
132
 
116
133
  // Listen to messages posted to this window
@@ -122,23 +139,64 @@ export class Inspector {
122
139
  inspector.createOutline();
123
140
  inspector._iv.setProgress(i18next.t("inspector.initializing")).then(() => {
124
141
  inspector._search = new ReportSearch(reportSet);
125
- inspector.buildDisplayOptionsMenu();
126
- inspector.buildToolbarHighlightMenu();
127
- inspector.buildHighlightKey();
142
+ inspector.handleFactDeepLink();
143
+ inspector.rebuildViewer();
128
144
  inspector.setupValidationReportIcon();
129
145
  inspector.initializeViewer();
146
+ inspector.doInitialSelection();
130
147
  resolve();
131
148
  });
132
149
  });
133
150
  });
134
151
  }
135
152
 
153
+ initializeTooltips() {
154
+ $("html").on("click", e =>
155
+ this.hideTooltip()
156
+ );
157
+ $("#inspector .inspector-body").on("scroll", e =>
158
+ this.hideTooltip()
159
+ );
160
+ $(document).on("keyup", (e) => {
161
+ if (e.keyCode == 27) {
162
+ this.hideTooltip();
163
+ }
164
+ });
165
+ $("#ixv").on("click", ".tooltip-icon", (e) => {
166
+ this.toggleTooltip($(e.currentTarget));
167
+ e.stopPropagation();
168
+ });
169
+
170
+ let tooltipHoverCount = 0;
171
+ $("#ixv").on("mouseenter", ".tooltip-icon", e => {
172
+ tooltipHoverCount++;
173
+ setTimeout(t => {
174
+ if (tooltipHoverCount > 0) {
175
+ this.showTooltip($(e.currentTarget), true);
176
+ }
177
+ }, 250);
178
+ });
179
+ $("#ixv").on("mouseenter", "#tooltip", e => tooltipHoverCount++);
180
+ $("#ixv").on("mouseleave", "#tooltip, .tooltip-icon", e => {
181
+ tooltipHoverCount--;
182
+ setTimeout(e => {
183
+ if (tooltipHoverCount == 0) {
184
+ this.hideTooltip(true);
185
+ }
186
+ }, 500);
187
+ });
188
+ }
189
+
136
190
  initializeViewer() {
137
191
  this._viewer.onSelect.add((vuid, eltSet, byClick) => this.selectItem(vuid, eltSet, byClick));
138
192
  this._viewer.onMouseEnter.add((id) => this.viewerMouseEnter(id));
139
193
  this._viewer.onMouseLeave.add(id => this.viewerMouseLeave(id));
140
- $('.ixbrl-next-tag').click(() => this._viewer.selectNextTag(this._currentItem));
141
- $('.ixbrl-prev-tag').click(() => this._viewer.selectPrevTag(this._currentItem));
194
+ $('.ixbrl-next-tag').on("click", () => this._viewer.selectNextTag(this._currentItem));
195
+ $('.ixbrl-prev-tag').on("click", () => this._viewer.selectPrevTag(this._currentItem));
196
+ $('#toggle-dark-mode')
197
+ .attr('title', i18next.t('toolbar.toggleDarkMode'))
198
+ .attr('aria-label', i18next.t('toolbar.toggleDarkMode'))
199
+ .on('click', toggleTheme);
142
200
  }
143
201
 
144
202
  postLoadAsync() {
@@ -165,6 +223,12 @@ export class Inspector {
165
223
  }
166
224
  }
167
225
 
226
+ doInitialSelection() {
227
+ if (!this._currentItem && this._iv.isFeatureEnabled(FEATURE_SEARCH_ON_STARTUP)) {
228
+ this.inspectorMode("search-mode");
229
+ }
230
+ }
231
+
168
232
  handleMessage(event) {
169
233
  const jsonString = event.originalEvent.data;
170
234
  let data;
@@ -204,30 +268,112 @@ export class Inspector {
204
268
  buildDisplayOptionsMenu() {
205
269
  this._optionsMenu.reset();
206
270
  if (this._reportSet) {
207
- const dl = this.selectDefaultLanguage();
208
- const langs = this._reportSet.availableLanguages();
209
- const langNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
271
+ // Doc language
272
+ const defaultDocLang = this.selectDefaultLanguage();
273
+ const docLangs = this._reportSet.availableLanguages();
274
+ const docLangNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
210
275
 
276
+ this._optionsMenu.addLabel(i18next.t("menu.documentLanguage"));
211
277
  this._optionsMenu.addCheckboxGroup(
212
- langs,
213
- Object.fromEntries(langs.map((l) => [l, langNames.of(l)])),
214
- dl,
215
- (lang) => { this.setLanguage(lang); this.update() },
278
+ docLangs,
279
+ Object.fromEntries(docLangs.map((l) => [l, docLangNames.of(l)])),
280
+ defaultDocLang,
281
+ (lang) => { this.setDocumentLanguage(lang); this.update() },
216
282
  "select-language"
217
283
  );
218
- this.setLanguage(dl);
284
+ this.setDocumentLanguage(defaultDocLang);
285
+
286
+ // Application language
287
+ const defaultAppLang = i18next.language.substring(0, 2);
288
+ const appLangs = Object.keys(i18next.options.resources);
289
+ const appLangNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
290
+
291
+ this._optionsMenu.addLabel(i18next.t("menu.applicationLanguage"));
292
+ this._optionsMenu.addCheckboxGroup(
293
+ appLangs,
294
+ Object.fromEntries(appLangs.map((l) => [l, appLangNames.of(l)])),
295
+ defaultAppLang,
296
+ (lang) => { this.changeApplicationLanguage(lang); },
297
+ "select-user-language"
298
+ );
299
+
300
+ // Actions
219
301
  if (this._reportSet.filingDocuments()) {
302
+ this._optionsMenu.addLabel(i18next.t("menu.actions"));
220
303
  this._optionsMenu.addDownloadButton("Download filing documents", this._reportSet.filingDocuments())
221
304
  }
222
- this._optionsMenu.addCheckboxItem(i18next.t("calculation.useCalculations11"), (useCalc11) => this.setCalculationMode(useCalc11), "calculation-mode", "select-language", this._useCalc11);
305
+
306
+ // Options
307
+ this._optionsMenu.addLabel(i18next.t("menu.options"));
308
+
309
+ if (this._reportSet.usesCalculations()) {
310
+ this._optionsMenu.addCheckboxItem(i18next.t("calculation.useCalculations11"), (useCalc11) => this.setCalculationMode(useCalc11), "calculation-mode", "select-language", this._useCalc11);
311
+ }
312
+ }
313
+ let helpLinks = {}
314
+ let guideLinkUrl = this._iv.getGuideLinkUrl();
315
+ if (guideLinkUrl) {
316
+ helpLinks[i18next.t("menu.userGuide")] = guideLinkUrl;
317
+ }
318
+ let supportLinkUrl = this._iv.getSupportLinkUrl();
319
+ if (supportLinkUrl) {
320
+ helpLinks[i18next.t("menu.contactUs")] = supportLinkUrl;
321
+ }
322
+ let surveyLinkUrl = this._iv.getSurveyLinkUrl();
323
+ if (surveyLinkUrl) {
324
+ helpLinks[i18next.t("menu.survey")] = surveyLinkUrl;
325
+ }
326
+ if (Object.entries(helpLinks).length > 0) {
327
+ this._optionsMenu.addLabel(i18next.t("menu.help"));
328
+ for (const [label, value] of Object.entries(helpLinks)) {
329
+ this._optionsMenu.addLink(label, value);
330
+ }
223
331
  }
224
332
  this._iv.callPluginMethod("extendDisplayOptionsMenu", this._optionsMenu);
225
333
  }
226
334
 
335
+ buildHomeLink() {
336
+ $('#top-bar #home-link').remove();
337
+ if (!this._iv.isStaticFeatureEnabled(FEATURE_HOME_LINK_URL)) {
338
+ return;
339
+ }
340
+ $("#top-bar img.header").remove();
341
+ let homeLinkUrl = this._iv.getStaticFeatureValue(FEATURE_HOME_LINK_URL);
342
+ let homeLinkText;
343
+ if (this._iv.isStaticFeatureEnabled(FEATURE_HOME_LINK_LABEL)) {
344
+ homeLinkText = this._iv.getStaticFeatureValue(FEATURE_HOME_LINK_LABEL);
345
+ } else {
346
+ homeLinkText = i18next.t("toolbar.homePage");
347
+ }
348
+ const query = sessionStorage.getItem(STORAGE_HOME_LINK_QUERY);
349
+ if (query) {
350
+ if (!homeLinkUrl.includes("?")) {
351
+ homeLinkUrl += "?";
352
+ } else {
353
+ homeLinkUrl += "&";
354
+ }
355
+ homeLinkUrl += query;
356
+ }
357
+ const homeLink = $('<a></a>')
358
+ .attr('href', homeLinkUrl)
359
+ .attr('id', 'home-link')
360
+ .text(homeLinkText);
361
+ $('#top-bar').prepend(homeLink);
362
+ }
363
+
364
+
365
+ highlightTagsOnStartup() {
366
+ const pref = window.localStorage.getItem(STORAGE_HIGHLIGHT_FACTS);
367
+ if (pref !== null) {
368
+ return JSON.parse(pref);
369
+ }
370
+ return this._iv.isFeatureEnabled(FEATURE_HIGHLIGHT_FACTS_ON_STARTUP);
371
+ }
372
+
227
373
  buildToolbarHighlightMenu() {
228
374
  const iv = this._iv;
229
375
  this._toolbarMenu.reset();
230
- this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked) => this.highlightAllTags(checked), "highlight-tags", null, this._iv.options.highlightTagsOnStartup);
376
+ this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked, explicitClick) => this.highlightAllTags(checked, explicitClick), "highlight-tags", null, this.highlightTagsOnStartup())
231
377
  if (iv.isReviewModeEnabled()) {
232
378
  this._toolbarMenu.addCheckboxItem("Untagged Numbers", function (checked) {
233
379
  const body = iv.viewer.contents().find("body");
@@ -262,7 +408,7 @@ export class Inspector {
262
408
  "Untagged Dates",
263
409
  ]
264
410
  } else {
265
- key = this._reportSet.namespaceGroups();
411
+ key = this._reportSet.namespaceGroups().map(p => this._reportSet.preferredPrefix(p));
266
412
  }
267
413
  this._iv.callPluginMethod("extendHighlightKey", key);
268
414
 
@@ -275,6 +421,50 @@ export class Inspector {
275
421
  }
276
422
  }
277
423
 
424
+ changeApplicationLanguage(lang) {
425
+ i18next.changeLanguage(lang);
426
+ this.rebuildViewer();
427
+ }
428
+
429
+ rebuildViewer() {
430
+ $("#ixv").localize();
431
+ this.buildDisplayOptionsMenu();
432
+ this.buildHomeLink()
433
+ this.buildToolbarHighlightMenu();
434
+ this.buildHighlightKey();
435
+ this.update();
436
+ }
437
+
438
+ inspectorMode(mode, toggle) {
439
+ const allModes = ["summary-mode", "outline-mode", "search-mode"];
440
+ const i = $("#inspector").removeClass(allModes.filter(m => m !== mode));
441
+ if (mode === undefined) {
442
+ this._prevInspectorMode = undefined;
443
+ return;
444
+ }
445
+ if (toggle) {
446
+ i.toggleClass(mode);
447
+ }
448
+ else {
449
+ i.addClass(mode);
450
+ }
451
+ }
452
+
453
+ /*
454
+ * Controls where the "back" button takes you. We only set this when you
455
+ * follow a link that switches between modes, otherwise back just takes you
456
+ * back to the main inspector mode.
457
+ */
458
+ pushInspectorMode(newMode, oldMode) {
459
+ this._prevInspectorMode = oldMode;
460
+ this.inspectorMode(newMode);
461
+ }
462
+
463
+ popInspectorMode() {
464
+ this.inspectorMode(this._prevInspectorMode);
465
+ this._prevInspectorMode = undefined;
466
+ }
467
+
278
468
  setCalculationMode(useCalc11) {
279
469
  this._useCalc11 = useCalc11;
280
470
  if (this._currentItem instanceof Fact) {
@@ -282,15 +472,19 @@ export class Inspector {
282
472
  }
283
473
  }
284
474
 
285
- highlightAllTags(checked) {
475
+ highlightAllTags(checked, explicitClick) {
476
+ if (explicitClick) {
477
+ window.localStorage.setItem(STORAGE_HIGHLIGHT_FACTS, JSON.stringify(checked));
478
+ }
286
479
  this._viewer.highlightAllTags(checked, this._reportSet.namespaceGroups());
287
480
  }
288
481
 
289
482
  factListRow(f) {
290
- const row = $('<div class="fact-list-item"></div>')
291
- .click(() => this.selectItem(f.vuid))
292
- .dblclick(() => $('#inspector').removeClass("search-mode"))
293
- .mousedown((e) => {
483
+ const row = $('<button class="fact-list-item"></button>')
484
+ // soft focus - highlight the fact, but don't close the search results
485
+ .on("click", () => this.selectItem(f.vuid, undefined, undefined, true))
486
+ .on("dblclick", () => this.selectItem(f.vuid))
487
+ .on("mousedown", (e) => {
294
488
  /* Prevents text selection via double click without
295
489
  * disabling click+drag text selection (which user-select:
296
490
  * none would )
@@ -299,30 +493,34 @@ export class Inspector {
299
493
  e.preventDefault()
300
494
  }
301
495
  })
302
- .mouseenter(() => this._viewer.linkedHighlightFact(f))
303
- .mouseleave(() => this._viewer.clearLinkedHighlightFact(f))
496
+ .on("mouseenter", () => this._viewer.linkedHighlightFact(f))
497
+ .on("mouseleave", () => this._viewer.clearLinkedHighlightFact(f))
304
498
  .data('ivid', f.vuid);
305
- $('<div class="select-icon"></div>')
306
- .click(() => {
499
+ $('<button class="select-icon"></button>')
500
+ .attr("title", i18next.t("search.viewFact"))
501
+ .on("click", () => {
307
502
  this.selectItem(f.vuid);
308
- $('#inspector').removeClass("search-mode");
309
503
  })
310
504
  .appendTo(row)
311
- $('<div class="title"></div>')
312
- .text(f.getLabelOrName("std"))
505
+ this._setLabelWithLang($('<div class="title"></div>'), f.getLabelOrNameAndLang("std"))
506
+ .appendTo(row);
507
+ const dt = f.concept().dataType();
508
+ if (dt !== undefined) {
509
+ $('<div class="datatype"></div>')
510
+ .text(dt.label())
313
511
  .appendTo(row);
512
+ }
314
513
  $('<div class="dimension"></div>')
315
514
  .text(f.period().toString())
316
515
  .appendTo(row);
317
516
 
318
517
  for (const aspect of f.aspects()) {
319
518
  if (aspect.isTaxonomyDefined() && !aspect.isNil()) {
320
- $('<div class="dimension"></div>')
321
- .text(aspect.valueLabel())
519
+ this._setLabelWithLang($('<div class="dimension"></div>'), aspect.valueLabelAndLang())
322
520
  .appendTo(row);
323
521
  }
324
522
  }
325
- const tags = $("<div></div>").addClass("tags").appendTo(row);
523
+ const tags = $("<div></div>").addClass("block-list-item-tags").appendTo(row);
326
524
  if (f.targetDocument() !== null) {
327
525
  $('<div class="hidden"></div>')
328
526
  .text(f.targetDocument())
@@ -358,25 +556,39 @@ export class Inspector {
358
556
  searchSpec() {
359
557
  const spec = {};
360
558
  spec.searchString = $('#ixbrl-search').val();
361
- spec.showVisibleFacts = $('#search-visible-fact-filter').prop('checked');
362
- spec.showHiddenFacts = $('#search-hidden-fact-filter').prop('checked');
363
- spec.namespacesFilter = $('#search-filter-namespaces select').val();
364
- spec.unitsFilter = $('#search-filter-units select').val();
365
- spec.scalesFilter = $('#search-filter-scales select').val();
366
- spec.periodFilter = $('#search-filter-period select').val();
559
+ spec.visibilityFilter = $('#search-filter-visibility').val();
560
+ spec.showMandatoryFacts = $('#search-mandatory-fact-filter').prop('checked');
367
561
  spec.conceptTypeFilter = $('#search-filter-concept-type').val();
562
+ for (const [key, name] of Object.entries(SEARCH_FILTER_MULTISELECTS)) {
563
+ spec[key] = $(`#${name} select`).val();
564
+ }
565
+
566
+ const selectedDataTypes = this._reportSet.getUsedConceptDataTypes().filter(d => spec.dataTypesFilter.includes(d.dataType.name));
567
+ if (
568
+ (spec.conceptTypeFilter == 'numeric' && selectedDataTypes.some(dt => !dt.isNumeric)) ||
569
+ (spec.conceptTypeFilter == 'text' && selectedDataTypes.some(dt => dt.isNumeric))) {
570
+ $("#search-filter-datatypes .datatype-conflict-warning").show();
571
+ }
572
+ else {
573
+ $("#search-filter-datatypes .datatype-conflict-warning").hide();
574
+ }
368
575
  spec.factValueFilter = $('#search-filter-fact-value').val();
369
- spec.calculationsFilter = $('#search-filter-calculations select').val();
370
- spec.dimensionTypeFilter = $('#search-filter-dimension-type select').val();
371
- spec.targetDocumentFilter = $('#search-filter-target-document select').val();
372
576
  return spec;
373
577
  }
374
578
 
579
+ hasActiveSearchFilters(searchSpec) {
580
+ return Object.keys(SEARCH_FILTER_MULTISELECTS).some(k => searchSpec[k].length > 0) ||
581
+ searchSpec.visibilityFilter !== '*' ||
582
+ searchSpec.showMandatoryFacts ||
583
+ searchSpec.conceptTypeFilter !== "*" ||
584
+ searchSpec.factValueFilter !== "*" ;
585
+ }
586
+
375
587
  setupSearchControls(viewer) {
376
588
  const inspector = this;
377
- $('.search-controls input, .search-controls select').change(() => this.search());
378
- $(".search-controls div.filter-toggle").click(() => $(".search-controls").toggleClass('show-filters'));
379
- $(".search-controls .search-filters .reset").click(() => this.resetSearchFilters());
589
+ $('.search-controls input, .search-controls select').on("change", () => this.search());
590
+ $(".search-controls button.filter-toggle").on("click", () => $(".search-controls").toggleClass('show-filters'));
591
+ $(".search-controls .reset").on("click", () => this.resetSearchFilters());
380
592
  $(".search-controls .search-filters .reset-multiselect").on("click", function () {
381
593
  $(this).siblings().children('select option:selected').prop('selected', false);
382
594
  inspector.search();
@@ -390,9 +602,20 @@ export class Inspector {
390
602
  for (const prefix of this._reportSet.getUsedConceptPrefixes()) {
391
603
  $("<option>")
392
604
  .attr("value", prefix)
393
- .text(`${prefix} (${this._reportSet.prefixMap()[prefix]})`)
605
+ .text(`${this._reportSet.preferredPrefix(prefix)} (${this._reportSet.prefixMap()[prefix]})`)
394
606
  .appendTo('#search-filter-namespaces select');
395
607
  }
608
+ if (this._reportSet.getUsedConceptDataTypes().length > 0) {
609
+ for (const dataType of this._reportSet.getUsedConceptDataTypes()) {
610
+ $("<option>")
611
+ .attr("value", dataType.dataType.name)
612
+ .text(dataType.dataType.label())
613
+ .appendTo('#search-filter-datatypes select');
614
+ }
615
+ }
616
+ else {
617
+ $('#search-filter-datatypes').hide();
618
+ }
396
619
  const targetDocuments = Array.from(this._reportSet.getTargetDocuments());
397
620
  if (targetDocuments.length == 1 && targetDocuments[0] == null) {
398
621
  $('#search-filter-target-document').hide();
@@ -435,18 +658,16 @@ export class Inspector {
435
658
  return scalesOptions;
436
659
  }
437
660
 
438
- resetSearchFilters() {
661
+ resetSearchFilters(defaults) {
662
+ defaults = defaults ?? {};
439
663
  $("#search-filter-period select option:selected").prop("selected", false);
664
+ $("#search-filter-visibility").val(defaults.visibility ?? "*");
440
665
  $("#search-filter-concept-type").val("*");
441
666
  $("#search-filter-fact-value").val("*");
442
- $("#search-filter-calculations select option:selected").prop("selected", false);
443
- $("#search-filter-dimension-type select option:selected").prop("selected", false);
444
- $("#search-hidden-fact-filter").prop("checked", true);
445
- $("#search-visible-fact-filter").prop("checked", true);
446
- $("#search-filter-namespaces select option:selected").prop("selected", false);
447
- $("#search-filter-target-document select option:selected").prop("selected", false);
448
- $("#search-filter-units select option:selected").prop("selected", false);
449
- $("#search-filter-scales select option:selected").prop("selected", false);
667
+ $("#search-mandatory-fact-filter").prop("checked", defaults.mandatoryFacts ?? false);
668
+ for (const name of Object.values(SEARCH_FILTER_MULTISELECTS)) {
669
+ $(`#${name} select option:selected`).prop("selected", false);
670
+ }
450
671
  this.search();
451
672
  }
452
673
 
@@ -457,14 +678,20 @@ export class Inspector {
457
678
  this.search();
458
679
  }
459
680
 
460
- search () {
681
+ search() {
461
682
  const spec = this.searchSpec();
683
+ if (this.hasActiveSearchFilters(spec)) {
684
+ $("#inspector .search-controls").addClass("active-filters");
685
+ }
686
+ else {
687
+ $("#inspector .search-controls").removeClass("active-filters");
688
+ }
462
689
  const results = this._search.search(spec);
463
690
  if (results === undefined) {
464
691
  return;
465
692
  }
466
693
  const container = $('#inspector .search-results .results');
467
- $('div', container).remove();
694
+ container.empty();
468
695
  this._viewer.clearRelatedHighlighting();
469
696
  const overlay = $('#inspector .search-results .search-overlay');
470
697
  if (results.length > 0) {
@@ -476,18 +703,14 @@ export class Inspector {
476
703
  $(".text", overlay).text(i18next.t("search.tryAgainDifferentKeywords"));
477
704
  overlay.show();
478
705
  }
479
- $("#matching-facts-count").text(results.length);
706
+ $("#matching-facts-summary").text(i18next.t("search.matchingFactsSummary", {nMatches: results.length, nTotal: this._reportSet.facts().length}));
480
707
  /* Don't highlight search results if there's no search string */
481
708
  if (spec.searchString != "") {
482
709
  this._viewer.highlightRelatedFacts(results.map(r => r.fact));
483
710
  }
484
- this.updateMultiSelectSubheader('search-filter-scales');
485
- this.updateMultiSelectSubheader('search-filter-units');
486
- this.updateMultiSelectSubheader('search-filter-namespaces');
487
- this.updateMultiSelectSubheader('search-filter-target-document');
488
- this.updateMultiSelectSubheader('search-filter-dimension-type');
489
- this.updateMultiSelectSubheader('search-filter-calculations');
490
- this.updateMultiSelectSubheader('search-filter-period');
711
+ for (const name of Object.values(SEARCH_FILTER_MULTISELECTS)) {
712
+ this.updateMultiSelectSubheader(name);
713
+ }
491
714
  }
492
715
 
493
716
  updateMultiSelectSubheader(id) {
@@ -513,13 +736,40 @@ export class Inspector {
513
736
  this._populateFactSummary(summaryDom);
514
737
  this._populateTagSummary(summaryDom);
515
738
  this._populateFileSummary(summaryDom);
739
+ this._populateReportCreation(summaryDom);
516
740
  }
517
741
 
518
742
  _populateFactSummary(summaryDom) {
519
743
  const totalFacts = this.summary.totalFacts();
520
- $("<span></span>")
521
- .text(totalFacts)
522
- .appendTo(summaryDom.find(".total-facts-value"));
744
+ $(".total-facts-value", summaryDom)
745
+ .text(totalFacts)
746
+ .on("click", () => {
747
+ this.resetSearchFilters();
748
+ this.pushInspectorMode("search-mode", "summary-mode");
749
+ });
750
+
751
+ const hiddenFacts = this.summary.hiddenFacts();
752
+ $(".hidden-facts-value", summaryDom)
753
+ .text(hiddenFacts)
754
+ .on("click", () => {
755
+ this.resetSearchFilters({visibility: 'hidden'});
756
+ this.pushInspectorMode("search-mode", "summary-mode");
757
+ });
758
+
759
+ const mandatoryFacts = this.summary.mandatoryFacts();
760
+ if (!mandatoryFacts) {
761
+ $('#mandatory-facts-row').hide();
762
+ $('#mandatory-fact-filter-checkbox').hide();
763
+ } else {
764
+ $('#mandatory-facts-row').show();
765
+ $('#mandatory-fact-filter-checkbox').show();
766
+ $(".mandatory-facts-value", summaryDom)
767
+ .text(mandatoryFacts)
768
+ .on("click", () => {
769
+ this.resetSearchFilters({mandatoryFacts: true});
770
+ this.pushInspectorMode("search-mode", "summary-mode");
771
+ });
772
+ }
523
773
  }
524
774
 
525
775
  _populateTagSummary(summaryDom) {
@@ -558,7 +808,7 @@ export class Inspector {
558
808
  const sortedPrefixCounts = [...tagCounts.entries()].sort((a, b) => a[0].localeCompare(b[0]));
559
809
  for (const [prefix, counts] of sortedPrefixCounts) {
560
810
  const countRow = $("<tr></tr>").appendTo(summaryTagsTableBody);
561
- countRow.append($("<th></th>").attr("scope", "row").text(prefix));
811
+ countRow.append($("<th></th>").attr("scope", "row").text(this._reportSet.preferredPrefix(prefix)));
562
812
  insertTagCount(countRow, counts[PRIMARY_ITEMS_KEY], totalPrimaryItemTags);
563
813
  insertTagCount(countRow, counts[DIMENSIONS_KEY], totalDimensionTags);
564
814
  insertTagCount(countRow, counts[MEMBERS_KEY], totalMemberTags);
@@ -613,16 +863,31 @@ export class Inspector {
613
863
  }
614
864
  };
615
865
 
866
+ _populateReportCreation(summaryDom) {
867
+ const softwareCredits = this.summary.getSoftwareCredits();
868
+
869
+ const reportCreationContent = summaryDom.find(".report-creation");
870
+
871
+ if (softwareCredits.length > 0) {
872
+ const ul = reportCreationContent.find('ul');
873
+ for (const softwareCredit of softwareCredits) {
874
+ ul.append($("<li></li>").text(softwareCredit));
875
+ }
876
+ } else {
877
+ reportCreationContent.hide();
878
+ }
879
+ };
880
+
616
881
  createOutline() {
617
882
  if (this.outline.hasOutline()) {
618
883
  $('.outline .no-outline-overlay').hide();
619
884
  const container = $('<div class="fact-list"></div>').appendTo($('.outline .body'));
620
885
  for (const group of this.outline.sortedSections()) {
621
- $('<div class="fact-list-item"></div>')
622
- .text(group.report.getRoleLabel(group.elr))
623
- .click(() => this.selectItem(group.fact.vuid))
624
- .dblclick(() => $('#inspector').removeClass("outline-mode"))
625
- .mousedown((e) => {
886
+ $('<button class="fact-list-item"></button>')
887
+ .text(group.report.getRoleLabelOrURI(group.elr))
888
+ .on("click", () => this.selectItem(group.fact.vuid))
889
+ .on("dblclick", () => $('#inspector').removeClass("outline-mode"))
890
+ .on("mousedown", (e) => {
626
891
  // Prevent text selection by double click
627
892
  if (e.detail > 1) {
628
893
  e.preventDefault()
@@ -636,9 +901,9 @@ export class Inspector {
636
901
  updateOutline(cf) {
637
902
  $('.fact-groups').empty();
638
903
  for (const group of this.outline.groupsForFact(cf)) {
639
- $('<div class="fact-list-item"></div>')
640
- .text(cf.report.getRoleLabel(group.elr))
641
- .click(() => this.selectItem(group.fact.vuid))
904
+ $('<button class="fact-list-item"></button>')
905
+ .text(cf.report.getRoleLabelOrURI(group.elr))
906
+ .on("click", () => this.selectItem(group.fact.vuid))
642
907
  .appendTo($('.fact-groups'));
643
908
  }
644
909
 
@@ -667,11 +932,11 @@ export class Inspector {
667
932
  if (anchors.length > 0) {
668
933
  for (const c of anchors) {
669
934
  const otherFacts = fact.report.getAlignedFacts(fact, { "c": c });
670
- const label = fact.report.getLabel(c, "std", true);
935
+ const labelLang = fact.report.getLabelAndLang(c, "std", true);
671
936
 
672
937
  $("<li></li>")
673
938
  .appendTo(html)
674
- .append(this.factLinkHTML(label, otherFacts));
939
+ .append(this.factLinkHTML(labelLang, otherFacts));
675
940
  }
676
941
  }
677
942
  else {
@@ -700,8 +965,38 @@ export class Inspector {
700
965
 
701
966
  }
702
967
 
968
+ labelRoleSort([role1, roleLabel1, label1], [role2, roleLabel2, label2]) {
969
+ // Sort built-ins before others. Reverse so that -1 (not found) sorts
970
+ // after the last built-in.
971
+ const builtIn = ['std', 'doc'].reverse();
972
+ const p1 = builtIn.indexOf(role1);
973
+ const p2 = builtIn.indexOf(role2);
974
+
975
+ if (p1 != p2) {
976
+ return p2 - p1;
977
+ }
978
+
979
+ return roleLabel1.localeCompare(roleLabel2);
980
+ }
981
+
982
+ updateLabels(fact) {
983
+ const container = $("div.labels").empty();
984
+ const dl = $("<dl></dl>").appendTo(container);
985
+ for (const [role, roleLabel, label] of
986
+ Object.entries(fact.concept().labels())
987
+ .map(([role, label]) => [role, fact.report.getLabelRoleLabel(role), label])
988
+ .sort(this.labelRoleSort)) {
989
+ $("<dt></dt>")
990
+ .text(roleLabel)
991
+ .appendTo(dl);
992
+ $("<dd></dd>")
993
+ .text(label)
994
+ .appendTo(dl);
995
+ }
996
+ return dl;
997
+ }
998
+
703
999
  _referencesHTML(fact) {
704
- const c = fact.concept();
705
1000
  const a = new Accordian();
706
1001
  for (const [i, r] of fact.concept().references().entries()) {
707
1002
  const title = $("<span></span>").text(r[0].value);
@@ -730,11 +1025,10 @@ export class Inspector {
730
1025
  const tableFacts = this._viewer.factsInSameTable(fact);
731
1026
  const selectedELR = calc.bestELRForFactSet(tableFacts);
732
1027
  const report = fact.report;
733
- const inspector = this;
734
1028
  const a = new Accordian();
735
1029
 
736
1030
  for (const rCalc of calc.resolvedCalculations()) {
737
- const label = report.getRoleLabel(rCalc.elr);
1031
+ const label = report.getRoleLabelOrURI(rCalc.elr);
738
1032
  const calcBody = $('<div></div>');
739
1033
  const calcTable = $('<table></table>')
740
1034
  .addClass("calculation-table")
@@ -743,18 +1037,18 @@ export class Inspector {
743
1037
  for (const r of rCalc.rows) {
744
1038
  const itemHTML = $("<tr></tr>")
745
1039
  .addClass("item")
746
- .append($("<td></td>").addClass("weight").text(r.weightSign + " "))
1040
+ .append($("<td></td>").addClass("weight").text(r.weightSign))
747
1041
  .append($("<td></td>").addClass("concept-name").text(r.concept.label()))
748
1042
  .append($("<td></td>").addClass("value"))
749
1043
  .appendTo(calcTable);
750
1044
 
751
1045
  if (!r.facts.isEmpty()) {
752
1046
  itemHTML.addClass("calc-fact-link");
753
- itemHTML.addClass("calc-fact-link");
1047
+ itemHTML.find(".concept-name").contents().wrap($("<button></button>").addClass("inline-button"));
754
1048
  itemHTML.data('ivids', r.facts.items().map(f => f.vuid));
755
- itemHTML.click(() => this.selectItem(r.facts.items[0].vuid));
756
- itemHTML.mouseenter(() => r.facts.items().forEach(f => this._viewer.linkedHighlightFact(f)));
757
- itemHTML.mouseleave(() => r.facts.items().forEach(f => this._viewer.clearLinkedHighlightFact(f)));
1049
+ itemHTML.on("click", () => this.selectItem(r.facts.items()[0].vuid));
1050
+ itemHTML.on("mouseenter", () => r.facts.items().forEach(f => this._viewer.linkedHighlightFact(f)));
1051
+ itemHTML.on("mouseleave", () => r.facts.items().forEach(f => this._viewer.clearLinkedHighlightFact(f)));
758
1052
  r.facts.items().forEach(f => this._viewer.highlightRelatedFact(f));
759
1053
  itemHTML.find(".value").text(r.facts.mostPrecise().readableValue());
760
1054
  }
@@ -770,17 +1064,17 @@ export class Inspector {
770
1064
  .append(calcStatusIcon)
771
1065
  .append($("<span></span>").text(label));
772
1066
  const calcStatusText = $("<span></span>");
773
- const calcDetailsLink = $("<span></span>")
1067
+ const calcDetailsLink = $("<button></button>")
774
1068
  .addClass("calculation-details-link")
775
1069
  .attr("title", i18next.t('factDetails.viewCalculationDetails'))
776
1070
  .text("details")
777
- .click((e) => {
1071
+ .on("click", (e) => {
778
1072
  const dialog = new CalculationInspector();
779
1073
  dialog.displayCalculation(rCalc);
780
1074
  dialog.show();
781
1075
  e.stopPropagation();
782
1076
  })
783
- const calcStatus = $("<p></p>")
1077
+ $("<p></p>")
784
1078
  .append(calcStatusText)
785
1079
  .append($("<span></span>").text(" ("))
786
1080
  .append(calcDetailsLink)
@@ -804,7 +1098,12 @@ export class Inspector {
804
1098
  calcStatusIcon
805
1099
  .addClass("unchecked-flag")
806
1100
  .attr("title", i18next.t('factDetails.calculationUnchecked'))
807
- calcStatusText.text(i18next.t('factDetails.calculationUnchecked'));
1101
+ if (rCalc.uncheckedDueToVersionMismatch()) {
1102
+ calcStatusText.text(i18next.t('factDetails.calculationUncheckedIncorrectVersion'));
1103
+ }
1104
+ else {
1105
+ calcStatusText.text(i18next.t('factDetails.calculationUnchecked'));
1106
+ }
808
1107
  }
809
1108
 
810
1109
  a.addCard(cardTitle, calcBody, rCalc.elr == selectedELR);
@@ -816,12 +1115,12 @@ export class Inspector {
816
1115
  const html = $("<div></div>").addClass("fact-list");
817
1116
  for (const fn of fact.footnotes()) {
818
1117
  if (fn instanceof Footnote) {
819
- $("<div></div>")
1118
+ $("<button></button>")
820
1119
  .addClass("block-list-item")
821
1120
  .text(truncateLabel(fn.textContent(), 120))
822
- .mouseenter(() => this._viewer.linkedHighlightFact(fn))
823
- .mouseleave(() => this._viewer.clearLinkedHighlightFact(fn))
824
- .click(() => this.selectItem(fn.vuid))
1121
+ .on("mouseenter", () => this._viewer.linkedHighlightFact(fn))
1122
+ .on("mouseleave", () => this._viewer.clearLinkedHighlightFact(fn))
1123
+ .on("click", () => this.selectItem(fn.vuid))
825
1124
  .appendTo(html);
826
1125
  }
827
1126
  else if (fn instanceof Fact) {
@@ -862,14 +1161,15 @@ export class Inspector {
862
1161
  }
863
1162
  }
864
1163
 
865
- factLinkHTML(label, factList) {
866
- const html = $("<span></span>").text(label);
1164
+ factLinkHTML(labelLang, factList) {
1165
+ const html = $("<span></span>");
1166
+ this._setLabelWithLang(html, labelLang);
867
1167
  if (factList.length > 0) {
868
1168
  html
869
1169
  .addClass("fact-link")
870
- .click(() => this.selectItem(factList[0].vuid))
871
- .mouseenter(() => factList.forEach(f => this._viewer.linkedHighlightFact(f)))
872
- .mouseleave(() => factList.forEach(f => this._viewer.clearLinkedHighlightFact(f)));
1170
+ .on("click", () => this.selectItem(factList[0].vuid))
1171
+ .on("mouseenter", () => factList.forEach(f => this._viewer.linkedHighlightFact(f)))
1172
+ .on("mouseleave", () => factList.forEach(f => this._viewer.clearLinkedHighlightFact(f)));
873
1173
  }
874
1174
  return html;
875
1175
  }
@@ -890,7 +1190,7 @@ export class Inspector {
890
1190
  const allMostRecent = fact.report.getAlignedFacts(mostRecent);
891
1191
  s = $("<span></span>")
892
1192
  .text(this.describeChange(mostRecent, fact))
893
- .append(this.factLinkHTML(mostRecent.periodString(), allMostRecent));
1193
+ .append(this.factLinkHTML({label: mostRecent.periodString()}, allMostRecent));
894
1194
 
895
1195
  }
896
1196
  else {
@@ -905,11 +1205,11 @@ export class Inspector {
905
1205
  }
906
1206
 
907
1207
  _updateValue(item, showAll, context) {
908
- const text = item.readableValue();
1208
+ const valueHTML = item.readableValueHTML();
909
1209
  const tr = $('tr.value', context);
910
- let v = text;
911
- if (!showAll) {
912
- const vv = wrapLabel(text, 120);
1210
+ const valueSpan = tr.find('td .value').empty();
1211
+ if (!item.isNumeric() && !showAll) {
1212
+ const vv = wrapLabel(valueHTML.textContent, 120);
913
1213
  if (vv.length > 1) {
914
1214
  tr.addClass("truncated");
915
1215
  tr.find('.show-all')
@@ -919,10 +1219,11 @@ export class Inspector {
919
1219
  else {
920
1220
  tr.removeClass('truncated');
921
1221
  }
922
- v = vv[0];
1222
+ valueSpan.text(vv[0]);
923
1223
  }
924
1224
  else {
925
1225
  tr.removeClass('truncated');
1226
+ valueSpan.append(valueHTML);
926
1227
  }
927
1228
 
928
1229
  // Only enable text block viewer for escaped, text block facts. This
@@ -932,17 +1233,12 @@ export class Inspector {
932
1233
  tr
933
1234
  .addClass('text-block')
934
1235
  .find('.expand-text-block')
935
- .off().click(() => this.showTextBlock(item));
1236
+ .off().on("click", () => this.showTextBlock(item));
936
1237
  }
937
1238
  else {
938
1239
  tr.removeClass('text-block');
939
1240
  }
940
1241
 
941
- const valueSpan = tr.find('td .value').empty().text(v);
942
- if (item instanceof Fact && (item.isNil() || item.isInvalidIXValue())) {
943
- valueSpan.wrapInner("<i></i>");
944
- }
945
-
946
1242
  }
947
1243
 
948
1244
  showTextBlock(item) {
@@ -951,6 +1247,38 @@ export class Inspector {
951
1247
  tbd.show();
952
1248
  }
953
1249
 
1250
+ _updateDataType(fact, context) {
1251
+ const dt = fact.concept()?.dataType();
1252
+ if (dt !== undefined) {
1253
+ $('tr.datatype td', context).text(dt.label());
1254
+ }
1255
+ else {
1256
+ $('tr.datatype', context).hide();
1257
+ }
1258
+ }
1259
+
1260
+ _updateBalance(fact, context) {
1261
+ const b = fact.concept()?.balance();
1262
+ if (b !== undefined) {
1263
+ $('tr.balance td', context).text(b.label());
1264
+ }
1265
+ else {
1266
+ $('tr.balance', context).hide();
1267
+ }
1268
+ }
1269
+
1270
+ _updateConcept(fact, context) {
1271
+ $('tr.concept td', context)
1272
+ .find('.text')
1273
+ .text(fact.conceptDisplayName())
1274
+ .attr("title", fact.conceptDisplayName())
1275
+ .end()
1276
+ .find('.clipboard-copy')
1277
+ .data('cb-text', fact.conceptDisplayName())
1278
+ .end();
1279
+
1280
+ }
1281
+
954
1282
  _updateEntityIdentifier(fact, context) {
955
1283
  $('tr.entity-identifier td', context)
956
1284
  .empty()
@@ -965,6 +1293,20 @@ export class Inspector {
965
1293
  return html;
966
1294
  }
967
1295
 
1296
+ _setLabelWithLang(elt, labelLang) {
1297
+ elt.removeAttr("lang");
1298
+ if (labelLang.label !== undefined) {
1299
+ elt.text(labelLang.label);
1300
+ if (labelLang.lang !== undefined) {
1301
+ elt.attr("lang", labelLang.lang);
1302
+ }
1303
+ }
1304
+ else {
1305
+ elt.text("");
1306
+ }
1307
+ return elt;
1308
+ }
1309
+
968
1310
  /*
969
1311
  * Build an accordian containing a summary of all nested facts/footnotes
970
1312
  * corresponding to the current viewer selection.
@@ -985,26 +1327,22 @@ export class Inspector {
985
1327
  const title = fs.minimallyUniqueLabel(fact);
986
1328
  if (fact instanceof Fact) {
987
1329
  factHTML = $(require('../html/fact-details.html'));
988
- $('.std-label', factHTML).text(fact.getLabelOrName("std", true));
989
- $('.documentation', factHTML).text(fact.getLabel("doc") || "");
990
- $('tr.concept td', factHTML)
991
- .find('.text')
992
- .text(fact.conceptName())
993
- .attr("title", fact.conceptName())
994
- .end()
995
- .find('.clipboard-copy')
996
- .data('cb-text', fact.conceptName())
997
- .end();
1330
+ this._setLabelWithLang($('.std-label', factHTML), fact.getLabelOrNameAndLang("std", true));
1331
+ this._setLabelWithLang($('.documentation', factHTML), fact.getLabelAndLang("doc"));
1332
+ this._updateConcept(fact, factHTML);
998
1333
  $('tr.period td', factHTML)
999
1334
  .text(fact.periodString());
1000
1335
  if (fact.isNumeric()) {
1001
1336
  $('tr.period td', factHTML).append(
1002
- $("<span></span>")
1003
- .addClass("analyse")
1337
+ $("<button></button>")
1338
+ .addClass(["analyse", "inline-button"])
1339
+ .attr("title", i18next.t("inspector.showAnalysisChart"))
1004
1340
  .text("")
1005
- .click(() => this.analyseDimension(fact, ["p"]))
1341
+ .on('click', () => this.analyseDimension(fact, ["p"]))
1006
1342
  );
1007
1343
  }
1344
+ this._updateDataType(fact, factHTML);
1345
+ this._updateBalance(fact, factHTML);
1008
1346
  this._updateEntityIdentifier(fact, factHTML);
1009
1347
  this._updateValue(fact, false, factHTML);
1010
1348
 
@@ -1024,19 +1362,18 @@ export class Inspector {
1024
1362
  $('#dimensions-label', factHTML).hide();
1025
1363
  }
1026
1364
  for (const aspect of taxonomyDefinedAspects) {
1027
- const h = $('<div class="dimension"></div>')
1028
- .text(aspect.label() || aspect.name())
1365
+ const h = this._setLabelWithLang($('<div class="dimension"></div>'), aspect.labelOrNameAndLang())
1029
1366
  .appendTo($('#dimensions', factHTML));
1030
1367
  if (fact.isNumeric()) {
1031
1368
  h.append(
1032
- $("<span></span>")
1033
- .addClass("analyse")
1369
+ $("<button></button>")
1370
+ .addClass(["analyse", "inline-button"])
1371
+ .attr("title", i18next.t("inspector.showAnalysisChart"))
1034
1372
  .text("")
1035
1373
  .on("click", () => this.analyseDimension(fact, [aspect.name()]))
1036
1374
  )
1037
1375
  }
1038
- const s = $('<div class="dimension-value"></div>')
1039
- .text(aspect.valueLabel())
1376
+ const s = this._setLabelWithLang($('<div class="dimension-value"></div>'), aspect.valueLabelAndLang())
1040
1377
  .appendTo(h);
1041
1378
  if (aspect.isNil()) {
1042
1379
  s.wrapInner("<i></i>");
@@ -1062,6 +1399,62 @@ export class Inspector {
1062
1399
  chart.analyseDimension(fact, dimensions);
1063
1400
  }
1064
1401
 
1402
+ toggleTooltip(icon) {
1403
+ if ($("#tooltip").hasClass("show")) {
1404
+ this.hideTooltip();
1405
+ }
1406
+ else {
1407
+ this.showTooltip(icon);
1408
+ }
1409
+ }
1410
+
1411
+ showTooltip(icon, hoverShow) {
1412
+ icon.closest(".has-tooltip").attr("aria-describedby", "tooltip");
1413
+ $("#tooltip .tooltip-text").text(i18next.t(`tooltips:${icon.data("tooltip-name")}`));
1414
+ $("#tooltip").addClass(hoverShow ? "hover-show" : "show");
1415
+ const glossaryLink = icon.data("tooltip-glossary-link");
1416
+ if (glossaryLink) {
1417
+ $("#tooltip").addClass("with-glossary-link");
1418
+ const url = new URL(GLOSSARY_URL);
1419
+ if (typeof glossaryLink === 'string' && glossaryLink.startsWith("#")) {
1420
+ url.hash = glossaryLink;
1421
+ }
1422
+ $("#tooltip .glossary-link a").attr("href", url.href);
1423
+ }
1424
+ else {
1425
+ $("#tooltip").removeClass("with-glossary-link");
1426
+ }
1427
+ this.positionTooltip(icon);
1428
+ }
1429
+
1430
+ hideTooltip(hoverShow) {
1431
+ const t = $("#tooltip");
1432
+ t.removeClass(hoverShow ? "hover-show" : "show");
1433
+ if (!t.hasClass("hover-show") && !t.hasClass("show")) {
1434
+ $(".has-tooltip").removeAttr("aria-describedby");
1435
+ }
1436
+ }
1437
+
1438
+
1439
+ positionTooltip(e) {
1440
+ const iconPos = e.offset();
1441
+ const tooltipWidth = 300;
1442
+ const clientWidth = document.documentElement.clientWidth;
1443
+ const right = clientWidth - Math.min(clientWidth - 30, iconPos.left + tooltipWidth);
1444
+ const left = Math.min(clientWidth - tooltipWidth, iconPos.left);
1445
+
1446
+ $("#tooltip")
1447
+ .css("inset","")
1448
+ .css("position", "fixed")
1449
+ .css("left", left)
1450
+ .css("right", right)
1451
+ .css("top", iconPos.top + 30);
1452
+
1453
+ if ($("#tooltip").get(0).getBoundingClientRect().bottom > document.documentElement.clientHeight) {
1454
+ $("#tooltip").css("top","").css("bottom", 30);
1455
+ }
1456
+ }
1457
+
1065
1458
  update() {
1066
1459
  const cf = this._currentItem;
1067
1460
  if (!cf) {
@@ -1083,6 +1476,7 @@ export class Inspector {
1083
1476
  this.updateFootnotes(cf);
1084
1477
  this.updateAnchoring(cf);
1085
1478
  $('div.references').empty().append(this._referencesHTML(cf));
1479
+ this.updateLabels(cf);
1086
1480
  $('#inspector .search-results .fact-list-item').removeClass('selected');
1087
1481
  $('#inspector .search-results .fact-list-item').filter((i, e) => $(e).data('ivid') == cf.vuid).addClass('selected');
1088
1482
 
@@ -1095,8 +1489,8 @@ export class Inspector {
1095
1489
  }
1096
1490
  }
1097
1491
  $('.duplicates .text').text(i18next.t('factDetails.duplicatesCount', { current: n + 1, total: ndup}));
1098
- $('.duplicates .prev').off().click(() => this.selectItem(duplicates[(n+ndup-1) % ndup].vuid));
1099
- $('.duplicates .next').off().click(() => this.selectItem(duplicates[(n+1) % ndup].vuid));
1492
+ $('.duplicates .prev').off().on("click", () => { this.selectItem(duplicates[(n+ndup-1) % ndup].vuid); $('.duplicates .prev').get(0).focus(); });
1493
+ $('.duplicates .next').off().on("click", () => { this.selectItem(duplicates[(n+1) % ndup].vuid); $('.duplicates .next').get(0).focus(); });
1100
1494
 
1101
1495
  this.getPeriodIncrease(cf);
1102
1496
  if (cf.isHidden()) {
@@ -1134,8 +1528,11 @@ export class Inspector {
1134
1528
  *
1135
1529
  * If itemIdList is omitted, the currently selected item list is reset to just
1136
1530
  * the primary item.
1531
+ *
1532
+ * noInspectorReset selects the fact, but doesn't close
1533
+ * search/summary/outline mode in the inspector.
1137
1534
  */
1138
- selectItem(vuid, itemIdList, noScroll) {
1535
+ selectItem(vuid, itemIdList, noScroll, noInspectorReset) {
1139
1536
  if (itemIdList === undefined) {
1140
1537
  this._currentItemList = [ this._reportSet.getItemById(vuid) ];
1141
1538
  }
@@ -1146,6 +1543,9 @@ export class Inspector {
1146
1543
  }
1147
1544
  }
1148
1545
  this.switchItem(vuid, noScroll);
1546
+ if (!noInspectorReset) {
1547
+ this.inspectorMode(undefined);
1548
+ }
1149
1549
  }
1150
1550
 
1151
1551
  /*
@@ -1195,7 +1595,7 @@ export class Inspector {
1195
1595
  return this._reportSet.availableLanguages()[0];
1196
1596
  }
1197
1597
 
1198
- setLanguage(lang) {
1598
+ setDocumentLanguage(lang) {
1199
1599
  this._viewerOptions.language = lang;
1200
1600
  }
1201
1601