New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

prosemirror-utils

Package Overview
Dependencies
Maintainers
5
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prosemirror-utils - npm Package Compare versions

Comparing version 0.9.6 to 1.0.0-0

56

CHANGELOG.md

@@ -0,1 +1,56 @@

## 1.0.0 (2020-09-21)
### Changed
- Breaking change; removed `prosemirror-tables` dependency
All utility functions related to tables will be moved to `editor-tables`. A link will be added once it is publicly available.
We are in the process of deprecating `promisemirror-tables` as the code has become increasingly difficult to maintain.
The functions removed in this release are:
* addColumnAt
* addRowAt
* cloneRowAt
* convertArrayOfRowsToTableNode
* convertTableNodeToArrayOfRows
* createCell
* createTable
* emptyCell
* findCellClosestToPos
* findCellRectClosestToPos
* findTable
* findTableClosestToPos
* forEachCellInColumn
* forEachCellInRow
* getCellsInColumn
* getCellsInRow
* getCellsInTable
* getSelectionRangeInColumn
* getSelectionRangeInRow
* getSelectionRect
* isCellSelection
* isColumnSelected
* isRectSelected
* isRowSelected
* isTableSelected
* moveColumn
* moveRow
* moveTableColumn
* moveTableRow
* removeColumnAt
* removeColumnClosestToPos
* removeRowAt
* removeRowClosestToPos
* removeSelectedColumns
* removeSelectedRows
* removeTable
* selectColumn
* selectRow
* selectTable
* setCellAttrs
* tableNodeTypes
* transpose
## 0.9.6 (2018-08-07)

@@ -11,3 +66,2 @@

## 0.5.0 (2018-06-04)

@@ -14,0 +68,0 @@

6

package.json
{
"name": "prosemirror-utils",
"version": "0.9.6",
"version": "1.0.0-0",
"description": "Utils library for ProseMirror",

@@ -50,4 +50,3 @@ "main": "dist/index.js",

"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.1",
"prosemirror-tables": "^0.9.1"
"prosemirror-state": "^1.0.1"
},

