@synclineapi/editor 2.0.0 → 3.0.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.
- package/README.md +296 -7
- package/dist/syncline-editor.es.js +1853 -1026
- package/dist/syncline-editor.es.js.map +1 -1
- package/dist/syncline-editor.umd.js +107 -25
- package/dist/syncline-editor.umd.js.map +1 -1
- package/dist/types/index.d.ts +413 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -75,6 +75,16 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
75
75
|
- [Indent Guides](#indent-guides)
|
|
76
76
|
- [Active Line Highlight](#active-line-highlight)
|
|
77
77
|
- [Read-Only Mode](#read-only-mode)
|
|
78
|
+
- [Collaborative Editing](#collaborative-editing)
|
|
79
|
+
- [Quick Start — Same Page](#quick-start--same-page)
|
|
80
|
+
- [Cross-Tab Collaboration](#cross-tab-collaboration)
|
|
81
|
+
- [Using WebSocket for Realtime Collaboration](#using-websocket-for-realtime-collaboration)
|
|
82
|
+
- [CollabConfig Reference](#collabconfig-reference)
|
|
83
|
+
- [Transport Reference](#transport-reference)
|
|
84
|
+
- [Peer Awareness & Cursors](#peer-awareness--cursors)
|
|
85
|
+
- [Remote Cursor CSS Classes](#remote-cursor-css-classes)
|
|
86
|
+
- [Offline & Reconnect](#offline--reconnect)
|
|
87
|
+
- [Playground Collab Panel](#playground-collab-panel)
|
|
78
88
|
- [Behavioral Options](#behavioral-options)
|
|
79
89
|
- [Auto-Close Pairs](#auto-close-pairs)
|
|
80
90
|
- [Line Comment Token](#line-comment-token)
|
|
@@ -115,11 +125,14 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
115
125
|
| **Unified autocomplete** | One `completions` array for keywords, snippets, custom symbols, and DSL items |
|
|
116
126
|
| **VS Code-style docs panel** | Description panel beside the popup — shown for items with `description` |
|
|
117
127
|
| **Dynamic provider** | `provideCompletions` callback for context-aware, fully runtime-controlled completions |
|
|
118
|
-
| **Built-in snippets** | 17 snippets across TypeScript/JS, CSS, and HTML — Tab-expandable |
|
|
128
|
+
| **Built-in snippets** | 17 snippets across TypeScript/JS, CSS, and HTML — Tab-expandable with full tab-stop session (`$1` → `$2` → …) |
|
|
119
129
|
| **Emmet expansion** | `div.wrapper>ul>li*3` → Tab — with inline preview tooltip |
|
|
120
130
|
| **Multi-cursor** | `Alt+Click` to add cursors; `Ctrl+D` to select next occurrence |
|
|
121
131
|
| **Hover documentation** | Tooltip on pointer-rest over any identifier — built-in JS/TS/CSS docs + custom `provideHover` callback |
|
|
122
132
|
| **Move line** | `Alt+Up` / `Alt+Down` to move the current line or selected block up/down |
|
|
133
|
+
| **Duplicate line** | `Shift+Alt+Down` / `Shift+Alt+Up` to duplicate the current line or selected block — cursor follows the new copy |
|
|
134
|
+
| **Go to Line** | `Ctrl+G` / `Cmd+G` prompt to jump to any line number — opt-in via `goToLine: true` |
|
|
135
|
+
| **Placeholder** | Ghost hint text shown in the editor when the document is empty — set via `placeholder` option |
|
|
123
136
|
| **Find & Replace** | Literal and regex search, case-sensitive mode, replace one / all |
|
|
124
137
|
| **Code folding** | Collapse `{}` blocks via gutter toggle |
|
|
125
138
|
| **Bracket matching** | Highlights matching `()[]{}` pairs at the cursor |
|
|
@@ -131,6 +144,7 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
131
144
|
| **Whitespace rendering** | Visible `·` / `→` for spaces and tabs (`none` / `boundary` / `all`) |
|
|
132
145
|
| **Cursor styles** | `line`, `block`, or `underline`; configurable blink rate |
|
|
133
146
|
| **Read-only mode** | All edits blocked; navigation, selection, and copy still work |
|
|
147
|
+
| **Collaborative editing** | Real-time multi-user editing via CRDT (RGA/YATA); remote cursors + selections rendered inline; three built-in transports |
|
|
134
148
|
|
|
135
149
|
---
|
|
136
150
|
|
|
@@ -258,6 +272,8 @@ Default `autoClosePairs`:
|
|
|
258
272
|
| `findReplace` | `boolean` | `true` | Enable find-and-replace (`Ctrl+H`). |
|
|
259
273
|
| `wordSelection` | `boolean` | `true` | Double-click selects the word under the cursor. |
|
|
260
274
|
| `hover` | `boolean` | `true` | Show a documentation tooltip when the pointer rests on a known identifier for ~500 ms. Covers built-in JS/TS symbols and any symbol in `completions` with a `description`, plus the `provideHover` callback. |
|
|
275
|
+
| `goToLine` | `boolean` | `false` | Enable the Go to Line bar (`Ctrl+G` / `Cmd+G`). Pressing the shortcut opens a centered prompt; `Enter` jumps the cursor, `Escape` dismisses. Off by default. |
|
|
276
|
+
| `placeholder` | `string` | `''` | Ghost text rendered at the cursor position when the document is completely empty. Hidden when the document has any content or when the string is empty. |
|
|
261
277
|
|
|
262
278
|
### Syntax & Autocomplete Customisation
|
|
263
279
|
|
|
@@ -913,7 +929,7 @@ editor.updateConfig({ hover: false });
|
|
|
913
929
|
|
|
914
930
|
## Snippets
|
|
915
931
|
|
|
916
|
-
Snippets let you type a short trigger word and press `Tab` to expand a full multi-line block.
|
|
932
|
+
Snippets let you type a short trigger word and press `Tab` to expand a full multi-line block. After expansion a **tab-stop session** begins: the cursor lands at `$1`, and each subsequent `Tab` advances to `$2`, `$3`, and so on. `Shift+Tab` moves backward. Upcoming tab stops are shown as dim ghost cursor markers so you always know where you'll land next. The session ends when you reach the last stop, press `Escape`, or click elsewhere.
|
|
917
933
|
|
|
918
934
|
Snippets appear in the unified autocomplete popup with an **S** badge alongside keywords, functions, and Emmet abbreviations. When a snippet item is selected in the popup, its body template is previewed in the description panel on the right.
|
|
919
935
|
|
|
@@ -1003,10 +1019,13 @@ createEditor(container, { snippetExpansion: false });
|
|
|
1003
1019
|
|
|
1004
1020
|
| Placeholder | Meaning |
|
|
1005
1021
|
|---|---|
|
|
1006
|
-
| `$1` | First
|
|
1007
|
-
| `$2`, `$3`, … | Additional tab stops
|
|
1022
|
+
| `$1` | First tab stop — cursor lands here immediately after expansion |
|
|
1023
|
+
| `$2`, `$3`, … | Additional tab stops — press `Tab` to jump through them in order |
|
|
1024
|
+
| `$0` | Final cursor position (VS Code convention) — session ends here |
|
|
1008
1025
|
| `\n` | Line break — continuation lines inherit the trigger line's indentation |
|
|
1009
1026
|
|
|
1027
|
+
Tab stops are rendered as **dim ghost cursor markers** at their positions in the document. As you type at the current stop the ghost markers shift to stay accurate. Pressing `Shift+Tab` moves back to the previous stop. The session exits when you reach `$0` (or the last stop), press `Escape`, click elsewhere, or press `Enter` / `Delete`.
|
|
1028
|
+
|
|
1010
1029
|
> **Priority:** Emmet is tried first on Tab. If both Emmet and a snippet match the same word, Emmet wins.
|
|
1011
1030
|
|
|
1012
1031
|
---
|
|
@@ -1296,6 +1315,253 @@ editor.updateConfig({ readOnly: true }); // lock again
|
|
|
1296
1315
|
|
|
1297
1316
|
---
|
|
1298
1317
|
|
|
1318
|
+
## Collaborative Editing
|
|
1319
|
+
|
|
1320
|
+
Syncline Editor has **built-in real-time collaboration** powered by an RGA/YATA CRDT. All you need is a transport — every operation is causally ordered and converges identically on every peer with no server-side merge logic required.
|
|
1321
|
+
|
|
1322
|
+
Collaboration is activated by adding a `collab` property to `EditorConfig`:
|
|
1323
|
+
|
|
1324
|
+
```ts
|
|
1325
|
+
import { createEditor, LocalTransport } from 'syncline-editor';
|
|
1326
|
+
|
|
1327
|
+
const transport = new LocalTransport('my-room');
|
|
1328
|
+
|
|
1329
|
+
const editor = createEditor(container, {
|
|
1330
|
+
value: '',
|
|
1331
|
+
language: 'typescript',
|
|
1332
|
+
collab: {
|
|
1333
|
+
transport,
|
|
1334
|
+
name: 'Alice',
|
|
1335
|
+
},
|
|
1336
|
+
});
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
That's all. The editor auto-creates the internal `RGADocument`, wires bidirectional sync, and starts rendering remote peers' cursors and selections.
|
|
1340
|
+
|
|
1341
|
+
---
|
|
1342
|
+
|
|
1343
|
+
### Quick Start — Same Page
|
|
1344
|
+
|
|
1345
|
+
`LocalTransport` uses a same-page event bus — perfect for demos and tests where multiple `createEditor` calls share one HTML document (e.g. an **A / B** split demo).
|
|
1346
|
+
|
|
1347
|
+
```ts
|
|
1348
|
+
import { createEditor, LocalTransport } from 'syncline-editor';
|
|
1349
|
+
|
|
1350
|
+
// Both editors share the same room ID — they sync instantly
|
|
1351
|
+
const transport = new LocalTransport('demo-room');
|
|
1352
|
+
|
|
1353
|
+
const editorA = createEditor(containerA, {
|
|
1354
|
+
collab: { transport, name: 'Alice' },
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
const editorB = createEditor(containerB, {
|
|
1358
|
+
collab: { transport: new LocalTransport('demo-room'), name: 'Bob' },
|
|
1359
|
+
});
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
> Each `LocalTransport` instance gets its own `siteId` automatically. Two instances with the same room string form a peer pair.
|
|
1363
|
+
|
|
1364
|
+
---
|
|
1365
|
+
|
|
1366
|
+
### Cross-Tab Collaboration
|
|
1367
|
+
|
|
1368
|
+
`BroadcastChannelTransport` uses the browser's built-in `BroadcastChannel` API — syncs across **multiple tabs on the same origin** with no server needed.
|
|
1369
|
+
|
|
1370
|
+
```ts
|
|
1371
|
+
import { createEditor, BroadcastChannelTransport } from 'syncline-editor';
|
|
1372
|
+
|
|
1373
|
+
const editor = createEditor(container, {
|
|
1374
|
+
collab: {
|
|
1375
|
+
transport: new BroadcastChannelTransport('my-room'),
|
|
1376
|
+
name: 'Alice',
|
|
1377
|
+
},
|
|
1378
|
+
});
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
Open the same page in two tabs — edits in one appear in the other within milliseconds.
|
|
1382
|
+
|
|
1383
|
+
---
|
|
1384
|
+
|
|
1385
|
+
### Using WebSocket for Realtime Collaboration
|
|
1386
|
+
|
|
1387
|
+
For cross-device / cross-origin collaboration, use `WebSocketTransport` with any WebSocket server that broadcasts messages to room participants. The server never needs to understand CRDT operations — it simply relays raw messages.
|
|
1388
|
+
|
|
1389
|
+
```ts
|
|
1390
|
+
import { createEditor, WebSocketTransport } from 'syncline-editor';
|
|
1391
|
+
|
|
1392
|
+
const editor = createEditor(container, {
|
|
1393
|
+
collab: {
|
|
1394
|
+
transport: new WebSocketTransport('wss://your-server.example.com', 'my-room'),
|
|
1395
|
+
name: 'Alice',
|
|
1396
|
+
onStatus: (s) => console.log('collab status:', s),
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
You can point `WebSocketTransport` at **any** WebSocket endpoint — a custom Node.js server, a managed service, or a serverless WebSocket API. All CRDT merging happens client-side; the server only needs to broadcast incoming messages to every other participant in the same room.
|
|
1402
|
+
|
|
1403
|
+
#### `WebSocketTransport` resilience
|
|
1404
|
+
|
|
1405
|
+
- **Exponential backoff** reconnect (100 ms → 1.6 s cap) with automatic re-send of any operations queued while offline
|
|
1406
|
+
- **State-vector sync** on reconnect — only missing operations are exchanged, not the full document
|
|
1407
|
+
- **Send queue** — operations generated while disconnected are buffered and flushed once the socket reopens
|
|
1408
|
+
|
|
1409
|
+
---
|
|
1410
|
+
|
|
1411
|
+
### CollabConfig Reference
|
|
1412
|
+
|
|
1413
|
+
```ts
|
|
1414
|
+
interface CollabConfig {
|
|
1415
|
+
/** Transport layer — required. Determines how ops are exchanged. */
|
|
1416
|
+
transport: CRDTTransport;
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Display name shown in the cursor label of remote peers.
|
|
1420
|
+
* Defaults to the site ID if omitted.
|
|
1421
|
+
*/
|
|
1422
|
+
name?: string;
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Called whenever the set of connected peers changes (join / leave / heartbeat).
|
|
1426
|
+
* Receives the full current peer map: siteId → AwarenessState.
|
|
1427
|
+
*/
|
|
1428
|
+
onPeersChange?: (peers: Map<string, AwarenessState>) => void;
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Called when the transport connection status changes.
|
|
1432
|
+
* Useful for showing a status indicator in your UI.
|
|
1433
|
+
*/
|
|
1434
|
+
onStatus?: (status: 'connecting' | 'connected' | 'disconnected' | 'syncing') => void;
|
|
1435
|
+
}
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
Pass `collab: undefined` (or omit it) to disable collaboration.
|
|
1439
|
+
Disable at runtime by calling `editor.updateConfig({ collab: undefined })`.
|
|
1440
|
+
|
|
1441
|
+
---
|
|
1442
|
+
|
|
1443
|
+
### Transport Reference
|
|
1444
|
+
|
|
1445
|
+
| Transport | Import | Description |
|
|
1446
|
+
|---|---|---|
|
|
1447
|
+
| `LocalTransport` | `syncline-editor` | In-page event bus. Ideal for split-screen demos and unit tests. |
|
|
1448
|
+
| `BroadcastChannelTransport` | `syncline-editor` | Cross-tab sync on the same origin. No server required. |
|
|
1449
|
+
| `WebSocketTransport` | `syncline-editor` | Realtime WebSocket. Works across devices and origins. |
|
|
1450
|
+
|
|
1451
|
+
All three implement the same `CRDTTransport` interface, so you can provide your own transport (e.g. WebRTC data channels, SSE, etc.) if needed:
|
|
1452
|
+
|
|
1453
|
+
```ts
|
|
1454
|
+
interface CRDTTransport {
|
|
1455
|
+
readonly siteId: string;
|
|
1456
|
+
connect(onMessage: (msg: ChannelMessage) => void): void;
|
|
1457
|
+
send(msg: ChannelMessage): void;
|
|
1458
|
+
disconnect(): void;
|
|
1459
|
+
}
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
---
|
|
1463
|
+
|
|
1464
|
+
### Peer Awareness & Cursors
|
|
1465
|
+
|
|
1466
|
+
Syncline broadcasts awareness state (cursor position, selection, display name) over the transport alongside document operations.
|
|
1467
|
+
|
|
1468
|
+
- Each connected peer gets a **deterministic color** derived from its `siteId` hash
|
|
1469
|
+
- Peer presence is maintained via **3-second heartbeats**; peers that miss 3 beats (9 s) are garbage-collected
|
|
1470
|
+
- Remote cursors and selections update on every render cycle — they respond to scrolling, word wrap changes, and virtual-scroll position automatically
|
|
1471
|
+
|
|
1472
|
+
#### Reacting to peer changes
|
|
1473
|
+
|
|
1474
|
+
```ts
|
|
1475
|
+
const editor = createEditor(container, {
|
|
1476
|
+
collab: {
|
|
1477
|
+
transport,
|
|
1478
|
+
name: 'Alice',
|
|
1479
|
+
onPeersChange: (peers) => {
|
|
1480
|
+
// peers: Map<siteId, AwarenessState>
|
|
1481
|
+
renderPeerAvatars([...peers.values()]);
|
|
1482
|
+
},
|
|
1483
|
+
},
|
|
1484
|
+
});
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
#### `AwarenessState` shape
|
|
1488
|
+
|
|
1489
|
+
```ts
|
|
1490
|
+
interface AwarenessState {
|
|
1491
|
+
siteId: string;
|
|
1492
|
+
name?: string;
|
|
1493
|
+
color: string; // hex color derived from siteId
|
|
1494
|
+
cursor?: { row: number; col: number };
|
|
1495
|
+
selection?: { anchor: { row: number; col: number }; focus: { row: number; col: number } };
|
|
1496
|
+
updatedAt: number; // timestamp of last heartbeat
|
|
1497
|
+
}
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
---
|
|
1501
|
+
|
|
1502
|
+
### Remote Cursor CSS Classes
|
|
1503
|
+
|
|
1504
|
+
Remote cursors and selections are rendered as inline `<span>` elements inside the Shadow DOM. You can override their appearance using Shadow DOM part selectors or by targeting the CSS custom properties from outside the shadow root if your host page injects styles into it.
|
|
1505
|
+
|
|
1506
|
+
| Class | Element | Description |
|
|
1507
|
+
|---|---|---|
|
|
1508
|
+
| `.sl-rc` | `<span>` | Zero-width anchor for a remote cursor beam |
|
|
1509
|
+
| `.sl-rc::after` | pseudo | The 2 px vertical cursor beam |
|
|
1510
|
+
| `.sl-rc-label` | `<span>` | Name chip that floats above the beam |
|
|
1511
|
+
| `.sl-rs` | `<span>` | Highlighted text span for a remote selection |
|
|
1512
|
+
|
|
1513
|
+
CSS custom properties set inline on each `.sl-rc` and `.sl-rs`:
|
|
1514
|
+
|
|
1515
|
+
| Property | Description |
|
|
1516
|
+
|---|---|
|
|
1517
|
+
| `--rc-color` | Peer color (hex) — controls the beam and label background |
|
|
1518
|
+
| `--rs-color` | Peer color with `33` alpha suffix — semi-transparent selection fill |
|
|
1519
|
+
|
|
1520
|
+
Example override (injected via `adoptedStyleSheets` or a `<style>` tag appended to the shadow root):
|
|
1521
|
+
|
|
1522
|
+
```css
|
|
1523
|
+
/* Thicker cursor beam */
|
|
1524
|
+
.sl-rc::after { width: 3px; }
|
|
1525
|
+
|
|
1526
|
+
/* Fully opaque name label */
|
|
1527
|
+
.sl-rc-label { opacity: 1; }
|
|
1528
|
+
|
|
1529
|
+
/* Rounder selection highlight */
|
|
1530
|
+
.sl-rs { border-radius: 4px; }
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1533
|
+
---
|
|
1534
|
+
|
|
1535
|
+
### Offline & Reconnect
|
|
1536
|
+
|
|
1537
|
+
When a peer disconnects and reconnects:
|
|
1538
|
+
|
|
1539
|
+
1. The transport fires a `connect` event with a **state vector** — a map of `siteId → clock` representing everything the reconnecting peer already has.
|
|
1540
|
+
2. The local `RGADocument` computes the **diff**: operations the peer is missing.
|
|
1541
|
+
3. Only the missing ops are retransmitted — not the full document history.
|
|
1542
|
+
|
|
1543
|
+
This makes reconnect efficient even for long editing sessions.
|
|
1544
|
+
|
|
1545
|
+
While disconnected, any local edits are stored in the **send queue** and flushed automatically once the socket reopens.
|
|
1546
|
+
|
|
1547
|
+
---
|
|
1548
|
+
|
|
1549
|
+
### Playground Collab Panel
|
|
1550
|
+
|
|
1551
|
+
The interactive playground (`npm run dev`) includes a dedicated **Collaboration** panel in the left sidebar:
|
|
1552
|
+
|
|
1553
|
+
- **Enable / disable** toggle — connect or disconnect with one click
|
|
1554
|
+
- **Display name** — the name shown in your cursor label on other peers' screens
|
|
1555
|
+
- **Transport selector** — `Local` / `BroadcastChannel` / `WebSocket`
|
|
1556
|
+
- **Room ID** — shared room name (all peers must use the same room)
|
|
1557
|
+
- **WebSocket URL** — shown only when the WebSocket transport is selected
|
|
1558
|
+
- **Status** — live `connecting` / `connected` / `disconnected` indicator
|
|
1559
|
+
- **Connected peers** — colored avatar chips with each peer's display name
|
|
1560
|
+
|
|
1561
|
+
The playground CRDT demo section also shows a live **Alice ↔ Bob** split view — both editors on the same page using `LocalTransport` — so you can see remote cursors and selections in action immediately.
|
|
1562
|
+
|
|
1563
|
+
---
|
|
1564
|
+
|
|
1299
1565
|
## Behavioral Options
|
|
1300
1566
|
|
|
1301
1567
|
### Auto-Close Pairs
|
|
@@ -1385,6 +1651,8 @@ createEditor(container, {
|
|
|
1385
1651
|
showGutter: false, // hide line numbers
|
|
1386
1652
|
showMinimap: false, // hide minimap panel
|
|
1387
1653
|
showStatusBar: false, // hide status bar
|
|
1654
|
+
goToLine: true, // enable Ctrl+G / Cmd+G Go to Line
|
|
1655
|
+
placeholder: 'Start typing…', // shown when document is empty
|
|
1388
1656
|
});
|
|
1389
1657
|
```
|
|
1390
1658
|
|
|
@@ -1402,6 +1670,7 @@ createEditor(container, {
|
|
|
1402
1670
|
| `Ctrl / Cmd + V` | Paste | `readOnly` |
|
|
1403
1671
|
| `Ctrl / Cmd + F` | Open find bar | `find` |
|
|
1404
1672
|
| `Ctrl / Cmd + H` | Open find + replace | `findReplace` |
|
|
1673
|
+
| `Ctrl / Cmd + G` | Open Go to Line prompt | `goToLine` |
|
|
1405
1674
|
| `Ctrl / Cmd + D` | Select next occurrence | `multiCursor` |
|
|
1406
1675
|
| `Ctrl / Cmd + Shift + D` | Duplicate line | `readOnly` |
|
|
1407
1676
|
| `Ctrl / Cmd + K` | Delete line | `readOnly` |
|
|
@@ -1409,12 +1678,16 @@ createEditor(container, {
|
|
|
1409
1678
|
| `Alt / Option + Z` | Toggle word wrap | — |
|
|
1410
1679
|
| `Alt / Option + ↑` | Move current line (or selected block) up | `readOnly` |
|
|
1411
1680
|
| `Alt / Option + ↓` | Move current line (or selected block) down | `readOnly` |
|
|
1412
|
-
| `
|
|
1413
|
-
| `Shift +
|
|
1681
|
+
| `Shift + Alt / Option + ↓` | Duplicate current line (or selected block) down — cursor moves to new copy | `readOnly` |
|
|
1682
|
+
| `Shift + Alt / Option + ↑` | Duplicate current line (or selected block) up — cursor stays on new copy | `readOnly` |
|
|
1683
|
+
| `Tab` | Advance to next snippet tab stop · expand Emmet · expand snippet · indent | `snippetExpansion`, `emmet`, `tabSize` |
|
|
1684
|
+
| `Shift + Tab` | Go to previous snippet tab stop · outdent | `snippetExpansion`, `tabSize` |
|
|
1685
|
+
| `Alt / Option + →` (Mac) | Word skip right (groups by character class: word / punctuation) | `wordSeparators` |
|
|
1686
|
+
| `Alt / Option + ←` (Mac) | Word skip left (groups by character class: word / punctuation) | `wordSeparators` |
|
|
1414
1687
|
| `Alt / Option + Click` | Add extra cursor | `multiCursor` |
|
|
1415
1688
|
| `Double-click` | Select word | `wordSelection`, `wordSeparators` |
|
|
1416
1689
|
| `Triple-click` | Select entire line | — |
|
|
1417
|
-
| `Escape` | Clear selection · close popup · close find · remove extra cursors | — |
|
|
1690
|
+
| `Escape` | Clear selection · exit snippet session · close popup · close find · remove extra cursors | — |
|
|
1418
1691
|
| `↑ ↓ ← →` | Move cursor | — |
|
|
1419
1692
|
| `Cmd + ← →` (Mac) | Start / end of line | — |
|
|
1420
1693
|
| `Cmd + ↑ ↓` (Mac) | Start / end of document | — |
|
|
@@ -1664,6 +1937,12 @@ import type {
|
|
|
1664
1937
|
// Themes
|
|
1665
1938
|
ThemeDefinition,
|
|
1666
1939
|
ThemeTokens,
|
|
1940
|
+
|
|
1941
|
+
// Collaboration
|
|
1942
|
+
CollabConfig,
|
|
1943
|
+
CRDTTransport,
|
|
1944
|
+
AwarenessState,
|
|
1945
|
+
ChannelMessage,
|
|
1667
1946
|
} from 'syncline-editor';
|
|
1668
1947
|
```
|
|
1669
1948
|
|
|
@@ -1774,6 +2053,15 @@ syncline-editor/
|
|
|
1774
2053
|
│ │ ├── document.ts # Document model, undo/redo, selection helpers
|
|
1775
2054
|
│ │ ├── tokeniser.ts # Language-aware syntax tokeniser (zero deps)
|
|
1776
2055
|
│ │ └── wrap-map.ts # Soft-wrap virtual line map
|
|
2056
|
+
│ ├── crdt/
|
|
2057
|
+
│ │ ├── rga.ts # RGA/YATA CRDT document — insert/delete with causal ordering
|
|
2058
|
+
│ │ ├── binding.ts # Two-way bridge between SynclineEditor and RGADocument
|
|
2059
|
+
│ │ ├── awareness.ts # Peer presence heartbeat + garbage collection
|
|
2060
|
+
│ │ ├── transport-local.ts # LocalTransport — same-page event bus
|
|
2061
|
+
│ │ ├── transport-bc.ts # BroadcastChannelTransport — cross-tab
|
|
2062
|
+
│ │ ├── transport-ws.ts # WebSocketTransport — realtime WebSocket + reconnect queue
|
|
2063
|
+
│ │ ├── types.ts # CRDT type definitions (CharId, CRDTOp, CollabConfig, …)
|
|
2064
|
+
│ │ └── index.ts # CRDT barrel export
|
|
1777
2065
|
│ ├── features/
|
|
1778
2066
|
│ │ ├── autocomplete.ts # Completion engine — ranking, filtering, snippet items
|
|
1779
2067
|
│ │ ├── bracket-matcher.ts # Bracket-pair finder
|
|
@@ -1850,6 +2138,7 @@ The playground at `playground/index.html` is a fully self-contained interactive
|
|
|
1850
2138
|
- **Behavior** — auto-close pairs, line comment token, word separators, undo batch window
|
|
1851
2139
|
- **Actions** — buttons for every `executeCommand` and `getValue` / `setValue`
|
|
1852
2140
|
- **Event log** — live feed of all `onChange` / `onCursorChange` / `onSelectionChange` / `onFocus` / `onBlur` events
|
|
2141
|
+
- **Collaboration** — enable/disable collab, choose transport (Local / BroadcastChannel / WebSocket), set display name and room ID, see live peer avatars and connection status
|
|
1853
2142
|
|
|
1854
2143
|
---
|
|
1855
2144
|
|