@peers-app/peers-ui 0.15.5 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,6 +62,7 @@ const markdown_plugin_1 = require("./markdown-plugin");
62
62
  const mention_node_1 = require("./mention-node");
63
63
  const mentions_plugin_1 = require("./mentions-plugin");
64
64
  const move_line_plugin_1 = require("./move-line-plugin");
65
+ const select_line_boundary_plugin_1 = require("./select-line-boundary-plugin");
65
66
  const theme_1 = __importDefault(require("./theme"));
66
67
  const toolbar_1 = require("./toolbar");
67
68
  const editorConfig = {
@@ -111,7 +112,7 @@ function MarkdownEditor(props) {
111
112
  };
112
113
  }
113
114
  const _mentionConfigs = props.mentionConfigs ?? mention_configs_1.mentionConfigs;
114
- return ((0, jsx_runtime_1.jsx)(LexicalComposer_1.LexicalComposer, { initialConfig: { ...editorConfig }, children: (0, jsx_runtime_1.jsxs)("div", { className: "editor-container", children: [props.hideToolbar ? null : (0, jsx_runtime_1.jsx)(toolbar_1.ToolbarPlugin, { topRightControls: props.topRightControls }), (0, jsx_runtime_1.jsxs)("div", { className: "editor-inner", ref: editorRef, style: { maxHeight: props.maxHeight, overflowY: "auto" }, children: [(0, jsx_runtime_1.jsx)(LexicalRichTextPlugin_1.RichTextPlugin, { contentEditable: (0, jsx_runtime_1.jsx)(LexicalContentEditable_1.ContentEditable, { className: "editor-input p-2" }), placeholder: null, ErrorBoundary: LexicalErrorBoundary_1.LexicalErrorBoundary }), (0, jsx_runtime_1.jsx)(LexicalHistoryPlugin_1.HistoryPlugin, {}), props.autoFocus && (0, jsx_runtime_1.jsx)(LexicalAutoFocusPlugin_1.AutoFocusPlugin, { defaultSelection: "rootEnd" }), (0, jsx_runtime_1.jsx)(LexicalListPlugin_1.ListPlugin, {}), _mentionConfigs.length > 0 && ((0, jsx_runtime_1.jsx)(mentions_plugin_1.MentionsPlugin, { mentionConfigs: _mentionConfigs, mentionsOpen: mentionsOpen })), (0, jsx_runtime_1.jsx)(autolink_plugin_1.LexicalAutoLinkPlugin, {}), (0, jsx_runtime_1.jsx)(markdown_plugin_1.MarkdownPlugin, { markdownObs: props.value }), (0, jsx_runtime_1.jsx)(LexicalMarkdownShortcutPlugin_1.MarkdownShortcutPlugin, { transformers: markdown_plugin_1.customMarkdownTransformers }), (0, jsx_runtime_1.jsx)(LexicalCheckListPlugin_1.CheckListPlugin, {}), (0, jsx_runtime_1.jsx)(LexicalTabIndentationPlugin_1.TabIndentationPlugin, {}), (0, jsx_runtime_1.jsx)(move_line_plugin_1.MoveLineWithAltArrowsPlugin, { mentionsOpen: mentionsOpen }), (0, jsx_runtime_1.jsx)(OnKeyDownPlugin, { effects: props.effects, mentionsOpen: mentionsOpen })] })] }) }));
115
+ return ((0, jsx_runtime_1.jsx)(LexicalComposer_1.LexicalComposer, { initialConfig: { ...editorConfig }, children: (0, jsx_runtime_1.jsxs)("div", { className: "editor-container", children: [props.hideToolbar ? null : (0, jsx_runtime_1.jsx)(toolbar_1.ToolbarPlugin, { topRightControls: props.topRightControls }), (0, jsx_runtime_1.jsxs)("div", { className: "editor-inner", ref: editorRef, style: { maxHeight: props.maxHeight, overflowY: "auto" }, children: [(0, jsx_runtime_1.jsx)(LexicalRichTextPlugin_1.RichTextPlugin, { contentEditable: (0, jsx_runtime_1.jsx)(LexicalContentEditable_1.ContentEditable, { className: "editor-input p-2" }), placeholder: null, ErrorBoundary: LexicalErrorBoundary_1.LexicalErrorBoundary }), (0, jsx_runtime_1.jsx)(LexicalHistoryPlugin_1.HistoryPlugin, {}), props.autoFocus && (0, jsx_runtime_1.jsx)(LexicalAutoFocusPlugin_1.AutoFocusPlugin, { defaultSelection: "rootEnd" }), (0, jsx_runtime_1.jsx)(LexicalListPlugin_1.ListPlugin, {}), _mentionConfigs.length > 0 && ((0, jsx_runtime_1.jsx)(mentions_plugin_1.MentionsPlugin, { mentionConfigs: _mentionConfigs, mentionsOpen: mentionsOpen })), (0, jsx_runtime_1.jsx)(autolink_plugin_1.LexicalAutoLinkPlugin, {}), (0, jsx_runtime_1.jsx)(markdown_plugin_1.MarkdownPlugin, { markdownObs: props.value }), (0, jsx_runtime_1.jsx)(LexicalMarkdownShortcutPlugin_1.MarkdownShortcutPlugin, { transformers: markdown_plugin_1.customMarkdownTransformers }), (0, jsx_runtime_1.jsx)(LexicalCheckListPlugin_1.CheckListPlugin, {}), (0, jsx_runtime_1.jsx)(LexicalTabIndentationPlugin_1.TabIndentationPlugin, {}), (0, jsx_runtime_1.jsx)(move_line_plugin_1.MoveLineWithAltArrowsPlugin, { mentionsOpen: mentionsOpen }), (0, jsx_runtime_1.jsx)(select_line_boundary_plugin_1.SelectToLineBoundaryPlugin, {}), (0, jsx_runtime_1.jsx)(OnKeyDownPlugin, { effects: props.effects, mentionsOpen: mentionsOpen })] })] }) }));
115
116
  }