@@ -68,3 +67,2 @@ "devDependencies": {

"prosemirror-state": "^1.0.1",
"prosemirror-tables": "^0.9.1",
"prosemirror-test-builder": "^1.0.1",

@@ -71,0 +69,0 @@ "prosemirror-view": "^1.1.1",

@@ -212,581 +212,2 @@ # Utils library for ProseMirror

### Utils for working with `table`
* **`findTable`**`(selection: Selection) → ?{pos: number, start: number, node: ProseMirrorNode}`\
Iterates over parent nodes, returning the closest table node.
```javascript
const table = findTable(selection);
```
* **`isCellSelection`**`(selection: Selection) → boolean`\
Checks if current selection is a `CellSelection`.
```javascript
if (isCellSelection(selection)) {
// ...
}
```
* **`isColumnSelected`**`(columnIndex: number) → fn(selection: Selection) → boolean`\
Checks if entire column at index `columnIndex` is selected.
```javascript
const className = isColumnSelected(i)(selection) ? 'selected' : '';
```
* **`isRowSelected`**`(rowIndex: number) → fn(selection: Selection) → boolean`\
Checks if entire row at index `rowIndex` is selected.
```javascript
const className = isRowSelected(i)(selection) ? 'selected' : '';
```
* **`isTableSelected`**`(selection: Selection) → boolean`\
Checks if entire table is selected
```javascript
const className = isTableSelected(selection) ? 'selected' : '';
```
* **`getCellsInColumn`**`(columnIndex: number | [number]) → fn(selection: Selection) → ?[{pos: number, start: number, node: ProseMirrorNode}]`\
Returns an array of cells in a column(s), where `columnIndex` could be a column index or an array of column indexes.
```javascript
const cells = getCellsInColumn(i)(selection); // [{node, pos}, {node, pos}]
```
* **`getCellsInRow`**`(rowIndex: number | [number]) → fn(selection: Selection) → ?[{pos: number, start: number, node: ProseMirrorNode}]`\
Returns an array of cells in a row(s), where `rowIndex` could be a row index or an array of row indexes.
```javascript
const cells = getCellsInRow(i)(selection); // [{node, pos}, {node, pos}]
```
* **`getCellsInTable`**`(selection: Selection) → ?[{pos: number, start: number, node: ProseMirrorNode}]`\
Returns an array of all cells in a table.
```javascript
const cells = getCellsInTable(selection); // [{node, pos}, {node, pos}]
```
* **`selectColumn`**`(columnIndex: number, expand: ?boolean) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that creates a `CellSelection` on a column at index `columnIndex`.
Use the optional `expand` param to extend from current selection.
```javascript
dispatch(
selectColumn(i)(state.tr)
);
```
* **`selectRow`**`(rowIndex: number, expand: ?boolean) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that creates a `CellSelection` on a column at index `rowIndex`.
Use the optional `expand` param to extend from current selection.
```javascript
dispatch(
selectRow(i)(state.tr)
);
```
* **`selectTable`**`(selection: Selection) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that creates a `CellSelection` on the entire table.
```javascript
dispatch(
selectTable(i)(state.tr)
);
```
* **`emptyCell`**`(cell: {pos: number, node: ProseMirrorNode}, schema: Schema) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that clears the content of a given `cell`.
```javascript
const $pos = state.doc.resolve(13);
dispatch(
emptyCell(findCellClosestToPos($pos), state.schema)(state.tr)
);
```
* **`addColumnAt`**`(columnIndex: number) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that adds a new column at index `columnIndex`.
```javascript
dispatch(
addColumnAt(i)(state.tr)
);
```
* **`moveRow`**`(originRowIndex: number, targetRowIndex: targetColumnIndex, options: ?MovementOptions) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that moves the origin row to the target index;
by default "tryToFit" is false, that means if you try to move a row to a place
where we will need to split a row with merged cells it'll throw an exception, for example:
```
____________________________
| | | |
0 | A1 | B1 | C1 |
|______|______|______ ______|
| | | |
1 | A2 | B2 | |
|______|______ ______| |
| | | | D1 |
2 | A3 | B3 | C2 | |
|______|______|______|______|
```
if you try to move the row 0 to the row index 1 with tryToFit false,
it'll throw an exception since you can't split the row 1;
but if "tryToFit" is true, it'll move the row using the current direction.
We defined current direction using the target and origin values
if the origin is greater than the target, that means the course is `bottom-to-top`,
so the `tryToFit` logic will use this direction to determine
if we should move the column to the right or the left.
for example, if you call the function using `moveRow(0, 1, { tryToFit: true })`
the result will be:
```
____________________________
| | | |
0 | A2 | B2 | |
|______|______ ______| |
| | | | D1 |
1 | A3 | B3 | C2 | |
|______|______|______|______|
| | | |
2 | A1 | B1 | C1 |
|______|______|______ ______|
```
since we could put the row zero on index one,
we pushed to the best place to fit the row index 0,
in this case, row index 2.
-------- HOW TO OVERRIDE DIRECTION --------
If you set "tryToFit" to "true", it will try to figure out the best direction
place to fit using the origin and target index, for example:
```
____________________________
| | | |
0 | A1 | B1 | C1 |
|______|______|______ ______|
| | | |
1 | A2 | B2 | |
|______|______ ______| |
| | | | D1 |
2 | A3 | B3 | C2 | |
|______|______|______|______|
| | | |
3 | A4 | B4 | |
|______|______ ______| |
| | | | D2 |
4 | A5 | B5 | C3 | |
|______|______|______|______|
```
If you try to move the row 0 to row index 4 with "tryToFit" enabled, by default,
the code will put it on after the merged rows,
but you can override it using the "direction" option.
-1: Always put the origin before the target
```
____________________________
| | | |
0 | A2 | B2 | |
|______|______ ______| |
| | | | D1 |
1 | A3 | B3 | C2 | |
|______|______|______|______|
| | | |
2 | A1 | B1 | C1 |
|______|______|______ ______|
| | | |
3 | A4 | B4 | |
|______|______ ______| |
| | | | D2 |
4 | A5 | B5 | C3 | |
|______|______|______|______|
```
0: Automatically decide the best place to fit
```
____________________________
| | | |
0 | A2 | B2 | |
|______|______ ______| |
| | | | D1 |
1 | A3 | B3 | C2 | |
|______|______|______|______|
| | | |
2 | A4 | B4 | |
|______|______ ______| |
| | | | D2 |
3 | A5 | B5 | C3 | |
|______|______|______|______|
| | | |
4 | A1 | B1 | C1 |
|______|______|______ ______|
```
1: Always put the origin after the target
```
____________________________
| | | |
0 | A2 | B2 | |
|______|______ ______| |
| | | | D1 |
1 | A3 | B3 | C2 | |
|______|______|______|______|
| | | |
2 | A4 | B4 | |
|______|______ ______| |
| | | | D2 |
3 | A5 | B5 | C3 | |
|______|______|______|______|
| | | |
4 | A1 | B1 | C1 |
|______|______|______ ______|
```
```javascript
dispatch(
moveRow(x, y, options)(state.tr)
);
```
* **`moveColumn`**`(originColumnIndex: number, targetColumnIndex: targetColumnIndex, options: ?MovementOptions) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that moves the origin column to the target index;
by default "tryToFit" is false, that means if you try to move a column to a place
where we will need to split a column with merged cells it'll throw an exception, for example:
```
0 1 2
____________________________
| | | |
| A1 | B1 | C1 |
|______|______|______ ______|
| | | |
| A2 | B2 | |
|______|______ ______| |
| | | | D1 |
| A3 | B3 | C2 | |
|______|______|______|______|
```
if you try to move the column 0 to the column index 1 with tryToFit false,
it'll throw an exception since you can't split the column 1;
but if "tryToFit" is true, it'll move the column using the current direction.
We defined current direction using the target and origin values
if the origin is greater than the target, that means the course is `right-to-left`,
so the `tryToFit` logic will use this direction to determine
if we should move the column to the right or the left.
for example, if you call the function using `moveColumn(0, 1, { tryToFit: true })`
the result will be:
```
0 1 2
_____________________ _______
| | | |
| B1 | C1 | A1 |
|______|______ ______|______|
| | | |
| B2 | | A2 |
|______ ______| |______|
| | | D1 | |
| B3 | C2 | | A3 |
|______|______|______|______|
```
since we could put the column zero on index one,
we pushed to the best place to fit the column 0, in this case, column index 2.
-------- HOW TO OVERRIDE DIRECTION --------
If you set "tryToFit" to "true", it will try to figure out the best direction
place to fit using the origin and target index, for example:
```
0 1 2 3 4 5 6
_________________________________________________
| | | | | |
| A1 | B1 | C1 | E1 | F1 |
|______|______|______ ______|______|______ ______|
| | | | | |
| A2 | B2 | | E2 | |
|______|______ ______| |______ ______| |
| | | | D1 | | | G2 |
| A3 | B3 | C3 | | E3 | F3 | |
|______|______|______|______|______|______|______|
```
If you try to move the column 0 to column index 5 with "tryToFit" enabled, by default,
the code will put it on after the merged columns,
but you can override it using the "direction" option.
-1: Always put the origin before the target
```
0 1 2 3 4 5 6
_________________________________________________
| | | | | |
| B1 | C1 | A1 | E1 | F1 |
|______|______ ______|______|______|______ ______|
| | | | | |
| B2 | | A2 | E2 | |
|______ ______| |______|______ ______| |
| | | D1 | | | | G2 |
| B3 | C3 | | A3 | E3 | F3 | |
|______|______|______|______|______|______|______|
```
0: Automatically decide the best place to fit
```
0 1 2 3 4 5 6
_________________________________________________
| | | | | |
| B1 | C1 | E1 | F1 | A1 |
|______|______ ______|______|______ ______|______|
| | | | | |
| B2 | | E2 | | A2 |
|______ ______| |______ ______| |______|
| | | D1 | | | G2 | |
| B3 | C3 | | E3 | F3 | | A3 |
|______|______|______|______|______|______|______|
```
1: Always put the origin after the target
```
0 1 2 3 4 5 6
_________________________________________________
| | | | | |
| B1 | C1 | E1 | F1 | A1 |
|______|______ ______|______|______ ______|______|
| | | | | |
| B2 | | E2 | | A2 |
|______ ______| |______ ______| |______|
| | | D1 | | | G2 | |
| B3 | C3 | | E3 | F3 | | A3 |
|______|______|______|______|______|______|______|
```
```javascript
dispatch(
moveColumn(x, y, options)(state.tr)
);
```
* **`addRowAt`**`(rowIndex: number, clonePreviousRow: ?boolean) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that adds a new row at index `rowIndex`. Optionally clone the previous row.
```javascript
dispatch(
addRowAt(i)(state.tr)
);
```
```javascript
dispatch(
addRowAt(i, true)(state.tr)
);
```
* **`cloneRowAt`**`(cloneRowIndex: number) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that adds a new row after `cloneRowIndex`, cloning the row attributes at `cloneRowIndex`.
```javascript
dispatch(
cloneRowAt(i)(state.tr)
);
```
* **`removeColumnAt`**`(columnIndex: number) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that removes a column at index `columnIndex`. If there is only one column left, it will remove the entire table.
```javascript
dispatch(
removeColumnAt(i)(state.tr)
);
```
* **`removeRowAt`**`(rowIndex: number) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that removes a row at index `rowIndex`. If there is only one row left, it will remove the entire table.
```javascript
dispatch(
removeRowAt(i)(state.tr)
);
```
* **`removeTable`**`(tr: Transaction) → Transaction`\
Returns a new transaction that removes a table node if the cursor is inside of it.
```javascript
dispatch(
removeTable(state.tr)
);
```
* **`removeSelectedColumns`**`(tr: Transaction) → Transaction`\
Returns a new transaction that removes selected columns.
```javascript
dispatch(
removeSelectedColumns(state.tr)
);
```
* **`removeSelectedRows`**`(tr: Transaction) → Transaction`\
Returns a new transaction that removes selected rows.
```javascript
dispatch(
removeSelectedRows(state.tr)
);
```
* **`removeColumnClosestToPos`**`($pos: ResolvedPos) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that removes a column closest to a given `$pos`.
```javascript
dispatch(
removeColumnClosestToPos(state.doc.resolve(3))(state.tr)
);
```
* **`removeRowClosestToPos`**`($pos: ResolvedPos) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that removes a row closest to a given `$pos`.
```javascript
dispatch(
removeRowClosestToPos(state.doc.resolve(3))(state.tr)
);
```
* **`findCellClosestToPos`**`($pos: ResolvedPos) → ?{pos: number, start: number, node: ProseMirrorNode}`\
Iterates over parent nodes, returning a table cell or a table header node closest to a given `$pos`.
```javascript
const cell = findCellClosestToPos(state.selection.$from);
```
* **`findCellRectClosestToPos`**`($pos: ResolvedPos) → ?{left: number, top: number, right: number, bottom: number}`\
Returns the rectangle spanning a cell closest to a given `$pos`.
```javascript
dispatch(
findCellRectClosestToPos(state.selection.$from)
);
```
* **`forEachCellInColumn`**`(columnIndex: number, cellTransform: fn(cell: {pos: number, start: number, node: ProseMirrorNode}, tr: Transaction) → Transaction, setCursorToLastCell: ?boolean) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that maps a given `cellTransform` function to each cell in a column at a given `columnIndex`.
It will set the selection into the last cell of the column if `setCursorToLastCell` param is set to `true`.
```javascript
dispatch(
forEachCellInColumn(0, (cell, tr) => emptyCell(cell, state.schema)(tr))(state.tr)
);
```
* **`forEachCellInRow`**`(rowIndex: number, cellTransform: fn(cell: {pos: number, start: number, node: ProseMirrorNode}, tr: Transaction) → Transaction, setCursorToLastCell: ?boolean) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that maps a given `cellTransform` function to each cell in a row at a given `rowIndex`.
It will set the selection into the last cell of the row if `setCursorToLastCell` param is set to `true`.
```javascript
dispatch(
forEachCellInRow(0, (cell, tr) => setCellAttrs(cell, { background: 'red' })(tr))(state.tr)
);
```
* **`setCellAttrs`**`(cell: {pos: number, start: number, node: ProseMirrorNode}, attrs: Object) → fn(tr: Transaction) → Transaction`\
Returns a new transaction that sets given `attrs` to a given `cell`.
```javascript
dispatch(
setCellAttrs(findCellClosestToPos($pos), { background: 'blue' })(tr);
);
```
* **`createTable`**`(schema: Schema, rowsCount: ?number = 3, colsCount: ?number = 3, withHeaderRow: ?boolean = true, cellContent: ?Node = null) → Node`\
Returns a table node of a given size.
`withHeaderRow` defines whether the first row of the table will be a header row.
`cellContent` defines the content of each cell.
```javascript
const table = createTable(state.schema); // 3x3 table node
dispatch(
tr.replaceSelectionWith(table).scrollIntoView()
);
```
* **`getSelectionRect`**`(selection: Selection) → ?{left: number, right: number, top: number, bottom: number}`\
Get the selection rectangle. Returns `undefined` if selection is not a CellSelection.
```javascript
const rect = getSelectionRect(selection);
```
* **`getSelectionRangeInColumn`**`(columnIndex: number) → fn(tr: Transaction) → {$anchor: ResolvedPos, $head: ResolvedPos, indexes: [number]}`\
Returns a range of rectangular selection spanning all merged cells around a column at index `columnIndex`.
```javascript
const range = getSelectionRangeInColumn(3)(state.tr);
```
* **`getSelectionRangeInRow`**`(rowIndex: number) → fn(tr: Transaction) → {$anchor: ResolvedPos, $head: ResolvedPos, indexes: [number]}`\
Returns a range of rectangular selection spanning all merged cells around a row at index `rowIndex`.
```javascript
const range = getSelectionRangeInRow(3)(state.tr);
```
### Utils for document transformation

@@ -907,60 +328,5 @@

* **`convertTableNodeToArrayOfRows`**`(tableNode: Node) → [Node]`\
This function will transform the table node
into a matrix of rows and columns respecting merged cells,
for example this table will be convert to the below:
```
____________________________
| | | |
| A1 | B1 | C1 |
|______|______|______ ______|
| | | |
| A2 | B2 | |
|______|______ ______| |
| | | | D1 |
| A3 | B3 | C2 | |
|______|______|______|______|
```
```javascript
array = [
[A1, B1, C1, null],
[A2, B2, null, D1],
[A3. B3, C2, null],
]
```
* **`convertArrayOfRowsToTableNode`**`(tableNode: Node, tableArray: [Node]) → Node`\
This function will transform a matrix of nodes
into table node respecting merged cells and rows configurations,
for example this array will be convert to the table below:
```javascript
array = [
[A1, B1, C1, null],
[A2, B2, null, D1],
[A3. B3, C2, null],
]
```
```
____________________________
| | | |
| A1 | B1 | C1 |
|______|______|______ ______|
| | | |
| A2 | B2 | |
|______|______ ______| |
| | | | D1 |
| A3 | B3 | C2 | |
|______|______|______|______|
```
## License
* **Apache 2.0** : http://www.apache.org/licenses/LICENSE-2.0
- **Apache 2.0** : http://www.apache.org/licenses/LICENSE-2.0

@@ -60,69 +60,2 @@ import { Node as ProsemirrorNode, Schema, NodeType, Mark, MarkType, ResolvedPos, Fragment } from 'prosemirror-model';

// Table
export function findTable(selection: Selection): ContentNodeWithPos | undefined;
export function isCellSelection(selection: Selection): boolean;
export function isColumnSelected(columnIndex: number): (selection: Selection) => boolean;
export function isRowSelected(rowIndex: number): (selection: Selection) => boolean;
export function isTableSelected(selection: Selection): boolean;
export function getCellsInColumn(columnIndex: number | number[]): (selection: Selection) => ContentNodeWithPos[] | undefined;
export function getCellsInRow(rowIndex: number | number[]): (selection: Selection) => ContentNodeWithPos[] | undefined;
export function getCellsInTable(selection: Selection): ContentNodeWithPos[] | undefined;
export function selectColumn(columnIndex: number, expand?: boolean): (tr: Transaction) => Transaction;
export function selectRow(rowIndex: number, expand?: boolean): (tr: Transaction) => Transaction;
export function selectTable(tr: Transaction): Transaction;
export function emptyCell(cell: ContentNodeWithPos, schema: Schema): (tr: Transaction) => Transaction;
export function addColumnAt(columnIndex: number): (tr: Transaction) => Transaction;
export function moveRow(originRowIndex: number, targetRowIndex: number, options?: MovementOptions): (tr: Transaction) => Transaction;
export function moveColumn(originColumnIndex: number, targetColumnIndex: number, options?: MovementOptions): (tr: Transaction) => Transaction;
export function addRowAt(rowIndex: number, clonePreviousRow?: boolean): (tr: Transaction) => Transaction;
export function cloneRowAt(cloneRowIndex: number): (tr: Transaction) => Transaction;
export function removeColumnAt(columnIndex: number): (tr: Transaction) => Transaction;
export function removeRowAt(rowIndex: number): (tr: Transaction) => Transaction;
export function removeSelectedColumns(tr: Transaction): Transaction;
export function removeSelectedRows(tr: Transaction): Transaction;
export function removeTable(tr: Transaction): Transaction;
export function removeColumnClosestToPos($pos: ResolvedPos): (tr: Transaction) => Transaction;
export function removeRowClosestToPos($pos: ResolvedPos): (tr: Transaction) => Transaction;
export function forEachCellInColumn(columnIndex: number, cellTransform: CellTransform, moveCursorToLastCell?: boolean): (tr: Transaction) => Transaction;
export function forEachCellInRow(rowIndex: number, cellTransform: CellTransform, moveCursorToLastCell?: boolean): (tr: Transaction) => Transaction;
export function setCellAttrs(cell: ContentNodeWithPos, attrs: Object): (tr: Transaction) => Transaction;
export function findCellClosestToPos($pos: ResolvedPos): ContentNodeWithPos | undefined;
export function findCellRectClosestToPos($pos: ResolvedPos): {top: number, bottom: number, left: number, right: number} | undefined;
export function createTable(schema: Schema, rowsCount?: number, colsCount?: number, withHeaderRow?: boolean, cellContent?: Node): ProsemirrorNode;
export function getSelectionRect(selection: Selection): {top: number, bottom: number, left: number, right: number} | undefined;
export function getSelectionRangeInColumn(columnIndex: number): (tr: Transaction) => {$anchor: ResolvedPos, $head: ResolvedPos, indexes: number[]};
export function getSelectionRangeInRow(rowIndex: number): (tr: Transaction) => {$anchor: ResolvedPos, $head: ResolvedPos, indexes: number[]};
// Transforms

@@ -137,6 +70,2 @@ export function removeParentNodeOfType(nodeType: NodeType | NodeType[]): (tr: Transaction) => Transaction;

export function convertTableNodeToArrayOfRows(tableNode: ProsemirrorNode): Array<Array<ProsemirrorNode | null>>;
export function convertArrayOfRowsToTableNode(tableNode: ProsemirrorNode, tableArray: Array<Array<ProsemirrorNode | null>>): ProsemirrorNode;
export function canInsert($pos: ResolvedPos, node: ProsemirrorNode | Fragment): boolean;

@@ -143,0 +72,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc