@odoo/o-spreadsheet 16.3.3 → 16.3.5
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/dist/o-spreadsheet.cjs.js +107 -70
- package/dist/o-spreadsheet.d.ts +172 -170
- package/dist/o-spreadsheet.esm.js +107 -70
- package/dist/o-spreadsheet.iife.js +107 -70
- package/dist/o-spreadsheet.iife.min.js +330 -324
- package/package.json +5 -5
|
@@ -112,8 +112,8 @@ const SELECTION_BORDER_COLOR = "#3266ca";
|
|
|
112
112
|
const HEADER_BORDER_COLOR = "#C0C0C0";
|
|
113
113
|
const CELL_BORDER_COLOR = "#E2E3E3";
|
|
114
114
|
const BACKGROUND_CHART_COLOR = "#FFFFFF";
|
|
115
|
-
const MENU_ITEM_DISABLED_COLOR = "#CACACA";
|
|
116
115
|
const BG_HOVER_COLOR = "#EBEBEB";
|
|
116
|
+
const DISABLED_TEXT_COLOR = "#CACACA";
|
|
117
117
|
const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
|
|
118
118
|
const LINK_COLOR = "#01666b";
|
|
119
119
|
const FILTERS_COLOR = "#188038";
|
|
@@ -754,12 +754,20 @@ function getDefaultCellHeight(cell) {
|
|
|
754
754
|
return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
|
|
755
755
|
}
|
|
756
756
|
function computeTextWidth(context, text, style) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
757
|
+
const font = computeTextFont(style);
|
|
758
|
+
if (!textWidthCache[font]) {
|
|
759
|
+
textWidthCache[font] = {};
|
|
760
|
+
}
|
|
761
|
+
if (textWidthCache[font][text] === undefined) {
|
|
762
|
+
context.save();
|
|
763
|
+
context.font = font;
|
|
764
|
+
const textWidth = context.measureText(text).width;
|
|
765
|
+
context.restore();
|
|
766
|
+
textWidthCache[font][text] = textWidth;
|
|
767
|
+
}
|
|
768
|
+
return textWidthCache[font][text];
|
|
762
769
|
}
|
|
770
|
+
const textWidthCache = {};
|
|
763
771
|
function fontSizeInPixels(fontSize) {
|
|
764
772
|
return Math.round((fontSize * 96) / 72);
|
|
765
773
|
}
|
|
@@ -4214,7 +4222,6 @@ const readonlyAllowedCommands = new Set([
|
|
|
4214
4222
|
"SET_VIEWPORT_OFFSET",
|
|
4215
4223
|
"SELECT_SEARCH_NEXT_MATCH",
|
|
4216
4224
|
"SELECT_SEARCH_PREVIOUS_MATCH",
|
|
4217
|
-
"REFRESH_SEARCH",
|
|
4218
4225
|
"UPDATE_SEARCH",
|
|
4219
4226
|
"CLEAR_SEARCH",
|
|
4220
4227
|
"EVALUATE_CELLS",
|
|
@@ -5535,7 +5542,7 @@ css /* scss */ `
|
|
|
5535
5542
|
}
|
|
5536
5543
|
}
|
|
5537
5544
|
&.disabled {
|
|
5538
|
-
color: ${
|
|
5545
|
+
color: ${DISABLED_TEXT_COLOR};
|
|
5539
5546
|
cursor: not-allowed;
|
|
5540
5547
|
}
|
|
5541
5548
|
}
|
|
@@ -9519,9 +9526,9 @@ const ARG_TYPES = [
|
|
|
9519
9526
|
"META",
|
|
9520
9527
|
];
|
|
9521
9528
|
function arg(definition, description = "") {
|
|
9522
|
-
return makeArg(
|
|
9529
|
+
return makeArg(definition, description);
|
|
9523
9530
|
}
|
|
9524
|
-
function makeArg(str) {
|
|
9531
|
+
function makeArg(str, description) {
|
|
9525
9532
|
let parts = str.match(ARG_REGEXP);
|
|
9526
9533
|
let name = parts[1].trim();
|
|
9527
9534
|
let types = [];
|
|
@@ -9551,7 +9558,6 @@ function makeArg(str) {
|
|
|
9551
9558
|
defaultValue = param.trim().slice(8);
|
|
9552
9559
|
}
|
|
9553
9560
|
}
|
|
9554
|
-
let description = parts[3].trim();
|
|
9555
9561
|
const result = {
|
|
9556
9562
|
name,
|
|
9557
9563
|
description,
|
|
@@ -16373,7 +16379,7 @@ const insertFunctionMin = {
|
|
|
16373
16379
|
};
|
|
16374
16380
|
const categorieFunctionAll = {
|
|
16375
16381
|
name: _lt("All"),
|
|
16376
|
-
children: allFunctionListMenuBuilder
|
|
16382
|
+
children: [allFunctionListMenuBuilder],
|
|
16377
16383
|
};
|
|
16378
16384
|
function allFunctionListMenuBuilder() {
|
|
16379
16385
|
const fnNames = functionRegistry.getKeys();
|
|
@@ -19640,6 +19646,7 @@ class FindAndReplacePanel extends owl.Component {
|
|
|
19640
19646
|
state = owl.useState(this.initialState());
|
|
19641
19647
|
debounceTimeoutId;
|
|
19642
19648
|
showFormulaState = false;
|
|
19649
|
+
debouncedUpdateSearch;
|
|
19643
19650
|
findAndReplaceRef = owl.useRef("findAndReplace");
|
|
19644
19651
|
get hasSearchResult() {
|
|
19645
19652
|
return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
|
|
@@ -19649,11 +19656,16 @@ class FindAndReplacePanel extends owl.Component {
|
|
|
19649
19656
|
}
|
|
19650
19657
|
setup() {
|
|
19651
19658
|
this.showFormulaState = this.env.model.getters.shouldShowFormulas();
|
|
19659
|
+
this.debouncedUpdateSearch = debounce(this.updateSearch.bind(this), 200);
|
|
19652
19660
|
owl.onMounted(() => this.focusInput());
|
|
19653
19661
|
owl.onWillUnmount(() => {
|
|
19654
19662
|
this.env.model.dispatch("CLEAR_SEARCH");
|
|
19655
19663
|
this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.showFormulaState });
|
|
19656
19664
|
});
|
|
19665
|
+
owl.useEffect(() => {
|
|
19666
|
+
this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
|
|
19667
|
+
this.searchFormulas();
|
|
19668
|
+
}, () => [this.env.model.getters.shouldShowFormulas()]);
|
|
19657
19669
|
}
|
|
19658
19670
|
onInput(ev) {
|
|
19659
19671
|
this.state.toSearch = ev.target.value;
|
|
@@ -19673,10 +19685,6 @@ class FindAndReplacePanel extends owl.Component {
|
|
|
19673
19685
|
this.replace();
|
|
19674
19686
|
}
|
|
19675
19687
|
}
|
|
19676
|
-
onFocusSidePanel() {
|
|
19677
|
-
this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
|
|
19678
|
-
this.env.model.dispatch("REFRESH_SEARCH");
|
|
19679
|
-
}
|
|
19680
19688
|
searchFormulas() {
|
|
19681
19689
|
this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
|
|
19682
19690
|
show: this.state.searchOptions.searchFormulas,
|
|
@@ -19695,13 +19703,6 @@ class FindAndReplacePanel extends owl.Component {
|
|
|
19695
19703
|
searchOptions: this.state.searchOptions,
|
|
19696
19704
|
});
|
|
19697
19705
|
}
|
|
19698
|
-
debouncedUpdateSearch() {
|
|
19699
|
-
clearTimeout(this.debounceTimeoutId);
|
|
19700
|
-
this.debounceTimeoutId = setTimeout(() => {
|
|
19701
|
-
this.updateSearch();
|
|
19702
|
-
this.debounceTimeoutId = undefined;
|
|
19703
|
-
}, 400);
|
|
19704
|
-
}
|
|
19705
19706
|
replace() {
|
|
19706
19707
|
this.env.model.dispatch("REPLACE_SEARCH", {
|
|
19707
19708
|
replaceWith: this.state.replaceWith,
|
|
@@ -20932,6 +20933,7 @@ class Composer extends owl.Component {
|
|
|
20932
20933
|
(this.autoCompleteState.selectedIndex + 1) % this.autoCompleteState.values.length;
|
|
20933
20934
|
}
|
|
20934
20935
|
}
|
|
20936
|
+
this.updateCursorIfNeeded();
|
|
20935
20937
|
}
|
|
20936
20938
|
processTabKey(ev) {
|
|
20937
20939
|
ev.preventDefault();
|
|
@@ -21000,10 +21002,12 @@ class Composer extends owl.Component {
|
|
|
21000
21002
|
}
|
|
21001
21003
|
else {
|
|
21002
21004
|
ev.stopPropagation();
|
|
21005
|
+
this.updateCursorIfNeeded();
|
|
21003
21006
|
}
|
|
21004
|
-
|
|
21005
|
-
|
|
21006
|
-
|
|
21007
|
+
}
|
|
21008
|
+
updateCursorIfNeeded() {
|
|
21009
|
+
if (!this.env.model.getters.isSelectingForComposer()) {
|
|
21010
|
+
const { start, end } = this.contentHelper.getCurrentSelection();
|
|
21007
21011
|
this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
|
|
21008
21012
|
this.isKeyStillDown = true;
|
|
21009
21013
|
}
|
|
@@ -21061,7 +21065,12 @@ class Composer extends owl.Component {
|
|
|
21061
21065
|
};
|
|
21062
21066
|
});
|
|
21063
21067
|
if (searchTerm) {
|
|
21064
|
-
|
|
21068
|
+
if (searchTerm.toLowerCase() === "true" || searchTerm.toLowerCase() === "false") {
|
|
21069
|
+
values = [];
|
|
21070
|
+
}
|
|
21071
|
+
else {
|
|
21072
|
+
values = fuzzyLookup(searchTerm, values, (t) => t.text);
|
|
21073
|
+
}
|
|
21065
21074
|
}
|
|
21066
21075
|
else {
|
|
21067
21076
|
// alphabetical order
|
|
@@ -30644,7 +30653,14 @@ class FiltersPlugin extends CorePlugin {
|
|
|
30644
30653
|
this.history.update("tables", filterTables);
|
|
30645
30654
|
break;
|
|
30646
30655
|
case "DUPLICATE_SHEET":
|
|
30647
|
-
|
|
30656
|
+
const tables = {};
|
|
30657
|
+
for (const filterTable of Object.values(this.tables[cmd.sheetId] || {})) {
|
|
30658
|
+
if (filterTable) {
|
|
30659
|
+
const newFilterTable = deepCopy(filterTable);
|
|
30660
|
+
tables[newFilterTable.id] = newFilterTable;
|
|
30661
|
+
}
|
|
30662
|
+
}
|
|
30663
|
+
this.history.update("tables", cmd.sheetIdTo, tables);
|
|
30648
30664
|
break;
|
|
30649
30665
|
case "ADD_COLUMNS_ROWS":
|
|
30650
30666
|
this.onAddColumnsRows(cmd);
|
|
@@ -34193,6 +34209,7 @@ class FilterEvaluationPlugin extends UIPlugin {
|
|
|
34193
34209
|
}
|
|
34194
34210
|
exportForExcel(data) {
|
|
34195
34211
|
for (const sheetData of data.sheets) {
|
|
34212
|
+
const sheetId = sheetData.id;
|
|
34196
34213
|
for (const tableData of sheetData.filterTables) {
|
|
34197
34214
|
const tableZone = toZone(tableData.range);
|
|
34198
34215
|
const filters = [];
|
|
@@ -34208,20 +34225,20 @@ class FilterEvaluationPlugin extends UIPlugin {
|
|
|
34208
34225
|
if (!filter)
|
|
34209
34226
|
continue;
|
|
34210
34227
|
const valuesInFilterZone = filter.filteredZone
|
|
34211
|
-
? positions(filter.filteredZone)
|
|
34212
|
-
.map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId: sheetData.id, col, row }))
|
|
34213
|
-
.filter((cell) => cell.type !== CellValueType.empty)
|
|
34214
|
-
.map((cell) => cell.formattedValue)
|
|
34228
|
+
? positions(filter.filteredZone).map((position) => this.getters.getEvaluatedCell({ sheetId, ...position }).formattedValue)
|
|
34215
34229
|
: [];
|
|
34216
|
-
|
|
34217
|
-
|
|
34218
|
-
|
|
34219
|
-
|
|
34220
|
-
|
|
34221
|
-
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34230
|
+
if (filteredValues.length) {
|
|
34231
|
+
const xlsxDisplayedValues = valuesInFilterZone
|
|
34232
|
+
.filter((val) => val)
|
|
34233
|
+
.filter((val) => !filteredValues.includes(val));
|
|
34234
|
+
filters.push({
|
|
34235
|
+
colId: i,
|
|
34236
|
+
displayedValues: [...new Set(xlsxDisplayedValues)],
|
|
34237
|
+
displayBlanks: !filteredValues.includes("") && valuesInFilterZone.some((val) => !val),
|
|
34238
|
+
});
|
|
34239
|
+
}
|
|
34240
|
+
// In xlsx, filter header should ALWAYS be a string and should be unique in the table
|
|
34241
|
+
const headerPosition = { col: filter.col, row: filter.zoneWithHeaders.top, sheetId };
|
|
34225
34242
|
const headerString = this.getters.getEvaluatedCell(headerPosition).formattedValue;
|
|
34226
34243
|
const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);
|
|
34227
34244
|
headerNames.push(headerName);
|
|
@@ -36869,6 +36886,7 @@ class FindAndReplacePlugin extends UIPlugin {
|
|
|
36869
36886
|
searchFormulas: false,
|
|
36870
36887
|
};
|
|
36871
36888
|
toSearch = "";
|
|
36889
|
+
isSearchDirty = false;
|
|
36872
36890
|
// ---------------------------------------------------------------------------
|
|
36873
36891
|
// Command Handling
|
|
36874
36892
|
// ---------------------------------------------------------------------------
|
|
@@ -36896,14 +36914,21 @@ class FindAndReplacePlugin extends UIPlugin {
|
|
|
36896
36914
|
case "REDO":
|
|
36897
36915
|
case "REMOVE_COLUMNS_ROWS":
|
|
36898
36916
|
case "ADD_COLUMNS_ROWS":
|
|
36899
|
-
|
|
36917
|
+
case "EVALUATE_CELLS":
|
|
36918
|
+
case "UPDATE_CELL":
|
|
36919
|
+
this.isSearchDirty = true;
|
|
36900
36920
|
break;
|
|
36901
36921
|
case "ACTIVATE_SHEET":
|
|
36902
|
-
case "REFRESH_SEARCH":
|
|
36903
36922
|
this.refreshSearch();
|
|
36904
36923
|
break;
|
|
36905
36924
|
}
|
|
36906
36925
|
}
|
|
36926
|
+
finalize() {
|
|
36927
|
+
if (this.isSearchDirty) {
|
|
36928
|
+
this.refreshSearch();
|
|
36929
|
+
this.isSearchDirty = false;
|
|
36930
|
+
}
|
|
36931
|
+
}
|
|
36907
36932
|
// ---------------------------------------------------------------------------
|
|
36908
36933
|
// Getters
|
|
36909
36934
|
// ---------------------------------------------------------------------------
|
|
@@ -36959,6 +36984,11 @@ class FindAndReplacePlugin extends UIPlugin {
|
|
|
36959
36984
|
if (this.toSearch) {
|
|
36960
36985
|
for (const cell of Object.values(cells)) {
|
|
36961
36986
|
const { col, row } = this.getters.getCellPosition(cell.id);
|
|
36987
|
+
const isColHidden = this.getters.isColHidden(sheetId, col);
|
|
36988
|
+
const isRowHidden = this.getters.isRowHidden(sheetId, row);
|
|
36989
|
+
if (isColHidden || isRowHidden) {
|
|
36990
|
+
continue;
|
|
36991
|
+
}
|
|
36962
36992
|
if (cell &&
|
|
36963
36993
|
this.currentSearchRegex &&
|
|
36964
36994
|
this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
|
|
@@ -38596,10 +38626,8 @@ class SortPlugin extends UIPlugin {
|
|
|
38596
38626
|
}
|
|
38597
38627
|
updateCellCommands.push(newCellValues);
|
|
38598
38628
|
}
|
|
38599
|
-
for (const cmd of updateCellCommands) {
|
|
38600
|
-
this.dispatch("UPDATE_CELL", cmd);
|
|
38601
|
-
}
|
|
38602
38629
|
}
|
|
38630
|
+
updateCellCommands.forEach((cmdPayload) => this.dispatch("UPDATE_CELL", cmdPayload));
|
|
38603
38631
|
}
|
|
38604
38632
|
/**
|
|
38605
38633
|
* Return the distances between main merge cells in the zone.
|
|
@@ -42864,6 +42892,11 @@ css /* scss */ `
|
|
|
42864
42892
|
.o-sidePanelButton:enabled {
|
|
42865
42893
|
cursor: pointer;
|
|
42866
42894
|
}
|
|
42895
|
+
|
|
42896
|
+
.o-sidePanelButton:disabled {
|
|
42897
|
+
color: ${DISABLED_TEXT_COLOR};
|
|
42898
|
+
}
|
|
42899
|
+
|
|
42867
42900
|
.o-sidePanelButton:last-child {
|
|
42868
42901
|
margin-right: 0px;
|
|
42869
42902
|
}
|
|
@@ -43293,11 +43326,9 @@ TopBarComposer.props = {
|
|
|
43293
43326
|
onComposerContentFocused: Function,
|
|
43294
43327
|
};
|
|
43295
43328
|
|
|
43296
|
-
// -----------------------------------------------------------------------------
|
|
43297
|
-
// TopBar
|
|
43298
|
-
// -----------------------------------------------------------------------------
|
|
43299
43329
|
css /* scss */ `
|
|
43300
43330
|
.o-font-size-editor {
|
|
43331
|
+
height: calc(100% - 4px);
|
|
43301
43332
|
input.o-font-size {
|
|
43302
43333
|
outline-color: ${SELECTION_BORDER_COLOR};
|
|
43303
43334
|
height: 20px;
|
|
@@ -43310,13 +43341,13 @@ css /* scss */ `
|
|
|
43310
43341
|
input::-webkit-inner-spin-button {
|
|
43311
43342
|
-webkit-appearance: none;
|
|
43312
43343
|
}
|
|
43313
|
-
|
|
43314
|
-
|
|
43315
|
-
|
|
43316
|
-
|
|
43317
|
-
|
|
43318
|
-
|
|
43319
|
-
|
|
43344
|
+
}
|
|
43345
|
+
.o-text-options > div {
|
|
43346
|
+
cursor: pointer;
|
|
43347
|
+
line-height: 26px;
|
|
43348
|
+
padding: 3px 12px;
|
|
43349
|
+
&:hover {
|
|
43350
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
43320
43351
|
}
|
|
43321
43352
|
}
|
|
43322
43353
|
`;
|
|
@@ -43381,6 +43412,7 @@ class FontSizeEditor extends owl.Component {
|
|
|
43381
43412
|
FontSizeEditor.props = {
|
|
43382
43413
|
onToggle: Function,
|
|
43383
43414
|
dropdownStyle: String,
|
|
43415
|
+
class: String,
|
|
43384
43416
|
};
|
|
43385
43417
|
|
|
43386
43418
|
// -----------------------------------------------------------------------------
|
|
@@ -46549,15 +46581,10 @@ function addAutoFilter(table) {
|
|
|
46549
46581
|
`;
|
|
46550
46582
|
}
|
|
46551
46583
|
function addFilterColumns(table) {
|
|
46552
|
-
const tableZone = toZone(table.range);
|
|
46553
46584
|
const columns = [];
|
|
46554
|
-
for (const
|
|
46555
|
-
const filter = table.filters[i];
|
|
46556
|
-
if (!filter || !filter.filteredValues.length) {
|
|
46557
|
-
continue;
|
|
46558
|
-
}
|
|
46585
|
+
for (const filter of table.filters) {
|
|
46559
46586
|
const colXml = escapeXml /*xml*/ `
|
|
46560
|
-
<filterColumn ${formatAttributes([["colId",
|
|
46587
|
+
<filterColumn ${formatAttributes([["colId", filter.colId]])}>
|
|
46561
46588
|
${addFilter(filter)}
|
|
46562
46589
|
</filterColumn>
|
|
46563
46590
|
`;
|
|
@@ -46566,9 +46593,10 @@ function addFilterColumns(table) {
|
|
|
46566
46593
|
return columns;
|
|
46567
46594
|
}
|
|
46568
46595
|
function addFilter(filter) {
|
|
46569
|
-
const filterValues = filter.
|
|
46596
|
+
const filterValues = filter.displayedValues.map((val) => escapeXml /*xml*/ `<filter ${formatAttributes([["val", val]])}/>`);
|
|
46597
|
+
const filterAttributes = filter.displayBlanks ? [["blank", 1]] : [];
|
|
46570
46598
|
return escapeXml /*xml*/ `
|
|
46571
|
-
<filters>
|
|
46599
|
+
<filters ${formatAttributes(filterAttributes)}>
|
|
46572
46600
|
${joinXmlNodes(filterValues)}
|
|
46573
46601
|
</filters>
|
|
46574
46602
|
`;
|
|
@@ -46687,8 +46715,13 @@ function addHyperlinks(construct, data, sheetIndex) {
|
|
|
46687
46715
|
const sheetId = parseSheetUrl(url);
|
|
46688
46716
|
const sheet = data.sheets.find((sheet) => sheet.id === sheetId);
|
|
46689
46717
|
const location = sheet ? `${sheet.name}!A1` : INCORRECT_RANGE_STRING;
|
|
46718
|
+
const hyperlinkAttributes = [
|
|
46719
|
+
["display", label],
|
|
46720
|
+
["location", location],
|
|
46721
|
+
["ref", xc],
|
|
46722
|
+
];
|
|
46690
46723
|
linkNodes.push(escapeXml /*xml*/ `
|
|
46691
|
-
<hyperlink
|
|
46724
|
+
<hyperlink ${formatAttributes(hyperlinkAttributes)}/>
|
|
46692
46725
|
`);
|
|
46693
46726
|
}
|
|
46694
46727
|
else {
|
|
@@ -46697,8 +46730,12 @@ function addHyperlinks(construct, data, sheetIndex) {
|
|
|
46697
46730
|
type: XLSX_RELATION_TYPE.hyperlink,
|
|
46698
46731
|
targetMode: "External",
|
|
46699
46732
|
});
|
|
46733
|
+
const hyperlinkAttributes = [
|
|
46734
|
+
["r:id", linkRelId],
|
|
46735
|
+
["ref", xc],
|
|
46736
|
+
];
|
|
46700
46737
|
linkNodes.push(escapeXml /*xml*/ `
|
|
46701
|
-
<hyperlink
|
|
46738
|
+
<hyperlink ${formatAttributes(hyperlinkAttributes)}/>
|
|
46702
46739
|
`);
|
|
46703
46740
|
}
|
|
46704
46741
|
}
|
|
@@ -47606,6 +47643,6 @@ exports.setTranslationMethod = setTranslationMethod;
|
|
|
47606
47643
|
exports.tokenize = tokenize;
|
|
47607
47644
|
|
|
47608
47645
|
|
|
47609
|
-
__info__.version = '16.3.
|
|
47610
|
-
__info__.date = '2023-
|
|
47611
|
-
__info__.hash = '
|
|
47646
|
+
__info__.version = '16.3.5';
|
|
47647
|
+
__info__.date = '2023-07-26T12:10:02.115Z';
|
|
47648
|
+
__info__.hash = '328bce4';
|