├── .github └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md ├── .gitignore ├── .vscode-test.mjs ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── csvEditorHtml ├── autoFill.ts ├── beforeDomLoaded.ts ├── browser │ ├── CHANGELOG.md │ ├── afterDomLoadedBrowser.ts │ ├── beforeDomLoadedBrowser.ts │ ├── browser.css │ ├── browser.ts │ ├── build │ │ └── build.ts │ ├── card-site.css │ ├── cookies.css │ ├── cookies.ts │ ├── imgs │ │ ├── GitHub-Mark-32px.png │ │ ├── GitHub-Mark-Light-32px.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-128.png │ │ ├── favicon-192.png │ │ ├── favicon-196.png │ │ ├── favicon-32.png │ │ ├── favicon-48.png │ │ ├── favicon-96.png │ │ └── old_icon │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-128.png │ │ │ ├── favicon-192.png │ │ │ ├── favicon-196.png │ │ │ ├── favicon-32.png │ │ │ ├── favicon-96.png │ │ │ └── favicon-svg.svg │ ├── indexBrowser.html │ ├── indexBrowserDebug.html │ ├── legalNotice.html │ ├── privacy.html │ ├── robots.txt │ └── third-party │ │ ├── LICENSE-iconv-lite.txt │ │ ├── LICENSE-node-chardet.txt │ │ ├── bulma-toast │ │ ├── LICENSE │ │ └── bulma-toast.min.js │ │ ├── parcel-iconv-chardet.js │ │ ├── sweetalert2-themes │ │ ├── LICESNE │ │ └── dark.min.css │ │ └── sweetalert2 │ │ ├── LICENSE │ │ ├── sweetalert2.min.css │ │ └── sweetalert2.min.js ├── dark.css ├── findWidget.ts ├── high_contrast.css ├── index.html ├── io.ts ├── light.css ├── main.css ├── main.ts ├── progressbar.ts ├── settingsOverwrite.css ├── test │ ├── init.ts │ ├── suite │ │ ├── autoFill │ │ │ ├── altKey.test.ts │ │ │ ├── dates.test.ts │ │ │ ├── endsWithFloat.test.ts │ │ │ ├── endsWithInt.test.ts │ │ │ ├── floats.test.ts │ │ │ ├── groups.test.ts │ │ │ ├── ints.test.ts │ │ │ ├── justCopy.test.ts │ │ │ ├── monthNames.test.ts │ │ │ ├── startsWithFloat.test.ts │ │ │ ├── startsWithInt.test.ts │ │ │ └── types.ts │ │ └── tryToGuessHasHeader │ │ │ └── guessHasHeader.test.ts │ └── vitest.config.ts ├── tsconfig.json ├── types.d.ts ├── ui.ts └── util.ts ├── docs ├── autoFillBehavior.md ├── autoFillDiagram.drawio ├── autoFillDiagram.jpg ├── quotes.md └── visualAndPhysicalIndices.md ├── exampleCSV └── autoFill.xlsx ├── images ├── logo.afdesign ├── logo.png ├── logo_old.png └── titleImg.gif ├── out ├── configurationHelper.js ├── extension.js ├── getHtml.js ├── instanceManager.js └── util.js ├── package-lock.json ├── package.json ├── promtUploadbuild.js ├── src ├── configurationHelper.ts ├── extension.ts ├── getHtml.ts ├── instanceManager.ts ├── test │ └── suite │ │ └── extension.test.ts └── util.ts ├── thirdParty ├── big.js │ ├── LICENCE │ ├── big.min.js │ └── info.md ├── fortawesome │ ├── LICENSE.txt │ ├── fontawesome-free │ │ ├── css │ │ │ └── all.min.css │ │ └── webfonts │ │ │ ├── fa-regular-400.woff2 │ │ │ └── fa-solid-900.woff2 │ └── info.md ├── handsontable │ ├── LICENSE │ ├── handsontable.css │ ├── handsontable.d.ts │ ├── handsontable.js │ ├── handsontable.min.css │ ├── handsontable.min.js │ └── info.md ├── info.md ├── mousetrap │ ├── LICENSE │ ├── info.md │ ├── mousetrap.js │ ├── mousetrap.min.js │ └── plugins │ │ └── global-bind │ │ ├── README.md │ │ ├── mousetrap-global-bind.js │ │ └── mousetrap-global-bind.min.js ├── papaparse │ ├── Changelog.md │ ├── LICENSE │ ├── info.md │ ├── papaparse.d.ts │ ├── papaparse.js │ ├── papaparse.min.js │ ├── papaparse.min.umd.js │ └── papaparse.umd.js ├── regression │ ├── LICENSE │ ├── info.md │ ├── regression.d.ts │ ├── regression.js │ └── regression.min.js └── toFormat │ ├── LICENSE │ ├── info.md │ └── toFormat.min.js ├── tsconfig.json └── tslint.json /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: I want to report a bug. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### What OS? 11 | - Windows 12 | - Mac 13 | - Linux 14 | 15 | 16 | ### Description 17 | A clear and concise description of what the bug is. 18 | 19 | ### Expected behavior 20 | 21 | What you'd expect to happen. 22 | 23 | 24 | ### Steps to reproduce 25 | A step by step guide how to reproduce this issue/bug. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: I want to suggest a feature 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Version? 11 | - vs code plugin 12 | - browser 13 | - both? 14 | 15 | ### Feature 16 | 17 | Describe the feature you want to suggest. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.map 2 | csvEditorHtml/out/ 3 | node_modules 4 | TODO.md 5 | .vscode-test 6 | scripts.md 7 | backup 8 | *.vsix 9 | out/test 10 | cypress 11 | csvEditorHtml/browser/dist 12 | csvEditorHtml/browser/imgs/private 13 | images/logo_backup/* 14 | .vscode-test-web/* 15 | -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'out/test/**/*.test.js', 5 | }); -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "eg2.tslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [{ 8 | "name": "Run Extension", 9 | "type": "extensionHost", 10 | "request": "launch", 11 | "runtimeExecutable": "${execPath}", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: watch" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "runtimeExecutable": "${execPath}", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/out/test" 28 | ], 29 | "outFiles": [ 30 | "${workspaceFolder}/out/test/**/*.js" 31 | ], 32 | "preLaunchTask": "npm: watch" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "spellright.language": [ 12 | "en" 13 | ], 14 | "spellright.documentTypes": [ 15 | "markdown", 16 | "latex", 17 | "html", 18 | "plaintext" 19 | ], 20 | "typescript.tsdk": "node_modules/typescript/lib", 21 | "cmake.configureOnOpen": false 22 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .vscode-test.mjs 4 | .github/** 5 | out/test/** 6 | src/** 7 | csvEditorHtml/index.html 8 | *.vsix 9 | .gitignore 10 | vsc-extension-quickstart.md 11 | **/tsconfig.json 12 | **/tslint.json 13 | **/*.map 14 | **/*.ts 15 | TODO.md 16 | node_modules/@types 17 | node_modules/@microsoft 18 | node_modules/@vscode/webview-ui-toolkit/**/* 19 | !node_modules/@vscode/webview-ui-toolkit/dist/toolkit.min.js 20 | node_modules/react/**/* 21 | node_modules/tabbable/**/* 22 | node_modules/exenv-es6/**/* 23 | node_modules/loose-envify/**/* 24 | node_modules/js-tokens/**/* 25 | node_modules/tslib/**/* 26 | node_modules/dayjs/**/* 27 | !node_modules/dayjs/dayjs.min.js 28 | !node_modules/dayjs/plugin/customParseFormat.js 29 | backup 30 | images/** 31 | !images/logo.png 32 | scripts.md 33 | thirdParty/info.md 34 | thirdParty/handsontable/handsontable.css 35 | thirdParty/handsontable/handsontable.d.ts 36 | thirdParty/handsontable/handsontable.js 37 | thirdParty/handsontable/handsontable.js.map 38 | thirdParty/mousetrap/mousetrap.js 39 | thirdParty/mousetrap/plugins/global-bind/mousetrap-global-bind.js 40 | thirdParty/mousetrap/plugins/global-bind/README.md 41 | thirdParty/papaparse/papaparse.js 42 | thirdParty/papaparse/papaparse.min.js 43 | thirdParty/papaparse/papaparse.umd.js 44 | thirdParty/papaparse/Changelog.md 45 | thirdParty/regression/regression.js 46 | promtUploadbuild.js 47 | cypress 48 | csvEditorHtml/browser/** 49 | csvEditorHtml/out/browser/** 50 | csvEditorHtml/out/test/** 51 | # not used anymore but we might need to copy certain rules... 52 | .vscode-test-web/** 53 | exampleCSV/** 54 | CHANGELOG.md 55 | docs/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 janisdd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /csvEditorHtml/beforeDomLoaded.ts: -------------------------------------------------------------------------------- 1 | //--- called before dom (body) is rendered 2 | 3 | document.documentElement.style.setProperty('--extension-options-bar-display', initialConfig?.optionsBarAppearance === "collapsed" ? `none` : `block`) 4 | 5 | //--- side-panel 6 | document.documentElement.style.setProperty('--extension-side-panel-display', initialConfig?.sidePanelAppearance === "collapsed" ? `none` : `flex`) 7 | document.documentElement.style.setProperty('--extension-side-panel-expand-icon-display', initialConfig?.sidePanelAppearance === "collapsed" ? `block` : `none`) 8 | document.documentElement.style.setProperty('--extension-side-panel-collapse-icon-display', initialConfig?.sidePanelAppearance === "collapsed" ? `none` : `block`) 9 | 10 | //--- table style 11 | document.documentElement.style.setProperty('--extension-table-font-family', initialConfig?.fontFamilyInTable === "sameAsCodeEditor" ? `var(--vscode-editor-font-family)` : `inherit`) 12 | 13 | -------------------------------------------------------------------------------- /csvEditorHtml/browser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log of the browser version 2 | 3 | note that the changelog is also present on the website when you click on the version. Keep them in sync! 4 | 5 | ### [Unreleased] 6 | 7 | ## 1.7.3 8 | - (same as vs code extension 0.11.4) 9 | - added feature #194 - Better unsaved changes indicator 10 | - added feature #193 - Copy column header to clipboard 11 | - updated `has header` ui tooltip 12 | 13 | ## 1.7.2 14 | - (same as vs code extension 0.11.3) 15 | - fixed delayed `readOption_hasHeader` feature where the options is automatically enabled as soon as the table has more than 1 row 16 | - lowered the context menu sub items display delay from 300ms to 100ms 17 | - context menu sub menu indicator is now centered 18 | - side bar tooltips is not longer clipped 19 | - removed preview tag (user requested this) 20 | 21 | ## 1.7.1 22 | - (same as vs code extension v0.11.1) 23 | - fixed issue #174 - added option `overwriteExceptEmpty` to the `pasteBehavior` setting 24 | - it only overwrites the cell content if the paste cell is not empty 25 | 26 | ## 1.7.0 27 | - (same as vs code extension v0.11.0) 28 | - added option `forceQuoteLeadingWhitespace` and `forceQuoteTrailingWhitespace` which will quote fields when they have leading or trailing whitespace 29 | - fixed issue #163 by changed option `retainQuoteInformation` from `bool` to `string` with the options 30 | - `none` (old false), `determinedByColumns` (old true) and `full` (new default) 31 | - `full` tracks the quote information for every cell individually 32 | - fixed issue where hiding comment rows hides the wrong rows 33 | - happened when data was sorted and then rows were removed 34 | 35 | ## 1.6.0 36 | - (same as vs code extension v0.10.0) 37 | - added feature #161 - `excelLike` option to `dragToAutoFill` setting 38 | - this is now the default 39 | - added feature #155 - zoom the table content (only cells, not headers) 40 | - ui buttons are added or use mouse + ctrl (same as vs code font size feature) 41 | - added option `pasteBehavior` to determine where the old cells should be moved after a paste operation , see #156 42 | - added option `pasteScrollBehavior` to determine where to scroll after a paste operation, see #156 43 | 44 | ## 1.5.1 45 | - (same as vs code extension v0.9.1) 46 | - fixed issue #146 - doubleClickRowHandleForcedHeight was a string (package config must be a number) 47 | - fixed issue #144 - tools menu item background was the same as foreground color 48 | 49 | ## 1.5.0 50 | - (same as vs code extension v0.9.0) 51 | - changed logo 52 | - fixed issue #140 - add feature to hide columns 53 | - fixed issue #31, #139 - `ctrl+tab`, `ctrl+shift+tab`, `alt+1`, ..., `alt+9` (on linux/windows) is not longer consumed by handsontable 54 | 55 | ## 1.4.1 56 | - (same as vs code extension v0.8.1) 57 | - fixed issue #132 - search hit are again highlighted when cells contain urls 58 | - maybe fixed issue #124 by - adding option to set delimiters to guess manually 59 | 60 | ## 1.4.0 61 | - (same as vs code extension v0.8.0) 62 | - trim cells can now be undone (not for header cells) 63 | - fixed issue #122 - add option customize text color 64 | - added feature #130 - add feature resize rows 65 | - works the same as `doubleClickColumnHandleForcedWith` (auto resize is a bit better implemented) 66 | - adds feature #109 - open urls in browser 67 | - controled via `convertUrlsToLinkTags` setting 68 | 69 | ## 1.3.6 70 | - (same as vs code extension v0.7.6) 71 | - fixed issue #110 - Add feature to swap rows and columns 72 | 73 | ## 1.3.5 74 | - (same as vs code extension v0.7.5) 75 | - fixed issue #111 - add feature resize columns and rows 76 | - context menu item (which sets the column width(s) to `doubleClickColumnHandleForcedWith`) 77 | - fixed many issues that would break the `doubleClickColumnHandleForcedWith` feature 78 | - fixed issue #115 - Add newline at the end of file 79 | - new setting `finalNewLine` which controls how the final newline is handled 80 | - fixed issue where edit button was not shown for `dynamic csv` mode (rainbow csv extension) 81 | 82 | ## 1.3.4 83 | - (same as vs code extension v0.7.4) 84 | - fixed issue #112 - added keyboard shortcut to delete the current row (`ctrl+shift+alt+minus`) 85 | - behaves the same was as context menu action (actually uses it) 86 | - also works on mac (because normally `alt+-` [and or shift] will insert a dash) 87 | - added option `csv-edit.showDeleteColumnHeaderButton` true: shows a delete column button in the column header (on hover), false: not (fixes issue #113) 88 | - added option `csv-edit.showDeleteRowHeaderButton` true: shows a delete row button in the row header (on hover), false: not (fixes issue #113) 89 | - fixed issue #114 - "dynamic csv" is now also supported as *language id* 90 | 91 | 92 | ## 1.3.3 93 | - (same as vs code extension v0.7.3) 94 | - added option `csv-edit.autoColumnWidthsIgnoreComments` to ignore comment cells for auto column sizing 95 | - `csv-edit.lastRowOrFirstRowNavigationBehavior` default is now `stop` (was `wrap`) 96 | - `csv-edit.lastColumnOrFirstColumnNavigationBehavior` default is now `stop` (was `wrap`) 97 | 98 | ## 1.3.2 99 | - (same as vs code extension v0.7.2) 100 | - fixed issue #97 - fixed rows/columns is ignored if `hasHeader` was changed 101 | 102 | ## 1.3.1 103 | 104 | - (same as vs code extension v0.7.1) 105 | - fixed issue 94 - find widget buttons are outside if panel 106 | - find widget design is now more similar to vs code ones 107 | 108 | ## 1.3.0 109 | 110 | - (same as vs code extension v0.7.0) 111 | - fixed issue #73 - changed style to match vs code ones (with webview-ui-toolki) 112 | - however, there are some issues with 113 | - text input: input event don't get triggered when some table cell has focus... 114 | - dropdown: chaning font size is not possible 115 | - removed bulma css 116 | 117 | ## 1.2.9 118 | 119 | - (same as vs code extension v0.6.10) 120 | - fixed issue 93 - regex search ignored match-case option) 121 | 122 | ## 1.2.8 123 | 124 | - (same as vs code extension v0.6.9) 125 | - added option `pasteMode` to control how clipboard content is pasted into the table 126 | - it allows to ignore row/column separators (\n, \t) to paste the data into "fewer" cells 127 | - added setting `fontFamilyInTable` to control which font is used for the table 128 | - added option `initialOnly_correctRowAlwaysFirstColumn` to setting `openTableAndSelectCellAtCursorPos` to only open the correct row but always column one 129 | - this is now the new default 130 | - in the setting `openTableAndSelectCellAtCursorPos` the option `initalOnly` was renamed to `initialOnly_correctRowAndColumn` 131 | - added settings `lastRowOrFirstRowNavigationBehavior` and `lastColumnOrFirstColumnNavigationBehavior` to control if we wrap ad the start/end of rows/columns while navigating 132 | - works with `lastRowEnterBehavior`, `lastColumnTabBehavior` (the `default` option will apply these two new settings) 133 | - see https://handsontable.com/docs/6.2.2/Options.html#autoWrapRow 134 | 135 | ## 1.2.7 136 | 137 | - (same as vs code extension v0.6.8) 138 | - added option `openTableAndSelectCellAtCursorPos` to open the table and selected the cell where the cursor was (fixed feature request #83) 139 | - on by default! 140 | - note for multi character delimiters it might not work properly (but should most of the time, only tested a few cases and it worked) 141 | - after `reset dat and apply read options` the scroll position and selected cell is restored (issue #84) 142 | 143 | ## 1.2.6 144 | 145 | - (same as vs code extension v0.6.6) 146 | - fixed issue 80: cell editor is not commited after pressing `ctrl/cmd+s`, so changes are not applied to file 147 | 148 | ## 1.2.5 149 | 150 | - (same as vs code extension v0.6.5) 151 | - fixed issue 77: Newlines inserted into pasted data (clipboard) 152 | - fixed via new internal handsontable version 153 | 154 | ## 1.2.4 155 | 156 | - (same as vs code extension v0.6.4) 157 | - fixed issue 72: copy limited to 1000 cells 158 | - changed the limit to 10000000 159 | 160 | ## 1.2.3 161 | 162 | - (same as vs code extension v0.6.3) 163 | - fixed issue 70: Removing columns doesn't remove header 164 | - undo/redo does not work with column headers 165 | 166 | ## 1.2.2 167 | 168 | - (same as vs code extension v0.6.2) 169 | - fixed issue 63: column header cells can now be edited 170 | - fixed issue 66: added readonly mode 171 | - fixed issue 64: some non-text keys (e.g. volume controls) not longer clear cell values 172 | - also compound characters also not clear cell values 173 | 174 | ## 1.2.1 175 | 176 | - (same as vs code extension v0.6.1) 177 | - added button to resize column to match their content 178 | 179 | ## 1.2.0 180 | 181 | - (same as vs code extension v0.6.0) 182 | - fixed issue where reordering/sorting breaks inserting 183 | - new logo 184 | 185 | ## 1.1.6 186 | 187 | - fixed issue in papaparse where multi-character delimiters won't work 188 | 189 | ## 1.1.5 190 | 191 | - added shortcuts to insert row above (ctrl+shift+alt+up) / below (ctrl+shift+alt+down) and insert column left (ctrl+shift+alt+left) / right (ctrl+shift+alt+right) 192 | 193 | ## 1.1.4 194 | 195 | - (same as vs code extension v0.5.6) 196 | - added option to handle empty values (null, undefined and empty (string) values) `quoteEmptyOrNullFields` 197 | - takes always precedence over retainQuoteInformation 198 | 199 | ## 1.1.3 200 | 201 | - (same as vs code extension v0.5.5) 202 | - fixed papaparse issue where `null` values (e.g. when a new row/col was added via handsontable) did't respect the `retainQuoteInformation` 203 | 204 | ## 1.1.2 205 | 206 | - (same as vs code extension v0.5.3) 207 | - added additional buttons to insert rows/columns 208 | - fixed issue where the table height is not properly resized when the windows is resized 209 | - resetting data now keeps column widths 210 | - when removing a column left from a column the (right) column keeps it size 211 | - the width is also shifted 212 | - added hint what option `EscapeChar` does (escapes the `QuoteChar` inside field values) 213 | 214 | ## 1.1.1 215 | 216 | - (same as vs code extension v0.5.2) 217 | - added hint to readme how to set csv for different file types in vs code 218 | - was asked several times 219 | - read option `has header` can now be toggled even if the table has only 1 row 220 | this will automatically enable the option as soon as the table gets >= 2 rows 221 | - side bar stats are only updated if the side bar is visible 222 | - opening the side bar will calculate the stats (reselect is not needed) 223 | 224 | ## 1.1.0 225 | - added multi column sorting (custom handsontable version bump) 226 | - left side panel (stats) now supports two different number styles 227 | 228 | ## 1.0.1 229 | - toast now displays the exported file encoding 230 | - side panel can now be resized 231 | 232 | ## 1.0.0 233 | - initial release -------------------------------------------------------------------------------- /csvEditorHtml/browser/afterDomLoadedBrowser.ts: -------------------------------------------------------------------------------- 1 | /* stuff to do after dom is fully loaded but before scripts */ 2 | executeAfterDomLoadedQueue.forEach(p => p()) -------------------------------------------------------------------------------- /csvEditorHtml/browser/beforeDomLoadedBrowser.ts: -------------------------------------------------------------------------------- 1 | 2 | //--- should be called before all scripts 3 | 4 | const themeDarkBgColor = `#003b49` 5 | const themeLightBgColor = `#fdf6e3` 6 | //things to execute after the dom has loaded but before other scripts run 7 | const executeAfterDomLoadedQueue: Array<() => void> = [] 8 | 9 | //overwrite the empty initial config 10 | 11 | var initialVars: InitialVars = { 12 | isWatchingSourceFile: false, 13 | sourceFileCursorLineIndex: null, 14 | sourceFileCursorColumnIndex: null, 15 | isCursorPosAfterLastColumn: false, 16 | openTableAndSelectCellAtCursorPos: 'initialOnly_correctRowAlwaysFirstColumn', 17 | os: 'web', 18 | } 19 | 20 | var initialConfig: EditCsvConfig | undefined = { 21 | highlightCsvComments: true, 22 | lastRowEnterBehavior: 'default', 23 | lastColumnTabBehavior: 'default', 24 | lastRowOrFirstRowNavigationBehavior: 'stop', 25 | lastColumnOrFirstColumnNavigationBehavior: 'stop', 26 | optionsBarAppearance: "collapsed", 27 | tryToGuessHasHeader: false, 28 | readOption_comment: "#", 29 | readOption_quoteChar: '"', 30 | readOption_escapeChar: '"', 31 | readOption_delimiter: "", 32 | readOption_delimitersToGuess: [",", "\t", "|", ";", "\u001e", "\u001f"], 33 | readOption_hasHeader: "false", 34 | writeOption_comment: "#", 35 | writeOption_delimiter: "", 36 | writeOption_quoteChar: '"', 37 | writeOption_escapeChar: '"', 38 | writeOption_hasHeader: "false", 39 | doubleClickColumnHandleForcedWith: 200, 40 | doubleClickRowHandleForcedHeight: 106, 41 | openSourceFileAfterApply: false, 42 | selectTextAfterBeginEditCell: false, 43 | quoteAllFields: false, 44 | quoteEmptyOrNullFields: 'false', 45 | initiallyHideComments: false, 46 | autoColumnWidthsIgnoreComments: true, 47 | enableWrapping: true, 48 | initialColumnWidth: 0, 49 | retainQuoteInformation: 'full', 50 | forceQuoteLeadingWhitespace: false, 51 | forceQuoteTrailingWhitespace: false, 52 | newColumnQuoteInformationIsQuoted: false, 53 | disableBorders: false, 54 | initiallyFixedRowsTop: 0, 55 | initiallyFixedColumnsLeft: 0, 56 | fontSizeInPx: 16, 57 | showColumnHeaderNamesWithLettersLikeExcel: false, 58 | shouldWatchCsvSourceFile: 'yesAndNotify', 59 | sidePanelAppearance: 'expanded', 60 | initialNumbersStyle: 'en', 61 | insertRowBehavior: 'keepRowKeepColumn', 62 | insertColBehavior: 'keepRowKeepColumn', 63 | initiallyIsInReadonlyMode: false, 64 | hideOpenCsvEditorUiActions: false, //noop, has only effect if set inside the user settings (vs code extension) 65 | openTableAndSelectCellAtCursorPos: "never", 66 | pasteMode: 'normal', 67 | pasteBehavior: 'overwrite', 68 | pasteScrollBehavior: 'scrollToLastPastedCell', 69 | fontFamilyInTable: 'default', 70 | showDeleteColumnHeaderButton: true, 71 | showDeleteRowHeaderButton: true, 72 | finalNewLine: 'sameAsSourceFile', 73 | darkThemeTextColor: '#d0d0d0', 74 | lightThemeTextColor: '#657b83', 75 | convertUrlsToLinkTags: true, 76 | dragToAutoFill: "excelLike", 77 | initiallyHiddenColumnNames: [], 78 | initiallyHiddenColumnNumbers: [], 79 | useSaveButtonsAsAdditionalUnsavedChangesIndicator: true, 80 | copyColumnHeaderNamesSeparator: `, `, 81 | } 82 | 83 | function __getById(id: string): HTMLElement { 84 | const el = document.getElementById(id) 85 | 86 | if (!el) { 87 | _error(`could not find element with id '${id}'`) 88 | return null as any 89 | } 90 | 91 | return el 92 | } 93 | const sweetalert2DarkThemeLink = __getById(`sweetalert2-dark-theme-link`) as HTMLLinkElement 94 | 95 | interface BrowserSettings { 96 | theme: 'dark' | 'light' 97 | } 98 | 99 | interface SiteSettings { 100 | version: string | undefined 101 | // csvEditSettings: CsvEditSettings | undefined 102 | browserSettings: BrowserSettings | undefined 103 | } 104 | 105 | 106 | interface SupportedEncoding { 107 | /** the display name with some addition information */ 108 | name: string 109 | /** without additional information */ 110 | internalName: string 111 | autoDetecting: boolean 112 | } 113 | 114 | function getTheme(): BrowserSettings['theme'] { 115 | const isDarkMode = document.body ? document.body.classList.contains('vscode-dark') : true 116 | return isDarkMode ? 'dark' : 'light' 117 | } 118 | 119 | /** 120 | * toggles the site theme 121 | */ 122 | function toggleTheme(isDark?: boolean) { 123 | const isDarkMode = document.body ? document.body.classList.contains('vscode-dark') : true 124 | 125 | if (isDarkMode) { 126 | 127 | if (isDark) return 128 | 129 | executeAfterDomLoaded(() => { 130 | document.body.classList.remove('vscode-dark') 131 | document.body.classList.add('vscode-light') 132 | }) 133 | 134 | sweetalert2DarkThemeLink.disabled = false 135 | 136 | document.documentElement.style.setProperty(`--theme-bg-color`, themeLightBgColor) 137 | 138 | } else { 139 | 140 | //in light mode 141 | 142 | if (isDark === false) return 143 | 144 | executeAfterDomLoaded(() => { 145 | document.body.classList.remove('vscode-light') 146 | document.body.classList.add('vscode-dark') 147 | }) 148 | 149 | sweetalert2DarkThemeLink.disabled = true 150 | 151 | document.documentElement.style.setProperty(`--theme-bg-color`, themeDarkBgColor) 152 | } 153 | 154 | executeAfterDomLoaded(() => { 155 | saveSettings() 156 | }) 157 | } 158 | 159 | const settingsLocalStorageKey = `siteSettings` 160 | function saveSettings() { 161 | 162 | const settings: SiteSettings = { 163 | // csvEditSettings: initialConfig, 164 | version: '1', 165 | browserSettings: { 166 | theme: getTheme() 167 | } 168 | } 169 | 170 | try { 171 | localStorage.setItem(settingsLocalStorageKey, JSON.stringify(settings)) 172 | } catch (error) { 173 | console.log(`could not save site setting into local storage`, error) 174 | Swal.fire(`Save settings error`, `Could not save the site settings`, `error`) 175 | } 176 | 177 | } 178 | 179 | function loadSettings(): SiteSettings | null { 180 | 181 | const settingsJson = localStorage.getItem(settingsLocalStorageKey) 182 | if (!settingsJson) { 183 | console.log(`could not load site settings from local storage (null)`) 184 | return null 185 | } 186 | const _settings = JSON.parse(settingsJson) as SiteSettings 187 | 188 | //just take the values 189 | 190 | const settings: SiteSettings = { 191 | // csvEditSettings: initialConfig, 192 | version: _settings?.version, 193 | browserSettings: { 194 | theme: _settings?.browserSettings?.theme ?? 'dark' 195 | } 196 | } 197 | 198 | if (settings.browserSettings && (settings.browserSettings.theme !== 'dark' && settings.browserSettings.theme !== 'light')) { 199 | settings.browserSettings.theme = 'dark' 200 | } 201 | 202 | return settings 203 | } 204 | 205 | function applySettings(settings: SiteSettings | null) { 206 | if (!settings) return 207 | 208 | toggleTheme(settings.browserSettings?.theme !== 'light') 209 | } 210 | 211 | 212 | applySettings(loadSettings()) 213 | 214 | 215 | function executeAfterDomLoaded(handler: () => void) { 216 | 217 | if (document.body) { 218 | handler() 219 | } else { 220 | executeAfterDomLoadedQueue.push(handler) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /csvEditorHtml/browser/browser.css: -------------------------------------------------------------------------------- 1 | html { 2 | --extension-font-size : 16px; 3 | --vscode-editor-font-size: 16px; 4 | --theme-bg-color: #003b49; 5 | 6 | overflow-y: auto; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | } 12 | 13 | body.vscode-dark { 14 | background-color: var(--theme-bg-color); 15 | } 16 | 17 | body.vscode-light { 18 | background-color: var(--theme-bg-color); 19 | } 20 | 21 | body .toast { 22 | top: 10px; 23 | cursor: pointer; 24 | } 25 | 26 | .vscode-dark .toast .body { 27 | background-color: #3a3a3ae6; 28 | } 29 | .vscode-light .toast .body { 30 | background-color: rgba(255, 255, 255, 0.9); 31 | } 32 | 33 | .page.full-h { 34 | height: calc(100% - 30px); 35 | } 36 | 37 | #site-header-bar { 38 | height: 30px; 39 | max-height: 30px; 40 | padding: 3px 10px; 41 | 42 | display: flex; 43 | justify-content: space-between; 44 | } 45 | 46 | .vscode-dark #site-header-bar { 47 | background-color: #2d3536; 48 | border-bottom: 1px solid #8e8e8e; 49 | } 50 | 51 | .vscode-light #site-header-bar { 52 | background-color: #f7f7f7; 53 | border-bottom: 1px solid #8e8e8e; 54 | } 55 | 56 | 57 | #site-header-bar .right-area { 58 | display: flex; 59 | } 60 | 61 | 62 | /* #read-options-content, 63 | #write-options-content, 64 | #preview-content { 65 | display: none; 66 | } */ 67 | 68 | #source-file-unwatched-indicator, 69 | #reload-file, 70 | #btn-apply-changes-to-file, 71 | #received-csv-prog-bar-wrapper { 72 | display: none; 73 | } 74 | 75 | 76 | 77 | /* see http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/ */ 78 | .options-title .changeable-indicator .changeable span { 79 | font-size: calc(var(--vscode-editor-font-size) - 6px); 80 | } 81 | 82 | #is-readonly-mode-toggle .fas.fa-slash:nth-child(3) { 83 | color: var(--theme-bg-color); 84 | } 85 | 86 | .file-input { 87 | width: 0.1px; 88 | height: 0.1px; 89 | opacity: 0; 90 | overflow: hidden; 91 | position: absolute; 92 | z-index: -1; 93 | } 94 | 95 | .file-input + label { 96 | display: inline-block; 97 | cursor: pointer; 98 | } 99 | 100 | .file-input:focus + label, 101 | .file-input + label:hover { 102 | } 103 | 104 | /* file drop style */ 105 | #csv-file-drop-area { 106 | border: 2px dashed #ccc; 107 | border-radius: 20px; 108 | width: 480px; 109 | font-family: sans-serif; 110 | margin: 100px auto; 111 | padding: 20px; 112 | } 113 | 114 | #drop-file-overlay { 115 | z-index: 1000; 116 | position: fixed; 117 | top: 0; 118 | left: 0; 119 | right: 0; 120 | bottom: 0; 121 | opacity: 0.8; 122 | background-color: black; 123 | padding: 1rem; 124 | pointer-events: none; 125 | } 126 | 127 | #drop-zone { 128 | width: 100%; 129 | height: 100%; 130 | display: flex; 131 | justify-content: center; 132 | align-items: center; 133 | border-radius: 10px; 134 | border: 5px dashed #ccc; 135 | color: white; 136 | font-size: 2em; 137 | pointer-events: none; 138 | } 139 | 140 | #drop-zone.active { 141 | border: 5px dashed rgb(67 202 67); 142 | color: rgb(67 202 67); 143 | } 144 | 145 | .browser .csv-editor-wrapper { 146 | 147 | } 148 | 149 | 150 | 151 | .vscode-light .notification { 152 | box-shadow: 4px 3px 7px 0px rgb(191 191 191 / 75%); 153 | } 154 | .vscode-dark .notification { 155 | background-color: #404040; 156 | color: #d0d0d0; 157 | box-shadow: 4px 3px 7px 0px rgb(0 0 0 / 75%) 158 | } 159 | 160 | 161 | /* scrolling modal */ 162 | .vscode-dark .modal .modal-card .modal-card-head, 163 | .vscode-dark .modal .modal-card .modal-card-foot { 164 | background-color: #353535; 165 | color: #d0d0d0; 166 | } 167 | 168 | .vscode-dark .modal .modal-card .modal-card-head .modal-card-title{ 169 | color: #fdfdfd; 170 | } 171 | 172 | .vscode-dark .modal .modal-card .modal-card-body { 173 | background-color: #404040; 174 | color: #d0d0d0; 175 | } 176 | 177 | li.new-version { 178 | border-bottom: 1px solid #8e8e8e; 179 | font-size: 1.5rem; 180 | margin-bottom: 0.5rem; 181 | padding-top: 1rem; 182 | } 183 | -------------------------------------------------------------------------------- /csvEditorHtml/browser/card-site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | /* see https://webgradients.com/ */ 4 | background-image: linear-gradient(to top, #c1dfc4 0%, #deecdd 100%); 5 | height: 100vh; 6 | } 7 | /* don't know why this is not working for both...*/ 8 | body.large { 9 | height: 100%; 10 | } 11 | 12 | main { 13 | padding: 1rem; 14 | } 15 | 16 | .site-title { 17 | font-size: 3rem; 18 | text-decoration: underline; 19 | text-decoration-color: #5eb95e; 20 | } 21 | 22 | .site-card { 23 | padding: 2rem 3rem 3rem 3rem; 24 | border-radius: 20px; 25 | width: 80%; 26 | margin: auto; 27 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 28 | background-color: white; 29 | } 30 | 31 | .centered { 32 | text-align: center; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5 { 40 | /* text-align: center; */ 41 | } 42 | 43 | p, 44 | ul { 45 | margin-left: 3rem; 46 | } -------------------------------------------------------------------------------- /csvEditorHtml/browser/cookies.css: -------------------------------------------------------------------------------- 1 | html { 2 | --cookie-box-display: inline-table; 3 | } 4 | /* see https://stackoverflow.com/questions/1776915/how-can-i-center-an-absolutely-positioned-element-in-a-div */ 5 | .cookie-box-wrapper { 6 | position: absolute; 7 | top: 0; 8 | left: 50%; 9 | transform: translate(-50%, 0); 10 | z-index: 2000; 11 | display: var(--cookie-box-display); 12 | color: #d0d0d0; 13 | max-width: 820px; 14 | } 15 | 16 | .cookie-box { 17 | position: relative; 18 | top: 0; 19 | /* left: -50%; */ 20 | background-color: rgb(61, 61, 61); 21 | display: inline-flex; 22 | flex-direction: column; 23 | /* border: 1px solid black; */ 24 | box-shadow: 4px 3px 7px -1px rgb(29 29 29 / 83%); 25 | padding: 5px 10px; 26 | } 27 | 28 | 29 | .cookie-box .banner-content { 30 | display: inline-flex; 31 | flex-direction: row; 32 | justify-content: space-between; 33 | } 34 | 35 | .cookie-box ul { 36 | list-style: revert; 37 | padding: revert; 38 | } 39 | 40 | .cookie-box .multi-buttons { 41 | display: inline-flex; 42 | margin: -5px -10px -5px 0; 43 | } 44 | 45 | .cookie-box .multi-buttons .as-button:nth-child(1) { 46 | border-right: 1px solid #b5b5b5; 47 | } 48 | 49 | .cookie-box .as-button { 50 | padding: 0 10px; 51 | background-color: rgb(2, 120, 255); 52 | cursor: pointer; 53 | /* border: 1px solid #929292; */ 54 | display: flex; 55 | align-items: center; 56 | user-select: none; 57 | } 58 | 59 | .cookie-box .as-button:hover { 60 | background-color: rgb(2, 94, 199); 61 | cursor: pointer; 62 | } 63 | 64 | .cookie-box .details { 65 | display: none; 66 | margin-top: 1rem; 67 | padding: 0 1rem 1rem 1rem; 68 | } -------------------------------------------------------------------------------- /csvEditorHtml/browser/cookies.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | const cookieBoxDisplayVisible = `inline-table` 4 | 5 | function toggleDetails() { 6 | const div = document.querySelector(`.cookie-box .details`) as HTMLDivElement 7 | const toggleCookieDetailsDiv = document.getElementById(`toggleCookieDetails`) as HTMLDivElement 8 | 9 | const isDisplayed = div.style.display === cookieBoxDisplayVisible 10 | 11 | div.style.display = !isDisplayed ? cookieBoxDisplayVisible : `none` 12 | toggleCookieDetailsDiv.innerText = !isDisplayed ? `Hide details` : `Show details` 13 | } 14 | 15 | const cookieBoxKey = `cookiesBoxKey` 16 | const okString = `ok` 17 | function cookiesOk() { 18 | localStorage.setItem(cookieBoxKey, okString) 19 | checkCookieBox() 20 | } 21 | 22 | 23 | function checkCookieBox() { 24 | const val = localStorage.getItem(cookieBoxKey) 25 | if (val === okString) { //hide box 26 | document.documentElement.style.setProperty(`--cookie-box-display`, `none`) 27 | } 28 | } 29 | checkCookieBox() -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/GitHub-Mark-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/GitHub-Mark-32px.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/GitHub-Mark-Light-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/GitHub-Mark-Light-32px.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/apple-touch-icon.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/favicon-128.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/favicon-192.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/favicon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/favicon-196.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/favicon-32.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/favicon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/favicon-48.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/favicon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/favicon-96.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/old_icon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/old_icon/apple-touch-icon.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/old_icon/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/old_icon/favicon-128.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/old_icon/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/old_icon/favicon-192.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/old_icon/favicon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/old_icon/favicon-196.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/old_icon/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/old_icon/favicon-32.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/imgs/old_icon/favicon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/csvEditorHtml/browser/imgs/old_icon/favicon-96.png -------------------------------------------------------------------------------- /csvEditorHtml/browser/legalNotice.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Impressum / Legal Notice 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 65 | 66 |
67 |
68 |
Edit CSV
69 | 70 | 71 |

- Website Terms and Conditions of Use -

72 | 73 |

Terms

74 |

75 | By accessing this Website, accessible from edit-csv.net, you are agreeing to be bound by these Website Terms and Conditions of Use and agree that you are responsible for the agreement with any applicable local laws. If you disagree with any of these terms, you are prohibited from accessing this site. The materials contained in this Website are protected by copyright and trade mark law. 76 |

77 |

Disclaimer

78 |

79 | All the materials on Dähne Solutions's Website are provided “as is”. Dähne Solutions makes no warranties, may it be expressed or implied, therefore negates all other warranties. Furthermore, Dähne Solutions does not make any representations concerning the accuracy or reliability of the use of the materials on its Website or otherwise relating to such materials or any sites linked to this Website. 80 |

81 |

Limitations

82 |

83 | Dähne Solutions or its suppliers will not be hold accountable for any damages that will arise with the use or inability to use the materials on Dähne Solutions's Website, even if Dähne Solutions or an authorize representative of this Website has been notified, orally or written, of the possibility of such damage. Some jurisdiction does not allow limitations on implied warranties or limitations of liability for incidental damages, these limitations may not apply to you. 84 |

85 |

Revisions and Errata

86 |

87 | The materials appearing on Dähne Solutions's Website may include technical, typographical, or photographic errors. Dähne Solutions will not promise that any of the materials in this Website are accurate, complete, or current. Dähne Solutions may change the materials contained on its Website at any time without notice. Dähne Solutions does not make any commitment to update the materials. 88 |

89 |

Links

90 |

91 | Dähne Solutions has not reviewed all of the sites linked to its Website and is not responsible for the contents of any such linked site. The presence of any link does not imply endorsement by Dähne Solutions of the site. The use of any linked website is at the user's own risk. 92 |

93 |

Site Terms of Use Modifications

94 |

95 | Dähne Solutions may revise these Terms of Use for its Website at any time without prior notice. By using this Website, you are agreeing to be bound by the current version of these Terms and Conditions of Use. 96 |

97 |

Governing Law

98 |

99 | Any claim related to Dähne Solutions's Website shall be governed by the laws of Germany without regards to its conflict of law provisions. 100 |

101 | 102 | 103 | 104 |

- Impressum -

105 | 106 |

Angaben gemäß § 5 TMG

107 |

108 | Angaben gemäß §5 TMG 109 |

110 | 111 |

Kontakt

112 |

113 | Kontakt 114 |

115 | 116 |

Umsatzsteuer-ID

117 |

Umsatzsteuer-Identifikationsnummer gemäß §27 a Umsatzsteuergesetz:
118 | DE 325 460 253

119 | 120 |

Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle 121 | teilzunehmen.

122 | 123 |

Haftung für Inhalte

124 | 125 |

Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach 126 | den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch 127 | nicht 128 | verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach 129 | Umständen 130 | zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.

131 |

Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen 132 | bleiben 133 | hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer 134 | konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir 135 | diese 136 | Inhalte umgehend entfernen.

137 | 138 |

Haftung für Links

139 | 140 |

Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. 141 | Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die 142 | Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die 143 | verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße 144 | überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar.

145 | 146 |

Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer 147 | Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend 148 | entfernen.

149 | 150 |

Urheberrecht

151 | 152 |

Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen 153 | Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb 154 | der 155 | Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. 156 | Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.

157 | 158 |

Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter 159 | beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine 160 | Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von 161 | Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.