116
117
  const OnKeyDownPlugin = (props) => {
117
118
  const [editor] = (0, LexicalComposerContext_1.useLexicalComposerContext)();
@@ -1,6 +1,16 @@
1
1
  import type { Observable } from "@peers-app/peers-sdk";
2
+ import { type LexicalNode } from "lexical";
2
3
  /**
3
- * Registers Alt/Option + ArrowUp/ArrowDown to swap the caret's block (or list item) with an adjacent sibling.
4
+ * Walks up from `node` to find the reorderable block for keyboard shortcuts that
5
+ * operate on "lines" (blocks). Returns a list item when inside a list, or the
6
+ * top-level block directly under the root.
7
+ */
8
+ export declare function $getMovableBlock(node: LexicalNode): LexicalNode | null;
9
+ /**
10
+ * Registers Alt/Option + ArrowUp/ArrowDown to move the selected block(s) — or the
11
+ * block at the caret — past an adjacent sibling. Supports both collapsed cursors and
12
+ * multi-block selections: the single neighbor block is relocated to the other side of
13
+ * the selection range, which effectively shifts the selected blocks up or down by one.
4
14
  * @param props.mentionsOpen — when true, the plugin defers so mention typeahead keeps keyboard focus.
5
15
  */
6
16
  export declare function MoveLineWithAltArrowsPlugin(props: {
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.$getMovableBlock = $getMovableBlock;
3
4
  exports.MoveLineWithAltArrowsPlugin = MoveLineWithAltArrowsPlugin;
4
5
  const list_1 = require("@lexical/list");
5
6
  const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext");
6
7
  const lexical_1 = require("lexical");
7
8
  const react_1 = require("react");
8
9
  /**
9
- * Returns the node to reorder for Alt/Option + arrow "move line":
10
- * a list item (among siblings in its list), or else the top-level block under the root.
10
+ * Walks up from `node` to find the reorderable block for keyboard shortcuts that
11
+ * operate on "lines" (blocks). Returns a list item when inside a list, or the
12
+ * top-level block directly under the root.
11
13
  */
12
14
  function $getMovableBlock(node) {
13
15
  let n = node;
@@ -27,7 +29,25 @@ function $getMovableBlock(node) {
27
29
  return null;
28
30
  }
29
31
  /**
30
- * Registers Alt/Option + ArrowUp/ArrowDown to swap the caret's block (or list item) with an adjacent sibling.
32
+ * Returns true when `node` sits at the absolute start of `block` i.e. it is
33
+ * the block itself or lies along the first-child path from block down to node.
34
+ * Used to detect a selection endpoint that bled into the next block at offset 0.
35
+ */
36
+ function $isFirstPositionInBlock(node, block) {
37
+ let n = node;
38
+ while (n !== null && n !== block) {
39
+ if (n.getPreviousSibling() !== null) {
40
+ return false;
41
+ }
42
+ n = n.getParent();
43
+ }
44
+ return n === block;
45
+ }
46
+ /**
47
+ * Registers Alt/Option + ArrowUp/ArrowDown to move the selected block(s) — or the
48
+ * block at the caret — past an adjacent sibling. Supports both collapsed cursors and
49
+ * multi-block selections: the single neighbor block is relocated to the other side of
50
+ * the selection range, which effectively shifts the selected blocks up or down by one.
31
51
  * @param props.mentionsOpen — when true, the plugin defers so mention typeahead keeps keyboard focus.
32
52
  */
33
53
  function MoveLineWithAltArrowsPlugin(props) {
@@ -49,33 +69,78 @@ function MoveLineWithAltArrowsPlugin(props) {
49
69
  // so handled would stay false, preventDefault would never run, and the
50
70
  // built-in arrow handler + browser would move the caret on top of our swap.
51
71
  const selection = (0, lexical_1.$getSelection)();
52
- if (!(0, lexical_1.$isRangeSelection)(selection) || !selection.isCollapsed()) {
72
+ if (!(0, lexical_1.$isRangeSelection)(selection)) {
73
+ return false;
74
+ }
75
+ const anchorBlock = $getMovableBlock(selection.anchor.getNode());
76
+ const focusBlock = $getMovableBlock(selection.focus.getNode());
77
+ if (anchorBlock === null || focusBlock === null) {
53
78
  return false;
54
79
  }
55
- const anchorNode = selection.anchor.getNode();
56
- const block = $getMovableBlock(anchorNode);
57
- if (block === null) {
80
+ // Both endpoints must share the same parent (same nesting level).
81
+ if (anchorBlock.getParent()?.getKey() !== focusBlock.getParent()?.getKey()) {
58
82
  return false;
59
83
  }
60
- const anchorKey = selection.anchor.key;
61
- const anchorOffset = selection.anchor.offset;
62
- const anchorType = selection.anchor.type;
63
- const focusKey = selection.focus.key;
64
- const focusOffset = selection.focus.offset;
65
- const focusType = selection.focus.type;
84
+ const anchorIdx = anchorBlock.getIndexWithinParent();
85
+ const focusIdx = focusBlock.getIndexWithinParent();
86
+ const firstBlock = anchorIdx <= focusIdx ? anchorBlock : focusBlock;
87
+ let lastBlock = anchorIdx <= focusIdx ? focusBlock : anchorBlock;
88
+ let anchorKey = selection.anchor.key;
89
+ let anchorOffset = selection.anchor.offset;
90
+ let anchorType = selection.anchor.type;
91
+ let focusKey = selection.focus.key;
92
+ let focusOffset = selection.focus.offset;
93
+ let focusType = selection.focus.type;
94
+ // When a selection extends to offset 0 of the next block (common when
95
+ // selecting to "end of line"), that trailing block has no selected
96
+ // content — exclude it so it isn't dragged along. Also clamp the
97
+ // trailing selection endpoint to the end of the new lastBlock so the
98
+ // restored highlight doesn't overextend into the excluded block.
99
+ if (lastBlock !== firstBlock) {
100
+ const lastPoint = anchorIdx > focusIdx ? selection.anchor : selection.focus;
101
+ if (lastPoint.offset === 0 && $isFirstPositionInBlock(lastPoint.getNode(), lastBlock)) {
102
+ const stepped = lastBlock.getPreviousSibling();
103
+ if (stepped === null) {
104
+ return false;
105
+ }
106
+ lastBlock = stepped;
107
+ const lastDesc = (0, lexical_1.$isElementNode)(lastBlock) ? lastBlock.getLastDescendant() : null;
108
+ const clampKey = lastDesc ? lastDesc.getKey() : lastBlock.getKey();
109
+ const clampOffset = lastDesc
110
+ ? lastDesc.getTextContentSize()
111
+ : (0, lexical_1.$isElementNode)(lastBlock)
112
+ ? lastBlock.getChildrenSize()
113
+ : lastBlock.getTextContentSize();
114
+ const clampType = lastDesc
115
+ ? "text"
116
+ : (0, lexical_1.$isElementNode)(lastBlock)
117
+ ? "element"
118
+ : "text";
119
+ if (anchorIdx > focusIdx) {
120
+ anchorKey = clampKey;
121
+ anchorOffset = clampOffset;
122
+ anchorType = clampType;
123
+ }
124
+ else {
125
+ focusKey = clampKey;
126
+ focusOffset = clampOffset;
127
+ focusType = clampType;
128
+ }
129
+ }
130
+ }
66
131
  if (code === "ArrowUp") {
67
- const prev = block.getPreviousSibling();
132
+ const prev = firstBlock.getPreviousSibling();
68
133
  if (prev === null) {
69
134
  return false;
70
135
  }
71
- prev.insertBefore(block, false);
136
+ lastBlock.insertAfter(prev, false);
72
137
  }
73
138
  else {
74
- const next = block.getNextSibling();
139
+ const next = lastBlock.getNextSibling();
75
140
  if (next === null) {
76
141
  return false;
77
142
  }
78
- next.insertAfter(block, false);
143
+ firstBlock.insertBefore(next, false);
79
144
  }
80
145
  if ((0, lexical_1.$getNodeByKey)(anchorKey) !== null) {
81
146
  const restored = (0, lexical_1.$createRangeSelection)();
@@ -86,7 +151,7 @@ function MoveLineWithAltArrowsPlugin(props) {
86
151
  (0, lexical_1.$setSelection)(restored);
87
152
  }
88
153
  else {
89
- block.selectEnd();
154
+ lastBlock.selectEnd();
90
155
  }
91
156
  event.preventDefault();
92
157
  return true;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Intercepts Cmd+Shift+ArrowLeft/Right (Mac line-boundary selection) and explicitly
3
+ * sets anchor/focus so that Lexical's reconciliation cannot flip the selection direction.
4
+ * Uses the browser's native `Selection.modify` with `'lineboundary'` granularity to
5
+ * find the correct **visual** line boundary (respecting text wrapping), then converts
6
+ * the result back to Lexical coordinates.
7
+ */
8
+ export declare function SelectToLineBoundaryPlugin(): null;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SelectToLineBoundaryPlugin = SelectToLineBoundaryPlugin;
4
+ const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext");
5
+ const lexical_1 = require("lexical");
6
+ const react_1 = require("react");
7
+ /**
8
+ * Intercepts Cmd+Shift+ArrowLeft/Right (Mac line-boundary selection) and explicitly
9
+ * sets anchor/focus so that Lexical's reconciliation cannot flip the selection direction.
10
+ * Uses the browser's native `Selection.modify` with `'lineboundary'` granularity to
11
+ * find the correct **visual** line boundary (respecting text wrapping), then converts
12
+ * the result back to Lexical coordinates.
13
+ */
14
+ function SelectToLineBoundaryPlugin() {
15
+ const [editor] = (0, LexicalComposerContext_1.useLexicalComposerContext)();
16
+ (0, react_1.useEffect)(() => {
17
+ return editor.registerCommand(lexical_1.KEY_DOWN_COMMAND, (event) => {
18
+ if (!event.shiftKey || !event.metaKey || event.altKey || event.ctrlKey) {
19
+ return false;
20
+ }
21
+ const code = event.code;
22
+ if (code !== "ArrowLeft" && code !== "ArrowRight") {
23
+ return false;
24
+ }
25
+ const selection = (0, lexical_1.$getSelection)();
26
+ if (!(0, lexical_1.$isRangeSelection)(selection)) {
27
+ return false;
28
+ }
29
+ const savedAnchorKey = selection.anchor.key;
30
+ const savedAnchorOffset = selection.anchor.offset;
31
+ const savedAnchorType = selection.anchor.type;
32
+ const focusElement = editor.getElementByKey(selection.focus.key);
33
+ if (!focusElement) {
34
+ return false;
35
+ }
36
+ const focusDomNode = selection.focus.type === "text" ? focusElement.firstChild : focusElement;
37
+ if (!focusDomNode) {
38
+ return false;
39
+ }
40
+ const domSelection = window.getSelection();
41
+ if (!domSelection) {
42
+ return false;
43
+ }
44
+ domSelection.collapse(focusDomNode, selection.focus.offset);
45
+ domSelection.modify("move", code === "ArrowLeft" ? "left" : "right", "lineboundary");
46
+ const boundaryDomNode = domSelection.anchorNode;
47
+ const boundaryDomOffset = domSelection.anchorOffset;
48
+ if (!boundaryDomNode) {
49
+ return false;
50
+ }
51
+ const boundaryLexicalNode = (0, lexical_1.$getNearestNodeFromDOMNode)(boundaryDomNode);
52
+ if (!boundaryLexicalNode) {
53
+ return false;
54
+ }
55
+ const newFocusKey = boundaryLexicalNode.getKey();
56
+ const newFocusOffset = boundaryDomOffset;
57
+ const newFocusType = (0, lexical_1.$isTextNode)(boundaryLexicalNode)
58
+ ? "text"
59
+ : "element";
60
+ const restored = (0, lexical_1.$createRangeSelection)();
61
+ restored.format = selection.format;
62
+ restored.style = selection.style;
63
+ restored.anchor.set(savedAnchorKey, savedAnchorOffset, savedAnchorType);
64
+ restored.focus.set(newFocusKey, newFocusOffset, newFocusType);
65
+ (0, lexical_1.$setSelection)(restored);
66
+ event.preventDefault();
67
+ return true;
68
+ }, lexical_1.COMMAND_PRIORITY_HIGH);
69
+ }, [editor]);
70
+ return null;
71
+ }
@@ -9,11 +9,46 @@ const ui_loader_1 = require("../../ui-router/ui-loader");
9
9
  function getDataExplorerApi() {
10
10
  return window.electronAPI?.dataExplorer;
11
11
  }
12
+ function formatRelativeTime(isoString) {
13
+ const date = new Date(isoString);
14
+ const now = Date.now();
15
+ const diffMs = now - date.getTime();
16
+ const diffMin = Math.floor(diffMs / 60_000);
17
+ if (diffMin < 1)
18
+ return "just now";
19
+ if (diffMin < 60)
20
+ return `${diffMin}m ago`;
21
+ const diffHrs = Math.floor(diffMin / 60);
22
+ if (diffHrs < 24)
23
+ return `${diffHrs}h ago`;
24
+ const diffDays = Math.floor(diffHrs / 24);
25
+ if (diffDays < 30)
26
+ return `${diffDays}d ago`;
27
+ return date.toLocaleDateString();
28
+ }
29
+ function DeleteConfirmModal({ tableName, onConfirm, onCancel, }) {
30
+ const [confirmText, setConfirmText] = (0, react_1.useState)("");
31
+ const [deleting, setDeleting] = (0, react_1.useState)(false);
32
+ const handleConfirm = async () => {
33
+ setDeleting(true);
34
+ try {
35
+ await onConfirm();
36
+ }
37
+ finally {
38
+ setDeleting(false);
39
+ }
40
+ };
41
+ return ((0, jsx_runtime_1.jsx)("div", { className: "position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center", style: { zIndex: 1050, backgroundColor: "rgba(0,0,0,0.5)" }, onClick: onCancel, children: (0, jsx_runtime_1.jsxs)("div", { className: "card shadow-lg", style: { maxWidth: 480, width: "100%" }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header bg-danger text-white d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Delete Table" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn-close btn-close-white", onClick: onCancel })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsxs)("div", { className: "alert alert-danger mb-3", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-exclamation-triangle-fill me-2" }), "This will permanently delete all data for ", (0, jsx_runtime_1.jsx)("strong", { children: tableName }), " on this device and all synced devices once synced."] }), (0, jsx_runtime_1.jsxs)("label", { className: "form-label", children: ["Type ", (0, jsx_runtime_1.jsx)("strong", { children: tableName }), " to confirm:"] }), (0, jsx_runtime_1.jsx)("input", { type: "text", className: "form-control", value: confirmText, onChange: (e) => setConfirmText(e.target.value), placeholder: tableName,
42
+ // biome-ignore lint/a11y/noAutofocus: confirmation input needs immediate focus
43
+ autoFocus: true })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-footer d-flex justify-content-end gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-secondary", onClick: onCancel, disabled: deleting, children: "Cancel" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-danger", disabled: confirmText !== tableName || deleting, onClick: handleConfirm, children: deleting ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" }), "Deleting..."] })) : ("Delete Table") })] })] }) }));
44
+ }
12
45
  function DataExplorerList() {
13
46
  const [tables, setTables] = (0, react_1.useState)([]);
47
+ const [deletedTables, setDeletedTables] = (0, react_1.useState)([]);
14
48
  const [loading, setLoading] = (0, react_1.useState)(true);
15
49
  const [currentContext, setCurrentContext] = (0, react_1.useState)("");
16
50
  const [compacting, setCompacting] = (0, react_1.useState)(false);
51
+ const [deleteTarget, setDeleteTarget] = (0, react_1.useState)(null);
17
52
  const formatBytes = (bytes) => {
18
53
  if (bytes === undefined)
19
54
  return "-";
@@ -30,12 +65,12 @@ function DataExplorerList() {
30
65
  const kb = 1024;
31
66
  const mb = kb * 1024;
32
67
  if (bytes < 10 * kb)
33
- return "text-secondary"; // 0 to 10KB: secondary (gray)
68
+ return "text-secondary";
34
69
  if (bytes < 10 * mb)
35
- return "text-primary"; // 10KB to 10MB: primary (blue)
70
+ return "text-primary";
36
71
  if (bytes < 100 * mb)
37
- return "text-warning"; // 10MB to 100MB: warning (orange)
38
- return "text-danger"; // >100MB: danger (red)
72
+ return "text-warning";
73
+ return "text-danger";
39
74
  };
40
75
  const loadTables = (0, react_1.useCallback)(async () => {
41
76
  try {
@@ -46,12 +81,9 @@ function DataExplorerList() {
46
81
  console.warn("Data Explorer API not available");
47
82
  return;
48
83
  }
49
- // Get current context info
50
84
  const contextInfo = await api.getCurrentContext();
51
85
  setCurrentContext(contextInfo.contextName);
52
- // Get all tables from IPC
53
86
  const allTablesInfo = await api.getAllTables();
54
- // Sort by registered status first, then by table name
55
87
  allTablesInfo.sort((a, b) => {
56
88
  if (a.isRegistered !== b.isRegistered) {
57
89
  return a.isRegistered ? -1 : 1;
@@ -59,6 +91,9 @@ function DataExplorerList() {
59
91
  return a.tableName.localeCompare(b.tableName);
60
92
  });
61
93
  setTables(allTablesInfo);
94
+ const deleted = await api.getDeletedTables();
95
+ deleted.sort((a, b) => a.tableName.localeCompare(b.tableName));
96
+ setDeletedTables(deleted);
62
97
  }
63
98
  catch (error) {
64
99
  console.error("Error loading tables:", error);
@@ -67,6 +102,23 @@ function DataExplorerList() {
67
102
  setLoading(false);
68
103
  }
69
104
  }, []);
105
+ const handleDeleteTable = async () => {
106
+ if (!deleteTarget)
107
+ return;
108
+ const api = getDataExplorerApi();
109
+ if (!api)
110
+ return;
111
+ try {
112
+ await api.deleteTable(deleteTarget);
113
+ setDeleteTarget(null);
114
+ setLoading(true);
115
+ await loadTables();
116
+ }
117
+ catch (error) {
118
+ console.error("Error deleting table:", error);
119
+ alert(`Error deleting table: ${error instanceof Error ? error.message : String(error)}`);
120
+ }
121
+ };
70
122
  const compactDatabase = async (beforeTimestamp) => {
71
123
  try {
72
124
  setCompacting(true);
@@ -85,7 +137,6 @@ function DataExplorerList() {
85
137
  }
86
138
  await api.compactDatabase(beforeTimestamp);
87
139
  alert(`Database compacted successfully!${isExtreme ? " (Extreme mode)" : ""}`);
88
- // Reload tables to show updated sizes
89
140
  setLoading(true);
90
141
  await loadTables();
91
142
  }
@@ -112,7 +163,6 @@ function DataExplorerList() {
112
163
  }
113
164
  await api.resetChangeTracking();
114
165
  alert("Change tracking reset successfully!");
115
- // Reload tables to show updated sizes
116
166
  setLoading(true);
117
167
  await loadTables();
118
168
  }
@@ -136,7 +186,9 @@ function DataExplorerList() {
136
186
  if (!currentContext && tables.length === 0) {
137
187
  return ((0, jsx_runtime_1.jsx)("div", { className: "container-fluid p-4", children: (0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: "Unable to load database information." }) }));
138
188
  }
139
- return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-4", children: [(0, jsx_runtime_1.jsxs)("ul", { className: "nav nav-tabs mb-4", children: [(0, jsx_runtime_1.jsx)("li", { className: "nav-item", children: (0, jsx_runtime_1.jsxs)("a", { className: "nav-link active", href: "#/data-explorer", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-table" }), " Tables"] }) }), (0, jsx_runtime_1.jsx)("li", { className: "nav-item", children: (0, jsx_runtime_1.jsxs)("a", { className: "nav-link", href: "#/data-explorer/query", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-terminal" }), " Query"] }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center mb-4", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Data Explorer" }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "btn-group", role: "group", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning", onClick: () => compactDatabase(), disabled: compacting || loading, title: "Compact database (cleanup changes older than 2 weeks)", children: compacting ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1", role: "status", "aria-hidden": "true" }), "Compacting..."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-disc" }), " Compact Database"] })) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning dropdown-toggle dropdown-toggle-split", "data-bs-toggle": "dropdown", "aria-expanded": "false", disabled: compacting || loading, title: "More compaction options", children: (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: "Toggle Dropdown" }) }), (0, jsx_runtime_1.jsxs)("ul", { className: "dropdown-menu", children: [(0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsxs)("a", { className: "dropdown-item", href: "#", onClick: (e) => {
189
+ const registeredTables = tables.filter((t) => t.isRegistered);
190
+ const unregisteredTables = tables.filter((t) => !t.isRegistered);
191
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-4", children: [deleteTarget && ((0, jsx_runtime_1.jsx)(DeleteConfirmModal, { tableName: deleteTarget, onConfirm: handleDeleteTable, onCancel: () => setDeleteTarget(null) })), (0, jsx_runtime_1.jsxs)("ul", { className: "nav nav-tabs mb-4", children: [(0, jsx_runtime_1.jsx)("li", { className: "nav-item", children: (0, jsx_runtime_1.jsxs)("a", { className: "nav-link active", href: "#/data-explorer", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-table" }), " Tables"] }) }), (0, jsx_runtime_1.jsx)("li", { className: "nav-item", children: (0, jsx_runtime_1.jsxs)("a", { className: "nav-link", href: "#/data-explorer/query", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-terminal" }), " Query"] }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center mb-4", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Data Explorer" }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "btn-group", role: "group", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning", onClick: () => compactDatabase(), disabled: compacting || loading, title: "Compact database (cleanup changes older than 2 weeks)", children: compacting ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1", role: "status", "aria-hidden": "true" }), "Compacting..."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-disc" }), " Compact Database"] })) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning dropdown-toggle dropdown-toggle-split", "data-bs-toggle": "dropdown", "aria-expanded": "false", disabled: compacting || loading, title: "More compaction options", children: (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: "Toggle Dropdown" }) }), (0, jsx_runtime_1.jsxs)("ul", { className: "dropdown-menu", children: [(0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsxs)("a", { className: "dropdown-item", href: "#", onClick: (e) => {
140
192
  e.preventDefault();
141
193
  compactDatabase(Date.now());
142
194
  }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-exclamation-triangle text-danger me-2" }), "Extreme Compaction", (0, jsx_runtime_1.jsx)("div", { className: "small text-muted", children: "Remove ALL change history" })] }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("hr", { className: "dropdown-divider" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsxs)("a", { className: "dropdown-item", href: "#", onClick: (e) => {
@@ -145,15 +197,11 @@ function DataExplorerList() {
145
197
  }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-clockwise text-warning me-2" }), "Reset Change Tracking", (0, jsx_runtime_1.jsx)("div", { className: "small text-muted", children: "Clear and rebuild all change history" })] }) })] })] }), (0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-primary", onClick: () => {
146
198
  setLoading(true);
147
199
  loadTables();
148
- }, disabled: compacting, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-clockwise" }), " Refresh"] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "card mb-4", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h5", { className: "card-title mb-2", children: "Current Data Context" }), (0, jsx_runtime_1.jsx)("p", { className: "mb-0", children: currentContext })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-end", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-muted small", children: "Total DB Size" }), (0, jsx_runtime_1.jsx)("div", { className: "fs-4 fw-bold", children: formatBytes(tables.reduce((sum, t) => sum + (t.bytes || 0), 0)) })] })] }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "card mb-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsxs)("h5", { className: "card-title", children: ["Registered Tables (", tables.filter((t) => t.isRegistered).length, ")", (0, jsx_runtime_1.jsx)("span", { className: "badge bg-success ms-2", children: "Active" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small", children: "These tables are registered with the TableContainer and actively used by the application." }), tables.filter((t) => t.isRegistered).length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: "text-muted", children: "No registered tables found" })) : ((0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-hover table-sm", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table Name" }), (0, jsx_runtime_1.jsx)("th", { children: "Table ID" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Row Count" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Bytes" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: tables
149
- .filter((t) => t.isRegistered)
150
- .map((table) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("strong", { children: table.tableName }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: table.tableId || "system" }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: table.rowCount !== undefined ? table.rowCount.toLocaleString() : "-" }), (0, jsx_runtime_1.jsx)("td", { className: `text-end fw-bold ${getBytesColorClass(table.bytes)}`, children: formatBytes(table.bytes) })] }, table.tableName))) })] }) }))] }) }), tables.some((t) => !t.isRegistered) && ((0, jsx_runtime_1.jsx)("div", { className: "card", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsxs)("h5", { className: "card-title", children: ["Unregistered Tables (", tables.filter((t) => !t.isRegistered).length, ")", (0, jsx_runtime_1.jsx)("span", { className: "badge bg-warning text-dark ms-2", children: "Raw SQLite" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small", children: "These tables exist in the SQLite database but are not registered with the TableContainer. They may be system tables, change tracking tables, or legacy tables." }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-hover table-sm", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table Name" }), (0, jsx_runtime_1.jsx)("th", { children: "Type" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Row Count" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Bytes" }), (0, jsx_runtime_1.jsx)("th", { children: "SQL Definition" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: tables
151
- .filter((t) => !t.isRegistered)
152
- .map((table) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("strong", { children: table.tableName }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { className: "badge bg-secondary", children: table.type || "table" }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: table.rowCount !== undefined ? table.rowCount.toLocaleString() : "-" }), (0, jsx_runtime_1.jsx)("td", { className: `text-end fw-bold ${getBytesColorClass(table.bytes)}`, children: formatBytes(table.bytes) }), (0, jsx_runtime_1.jsx)("td", { children: table.sql ? ((0, jsx_runtime_1.jsxs)("details", { children: [(0, jsx_runtime_1.jsx)("summary", { className: "cursor-pointer text-primary", style: { cursor: "pointer" }, children: (0, jsx_runtime_1.jsx)("small", { children: "View SQL" }) }), (0, jsx_runtime_1.jsx)("pre", { className: "mt-2 p-2 bg-body-secondary border rounded", style: {
200
+ }, disabled: compacting, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-clockwise" }), " Refresh"] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "card mb-4", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h5", { className: "card-title mb-2", children: "Current Data Context" }), (0, jsx_runtime_1.jsx)("p", { className: "mb-0", children: currentContext })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-end", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-muted small", children: "Total DB Size" }), (0, jsx_runtime_1.jsx)("div", { className: "fs-4 fw-bold", children: formatBytes(tables.reduce((sum, t) => sum + (t.bytes || 0), 0)) })] })] }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "card mb-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsxs)("h5", { className: "card-title", children: ["Registered Tables (", registeredTables.length, ")", (0, jsx_runtime_1.jsx)("span", { className: "badge bg-success ms-2", children: "Active" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small", children: "These tables are registered with the TableContainer and actively used by the application." }), registeredTables.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: "text-muted", children: "No registered tables found" })) : ((0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-hover table-sm", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table Name" }), (0, jsx_runtime_1.jsx)("th", { children: "Table ID" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Row Count" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Bytes" }), (0, jsx_runtime_1.jsx)("th", { style: { width: "1%" } })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: registeredTables.map((table) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("strong", { children: table.tableName }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: table.tableId || "system" }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: table.rowCount !== undefined ? table.rowCount.toLocaleString() : "-" }), (0, jsx_runtime_1.jsx)("td", { className: `text-end fw-bold ${getBytesColorClass(table.bytes)}`, children: formatBytes(table.bytes) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger border-0", title: `Delete ${table.tableName}`, onClick: () => setDeleteTarget(table.tableName), children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-trash" }) }) })] }, table.tableName))) })] }) }))] }) }), unregisteredTables.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "card mb-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsxs)("h5", { className: "card-title", children: ["Unregistered Tables (", unregisteredTables.length, ")", (0, jsx_runtime_1.jsx)("span", { className: "badge bg-warning text-dark ms-2", children: "Raw SQLite" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small", children: "These tables exist in the SQLite database but are not registered with the TableContainer. They may be system tables, change tracking tables, or legacy tables." }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-hover table-sm", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table Name" }), (0, jsx_runtime_1.jsx)("th", { children: "Type" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Row Count" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Bytes" }), (0, jsx_runtime_1.jsx)("th", { children: "SQL Definition" }), (0, jsx_runtime_1.jsx)("th", { style: { width: "1%" } })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: unregisteredTables.map((table) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("strong", { children: table.tableName }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { className: "badge bg-secondary", children: table.type || "table" }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: table.rowCount !== undefined ? table.rowCount.toLocaleString() : "-" }), (0, jsx_runtime_1.jsx)("td", { className: `text-end fw-bold ${getBytesColorClass(table.bytes)}`, children: formatBytes(table.bytes) }), (0, jsx_runtime_1.jsx)("td", { children: table.sql ? ((0, jsx_runtime_1.jsxs)("details", { children: [(0, jsx_runtime_1.jsx)("summary", { className: "cursor-pointer text-primary", style: { cursor: "pointer" }, children: (0, jsx_runtime_1.jsx)("small", { children: "View SQL" }) }), (0, jsx_runtime_1.jsx)("pre", { className: "mt-2 p-2 bg-body-secondary border rounded", style: {
153
201
  fontSize: "0.75rem",
154
202
  maxHeight: "300px",
155
203
  overflow: "auto",
156
- }, children: (0, jsx_runtime_1.jsx)("code", { className: "text-body", children: table.sql }) })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted small", children: "-" })) })] }, table.tableName))) })] }) })] }) }))] }));
204
+ }, children: (0, jsx_runtime_1.jsx)("code", { className: "text-body", children: table.sql }) })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted small", children: "-" })) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger border-0", title: `Delete ${table.tableName}`, onClick: () => setDeleteTarget(table.tableName), children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-trash" }) }) })] }, table.tableName))) })] }) })] }) })), deletedTables.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "card mb-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsxs)("h5", { className: "card-title", children: ["Deleted Tables (", deletedTables.length, ")", (0, jsx_runtime_1.jsx)("span", { className: "badge bg-danger ms-2", children: "Deleted" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small", children: "These tables have been deleted. Their definitions are retained so other devices know to stop syncing data for them." }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-hover table-sm", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table Name" }), (0, jsx_runtime_1.jsx)("th", { children: "Table ID" }), (0, jsx_runtime_1.jsx)("th", { children: "Deleted At" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: deletedTables.map((table) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("strong", { className: "text-muted", children: table.tableName }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: table.tableId }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { title: new Date(table.deletedAt).toLocaleString(), children: formatRelativeTime(table.deletedAt) }) })] }, table.tableId))) })] }) })] }) }))] }));
157
205
  }
158
206
  (0, ui_loader_1.registerInternalPeersUI)({
159
207
  peersUIId: "data-explorer-list-ui",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-ui",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-ui.git"
@@ -28,7 +28,7 @@
28
28
  "lint:fix": "biome check --write ."
29
29
  },
30
30
  "peerDependencies": {
31
- "@peers-app/peers-sdk": "^0.15.5",
31
+ "@peers-app/peers-sdk": "^0.16.0",
32
32
  "bootstrap": "^5.3.3",
33
33
  "react": "^18.0.0",
34
34
  "react-dom": "^18.0.0"
@@ -39,7 +39,7 @@
39
39
  "@babel/preset-env": "^7.24.5",
40
40
  "@babel/preset-react": "^7.24.1",
41
41
  "@babel/preset-typescript": "^7.27.1",
42
- "@peers-app/peers-sdk": "0.15.5",
42
+ "@peers-app/peers-sdk": "0.16.0",
43
43
  "@testing-library/dom": "^10.4.0",
44
44
  "@testing-library/jest-dom": "^6.6.3",
45
45
  "@testing-library/react": "^16.3.0",
@@ -22,6 +22,7 @@ import { customMarkdownTransformers, MarkdownPlugin } from "./markdown-plugin";
22
22
  import { MentionNode } from "./mention-node";
23
23
  import { MentionsPlugin } from "./mentions-plugin";
24
24
  import { MoveLineWithAltArrowsPlugin } from "./move-line-plugin";
25
+ import { SelectToLineBoundaryPlugin } from "./select-line-boundary-plugin";
25
26
  import theme from "./theme";
26
27
  import { type IToolbarControl, ToolbarPlugin } from "./toolbar";
27
28
 
@@ -121,6 +122,7 @@ export function MarkdownEditor(props: IMarkdownEditorProps) {
121
122
  <CheckListPlugin />
122
123
  <TabIndentationPlugin />
123
124
  <MoveLineWithAltArrowsPlugin mentionsOpen={mentionsOpen} />
125
+ <SelectToLineBoundaryPlugin />
124
126
  <OnKeyDownPlugin effects={props.effects} mentionsOpen={mentionsOpen} />
125
127
  </div>
126
128
  </div>