mirror of
https://github.com/lumehq/lume.git
synced 2025-03-28 18:52:33 +01:00
feat: new post editor
This commit is contained in:
parent
bacfaed48a
commit
0a8eed9a46
@ -52,8 +52,7 @@
|
|||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-i18next": "^15.0.2",
|
"react-i18next": "^15.0.2",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"slate": "^0.103.0",
|
"rich-textarea": "^0.26.3",
|
||||||
"slate-react": "^0.107.1",
|
|
||||||
"use-debounce": "^10.0.3",
|
"use-debounce": "^10.0.3",
|
||||||
"virtua": "^0.33.7"
|
"virtua": "^0.33.7"
|
||||||
},
|
},
|
||||||
|
101
pnpm-lock.yaml
generated
101
pnpm-lock.yaml
generated
@ -134,12 +134,9 @@ importers:
|
|||||||
react-string-replace:
|
react-string-replace:
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
slate:
|
rich-textarea:
|
||||||
specifier: ^0.103.0
|
specifier: ^0.26.3
|
||||||
version: 0.103.0
|
version: 0.26.3(react@19.0.0-rc-d025ddd3-20240722)
|
||||||
slate-react:
|
|
||||||
specifier: ^0.107.1
|
|
||||||
version: 0.107.1(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(slate@0.103.0)
|
|
||||||
use-debounce:
|
use-debounce:
|
||||||
specifier: ^10.0.3
|
specifier: ^10.0.3
|
||||||
version: 10.0.3(react@19.0.0-rc-d025ddd3-20240722)
|
version: 10.0.3(react@19.0.0-rc-d025ddd3-20240722)
|
||||||
@ -725,9 +722,6 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||||
|
|
||||||
'@juggle/resize-observer@3.4.0':
|
|
||||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
|
||||||
|
|
||||||
'@lightninglabs/lnc-core@0.3.1-alpha':
|
'@lightninglabs/lnc-core@0.3.1-alpha':
|
||||||
resolution: {integrity: sha512-I/hThdItLWJ6RU8Z27ZIXhpBS2JJuD3+TjtaQXX2CabaUYXlcN4sk+Kx8N/zG/fk8qZvjlRWum4vHu4ZX554Fg==}
|
resolution: {integrity: sha512-I/hThdItLWJ6RU8Z27ZIXhpBS2JJuD3+TjtaQXX2CabaUYXlcN4sk+Kx8N/zG/fk8qZvjlRWum4vHu4ZX554Fg==}
|
||||||
|
|
||||||
@ -1405,9 +1399,6 @@ packages:
|
|||||||
'@types/estree@1.0.5':
|
'@types/estree@1.0.5':
|
||||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||||
|
|
||||||
'@types/is-hotkey@0.1.10':
|
|
||||||
resolution: {integrity: sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==}
|
|
||||||
|
|
||||||
'@types/istanbul-lib-coverage@2.0.6':
|
'@types/istanbul-lib-coverage@2.0.6':
|
||||||
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
||||||
|
|
||||||
@ -1417,9 +1408,6 @@ packages:
|
|||||||
'@types/istanbul-reports@1.1.2':
|
'@types/istanbul-reports@1.1.2':
|
||||||
resolution: {integrity: sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==}
|
resolution: {integrity: sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==}
|
||||||
|
|
||||||
'@types/lodash@4.17.7':
|
|
||||||
resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==}
|
|
||||||
|
|
||||||
'@types/prop-types@15.7.13':
|
'@types/prop-types@15.7.13':
|
||||||
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
||||||
|
|
||||||
@ -1562,9 +1550,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
compute-scroll-into-view@3.1.0:
|
|
||||||
resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==}
|
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@ -1601,10 +1586,6 @@ packages:
|
|||||||
didyoumean@1.2.2:
|
didyoumean@1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
|
|
||||||
direction@1.0.4:
|
|
||||||
resolution: {integrity: sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
dlv@1.1.3:
|
dlv@1.1.3:
|
||||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||||
|
|
||||||
@ -1762,17 +1743,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-hotkey@0.2.0:
|
|
||||||
resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==}
|
|
||||||
|
|
||||||
is-number@7.0.0:
|
is-number@7.0.0:
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
is-plain-object@5.0.0:
|
|
||||||
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
@ -2094,6 +2068,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
|
|
||||||
|
rich-textarea@0.26.3:
|
||||||
|
resolution: {integrity: sha512-3IGAzvM9yyIOQR5GmV/zoofKfo2KCMy5yecPDNstxTDTaEAOcKSlgQcUXZBSO5jg9RvHAEgSNxqsJu/7ktCiPw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.14.0'
|
||||||
|
|
||||||
rollup@4.22.0:
|
rollup@4.22.0:
|
||||||
resolution: {integrity: sha512-W21MUIFPZ4+O2Je/EU+GP3iz7PH4pVPUXSbEZdatQnxo29+3rsUjgrJmzuAZU24z7yRAnFN6ukxeAhZh/c7hzg==}
|
resolution: {integrity: sha512-W21MUIFPZ4+O2Je/EU+GP3iz7PH4pVPUXSbEZdatQnxo29+3rsUjgrJmzuAZU24z7yRAnFN6ukxeAhZh/c7hzg==}
|
||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
@ -2105,9 +2084,6 @@ packages:
|
|||||||
scheduler@0.25.0-rc-d025ddd3-20240722:
|
scheduler@0.25.0-rc-d025ddd3-20240722:
|
||||||
resolution: {integrity: sha512-W+CjyTUXoOf/l6b2C9uWAFA696ib1s40vKoLnVQ7o34Cgi9t18mJ7ak4AiVsKBy4pibxZAlmAZJvlKr2ra2p0w==}
|
resolution: {integrity: sha512-W+CjyTUXoOf/l6b2C9uWAFA696ib1s40vKoLnVQ7o34Cgi9t18mJ7ak4AiVsKBy4pibxZAlmAZJvlKr2ra2p0w==}
|
||||||
|
|
||||||
scroll-into-view-if-needed@3.1.0:
|
|
||||||
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
|
|
||||||
|
|
||||||
semver@6.3.1:
|
semver@6.3.1:
|
||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -2124,16 +2100,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
slate-react@0.107.1:
|
|
||||||
resolution: {integrity: sha512-CDIFzeSkTqwOaFHIxRg4MnOsv0Ml8/PoaWiM5zL5hvDYFqVXQUEhMNQqpPEFTWJ5xVLzEv/rd9N3WloiCyEWYQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=18.2.0'
|
|
||||||
react-dom: '>=18.2.0'
|
|
||||||
slate: '>=0.99.0'
|
|
||||||
|
|
||||||
slate@0.103.0:
|
|
||||||
resolution: {integrity: sha512-eCUOVqUpADYMZ59O37QQvUdnFG+8rin0OGQAXNHvHbQeVJ67Bu0spQbcy621vtf8GQUXTEQBlk6OP9atwwob4w==}
|
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -2204,9 +2170,6 @@ packages:
|
|||||||
thenify@3.3.1:
|
thenify@3.3.1:
|
||||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||||
|
|
||||||
tiny-invariant@1.3.1:
|
|
||||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
|
|
||||||
|
|
||||||
tiny-invariant@1.3.3:
|
tiny-invariant@1.3.3:
|
||||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||||
|
|
||||||
@ -2834,8 +2797,6 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
'@juggle/resize-observer@3.4.0': {}
|
|
||||||
|
|
||||||
'@lightninglabs/lnc-core@0.3.1-alpha': {}
|
'@lightninglabs/lnc-core@0.3.1-alpha': {}
|
||||||
|
|
||||||
'@lightninglabs/lnc-web@0.3.1-alpha':
|
'@lightninglabs/lnc-web@0.3.1-alpha':
|
||||||
@ -3470,8 +3431,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.5': {}
|
'@types/estree@1.0.5': {}
|
||||||
|
|
||||||
'@types/is-hotkey@0.1.10': {}
|
|
||||||
|
|
||||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||||
|
|
||||||
'@types/istanbul-lib-report@3.0.3':
|
'@types/istanbul-lib-report@3.0.3':
|
||||||
@ -3483,8 +3442,6 @@ snapshots:
|
|||||||
'@types/istanbul-lib-coverage': 2.0.6
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
'@types/istanbul-lib-report': 3.0.3
|
'@types/istanbul-lib-report': 3.0.3
|
||||||
|
|
||||||
'@types/lodash@4.17.7': {}
|
|
||||||
|
|
||||||
'@types/prop-types@15.7.13': {}
|
'@types/prop-types@15.7.13': {}
|
||||||
|
|
||||||
'@types/react@18.3.8':
|
'@types/react@18.3.8':
|
||||||
@ -3639,8 +3596,6 @@ snapshots:
|
|||||||
|
|
||||||
commander@4.1.1: {}
|
commander@4.1.1: {}
|
||||||
|
|
||||||
compute-scroll-into-view@3.1.0: {}
|
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cross-spawn@7.0.3:
|
cross-spawn@7.0.3:
|
||||||
@ -3665,8 +3620,6 @@ snapshots:
|
|||||||
|
|
||||||
didyoumean@1.2.2: {}
|
didyoumean@1.2.2: {}
|
||||||
|
|
||||||
direction@1.0.4: {}
|
|
||||||
|
|
||||||
dlv@1.1.3: {}
|
dlv@1.1.3: {}
|
||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
eastasianwidth@0.2.0: {}
|
||||||
@ -3829,7 +3782,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.6
|
'@babel/runtime': 7.25.6
|
||||||
|
|
||||||
immer@10.1.1: {}
|
immer@10.1.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
invariant@2.2.4:
|
invariant@2.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3851,12 +3805,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob: 2.1.1
|
is-extglob: 2.1.1
|
||||||
|
|
||||||
is-hotkey@0.2.0: {}
|
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
is-plain-object@5.0.0: {}
|
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
jackspeak@3.4.3:
|
jackspeak@3.4.3:
|
||||||
@ -4118,6 +4068,10 @@ snapshots:
|
|||||||
|
|
||||||
reusify@1.0.4: {}
|
reusify@1.0.4: {}
|
||||||
|
|
||||||
|
rich-textarea@0.26.3(react@19.0.0-rc-d025ddd3-20240722):
|
||||||
|
dependencies:
|
||||||
|
react: 19.0.0-rc-d025ddd3-20240722
|
||||||
|
|
||||||
rollup@4.22.0:
|
rollup@4.22.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
@ -4146,10 +4100,6 @@ snapshots:
|
|||||||
|
|
||||||
scheduler@0.25.0-rc-d025ddd3-20240722: {}
|
scheduler@0.25.0-rc-d025ddd3-20240722: {}
|
||||||
|
|
||||||
scroll-into-view-if-needed@3.1.0:
|
|
||||||
dependencies:
|
|
||||||
compute-scroll-into-view: 3.1.0
|
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
@ -4160,27 +4110,6 @@ snapshots:
|
|||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
slate-react@0.107.1(react-dom@19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722))(react@19.0.0-rc-d025ddd3-20240722)(slate@0.103.0):
|
|
||||||
dependencies:
|
|
||||||
'@juggle/resize-observer': 3.4.0
|
|
||||||
'@types/is-hotkey': 0.1.10
|
|
||||||
'@types/lodash': 4.17.7
|
|
||||||
direction: 1.0.4
|
|
||||||
is-hotkey: 0.2.0
|
|
||||||
is-plain-object: 5.0.0
|
|
||||||
lodash: 4.17.21
|
|
||||||
react: 19.0.0-rc-d025ddd3-20240722
|
|
||||||
react-dom: 19.0.0-rc-d025ddd3-20240722(react@19.0.0-rc-d025ddd3-20240722)
|
|
||||||
scroll-into-view-if-needed: 3.1.0
|
|
||||||
slate: 0.103.0
|
|
||||||
tiny-invariant: 1.3.1
|
|
||||||
|
|
||||||
slate@0.103.0:
|
|
||||||
dependencies:
|
|
||||||
immer: 10.1.1
|
|
||||||
is-plain-object: 5.0.0
|
|
||||||
tiny-warning: 1.0.3
|
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
source-map@0.5.7: {}
|
source-map@0.5.7: {}
|
||||||
@ -4272,8 +4201,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
any-promise: 1.3.0
|
||||||
|
|
||||||
tiny-invariant@1.3.1: {}
|
|
||||||
|
|
||||||
tiny-invariant@1.3.3: {}
|
tiny-invariant@1.3.3: {}
|
||||||
|
|
||||||
tiny-warning@1.0.3: {}
|
tiny-warning@1.0.3: {}
|
||||||
|
@ -20,6 +20,14 @@ pub struct Profile {
|
|||||||
website: Option<String>,
|
website: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||||
|
pub struct Mention {
|
||||||
|
pubkey: String,
|
||||||
|
avatar: String,
|
||||||
|
display_name: String,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<String, String> {
|
pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
@ -195,6 +203,36 @@ pub async fn toggle_contact(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn get_mention_list(state: State<'_, Nostr>) -> Result<Vec<Mention>, String> {
|
||||||
|
let client = &state.client;
|
||||||
|
let filter = Filter::new().kind(Kind::Metadata);
|
||||||
|
|
||||||
|
let events = client
|
||||||
|
.database()
|
||||||
|
.query(vec![filter])
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let data: Vec<Mention> = events
|
||||||
|
.iter()
|
||||||
|
.map(|event| {
|
||||||
|
let pubkey = event.pubkey.to_bech32().unwrap();
|
||||||
|
let metadata = Metadata::from_json(&event.content).unwrap_or(Metadata::new());
|
||||||
|
|
||||||
|
Mention {
|
||||||
|
pubkey,
|
||||||
|
avatar: metadata.picture.unwrap_or_else(|| "".to_string()),
|
||||||
|
display_name: metadata.display_name.unwrap_or_else(|| "".to_string()),
|
||||||
|
name: metadata.name.unwrap_or_else(|| "".to_string()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn set_lume_store(
|
pub async fn set_lume_store(
|
||||||
|
@ -115,6 +115,7 @@ fn main() {
|
|||||||
is_contact_list_empty,
|
is_contact_list_empty,
|
||||||
check_contact,
|
check_contact,
|
||||||
toggle_contact,
|
toggle_contact,
|
||||||
|
get_mention_list,
|
||||||
get_lume_store,
|
get_lume_store,
|
||||||
set_lume_store,
|
set_lume_store,
|
||||||
set_wallet,
|
set_wallet,
|
||||||
|
@ -160,6 +160,14 @@ async toggleContact(id: string, alias: string | null) : Promise<Result<string, s
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getMentionList() : Promise<Result<Mention[], string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("get_mention_list") };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async getLumeStore(key: string) : Promise<Result<string, string>> {
|
async getLumeStore(key: string) : Promise<Result<string, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_lume_store", { key }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_lume_store", { key }) };
|
||||||
@ -466,6 +474,7 @@ subscription: "subscription"
|
|||||||
/** user-defined types **/
|
/** user-defined types **/
|
||||||
|
|
||||||
export type Column = { label: string; url: string; x: number; y: number; width: number; height: number }
|
export type Column = { label: string; url: string; x: number; y: number; width: number; height: number }
|
||||||
|
export type Mention = { pubkey: string; avatar: string; display_name: string; name: string }
|
||||||
export type Meta = { content: string; images: string[]; videos: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
export type Meta = { content: string; images: string[]; videos: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
||||||
export type NewSettings = Settings
|
export type NewSettings = Settings
|
||||||
export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null }
|
export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null }
|
||||||
|
@ -15,8 +15,6 @@ import dayjs from "dayjs";
|
|||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
import updateLocale from "dayjs/plugin/updateLocale";
|
import updateLocale from "dayjs/plugin/updateLocale";
|
||||||
import { decode } from "light-bolt11-decoder";
|
import { decode } from "light-bolt11-decoder";
|
||||||
import { type BaseEditor, Transforms } from "slate";
|
|
||||||
import { ReactEditor } from "slate-react";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import type { RichEvent, Settings } from "./commands.gen";
|
import type { RichEvent, Settings } from "./commands.gen";
|
||||||
import { LumeEvent } from "./system";
|
import { LumeEvent } from "./system";
|
||||||
@ -59,51 +57,6 @@ export const isImageUrl = (url: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertImage = (editor: ReactEditor | BaseEditor, url: string) => {
|
|
||||||
const text = { text: "" };
|
|
||||||
const image = [
|
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
url,
|
|
||||||
children: [text],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const extraText = [
|
|
||||||
{
|
|
||||||
type: "paragraph",
|
|
||||||
children: [text],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// @ts-ignore, idk
|
|
||||||
ReactEditor.focus(editor);
|
|
||||||
Transforms.insertNodes(editor, image);
|
|
||||||
Transforms.insertNodes(editor, extraText);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const insertNostrEvent = (
|
|
||||||
editor: ReactEditor | BaseEditor,
|
|
||||||
eventId: string,
|
|
||||||
) => {
|
|
||||||
const text = { text: "" };
|
|
||||||
const event = [
|
|
||||||
{
|
|
||||||
type: "event",
|
|
||||||
eventId: `nostr:${eventId}`,
|
|
||||||
children: [text],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const extraText = [
|
|
||||||
{
|
|
||||||
type: "paragraph",
|
|
||||||
children: [text],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
Transforms.insertNodes(editor, event);
|
|
||||||
Transforms.insertNodes(editor, extraText);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function formatCreatedAt(time: number, message = false) {
|
export function formatCreatedAt(time: number, message = false) {
|
||||||
let formated: string;
|
let formated: string;
|
||||||
|
|
||||||
@ -257,18 +210,16 @@ export async function upload(filePath?: string) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const selected =
|
const selected =
|
||||||
filePath ||
|
filePath ??
|
||||||
(
|
(await open({
|
||||||
await open({
|
multiple: false,
|
||||||
multiple: false,
|
filters: [
|
||||||
filters: [
|
{
|
||||||
{
|
name: "Media",
|
||||||
name: "Media",
|
extensions: allowExts,
|
||||||
extensions: allowExts,
|
},
|
||||||
},
|
],
|
||||||
],
|
}));
|
||||||
})
|
|
||||||
).path;
|
|
||||||
|
|
||||||
// User cancelled action
|
// User cancelled action
|
||||||
if (!selected) return null;
|
if (!selected) return null;
|
||||||
@ -331,6 +282,7 @@ export const appSettings = new Store<Settings>({
|
|||||||
image_resize_service: "https://wsrv.nl",
|
image_resize_service: "https://wsrv.nl",
|
||||||
use_relay_hint: true,
|
use_relay_hint: true,
|
||||||
content_warning: true,
|
content_warning: true,
|
||||||
|
trusted_only: true,
|
||||||
display_avatar: true,
|
display_avatar: true,
|
||||||
display_zap_button: true,
|
display_zap_button: true,
|
||||||
display_repost_button: true,
|
display_repost_button: true,
|
||||||
|
@ -1,20 +1,31 @@
|
|||||||
import { insertImage, isImagePath, upload } from "@/commons";
|
import { isImagePath, upload } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { Images } from "@phosphor-icons/react";
|
import { Images } from "@phosphor-icons/react";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useEffect, useTransition } from "react";
|
import {
|
||||||
import { useSlateStatic } from "slate-react";
|
type Dispatch,
|
||||||
|
type SetStateAction,
|
||||||
|
useEffect,
|
||||||
|
useTransition,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
export function MediaButton() {
|
export function MediaButton({
|
||||||
const editor = useSlateStatic();
|
setText,
|
||||||
|
setAttaches,
|
||||||
|
}: {
|
||||||
|
setText: Dispatch<SetStateAction<string>>;
|
||||||
|
setAttaches: Dispatch<SetStateAction<string[]>>;
|
||||||
|
}) {
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const uploadMedia = () => {
|
const uploadMedia = () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
const image = await upload();
|
const image = await upload();
|
||||||
return insertImage(editor, image);
|
setText((prev) => `${prev}\n${image}`);
|
||||||
|
setAttaches((prev) => [...prev, image]);
|
||||||
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await message(String(e), { title: "Upload", kind: "error" });
|
await message(String(e), { title: "Upload", kind: "error" });
|
||||||
return;
|
return;
|
||||||
@ -32,7 +43,8 @@ export function MediaButton() {
|
|||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (isImagePath(item)) {
|
if (isImagePath(item)) {
|
||||||
const image = await upload(item);
|
const image = await upload(item);
|
||||||
insertImage(editor, image);
|
setText((prev) => `${prev}\n${image}`);
|
||||||
|
setAttaches((prev) => [...prev, image]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,20 @@
|
|||||||
import { cn, insertImage, insertNostrEvent, isImageUrl } from "@/commons";
|
// @ts-nocheck
|
||||||
|
import { type Mention, commands } from "@/commands.gen";
|
||||||
|
import { cn } from "@/commons";
|
||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import { Note } from "@/components/note";
|
import { Note } from "@/components/note";
|
||||||
import { MentionNote } from "@/components/note/mentions/note";
|
|
||||||
import { User } from "@/components/user";
|
import { User } from "@/components/user";
|
||||||
import { LumeEvent, useEvent } from "@/system";
|
import { LumeEvent, useEvent } from "@/system";
|
||||||
import { Feather } from "@phosphor-icons/react";
|
import { Feather } from "@phosphor-icons/react";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useRef, useState, useTransition } from "react";
|
||||||
import { type Descendant, Node, Transforms, createEditor } from "slate";
|
import { createPortal } from "react-dom";
|
||||||
import {
|
import {
|
||||||
Editable,
|
RichTextarea,
|
||||||
ReactEditor,
|
type RichTextareaHandle,
|
||||||
Slate,
|
createRegexRenderer,
|
||||||
useFocused,
|
} from "rich-textarea";
|
||||||
useSelected,
|
|
||||||
useSlateStatic,
|
|
||||||
withReact,
|
|
||||||
} from "slate-react";
|
|
||||||
import { MediaButton } from "./-components/media";
|
import { MediaButton } from "./-components/media";
|
||||||
import { PowButton } from "./-components/pow";
|
import { PowButton } from "./-components/pow";
|
||||||
import { WarningButton } from "./-components/warning";
|
import { WarningButton } from "./-components/warning";
|
||||||
@ -27,11 +24,39 @@ type EditorSearch = {
|
|||||||
quote: string;
|
quote: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EditorElement = {
|
const MENTION_REG = /\B@([\-+\w]*)$/;
|
||||||
type: string;
|
const MAX_LIST_LENGTH = 5;
|
||||||
children: Descendant[];
|
|
||||||
eventId?: string;
|
const renderer = createRegexRenderer([
|
||||||
};
|
[
|
||||||
|
/https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+/g,
|
||||||
|
({ children, key, value }) => (
|
||||||
|
<a
|
||||||
|
key={key}
|
||||||
|
href={value}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
/(?:^|\W)nostr:(\w+)(?!\w)/g,
|
||||||
|
({ children, key, value }) => (
|
||||||
|
<a
|
||||||
|
key={key}
|
||||||
|
href={value}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
export const Route = createFileRoute("/editor/")({
|
export const Route = createFileRoute("/editor/")({
|
||||||
validateSearch: (search: Record<string, string>): EditorSearch => {
|
validateSearch: (search: Record<string, string>): EditorSearch => {
|
||||||
@ -40,201 +65,295 @@ export const Route = createFileRoute("/editor/")({
|
|||||||
quote: search.quote,
|
quote: search.quote,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
beforeLoad: ({ search }) => {
|
beforeLoad: async ({ search }) => {
|
||||||
let initialValue: EditorElement[];
|
let users: Mention[] = [];
|
||||||
|
let initialValue: string;
|
||||||
|
|
||||||
if (search?.quote?.length) {
|
if (search?.quote?.length) {
|
||||||
const eventId = nip19.noteEncode(search.quote);
|
initialValue = `\nnostr:${nip19.noteEncode(search.quote)}`;
|
||||||
initialValue = [
|
|
||||||
{
|
|
||||||
type: "paragraph",
|
|
||||||
children: [{ text: "" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "event",
|
|
||||||
eventId: `nostr:${eventId}`,
|
|
||||||
children: [{ text: "" }],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
initialValue = [
|
initialValue = "";
|
||||||
{
|
|
||||||
type: "paragraph",
|
|
||||||
children: [{ text: "" }],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { initialValue };
|
const res = await commands.getMentionList();
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
users = res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { users, initialValue };
|
||||||
},
|
},
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { reply_to } = Route.useSearch();
|
const { reply_to } = Route.useSearch();
|
||||||
const { initialValue } = Route.useRouteContext();
|
const { users, initialValue } = Route.useRouteContext();
|
||||||
|
|
||||||
const [editorValue, setEditorValue] = useState<EditorElement[]>(null);
|
const [isPending, startTransition] = useTransition();
|
||||||
const [loading, setLoading] = useState(false);
|
const [text, setText] = useState("");
|
||||||
|
const [attaches, setAttaches] = useState<string[]>(null);
|
||||||
const [warning, setWarning] = useState({ enable: false, reason: "" });
|
const [warning, setWarning] = useState({ enable: false, reason: "" });
|
||||||
const [difficulty, setDifficulty] = useState({ enable: false, num: 21 });
|
const [difficulty, setDifficulty] = useState({ enable: false, num: 21 });
|
||||||
const [editor] = useState(() =>
|
const [index, setIndex] = useState<number>(0);
|
||||||
withMentions(withNostrEvent(withImages(withReact(createEditor())))),
|
const [pos, setPos] = useState<{
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
caret: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const ref = useRef<RichTextareaHandle>(null);
|
||||||
|
const targetText = pos ? text.slice(0, pos.caret) : text;
|
||||||
|
const match = pos && targetText.match(MENTION_REG);
|
||||||
|
const name = match?.[1] ?? "";
|
||||||
|
const filtered = useMemo(
|
||||||
|
() =>
|
||||||
|
users
|
||||||
|
.filter((u) => u.name.toLowerCase().startsWith(name.toLowerCase()))
|
||||||
|
.slice(0, MAX_LIST_LENGTH),
|
||||||
|
[name],
|
||||||
);
|
);
|
||||||
|
|
||||||
const reset = () => {
|
const insert = (i: number) => {
|
||||||
// @ts-expect-error, backlog
|
if (!ref.current || !pos) return;
|
||||||
editor.children = [{ type: "paragraph", children: [{ text: "" }] }];
|
|
||||||
setEditorValue([{ type: "paragraph", children: [{ text: "" }] }]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const serialize = (nodes: Descendant[]) => {
|
const selected = filtered[i];
|
||||||
return nodes
|
|
||||||
.map((n) => {
|
|
||||||
// @ts-expect-error, backlog
|
|
||||||
if (n.type === "image") return n.url;
|
|
||||||
// @ts-expect-error, backlog
|
|
||||||
if (n.type === "event") return n.eventId;
|
|
||||||
|
|
||||||
// @ts-expect-error, backlog
|
ref.current.setRangeText(
|
||||||
if (n.children.length) {
|
`nostr:${selected.pubkey} `,
|
||||||
// @ts-expect-error, backlog
|
pos.caret - name.length - 1,
|
||||||
return n.children
|
pos.caret,
|
||||||
.map((n) => {
|
"end",
|
||||||
if (n.type === "mention") return n.npub;
|
);
|
||||||
return Node.string(n).trim();
|
|
||||||
})
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Node.string(n);
|
setPos(null);
|
||||||
})
|
setIndex(0);
|
||||||
.join("\n");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const publish = async () => {
|
const publish = async () => {
|
||||||
try {
|
startTransition(async () => {
|
||||||
// start loading
|
try {
|
||||||
setLoading(true);
|
const content = text.trim();
|
||||||
|
|
||||||
const content = serialize(editor.children);
|
await LumeEvent.publish(
|
||||||
const eventId = await LumeEvent.publish(
|
content,
|
||||||
content,
|
warning.enable && warning.reason.length ? warning.reason : null,
|
||||||
warning.enable && warning.reason.length ? warning.reason : null,
|
difficulty.num,
|
||||||
difficulty.enable && difficulty.num > 0 ? difficulty.num : null,
|
reply_to,
|
||||||
reply_to,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (eventId) {
|
setText("");
|
||||||
// stop loading
|
} catch {
|
||||||
setLoading(false);
|
return;
|
||||||
// reset form
|
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
});
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditorValue(initialValue);
|
if (initialValue?.length) {
|
||||||
|
setText(initialValue);
|
||||||
|
}
|
||||||
}, [initialValue]);
|
}, [initialValue]);
|
||||||
|
|
||||||
if (!editorValue) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full">
|
<div className="flex flex-col w-full h-full">
|
||||||
<Slate editor={editor} initialValue={editorValue}>
|
<div data-tauri-drag-region className="h-11 shrink-0" />
|
||||||
<div data-tauri-drag-region className="h-11 shrink-0" />
|
<div className="flex flex-col flex-1 overflow-y-auto">
|
||||||
<div className="flex flex-col flex-1 overflow-y-auto">
|
{reply_to?.length ? (
|
||||||
{reply_to?.length ? (
|
<div className="flex flex-col gap-2 px-3.5 pb-3 border-b border-black/5 dark:border-white/5">
|
||||||
<div className="flex flex-col gap-2 px-3.5 pb-3 border-b border-black/5 dark:border-white/5">
|
<span className="text-sm font-semibold">Reply to:</span>
|
||||||
<span className="text-sm font-semibold">Reply to:</span>
|
<EmbedNote id={reply_to} />
|
||||||
<ChildNote id={reply_to} />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="px-4 py-4 overflow-y-auto">
|
|
||||||
<Editable
|
|
||||||
key={JSON.stringify(editorValue)}
|
|
||||||
autoFocus={true}
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect="none"
|
|
||||||
spellCheck={false}
|
|
||||||
renderElement={(props) => <Element {...props} />}
|
|
||||||
placeholder={
|
|
||||||
reply_to ? "Type your reply..." : "What're you up to?"
|
|
||||||
}
|
|
||||||
className="focus:outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{warning.enable ? (
|
|
||||||
<div className="flex items-center w-full px-4 border-t h-11 shrink-0 border-black/5 dark:border-white/5">
|
|
||||||
<span className="text-sm shrink-0 text-black/50 dark:text-white/50">
|
|
||||||
Reason:
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="NSFW..."
|
|
||||||
value={warning.reason}
|
|
||||||
onChange={(e) =>
|
|
||||||
setWarning((prev) => ({ ...prev, reason: e.target.value }))
|
|
||||||
}
|
|
||||||
className="flex-1 text-sm bg-transparent border-none focus:outline-none focus:ring-0 placeholder:text-black/50 dark:placeholder:text-white/50"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{difficulty.enable ? (
|
<div className="p-4 overflow-y-auto h-full">
|
||||||
<div className="flex items-center w-full px-4 border-t h-11 shrink-0 border-black/5 dark:border-white/5">
|
<RichTextarea
|
||||||
<span className="text-sm shrink-0 text-black/50 dark:text-white/50">
|
ref={ref}
|
||||||
Difficulty:
|
value={text}
|
||||||
</span>
|
placeholder={reply_to ? "Type your reply..." : "What're you up to?"}
|
||||||
<input
|
style={{ width: "100%", height: "100%" }}
|
||||||
type="text"
|
className="text-[15px] leading-normal resize-none border-none focus:outline-none focus:ring-0 placeholder:text-neutral-500 placeholder:pt-[1.5px] placeholder:pl-2"
|
||||||
inputMode="numeric"
|
onChange={(e) => setText(e.target.value)}
|
||||||
pattern="[0-9]"
|
onKeyDown={(e) => {
|
||||||
onKeyDown={(event) => {
|
if (!pos || !filtered.length) return;
|
||||||
if (!/[0-9]/.test(event.key)) {
|
switch (e.code) {
|
||||||
event.preventDefault();
|
case "ArrowUp": {
|
||||||
|
e.preventDefault();
|
||||||
|
const nextIndex =
|
||||||
|
index <= 0 ? filtered.length - 1 : index - 1;
|
||||||
|
setIndex(nextIndex);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}}
|
case "ArrowDown": {
|
||||||
placeholder="21"
|
e.preventDefault();
|
||||||
defaultValue={difficulty.num}
|
const prevIndex =
|
||||||
onChange={(e) =>
|
index >= filtered.length - 1 ? 0 : index + 1;
|
||||||
setWarning((prev) => ({ ...prev, num: Number(e.target.value) }))
|
setIndex(prevIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Enter":
|
||||||
|
e.preventDefault();
|
||||||
|
insert(index);
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
e.preventDefault();
|
||||||
|
setPos(null);
|
||||||
|
setIndex(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
className="flex-1 text-sm bg-transparent border-none focus:outline-none focus:ring-0 placeholder:text-black/50 dark:placeholder:text-white/50"
|
}}
|
||||||
/>
|
onSelectionChange={(r) => {
|
||||||
</div>
|
if (
|
||||||
) : null}
|
r.focused &&
|
||||||
<div
|
MENTION_REG.test(text.slice(0, r.selectionStart))
|
||||||
data-tauri-drag-region
|
) {
|
||||||
className="flex items-center w-full h-16 gap-4 px-4 border-t divide-x divide-black/5 dark:divide-white/5 shrink-0 border-black/5 dark:border-white/5"
|
setPos({
|
||||||
>
|
top: r.top + r.height,
|
||||||
<button
|
left: r.left,
|
||||||
type="button"
|
caret: r.selectionStart,
|
||||||
onClick={() => publish()}
|
});
|
||||||
className="inline-flex items-center justify-center h-8 gap-1 px-2.5 text-sm font-medium rounded-lg bg-black/10 w-max hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
|
setIndex(0);
|
||||||
|
} else {
|
||||||
|
setPos(null);
|
||||||
|
setIndex(0);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{renderer}
|
||||||
<Spinner className="size-4" />
|
</RichTextarea>
|
||||||
) : (
|
{pos
|
||||||
<Feather className="size-4" weight="fill" />
|
? createPortal(
|
||||||
)}
|
<Menu
|
||||||
Publish
|
top={pos.top}
|
||||||
</button>
|
left={pos.left}
|
||||||
<div className="inline-flex items-center flex-1 gap-2 pl-4">
|
users={filtered}
|
||||||
<MediaButton />
|
index={index}
|
||||||
<WarningButton setWarning={setWarning} />
|
insert={insert}
|
||||||
<PowButton setDifficulty={setDifficulty} />
|
/>,
|
||||||
</div>
|
document.body,
|
||||||
|
)
|
||||||
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</Slate>
|
</div>
|
||||||
|
{warning.enable ? (
|
||||||
|
<div className="flex items-center w-full px-4 border-t h-11 shrink-0 border-black/5 dark:border-white/5">
|
||||||
|
<span className="text-sm shrink-0 text-black/50 dark:text-white/50">
|
||||||
|
Reason:
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="NSFW..."
|
||||||
|
value={warning.reason}
|
||||||
|
onChange={(e) =>
|
||||||
|
setWarning((prev) => ({ ...prev, reason: e.target.value }))
|
||||||
|
}
|
||||||
|
className="flex-1 text-sm bg-transparent border-none focus:outline-none focus:ring-0 placeholder:text-black/50 dark:placeholder:text-white/50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{difficulty.enable ? (
|
||||||
|
<div className="flex items-center w-full px-4 border-t h-11 shrink-0 border-black/5 dark:border-white/5">
|
||||||
|
<span className="text-sm shrink-0 text-black/50 dark:text-white/50">
|
||||||
|
Difficulty:
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
inputMode="numeric"
|
||||||
|
pattern="[0-9]"
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (!/[0-9]/.test(event.key)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="21"
|
||||||
|
defaultValue={difficulty.num}
|
||||||
|
onChange={(e) =>
|
||||||
|
setWarning((prev) => ({ ...prev, num: Number(e.target.value) }))
|
||||||
|
}
|
||||||
|
className="flex-1 text-sm bg-transparent border-none focus:outline-none focus:ring-0 placeholder:text-black/50 dark:placeholder:text-white/50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="flex items-center w-full h-16 gap-4 px-4 border-t divide-x divide-black/5 dark:divide-white/5 shrink-0 border-black/5 dark:border-white/5"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => publish()}
|
||||||
|
className="inline-flex items-center justify-center h-8 gap-1 px-2.5 text-sm font-medium rounded-lg bg-black/10 w-max hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
|
||||||
|
>
|
||||||
|
{isPending ? (
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
) : (
|
||||||
|
<Feather className="size-4" weight="fill" />
|
||||||
|
)}
|
||||||
|
Publish
|
||||||
|
</button>
|
||||||
|
<div className="inline-flex items-center flex-1 gap-2 pl-4">
|
||||||
|
<MediaButton setText={setText} setAttaches={setAttaches} />
|
||||||
|
<WarningButton setWarning={setWarning} />
|
||||||
|
<PowButton setDifficulty={setDifficulty} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChildNote({ id }: { id: string }) {
|
function Menu({
|
||||||
|
users,
|
||||||
|
index,
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
insert,
|
||||||
|
}: {
|
||||||
|
users: Mention[];
|
||||||
|
index: number;
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
insert: (index: number) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
top: top,
|
||||||
|
left: left,
|
||||||
|
}}
|
||||||
|
className="fixed w-[200px] text-sm bg-white dark:bg-black shadow-lg shadow-neutral-500/20 rounded-lg overflow-hidden"
|
||||||
|
>
|
||||||
|
{users.map((u, i) => (
|
||||||
|
<div
|
||||||
|
key={u.pubkey}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1.5 p-2",
|
||||||
|
index === i ? "bg-neutral-100 dark:bg-neutral-900" : null,
|
||||||
|
)}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
insert(i);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="size-7 shrink-0">
|
||||||
|
{u.avatar?.length ? (
|
||||||
|
<img
|
||||||
|
src={u.avatar}
|
||||||
|
className="size-7 rounded-full outline outline-1 -outline-offset-1 outline-black/15"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="size-7 rounded-full bg-blue-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{u.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmbedNote({ id }: { id: string }) {
|
||||||
const { isLoading, isError, data } = useEvent(id);
|
const { isLoading, isError, data } = useEvent(id);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@ -258,142 +377,3 @@ function ChildNote({ id }: { id: string }) {
|
|||||||
</Note.Provider>
|
</Note.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const withNostrEvent = (editor: ReactEditor) => {
|
|
||||||
const { insertData, isVoid } = editor;
|
|
||||||
|
|
||||||
editor.isVoid = (element) => {
|
|
||||||
// @ts-expect-error, wtf
|
|
||||||
return element.type === "event" ? true : isVoid(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.insertData = (data) => {
|
|
||||||
const text = data.getData("text/plain");
|
|
||||||
|
|
||||||
if (text.startsWith("nevent") || text.startsWith("note")) {
|
|
||||||
insertNostrEvent(editor, text);
|
|
||||||
} else {
|
|
||||||
insertData(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return editor;
|
|
||||||
};
|
|
||||||
|
|
||||||
const withMentions = (editor: ReactEditor) => {
|
|
||||||
const { isInline, isVoid, markableVoid } = editor;
|
|
||||||
|
|
||||||
editor.isInline = (element) => {
|
|
||||||
// @ts-expect-error, wtf
|
|
||||||
return element.type === "mention" ? true : isInline(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.isVoid = (element) => {
|
|
||||||
// @ts-expect-error, wtf
|
|
||||||
return element.type === "mention" ? true : isVoid(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.markableVoid = (element) => {
|
|
||||||
// @ts-expect-error, wtf
|
|
||||||
return element.type === "mention" || markableVoid(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
return editor;
|
|
||||||
};
|
|
||||||
|
|
||||||
const withImages = (editor: ReactEditor) => {
|
|
||||||
const { insertData, isVoid } = editor;
|
|
||||||
|
|
||||||
editor.isVoid = (element) => {
|
|
||||||
// @ts-expect-error, wtf
|
|
||||||
return element.type === "image" ? true : isVoid(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.insertData = (data) => {
|
|
||||||
const text = data.getData("text/plain");
|
|
||||||
|
|
||||||
if (isImageUrl(text)) {
|
|
||||||
insertImage(editor, text);
|
|
||||||
} else {
|
|
||||||
insertData(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return editor;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Image = ({ attributes, element, children }) => {
|
|
||||||
const editor = useSlateStatic();
|
|
||||||
const selected = useSelected();
|
|
||||||
const focused = useFocused();
|
|
||||||
const path = ReactEditor.findPath(editor as ReactEditor, element);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div {...attributes}>
|
|
||||||
{children}
|
|
||||||
<img
|
|
||||||
src={element.url}
|
|
||||||
alt={element.url}
|
|
||||||
className={cn(
|
|
||||||
"my-2 h-auto w-1/2 rounded-lg object-cover ring-2 outline outline-1 -outline-offset-1 outline-black/15",
|
|
||||||
selected && focused ? "ring-blue-500" : "ring-transparent",
|
|
||||||
)}
|
|
||||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
|
||||||
onKeyDown={() => Transforms.removeNodes(editor, { at: path })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Mention = ({ attributes, element }) => {
|
|
||||||
const editor = useSlateStatic();
|
|
||||||
const path = ReactEditor.findPath(editor as ReactEditor, element);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
{...attributes}
|
|
||||||
type="button"
|
|
||||||
contentEditable={false}
|
|
||||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
|
||||||
className="inline-block text-blue-500 align-baseline hover:text-blue-600"
|
|
||||||
>{`@${element.name}`}</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Event = ({ attributes, element, children }) => {
|
|
||||||
const editor = useSlateStatic();
|
|
||||||
const path = ReactEditor.findPath(editor as ReactEditor, element);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div {...attributes}>
|
|
||||||
{children}
|
|
||||||
<div
|
|
||||||
contentEditable={false}
|
|
||||||
className="relative my-2 user-select-none"
|
|
||||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
|
||||||
onKeyDown={() => Transforms.removeNodes(editor, { at: path })}
|
|
||||||
>
|
|
||||||
<MentionNote eventId={element.eventId} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Element = (props) => {
|
|
||||||
const { attributes, children, element } = props;
|
|
||||||
|
|
||||||
switch (element.type) {
|
|
||||||
case "image":
|
|
||||||
return <Image {...props} />;
|
|
||||||
case "mention":
|
|
||||||
return <Mention {...props} />;
|
|
||||||
case "event":
|
|
||||||
return <Event {...props} />;
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<p {...attributes} className="text-[15px]">
|
|
||||||
{children}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user