From e19b686b1da93803449430c396b82d4caac405e4 Mon Sep 17 00:00:00 2001 From: Amelia Wattenberger Date: Mon, 31 Jan 2022 16:39:26 -0500 Subject: [PATCH 01/11] fix: sort blank number cells to end --- src/store.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/store.ts b/src/store.ts index 67d1b0c..bf9e54f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -513,6 +513,10 @@ const getSortFunction = (sort: string[], typeOfValue: string) => { aVal = (aVal || '')?.toUpperCase?.() || ''; if (!aVal || aVal === '\n') aVal = direction === 'asc' ? 'zzzzzz' : ''; aVal = aVal.trimStart(); + } else if (typeOfValue === 'number') { + aVal = Number.isFinite(aVal) + ? aVal + : Infinity * (direction === 'asc' ? 1 : -1); } // @ts-ignore let bVal = b[columnName]; @@ -520,8 +524,11 @@ const getSortFunction = (sort: string[], typeOfValue: string) => { bVal = (bVal || '')?.toUpperCase?.() || ''; if (!bVal || bVal === '\n') bVal = direction === 'asc' ? 'zzzzzz' : ''; bVal = bVal.trimStart(); + } else if (typeOfValue === 'number') { + bVal = Number.isFinite(bVal) + ? bVal + : Infinity * (direction === 'asc' ? 1 : -1); } - return direction == 'desc' ? // @ts-ignore descending(aVal, bVal) From 5c2d7a5c4e287e5b9eb59bcc7b4920ee7faf3768 Mon Sep 17 00:00:00 2001 From: Amelia Wattenberger Date: Mon, 31 Jan 2022 16:42:12 -0500 Subject: [PATCH 02/11] v0.13.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f63b2..43261bf 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.13.2", + "version": "0.13.3", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", From c7faa3e2c96481a9207ac68f2220ec9f1daa93f5 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Tue, 8 Feb 2022 15:08:19 -0500 Subject: [PATCH 03/11] security: bump immer (#24) --- package.json | 4 ++-- src/store.ts | 41 ++++++++++++++++++++++++++++++++++++----- yarn.lock | 16 ++++++++-------- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 43261bf..1871768 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "date-fns": "^2.19.0", "dompurify": "^2.2.9", "downshift": "^6.1.1", - "immer": "^8.0.2", + "immer": "^9.0.12", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "match-sorter": "^6.3.0", @@ -115,6 +115,6 @@ "react-virtualized-auto-sizer": "^1.0.5", "react-window": "^1.8.6", "twin.macro": "^2.6.2", - "zustand": "^3.3.3" + "zustand": "^3.6.9" } } diff --git a/src/store.ts b/src/store.ts index bf9e54f..070febd 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,6 +1,13 @@ import tw from 'twin.macro'; -import create, { StateCreator } from 'zustand'; +import create, { + GetState, + SetState, + State, + StateCreator, + StoreApi, +} from 'zustand'; import produce from 'immer'; +import type { Draft } from 'immer'; import { format as d3Format, timeFormat, @@ -29,10 +36,34 @@ import { StringFilter } from './components/filters/string'; import { CategoryFilter } from './components/filters/category'; import { RangeFilter } from './components/filters/range'; -export const immer = ( - config: StateCreator void) => void> -): StateCreator => (set, get, api) => - config((fn) => set(produce(fn) as (state: T) => T), get, api); +const immer = < + T extends State, + CustomSetState extends SetState, + CustomGetState extends GetState, + CustomStoreApi extends StoreApi +>( + config: StateCreator< + T, + (partial: ((draft: Draft) => void) | T, replace?: boolean) => void, + CustomGetState, + CustomStoreApi + > +): StateCreator => ( + set, + get, + api +) => + config( + (partial, replace) => { + const nextState = + typeof partial === 'function' + ? produce(partial as (state: Draft) => T) + : (partial as T); + return set(nextState, replace); + }, + get, + api + ); export type GridState = { data: any[]; diff --git a/yarn.lock b/yarn.lock index 4a33c5a..527bcc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8398,10 +8398,10 @@ immer@8.0.1: resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== -immer@^8.0.2: - version "8.0.4" - resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" - integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== +immer@^9.0.12: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== import-cwd@^3.0.0: version "3.0.0" @@ -15567,10 +15567,10 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zustand@^3.3.3: - version "3.5.6" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.6.tgz#c28cfbdfdd999d26d1a94ea105a6fd1da56ed38a" - integrity sha512-8XrpRO5scF8MSxeAlu7vFupmLG+5MTWhT+6+3QNsihs0QZfOjaArFyvenUgrk30WdZVGVHLHXBhbqC2/QzLeMA== +zustand@^3.6.9: + version "3.6.9" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.6.9.tgz#f61a756ddea9f95c7ee7cfd3af2f88c10078afbc" + integrity sha512-OvDNu/jEWpRnEC7k8xh8GKjqYog7td6FZrLMuHs/IeI8WhrCwV+FngVuwMIFhp5kysZXr6emaeReMqjLGaldAQ== zwitch@^1.0.0: version "1.0.5" From 5bbba37df6424cb82255eee269a96c4d0d2902fa Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Tue, 8 Feb 2022 15:10:45 -0500 Subject: [PATCH 04/11] v0.13.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1871768..2221477 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.13.3", + "version": "0.13.4", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", From cd365cd78212cfd05835f063b36b60de6c5509a4 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Wed, 9 Feb 2022 11:40:54 -0500 Subject: [PATCH 05/11] feat: replace anchorme with linkify-it (#25) --- package.json | 3 +- src/components/cell.tsx | 82 ++++++++++++++++++++++------------------- yarn.lock | 22 ++++++++--- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 2221477..6faa915 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@storybook/addons": "^6.1.21", "@storybook/react": "^6.1.21", "@types/d3": "^6.3.0", + "@types/linkify-it": "^3.0.2", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.2", "@types/react-virtualized": "^9.21.11", @@ -101,12 +102,12 @@ "@types/lodash": "^4.0.6", "@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-window": "^1.8.2", - "anchorme": "^2.1.2", "d3": "^6.6.0", "date-fns": "^2.19.0", "dompurify": "^2.2.9", "downshift": "^6.1.1", "immer": "^9.0.12", + "linkify-it": "^3.0.3", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "match-sorter": "^6.3.0", diff --git a/src/components/cell.tsx b/src/components/cell.tsx index 8eba132..8276273 100644 --- a/src/components/cell.tsx +++ b/src/components/cell.tsx @@ -1,12 +1,14 @@ import React, { useEffect } from 'react'; import { areEqual } from 'react-window'; import tw, { TwStyle } from 'twin.macro'; -import anchorme from 'anchorme'; +import Linkify from 'linkify-it'; import { cellTypeMap } from '../store'; import { DashIcon, DiffModifiedIcon, PlusIcon } from '@primer/octicons-react'; import DOMPurify from 'dompurify'; import { EditableCell } from './editable-cell'; +const linkify = Linkify().add('ftp:', null).add('mailto:', null); + interface CellProps { type: string; value: any; @@ -47,37 +49,39 @@ export const Cell = React.memo(function (props: CellProps) { onFocusChange, background, style = {}, - onMouseEnter = () => { }, + onMouseEnter = () => {}, } = props; // @ts-ignore const cellInfo = cellTypeMap[type]; - const { cell: CellComponent } = cellInfo || {} + const { cell: CellComponent } = cellInfo || {}; - const displayValue = (formattedValue || value || "").toString(); + const displayValue = (formattedValue || value || '').toString(); const isLongValue = (displayValue || '').length > 23; - const stringWithLinks = React.useMemo( - () => displayValue ? ( - DOMPurify.sanitize( - anchorme({ - input: displayValue + '', - options: { - attributes: { - target: '_blank', - rel: 'noopener', - }, - }, - }) - ) - ) : "", - [value] - ) + const stringWithLinks = React.useMemo(() => { + if (!displayValue) return ''; + + const sanitized = DOMPurify.sanitize(displayValue); + // Does the sanitized string contain any links? + if (!linkify.test(sanitized)) return sanitized; + + // If so, we need to linkify it. + const matches = linkify.match(sanitized); + + // If there are no matches, we can just return the sanitized string. + if (!matches || matches.length === 0) return sanitized; + + // Otherwise, let's naively use the first match. + return ` + ${matches[0].url} + `; + }, [value]); useEffect(() => { - if (!isFocused) return - onMouseEnter() - }, [isFocused]) + if (!isFocused) return; + onMouseEnter(); + }, [isFocused]); if (!cellInfo) return null; @@ -91,14 +95,15 @@ export const Cell = React.memo(function (props: CellProps) { 'modified-row': DiffModifiedIcon, }[status || '']; const statusColor = - isFirstColumn && - // @ts-ignore - { - new: 'text-green-400', - old: 'text-pink-400', - modified: 'text-yellow-500', - 'modified-row': 'text-yellow-500', - }[status || ''] || "" + (isFirstColumn && + // @ts-ignore + { + new: 'text-green-400', + old: 'text-pink-400', + modified: 'text-yellow-500', + 'modified-row': 'text-yellow-500', + }[status || '']) || + ''; return (
+ }} + > + onRowDelete={onRowDelete} + > onMouseEnter?.()} > @@ -214,5 +220,5 @@ const CellInner = React.memo(function CellInner({
)} - ) -}) \ No newline at end of file + ); +}); diff --git a/yarn.lock b/yarn.lock index 527bcc3..f88d809 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3251,6 +3251,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== +"@types/linkify-it@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" + integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + "@types/lodash@^4.0.6": version "4.14.171" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b" @@ -3837,11 +3842,6 @@ alphanum-sort@^1.0.0, alphanum-sort@^1.0.2: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= -anchorme@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/anchorme/-/anchorme-2.1.2.tgz#4abc7e128a8a42d0036a61ebb9b18bbc032fa52a" - integrity sha512-2iPY3kxDDZvtRzauqKDb4v7a5sTF4GZ+esQTY8nGYvmhAtGTeFPMn4cRnvyWS1qmtPTP0Mv8hyLOp9l3ZzWMKg== - ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -9793,6 +9793,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +linkify-it@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== + dependencies: + uc.micro "^1.0.1" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -14795,6 +14802,11 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== +uc.micro@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" From 2eb6655de353a22573ea281b1ba34414ae3fd782 Mon Sep 17 00:00:00 2001 From: Matt Rothenberg Date: Wed, 9 Feb 2022 11:43:03 -0500 Subject: [PATCH 06/11] v0.13.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6faa915..0ef174e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.13.4", + "version": "0.13.5", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", From d4864ac6e9438b8a172e71f3a4f26a0876cd3b26 Mon Sep 17 00:00:00 2001 From: Amelia Wattenberger Date: Fri, 25 Mar 2022 15:26:29 -0700 Subject: [PATCH 07/11] feat: add number of columns fixes #26 --- src/components/grid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/grid.tsx b/src/components/grid.tsx index 133bb43..d2fcbb9 100644 --- a/src/components/grid.tsx +++ b/src/components/grid.tsx @@ -527,7 +527,7 @@ export function Grid(props: GridProps) {
Showing {filteredData.length.toLocaleString()} {isFiltered && ` of ${data.length.toLocaleString()}`} row - {(isFiltered ? filteredData : data).length === 1 ? '' : 's'} + {data.length === 1 ? '' : 's'} × {columnNames.length.toLocaleString()} column{columnNames.length === 1 ? '' : 's'}
From 97f767948fbde49cbd9bf3b68a8fe9ee6ba247f8 Mon Sep 17 00:00:00 2001 From: Amelia Wattenberger Date: Fri, 25 Mar 2022 15:27:28 -0700 Subject: [PATCH 08/11] v0.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ef174e..5f86648 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.13.5", + "version": "0.14.0", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", From 6dff1ef76b1fe52c8392fbac42194ce9a66b4cbb Mon Sep 17 00:00:00 2001 From: Amelia Wattenberger Date: Thu, 5 May 2022 13:36:40 -0700 Subject: [PATCH 09/11] fix: handle getCellIndicies when child is undefined --- src/components/sticky-grid.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/sticky-grid.tsx b/src/components/sticky-grid.tsx index 06d7d46..dfcce35 100644 --- a/src/components/sticky-grid.tsx +++ b/src/components/sticky-grid.tsx @@ -6,7 +6,10 @@ import tw from 'twin.macro'; import { FilterValue } from '../types'; function getCellIndicies(child) { - return { row: child.props.rowIndex, column: child.props.columnIndex }; + return { + row: child?.props.rowIndex || 0, + column: child?.props.columnIndex || 0 + }; } function getShownIndicies(children) { From 261d676352230fe2ed20e670cb22d30a3480aaf1 Mon Sep 17 00:00:00 2001 From: Amelia Wattenberger Date: Thu, 5 May 2022 13:40:42 -0700 Subject: [PATCH 10/11] v0.14.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f86648..e05b812 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.14.0", + "version": "0.14.1", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", From cfd8b6ef8817c511f2e794518eedfb4ed32bda89 Mon Sep 17 00:00:00 2001 From: Viktor Persson Date: Mon, 6 Jun 2022 20:23:05 +0200 Subject: [PATCH 11/11] Update readme; fix docs on defaultSort. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f12af83..f967a86 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,9 @@ The user can interact with the table and update the filters, but the table will ### defaultSort -`string` +`array` -The name of the column you want the table to initialize sorting by. The user can interact with the table and update the sort, but the table will use the default sort when `defaultSort` or `data` changes. +The name of the column and the order you want the table to initialize sorting by (e.g. `["Location", "desc"]`). The user can interact with the table and update the sort, but the table will use the default sort when `defaultSort` or `data` changes. ### defaultStickyColumnName