162 | 163 | 164 | 165 |
166 | 167 |
168 | 169 | 170 | -------------------------------------------------------------------------------- /csvEditorHtml/browser/robots.txt: -------------------------------------------------------------------------------- 1 | 2 | User-agent: * 3 | Disallow: /legalNotice.html 4 | Disallow: /privacy.html -------------------------------------------------------------------------------- /csvEditorHtml/browser/third-party/LICENSE-iconv-lite.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Alexander Shtuchkin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /csvEditorHtml/browser/third-party/LICENSE-node-chardet.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Dmitry Shirokov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /csvEditorHtml/browser/third-party/bulma-toast/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rafael Franco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /csvEditorHtml/browser/third-party/bulma-toast/bulma-toast.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bulma-toast 2.0.3 3 | * (c) 2018-present @rfoel 4 | * Released under the MIT License. 5 | */ 6 | (function(a,b){"object"==typeof exports&&"undefined"!=typeof module?b(exports):"function"==typeof define&&define.amd?define(["exports"],b):(a=a||self,b(a.bulmaToast={}))})(this,function(a){'use strict';function b(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function c(a,b){for(var c,d=0;d=a.children.length&&a.remove()}},{key:"onAnimationEnd",value:function(){var a=0 td, 121 | .vscode-dark .ht_clone_left tr:nth-of-type(odd)> td, 122 | .vscode-dark .ht_clone_top_left_corner tr:nth-of-type(odd)> td { 123 | background-color: #262c2e; 124 | border-color: #5c5c5c; 125 | } 126 | 127 | .vscode-dark .ht_clone_top tr:nth-of-type(even)> td, 128 | .vscode-dark .ht_clone_left tr:nth-of-type(even)> td, 129 | .vscode-dark .ht_clone_top_left_corner tr:nth-of-type(even)> td { 130 | background-color: #2d3436; 131 | border-color: #5c5c5c; 132 | } 133 | 134 | .vscode-dark .ht_clone_top tr:last-child > td, 135 | .vscode-dark .ht_clone_top_left_corner tr:last-child > td { 136 | border-bottom-color: #b7b7b7; 137 | } 138 | 139 | .vscode-dark .ht_clone_left tr > td:last-child, 140 | .vscode-dark .ht_clone_top_left_corner tr > td:last-child { 141 | border-right-color: #b7b7b7; 142 | } 143 | 144 | .vscode-dark .handsontable tr { 145 | background: #5c5c5c; 146 | } 147 | 148 | /* normal rows */ 149 | .vscode-dark .ht_master tr:nth-of-type(odd) > td { 150 | background-color: #262c2e; 151 | border-color: #5c5c5c; 152 | } 153 | 154 | .vscode-dark .ht_master tr:nth-of-type(even) > td { 155 | background-color: #2d3436; 156 | border-color: #5c5c5c; 157 | } 158 | 159 | .vscode-dark .ht_clone_left th { 160 | background-color:rgb(26, 29, 29); 161 | border-color: #5c5c5c !important; 162 | } 163 | 164 | .vscode-dark .ht_clone_top th { 165 | background-color: rgb(26, 29, 29); 166 | border-color: #5c5c5c !important; 167 | } 168 | 169 | .vscode-dark .handsontable tbody th.ht__highlight.foo, 170 | .vscode-dark .handsontable thead th.ht__highlight.foo { 171 | background-color: #0251b5; 172 | } 173 | 174 | /* we don't need this... first real column... light css doesn't even have this*/ 175 | .vscode-dark .handsontable th:nth-child(2) { 176 | /* border-left: 1px solid #5c5c5c !important; */ 177 | } 178 | .vscode-dark .ht_clone_top thead tr:nth-child(2) { 179 | border: 0 !important; 180 | } 181 | 182 | .vscode-dark .ht_clone_top_left_corner th { 183 | border-color: #5c5c5c!important; 184 | } 185 | 186 | .vscode-dark .ht_clone_top_left_corner thead tr:nth-child(1) { 187 | border-right: 0 !important; 188 | } 189 | 190 | .vscode-dark .comment-row { 191 | /* background-color: #5f5f5f!important; */ 192 | color: #888787; 193 | } 194 | 195 | .vscode-dark .comment-row td:nth-child(n+3) { 196 | color: red; 197 | } 198 | 199 | /* context menu */ 200 | .vscode-dark .htContextMenu table tbody tr td { 201 | background-color: #262c2e !important; 202 | border-color: #5c5c5c !important; 203 | } 204 | 205 | .vscode-dark .htContextMenu table tbody tr td:hover { 206 | background-color: #4e4e4e !important; 207 | } 208 | .vscode-dark .htContextMenu table tbody tr td.htDisabled:hover { 209 | background-color: #262c2e !important; 210 | } 211 | 212 | .vscode-dark .htContextMenu table.htCore { 213 | border-color: #5c5c5c !important; 214 | } 215 | 216 | .vscode-dark .ht_master tr> td.search-result-cell { 217 | background-color: #585802; /* this is better for comments */ 218 | /* background-color: #7b7b03; */ 219 | } 220 | 221 | .vscode-dark .find-widget { 222 | background-color: #2a2a2a; 223 | /* filter: brightness(120%); */ 224 | } 225 | 226 | .vscode-dark .find-widget .divider { 227 | background-color: #4c4c4c; 228 | } 229 | 230 | .vscode-dark .find-widget .btn.active { 231 | border-color: rgba(0, 122, 204, 0); 232 | background-color: rgba(0, 127, 212, 0.4); 233 | } 234 | 235 | .vscode-dark .find-widget .error-message { 236 | 237 | } 238 | 239 | .vscode-dark .options-title { 240 | color: #d0d0d0; 241 | } 242 | 243 | .vscode-dark .all-options table { 244 | border: 1px solid #5e5e5e; 245 | } 246 | 247 | .vscode-dark a { 248 | color: #5c9bff; 249 | } 250 | 251 | .vscode-dark .handsontable .htDimmed { 252 | /*color: #b7b7b7*/ /*harder to read*/ 253 | color: inherit; 254 | } 255 | 256 | .vscode-dark .row-col-insert-btns { 257 | border-left: 1px solid #5e5e5e; 258 | } 259 | 260 | .vscode-dark .btn-with-menu-wrapper .menu { 261 | border: 1px solid var(--dropdown-border); 262 | background-color: var(--dropdown-background); 263 | } 264 | -------------------------------------------------------------------------------- /csvEditorHtml/high_contrast.css: -------------------------------------------------------------------------------- 1 | body.vscode-high-contrast { 2 | color: white; 3 | } 4 | 5 | 6 | .vscode-high-contrast .button.is-light { 7 | color: black; 8 | } 9 | 10 | .vscode-high-contrast .button.is-outlined:hover { 11 | background-color: white; 12 | color: black; 13 | } 14 | 15 | .vscode-high-contrast .button.is-outlined:focus { 16 | background-color: transparent; 17 | color: white; 18 | } 19 | 20 | .vscode-high-contrast .select select { 21 | color: black; 22 | } 23 | 24 | .vscode-high-contrast .title { 25 | color: white; 26 | } 27 | 28 | .vscode-dark .radio:hover { 29 | color: white; 30 | } 31 | 32 | .vscode-high-contrast .box { 33 | background-color: black; 34 | color: white; 35 | } 36 | 37 | .vscode-high-contrast .keys { 38 | color: white; 39 | background: #404040; 40 | border-color: #ccc #aaa #888 #bbb; 41 | -moz-box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px #888 inset; 42 | -webkit-box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px #888 inset; 43 | box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px #888 inset; 44 | } 45 | 46 | .vscode-high-contrast a { 47 | color: #f9f500; 48 | } 49 | 50 | .vscode-high-contrast a:hover { 51 | color: red; 52 | } 53 | 54 | * handson table styles */ 55 | 56 | .vscode-high-contrast .ht_master, 57 | .vscode-high-contrast .handsontable th { 58 | color: #d0d0d0 !important; 59 | } 60 | 61 | 62 | /* Edit mode */ 63 | .vscode-high-contrast .handsontableInput { 64 | background-color: #504f4f!important; 65 | color: #d0d0d0; 66 | } 67 | 68 | /* All headers */ 69 | .vscode-high-contrast .handsontable th { 70 | background-color: rgb(26, 29, 29); 71 | color: white; 72 | } 73 | 74 | .vscode-high-contrast .handsontable span.colHeader.columnSorting::before { 75 | filter: invert(100%); 76 | } 77 | 78 | .vscode-high-contrast .ht_clone_top th { 79 | border-color: #5c5c5c; 80 | } 81 | 82 | /* fixed rows top */ 83 | .vscode-high-contrast .ht_clone_top tr:nth-of-type(odd)> td { 84 | background-color: black; 85 | border-color: #5c5c5c; 86 | } 87 | 88 | .vscode-high-contrast .ht_clone_top tr:nth-of-type(even)> td { 89 | background-color: #1d1d1d; 90 | border-color: #5c5c5c; 91 | } 92 | 93 | .vscode-high-contrast .ht_clone_top tr:last-child > td { 94 | border-bottom-color: red; 95 | } 96 | 97 | .vscode-dark .handsontable tr { 98 | background: #5c5c5c; 99 | } 100 | 101 | /* normal rows */ 102 | .vscode-high-contrast .ht_master tr:nth-of-type(odd) > td { 103 | background-color: black; 104 | border-color: #5c5c5c; 105 | } 106 | 107 | .vscode-high-contrast .ht_master tr:nth-of-type(even) > td { 108 | background-color: #1d1d1d; 109 | border-color: #5c5c5c; 110 | } 111 | 112 | .vscode-high-contrast .ht_clone_left th { 113 | background-color:rgb(26, 29, 29); 114 | border-color: #5c5c5c !important; 115 | } 116 | 117 | .vscode-high-contrast .ht_clone_top th { 118 | background-color: rgb(26, 29, 29); 119 | border-color: #5c5c5c !important; 120 | } 121 | 122 | .vscode-high-contrast .handsontable tbody th.ht__highlight.foo, 123 | .vscode-high-contrast .handsontable thead th.ht__highlight.foo { 124 | background-color: red; 125 | } 126 | 127 | /* we don't need this... first real column...*/ 128 | .vscode-high-contrast .handsontable th:nth-child(2) { 129 | /* border-left: 1px solid #5c5c5c !important; */ 130 | } 131 | 132 | .vscode-high-contrast .ht_clone_top thead tr:nth-child(2) { 133 | border: 0 !important; 134 | } 135 | 136 | .vscode-high-contrast .ht_clone_top_left_corner th { 137 | border-color: #5c5c5c!important; 138 | } 139 | 140 | .vscode-high-contrast .ht_clone_top_left_corner thead tr:nth-child(1) { 141 | border-right: 0 !important; 142 | } 143 | 144 | .vscode-high-contrast .comment-comment-row { 145 | /* background-color: #f7f7f7!important; */ 146 | color: #ff9d00; 147 | } 148 | 149 | .vscode-high-contrast .comment-row td:nth-child(n+3) { 150 | color: red; 151 | } 152 | 153 | /* context menu */ 154 | .vscode-high-contrast .htContextMenu table tbody tr td { 155 | background-color: black !important; 156 | border-color: #5c5c5c !important; 157 | } 158 | 159 | .vscode-high-contrast .htContextMenu table tbody tr td:hover { 160 | background-color: #ff0d0d !important; 161 | } 162 | .vscode-high-contrast .htContextMenu table tbody tr td.htDisabled:hover { 163 | background-color: black !important; 164 | } 165 | 166 | .vscode-high-contrast .htContextMenu table.htCore { 167 | border-color: #5c5c5c !important; 168 | } 169 | 170 | .vscode-high-contrast .ht_master tr> td.search-result-cell { 171 | background-color: #9d00ff!important; 172 | } 173 | 174 | .vscode-high-contrast .find-widget{ 175 | background-color: black; 176 | color: white; 177 | border: 2px solid #6fc3df; 178 | } 179 | 180 | .vscode-high-contrast .find-widget .divider { 181 | background-color: #676767; 182 | } 183 | 184 | .vscode-high-contrast .find-widget .btn.active { 185 | background-color: #28ff00; 186 | } 187 | 188 | .vscode-high-contrast .find-widget .error-message { 189 | color: white; 190 | } 191 | 192 | .vscode-high-contrast .options-title { 193 | font-size: 130%; 194 | color: #d0d0d0; 195 | } 196 | 197 | .vscode-high-contrast .all-options table { 198 | border: 1px solid white; 199 | border-collapse: initial; 200 | border-radius: 10px; 201 | } 202 | 203 | .vscode-high-contrast .handsontable .htDimmed { 204 | /*color: #cacaca;*/ /*harder to read*/ 205 | cursor: pointer; /*as color does not really work maybe this helps?*/ 206 | color: inherit; 207 | } 208 | 209 | .vscode-light .row-col-insert-btns { 210 | border-left: 1px solid white; 211 | } 212 | -------------------------------------------------------------------------------- /csvEditorHtml/light.css: -------------------------------------------------------------------------------- 1 | body.vscode-light { 2 | --text-color: #657b83; 3 | color: #657b83; 4 | 5 | --button-icon-background: transparent; 6 | --button-icon-hover-background: rgba(90, 93, 94, 0.31); 7 | 8 | --input-foreground: #586e75; 9 | --input-background: #ddd6c1; 10 | --dropdown-border: #d3af86; 11 | --dropdown-background: #eee8d5; 12 | --checkbox-background: #eee8d5; 13 | --checkbox-border: #d3af86; 14 | 15 | --button-secondary-hover-background: #4c5561; 16 | 17 | --button-secondary-background: #5f6a79; 18 | --button-secondary-foreground: #ffffff; 19 | --foreground: #616161; 20 | --input-foreground: #586e75; 21 | --focus-border: #d3af86; 22 | --list-active-selection-background: #dfca88; 23 | --list-active-selection-foreground: #6c6c6c; 24 | --list-hover-background: rgba(223, 202, 136, 0.27); 25 | } 26 | 27 | .vscode-light .button.is-light { 28 | color: #657b83; 29 | } 30 | 31 | .vscode-light .button.is-outlined { 32 | color: #657b83; 33 | background-color: transparent; 34 | } 35 | 36 | .vscode-light .button.is-outlined:hover { 37 | background-color: #404040; /* maybe #676767 ??*/ 38 | color: #d0d0d0; 39 | } 40 | .vscode-light .button.is-outlined:focus { 41 | background-color:transparent; 42 | color: #404040; 43 | } 44 | 45 | .vscode-light .radio:hover { 46 | color: #657b83; 47 | } 48 | 49 | /* handson table styles */ 50 | 51 | .vscode-light .ht_master a[data-link-is-openable="1"] { 52 | cursor: default; 53 | text-decoration: underline; 54 | color: var(--text-color, #657b83); 55 | } 56 | 57 | .vscode-light .ht_master a[data-link-is-openable="1"].link-hovered.is-open-url-key-down { 58 | color: var(--vscode-editorLink-activeForeground, #0000ff); /* default for browser */ 59 | cursor: pointer; 60 | } 61 | 62 | .vscode-light .ht_master, 63 | .vscode-light .handsontable th { 64 | color: var(--text-color, #657b83) !important; 65 | } 66 | 67 | /* fixed rows/columns */ 68 | .vscode-light .ht_clone_top tr:nth-of-type(odd)> td, 69 | .vscode-light .ht_clone_left tr:nth-of-type(odd)> td, 70 | .vscode-light .ht_clone_top_left_corner tr:nth-of-type(odd)> td { 71 | background-color: white; 72 | } 73 | 74 | .vscode-light .ht_clone_top tr:nth-of-type(even)> td, 75 | .vscode-light .ht_clone_left tr:nth-of-type(even)> td, 76 | .vscode-light .ht_clone_top_left_corner tr:nth-of-type(even)> td { 77 | background-color: #f7f7f7; 78 | } 79 | 80 | .vscode-light .ht_clone_top tr:last-child > td, 81 | .vscode-light .ht_clone_top_left_corner tr:last-child > td { 82 | border-bottom-color: #3c3c3c; 83 | } 84 | 85 | .vscode-light .ht_clone_left tr > td:last-child, 86 | .vscode-light .ht_clone_top_left_corner tr > td:last-child { 87 | border-right-color: #3c3c3c; 88 | } 89 | 90 | /* normal rows */ 91 | .vscode-light .ht_master tr:nth-of-type(odd) > td { 92 | background-color: white; 93 | } 94 | 95 | .vscode-light .ht_master tr:nth-of-type(even) > td { 96 | background-color: #f7f7f7; 97 | } 98 | 99 | 100 | .vscode-light .comment-row { 101 | /* background-color: #f7f7f7!important; */ 102 | color: #b1b1b1; 103 | } 104 | 105 | .vscode-light .comment-row td:nth-child(n+3) { 106 | color: red; 107 | } 108 | 109 | /* context menu */ 110 | .vscode-light .htContextMenu table tbody tr td { 111 | background-color: white !important; 112 | } 113 | 114 | .vscode-light .htContextMenu table tbody tr td:hover { 115 | background-color: #e4e4e4 !important; 116 | } 117 | .vscode-light .htContextMenu table tbody tr td.htDisabled:hover { 118 | background-color: white !important; 119 | } 120 | 121 | .vscode-light .htContextMenu table.htCore { 122 | /* border-color: #5c5c5c !important; */ 123 | } 124 | 125 | .vscode-light .ht_master tr> td.search-result-cell { 126 | background-color: #ffff5f!important; 127 | } 128 | 129 | .vscode-light .find-widget { 130 | background-color: #f3f3f3; /*from vs code light */ 131 | /* filter: brightness(80%); */ 132 | } 133 | 134 | .vscode-light .find-widget .divider { 135 | background-color: #c7c7c7; 136 | } 137 | 138 | .vscode-light .find-widget .btn.active { 139 | border-color: rgba(0, 122, 204, 0); 140 | background-color:rgba(0, 144, 241, 0.2) 141 | } 142 | 143 | .vscode-light .find-widget .error-message { 144 | color: white; 145 | } 146 | 147 | .vscode-light .options-title { 148 | color: #657b83; 149 | } 150 | 151 | .vscode-light .all-options table { 152 | border: 1px solid #5e5e5e; 153 | } 154 | 155 | .vscode-light a { 156 | color: #0866ff; 157 | } 158 | 159 | .vscode-light .handsontable .htDimmed { 160 | /*color: #8c8c8c*/ /*harder to read*/ 161 | color: inherit; 162 | } 163 | 164 | .vscode-light .row-col-insert-btns { 165 | border-left: 1px solid #5e5e5e; 166 | } 167 | 168 | .vscode-light .btn-with-menu-wrapper .menu { 169 | border: 1px solid var(--dropdown-border); 170 | background-color: var(--dropdown-background); 171 | } 172 | 173 | -------------------------------------------------------------------------------- /csvEditorHtml/progressbar.ts: -------------------------------------------------------------------------------- 1 | 2 | class Progressbar { 3 | 4 | el: HTMLDivElement 5 | 6 | constructor(public id: string) { 7 | 8 | this.el = document.getElementById(id) as HTMLDivElement 9 | 10 | if (!this.el) { 11 | throw new Error(`could not find element with id ${id}`) 12 | } 13 | } 14 | 15 | setValue(percentage: number) { 16 | this.el.style.right = `${100-percentage}%` 17 | } 18 | 19 | show() { 20 | this.el.style.display = 'block' 21 | } 22 | 23 | hide() { 24 | this.el.style.display = 'none' 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /csvEditorHtml/settingsOverwrite.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | this file is used to apply extension settings via css variables 4 | */ 5 | 6 | html { 7 | --extension-font-size: 16px; 8 | --hot-font-size-add-modifier: 1; 9 | } 10 | 11 | /* use font size from editor */ 12 | 13 | body.vs-code-settings-font-size { 14 | font-size: calc(1 * var(--vscode-editor-font-size)); 15 | } 16 | 17 | body.vs-code-settings-font-size .handsontable td { 18 | font-size: calc(var(--vscode-editor-font-size) + var(--hot-font-size-add-modifier)); 19 | height: calc(var(--vscode-editor-font-size) + var(--hot-font-size-add-modifier) + 6px); 20 | line-height: calc(var(--vscode-editor-font-size) + var(--hot-font-size-add-modifier) + 5px); 21 | } 22 | body.vs-code-settings-font-size .handsontableInputHolder textarea { 23 | font-size: calc(var(--vscode-editor-font-size) + var(--hot-font-size-add-modifier)); 24 | height: calc(var(--vscode-editor-font-size) + var(--hot-font-size-add-modifier) + 6px); 25 | line-height: calc(var(--vscode-editor-font-size) + var(--hot-font-size-add-modifier) + 5px); 26 | } 27 | 28 | body.vs-code-settings-font-size .input { 29 | font-size: calc(1 * var(--vscode-editor-font-size)); 30 | } 31 | 32 | body.vs-code-settings-font-size .button { 33 | font-size: calc(1 * var(--vscode-editor-font-size)); 34 | } 35 | 36 | body.vs-code-settings-font-size .switch[type=checkbox]+label { 37 | font-size: calc(1 * var(--vscode-editor-font-size)); 38 | padding-left: calc(4 * var(--vscode-editor-font-size)); 39 | padding-top: 0; 40 | } 41 | 42 | body.vs-code-settings-font-size .switch[type=checkbox]+label span { 43 | /* margin-left: calc(1 * var(--vscode-editor-font-size)); */ 44 | } 45 | 46 | body.vs-code-settings-font-size .switch[type=checkbox]+label::before, .switch[type=checkbox]+label:before { 47 | height: calc(1.3 * var(--vscode-editor-font-size)); 48 | width: calc(3 * var(--vscode-editor-font-size)); 49 | } 50 | 51 | body.vs-code-settings-font-size .switch[type=checkbox]+label::after, .switch[type=checkbox]+label:after { 52 | height: calc(1 * var(--vscode-editor-font-size)); 53 | width: calc(1 * var(--vscode-editor-font-size)); 54 | top: calc(0.2 * var(--vscode-editor-font-size)); 55 | } 56 | 57 | body.vs-code-settings-font-size .textarea { 58 | font-size: calc(1 * var(--vscode-editor-font-size)); 59 | } 60 | 61 | /* END use font size from editor */ 62 | 63 | /* use font size from extension settings */ 64 | 65 | body.extension-settings-font-size { 66 | font-size: calc(1 * var(--extension-font-size)); 67 | } 68 | 69 | body.extension-settings-font-size .handsontable td { 70 | font-size: calc(var(--extension-font-size) + var(--hot-font-size-add-modifier)); 71 | height: calc(var(--extension-font-size) + var(--hot-font-size-add-modifier) + 6px); 72 | line-height: calc(var(--extension-font-size) + var(--hot-font-size-add-modifier) + 5px); 73 | } 74 | body.extension-settings-font-size .handsontableInputHolder textarea { 75 | font-size: calc(var(--extension-font-size) + var(--hot-font-size-add-modifier)); 76 | height: calc(var(--extension-font-size) + var(--hot-font-size-add-modifier) + 6px); 77 | line-height: calc(var(--extension-font-size) + var(--hot-font-size-add-modifier) + 5px); 78 | } 79 | 80 | body.extension-settings-font-size .input { 81 | font-size: calc(1 * var(--extension-font-size)); 82 | } 83 | 84 | body.extension-settings-font-size .button { 85 | font-size: calc(1 * var(--extension-font-size)); 86 | } 87 | 88 | body.extension-settings-font-size .switch[type=checkbox]+label { 89 | font-size: calc(1 * var(--extension-font-size)); 90 | padding-left: calc(4 * var(--extension-font-size)); 91 | padding-top: 0; 92 | } 93 | 94 | body.extension-settings-font-size .switch[type=checkbox]+label span { 95 | /* margin-left: calc(1 * var(--extension-font-size)); */ 96 | } 97 | 98 | body.extension-settings-font-size .switch[type=checkbox]+label::before, .switch[type=checkbox]+label:before { 99 | height: calc(1.3 * var(--extension-font-size)); 100 | width: calc(3 * var(--extension-font-size)); 101 | } 102 | 103 | body.extension-settings-font-size .switch[type=checkbox]+label::after, .switch[type=checkbox]+label:after { 104 | height: calc(1 * var(--extension-font-size)); 105 | width: calc(1 * var(--extension-font-size)); 106 | top: calc(0.2 * var(--extension-font-size)); 107 | } 108 | body.extension-settings-font-size .textarea { 109 | font-size: calc(1 * var(--extension-font-size)); 110 | } 111 | 112 | /* END use font size from extension settings */ -------------------------------------------------------------------------------- /csvEditorHtml/test/init.ts: -------------------------------------------------------------------------------- 1 | 2 | window.vscode = null 3 | window.papaCsv = window.Papa 4 | 5 | const defaultInitialVars = { 6 | isWatchingSourceFile: false, 7 | sourceFileCursorLineIndex: null, 8 | sourceFileCursorColumnIndex: null, 9 | isCursorPosAfterLastColumn: false, 10 | openTableAndSelectCellAtCursorPos: 'initialOnly_correctRowAlwaysFirstColumn', 11 | os: 'web', 12 | } 13 | window.initialVars = defaultInitialVars 14 | 15 | let defaultCsvReadOptions: CsvReadOptions = { 16 | header: false, //always use false to get an array of arrays 17 | comments: '#', 18 | delimiter: '', //auto detect 19 | delimitersToGuess: [',', '\t', '|', ';', 20 | String.fromCharCode(30), //Papa.RECORD_SEP // \u001e" // INFORMATION SEPARATOR TWO 21 | String.fromCharCode(31), //Papa.UNIT_SEP // \u001f" // INFORMATION SEPARATOR ONE 22 | ], 23 | newline: '', //auto detect 24 | quoteChar: '"', 25 | escapeChar: '"', 26 | skipEmptyLines: true, 27 | dynamicTyping: false, 28 | _hasHeader: false, 29 | } 30 | window.defaultCsvReadOptions = defaultCsvReadOptions 31 | 32 | 33 | //this gets overwritten with the real configuration in setCsvWriteOptionsInitial 34 | let defaultCsvWriteOptions: CsvWriteOptions = { 35 | header: false, 36 | comments: '#', 37 | delimiter: '', //'' = use from input, will be set from empty to string when exporting (or earlier) 38 | newline: '', //set by editor 39 | quoteChar: '"', 40 | escapeChar: '"', 41 | quoteAllFields: false, 42 | quoteEmptyOrNullFields: false, 43 | } 44 | window.defaultCsvWriteOptions = defaultCsvWriteOptions 45 | 46 | 47 | //do not use original type here, else we get some error that modules are not found... 48 | window.knownNumberStylesMap = { 49 | "en": { 50 | key: 'en', 51 | /** 52 | * this allows: 53 | * 0(000) 54 | * 0(000).0(000) 55 | * .0(000) 56 | * all repeated with - in front (negative numbers) 57 | * all repeated with e0(000) | e+0(000) | e-0(000) 58 | */ 59 | regex: /-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?/, 60 | regexStartToEnd: /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/, 61 | thousandSeparator: /(\,| )/gm, 62 | thousandSeparatorReplaceRegex: /((\,| )\d{3})+/gm 63 | }, 64 | "non-en": { 65 | key: 'non-en', 66 | /** 67 | * this allows: 68 | * 0(000) 69 | * 0(000),0(000) 70 | * ,0(000) 71 | * all repeated with - in front (negative numbers) 72 | * all repeated with e0(000) | e+0(000) | e-0(000) 73 | */ 74 | regex: /-?(\d+(\,\d*)?|\,\d+)(e[+-]?\d+)?/, 75 | regexStartToEnd: /^-?(\d+(\,\d*)?|\,\d+)(e[+-]?\d+)?$/, 76 | thousandSeparator: /(\.| )/gm, 77 | thousandSeparatorReplaceRegex: /((\.| )\d{3})+/gm 78 | } 79 | } 80 | 81 | window.newColumnQuoteInformationIsQuoted = false 82 | 83 | 84 | //ui mocks 85 | 86 | window.newlineSameSsInputOptionText = `Same as input` 87 | window.readDelimiterTooltipText = "Empty to auto detect" 88 | 89 | window.newlineSameSsInputOption = { 90 | innerText: '', 91 | } 92 | window.readDelimiterTooltip = { 93 | setAttribute() { 94 | } 95 | } 96 | 97 | window._setHasUnsavedChangesUiIndicator = function () { 98 | } -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/altKey.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | 3 | 4 | suite("alt key pressed tests", () => { 5 | 6 | // special test 7 | test('holding alt will copy only (uses handsontable default auto fill func)', async function () { 8 | let result = customAutoFillFunc(['1', '2', '3'], 1, true, { altKey: true } as MouseEvent) 9 | expect(result).toEqual([]) 10 | }) 11 | 12 | }) 13 | 14 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/endsWithFloat.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | import { AutoFillTestData, AutoFillTestSuit } from './types' 3 | 4 | 5 | let tests_containsNumbersInts_StartsWithNumber_singleGroup: AutoFillTestData[] = [ 6 | { 7 | name: 'ends with number 0, 1 cell, 1 target count', 8 | data: ['test -5.3'], 9 | targetCount: 1, 10 | isNormalDirection: true, 11 | expected: ['test -4.3'] 12 | }, 13 | { 14 | name: 'ends with number 0, 1 cell, 1 target count', 15 | data: ['test 0.7'], 16 | targetCount: 1, 17 | isNormalDirection: true, 18 | expected: ['test 1.7'] 19 | }, 20 | { 21 | name: 'ends with number 99, 1 cell, 1 target count', 22 | data: ['test 99.2'], 23 | targetCount: 1, 24 | isNormalDirection: true, 25 | expected: ['test 100.2'] 26 | }, 27 | { 28 | name: 'ends with number 1, 1 cell, 1 target count', 29 | data: ['test 1.9'], 30 | targetCount: 1, 31 | isNormalDirection: true, 32 | expected: ['test 2.9'] 33 | }, 34 | { 35 | name: 'ends with number 1, 1 cell, 2 target count', 36 | data: ['test 1.8'], 37 | targetCount: 2, 38 | isNormalDirection: true, 39 | expected: ['test 2.8', 'test 3.8'] 40 | }, 41 | { 42 | name: 'ends with number 1, 1 cell, 3 target count', 43 | data: ['test 1.6'], 44 | targetCount: 3, 45 | isNormalDirection: true, 46 | expected: ['test 2.6', 'test 3.6', 'test 4.6'] 47 | }, 48 | 49 | { 50 | name: 'ends with number 1, contains 2 numbers, 1 cell, 3 target count', 51 | data: ['test 2.1 test 1.1'], 52 | targetCount: 3, 53 | isNormalDirection: true, 54 | expected: ['test 2.1 test 2.1', 'test 2.1 test 3.1', 'test 2.1 test 4.1'] 55 | }, 56 | 57 | 58 | 59 | //--- other direction 60 | { 61 | name: 'ends with number -5, 1 cell, 1 target count (other dir)', 62 | data: ['test -5.5'], 63 | targetCount: 1, 64 | isNormalDirection: false, 65 | expected: ['test -6.5'] 66 | }, 67 | { 68 | name: 'ends with number 0, 1 cell, 1 target count (other dir)', 69 | data: ['test 0.4'], 70 | targetCount: 1, 71 | isNormalDirection: false, 72 | expected: ['test -0.6'] 73 | }, 74 | { 75 | name: 'ends with number 99, 1 cell, 1 target count (other dir) [trailing zeros are removed]', 76 | data: ['test 99.0'], 77 | targetCount: 1, 78 | isNormalDirection: false, 79 | expected: ['test 98'] 80 | }, 81 | { 82 | name: 'ends with number 1, 1 cell, 1 target count (other dir) [trailing zeros are removed]', 83 | data: ['test 1.0'], 84 | targetCount: 1, 85 | isNormalDirection: false, 86 | expected: ['test 0'] 87 | }, 88 | { 89 | name: 'ends with number 1, 1 cell, 2 target count (other dir)', 90 | data: ['test 1.1'], 91 | targetCount: 2, 92 | isNormalDirection: false, 93 | expected: ['test 0.1', 'test -0.9'].reverse() 94 | }, 95 | { 96 | name: 'ends with number 1, 1 cell, 3 target count (other dir)', 97 | data: ['test 1.8'], 98 | targetCount: 3, 99 | isNormalDirection: false, 100 | expected: ['test 0.8', 'test -0.2', 'test -1.2'].reverse() 101 | }, 102 | 103 | { 104 | name: 'ends with number 1, contains 2 numbers, 1 cell, 3 target count (other dir)', 105 | data: ['test 2 test 1.3'], 106 | targetCount: 3, 107 | isNormalDirection: false, 108 | expected: ['test 2 test 0.3', 'test 2 test -0.7', 'test 2 test -1.7'].reverse() 109 | }, 110 | 111 | ] 112 | 113 | 114 | let tests_containsNumbersInts_StartsWithNumber_multiCells: AutoFillTestData[] = [ 115 | { 116 | name: '2 consecutive numbered cells [0, 1], contains number does only constant delta (1 here)', 117 | data: ['test 0.1', 'test 1.1'], 118 | targetCount: 1, 119 | isNormalDirection: true, 120 | expected: ['test 2.1'] 121 | }, 122 | { 123 | name: '2 consecutive numbered cells [7, 8], contains number does only constant delta (1 here)', 124 | data: ['test 7.15', 'test 8.15'], 125 | targetCount: 3, 126 | isNormalDirection: true, 127 | expected: ['test 9.15', 'test 10.15', 'test 11.15'] 128 | }, 129 | { 130 | name: '3 consecutive numbered cells [7, 8, 9], contains number does only constant delta (1 here)', 131 | data: ['test 7.99', 'test 8.99', 'test 9.99'], 132 | targetCount: 3, 133 | isNormalDirection: true, 134 | expected: ['test 10.99', 'test 11.99', 'test 12.99'] 135 | }, 136 | { 137 | name: '3 not consecutive numbered cells [7, 8, 9], contains number does only constant delta (2 here)', 138 | data: ['test 7.3', 'test 9.3', 'test 11.3'], 139 | targetCount: 3, 140 | isNormalDirection: true, 141 | expected: ['test 13.3', 'test 15.3', 'test 17.3'] 142 | }, 143 | 144 | { 145 | name: '3 not consecutive numbered cells [7, 9, 12], defaults to copy only', 146 | data: ['test 7.56', 'test 9.56', 'test 12.56'], 147 | targetCount: 7, 148 | isNormalDirection: true, 149 | expected: ['test 7.56', 'test 9.56', 'test 12.56', 'test 7.56', 'test 9.56', 'test 12.56', 'test 7.56'] 150 | }, 151 | { 152 | name: '3 not consecutive numbered cells [7, 9, 12, 15] (delta 2, delta 3), defaults to copy only', 153 | data: ['test 7.0123456789', 'test 9.0123456789', 'test 12.0123456789', 'test 15.0123456789', 'test 18.0123456789'], 154 | targetCount: 5, 155 | isNormalDirection: true, 156 | expected: ['test 7.0123456789', 'test 9.0123456789', 'test 12.0123456789', 'test 15.0123456789', 'test 18.0123456789'], 157 | }, 158 | 159 | //--- other direction 160 | 161 | { 162 | name: '2 consecutive numbered cells [0, 1], contains number does only constant delta (1 here) (other dir)', 163 | data: ['test 0.3', 'test 1.3'], 164 | targetCount: 1, 165 | isNormalDirection: false, 166 | expected: ['test -0.7'] 167 | }, 168 | { 169 | name: '2 consecutive numbered cells [7, 8], contains number does only constant delta (1 here) (other dir)', 170 | data: ['test 7.7', 'test 8.7'], 171 | targetCount: 3, 172 | isNormalDirection: false, 173 | expected: ['test 6.7', 'test 5.7', 'test 4.7'].reverse() 174 | }, 175 | { 176 | name: '3 consecutive numbered cells [7, 8, 9], contains number does only constant delta (1 here) (other dir)', 177 | data: ['test 7.1', 'test 8.1', 'test 9.1'], 178 | targetCount: 3, 179 | isNormalDirection: false, 180 | expected: ['test 6.1', 'test 5.1', 'test 4.1'].reverse() 181 | }, 182 | { 183 | name: '3 not consecutive numbered cells [7, 8, 9], contains number does only constant delta (2 here) (other dir)', 184 | data: ['test 7.1', 'test 9.1', 'test 11.1'], 185 | targetCount: 3, 186 | isNormalDirection: false, 187 | expected: ['test 5.1', 'test 3.1', 'test 1.1'].reverse() 188 | }, 189 | 190 | { 191 | name: '3 not consecutive numbered cells [7, 9, 12], defaults to copy only (other dir)', 192 | data: ['test 7.1', 'test 9.1', 'test 12.1'], 193 | targetCount: 7, 194 | isNormalDirection: false, 195 | expected: ['test 12.1', 'test 9.1', 'test 7.1', 'test 12.1', 'test 9.1', 'test 7.1', 'test 12.1'].reverse() 196 | }, 197 | { 198 | name: '3 not consecutive numbered cells [7, 9, 12, 15] (delta 2, delta 3), defaults to copy only', 199 | data: ['test 7.1', 'test 9.1', 'test 12.1', 'test 15.1', 'test 18.1'], 200 | targetCount: 5, 201 | isNormalDirection: false, 202 | expected: ['test 18.1', 'test 15.1', 'test 12.1', 'test 9.1', 'test 7.1'].reverse() 203 | }, 204 | 205 | ] 206 | 207 | let tests_containsNumbersInts_EndsWithNumber_special: AutoFillTestData[] = [ 208 | { 209 | name: 'ends with float precision test 1 (big properly configured, at least 20 fract digits)', 210 | data: ['xyz abc 1.01234567890123456789'], 211 | targetCount: 1, 212 | isNormalDirection: true, 213 | expected: ['xyz abc 2.01234567890123456789'] 214 | }, 215 | { 216 | name: 'ends with float precision test -1 (big properly configured, at least 20 fract digits)', 217 | data: ['xyz abc -1.01234567890123456789'], 218 | targetCount: 1, 219 | isNormalDirection: true, 220 | expected: ['xyz abc -0.01234567890123456789'] 221 | }, 222 | { 223 | name: 'ends with float large values test 5 (big properly configured, at least 20 fract digits)', 224 | //unsigned long: 4,294,967,295 -> 4294967295 -> just a bit more 225 | data: ['xyz abc 5294967295.01234567890123456789'], 226 | targetCount: 1, 227 | isNormalDirection: true, 228 | expected: ['xyz abc 5294967296.01234567890123456789'] 229 | }, 230 | { 231 | name: 'ends with float large values test -5 (big properly configured, at least 20 fract digits)', 232 | //unsigned long: 4,294,967,295 -> 4294967295 233 | data: ['xyz abc -5294967295.01234567890123456789'], 234 | targetCount: 1, 235 | isNormalDirection: true, 236 | expected: ['xyz abc -5294967294.01234567890123456789'] 237 | }, 238 | 239 | { 240 | name: 'ends with float precision test 1 (big properly configured, at least 20 fract digits)', 241 | data: ['xyz abc 1.0123456789012345678913333333334000000000000000000000000000000000000000000000000000000000004564560198'], 242 | targetCount: 1, 243 | isNormalDirection: true, 244 | expected: ['xyz abc 2.0123456789012345678913333333334000000000000000000000000000000000000000000000000000000000004564560198'] 245 | }, 246 | { 247 | name: 'ends with float precision test 1 (big properly configured, at least 20 fract digits)', 248 | //100 digits . 100 digits 249 | data: ['xyz abc 1123456789012345678913333333334000000000000000000000000000000000000000000000000000000000004564560198.1123456789012345678913333333334000000000000000000000000000000000000000000000000000000000004564560198'], 250 | targetCount: 1, 251 | isNormalDirection: true, 252 | expected: ['xyz abc 1123456789012345678913333333334000000000000000000000000000000000000000000000000000000000004564560199.1123456789012345678913333333334000000000000000000000000000000000000000000000000000000000004564560198'] 253 | }, 254 | ] 255 | 256 | let allTests: AutoFillTestSuit[] = [ 257 | { 258 | name: 'ends with number tests, single group', 259 | tests: tests_containsNumbersInts_StartsWithNumber_singleGroup 260 | }, 261 | { 262 | name: 'ends with number tests, multiple groups', 263 | tests: tests_containsNumbersInts_StartsWithNumber_multiCells 264 | }, 265 | { 266 | name: 'ends with number tests, special', 267 | tests: tests_containsNumbersInts_EndsWithNumber_special 268 | }, 269 | ] 270 | 271 | 272 | for (let i = 0; i < allTests.length; i++) { 273 | const testSuit = allTests[i] 274 | 275 | suite(testSuit.name, () => { 276 | 277 | for (let j = 0; j < testSuit.tests.length; j++) { 278 | const testCase = testSuit.tests[j] 279 | 280 | test(testCase.name, () => { 281 | let result = customAutoFillFunc( 282 | testCase.data, 283 | testCase.targetCount, 284 | testCase.isNormalDirection, 285 | { altKey: false } as MouseEvent 286 | ) 287 | expect(result).toEqual(testCase.expected) 288 | }) 289 | 290 | } 291 | 292 | }) 293 | 294 | } 295 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/endsWithInt.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | import { AutoFillTestData, AutoFillTestSuit } from './types' 3 | 4 | 5 | let tests_containsNumbersInts_EdnsWithNumber_singleGroup: AutoFillTestData[] = [ 6 | { 7 | name: 'ends with number -5, 1 cell, 1 target count', 8 | data: ['test -5'], 9 | targetCount: 1, 10 | isNormalDirection: true, 11 | expected: ['test -4'] 12 | }, 13 | { 14 | name: 'ends with number 0, 1 cell, 1 target count', 15 | data: ['test 0'], 16 | targetCount: 1, 17 | isNormalDirection: true, 18 | expected: ['test 1'] 19 | }, 20 | { 21 | name: 'ends with number 99, 1 cell, 1 target count', 22 | data: ['test 99'], 23 | targetCount: 1, 24 | isNormalDirection: true, 25 | expected: ['test 100'] 26 | }, 27 | { 28 | name: 'ends with number 1, 1 cell, 1 target count', 29 | data: ['test 1'], 30 | targetCount: 1, 31 | isNormalDirection: true, 32 | expected: ['test 2'] 33 | }, 34 | { 35 | name: 'ends with number 1, 1 cell, 2 target count', 36 | data: ['test 1'], 37 | targetCount: 2, 38 | isNormalDirection: true, 39 | expected: ['test 2', 'test 3'] 40 | }, 41 | { 42 | name: 'ends with number 1, 1 cell, 3 target count', 43 | data: ['test 1'], 44 | targetCount: 3, 45 | isNormalDirection: true, 46 | expected: ['test 2', 'test 3', 'test 4'] 47 | }, 48 | 49 | { 50 | name: 'ends with number 1, contains 2 numbers, 1 cell, 3 target count', 51 | data: ['test 1 test 2'], 52 | targetCount: 3, 53 | isNormalDirection: true, 54 | expected: ['test 1 test 3', 'test 1 test 4', 'test 1 test 5'] 55 | }, 56 | 57 | // { 58 | // name: 'ends with number has priority, 1 cell, 3 target count', //start has priority 59 | // data: ['2 test 3'], 60 | // targetCount: 3, 61 | // isNormalDirection: true, 62 | // expected: ['3 test 3', '4 test 3', '5 test 3'] 63 | // }, 64 | 65 | //--- other direction 66 | { 67 | name: 'ends with number -5, 1 cell, 1 target count (other dir)', 68 | data: ['test -5'], 69 | targetCount: 1, 70 | isNormalDirection: false, 71 | expected: ['test -6'] 72 | }, 73 | { 74 | name: 'ends with number 0, 1 cell, 1 target count (other dir)', 75 | data: ['test 0'], 76 | targetCount: 1, 77 | isNormalDirection: false, 78 | expected: ['test -1'] 79 | }, 80 | { 81 | name: 'ends with number 99, 1 cell, 1 target count (other dir)', 82 | data: ['test 99'], 83 | targetCount: 1, 84 | isNormalDirection: false, 85 | expected: ['test 98'] 86 | }, 87 | { 88 | name: 'ends with number 1, 1 cell, 1 target count (other dir)', 89 | data: ['test 1'], 90 | targetCount: 1, 91 | isNormalDirection: false, 92 | expected: ['test 0'] 93 | }, 94 | { 95 | name: 'ends with number 1, 1 cell, 2 target count (other dir)', 96 | data: ['test 1'], 97 | targetCount: 2, 98 | isNormalDirection: false, 99 | expected: ['test 0', 'test -1'].reverse() 100 | }, 101 | { 102 | name: 'ends with number 1, 1 cell, 3 target count (other dir)', 103 | data: ['test 1'], 104 | targetCount: 3, 105 | isNormalDirection: false, 106 | expected: ['test 0', 'test -1', 'test -2'].reverse() 107 | }, 108 | 109 | { 110 | name: 'ends with number 1, contains 2 numbers, 1 cell, 3 target count (other dir)', 111 | data: ['test 1 test 2'], 112 | targetCount: 3, 113 | isNormalDirection: false, 114 | expected: ['test 1 test 1', 'test 1 test 0', 'test 1 test -1'].reverse() 115 | }, 116 | ] 117 | 118 | //TODO 119 | let tests_containsNumbersInts_EndsWithNumber_multiCells: AutoFillTestData[] = [ 120 | { 121 | name: '2 consecutive numbered cells [0, 1], contains number does only constant delta (1 here)', 122 | data: ['test 0', 'test 1'], 123 | targetCount: 1, 124 | isNormalDirection: true, 125 | expected: ['test 2'] 126 | }, 127 | { 128 | name: '2 consecutive numbered cells [7, 8], contains number does only constant delta (1 here)', 129 | data: ['test 7', 'test 8'], 130 | targetCount: 3, 131 | isNormalDirection: true, 132 | expected: ['test 9', 'test 10', 'test 11'] 133 | }, 134 | { 135 | name: '3 consecutive numbered cells [7, 8, 9], contains number does only constant delta (1 here)', 136 | data: ['test 7', 'test 8', 'test 9'], 137 | targetCount: 3, 138 | isNormalDirection: true, 139 | expected: ['test 10', 'test 11', 'test 12'] 140 | }, 141 | { 142 | name: '3 not consecutive numbered cells [7, 8, 9], contains number does only constant delta (2 here)', 143 | data: ['test 7', 'test 9', 'test 11'], 144 | targetCount: 3, 145 | isNormalDirection: true, 146 | expected: ['test 13', 'test 15', 'test 17'] 147 | }, 148 | 149 | { 150 | name: '3 not consecutive numbered cells [7, 9, 12], defaults to copy only', 151 | data: ['test 7', 'test 9', 'test 12'], 152 | targetCount: 7, 153 | isNormalDirection: true, 154 | expected: ['test 7', 'test 9', 'test 12', 'test 7', 'test 9', 'test 12', 'test 7'] 155 | }, 156 | { 157 | name: '3 not consecutive numbered cells [7, 9, 12, 15] (delta 2, delta 3), defaults to copy only', 158 | data: ['test 7', 'test 9', 'test 12', 'test 15', 'test 18'], 159 | targetCount: 5, 160 | isNormalDirection: true, 161 | expected: ['test 7', 'test 9', 'test 12', 'test 15', 'test 18'], 162 | }, 163 | 164 | //--- other direction 165 | 166 | { 167 | name: '2 consecutive numbered cells [0, 1], contains number does only constant delta (1 here) (other dir)', 168 | data: ['test 0', 'test 1'], 169 | targetCount: 1, 170 | isNormalDirection: false, 171 | expected: ['test -1'] 172 | }, 173 | { 174 | name: '2 consecutive numbered cells [7, 8], contains number does only constant delta (1 here) (other dir)', 175 | data: ['test 7', 'test 8'], 176 | targetCount: 3, 177 | isNormalDirection: false, 178 | expected: ['test 6', 'test 5', 'test 4'].reverse() 179 | }, 180 | { 181 | name: '3 consecutive numbered cells [7, 8, 9], contains number does only constant delta (1 here) (other dir)', 182 | data: ['test 7', 'test 8', 'test 9'], 183 | targetCount: 3, 184 | isNormalDirection: false, 185 | expected: ['test 6', 'test 5', 'test 4'].reverse() 186 | }, 187 | { 188 | name: '3 not consecutive numbered cells [7, 8, 9], contains number does only constant delta (2 here) (other dir)', 189 | data: ['test 7', 'test 9', 'test 11'], 190 | targetCount: 3, 191 | isNormalDirection: false, 192 | expected: ['test 5', 'test 3', 'test 1'].reverse() 193 | }, 194 | 195 | { 196 | name: '3 not consecutive numbered cells [7, 9, 12], defaults to copy only (other dir)', 197 | data: ['test 7', 'test 9', 'test 12'], 198 | targetCount: 7, 199 | isNormalDirection: false, 200 | expected: ['test 12', 'test 9', 'test 7', 'test 12', 'test 9', 'test 7', 'test 12'].reverse() 201 | }, 202 | { 203 | name: '3 not consecutive numbered cells [7, 9, 12, 15] (delta 2, delta 3), defaults to copy only', 204 | data: ['test 7', 'test 9', 'test 12', 'test 15', 'test 18'], 205 | targetCount: 5, 206 | isNormalDirection: false, 207 | expected: ['test 18', 'test 15', 'test 12', 'test 9', 'test 7'].reverse() 208 | }, 209 | ] 210 | 211 | let allTests: AutoFillTestSuit[] = [ 212 | { 213 | name: 'ends with number tests, single group', 214 | tests: tests_containsNumbersInts_EdnsWithNumber_singleGroup 215 | }, 216 | { 217 | name: 'ends with number tests, multiple groups', 218 | tests: tests_containsNumbersInts_EndsWithNumber_multiCells 219 | }, 220 | ] 221 | 222 | 223 | for (let i = 0; i < allTests.length; i++) { 224 | const testSuit = allTests[i] 225 | 226 | suite(testSuit.name, () => { 227 | 228 | for (let j = 0; j < testSuit.tests.length; j++) { 229 | const testCase = testSuit.tests[j] 230 | 231 | test(testCase.name, () => { 232 | let result = customAutoFillFunc( 233 | testCase.data, 234 | testCase.targetCount, 235 | testCase.isNormalDirection, 236 | { altKey: false } as MouseEvent 237 | ) 238 | expect(result).toEqual(testCase.expected) 239 | }) 240 | 241 | } 242 | 243 | }) 244 | 245 | } 246 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/floats.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | import { AutoFillTestData, AutoFillTestSuit } from './types' 3 | 4 | 5 | let tests_onlyFloatsLinearRegression: AutoFillTestData[] = [ 6 | { 7 | name: 'increment by 1, 1 cell, 1 target count', 8 | data: ['1.3'], 9 | targetCount: 3, 10 | isNormalDirection: true, 11 | expected: ['2.3', '3.3', '4.3'] 12 | }, 13 | { 14 | name: 'increment by 1, int: 5, 3 target count', 15 | data: ['5.05'], 16 | targetCount: 3, 17 | isNormalDirection: true, 18 | expected: ['6.05', '7.05', '8.05'] 19 | }, 20 | { 21 | name: 'increment by 1, int: -5, 3 target count', 22 | data: ['-5.9'], 23 | targetCount: 3, 24 | isNormalDirection: true, 25 | expected: ['-4.9', '-3.9', '-2.9'] 26 | }, 27 | { 28 | name: 'increment by 2, int: 5', 29 | data: ['5.3', '7'], 30 | targetCount: 3, 31 | isNormalDirection: true, 32 | expected: ['8.7', '10.4', '12.1'] 33 | }, 34 | { 35 | name: 'increment by 2, int: [1, 3], always rounded to 2 decimals (only when interpolating)', 36 | data: ['1.5555', '3.5555'], 37 | targetCount: 3, 38 | isNormalDirection: true, 39 | expected: ['5.56', '7.56', '9.56'] 40 | }, 41 | { 42 | name: 'real regression 1, int: [1, 3, 8], always rounded to 2 decimals (only when interpolating)', 43 | data: ['1.5', '3.3', '8.9111111111'], 44 | targetCount: 5, 45 | isNormalDirection: true, 46 | expected: ['11.99', '15.7', '19.41', '23.12', '26.83'] 47 | }, 48 | { 49 | name: 'floats are rounded, even when only incrementing because of interpolation (not just plain add/sub)', 50 | data: ['1.5555'], 51 | targetCount: 3, 52 | isNormalDirection: true, 53 | expected: ['2.56', '3.56', '4.56'] 54 | }, 55 | { 56 | name: 'real regression 2, int: [23, 67, 89, 101]', 57 | data: ['23.5', '67.1', '89', '101.1'], 58 | targetCount: 7, 59 | isNormalDirection: true, 60 | expected: ['133.85', '159.32', '184.79', '210.26', '235.73', '261.2', '286.67'] 61 | }, 62 | { 63 | name: 'real regression 3, int: [5, 8, -2, 8, -4]', 64 | data: ['5.3', '8.3', '-2.3', '8.3', '-4.4'], 65 | targetCount: 5, 66 | isNormalDirection: true, 67 | expected: ['-2.78', '-4.72', '-6.66', '-8.6', '-10.54'] 68 | }, 69 | 70 | //--- other direction 71 | 72 | { 73 | name: 'decrement by 1, 1 cell, 1 target count (other dir)', 74 | data: ['1.3'], 75 | targetCount: 3, 76 | isNormalDirection: false, 77 | expected: ['0.3', '-0.7', '-1.7'].reverse() 78 | }, 79 | { 80 | name: 'decrement by 1, int: 5, 3 target count (other dir)', 81 | data: ['5.05'], 82 | targetCount: 3, 83 | isNormalDirection: false, 84 | expected: ['4.05', '3.05', '2.05'].reverse() 85 | }, 86 | { 87 | name: 'decrement by 1, int: -5, 3 target count (other dir)', 88 | data: ['-5.9'], 89 | targetCount: 3, 90 | isNormalDirection: false, 91 | expected: ['-6.9', '-7.9', '-8.9'].reverse() 92 | }, 93 | { 94 | name: 'decrement by 2, int: 5 (other dir)', 95 | data: ['5.3', '7'], 96 | targetCount: 3, 97 | isNormalDirection: false, 98 | expected: ['3.6', '1.9', '0.2'].reverse() 99 | }, 100 | { 101 | name: 'floats are rounded, even when only incrementing because of interpolation (not just plain add/sub) (other dir)', 102 | data: ['1.5555', '3.5555'], 103 | targetCount: 3, 104 | isNormalDirection: false, 105 | expected: ['-0.44', '-2.44', '-4.44'].reverse() 106 | }, 107 | { 108 | name: 'real regression 1, int: [1, 3, 8], always rounded to 2 decimals (only when interpolating) (other dir)', 109 | data: ['1.5', '3.3', '8.9111111111'], 110 | targetCount: 5, 111 | isNormalDirection: false, 112 | expected: ['-2.85', '-6.56', '-10.27', '-13.98', '-17.69'].reverse() 113 | }, 114 | { 115 | name: 'no rounding when only decrementing (other dir)', 116 | data: ['2.5555'], 117 | targetCount: 3, 118 | isNormalDirection: false, 119 | expected: ['1.56', '0.56', '-0.44'].reverse() 120 | }, 121 | { 122 | name: 'real regression 2, int: [23, 67, 89, 101] (other dir)', 123 | data: ['23.5', '67.1', '89', '101.1'], 124 | targetCount: 7, 125 | isNormalDirection: false, 126 | expected: ['6.5', '-18.97', '-44.44', '-69.91', '-95.38', '-120.85', '-146.32'].reverse() 127 | }, 128 | { 129 | name: 'real regression 3, int: [5, 8, -2, 8, -4] (other dir)', 130 | data: ['5.3', '8.3', '-2.3', '8.3', '-4.4'], 131 | targetCount: 5, 132 | isNormalDirection: false, 133 | expected: ['8.86', '10.8', '12.74', '14.68', '16.62'].reverse() 134 | }, 135 | ] 136 | 137 | let allTests: AutoFillTestSuit[] = [ 138 | { 139 | name: 'test only floats linear regression', 140 | tests: tests_onlyFloatsLinearRegression 141 | }, 142 | ] 143 | 144 | 145 | for (let i = 0; i < allTests.length; i++) { 146 | const testSuit = allTests[i] 147 | 148 | suite(testSuit.name, () => { 149 | 150 | for (let j = 0; j < testSuit.tests.length; j++) { 151 | const testCase = testSuit.tests[j] 152 | 153 | test(testCase.name, () => { 154 | let result = customAutoFillFunc( 155 | testCase.data, 156 | testCase.targetCount, 157 | testCase.isNormalDirection, 158 | { altKey: false } as MouseEvent 159 | ) 160 | expect(result).toEqual(testCase.expected) 161 | }) 162 | 163 | } 164 | 165 | }) 166 | 167 | } 168 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/ints.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | import { AutoFillTestData, AutoFillTestSuit } from './types' 3 | 4 | 5 | let tests_onlyIntsLinearRegression: AutoFillTestData[] = [ 6 | { 7 | name: 'increment by 1, 1 cell, 1 target count', 8 | data: ['1'], 9 | targetCount: 3, 10 | isNormalDirection: true, 11 | expected: ['2', '3', '4'] 12 | }, 13 | { 14 | name: 'increment by 1, int: 5, 3 target count', 15 | data: ['5'], 16 | targetCount: 3, 17 | isNormalDirection: true, 18 | expected: ['6', '7', '8'] 19 | }, 20 | { 21 | name: 'increment by 1, int: -5, 3 target count', 22 | data: ['-5'], 23 | targetCount: 3, 24 | isNormalDirection: true, 25 | expected: ['-4', '-3', '-2'] 26 | }, 27 | { 28 | name: 'increment by 2, int: 5', 29 | data: ['5', '7'], 30 | targetCount: 3, 31 | isNormalDirection: true, 32 | expected: ['9', '11', '13'] 33 | }, 34 | { 35 | name: 'real regression 1, int: [1, 3, 8]', 36 | data: ['1', '3', '8'], 37 | targetCount: 5, 38 | isNormalDirection: true, 39 | expected: ['11', '14.5', '18', '21.5', '25'] 40 | }, 41 | { 42 | name: 'real regression 2, int: [23, 67, 89, 101]', 43 | data: ['23', '67', '89', '101'], 44 | targetCount: 7, 45 | isNormalDirection: true, 46 | expected: ['134', '159.6', '185.2', '210.8', '236.4', '262', '287.6'] 47 | }, 48 | { 49 | name: 'real regression 3, int: [5, 8, -2, 8, -4]', 50 | data: ['5', '8', '-2', '8', '-4'], 51 | targetCount: 5, 52 | isNormalDirection: true, 53 | expected: ['-2.4', '-4.2', '-6', '-7.8', '-9.6'] 54 | }, 55 | //--- large numbers with big.js 56 | { 57 | name: 'larger than unsigned long, 1 cell, 1 target count', 58 | //unsigned long: 4,294,967,295 -> 4294967295 -> just a bit more 59 | data: ['01234567890123456789012345678901234567890123456789'], 60 | targetCount: 1, 61 | isNormalDirection: true, 62 | expected: ['1234567890123456789012345678901234567890123456790'] 63 | }, 64 | { 65 | name: 'larger than unsigned long, 1 cell, 1 target count (end is same as [1, 3, 8])', 66 | //unsigned long: 4,294,967,295 -> 4294967295 -> just a bit more 67 | //end is same as [1, 3, 8] 68 | data: ['12345678901234567890123456789012345678901234567891', '12345678901234567890123456789012345678901234567893', '12345678901234567890123456789012345678901234567898'], 69 | targetCount: 2, 70 | isNormalDirection: true, 71 | expected: ['12345678901234567890123456789012345678901234567901', '12345678901234567890123456789012345678901234567904.5'] 72 | }, 73 | 74 | //--- other direction 75 | 76 | { 77 | name: 'decrement by 1, 1 cell, 1 target count', 78 | data: ['1'], 79 | targetCount: 3, 80 | isNormalDirection: false, 81 | expected: ['0', '-1', '-2'].reverse() 82 | }, 83 | { 84 | name: 'decrement by 1, int: 5, 3 target count', 85 | data: ['5'], 86 | targetCount: 3, 87 | isNormalDirection: false, 88 | expected: ['4', '3', '2'].reverse() 89 | }, 90 | { 91 | name: 'decrement by 1, int: -5, 3 target count', 92 | data: ['-5'], 93 | targetCount: 3, 94 | isNormalDirection: false, 95 | expected: ['-6', '-7', '-8'].reverse() 96 | }, 97 | { 98 | name: 'decrement by 2, int: 5', 99 | data: ['5', '7'], 100 | targetCount: 3, 101 | isNormalDirection: false, 102 | expected: ['3', '1', '-1'].reverse() 103 | }, 104 | { 105 | name: 'real regression 1, int: [1, 3, 8] (other dir)', 106 | data: ['1', '3', '8'], 107 | targetCount: 5, 108 | isNormalDirection: false, 109 | expected: ['-3', '-6.5', '-10', '-13.5', '-17'].reverse() 110 | }, 111 | { 112 | name: 'real regression 2, int: [23, 67, 89, 101] (other dir)', 113 | data: ['23', '67', '89', '101'], 114 | targetCount: 7, 115 | isNormalDirection: false, 116 | expected: ['6', '-19.6', '-45.2', '-70.8', '-96.4', '-122', '-147.6'].reverse() 117 | }, 118 | { 119 | name: 'real regression 3, int: [5, 8, -2, 8, -4] (other dir)', 120 | data: ['5', '8', '-2', '8', '-4'], 121 | targetCount: 5, 122 | isNormalDirection: false, 123 | expected: ['8.4', '10.2', '12', '13.8', '15.6'].reverse() 124 | }, 125 | //--- large numbers with big.js 126 | { 127 | name: 'larger than unsigned long, 1 cell, 1 target count (other dir)', 128 | //unsigned long: 4,294,967,295 -> 4294967295 -> just a bit more 129 | data: ['01234567890123456789012345678901234567890123456789'], 130 | targetCount: 1, 131 | isNormalDirection: false, 132 | expected: ['1234567890123456789012345678901234567890123456788'] 133 | }, 134 | { 135 | name: 'larger than unsigned long, 1 cell, 1 target count (end is same as [1, 3, 8]) (other dir)', 136 | //unsigned long: 4,294,967,295 -> 4294967295 -> just a bit more 137 | //end is same as [1, 3, 8] 138 | data: ['12345678901234567890123456789012345678901234567891', '12345678901234567890123456789012345678901234567893', '12345678901234567890123456789012345678901234567898'], 139 | targetCount: 2, 140 | isNormalDirection: false, 141 | expected: ['12345678901234567890123456789012345678901234567887', '12345678901234567890123456789012345678901234567883.5'].reverse() 142 | }, 143 | ] 144 | 145 | let allTests: AutoFillTestSuit[] = [ 146 | { 147 | name: 'test only ints linear regression', 148 | tests: tests_onlyIntsLinearRegression 149 | }, 150 | ] 151 | 152 | 153 | for (let i = 0; i < allTests.length; i++) { 154 | const testSuit = allTests[i] 155 | 156 | suite(testSuit.name, () => { 157 | 158 | for (let j = 0; j < testSuit.tests.length; j++) { 159 | const testCase = testSuit.tests[j] 160 | 161 | test(testCase.name, () => { 162 | let result = customAutoFillFunc( 163 | testCase.data, 164 | testCase.targetCount, 165 | testCase.isNormalDirection, 166 | { altKey: false } as MouseEvent 167 | ) 168 | expect(result).toEqual(testCase.expected) 169 | }) 170 | 171 | } 172 | 173 | }) 174 | 175 | } 176 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/justCopy.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | import { AutoFillTestData, AutoFillTestSuit } from './types' 3 | 4 | 5 | let tests_justCopy: AutoFillTestData[] = [ 6 | { 7 | name: 'just copy, 1 cell, 1 target', 8 | data: ['test'], 9 | targetCount: 1, 10 | isNormalDirection: true, 11 | expected: ['test'] 12 | }, 13 | { 14 | name: 'just copy, 1 cell, 3 target', 15 | data: ['test'], 16 | targetCount: 3, 17 | isNormalDirection: true, 18 | expected: ['test', 'test', 'test'] 19 | }, 20 | 21 | { 22 | name: 'just copy, 2 cells, 1 target', 23 | data: ['test', 'xyz'], 24 | targetCount: 1, 25 | isNormalDirection: true, 26 | expected: ['test'] 27 | }, 28 | { 29 | name: 'just copy, 2 cells, 2 target', 30 | data: ['test', 'xyz'], 31 | targetCount: 2, 32 | isNormalDirection: true, 33 | expected: ['test', 'xyz'] 34 | }, 35 | { 36 | name: 'just copy, 2 cells, 3 target', 37 | data: ['test', 'xyz'], 38 | targetCount: 3, 39 | isNormalDirection: true, 40 | expected: ['test', 'xyz', 'test'] 41 | }, 42 | { 43 | name: 'just copy, 2 cells, 4 target', 44 | data: ['test', 'xyz'], 45 | targetCount: 4, 46 | isNormalDirection: true, 47 | expected: ['test', 'xyz', 'test', 'xyz'] 48 | }, 49 | { 50 | name: 'just copy, 1 cell, 1 target (ends with number)', 51 | data: ['test', 'test2'], 52 | targetCount: 2, 53 | isNormalDirection: true, 54 | expected: ['test', 'test3'] 55 | }, 56 | //--- other dir 57 | { 58 | name: 'just copy, 1 cell, 1 targe target (other dir)t', 59 | data: ['test'], 60 | targetCount: 1, 61 | isNormalDirection: false, 62 | expected: ['test'] 63 | }, 64 | { 65 | name: 'just copy, 1 cell, 3 target target (other dir)', 66 | data: ['test'], 67 | targetCount: 3, 68 | isNormalDirection: false, 69 | expected: ['test', 'test', 'test'].reverse() 70 | }, 71 | 72 | { 73 | name: 'just copy, 2 cells, 1 target target (other dir)', 74 | data: ['test', 'xyz'], 75 | targetCount: 1, 76 | isNormalDirection: false, 77 | expected: ['xyz'] 78 | }, 79 | { 80 | name: 'just copy, 2 cells, 2 target target (other dir)', 81 | data: ['test', 'xyz'], 82 | targetCount: 2, 83 | isNormalDirection: false, 84 | expected: ['xyz', 'test'].reverse() 85 | }, 86 | { 87 | name: 'just copy, 2 cells, 3 target target (other dir)', 88 | data: ['test', 'xyz'], 89 | targetCount: 3, 90 | isNormalDirection: false, 91 | expected: ['xyz', 'test', 'xyz'].reverse() 92 | }, 93 | { 94 | name: 'just copy, 2 cells, 4 target (other dir)', 95 | data: ['test', 'xyz'], 96 | targetCount: 4, 97 | isNormalDirection: false, 98 | expected: ['xyz', 'test', 'xyz', 'test'].reverse() 99 | }, 100 | { 101 | name: 'just copy, 1 cell, 1 target (ends with number) (other dir)', 102 | data: ['test', 'test2'], 103 | targetCount: 2, 104 | isNormalDirection: false, 105 | expected: ['test1', 'test'].reverse() 106 | }, 107 | ] 108 | 109 | let allTests: AutoFillTestSuit[] = [ 110 | { 111 | name: 'just copy', 112 | tests: tests_justCopy 113 | }, 114 | ] 115 | 116 | 117 | for (let i = 0; i < allTests.length; i++) { 118 | const testSuit = allTests[i] 119 | 120 | suite(testSuit.name, () => { 121 | 122 | for (let j = 0; j < testSuit.tests.length; j++) { 123 | const testCase = testSuit.tests[j] 124 | 125 | test(testCase.name, () => { 126 | let result = customAutoFillFunc( 127 | testCase.data, 128 | testCase.targetCount, 129 | testCase.isNormalDirection, 130 | { altKey: false } as MouseEvent 131 | ) 132 | expect(result).toEqual(testCase.expected) 133 | }) 134 | 135 | } 136 | 137 | }) 138 | 139 | } 140 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/startsWithInt.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | import { AutoFillTestData, AutoFillTestSuit } from './types' 3 | 4 | 5 | let tests_containsNumbersInts_StartsWithNumber_singleGroup: AutoFillTestData[] = [ 6 | { 7 | name: 'starts with number 0, 1 cell, 1 target count', 8 | data: ['-5 test'], 9 | targetCount: 1, 10 | isNormalDirection: true, 11 | expected: ['-4 test'] 12 | }, 13 | { 14 | name: 'starts with number 0, 1 cell, 1 target count', 15 | data: ['0 test'], 16 | targetCount: 1, 17 | isNormalDirection: true, 18 | expected: ['1 test'] 19 | }, 20 | { 21 | name: 'starts with number 99, 1 cell, 1 target count', 22 | data: ['99 test'], 23 | targetCount: 1, 24 | isNormalDirection: true, 25 | expected: ['100 test'] 26 | }, 27 | { 28 | name: 'starts with number 1, 1 cell, 1 target count', 29 | data: ['1 test'], 30 | targetCount: 1, 31 | isNormalDirection: true, 32 | expected: ['2 test'] 33 | }, 34 | { 35 | name: 'starts with number 1, 1 cell, 2 target count', 36 | data: ['1 test'], 37 | targetCount: 2, 38 | isNormalDirection: true, 39 | expected: ['2 test', '3 test'] 40 | }, 41 | { 42 | name: 'starts with number 1, 1 cell, 3 target count', 43 | data: ['1 test'], 44 | targetCount: 3, 45 | isNormalDirection: true, 46 | expected: ['2 test', '3 test', '4 test'] 47 | }, 48 | 49 | { 50 | name: 'starts with number 1, contains 2 numbers, 1 cell, 3 target count', 51 | data: ['1 test 2 test'], 52 | targetCount: 3, 53 | isNormalDirection: true, 54 | expected: ['2 test 2 test', '3 test 2 test', '4 test 2 test'] 55 | }, 56 | 57 | { 58 | name: 'start with number has priority, 1 cell, 3 target count', 59 | data: ['2 test 3'], 60 | targetCount: 3, 61 | isNormalDirection: true, 62 | expected: ['3 test 3', '4 test 3', '5 test 3'] 63 | }, 64 | 65 | //--- other direction 66 | { 67 | name: 'starts with number 0, 1 cell, 1 target count (other dir)', 68 | data: ['-5 test'], 69 | targetCount: 1, 70 | isNormalDirection: false, 71 | expected: ['-6 test'] 72 | }, 73 | { 74 | name: 'starts with number 0, 1 cell, 1 target count (other dir)', 75 | data: ['0 test'], 76 | targetCount: 1, 77 | isNormalDirection: false, 78 | expected: ['-1 test'] 79 | }, 80 | { 81 | name: 'starts with number 99, 1 cell, 1 target count (other dir)', 82 | data: ['99 test'], 83 | targetCount: 1, 84 | isNormalDirection: false, 85 | expected: ['98 test'] 86 | }, 87 | { 88 | name: 'starts with number 1, 1 cell, 1 target count (other dir)', 89 | data: ['1 test'], 90 | targetCount: 1, 91 | isNormalDirection: false, 92 | expected: ['0 test'] 93 | }, 94 | { 95 | name: 'starts with number 1, 1 cell, 2 target count (other dir)', 96 | data: ['1 test'], 97 | targetCount: 2, 98 | isNormalDirection: false, 99 | expected: ['0 test', '-1 test'].reverse() 100 | }, 101 | { 102 | name: 'starts with number 1, 1 cell, 3 target count (other dir)', 103 | data: ['1 test'], 104 | targetCount: 3, 105 | isNormalDirection: false, 106 | expected: ['0 test', '-1 test', '-2 test'].reverse() 107 | }, 108 | 109 | { 110 | name: 'starts with number 1, contains 2 numbers, 1 cell, 3 target count (other dir)', 111 | data: ['1 test 2 test'], 112 | targetCount: 3, 113 | isNormalDirection: false, 114 | expected: ['0 test 2 test', '-1 test 2 test', '-2 test 2 test'].reverse() 115 | }, 116 | 117 | { 118 | name: 'start with number has priority, 1 cell, 3 target count (other dir)', 119 | data: ['2 test 3'], 120 | targetCount: 3, 121 | isNormalDirection: false, 122 | expected: ['1 test 3', '0 test 3', '-1 test 3'].reverse() 123 | } 124 | ] 125 | 126 | 127 | let tests_containsNumbersInts_StartsWithNumber_multiCells: AutoFillTestData[] = [ 128 | { 129 | name: '2 consecutive numbered cells [0, 1], contains number does only constant delta (1 here)', 130 | data: ['0 test', '1 test'], 131 | targetCount: 1, 132 | isNormalDirection: true, 133 | expected: ['2 test'] 134 | }, 135 | { 136 | name: '2 consecutive numbered cells [7, 8], contains number does only constant delta (1 here)', 137 | data: ['7 test', '8 test'], 138 | targetCount: 3, 139 | isNormalDirection: true, 140 | expected: ['9 test', '10 test', '11 test'] 141 | }, 142 | { 143 | name: '3 consecutive numbered cells [7, 8, 9], contains number does only constant delta (1 here)', 144 | data: ['7 test', '8 test', '9 test'], 145 | targetCount: 3, 146 | isNormalDirection: true, 147 | expected: ['10 test', '11 test', '12 test'] 148 | }, 149 | { 150 | name: '3 not consecutive numbered cells [7, 8, 9], contains number does only constant delta (2 here)', 151 | data: ['7 test', '9 test', '11 test'], 152 | targetCount: 3, 153 | isNormalDirection: true, 154 | expected: ['13 test', '15 test', '17 test'] 155 | }, 156 | 157 | { 158 | name: '3 not consecutive numbered cells [7, 9, 12], defaults to copy only', 159 | data: ['7 test', '9 test', '12 test'], 160 | targetCount: 7, 161 | isNormalDirection: true, 162 | expected: ['7 test', '9 test', '12 test', '7 test', '9 test', '12 test', '7 test'] 163 | }, 164 | { 165 | name: '3 not consecutive numbered cells [7, 9, 12, 15] (delta 2, delta 3), defaults to copy only', 166 | data: ['7 test', '9 test', '12 test', '15 test', '18 test'], 167 | targetCount: 5, 168 | isNormalDirection: true, 169 | expected: ['7 test', '9 test', '12 test', '15 test', '18 test'], 170 | }, 171 | 172 | //--- other direction 173 | 174 | { 175 | name: '2 consecutive numbered cells [0, 1], contains number does only constant delta (1 here) (other dir)', 176 | data: ['0 test', '1 test'], 177 | targetCount: 1, 178 | isNormalDirection: false, 179 | expected: ['-1 test'] 180 | }, 181 | { 182 | name: '2 consecutive numbered cells [7, 8], contains number does only constant delta (1 here) (other dir)', 183 | data: ['7 test', '8 test'], 184 | targetCount: 3, 185 | isNormalDirection: false, 186 | expected: ['6 test', '5 test', '4 test'].reverse() 187 | }, 188 | { 189 | name: '3 consecutive numbered cells [7, 8, 9], contains number does only constant delta (1 here) (other dir)', 190 | data: ['7 test', '8 test', '9 test'], 191 | targetCount: 3, 192 | isNormalDirection: false, 193 | expected: ['6 test', '5 test', '4 test'].reverse() 194 | }, 195 | { 196 | name: '3 not consecutive numbered cells [7, 8, 9], contains number does only constant delta (2 here) (other dir)', 197 | data: ['7 test', '9 test', '11 test'], 198 | targetCount: 3, 199 | isNormalDirection: false, 200 | expected: ['5 test', '3 test', '1 test'].reverse() 201 | }, 202 | 203 | { 204 | name: '3 not consecutive numbered cells [7, 9, 12], defaults to copy only (other dir)', 205 | data: ['7 test', '9 test', '12 test'], 206 | targetCount: 7, 207 | isNormalDirection: false, 208 | expected: ['12 test', '9 test', '7 test', '12 test', '9 test', '7 test', '12 test'].reverse() 209 | }, 210 | { 211 | name: '3 not consecutive numbered cells [7, 9, 12, 15] (delta 2, delta 3), defaults to copy only', 212 | data: ['7 test', '9 test', '12 test', '15 test', '18 test'], 213 | targetCount: 5, 214 | isNormalDirection: false, 215 | expected: ['18 test', '15 test', '12 test', '9 test', '7 test'].reverse() 216 | }, 217 | 218 | ] 219 | 220 | let allTests: AutoFillTestSuit[] = [ 221 | { 222 | name: 'starts with number tests, single group', 223 | tests: tests_containsNumbersInts_StartsWithNumber_singleGroup 224 | }, 225 | { 226 | name: 'starts with number tests, multiple groups', 227 | tests: tests_containsNumbersInts_StartsWithNumber_multiCells 228 | }, 229 | ] 230 | 231 | 232 | for (let i = 0; i < allTests.length; i++) { 233 | const testSuit = allTests[i] 234 | 235 | suite(testSuit.name, () => { 236 | 237 | for (let j = 0; j < testSuit.tests.length; j++) { 238 | const testCase = testSuit.tests[j] 239 | 240 | test(testCase.name, () => { 241 | let result = customAutoFillFunc( 242 | testCase.data, 243 | testCase.targetCount, 244 | testCase.isNormalDirection, 245 | { altKey: false } as MouseEvent 246 | ) 247 | expect(result).toEqual(testCase.expected) 248 | }) 249 | 250 | } 251 | 252 | }) 253 | 254 | } 255 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/autoFill/types.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | 3 | export type AutoFillTestSuit = { 4 | name: string 5 | tests: AutoFillTestData[] 6 | } 7 | 8 | export type AutoFillTestData = { 9 | name: string 10 | data: string[] 11 | targetCount: number 12 | /** 13 | * true: is down or right, false: is up or left (reverse) 14 | */ 15 | isNormalDirection: boolean 16 | expected: string[] 17 | } 18 | -------------------------------------------------------------------------------- /csvEditorHtml/test/suite/tryToGuessHasHeader/guessHasHeader.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, suite } from 'vitest' 2 | 3 | type HasHeaderTestData = { 4 | name: string, 5 | csv: string, 6 | expected: boolean 7 | } 8 | 9 | type MyTestSuit = { 10 | name: string 11 | tests: HasHeaderTestData[] 12 | } 13 | 14 | 15 | //comment, numbers 16 | 17 | let test_normal: HasHeaderTestData[] = [ 18 | { 19 | name: 'normal data', 20 | csv: ` 21 | UserId,FirstName,ShouldNotCount,PLangName,ParticipationCount,MaxNormalTestPoints,MaxSubmitTestPoints 22 | 513,Alaa,False,Java,1,3,2 23 | 329,Hanna,False,Java,1,3,2 24 | 588,David,False,Java,1,3,2 25 | `, 26 | expected: true, 27 | }, 28 | { 29 | name: 'no header, normal data', 30 | csv: ` 31 | 512,Max,False,Python,1,3,2 32 | 513,Alaa,False,Java,1,3,2 33 | 329,Hanna,False,Java,1,3,2 34 | 588,David,False,Java,1,3,2 35 | `, 36 | expected: false, 37 | }, 38 | { 39 | name: 'threshold many unique header values (X,Y,Z, not part of other values in same column) -> has header', 40 | csv: ` 41 | X,Y,Z,Online,M 42 | Central America and the Caribbean,Belize,Household,Offline,H 43 | Europe,Denmark,Clothes,Online,C 44 | Europe,Germany,Cosmetics,Offline,M 45 | `, 46 | expected: true, 47 | }, 48 | { 49 | name: 'less than threshold many unique header values -> no header', //because Germany,Online,M is repeated 50 | csv: ` 51 | X,Germany,Personal Care,Online,M 52 | Central America and the Caribbean,Belize,Household,Offline,H 53 | Europe,Denmark,Clothes,Online,C 54 | Europe,Germany,Cosmetics,Offline,M 55 | `, 56 | expected: false, 57 | }, 58 | { 59 | name: 'normal data, with comment', 60 | csv: ` 61 | # just a comment 62 | UserId,FirstName,ShouldNotCount,PLangName,ParticipationCount,MaxNormalTestPoints,MaxSubmitTestPoints 63 | 513,Alaa,False,Java,1,3,2 64 | 329,Hanna,False,Java,1,3,2 65 | 588,David,False,Java,1,3,2 66 | `, 67 | expected: true, 68 | }, 69 | { 70 | name: 'no header, normal data', 71 | csv: ` 72 | # just a comment 73 | 512,Max,False,Python,1,3,2 74 | 513,Alaa,False,Java,1,3,2 75 | 329,Hanna,False,Java,1,3,2 76 | 588,David,False,Java,1,3,2 77 | `, 78 | expected: false, 79 | }, 80 | { 81 | name: 'normal data, with comment', 82 | csv: ` 83 | # just a comment 84 | # another comment 85 | UserId,FirstName,ShouldNotCount,PLangName,ParticipationCount,MaxNormalTestPoints,MaxSubmitTestPoints 86 | 513,Alaa,False,Java,1,3,2 87 | 329,Hanna,False,Java,1,3,2 88 | 588,David,False,Java,1,3,2 89 | `, 90 | expected: true, 91 | }, 92 | { 93 | name: 'no header, normal data', 94 | csv: ` 95 | # just a comment 96 | # another comment 97 | 512,Max,False,Python,1,3,2 98 | 513,Alaa,False,Java,1,3,2 99 | 329,Hanna,False,Java,1,3,2 100 | 588,David,False,Java,1,3,2 101 | `, 102 | expected: false, 103 | }, 104 | 105 | // numbers 106 | { 107 | name: 'ints in header, more than threshold many numbers in header -> no header', 108 | csv: ` 109 | UserId,123,123,123 110 | 513,Alaa,False,Java,1,3,2 111 | 329,Hanna,False,Java,1,3,2 112 | 588,David,False,Java,1,3,2 113 | `, 114 | expected: false, 115 | }, 116 | { 117 | name: 'floats in header', 118 | csv: ` 119 | UserId,9.0,9.1,9.2 120 | 513,Alaa,False,Java,1,3,2 121 | 329,Hanna,False,Java,1,3,2 122 | 588,David,False,Java,1,3,2 123 | `, 124 | expected: false, 125 | }, 126 | { 127 | name: 'floats with separators in header', 128 | csv: ` 129 | UserId,"9,000.0","9,876.5","9,000,000.0" 130 | 513,Alaa,False,Java,1,3,2 131 | 329,Hanna,False,Java,1,3,2 132 | 588,David,False,Java,1,3,2 133 | `, 134 | expected: false, 135 | }, 136 | { 137 | name: 'non-en floats in header', 138 | csv: ` 139 | UserId,"9,0","9,1","9,2" 140 | 513,Alaa,False,Java,1,3,2 141 | 329,Hanna,False,Java,1,3,2 142 | 588,David,False,Java,1,3,2 143 | `, 144 | expected: false, 145 | }, 146 | { 147 | name: 'non-enfloats with separators in header', 148 | csv: ` 149 | UserId,"9.000,0","9.876,5","9.000.000,0" 150 | 513,Alaa,False,Java,1,3,2 151 | 329,Hanna,False,Java,1,3,2 152 | 588,David,False,Java,1,3,2 153 | `, 154 | expected: false, 155 | }, 156 | 157 | // mixed string & number in cell --> not a single number -> text 158 | 159 | { 160 | name: 'non-enfloats with separators in header', 161 | csv: ` 162 | UserId,"a 9.000,0","9.876,5","9.000.000,0" 163 | 513,Alaa,False,Java,1,3,2 164 | 329,Hanna,False,Java,1,3,2 165 | 588,David,False,Java,1,3,2 166 | `, 167 | expected: false, 168 | }, 169 | 170 | ] 171 | 172 | 173 | let test_threshold: HasHeaderTestData[] = [ 174 | { 175 | name: 'at least 3 columns look like header', 176 | csv: ` 177 | UserId,Max,Passed,123, 178 | 513,Alaa,False,Java,1,3,2 179 | 329,Hanna,False,Java,1,3,2 180 | 588,David,False,Java,1,3,2 181 | `, 182 | expected: true, 183 | }, 184 | { 185 | name: 'at least 3 columns look like numbers', //numbers are normally not in the header 186 | csv: ` 187 | UserId,Max,999,999,999 188 | 513,Alaa,False,Java,1,3,2 189 | 329,Hanna,False,Java,1,3,2 190 | 588,David,False,Java,1,3,2 191 | `, 192 | expected: false, 193 | }, 194 | { 195 | name: 'threshold number, threshold normal values -> numbers take precedence', //numbers are normally not in the header 196 | csv: ` 197 | UserId,Max,Passed,999,999,999 198 | 513,Alaa,False,Java,1,3,2 199 | 329,Hanna,False,Java,1,3,2 200 | 588,David,False,Java,1,3,2 201 | `, 202 | expected: false, 203 | }, 204 | 205 | { 206 | name: 'header with numbers (we count it as body)', 207 | csv: ` 208 | UserId,123,a 123,b123 209 | 513,Alaa,False,Java,1,3,2 210 | 329,Hanna,False,Java,1,3,2 211 | 588,David,False,Java,1,3,2 212 | `, 213 | expected: true, 214 | }, 215 | { 216 | name: 'header with floats (we count it as body)', 217 | csv: ` 218 | UserId,Test 1, Test 2, Test 3 219 | 513,Alaa,False,Java,1,3,2 220 | 329,Hanna,False,Java,1,3,2 221 | 588,David,False,Java,1,3,2 222 | `, 223 | expected: true, 224 | }, 225 | 226 | // known cell values 227 | { 228 | name: 'true/false are known values', 229 | csv: ` 230 | UserId,false,true,Language,true 231 | 513,Alaa,False,Java,1,3,2 232 | 329,Hanna,False,Java,1,3,2 233 | 588,David,False,Java,1,3,2 234 | `, 235 | expected: false, 236 | }, 237 | { 238 | name: 'true/false are known values but 3 normal columns', 239 | csv: ` 240 | UserId,false,true,Language,true,Count 241 | 513,Alaa,False,Java,1,3,2 242 | 329,Hanna,False,Java,1,3,2 243 | 588,David,False,Java,1,3,2 244 | `, 245 | expected: true, 246 | }, 247 | ] 248 | 249 | let allTests: MyTestSuit[] = [ 250 | { 251 | name: 'test normal', 252 | tests: test_normal 253 | }, 254 | { 255 | name: 'test threshold', 256 | tests: test_threshold 257 | }, 258 | ] 259 | 260 | 261 | for (let i = 0; i < allTests.length; i++) { 262 | const testSuit = allTests[i] 263 | 264 | suite(testSuit.name, () => { 265 | 266 | for (let j = 0; j < testSuit.tests.length; j++) { 267 | const testCase = testSuit.tests[j] 268 | 269 | test(testCase.name, () => { 270 | const parsedCsv = parseCsv(testCase.csv.trim(), defaultCsvReadOptions) 271 | 272 | _normalizeDataArray(parsedCsv, defaultCsvReadOptions) 273 | 274 | window.knownNumberStylesMap = { 275 | "en": { 276 | key: 'en', 277 | /** 278 | * this allows: 279 | * 0(000) 280 | * 0(000).0(000) 281 | * .0(000) 282 | * all repeated with - in front (negative numbers) 283 | * all repeated with e0(000) | e+0(000) | e-0(000) 284 | */ 285 | regex: /-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?/, 286 | regexStartToEnd: /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/, 287 | thousandSeparator: /(\,| )/gm, 288 | thousandSeparatorReplaceRegex: /((\,| )\d{3})+/gm 289 | }, 290 | "non-en": { 291 | key: 'non-en', 292 | /** 293 | * this allows: 294 | * 0(000) 295 | * 0(000),0(000) 296 | * ,0(000) 297 | * all repeated with - in front (negative numbers) 298 | * all repeated with e0(000) | e+0(000) | e-0(000) 299 | */ 300 | regex: /-?(\d+(\,\d*)?|\,\d+)(e[+-]?\d+)?/, 301 | regexStartToEnd: /^-?(\d+(\,\d*)?|\,\d+)(e[+-]?\d+)?$/, 302 | thousandSeparator: /(\.| )/gm, 303 | thousandSeparatorReplaceRegex: /((\.| )\d{3})+/gm 304 | } 305 | } 306 | 307 | let hasHeader = tryToGuessHasHeader( 308 | parsedCsv.data, 309 | defaultCsvReadOptions 310 | ) 311 | expect(hasHeader).toEqual(testCase.expected) 312 | }) 313 | 314 | } 315 | 316 | }) 317 | 318 | } 319 | -------------------------------------------------------------------------------- /csvEditorHtml/test/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | 5 | test: { 6 | browser: { 7 | enabled: true, 8 | headless: true, 9 | screenshotFailures: false, 10 | name: 'chromium', 11 | provider: 'playwright', 12 | testerScripts: [ 13 | { 14 | src: './node_modules/dayjs/dayjs.min.js', 15 | type: 'text/javascript', 16 | }, 17 | { 18 | src: './node_modules/dayjs/plugin/customParseFormat.js', 19 | type: 'text/javascript', 20 | }, 21 | { 22 | src: './thirdParty/big.js/big.min.js', 23 | type: 'text/javascript', 24 | }, 25 | { 26 | src: './thirdParty/toFormat/toFormat.min.js', 27 | type: 'text/javascript', 28 | }, 29 | { 30 | src: './thirdParty/regression/regression.min.js', //our regression uses big.js 31 | type: 'text/javascript', 32 | }, 33 | { 34 | src: './thirdParty/papaparse/papaparse.min.js', 35 | type: 'text/javascript', 36 | }, 37 | { 38 | src: './csvEditorHtml/util.ts', 39 | type: 'text/javascript', 40 | }, 41 | { 42 | src: './csvEditorHtml/io.ts', 43 | type: 'text/javascript', 44 | }, 45 | { 46 | src: './csvEditorHtml/autoFill.ts', 47 | type: 'text/javascript', 48 | }, 49 | //could also be moved to init... 50 | { 51 | content: ` 52 | window.numbersStyleEnRadio = { 53 | checked: true 54 | } 55 | //add toFormat to big numbers 56 | toFormat(Big) 57 | //for custom formatted dates 58 | dayjs.extend(dayjs_plugin_customParseFormat); 59 | 60 | `, 61 | type: 'text/javascript', 62 | }, 63 | { 64 | src: './csvEditorHtml/test/init.ts', 65 | type: 'text/javascript', 66 | }, 67 | 68 | ], 69 | }, 70 | include: ['csvEditorHtml/test/**/*.test.ts'], 71 | }, 72 | }) -------------------------------------------------------------------------------- /csvEditorHtml/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "none", 4 | "target": "es2021", 5 | "moduleResolution": "node", 6 | "outDir": "./out", 7 | "lib": [ 8 | "ES2021", "dom" 9 | ], 10 | "sourceMap": true, 11 | /* Strict Type-Checking Option */ 12 | "strict": true, /* enable all strict type-checking options */ 13 | /* Additional Checks */ 14 | "noUnusedLocals": true, /* Report errors on unused locals. */ 15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 18 | "skipLibCheck": true, 19 | "removeComments": true 20 | }, 21 | "exclude": [ 22 | "../node_modules", 23 | "../src", 24 | "out", 25 | "test" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /docs/autoFillBehavior.md: -------------------------------------------------------------------------------- 1 | # Excel like Auto Fill Behavior 2 | 3 | I've done my best to replicate Excel's auto-fill from observations.... 4 | 5 | If possible, the selected values are grouped in consecutive sequences. 6 | These groups are then used to determine the interpolations. 7 | 8 | e.g. `2,4,a` will create the groups `2,4` and `a`. 9 | Then `2,4` is used to interpolate the next values (`6,8,10,...`), 10 | while `a` ist just copies over and over 11 | 12 | e.g. `2,4,a,01.01.2024,03.01.2024` will create the groups `2,4`, `a` and `01.01.2024, 03.01.2024` 13 | 14 | Again, `2,4` is used to interpolate the next numbers, 15 | `a` is just copied 16 | `01.01.2024,03.01.2024` is used to interpolate the next dates `05.01.2024, 07.01.2024, 09.01.2024, ...` 17 | 18 | 19 | The formation of groups enables interpolation, even if heterogeneous data has been selected. 20 | 21 | However, consecutive sequences must have the same *delta* to be a correct sequence. 22 | 23 | For numbers, linear regression can always form a line, but no *model* is used for month names, numbers with text and dates. 24 | 25 | 26 | This means that if the distance between 3 month names in the sequences is different, the autofill will fall back to simply copying the sequence over and over again. 27 | Example: `jan,feb,apr` with the distances/deltas of `1,2`. If the deltas are different, it is not clear what the next value should be. 28 | 29 | Example: `jan,feb,mar` with the distances/deltas of `1,1`. All deltas are the same, so we can continue with this delta and always add `1` month. 30 | 31 | This also applies to dates when the spacing is used in days. 32 | 33 | Below is a flowchart explaining the individual steps in a bit more detail. 34 | 35 | #### Numbers 36 | 37 | The cell text is a number (no other content). 38 | 39 | 40 | The language settings are important for numbers (floats), e.g. 3.45 or 3.45 41 | 42 | For the automatic filling of numbers, the setting `numbers style` is taken from the table UI. You can find it when you open the statistics panel on the left-hand side (by clicking on the arrow next to `add row`). 43 | 44 | If you mainly work with a number style, you can change the default via the extension setting `initialNumbersStyle`. 45 | 46 | Automatic filling numbers uses [linear regression](https://en.wikipedia.org/wiki/Linear_regression) to determine the values to be filled in (Excel also uses linear regression). 47 | 48 | Two decimal places are used for Int/Float interpolation. 49 | 50 | ### Contains Number 51 | 52 | This differs from normal numbers, as here the cell text must begin or end with a number (or both). 53 | 54 | In this case, the interpolation is only a constant delta. 55 | The delta is determined by the first two cell values in the selection. 56 | 57 | e.g. `2. test, 4. test` will calculate a delta of `2`, so the next value will be `6. test` 58 | 59 | If there are different deltas in the selected cells, the auto fill function will default to copy only. 60 | 61 | e.g. `2. test, 4. test, 5. test` will calculate a delta of `2`, so the next values will be `2. test, 4. test, 5. test, 2. test, ...` 62 | 63 | If there is a number at the beginning and at the end of the cell, the number at the beginning takes precedence. 64 | 65 | 66 | #### Dates 67 | 68 | The following formats are supported/known for dates: 69 | 70 | - `YYYY-MM-DD` 71 | - `YYYY-M-DD` 72 | - `YYYY-MM-D` 73 | - `YYYY-M-D` 74 | - `DD-MM-YYYY` 75 | - `DD-M-YYYY` 76 | - `D-MM-YYYY` 77 | - `D-M-YYYY` 78 | - `DD-MM-YY` 79 | - `DD-M-YY` 80 | - `D-MM-YY` 81 | - `D-M-YY` 82 | 83 | where `YY/YYYY` stands for the year, e.g. 24/2024, `M/MM` stands for the month, e.g. 5/05 and `D/DD` stands for the day, e.g. 5/05 84 | 85 | The separator `-` can actually be one of the following: `- / .` 86 | 87 | **Yes**, there is no `MM-DD-YYYY` format! 88 | 89 | Only the first two selected data are used for the interpolation (to determine the delta). 90 | 91 | 92 | Then the diff in `days`, `months` and `years` is calculated, ignoring the other parts of the date. 93 | e.g. the diff/delta in days for `25.05.2024` - `26.07.2024` is still only 1 `day` 94 | 95 | If the diff in `days` is 0 and diff in `months` is 0, use the diff in years for interpolation. 96 | This ensures days and months will stay the same: `25.05.2024, 25.05.2026` -> `25.05.2028` 97 | 98 | If only the diff in `days` is 0, use the diff in month for interpolation. 99 | e.g. `25.05.2024, 25.07.2026` -> `25.09.2028` (delta in months: 26) 100 | 101 | In any other case, use the difference in `days`, but this time consider all parts of the date as delta for the interpolation. 102 | e.g. `01.01.2024, 02.02.2024` gives a delta of 32 `days` 103 | and the next date will be `05.03.2024` 104 | 105 | If there are more than 2 pieces of data in the group sequence, they must have the same difference/delta. 106 | If this is not the case, the data is copied again and again as a sequence by default. 107 | 108 | If the interpolation would result in an invalid date such as `30.02.20`, `dayjs` will convert the date into a valid date (here `28.02.20`). 109 | 110 | #### Month Names 111 | 112 | Only English names are supported for month names: `january|february|march|april|may|june|july|august|september|october|november|december` 113 | The first 3 letters can also be used as month names: `jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec` 114 | 115 | In order for the month names to be filled in automatically, the month name must be the only text in the cell. 116 | 117 | 118 | #### Shortcut for just coyping 119 | 120 | Similar to Excel, you can hold down the `alt` key before releasing the mouse button to copy the cell values. No interpolation is carried out. 121 | 122 | #### Differences to Excel 123 | 124 | If only 1 cell is selected, interpolation increases 125 | 126 | - `+1/-1` for numbers 127 | - next/previous month name 128 | - `+1/-1` day for dates 129 | 130 | where Excel simply copies the value 131 | 132 | There could be other differences... 133 | 134 | 135 | #### Auto Fill Flowchart 136 | 137 | If it's too small, open it from `docs/autoFillDiagram.jpg` 138 | 139 | ![alt text](docs/autoFillDiagram.jpg) -------------------------------------------------------------------------------- /docs/autoFillDiagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/docs/autoFillDiagram.jpg -------------------------------------------------------------------------------- /docs/quotes.md: -------------------------------------------------------------------------------- 1 | # Some notes about quotes and related settings 2 | 3 | ### Papaparse options 4 | 5 | `quoteChar` is used to quote fields. A field has to be quoted if it contains the delimiter, `quoteChar`, `\n`, `\r` a trailing or leading space. 6 | 7 | `escapeChar` is used to escape fields. A field has to be escaped if it contains the `quoteChar`. It is escaped by prepended the `escapeChar` `quoteChar`. 8 | 9 | Example: 10 | `escapeChar: +`, `quoteChar:"` 11 | Input: `a"b` 12 | 13 | field contains the quote char -> has to be quoted --> `"a"b"` 14 | the inner quotes has to be escaped -> `"a+"b"` 15 | 16 | 17 | ### Leading or trainling spaces and quotes 18 | 19 | If we have 20 | ``` 21 | a,b,c 22 | ``` 23 | 24 | we expect the result to be `a`,`b`,`c` 25 | 26 | If we have 27 | 28 | ``` 29 | a, b, c 30 | ``` 31 | 32 | we expect the result to be `a`, ` b`, ` c` 33 | (b,c have a leading space) 34 | 35 | If we have 36 | 37 | ``` 38 | a, b," c" 39 | ``` 40 | 41 | we expect the same result. The quotes are option but make the space more "visible". 42 | 43 | However, if we have 44 | 45 | ``` 46 | a, b, "c" 47 | ``` 48 | 49 | It is not clear what to expect here... 50 | 51 | - the quotes indicate the intend to not have a leading space 52 | - the space between the `,` and the `"` indicate a space 53 | 54 | Papaparse chooses to include the leading space, resulting in `a`, ` b`, ` "c"` 55 | Probably because the csv rfc states: 56 | >spaces are considered part of a field and should not be ignored. 57 | 58 | This also means that the `"` in this field are **not** used to quote the field! (you could imageine swapping the `"` with any other character without special meaning like `@` -> `a`, ` b`, ` @c@`). 59 | 60 | The implication is that text like 61 | 62 | ``` 63 | a, b, "c," 64 | ``` 65 | 66 | will not work because the delimiter inside the `"` is **not** escaped by the quotes. 67 | 68 | As there is no *correct* decision here, we are not changing the current behaviour in this case. 69 | -------------------------------------------------------------------------------- /docs/visualAndPhysicalIndices.md: -------------------------------------------------------------------------------- 1 | # Visual and Physical Indices 2 | 3 | 4 | 5 | ### Current Mapping 6 | 7 | To get the current mapping, run 8 | 9 | ```js 10 | # rows 11 | Array(hot.countRows()).fill(0).map((p,i) => hot.toPhysicalRow(i)) 12 | # columns 13 | Array(hot.countCols()).fill(0).map((p,i) => hot.toPhysicalColumn(i)) 14 | ``` 15 | 16 | ### Inserting a row/column befor/after uses visual indices 17 | 18 | When you insert a row/col they are inserted at the visual (displayed) location. 19 | If you sort the data, insert a row and then revert the sorting, the new row/col will stay at the inserted position. 20 | 21 | Here is why: 22 | 23 | NOTE: the physical and visual index handling is messed up in handsontable... 24 | 25 | e.g. in core.js > alter(action, index, ...) the index is expected to be a visual index 26 | for 'insert_row' it calls 'datamap.createRow(index, ...)' (the index is not changed) 27 | the docs of DataMap.prototype.createRow = function(index, ...) says it's expecting a physical index! 28 | this is true because spliceData(index, ...) is called, which modified the source data directly at the given index (so it must be physical!) 29 | after that the afterCreateRow hooks are run which updates the visual <-> physical index mapping (the index is still not changed) 30 | the mapping is stored in the manualRowMove.js plugin, the hook 'onAfterCreateRow' is triggered with the index and 'this.rowsMapper.shiftItems(index, amount)' is called 31 | shiftItems shifts all indices greater than index (and increases them) and then inserts the index itself, so array[index] = index 32 | this means that the mapping for the new row is inserted at the correct position (when we look up a physical index, we would execute array[visualIndex] where the new index is stored) 33 | however, array[index] = index, so the physical index of the new row is the visual index 34 | it would be more intuitive if we would insert the new physical index but this is not possible by the shiftItems method because the entry for array[index] is always the index itself 35 | to reproduce this, create a table with 1,2,3,4,5, and some data between, sort it, and then insert a row before row 3 36 | the row will be displayed at the correct position but the physical index 'wrong' because if you revert the sort, the row will not be before row 3 but at the visual index it was inserted 37 | THIS IS THE CURRENT behavior of handsontable, even in version 12.x 38 | fixing this is not easy, was we would have to pass the physical index to the alter method (in oder to correctly 'spliceData') but all other hooks expect the visual index!! 39 | also, when we use 'insert_row' we don't know the real physical index because we don't know if the row should be above or below the given row and we would not know the correct visual index 40 | handsontable removed this method in favor of 'insert_row_below' and 'insert_row_above', this way we could compute the correct visual index (by toVisualIndex(index) and then +/-1) 41 | BUT FOR NOW we keep the current bahavior of handsontable -------------------------------------------------------------------------------- /exampleCSV/autoFill.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/exampleCSV/autoFill.xlsx -------------------------------------------------------------------------------- /images/logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/images/logo.afdesign -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/images/logo.png -------------------------------------------------------------------------------- /images/logo_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/images/logo_old.png -------------------------------------------------------------------------------- /images/titleImg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janisdd/vscode-edit-csv/7f06cf1c718cf27dfb287ee9d2176d5381487fa4/images/titleImg.gif -------------------------------------------------------------------------------- /out/configurationHelper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.overwriteConfiguration = exports.getExtensionConfiguration = void 0; 4 | const vscode = require("vscode"); 5 | const extension_1 = require("./extension"); 6 | const util_1 = require("./util"); 7 | const defaultConfig = { 8 | highlightCsvComments: true, 9 | lastRowEnterBehavior: 'default', 10 | lastColumnTabBehavior: 'default', 11 | lastRowOrFirstRowNavigationBehavior: 'wrap', 12 | lastColumnOrFirstColumnNavigationBehavior: 'wrap', 13 | optionsBarAppearance: "collapsed", 14 | tryToGuessHasHeader: false, 15 | readOption_comment: "#", 16 | readOption_quoteChar: '"', 17 | readOption_escapeChar: '"', 18 | readOption_delimiter: "", 19 | readOption_delimitersToGuess: [",", "\t", "|", ";", "\u001e", "\u001f"], 20 | readOption_hasHeader: "false", 21 | writeOption_comment: "#", 22 | writeOption_delimiter: "", 23 | writeOption_quoteChar: '"', 24 | writeOption_escapeChar: '"', 25 | writeOption_hasHeader: "false", 26 | doubleClickColumnHandleForcedWith: 200, 27 | doubleClickRowHandleForcedHeight: 106, 28 | openSourceFileAfterApply: false, 29 | selectTextAfterBeginEditCell: false, 30 | quoteAllFields: false, 31 | quoteEmptyOrNullFields: 'false', 32 | initiallyHideComments: false, 33 | enableWrapping: true, 34 | initialColumnWidth: 0, 35 | autoColumnWidthsIgnoreComments: true, 36 | retainQuoteInformation: 'full', 37 | forceQuoteLeadingWhitespace: false, 38 | forceQuoteTrailingWhitespace: false, 39 | newColumnQuoteInformationIsQuoted: false, 40 | disableBorders: false, 41 | initiallyFixedRowsTop: 0, 42 | initiallyFixedColumnsLeft: 0, 43 | fontSizeInPx: 16, 44 | showColumnHeaderNamesWithLettersLikeExcel: false, 45 | shouldWatchCsvSourceFile: 'yesAndNotify', 46 | sidePanelAppearance: 'collapsed', 47 | initialNumbersStyle: 'en', 48 | insertRowBehavior: 'keepRowKeepColumn', 49 | insertColBehavior: 'keepRowKeepColumn', 50 | initiallyIsInReadonlyMode: false, 51 | hideOpenCsvEditorUiActions: false, 52 | openTableAndSelectCellAtCursorPos: "initialOnly_correctRowAlwaysFirstColumn", 53 | pasteMode: 'normal', 54 | pasteBehavior: 'overwrite', 55 | pasteScrollBehavior: 'scrollToLastPastedCell', 56 | fontFamilyInTable: 'default', 57 | showDeleteColumnHeaderButton: true, 58 | showDeleteRowHeaderButton: true, 59 | finalNewLine: 'sameAsSourceFile', 60 | darkThemeTextColor: '#d0d0d0', 61 | lightThemeTextColor: '#657b83', 62 | convertUrlsToLinkTags: true, 63 | dragToAutoFill: "excelLike", 64 | initiallyHiddenColumnNames: [], 65 | initiallyHiddenColumnNumbers: [], 66 | useSaveButtonsAsAdditionalUnsavedChangesIndicator: true, 67 | copyColumnHeaderNamesSeparator: `, `, 68 | }; 69 | /** 70 | * returns the configuration for this extension 71 | */ 72 | function getExtensionConfiguration() { 73 | const configObj = vscode.workspace.getConfiguration(extension_1.editorUriScheme); 74 | const copy = Object.assign({}, defaultConfig); 75 | for (const key in defaultConfig) { 76 | const optionValue = configObj.get(key); 77 | if (optionValue === undefined) { 78 | vscode.window.showWarningMessage(`Could not find option: ${key} in csv-edit configuration`); 79 | continue; 80 | } 81 | //@ts-ignore 82 | copy[key] = optionValue; 83 | } 84 | //ensure single character requirements 85 | copy.readOption_quoteChar = (0, util_1.limitSingleCharacterString)(copy.readOption_quoteChar); 86 | copy.readOption_escapeChar = (0, util_1.limitSingleCharacterString)(copy.readOption_escapeChar); 87 | copy.writeOption_quoteChar = (0, util_1.limitSingleCharacterString)(copy.writeOption_quoteChar); 88 | copy.writeOption_escapeChar = (0, util_1.limitSingleCharacterString)(copy.writeOption_escapeChar); 89 | console.log(`[edit csv] settings`, copy); 90 | return copy; 91 | } 92 | exports.getExtensionConfiguration = getExtensionConfiguration; 93 | function overwriteConfiguration(currentConfig, overwriteConfigObj) { 94 | for (const key in overwriteConfigObj) { 95 | if (!currentConfig.hasOwnProperty(key)) { 96 | vscode.window.showWarningMessage(`unknown setting '${key}', skipping this setting`); 97 | continue; 98 | } 99 | //@ts-ignore 100 | currentConfig[key] = overwriteConfigObj[key]; 101 | console.log(`[edit csv] overwrote config key: '${key}' with value: `, overwriteConfigObj[key]); 102 | } 103 | console.log(`[edit csv] resulting settings`, currentConfig); 104 | } 105 | exports.overwriteConfiguration = overwriteConfiguration; 106 | //# sourceMappingURL=configurationHelper.js.map -------------------------------------------------------------------------------- /out/instanceManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.InstanceManager = void 0; 4 | /** 5 | * we need keep track of all editor instances 6 | * so we can ensure that e.g. we open only one editor per csv file, 7 | * find the source file for an editor... 8 | */ 9 | class InstanceManager { 10 | constructor() { 11 | this.instances = {}; 12 | } 13 | getAllInstances() { 14 | const keys = Object.keys(this.instances); 15 | const allInstances = keys.map(p => this.instances[p]); 16 | return allInstances; 17 | } 18 | addInstance(instance) { 19 | const oldInstance = this.instances[instance.sourceUri.toString()]; 20 | if (oldInstance) { 21 | throw new Error('tried to add a new instance but we got old one (with the source uri)'); 22 | } 23 | this.instances[instance.sourceUri.toString()] = instance; 24 | } 25 | removeInstance(instance) { 26 | const oldInstance = this.instances[instance.sourceUri.toString()]; 27 | if (!oldInstance) { 28 | throw new Error('could not find old instance'); 29 | } 30 | delete this.instances[instance.sourceUri.toString()]; 31 | } 32 | findInstanceBySourceUri(sourceUri) { 33 | //key is the source uri 34 | const instance = this.instances[sourceUri.toString()]; 35 | // const instance = this.getAllInstances().find(p => p.sourceUri.toString() == sourceUri.toString()) 36 | if (!instance) 37 | return null; 38 | return instance; 39 | } 40 | // public findInstanceByEditorUri(editorUri: vscode.Uri): SomeInstance | null { 41 | // const instance = this.getAllInstances().find(p => p.editorUri === editorUri) 42 | // if (!instance) return null 43 | // return instance 44 | // } 45 | hasActiveEditorInstance() { 46 | const activeInstances = this.getAllInstances().filter(p => p.panel.active); 47 | return activeInstances.length > 0; // or === 1 ? 48 | } 49 | getActiveEditorInstance() { 50 | const activeInstances = this.getAllInstances().filter(p => p.panel.active); 51 | if (activeInstances.length === 0) { 52 | throw new Error('no active editor found'); 53 | } 54 | if (activeInstances.length > 1) { 55 | throw new Error('too many active editors found'); 56 | } 57 | return activeInstances[0]; 58 | } 59 | } 60 | exports.InstanceManager = InstanceManager; 61 | //# sourceMappingURL=instanceManager.js.map -------------------------------------------------------------------------------- /out/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.partitionString = exports.isCsvFile = exports.debounce = exports.limitSingleCharacterString = exports.getCurrentViewColumn = exports.debugLog = void 0; 4 | const vscode = require("vscode"); 5 | function debugLog(msg) { 6 | // console.log(msg) 7 | } 8 | exports.debugLog = debugLog; 9 | /** 10 | * gets the current view column (e.g. we could have split view) 11 | */ 12 | function getCurrentViewColumn() { 13 | return vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn 14 | ? vscode.window.activeTextEditor.viewColumn 15 | : vscode.ViewColumn.One; 16 | } 17 | exports.getCurrentViewColumn = getCurrentViewColumn; 18 | function limitSingleCharacterString(value) { 19 | if (value.length > 1) { 20 | //using last char is more user friendly as we can click and press a key to use the new char 21 | value = value.substring(value.length - 1); 22 | } 23 | return value; 24 | } 25 | exports.limitSingleCharacterString = limitSingleCharacterString; 26 | //from https://davidwalsh.name/javascript-debounce-function 27 | function debounce(func, wait, immediate = false) { 28 | var timeout; 29 | return function () { 30 | var context = this, args = arguments; 31 | var later = function () { 32 | timeout = null; 33 | if (!immediate) 34 | func.apply(context, args); 35 | }; 36 | var callNow = immediate && !timeout; 37 | clearTimeout(timeout); 38 | timeout = setTimeout(later, wait); 39 | if (callNow) 40 | func.apply(context, args); 41 | }; 42 | } 43 | exports.debounce = debounce; 44 | //inspired from https://github.com/jjuback/gc-excelviewer/blob/master/src/extension.ts 45 | function isCsvFile(document) { 46 | if (!document) 47 | return false; 48 | let lang = document.languageId.toLowerCase(); 49 | let possible = ['csv', 'tsv', 'plaintext', 50 | //rainbow csv extension types, see https://github.com/mechatroner/vscode_rainbow_csv 51 | 'csv (semicolon)', 'csv (pipe)', 'csv (whitespace)', 'csv (tilde)', 'csv (caret)', 'csv (colon)', 'csv (double quote)', 'csv (equals)', 'csv (dot)', 'csv (hyphen)', 'dynamic csv' 52 | ]; 53 | const _isCsvFile = possible.find(p => p === lang) && document.uri.scheme !== 'csv-edit'; 54 | return _isCsvFile; 55 | } 56 | exports.isCsvFile = isCsvFile; 57 | function partitionString(text, sliceLength) { 58 | const slices = []; 59 | const totalSlices = Math.ceil(text.length / sliceLength); 60 | for (let i = 0; i < totalSlices; i++) { 61 | const _part = text.substr(i * sliceLength, sliceLength); 62 | slices.push({ 63 | text: _part, 64 | sliceNr: i + 1, 65 | totalSlices 66 | }); 67 | } 68 | return slices; 69 | } 70 | exports.partitionString = partitionString; 71 | //# sourceMappingURL=util.js.map -------------------------------------------------------------------------------- /promtUploadbuild.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline') 2 | 3 | 4 | //see https://stackoverflow.com/questions/18193953/waiting-for-user-to-enter-input-in-node-js 5 | 6 | /** 7 | * 8 | * @param {string} query 9 | * @returns {Promise} 10 | */ 11 | function askQuestion(query) { 12 | const rl = readline.createInterface({ 13 | input: process.stdin, 14 | output: process.stdout, 15 | }) 16 | 17 | return new Promise(resolve => rl.question(query, ans => { 18 | rl.close(); 19 | resolve(ans); 20 | })) 21 | } 22 | 23 | //see https://stackoverflow.com/questions/46515764/how-can-i-use-async-await-at-the-top-level 24 | 25 | /** 26 | * @returns {Promise} exit code 27 | */ 28 | async function main() { 29 | const answer = await askQuestion('Did you upload the .vsix file? (y/n)') 30 | return answer === 'y' ? 0 : 1 31 | } 32 | 33 | // main().then(p => { 34 | // console.log(p); 35 | // }) 36 | 37 | // const exitCode = await main() 38 | // process.exit() 39 | (async () => { 40 | try { 41 | const exitCode = await main() 42 | process.exit(exitCode) 43 | } catch (e) { 44 | } 45 | })(); 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/configurationHelper.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { editorUriScheme } from './extension'; 3 | import { limitSingleCharacterString } from "./util"; 4 | 5 | 6 | 7 | const defaultConfig: EditCsvConfig = { 8 | highlightCsvComments: true, 9 | lastRowEnterBehavior: 'default', 10 | lastColumnTabBehavior: 'default', 11 | lastRowOrFirstRowNavigationBehavior: 'wrap', 12 | lastColumnOrFirstColumnNavigationBehavior: 'wrap', 13 | optionsBarAppearance: "collapsed", 14 | tryToGuessHasHeader: false, 15 | readOption_comment: "#", 16 | readOption_quoteChar: '"', 17 | readOption_escapeChar: '"', 18 | readOption_delimiter: "", 19 | readOption_delimitersToGuess: [",", "\t", "|", ";", "\u001e", "\u001f"], 20 | readOption_hasHeader: "false", 21 | writeOption_comment: "#", 22 | writeOption_delimiter: "", 23 | writeOption_quoteChar: '"', 24 | writeOption_escapeChar: '"', 25 | writeOption_hasHeader: "false", 26 | doubleClickColumnHandleForcedWith: 200, 27 | doubleClickRowHandleForcedHeight: 106, //this gives us 5 rows with the default font size 28 | openSourceFileAfterApply: false, 29 | selectTextAfterBeginEditCell: false, 30 | quoteAllFields: false, 31 | quoteEmptyOrNullFields: 'false', 32 | initiallyHideComments: false, 33 | enableWrapping: true, 34 | initialColumnWidth: 0, 35 | autoColumnWidthsIgnoreComments: true, 36 | retainQuoteInformation: 'full', 37 | forceQuoteLeadingWhitespace: false, 38 | forceQuoteTrailingWhitespace: false, 39 | newColumnQuoteInformationIsQuoted: false, 40 | disableBorders: false, 41 | initiallyFixedRowsTop: 0, 42 | initiallyFixedColumnsLeft: 0, 43 | fontSizeInPx: 16, 44 | showColumnHeaderNamesWithLettersLikeExcel: false, 45 | shouldWatchCsvSourceFile: 'yesAndNotify', 46 | sidePanelAppearance: 'collapsed', 47 | initialNumbersStyle: 'en', 48 | insertRowBehavior: 'keepRowKeepColumn', 49 | insertColBehavior: 'keepRowKeepColumn', 50 | initiallyIsInReadonlyMode: false, 51 | hideOpenCsvEditorUiActions: false, //noop, has only effect if set inside the user settings 52 | openTableAndSelectCellAtCursorPos: "initialOnly_correctRowAlwaysFirstColumn", 53 | pasteMode: 'normal', 54 | pasteBehavior: 'overwrite', 55 | pasteScrollBehavior: 'scrollToLastPastedCell', 56 | fontFamilyInTable: 'default', 57 | showDeleteColumnHeaderButton: true, 58 | showDeleteRowHeaderButton: true, 59 | finalNewLine: 'sameAsSourceFile', 60 | darkThemeTextColor: '#d0d0d0', 61 | lightThemeTextColor: '#657b83', 62 | convertUrlsToLinkTags: true, 63 | dragToAutoFill: "excelLike", 64 | initiallyHiddenColumnNames: [], 65 | initiallyHiddenColumnNumbers: [], 66 | useSaveButtonsAsAdditionalUnsavedChangesIndicator: true, 67 | copyColumnHeaderNamesSeparator: `, `, 68 | } 69 | 70 | /** 71 | * returns the configuration for this extension 72 | */ 73 | export function getExtensionConfiguration(): EditCsvConfig { 74 | const configObj = vscode.workspace.getConfiguration(editorUriScheme) 75 | 76 | const copy: EditCsvConfig = { 77 | ...defaultConfig 78 | } 79 | 80 | for (const key in defaultConfig) { 81 | const optionValue = configObj.get(key) 82 | 83 | if (optionValue === undefined) { 84 | vscode.window.showWarningMessage(`Could not find option: ${key} in csv-edit configuration`) 85 | continue 86 | } 87 | 88 | //@ts-ignore 89 | copy[key] = optionValue 90 | } 91 | 92 | //ensure single character requirements 93 | copy.readOption_quoteChar = limitSingleCharacterString(copy.readOption_quoteChar) 94 | copy.readOption_escapeChar = limitSingleCharacterString(copy.readOption_escapeChar) 95 | copy.writeOption_quoteChar = limitSingleCharacterString(copy.writeOption_quoteChar) 96 | copy.writeOption_escapeChar = limitSingleCharacterString(copy.writeOption_escapeChar) 97 | 98 | console.log(`[edit csv] settings`, copy) 99 | 100 | return copy 101 | } 102 | 103 | export function overwriteConfiguration(currentConfig: EditCsvConfig, overwriteConfigObj: EditCsvConfigOverwrite): void{ 104 | 105 | for (const key in overwriteConfigObj) { 106 | 107 | if (!currentConfig.hasOwnProperty(key)) { 108 | vscode.window.showWarningMessage(`unknown setting '${key}', skipping this setting`) 109 | continue 110 | } 111 | 112 | //@ts-ignore 113 | currentConfig[key] = overwriteConfigObj[key] as any 114 | 115 | console.log(`[edit csv] overwrote config key: '${key}' with value: `, (overwriteConfigObj as any)[key]) 116 | } 117 | 118 | console.log(`[edit csv] resulting settings`, currentConfig) 119 | } -------------------------------------------------------------------------------- /src/instanceManager.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export interface Instance { 4 | /** 5 | * the panel with the editor 6 | */ 7 | panel: vscode.WebviewPanel 8 | /** 9 | * the uri of the source file 10 | * this is the same as in {@link document} 11 | */ 12 | sourceUri: vscode.Uri 13 | /** 14 | * the uri of the editor webview 15 | */ 16 | editorUri: vscode.Uri 17 | 18 | /** 19 | * the source file reference 20 | * might be closed 21 | * or out of sync (for non-workspace files) 22 | */ 23 | document: vscode.TextDocument 24 | 25 | /** 26 | * the last known content of the file (set by this extension on save) 27 | * 28 | * we need this to check if the file content really changed 29 | * sometimes fake change events are triggered and then we compare the file contents 30 | */ 31 | lastCommittedContent: string 32 | 33 | /** 34 | * true: edit has unsaved changes, false: not 35 | */ 36 | hasChanges: boolean 37 | 38 | /** 39 | * the original title for the tab 40 | */ 41 | originalTitle: string 42 | 43 | /** 44 | * when the table saves the file we need to ignore the next change else the disk change will trigger the table to reload (losing undo, ...) 45 | */ 46 | ignoreChangeEvents: boolean 47 | 48 | /** 49 | * used to watch the source file and notify the extension view 50 | */ 51 | sourceFileWatcher: vscode.FileSystemWatcher | null 52 | 53 | unsubscribeWatcher: vscode.Disposable | null 54 | } 55 | 56 | export interface InstanceWorkspaceSourceFile extends Instance { 57 | kind: 'workspaceFile' 58 | } 59 | 60 | export interface InstanceExternalFile extends Instance { 61 | kind: 'externalFile' 62 | } 63 | 64 | export type SomeInstance = InstanceWorkspaceSourceFile | InstanceExternalFile 65 | 66 | export interface InstanceStorage { 67 | /** 68 | * the key is the source uri to string 69 | */ 70 | [sourceUriString: string]: SomeInstance 71 | } 72 | 73 | /** 74 | * we need keep track of all editor instances 75 | * so we can ensure that e.g. we open only one editor per csv file, 76 | * find the source file for an editor... 77 | */ 78 | export class InstanceManager { 79 | 80 | private instances: InstanceStorage = {} 81 | 82 | 83 | public getAllInstances(): SomeInstance[] { 84 | const keys = Object.keys(this.instances) 85 | const allInstances = keys.map(p => this.instances[p]) 86 | return allInstances 87 | } 88 | 89 | public addInstance(instance: SomeInstance) { 90 | 91 | const oldInstance = this.instances[instance.sourceUri.toString()] 92 | 93 | if (oldInstance) { 94 | throw new Error('tried to add a new instance but we got old one (with the source uri)') 95 | } 96 | 97 | this.instances[instance.sourceUri.toString()] = instance 98 | } 99 | 100 | public removeInstance(instance: SomeInstance) { 101 | const oldInstance = this.instances[instance.sourceUri.toString()] 102 | 103 | if (!oldInstance) { 104 | throw new Error('could not find old instance') 105 | } 106 | 107 | delete this.instances[instance.sourceUri.toString()] 108 | } 109 | 110 | public findInstanceBySourceUri(sourceUri: vscode.Uri): SomeInstance | null { 111 | 112 | //key is the source uri 113 | const instance = this.instances[sourceUri.toString()] 114 | // const instance = this.getAllInstances().find(p => p.sourceUri.toString() == sourceUri.toString()) 115 | 116 | if (!instance) return null 117 | 118 | return instance 119 | } 120 | 121 | // public findInstanceByEditorUri(editorUri: vscode.Uri): SomeInstance | null { 122 | 123 | // const instance = this.getAllInstances().find(p => p.editorUri === editorUri) 124 | 125 | // if (!instance) return null 126 | 127 | // return instance 128 | // } 129 | 130 | public hasActiveEditorInstance(): boolean { 131 | const activeInstances = this.getAllInstances().filter(p => p.panel.active) 132 | return activeInstances.length > 0 // or === 1 ? 133 | } 134 | 135 | public getActiveEditorInstance(): SomeInstance { 136 | const activeInstances = this.getAllInstances().filter(p => p.panel.active) 137 | 138 | if (activeInstances.length === 0) { 139 | throw new Error('no active editor found') 140 | } 141 | 142 | if (activeInstances.length > 1) { 143 | throw new Error('too many active editors found') 144 | } 145 | 146 | return activeInstances[0] 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | // import * as vscode from 'vscode' 9 | import { partitionString } from '../../util'; 10 | 11 | 12 | // Defines a Mocha unit test 13 | // test("Something 1", function() { 14 | // assert.equal(-1, [1, 2, 3].indexOf(5)); 15 | // assert.equal(-1, [1, 2, 3].indexOf(0)); 16 | // }); 17 | 18 | //see https://vscode.rocks/testing/ 19 | // const newFile = vscode.Uri.parse('untitled:Untitled-2') 20 | // const document = await vscode.workspace.openTextDocument(newFile) 21 | // const textEditor = await vscode.window.showTextDocument(document) 22 | 23 | // await vscode.commands.executeCommand('edit-csv.edit') 24 | 25 | // // await sleep(1000) 26 | // assert.equal(textEditor, vscode.window.activeTextEditor) 27 | 28 | // function sleep(ms: number): Promise { 29 | // return new Promise(resolve => { 30 | // setTimeout(resolve, ms) 31 | // }) 32 | // } 33 | 34 | //to run tests start tsc -w and then go to the debug tab and select "EXtension Tests" and run 35 | 36 | // Defines a Mocha test suite to group tests of similar kind together 37 | suite("partitionString working properly", function () { 38 | 39 | test('partition size not fitting', async function () { 40 | 41 | const text = '0123456789' 42 | 43 | const parts = partitionString(text, 3) 44 | 45 | const margedText = parts.map(p => p.text).join('') 46 | 47 | assert.equal(margedText, text) 48 | }) 49 | 50 | test('partition size larger than text', async function () { 51 | 52 | const text = '0123456789' 53 | 54 | const parts = partitionString(text, text.length + 10) 55 | 56 | const margedText = parts.map(p => p.text).join('') 57 | 58 | assert.equal(margedText, text) 59 | }) 60 | 61 | test('partition size perfect fit', async function () { 62 | 63 | const text = '0123456789' 64 | 65 | const parts = partitionString(text, text.length) 66 | 67 | const margedText = parts.map(p => p.text).join('') 68 | 69 | assert.equal(margedText, text) 70 | }) 71 | 72 | 73 | }) 74 | 75 | 76 | suite('some frontend func tests', function () { 77 | 78 | test('excel like column names (letters) func is correct (like handsontable)', async function () { 79 | 80 | const COLUMN_LABEL_BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 81 | const COLUMN_LABEL_BASE_LENGTH = COLUMN_LABEL_BASE.length 82 | 83 | function spreadsheetColumnLetterLabel(index: number): string { 84 | let num = index 85 | let columnLabel = '' 86 | //see https://stackoverflow.com/questions/34813980/getting-an-array-of-column-names-at-sheetjs 87 | while (num >= 0) { 88 | columnLabel = COLUMN_LABEL_BASE[num % 26] + columnLabel 89 | num = Math.floor(num / 26) - 1 90 | } 91 | return columnLabel 92 | } 93 | 94 | function spreadsheetColumnLabel(index: number): string { 95 | let dividend = index + 1 96 | let columnLabel = '' 97 | let modulo 98 | 99 | while (dividend > 0) { 100 | modulo = (dividend - 1) % COLUMN_LABEL_BASE_LENGTH; 101 | columnLabel = String.fromCharCode(65 + modulo) + columnLabel; 102 | dividend = parseInt((dividend - modulo) / COLUMN_LABEL_BASE_LENGTH as any, 10); 103 | } 104 | 105 | return columnLabel; 106 | } 107 | 108 | 109 | for (let i = 0; i < 1_000_000; i++) { 110 | let correct = spreadsheetColumnLabel(i) 111 | let test = spreadsheetColumnLetterLabel(i) 112 | 113 | assert.equal(test, correct) 114 | } 115 | 116 | }) 117 | 118 | }) 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export function debugLog(msg: any) { 4 | // console.log(msg) 5 | } 6 | 7 | /** 8 | * gets the current view column (e.g. we could have split view) 9 | */ 10 | export function getCurrentViewColumn(): vscode.ViewColumn { 11 | return vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn 12 | ? vscode.window.activeTextEditor.viewColumn 13 | : vscode.ViewColumn.One 14 | } 15 | 16 | export function limitSingleCharacterString(value: string): string { 17 | 18 | if (value.length > 1) { 19 | //using last char is more user friendly as we can click and press a key to use the new char 20 | value = value.substring(value.length-1) 21 | } 22 | 23 | return value 24 | } 25 | 26 | //from https://davidwalsh.name/javascript-debounce-function 27 | export function debounce(func: T, wait: number, immediate = false): T { 28 | var timeout: any; 29 | return function (this: any) { 30 | var context = this, args = arguments; 31 | var later = function () { 32 | timeout = null; 33 | if (!immediate) func.apply(context, args); 34 | }; 35 | var callNow = immediate && !timeout; 36 | clearTimeout(timeout); 37 | timeout = setTimeout(later, wait); 38 | if (callNow) func.apply(context, args); 39 | } as any 40 | } 41 | 42 | //inspired from https://github.com/jjuback/gc-excelviewer/blob/master/src/extension.ts 43 | export function isCsvFile(document: vscode.TextDocument) { 44 | if (!document) return false 45 | 46 | let lang = document.languageId.toLowerCase() 47 | let possible = ['csv', 'tsv', 'plaintext', 48 | //rainbow csv extension types, see https://github.com/mechatroner/vscode_rainbow_csv 49 | 'csv (semicolon)', 'csv (pipe)', 'csv (whitespace)', 'csv (tilde)', 'csv (caret)', 'csv (colon)', 'csv (double quote)', 'csv (equals)', 'csv (dot)', 'csv (hyphen)', 'dynamic csv' 50 | ] 51 | const _isCsvFile = possible.find(p => p === lang) && document.uri.scheme !== 'csv-edit' 52 | return _isCsvFile 53 | } 54 | 55 | export function partitionString(text: string, sliceLength: number): StringSlice[] { 56 | 57 | const slices: StringSlice[] = [] 58 | const totalSlices = Math.ceil(text.length / sliceLength) 59 | 60 | for (let i = 0; i < totalSlices; i++) { 61 | const _part = text.substr(i * sliceLength, sliceLength) 62 | 63 | slices.push({ 64 | text: _part, 65 | sliceNr: i + 1, 66 | totalSlices 67 | }) 68 | } 69 | 70 | return slices 71 | } -------------------------------------------------------------------------------- /thirdParty/big.js/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT Licence (Expat). 2 | 3 | Copyright (c) 2018 Michael Mclaughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /thirdParty/big.js/big.min.js: -------------------------------------------------------------------------------- 1 | /* big.js v5.2.2 https://github.com/MikeMcl/big.js/LICENCE */ 2 | !function(e){"use strict";var r,i=20,s=1,P=1e6,o=-7,f=21,c="[big.js] ",u=c+"Invalid ",b=u+"decimal places",h=u+"rounding mode",x=c+"Division by zero",l={},D=void 0,a=/^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;function R(e,r,t,n){var i=e.c,s=e.e+r+1;if(s++n&&R(e,t,o.RM),2==r&&(n=e.e+t+1);e.c.length=o.PE))s=s.charAt(0)+(1t)for(i-=t;i--;)s+="0";else ii[s]^r?1:-1;return f==c?0:cw[l]?1:-1;break}if(!(h<0))break;for(c=d==f?i:a;d;){if(w[--d]>=1;)r=r.times(r);return i?t.div(n):n},l.round=function(e,r){var t=this.constructor;if(e===D)e=0;else if(e!==~~e||e<-P||P 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /thirdParty/handsontable/info.md: -------------------------------------------------------------------------------- 1 | source: https://github.com/janisdd/handsontable 2 | version: 6.5.5 [branch forCsvEdit6.2.2] (custom for this project) 3 | 4 | -------------------------------------------------------------------------------- /thirdParty/info.md: -------------------------------------------------------------------------------- 1 | when we need some more files add these to package.json (to get the right version of the files): 2 | 3 | ```json 4 | "devDependencies": { 5 | ... 6 | "handsontable": "github:janisdd/handsontable#6.3.0", 7 | "mousetrap": "1.6.3", 8 | "@fortawesome/fontawesome-free": "5.6.3", 9 | } 10 | ``` 11 | -------------------------------------------------------------------------------- /thirdParty/mousetrap/info.md: -------------------------------------------------------------------------------- 1 | source: https://github.com/ccampbell/mousetrap 2 | version: 1.6.3 3 | 4 | -------------------------------------------------------------------------------- /thirdParty/mousetrap/mousetrap.min.js: -------------------------------------------------------------------------------- 1 | /* mousetrap v1.6.3 craig.is/killing/mice */ 2 | (function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function z(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function w(a){return"shift"==a||"ctrl"==a||"alt"==a|| 3 | "meta"==a}function A(a,b){var g,d=[];var e=a;"+"===e?e=["+"]:(e=e.replace(/\+{2}/g,"+plus"),e=e.split("+"));for(g=0;gc||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?"keydown":"keypress"}"keypress"==g&&d.length&&(g="keydown");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a= 4 | a||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter", 9 | escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p;for(c=1;20>c;++c)n[111+c]="f"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={}; 10 | this._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(" "+b.className+" ").indexOf(" mousetrap ")||D(b,this.target))return!1;if("composedPath"in a&&"function"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null}; 11 | d.init=function(){var a=d(u),b;for(b in a)"_"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();q.Mousetrap=d;"undefined"!==typeof module&&module.exports&&(module.exports=d);"function"===typeof define&&define.amd&&define(function(){return d})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); 12 | -------------------------------------------------------------------------------- /thirdParty/mousetrap/plugins/global-bind/README.md: -------------------------------------------------------------------------------- 1 | # Global Bind 2 | 3 | This extension allows you to specify keyboard events that will work anywhere including inside textarea/input fields. 4 | 5 | Usage looks like: 6 | 7 | ```javascript 8 | Mousetrap.bindGlobal('ctrl+s', function() { 9 | _save(); 10 | }); 11 | ``` 12 | 13 | This means that a keyboard event bound using ``Mousetrap.bind`` will only work outside of form input fields, but using ``Moustrap.bindGlobal`` will work in both places. 14 | 15 | If you wanted to create keyboard shortcuts that only work when you are inside a specific textarea you can do that too by creating your own extension. 16 | -------------------------------------------------------------------------------- /thirdParty/mousetrap/plugins/global-bind/mousetrap-global-bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * adds a bindGlobal method to Mousetrap that allows you to 3 | * bind specific keyboard shortcuts that will still work 4 | * inside a text input field 5 | * 6 | * usage: 7 | * Mousetrap.bindGlobal('ctrl+s', _saveChanges); 8 | */ 9 | /* global Mousetrap:true */ 10 | (function(Mousetrap) { 11 | var _globalCallbacks = {}; 12 | var _originalStopCallback = Mousetrap.prototype.stopCallback; 13 | 14 | Mousetrap.prototype.stopCallback = function(e, element, combo, sequence) { 15 | var self = this; 16 | 17 | if (self.paused) { 18 | return true; 19 | } 20 | 21 | if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { 22 | return false; 23 | } 24 | 25 | return _originalStopCallback.call(self, e, element, combo); 26 | }; 27 | 28 | Mousetrap.prototype.bindGlobal = function(keys, callback, action) { 29 | var self = this; 30 | self.bind(keys, callback, action); 31 | 32 | if (keys instanceof Array) { 33 | for (var i = 0; i < keys.length; i++) { 34 | _globalCallbacks[keys[i]] = true; 35 | } 36 | return; 37 | } 38 | 39 | _globalCallbacks[keys] = true; 40 | }; 41 | 42 | Mousetrap.init(); 43 | }) (Mousetrap); 44 | -------------------------------------------------------------------------------- /thirdParty/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var c={},d=a.prototype.stopCallback;a.prototype.stopCallback=function(e,b,a,f){return this.paused?!0:c[a]||c[f]?!1:d.call(this,e,b,a)};a.prototype.bindGlobal=function(a,b,d){this.bind(a,b,d);if(a instanceof Array)for(b=0;b we never stop at `"..."\n` because we actually could have `"..."\r\n` so after the quote the `\r` follows, not the new line character 27 | 28 | - added option to parsing to retain quote information for columns 29 | - the returned type is now: oldResult & {columnIsQuoted: boolean[] or null if option is not set} 30 | - the parse option is: retainQuoteInformation: {boolean} 31 | 32 | - added parse/unparse option `rowInsertCommentLines_commentsString` 33 | - used to treat comments as normal 1 cell rows 34 | - parse: rowInsertCommentLines_commentsString !== null, left trimmed strings starting with it are treated as comments and are parsed into a row with 1 cell 35 | - unparse: rowInsertCommentLines_commentsString !== null, left trimmed strings first cells will be trimmed left and only the first cell will be exported 36 | 37 | - fixed issue https://github.com/mholt/PapaParse/issues/1035 (same as https://github.com/janisdd/vscode-edit-csv/issues/167) 38 | - also fixes https://github.com/mholt/PapaParse/issues/1068 39 | - issue: the escape char was not properly set when only the quoteChar was changed 40 | - subsequent issue: do determine if a field must be quoted `BAD_DELIMITERS` was used, which always includes `"` and `Papa.BYTE_ORDER_MARK` 41 | - it also didn't check the actual quoteChar 42 | 43 | - added option `_quoteLeadingSpace` and `_quoteTrailingSpace` 44 | - `_quoteLeadingSpace` defaults to true: if a field starts with a whitespace, should it be quoted (true) or not (false) 45 | - `_quoteTrailingSpace` defaults to true: if a field ends with a whitespace, should it be quoted (true) or not (false) 46 | 47 | - added option `_determineFieldHasQuotesFunc` to determine if a field should be quoted (it cannot remove quotes!!) 48 | - if a field contains some special characters, it is quoted, e.g. delimiter, quotes, new line, ... 49 | - this func can be used to add quotes to fields (but not to remove quotes!) it is OR-ed with the other indicators 50 | 51 | - when setting `retainQuoteInformation` to `true`, we now also output `cellIsQuotedInfo` which contains the information if a cell was quoted or not 52 | - `cellIsQuotedInfo` now respects `skipEmptyLines` and returns the same amount of rows as the data array 53 | 54 | --- all further changes are directly noted in the `.js` file --- 55 | 56 | ## Minified version 57 | 58 | minified with https://javascript-minifier.com/ -------------------------------------------------------------------------------- /thirdParty/papaparse/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matthew Holt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /thirdParty/papaparse/info.md: -------------------------------------------------------------------------------- 1 | source: https://github.com/mholt/PapaParse 2 | version: 5.0.0 (modified, custom version branch: fix609_main) 3 | modified version: https://github.com/janisdd/PapaParse 4 | -------------------------------------------------------------------------------- /thirdParty/papaparse/papaparse.d.ts: -------------------------------------------------------------------------------- 1 | export type FieldPosition = { 2 | start: number; 3 | end: number; 4 | }; 5 | export type ParseConfigAll = { 6 | /** 7 | * empty for auto-detect 8 | */ 9 | delimiter: '' | string; 10 | /** 11 | * empty for auto-detect 12 | */ 13 | newline: '' | '\r' | '\n' | '\r\n'; 14 | /** 15 | * when a cell starts with this string, it is treated as a comment and the row is ignored 16 | * 17 | * if you want to include comment rows in the parse result, use {@link rowInsertCommentLines_commentsString} and set this to null 18 | */ 19 | comments: string | null; 20 | /** 21 | * used to treat comments as normal 1 cell rows 22 | * - parse: rowInsertCommentLines_commentsString !== null, left trimmed strings starting with it are treated as comments and are parsed into a row with 1 cell 23 | * - unparse: rowInsertCommentLines_commentsString !== null, left trimmed strings first cells will be trimmed left and only the first cell will be exported 24 | */ 25 | rowInsertCommentLines_commentsString: string | null; 26 | /** 27 | * if a field should contain the delimiter but as data and not as delimiter, it must be quoted 28 | */ 29 | quoteChar: string; 30 | /** 31 | * quotes are normally ignored as they don't change the resulting data 32 | * but for some applications we need to know if a cell was quoted 33 | * true: the result will contain the information if a cell was quoted or not 34 | * see {@link ParseResult.columnIsQuoted} and {@link ParseResult.cellIsQuotedInfo} 35 | * false: information will be null 36 | */ 37 | retainQuoteInformation: boolean; 38 | /** 39 | * if a field should contain the quoteChar but as data and not as quoteChar, it must be escaped 40 | * empty to use quote char 41 | */ 42 | escapeChar: '' | string; 43 | /** 44 | * f true, lines that are completely empty (those which evaluate to an empty string) will be skipped. If set to 'greedy', 45 | * lines that evaluate to empty strings after processing will also be skipped. 46 | */ 47 | skipEmptyLines: boolean | 'greedy'; 48 | delimitersToGuess: string[]; 49 | /** 50 | * the max number of characters of the input to guess the delimiter 51 | */ 52 | maxDelimiterGuessLength: number; 53 | /** 54 | * If > 0, only that many rows will be parsed. 55 | * null or <= 0 to not use preview 56 | */ 57 | previewInRows: number | null; 58 | calcLineIndexToCsvLineIndexMapping: boolean; 59 | calcColumnIndexToCsvColumnIndexMapping: boolean; 60 | calcCsvFieldToInputPositionMapping: boolean; 61 | }; 62 | export type ParseConfig = Partial; 63 | export type ParseResult = { 64 | data: string[][]; 65 | errors: ParseError[]; 66 | /** 67 | * meta information about the parsing 68 | */ 69 | meta: ParseResultMeta; 70 | }; 71 | export interface ParseResultMeta { 72 | /** 73 | * Delimiter used 74 | */ 75 | delimiter: string; 76 | /** 77 | * Line break sequence used 78 | */ 79 | linebreak: string; 80 | cursor: number; 81 | /** 82 | * when {@link ParseConfigAll.retainQuoteInformation} is set to true, this array contains the information if a column was quoted or not 83 | * a column is quoted if the first cell of the column was quoted 84 | * 85 | * @deprecated 86 | * this is more a legacy feature, use {@link cellIsQuotedInfo} instead 87 | */ 88 | columnIsQuoted: boolean[] | null; 89 | /** 90 | * when {@link ParseConfigAll.retainQuoteInformation} is set to true, this array contains the information if a cell was quoted or not 91 | */ 92 | cellIsQuotedInfo: boolean[][] | null; 93 | /** 94 | * for each line index in the input text the csv line index it refers to 95 | */ 96 | outLineIndexToCsvLineIndexMapping: number[] | null; 97 | outColumnIndexToCsvColumnIndexMapping: number[][] | null; 98 | outCsvFieldToInputPositionMapping: FieldPosition[][] | null; 99 | } 100 | export interface ParseError { 101 | /** 102 | * A generalization of the error 103 | */ 104 | type: string; 105 | /** 106 | * Standardized error code 107 | */ 108 | code: string; 109 | /** 110 | * Human-readable details 111 | */ 112 | message: string; 113 | /** 114 | * Row index of parsed data where error is 115 | */ 116 | row?: number; 117 | /** 118 | * column index (cursor position) of the error 119 | */ 120 | index?: number; 121 | } 122 | export type UnparseResult = { 123 | csv: string; 124 | /** 125 | * meta information about the unparsing 126 | */ 127 | meta: UnparseResultMeta; 128 | }; 129 | export interface UnparseResultMeta { 130 | outCsvFieldToInputPositionMapping: FieldPosition[][] | null; 131 | } 132 | export type UnparseConfigAll = { 133 | delimiter: string; 134 | newline: string; 135 | quoteChar: string; 136 | /** 137 | * empty to use quote char 138 | */ 139 | escapeChar: '' | string; 140 | skipEmptyLines: boolean | 'greedy'; 141 | /** 142 | * If true, forces all fields to be enclosed in quotes. 143 | * If an array of true/false values, specifies which fields should be force-quoted (first boolean is for the first column, second boolean for the second column, ...) 144 | * 145 | * @note 146 | * old version used option columnIsQuoted for the array but we changed it back to one option 147 | */ 148 | quotes: boolean | boolean[]; 149 | /** 150 | * true: quote empty/null/undefined fields 151 | */ 152 | quoteEmptyOrNullFields: boolean; 153 | quoteLeadingSpace: boolean; 154 | quoteTrailingSpace: boolean; 155 | determineFieldHasQuotesFunc?: ((field: string, row: number, col: number) => boolean); 156 | /** 157 | * see {@link ParseConfigAll.rowInsertCommentLines_commentsString} 158 | */ 159 | rowInsertCommentLines_commentsString: string | null; 160 | calcCsvFieldToInputPositionMapping: boolean; 161 | }; 162 | export type UnparseConfig = Partial; 163 | /** 164 | * some options might be unset or will bet auto-detected, 165 | * this is the effective configuration 166 | */ 167 | export interface ParseConfigEffective extends ParseConfigAll { 168 | delimiter: string; 169 | newline: '\r' | '\n' | '\r\n'; 170 | comments: string; 171 | previewInRows: number; 172 | } 173 | /** 174 | * only exports to inspect defaults 175 | */ 176 | export declare const __parseConfigUserDefaults: ParseConfigAll; 177 | /** 178 | * only exports to inspect defaults 179 | */ 180 | export declare const __unparseConfigUserDefaults: UnparseConfigAll; 181 | export declare class Papa { 182 | static RECORD_SEP: string; 183 | static UNIT_SEP: string; 184 | static BYTE_ORDER_MARK: string; 185 | static BAD_DELIMITERS: string[]; 186 | static NEED_QUOTES_CHARS: string[]; 187 | static DefaultDelimiter: string; 188 | static DefaultQuoteChar: string; 189 | static DefaultEscapeChar: string; 190 | static parse(input: string, _config?: ParseConfig): ParseResult; 191 | static unparse(data: Array>, _config?: UnparseConfig): UnparseResult; 192 | } 193 | export declare class Parser { 194 | _config: ParseConfigEffective; 195 | _input: string; 196 | _quoteSearch: number; 197 | _nextNewline: number; 198 | _cursor: number; 199 | _lastCursor: number; 200 | _data: string[][]; 201 | _row: string[]; 202 | _errors: ParseError[]; 203 | _delim: string; 204 | _quoteChar: string; 205 | _newlineString: string; 206 | _inputLen: number; 207 | _escapeChar: string; 208 | _previewInRows: number; 209 | _firstQuoteInformationRowFound: boolean; 210 | _maxGuessLength: number; 211 | _isGuessingDelimiter: boolean; 212 | /** 213 | * normally when parsing quotes are discarded as they don't change the retrieved data 214 | * true: collect information about quotes (cells, columns) 215 | * false: do not collect quote information 216 | * see {@link _columnIsQuoted}, {@link _cellIsQuotedInfo} 217 | * 218 | * NOTE: if false -> we keep the arrays empty and in post-processing we set them to null (not here) 219 | * 220 | * 221 | * to determine if a column is quoted we use the first cell only (if a column has no cells then it's not quoted) 222 | * so if the first line has only 3 columns and all other more than 3 (e.g. 4) then all columns starting from 4 are treated as not quoted!! 223 | * not that there is no difference if we have column headers (first row is used) 224 | * comment rows are ignored for this 225 | */ 226 | _retainQuoteInformation: boolean; 227 | _columnIsQuoted: boolean[]; 228 | /** @type {boolean[][]} for each cell the info if it was quoted originally */ 229 | _cellIsQuotedInfo: boolean[][]; 230 | _cellIsQuotedInfoRow: boolean[]; 231 | _currentRowStartIndex: number; 232 | _comments: string; 233 | _rowInsertCommentLines_commentsString: string | null; 234 | _outColumnIndexToCsvColumnIndexMapping: number[][] | null; 235 | _currSingleRowColumnIndexToCsvColumnIndexMapping: number[]; 236 | _outFieldPositionMapping: Array> | null; 237 | _currentRowFieldPositions: Array; 238 | _fieldStart: number; 239 | constructor(config: ParseConfigEffective, isGuessingDelimiter: boolean); 240 | parse(input: string): ParseResult; 241 | pushRow(row: string[]): void; 242 | /** 243 | * adds the given index to the column mapping if we still calculate it 244 | * @param cumulativeColumnIndex 245 | */ 246 | addColumnIndexMapping(cumulativeColumnIndex: number): void; 247 | /** 248 | * Appends the remaining input from cursor to the end into 249 | * row, saves the row, calls step, and returns the results. 250 | */ 251 | finish(value?: string): ParseResult; 252 | /** 253 | * Appends the current row to the results. It sets the cursor 254 | * to newCursor and finds the nextNewline. The caller should 255 | * take care to execute user's step function and check for 256 | * preview and end parsing if necessary. 257 | */ 258 | saveRow(newCursor: number): void; 259 | /** Returns an object with the results, errors, and meta. */ 260 | returnable(): ParseResult; 261 | /** Gets the delimiter character, which is not inside the quoted field */ 262 | getNextUnqotedDelimiter(nextDelim: number, quoteSearch: number, nextNewline: number): { 263 | nextDelim: number | null; 264 | quoteSearch: number | null; 265 | }; 266 | /** 267 | * checks if there are extra spaces after closing quote and given index without any text 268 | * if Yes, returns the number of spaces 269 | */ 270 | extraSpaces(index: number): number; 271 | private addFieldPosition; 272 | } 273 | -------------------------------------------------------------------------------- /thirdParty/regression/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Tom Alexander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /thirdParty/regression/info.md: -------------------------------------------------------------------------------- 1 | source: https://github.com/janisdd/regression-js 2 | is a fork from https://github.com/Tom-Alexander/regression-js 3 | version: 3.0.0 (modified) 4 | 5 | -------------------------------------------------------------------------------- /thirdParty/regression/regression.d.ts: -------------------------------------------------------------------------------- 1 | import { default as Big } from 'big.js'; 2 | type DataPoint = number[]; 3 | type DataPointBig = Big[]; 4 | type DEFAULT_OPTIONS_TYPE = { 5 | [key in keyof typeof DEFAULT_OPTIONS]?: typeof DEFAULT_OPTIONS[key]; 6 | }; 7 | declare const DEFAULT_OPTIONS: { 8 | precision: number; 9 | precisionBig: number; 10 | predictPoints: boolean; 11 | bigRoundingMode: number; 12 | }; 13 | declare const _default: { 14 | linear: (data: DataPoint[], options?: DEFAULT_OPTIONS_TYPE) => { 15 | points: number[][]; 16 | predict: (x: number) => number[]; 17 | equation: number[]; 18 | string: string; 19 | }; 20 | linearBig: (data: DataPointBig[], options?: DEFAULT_OPTIONS_TYPE) => { 21 | points: Big.Big[][]; 22 | predict: (x: Big) => Big.Big[]; 23 | equation: Big.Big[]; 24 | string: string; 25 | }; 26 | }; 27 | export default _default; 28 | -------------------------------------------------------------------------------- /thirdParty/regression/regression.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('big.js')) : 3 | typeof define === 'function' && define.amd ? define(['big.js'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.regression = factory(global.Big)); 5 | })(this, (function (Big) { 'use strict'; 6 | 7 | const DEFAULT_OPTIONS = { 8 | precision: 2, 9 | precisionBig: 20, 10 | //default for Big.DP https://mikemcl.github.io/big.js/#dp 11 | predictPoints: false, 12 | bigRoundingMode: 1 13 | }; 14 | function round(number, precision) { 15 | const factor = 10 ** precision; 16 | return Math.round(number * factor) / factor; 17 | } 18 | function roundBig(number, precision, roundingMode) { 19 | return number.round(precision, Big.roundHalfUp); 20 | } 21 | function _linear(data, options) { 22 | const sum = [0, 0, 0, 0, 0]; 23 | let len = 0; 24 | for (let n = 0; n < data.length; n++) { 25 | if (data[n][1] !== null) { 26 | len++; 27 | sum[0] += data[n][0]; 28 | sum[1] += data[n][1]; 29 | sum[2] += data[n][0] * data[n][0]; 30 | sum[3] += data[n][0] * data[n][1]; 31 | sum[4] += data[n][1] * data[n][1]; 32 | } 33 | } 34 | const run = len * sum[2] - sum[0] * sum[0]; 35 | const rise = len * sum[3] - sum[0] * sum[1]; 36 | const gradient = run === 0 ? 0 : round(rise / run, options.precision); 37 | const intercept = round(sum[1] / len - gradient * sum[0] / len, options.precision); 38 | const predict = (x) => [ 39 | round(x, options.precision), 40 | round(gradient * x + intercept, options.precision) 41 | ]; 42 | return { 43 | points: options.predictPoints ? data.map((point) => predict(point[0])) : [], 44 | predict, 45 | equation: [gradient, intercept], 46 | string: intercept === 0 ? `y = ${gradient}x` : `y = ${gradient}x + ${intercept}` 47 | }; 48 | } 49 | function _linearBig(data, options) { 50 | const sum = [Big(0), Big(0), Big(0), Big(0), Big(0)]; 51 | let len = Big(0); 52 | for (let n = 0; n < data.length; n++) { 53 | if (data[n][1] !== null) { 54 | len = len.add(1); 55 | sum[0] = sum[0].add(data[n][0]); 56 | sum[1] = sum[1].add(data[n][1]); 57 | sum[2] = sum[2].add(data[n][0].mul(data[n][0])); 58 | sum[3] = sum[3].add(data[n][0].mul(data[n][1])); 59 | sum[4] = sum[4].add(data[n][1].mul(data[n][1])); 60 | } 61 | } 62 | const run = len.mul(sum[2]).minus(sum[0].mul(sum[0])); 63 | const rise = len.mul(sum[3]).minus(sum[0].mul(sum[1])); 64 | const zero = Big(0); 65 | const gradient = run.cmp(zero) === 0 ? zero : roundBig(rise.div(run), options.precisionBig, options.bigRoundingMode); 66 | const intercept = roundBig(sum[1].div(len).sub(gradient.mul(sum[0]).div(len)), options.precisionBig, options.bigRoundingMode); 67 | const predict = (x) => [ 68 | roundBig(x, options.precisionBig, options.bigRoundingMode), 69 | roundBig(gradient.mul(x).add(intercept), options.precisionBig, options.bigRoundingMode) 70 | ]; 71 | return { 72 | points: options.predictPoints ? data.map((point) => predict(point[0])) : [], 73 | predict, 74 | equation: [gradient, intercept], 75 | string: intercept.cmp(zero) === 0 ? `y = ${gradient}x` : `y = ${gradient}x + ${intercept}` 76 | }; 77 | } 78 | const regression = { 79 | linear: (data, options) => { 80 | return _linear(data, { 81 | ...DEFAULT_OPTIONS, 82 | ...options 83 | }); 84 | }, 85 | linearBig: (data, options) => { 86 | return _linearBig(data, { 87 | ...DEFAULT_OPTIONS, 88 | ...options 89 | }); 90 | } 91 | }; 92 | 93 | return regression; 94 | 95 | })); 96 | -------------------------------------------------------------------------------- /thirdParty/regression/regression.min.js: -------------------------------------------------------------------------------- 1 | (function(o,l){typeof exports=="object"&&typeof module<"u"?module.exports=l(require("big.js")):typeof define=="function"&&define.amd?define(["big.js"],l):(o=typeof globalThis<"u"?globalThis:o||self,o.regression=l(o.Big))})(this,function(o){"use strict";const l={precision:2,precisionBig:20,predictPoints:!1,bigRoundingMode:1};function f(e,i){const n=10**i;return Math.round(e*n)/n}function m(e,i,n){return e.round(i,o.roundHalfUp)}function b(e,i){const n=[0,0,0,0,0];let s=0;for(let r=0;r[f(r,i.precision),f(t*r+c,i.precision)];return{points:i.predictPoints?e.map(r=>d(r[0])):[],predict:d,equation:[t,c],string:c===0?`y = ${t}x`:`y = ${t}x + ${c}`}}function M(e,i){const n=[o(0),o(0),o(0),o(0),o(0)];let s=o(0);for(let u=0;u[m(u,i.precisionBig,i.bigRoundingMode),m(c.mul(u).add(d),i.precisionBig,i.bigRoundingMode)];return{points:i.predictPoints?e.map(u=>r(u[0])):[],predict:r,equation:[c,d],string:d.cmp(t)===0?`y = ${c}x`:`y = ${c}x + ${d}`}}return{linear:(e,i)=>b(e,{...l,...i}),linearBig:(e,i)=>M(e,{...l,...i})}}); 2 | -------------------------------------------------------------------------------- /thirdParty/toFormat/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Michael Mclaughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /thirdParty/toFormat/info.md: -------------------------------------------------------------------------------- 1 | source: https://github.com/MikeMcl/toFormat 2 | version: 2.0.0 3 | 4 | -------------------------------------------------------------------------------- /thirdParty/toFormat/toFormat.min.js: -------------------------------------------------------------------------------- 1 | /* toFormat.js v2.0.0 https://github.com/MikeMcl/toFormat */ 2 | function toFormat(r){"use strict";return r.prototype.toFormat=function(r,o,t){if(!this.e&&0!==this.e)return this.toString();var e,a,i,p,u,c,s,n,S,f,d,m,g,l,z,G=this.format||{},h=this.constructor.format||{};if(r!=u?"object"==typeof r?(t=r,r=u):o!=u?"object"==typeof o?(t=o,o=u):"object"!=typeof t&&(t={}):t={}:t={},e=this.toFixed(r,o).split("."),n=e[0],S=e[1],s=this.s<0?n.slice(1):n,c=s.length,f=t.decimalSeparator,f==u&&(f=G.decimalSeparator,f==u&&(f=h.decimalSeparator,f==u&&(f="."))),d=t.groupSeparator,d==u&&(d=G.groupSeparator,d==u&&(d=h.groupSeparator)),d&&(m=t.groupSize,m==u&&(m=G.groupSize,m==u&&(m=h.groupSize,m==u&&(m=0))),g=t.secondaryGroupSize,g==u&&(g=G.secondaryGroupSize,g==u&&(g=h.secondaryGroupSize,g==u&&(g=0))),g?(a=+g,i=+m,c-=i):(a=+m,i=+g),a>0&&c>0)){for(p=c%a||a,n=s.substr(0,p);c>p;p+=a)n+=d+s.substr(p,a);i>0&&(n+=d+s.slice(p)),this.s<0&&(n="-"+n)}return S?(l=t.fractionGroupSeparator,l==u&&(l=G.fractionGroupSeparator,l==u&&(l=h.fractionGroupSeparator)),l&&(z=t.fractionGroupSize,z==u&&(z=G.fractionGroupSize,z==u&&(z=h.fractionGroupSize,z==u&&(z=0))),z=+z,z&&(S=S.replace(new RegExp("\\d{"+z+"}\\B","g"),"$&"+l))),n+f+S):n},r.format={decimalSeparator:".",groupSeparator:",",groupSize:3,secondaryGroupSize:0,fractionGroupSeparator:"",fractionGroupSize:0},r}"undefined"!=typeof module&&module.exports&&(module.exports=toFormat); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | /* Strict Type-Checking Option */ 12 | "strict": true, /* enable all strict type-checking options */ 13 | /* Additional Checks */ 14 | "noUnusedLocals": true, /* Report errors on unused locals. */ 15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 18 | "skipLibCheck": true 19 | }, 20 | "files": [ 21 | "csvEditorHtml/types.d.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | ".vscode-test", 26 | "csvEditorHtml/**/*" 27 | ], 28 | "include": [ 29 | "src/**/*" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": false, 7 | "class-name": true, 8 | "space-before-function-paren":false, 9 | "space-within-parens":false, 10 | "semicolon": [ 11 | false, 12 | "always" 13 | ], 14 | "triple-equals": true 15 | }, 16 | "defaultSeverity": "error" 17 | } 18 | --------------------------------------------------------------------------------