@filipc77/cowrite 0.6.7 → 0.6.8

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.
@@ -0,0 +1,34 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Shorthand DOM selector.
5
+ * @param {string} sel
6
+ * @returns {HTMLElement}
7
+ */
8
+ export const $ = (sel) => document.querySelector(sel);
9
+
10
+ /**
11
+ * Escape HTML entities to prevent XSS.
12
+ * @param {string} text
13
+ * @returns {string}
14
+ */
15
+ export function escapeHtml(text) {
16
+ const div = document.createElement("div");
17
+ div.textContent = text;
18
+ return div.innerHTML;
19
+ }
20
+
21
+ /**
22
+ * Human-readable relative time.
23
+ * @param {string} iso
24
+ * @returns {string}
25
+ */
26
+ export function timeAgo(iso) {
27
+ const diff = Date.now() - new Date(iso).getTime();
28
+ const mins = Math.floor(diff / 60000);
29
+ if (mins < 1) return "just now";
30
+ if (mins < 60) return `${mins}m ago`;
31
+ const hours = Math.floor(mins / 60);
32
+ if (hours < 24) return `${hours}h ago`;
33
+ return `${Math.floor(hours / 24)}d ago`;
34
+ }
@@ -0,0 +1,63 @@
1
+ // @ts-check
2
+
3
+ /** @typedef {import('../../src/types.js').WSServerMessage} WSServerMessage */
4
+
5
+ import { $ } from './utils.js';
6
+ import { state } from './state.js';
7
+
8
+ /**
9
+ * Send a message over the WebSocket.
10
+ * @param {object} msg
11
+ */
12
+ export function send(msg) {
13
+ if (state.ws && state.ws.readyState === WebSocket.OPEN) {
14
+ state.ws.send(JSON.stringify(msg));
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Initialize WebSocket connection with auto-reconnect.
20
+ * @param {object} handlers
21
+ * @param {(msg: any) => void} handlers.onFileUpdate
22
+ * @param {(msg: any) => void} handlers.onCommentsUpdate
23
+ * @param {(msg: any) => void} handlers.onError
24
+ * @param {() => void} handlers.onOpen
25
+ */
26
+ export function initWebSocket(handlers) {
27
+ const statusEl = $("#status");
28
+
29
+ function connect() {
30
+ const protocol = location.protocol === "https:" ? "wss:" : "ws:";
31
+ state.ws = new WebSocket(`${protocol}//${location.host}`);
32
+
33
+ state.ws.onopen = () => {
34
+ statusEl.innerHTML = '<span class="status-dot"></span>Connected';
35
+ statusEl.className = "status connected";
36
+ handlers.onOpen();
37
+ };
38
+
39
+ state.ws.onclose = () => {
40
+ statusEl.innerHTML = '<span class="status-dot"></span>Disconnected';
41
+ statusEl.className = "status";
42
+ setTimeout(connect, 2000);
43
+ };
44
+
45
+ state.ws.onmessage = (event) => {
46
+ /** @type {WSServerMessage} */
47
+ const msg = JSON.parse(event.data);
48
+ switch (msg.type) {
49
+ case "file_update":
50
+ handlers.onFileUpdate(msg);
51
+ break;
52
+ case "comments_update":
53
+ handlers.onCommentsUpdate(msg);
54
+ break;
55
+ case "error":
56
+ handlers.onError(msg);
57
+ break;
58
+ }
59
+ };
60
+ }
61
+
62
+ connect();
63
+ }
package/ui/styles.css CHANGED
@@ -793,6 +793,24 @@ body.sidebar-resizing {
793
793
  background: var(--green-bg);
794
794
  }
795
795
 
796
+ /* ---- Orphaned comments ---- */
797
+ .comment-card.orphaned {
798
+ opacity: 0.7;
799
+ }
800
+
801
+ .orphaned-badge {
802
+ display: inline-block;
803
+ font-size: 9px;
804
+ font-weight: 600;
805
+ padding: 2px 8px;
806
+ border-radius: 100px;
807
+ background: rgba(212, 97, 110, 0.1);
808
+ color: var(--red);
809
+ text-transform: uppercase;
810
+ letter-spacing: 0.4px;
811
+ margin-left: 6px;
812
+ }
813
+
796
814
  /* ---- Replies ---- */
797
815
  .comment-replies {
798
816
  margin-top: 12px;
@@ -1510,4 +1528,124 @@ body.sidebar-resizing {
1510
1528
  background: var(--surface-hover);
1511
1529
  }
1512
1530
 
1531
+ /* ---- TipTap / ProseMirror overrides ---- */
1532
+ .ProseMirror {
1533
+ outline: none;
1534
+ min-height: 200px;
1535
+ }
1536
+
1537
+ .ProseMirror p.is-editor-empty:first-child::before {
1538
+ content: attr(data-placeholder);
1539
+ float: left;
1540
+ color: var(--text-faint);
1541
+ pointer-events: none;
1542
+ height: 0;
1543
+ font-style: italic;
1544
+ }
1545
+
1546
+ .ProseMirror table {
1547
+ border-collapse: collapse;
1548
+ width: 100%;
1549
+ margin: 16px 0;
1550
+ }
1551
+
1552
+ .ProseMirror th,
1553
+ .ProseMirror td {
1554
+ border: 1px solid var(--border);
1555
+ padding: 8px 12px;
1556
+ text-align: left;
1557
+ }
1558
+
1559
+ .ProseMirror th {
1560
+ background: var(--surface-hover);
1561
+ font-weight: 600;
1562
+ }
1563
+
1564
+ .ProseMirror ul[data-type="taskList"] {
1565
+ list-style: none;
1566
+ padding-left: 0;
1567
+ }
1568
+
1569
+ .ProseMirror ul[data-type="taskList"] li {
1570
+ display: flex;
1571
+ align-items: flex-start;
1572
+ gap: 8px;
1573
+ }
1574
+
1575
+ .ProseMirror ul[data-type="taskList"] li input[type="checkbox"] {
1576
+ margin-top: 4px;
1577
+ accent-color: var(--accent);
1578
+ }
1579
+
1580
+ .ProseMirror .hljs {
1581
+ background: var(--surface) !important;
1582
+ }
1583
+
1584
+ /* Ensure the TipTap editor inherits markdown-body styles */
1585
+ #fileContent .ProseMirror {
1586
+ font-family: var(--font-sans);
1587
+ color: var(--text);
1588
+ line-height: 1.6;
1589
+ }
1590
+
1591
+ #fileContent .ProseMirror h1 { font-size: 2em; font-weight: 600; margin: 24px 0 16px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
1592
+ #fileContent .ProseMirror h2 { font-size: 1.5em; font-weight: 600; margin: 20px 0 12px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
1593
+ #fileContent .ProseMirror h3 { font-size: 1.25em; font-weight: 600; margin: 16px 0 8px; }
1594
+ #fileContent .ProseMirror h4 { font-size: 1em; font-weight: 600; margin: 12px 0 6px; }
1595
+
1596
+ #fileContent .ProseMirror p { margin: 0 0 16px; }
1597
+ #fileContent .ProseMirror ul,
1598
+ #fileContent .ProseMirror ol { padding-left: 2em; margin: 0 0 16px; }
1599
+ #fileContent .ProseMirror li { margin: 4px 0; }
1600
+ #fileContent .ProseMirror li p { margin: 0; }
1601
+
1602
+ #fileContent .ProseMirror blockquote {
1603
+ border-left: 3px solid var(--border);
1604
+ padding-left: 16px;
1605
+ color: var(--text-dim);
1606
+ margin: 0 0 16px;
1607
+ }
1608
+
1609
+ #fileContent .ProseMirror pre {
1610
+ background: var(--surface);
1611
+ border: 1px solid var(--border);
1612
+ border-radius: 6px;
1613
+ padding: 16px;
1614
+ overflow-x: auto;
1615
+ margin: 0 0 16px;
1616
+ }
1617
+
1618
+ #fileContent .ProseMirror code {
1619
+ font-family: var(--font-mono);
1620
+ font-size: 0.9em;
1621
+ }
1622
+
1623
+ #fileContent .ProseMirror :not(pre) > code {
1624
+ background: var(--surface-hover);
1625
+ padding: 2px 6px;
1626
+ border-radius: 4px;
1627
+ }
1628
+
1629
+ #fileContent .ProseMirror hr {
1630
+ border: none;
1631
+ border-top: 1px solid var(--border);
1632
+ margin: 24px 0;
1633
+ }
1513
1634
 
1635
+ #fileContent .ProseMirror a {
1636
+ color: var(--accent);
1637
+ text-decoration: none;
1638
+ }
1639
+ #fileContent .ProseMirror a:hover {
1640
+ text-decoration: underline;
1641
+ }
1642
+
1643
+ #fileContent .ProseMirror img {
1644
+ max-width: 100%;
1645
+ height: auto;
1646
+ }
1647
+
1648
+ /* Large font size mode for TipTap */
1649
+ body.font-large #fileContent .ProseMirror {
1650
+ font-size: 19px !important;
1651
+ }