mirror of
https://github.com/ruszabarov/obsidian-unit-converter.git
synced 2026-07-22 05:31:40 +00:00
fix: conversion parsing, rendering, and autosuggest edge cases
This commit is contained in:
parent
70ee07b587
commit
f69a9bcffa
13 changed files with 846 additions and 43 deletions
31
README.md
31
README.md
|
|
@ -15,6 +15,7 @@ Use the following syntax within your notes to perform inline conversion:
|
|||
|
||||
- [Autosuggest](#autosuggest)
|
||||
- [Convert units command](#convert-units-command)
|
||||
- [Custom units](#custom-units)
|
||||
|
||||
### Autosuggest
|
||||
|
||||
|
|
@ -28,33 +29,41 @@ You can open a modal to perform a unit conversion. This is mostly done to improv
|
|||
|
||||
<img src="https://github.com/user-attachments/assets/ca168075-8408-41e3-812f-181c511b1585" alt="convert units command example" width="350">
|
||||
|
||||
### Custom units
|
||||
|
||||
You can add custom units in settings. Each custom unit belongs to an existing measure and is defined as a positive factor of a built-in anchor unit.
|
||||
|
||||
## Supported Units
|
||||
|
||||
**Length**: mm, cm, m, in, ft-us, ft, mi
|
||||
**Length**: mm, cm, m, km, in, yd, ft-us, ft, mi
|
||||
|
||||
**Area**: mm2 cm2, m2, ha, km2, in2, ft2, ac, mi2
|
||||
**Area**: mm2, cm2, m2, ha, km2, in2, yd2, ft2, ac, mi2
|
||||
|
||||
**Mass**: mcg, mg, g, kg, oz, lb, mt, t
|
||||
**Mass**: mcg, mg, g, kg, mt, oz, lb, t
|
||||
|
||||
**Volume**: mm3, cm3, ml, l, kl, m3, km3, tsp, Tbs, in3, fl-oz, cup, pnt, qt, gal, ft3, yd3
|
||||
**Volume**: mm3, cm3, ml, cl, dl, l, kl, m3, km3, krm, tsk, msk, kkp, glas, kanna, tsp, Tbs, in3, fl-oz, cup, pnt, qt, gal, ft3, yd3
|
||||
|
||||
**Volume Flow Rate**: mm3/s, cm3/s, ml/s, cl/s, dl/s, l/s, l/min, l/h, kl/s, kl/min, kl/h, m3/s, m3/min, m3/h, km3/s, tsp/s, Tbs/s, in3/s, in3/min, in3/h, fl-oz/s, fl-oz/min, fl-oz/h, cup/s, pnt/s, pnt/min, pnt/h, qt/s, gal/s, gal/min, gal/h, ft3/s, ft3/min, ft3/h, yd3/s, yd3/min, yd3/h'
|
||||
**Volume Flow Rate**: mm3/s, cm3/s, ml/s, cl/s, dl/s, l/s, l/min, l/h, kl/s, kl/min, kl/h, m3/s, m3/min, m3/h, km3/s, tsp/s, Tbs/s, in3/s, in3/min, in3/h, fl-oz/s, fl-oz/min, fl-oz/h, cup/s, pnt/s, pnt/min, pnt/h, qt/s, gal/s, gal/min, gal/h, ft3/s, ft3/min, ft3/h, yd3/s, yd3/min, yd3/h
|
||||
|
||||
**Temperature**: C, F, K, R
|
||||
**Each**: ea, dz
|
||||
|
||||
**Temperature**: C, K, F, R
|
||||
|
||||
**Time**: ns, mu, ms, s, min, h, d, week, month, year
|
||||
|
||||
**Frequency**: Hz, mHz, kHz, MHz, GHz, THz, rpm, deg/s, rad/s
|
||||
**Frequency**: mHz, Hz, kHz, MHz, GHz, THz, rpm, deg/s, rad/s
|
||||
|
||||
**Parts Per**: ppm, ppb, ppt, ppq
|
||||
|
||||
**Speed**: m/s, km/h, m/h, knot, ft/s
|
||||
|
||||
**Pace**: s/m, min/km, s/ft, min/km
|
||||
**Pace**: min/km, s/m, min/mi, s/ft
|
||||
|
||||
**Pressure**: Pa, hPa, kPa, MPa, bar, torr, psi, ksi
|
||||
**Pressure**: Pa, kPa, MPa, hPa, bar, torr, psi, ksi
|
||||
|
||||
**Digital**: b, Kb, Mb, Gb, Tb, B, KB, MB, GB, TB
|
||||
|
||||
**Illuminance**: lx, ft-cd, Parts-Per, ppm, ppb, ppt, ppq
|
||||
**Illuminance**: lx, ft-cd
|
||||
|
||||
**Voltage**: V, mV, kV
|
||||
|
||||
|
|
@ -70,4 +79,4 @@ You can open a modal to perform a unit conversion. This is mostly done to improv
|
|||
|
||||
**Reactive Energy**: VARh, mVARh, kVARh, MVARh, GVARh
|
||||
|
||||
**Angle**: deg, rad, grad, arcmin, arcsec
|
||||
**Angle**: rad, deg, grad, arcmin, arcsec
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
"dev": "rolldown --config rolldown.config.js -w",
|
||||
"build": "rolldown --config rolldown.config.js",
|
||||
"lint": "oxlint",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format": "oxfmt",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
|
|
@ -27,7 +29,8 @@
|
|||
"oxfmt": "^0.49.0",
|
||||
"oxlint": "^1.64.0",
|
||||
"rolldown": "1.0.1",
|
||||
"typescript": "^6.0.3"
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.6"
|
||||
},
|
||||
"packageManager": "pnpm@11.1.3"
|
||||
}
|
||||
|
|
|
|||
502
pnpm-lock.yaml
502
pnpm-lock.yaml
|
|
@ -39,6 +39,9 @@ importers:
|
|||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
vitest:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6(vite@8.0.13)
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -57,6 +60,9 @@ packages:
|
|||
'@emnapi/wasi-threads@1.2.1':
|
||||
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
|
|
@ -411,31 +417,182 @@ packages:
|
|||
'@rolldown/pluginutils@1.0.1':
|
||||
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
'@types/codemirror@5.60.8':
|
||||
resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==}
|
||||
|
||||
'@types/convert-units@2.3.12':
|
||||
resolution: {integrity: sha512-4EaowCOJMy8Cb2nB8XSN61U82VV4qFsFYY2WhKIwXy936aBTp2yWKvMpTCt1g76MDmd+DmVi9YxKSYVrfCQ/yw==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/estree@1.0.9':
|
||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||
|
||||
'@types/tern@0.23.9':
|
||||
resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==}
|
||||
|
||||
'@vitest/expect@4.1.6':
|
||||
resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==}
|
||||
|
||||
'@vitest/mocker@4.1.6':
|
||||
resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@4.1.6':
|
||||
resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==}
|
||||
|
||||
'@vitest/runner@4.1.6':
|
||||
resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==}
|
||||
|
||||
'@vitest/snapshot@4.1.6':
|
||||
resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==}
|
||||
|
||||
'@vitest/spy@4.1.6':
|
||||
resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==}
|
||||
|
||||
'@vitest/utils@4.1.6':
|
||||
resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
chai@6.2.2:
|
||||
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
convert-units@2.3.4:
|
||||
resolution: {integrity: sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==}
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
es-module-lexer@2.1.0:
|
||||
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
expect-type@1.3.0:
|
||||
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
globals@17.6.0:
|
||||
resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.32.0:
|
||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lodash._basebind@2.3.0:
|
||||
resolution: {integrity: sha512-SHqM7YCuJ+BeGTs7lqpWnmdHEeF4MWxS3dksJctHFNxR81FXPOzA4bS5Vs5CpcGTkBpM8FCl+YEbQEblRw8ABg==}
|
||||
|
||||
|
|
@ -493,15 +650,26 @@ packages:
|
|||
lodash.support@2.3.0:
|
||||
resolution: {integrity: sha512-etc7VWbB0U3Iya8ixj2xy4sDBN3jvPX7ODi8iXtn4KkkjNpdngrdc7Vlt5jub/Vgqx6/dWtp7Ml9awhCQPYKGQ==}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
moment@2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
|
||||
nanoid@3.3.12:
|
||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
obsidian@1.12.3:
|
||||
resolution: {integrity: sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==}
|
||||
peerDependencies:
|
||||
'@codemirror/state': 6.5.0
|
||||
'@codemirror/view': 6.38.6
|
||||
|
||||
obug@2.1.1:
|
||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||
|
||||
oxfmt@0.49.0:
|
||||
resolution: {integrity: sha512-IAHFMdlJSWe+oAr65dx22UvjCtV9DBMisAuLnKpDqMQrctzCkGnj3QRwNHm0d+uwSWPalsDF8ZYLz9rh6nH2IQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
|
|
@ -522,18 +690,60 @@ packages:
|
|||
oxlint-tsgolint:
|
||||
optional: true
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.4:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
postcss@8.5.15:
|
||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
rolldown@1.0.1:
|
||||
resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
std-env@4.1.0:
|
||||
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
|
||||
|
||||
style-mod@4.1.3:
|
||||
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
tinyexec@1.1.2:
|
||||
resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.16:
|
||||
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@2.1.0:
|
||||
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
|
|
@ -542,9 +752,98 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
vite@8.0.13:
|
||||
resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
'@vitejs/devtools': ^0.1.18
|
||||
esbuild: ^0.27.0 || ^0.28.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.0
|
||||
sass: ^1.70.0
|
||||
sass-embedded: ^1.70.0
|
||||
stylus: '>=0.54.8'
|
||||
sugarss: ^5.0.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitejs/devtools':
|
||||
optional: true
|
||||
esbuild:
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.6:
|
||||
resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.6
|
||||
'@vitest/browser-preview': 4.1.6
|
||||
'@vitest/browser-webdriverio': 4.1.6
|
||||
'@vitest/coverage-istanbul': 4.1.6
|
||||
'@vitest/coverage-v8': 4.1.6
|
||||
'@vitest/ui': 4.1.6
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser-playwright':
|
||||
optional: true
|
||||
'@vitest/browser-preview':
|
||||
optional: true
|
||||
'@vitest/browser-webdriverio':
|
||||
optional: true
|
||||
'@vitest/coverage-istanbul':
|
||||
optional: true
|
||||
'@vitest/coverage-v8':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@codemirror/state@6.6.0':
|
||||
|
|
@ -574,6 +873,8 @@ snapshots:
|
|||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
|
||||
|
|
@ -750,23 +1051,79 @@ snapshots:
|
|||
|
||||
'@rolldown/pluginutils@1.0.1': {}
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
assertion-error: 2.0.1
|
||||
|
||||
'@types/codemirror@5.60.8':
|
||||
dependencies:
|
||||
'@types/tern': 0.23.9
|
||||
|
||||
'@types/convert-units@2.3.12': {}
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
|
||||
'@types/tern@0.23.9':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
|
||||
'@vitest/expect@4.1.6':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.6
|
||||
'@vitest/utils': 4.1.6
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/mocker@4.1.6(vite@8.0.13)':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.6
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 8.0.13
|
||||
|
||||
'@vitest/pretty-format@4.1.6':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/runner@4.1.6':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.6
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.6':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.6
|
||||
'@vitest/utils': 4.1.6
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@4.1.6': {}
|
||||
|
||||
'@vitest/utils@4.1.6':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.6
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
chai@6.2.2: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
convert-units@2.3.4:
|
||||
dependencies:
|
||||
lodash.foreach: 2.3.0
|
||||
|
|
@ -774,8 +1131,74 @@ snapshots:
|
|||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
es-module-lexer@2.1.0: {}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
|
||||
expect-type@1.3.0: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
globals@17.6.0: {}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss@1.32.0:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64: 1.32.0
|
||||
lightningcss-darwin-arm64: 1.32.0
|
||||
lightningcss-darwin-x64: 1.32.0
|
||||
lightningcss-freebsd-x64: 1.32.0
|
||||
lightningcss-linux-arm-gnueabihf: 1.32.0
|
||||
lightningcss-linux-arm64-gnu: 1.32.0
|
||||
lightningcss-linux-arm64-musl: 1.32.0
|
||||
lightningcss-linux-x64-gnu: 1.32.0
|
||||
lightningcss-linux-x64-musl: 1.32.0
|
||||
lightningcss-win32-arm64-msvc: 1.32.0
|
||||
lightningcss-win32-x64-msvc: 1.32.0
|
||||
|
||||
lodash._basebind@2.3.0:
|
||||
dependencies:
|
||||
lodash._basecreate: 2.3.0
|
||||
|
|
@ -860,8 +1283,14 @@ snapshots:
|
|||
dependencies:
|
||||
lodash._renative: 2.3.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
moment@2.29.4: {}
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
|
||||
obsidian@1.12.3(@codemirror/state@6.6.0)(@codemirror/view@6.43.0):
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
|
|
@ -869,6 +1298,8 @@ snapshots:
|
|||
'@types/codemirror': 5.60.8
|
||||
moment: 2.29.4
|
||||
|
||||
obug@2.1.1: {}
|
||||
|
||||
oxfmt@0.49.0:
|
||||
dependencies:
|
||||
tinypool: 2.1.0
|
||||
|
|
@ -915,6 +1346,18 @@ snapshots:
|
|||
'@oxlint/binding-win32-ia32-msvc': 1.64.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.64.0
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
postcss@8.5.15:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
rolldown@1.0.1:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.130.0
|
||||
|
|
@ -936,13 +1379,72 @@ snapshots:
|
|||
'@rolldown/binding-win32-arm64-msvc': 1.0.1
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.1
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
std-env@4.1.0: {}
|
||||
|
||||
style-mod@4.1.3: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@1.1.2: {}
|
||||
|
||||
tinyglobby@0.2.16:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
tinypool@2.1.0: {}
|
||||
|
||||
tinyrainbow@3.1.0: {}
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
|
||||
typescript@6.0.3: {}
|
||||
|
||||
vite@8.0.13:
|
||||
dependencies:
|
||||
lightningcss: 1.32.0
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.15
|
||||
rolldown: 1.0.1
|
||||
tinyglobby: 0.2.16
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitest@4.1.6(vite@8.0.13):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.6
|
||||
'@vitest/mocker': 4.1.6(vite@8.0.13)
|
||||
'@vitest/pretty-format': 4.1.6
|
||||
'@vitest/runner': 4.1.6
|
||||
'@vitest/snapshot': 4.1.6
|
||||
'@vitest/spy': 4.1.6
|
||||
'@vitest/utils': 4.1.6
|
||||
es-module-lexer: 2.1.0
|
||||
expect-type: 1.3.0
|
||||
magic-string: 0.30.21
|
||||
obug: 2.1.1
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.4
|
||||
std-env: 4.1.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.1.2
|
||||
tinyglobby: 0.2.16
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 8.0.13
|
||||
why-is-node-running: 2.3.0
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
|
|
|||
39
src/editor/extension.test.ts
Normal file
39
src/editor/extension.test.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Text } from "@codemirror/state";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { isIgnoredMarkdownPosition } from "./extension";
|
||||
|
||||
vi.mock("obsidian", () => ({
|
||||
MarkdownView: class {},
|
||||
}));
|
||||
|
||||
function positionOf(doc: Text, text: string): number {
|
||||
const position = doc.toString().indexOf(text);
|
||||
if (position === -1) {
|
||||
throw new Error(`Could not find ${text}`);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
describe("isIgnoredMarkdownPosition", () => {
|
||||
it("allows normal markdown text", () => {
|
||||
const doc = Text.of(["normal [1m|cm]"]);
|
||||
expect(isIgnoredMarkdownPosition(doc, positionOf(doc, "[1m|cm]"))).toBe(false);
|
||||
});
|
||||
|
||||
it("skips inline code", () => {
|
||||
const doc = Text.of(["normal `[1m|cm]`"]);
|
||||
expect(isIgnoredMarkdownPosition(doc, positionOf(doc, "[1m|cm]"))).toBe(true);
|
||||
});
|
||||
|
||||
it("skips fenced code blocks", () => {
|
||||
const doc = Text.of(["```", "[1m|cm]", "```"]);
|
||||
expect(isIgnoredMarkdownPosition(doc, positionOf(doc, "[1m|cm]"))).toBe(true);
|
||||
});
|
||||
|
||||
it("skips frontmatter", () => {
|
||||
const doc = Text.of(["---", "distance: [1m|cm]", "---", "body [2m|cm]"]);
|
||||
expect(isIgnoredMarkdownPosition(doc, positionOf(doc, "[1m|cm]"))).toBe(true);
|
||||
expect(isIgnoredMarkdownPosition(doc, positionOf(doc, "[2m|cm]"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -7,11 +7,65 @@ import {
|
|||
WidgetType,
|
||||
} from "@codemirror/view";
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import type { Text } from "@codemirror/state";
|
||||
import UnitConverterPlugin from "../main";
|
||||
import { convertValue, formatConversion, CONVERSION_REGEX } from "../utils/conversion";
|
||||
import type { UnitCode } from "../utils/conversion";
|
||||
import { MarkdownView } from "obsidian";
|
||||
|
||||
function isFenceLine(line: string): boolean {
|
||||
return /^\s{0,3}(```|~~~)/.test(line);
|
||||
}
|
||||
|
||||
function isInFrontmatter(doc: Text, lineNumber: number): boolean {
|
||||
if (doc.lines === 0 || doc.line(1).text.trim() !== "---") {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let currentLine = 2; currentLine <= doc.lines; currentLine++) {
|
||||
if (doc.line(currentLine).text.trim() === "---") {
|
||||
return lineNumber <= currentLine;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isInFencedCode(doc: Text, lineNumber: number): boolean {
|
||||
let isInFence = false;
|
||||
|
||||
for (let currentLine = 1; currentLine <= lineNumber; currentLine++) {
|
||||
const line = doc.line(currentLine).text;
|
||||
if (!isFenceLine(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentLine === lineNumber) {
|
||||
return true;
|
||||
}
|
||||
|
||||
isInFence = !isInFence;
|
||||
}
|
||||
|
||||
return isInFence;
|
||||
}
|
||||
|
||||
function isInInlineCode(line: string, offset: number): boolean {
|
||||
const textBeforeMatch = line.slice(0, offset);
|
||||
const unescapedBackticks = textBeforeMatch.match(/(?<!\\)`/g);
|
||||
return (unescapedBackticks?.length ?? 0) % 2 === 1;
|
||||
}
|
||||
|
||||
export function isIgnoredMarkdownPosition(doc: Text, position: number): boolean {
|
||||
const line = doc.lineAt(position);
|
||||
|
||||
return (
|
||||
isInFrontmatter(doc, line.number) ||
|
||||
isInFencedCode(doc, line.number) ||
|
||||
isInInlineCode(line.text, position - line.from)
|
||||
);
|
||||
}
|
||||
|
||||
class ConversionWidget extends WidgetType {
|
||||
constructor(
|
||||
private readonly value: number,
|
||||
|
|
@ -63,13 +117,17 @@ export function createUnitConversionExtension(plugin: UnitConverterPlugin) {
|
|||
isLivePreview: boolean = false;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.isLivePreview = this.detectEditorMode();
|
||||
this.isLivePreview = this.detectEditorMode(view);
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
detectEditorMode(): boolean {
|
||||
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
detectEditorMode(editorView: EditorView): boolean {
|
||||
const sourceView = editorView.dom.closest(".markdown-source-view");
|
||||
if (sourceView) {
|
||||
return sourceView.classList.contains("is-live-preview");
|
||||
}
|
||||
|
||||
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!view) return false;
|
||||
|
||||
const currentMode = view.currentMode;
|
||||
|
|
@ -77,7 +135,7 @@ export function createUnitConversionExtension(plugin: UnitConverterPlugin) {
|
|||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
const newMode = this.detectEditorMode();
|
||||
const newMode = this.detectEditorMode(update.view);
|
||||
const modeChanged = newMode !== this.isLivePreview;
|
||||
|
||||
if (
|
||||
|
|
@ -97,7 +155,6 @@ export function createUnitConversionExtension(plugin: UnitConverterPlugin) {
|
|||
}
|
||||
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
|
||||
const cursorPos = view.state.selection.main.head;
|
||||
const cursorLine = view.state.doc.lineAt(cursorPos).number;
|
||||
|
||||
|
|
@ -105,13 +162,17 @@ export function createUnitConversionExtension(plugin: UnitConverterPlugin) {
|
|||
const text = view.state.doc.sliceString(from, to);
|
||||
let match;
|
||||
|
||||
CONVERSION_REGEX.lastIndex = 0;
|
||||
while ((match = CONVERSION_REGEX.exec(text)) !== null) {
|
||||
const start = from + match.index;
|
||||
const end = start + match[0].length;
|
||||
const lineAtMatch = view.state.doc.lineAt(start).number;
|
||||
|
||||
// Apply decoration if we're not on the active line
|
||||
if (lineAtMatch !== cursorLine) {
|
||||
if (
|
||||
lineAtMatch !== cursorLine &&
|
||||
!isIgnoredMarkdownPosition(view.state.doc, start)
|
||||
) {
|
||||
const value = parseFloat(match[1]);
|
||||
const fromUnit = match[2];
|
||||
const toUnit = match[3];
|
||||
|
|
|
|||
38
src/editor/md-post-processor.test.ts
Normal file
38
src/editor/md-post-processor.test.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { isWithinIgnoredMarkdownElement } from "./md-post-processor";
|
||||
|
||||
interface FakeElement {
|
||||
tagName: string;
|
||||
classList: {
|
||||
contains: (className: string) => boolean;
|
||||
};
|
||||
parentElement: FakeElement | null;
|
||||
}
|
||||
|
||||
function createNode(tagName: string, classNames: string[] = [], parentElement: FakeElement | null = null) {
|
||||
const element: FakeElement = {
|
||||
tagName,
|
||||
classList: {
|
||||
contains: (className: string) => classNames.includes(className),
|
||||
},
|
||||
parentElement,
|
||||
};
|
||||
|
||||
return { parentElement: element } as unknown as Node;
|
||||
}
|
||||
|
||||
describe("isWithinIgnoredMarkdownElement", () => {
|
||||
it("skips text inside code and pre elements", () => {
|
||||
expect(isWithinIgnoredMarkdownElement(createNode("CODE"))).toBe(true);
|
||||
expect(isWithinIgnoredMarkdownElement(createNode("PRE"))).toBe(true);
|
||||
});
|
||||
|
||||
it("skips text inside Obsidian code-related containers", () => {
|
||||
expect(isWithinIgnoredMarkdownElement(createNode("SPAN", ["cm-inline-code"]))).toBe(true);
|
||||
expect(isWithinIgnoredMarkdownElement(createNode("DIV", ["HyperMD-codeblock"]))).toBe(true);
|
||||
});
|
||||
|
||||
it("allows normal markdown text", () => {
|
||||
expect(isWithinIgnoredMarkdownElement(createNode("P"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,10 +1,30 @@
|
|||
import { MarkdownPostProcessor } from "obsidian";
|
||||
import type { UnitConverterSettings } from "../settings";
|
||||
import type { MarkdownPostProcessor } from "obsidian";
|
||||
import type UnitConverterPlugin from "../main";
|
||||
import { CONVERSION_REGEX, formatConversion } from "../utils/conversion";
|
||||
|
||||
export function createMarkdownPostProcessor(
|
||||
settings: UnitConverterSettings,
|
||||
): MarkdownPostProcessor {
|
||||
export function isWithinIgnoredMarkdownElement(node: Node): boolean {
|
||||
let element = node.parentElement;
|
||||
|
||||
while (element) {
|
||||
if (element.tagName === "CODE" || element.tagName === "PRE") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
element.classList.contains("HyperMD-codeblock") ||
|
||||
element.classList.contains("cm-inline-code") ||
|
||||
element.classList.contains("frontmatter")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
element = element.parentElement;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createMarkdownPostProcessor(plugin: UnitConverterPlugin): MarkdownPostProcessor {
|
||||
return (element: HTMLElement) => {
|
||||
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
|
||||
let node = walker.nextNode();
|
||||
|
|
@ -12,11 +32,12 @@ export function createMarkdownPostProcessor(
|
|||
while (node) {
|
||||
const text = node.nodeValue;
|
||||
|
||||
if (!text) {
|
||||
if (!text || isWithinIgnoredMarkdownElement(node)) {
|
||||
node = walker.nextNode();
|
||||
continue;
|
||||
}
|
||||
|
||||
CONVERSION_REGEX.lastIndex = 0;
|
||||
const newText = text.replace(
|
||||
CONVERSION_REGEX,
|
||||
(match: string, valueStr: string, fromUnit: string, toUnit: string) => {
|
||||
|
|
@ -25,9 +46,9 @@ export function createMarkdownPostProcessor(
|
|||
value,
|
||||
fromUnit,
|
||||
toUnit,
|
||||
settings.useDescriptiveNames,
|
||||
settings.showOriginalUnits,
|
||||
settings.customUnits,
|
||||
plugin.settings.useDescriptiveNames,
|
||||
plugin.settings.showOriginalUnits,
|
||||
plugin.settings.customUnits,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
22
src/main.ts
22
src/main.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { Plugin, Editor } from "obsidian";
|
||||
import { Editor, MarkdownView, Plugin } from "obsidian";
|
||||
import { DEFAULT_SETTINGS, UnitConverterSettings, UnitConverterSettingTab } from "./settings";
|
||||
import DestinationUnitSuggest from "./suggest/to-unit-suggest";
|
||||
import { ConversionModal } from "./modal/conversion-modal";
|
||||
|
|
@ -17,11 +17,9 @@ export default class UnitConverterPlugin extends Plugin {
|
|||
|
||||
this.addSettingTab(new UnitConverterSettingTab(this));
|
||||
|
||||
if (this.settings.isAutosuggestEnabled) {
|
||||
this.registerEditorSuggest(new DestinationUnitSuggest(this));
|
||||
}
|
||||
this.registerEditorSuggest(new DestinationUnitSuggest(this));
|
||||
|
||||
this.registerMarkdownPostProcessor(createMarkdownPostProcessor(this.settings));
|
||||
this.registerMarkdownPostProcessor(createMarkdownPostProcessor(this));
|
||||
|
||||
this.registerEditorExtension(createUnitConversionExtension(this));
|
||||
|
||||
|
|
@ -48,5 +46,19 @@ export default class UnitConverterPlugin extends Plugin {
|
|||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
this.refreshMarkdownViews();
|
||||
}
|
||||
|
||||
refreshMarkdownViews() {
|
||||
this.app.workspace.iterateAllLeaves((leaf) => {
|
||||
if (!(leaf.view instanceof MarkdownView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
leaf.view.editor?.refresh();
|
||||
if (leaf.view.getMode() === "preview") {
|
||||
leaf.view.previewMode.rerender(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ export class UnitConverterSettingTab extends PluginSettingTab {
|
|||
.addText((text) => {
|
||||
text.setPlaceholder("60").setValue(draft.factor.toString());
|
||||
text.inputEl.setAttribute("type", "number");
|
||||
text.inputEl.setAttribute("min", "0");
|
||||
text.inputEl.setAttribute("min", "0.0000000001");
|
||||
text.inputEl.setAttribute("step", "any");
|
||||
text.onChange((value) => {
|
||||
draft.factor = Number(value);
|
||||
|
|
|
|||
47
src/suggest/to-unit-suggest.test.ts
Normal file
47
src/suggest/to-unit-suggest.test.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import DestinationUnitSuggest from "./to-unit-suggest";
|
||||
import type { UnitConverterSettings } from "../settings";
|
||||
|
||||
vi.mock("obsidian", () => ({
|
||||
EditorSuggest: class {
|
||||
context = null;
|
||||
|
||||
constructor() {}
|
||||
},
|
||||
}));
|
||||
|
||||
function createSuggest(settings: Partial<UnitConverterSettings> = {}) {
|
||||
return new DestinationUnitSuggest({
|
||||
app: {},
|
||||
settings: {
|
||||
useDescriptiveNames: false,
|
||||
isAutosuggestEnabled: true,
|
||||
showOriginalUnits: false,
|
||||
customUnits: [],
|
||||
...settings,
|
||||
},
|
||||
} as never);
|
||||
}
|
||||
|
||||
describe("DestinationUnitSuggest", () => {
|
||||
it("does not trigger when autosuggest is disabled", () => {
|
||||
const suggest = createSuggest({ isAutosuggestEnabled: false });
|
||||
const editor = { getLine: () => "[1m|" };
|
||||
|
||||
expect(suggest.onTrigger({ line: 0, ch: 4 }, editor as never)).toBeNull();
|
||||
});
|
||||
|
||||
it("uses the nearest unfinished conversion before the cursor", () => {
|
||||
const suggest = createSuggest();
|
||||
const line = "[1m|cm] [2kg|";
|
||||
const editor = { getLine: () => line };
|
||||
|
||||
const trigger = suggest.onTrigger({ line: 0, ch: line.length }, editor as never);
|
||||
|
||||
expect(trigger).toEqual({
|
||||
start: { line: 0, ch: 13 },
|
||||
end: { line: 0, ch: 13 },
|
||||
query: "kg",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,7 +6,7 @@ import {
|
|||
EditorSuggestContext,
|
||||
EditorSuggestTriggerInfo,
|
||||
} from "obsidian";
|
||||
import { getCompatibleUnits, isBuiltInUnit } from "../utils/conversion";
|
||||
import { getCompatibleUnits, isBuiltInUnit, PARTIAL_CONVERSION_REGEX } from "../utils/conversion";
|
||||
|
||||
interface DestinationUnitCompletion {
|
||||
label: string;
|
||||
|
|
@ -19,11 +19,14 @@ export default class DestinationUnitSuggest extends EditorSuggest<DestinationUni
|
|||
}
|
||||
|
||||
onTrigger(cursor: EditorPosition, editor: Editor): EditorSuggestTriggerInfo | null {
|
||||
if (!this.plugin.settings.isAutosuggestEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const line = editor.getLine(cursor.line);
|
||||
const subString = line.substring(0, cursor.ch);
|
||||
|
||||
// Match pattern [value unit| or [value unit|text
|
||||
const match = subString.match(/\[([\d.]+)([a-zA-Z0-9\-/]+)\|([a-zA-Z0-9-]*)/);
|
||||
const match = subString.match(PARTIAL_CONVERSION_REGEX);
|
||||
if (!match) return null;
|
||||
|
||||
const [, , fromUnit] = match;
|
||||
|
|
|
|||
55
src/utils/conversion.test.ts
Normal file
55
src/utils/conversion.test.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
CONVERSION_REGEX,
|
||||
convertValue,
|
||||
validateCustomUnit,
|
||||
type CustomUnitDefinition,
|
||||
} from "./conversion";
|
||||
|
||||
function matchConversion(input: string): RegExpExecArray | null {
|
||||
CONVERSION_REGEX.lastIndex = 0;
|
||||
return CONVERSION_REGEX.exec(input);
|
||||
}
|
||||
|
||||
describe("CONVERSION_REGEX", () => {
|
||||
it("matches common numeric syntax", () => {
|
||||
expect(matchConversion("[-3m|cm]")?.[1]).toBe("-3");
|
||||
expect(matchConversion("[.5m|cm]")?.[1]).toBe(".5");
|
||||
expect(matchConversion("[1.2m|cm]")?.[1]).toBe("1.2");
|
||||
expect(matchConversion("[1e3m|km]")).toMatchObject({
|
||||
1: "1e3",
|
||||
2: "m",
|
||||
3: "km",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects malformed numeric syntax", () => {
|
||||
expect(matchConversion("[1.2.3m|cm]")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom units", () => {
|
||||
const customUnits: CustomUnitDefinition[] = [
|
||||
{
|
||||
abbr: "step",
|
||||
singular: "step",
|
||||
plural: "steps",
|
||||
measure: "length",
|
||||
anchorUnit: "cm",
|
||||
factor: 60,
|
||||
},
|
||||
];
|
||||
|
||||
it("requires a positive finite factor", () => {
|
||||
expect(validateCustomUnit({ ...customUnits[0], factor: 0 }).isValid).toBe(false);
|
||||
expect(validateCustomUnit({ ...customUnits[0], factor: -1 }).isValid).toBe(false);
|
||||
expect(validateCustomUnit({ ...customUnits[0], factor: Number.POSITIVE_INFINITY }).isValid).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("converts custom units through built-in anchor units", () => {
|
||||
expect(convertValue(2, "step", "m", customUnits)).toBeCloseTo(1.2);
|
||||
expect(convertValue(120, "cm", "step", customUnits)).toBeCloseTo(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -27,7 +27,10 @@ export interface CustomUnitValidationResult {
|
|||
message?: string;
|
||||
}
|
||||
|
||||
const UNIT_CODE_REGEX = /^[a-zA-Z0-9\-/]+$/;
|
||||
const UNIT_CODE_PATTERN = "[a-zA-Z0-9\\-/]+";
|
||||
const PARTIAL_UNIT_CODE_PATTERN = "[a-zA-Z0-9\\-/]*";
|
||||
const NUMBER_PATTERN = "-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?";
|
||||
const UNIT_CODE_REGEX = new RegExp(`^${UNIT_CODE_PATTERN}$`);
|
||||
|
||||
function getCustomUnit(
|
||||
unit: UnitCode,
|
||||
|
|
@ -174,7 +177,7 @@ export function getDisplayUnit(
|
|||
return (value === 1 ? measure.singular : measure.plural).toLowerCase();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error getting descriptive name:", e);
|
||||
console.debug("Error getting descriptive name:", e);
|
||||
}
|
||||
|
||||
return unit.toString();
|
||||
|
|
@ -207,7 +210,7 @@ export function formatConversion(
|
|||
const originalUnit = getDisplayUnit(value, fromUnit, useDescriptiveNames, customUnits);
|
||||
return `${value} ${originalUnit} (${convertedValue.toFixed(precision)} ${displayUnit})`;
|
||||
} catch (e) {
|
||||
console.error("Conversion error:", e);
|
||||
console.debug("Conversion error:", e);
|
||||
return `[${value}${fromUnit}|${toUnit}]`; // Return original format on error
|
||||
}
|
||||
}
|
||||
|
|
@ -237,8 +240,8 @@ export function validateCustomUnit(
|
|||
return { isValid: false, message: "Add singular and plural names." };
|
||||
}
|
||||
|
||||
if (!Number.isFinite(unit.factor) || unit.factor < 0) {
|
||||
return { isValid: false, message: "Factor must be 0 or greater." };
|
||||
if (!Number.isFinite(unit.factor) || unit.factor <= 0) {
|
||||
return { isValid: false, message: "Factor must be greater than 0." };
|
||||
}
|
||||
|
||||
if (!isBuiltInUnit(anchorUnit)) {
|
||||
|
|
@ -274,6 +277,16 @@ export function validateCustomUnit(
|
|||
}
|
||||
|
||||
/**
|
||||
* Regular expression to match unit conversion syntax
|
||||
* Regular expression to match unit conversion syntax.
|
||||
*/
|
||||
export const CONVERSION_REGEX = /\[([\d.]+)([a-zA-Z0-9\-/]+)\|([a-zA-Z0-9\-/]+)\]/g;
|
||||
export const CONVERSION_REGEX = new RegExp(
|
||||
`\\[(${NUMBER_PATTERN})(${UNIT_CODE_PATTERN})\\|(${UNIT_CODE_PATTERN})\\]`,
|
||||
"g",
|
||||
);
|
||||
|
||||
/**
|
||||
* Regular expression to match an unfinished conversion before the cursor.
|
||||
*/
|
||||
export const PARTIAL_CONVERSION_REGEX = new RegExp(
|
||||
`\\[(${NUMBER_PATTERN})(${UNIT_CODE_PATTERN})\\|(${PARTIAL_UNIT_CODE_PATTERN})$`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue