feat: live preview

This commit is contained in:
Ruslan Zabarov 2025-02-25 16:29:27 -05:00 committed by Ruslan Zabarov
parent b39c0a4d6e
commit 2b9a4fc0b3
14 changed files with 488 additions and 217 deletions

View file

@ -1,24 +1,29 @@
name: Build
on:
pull_request:
branches:
- master
workflow_call:
pull_request:
branches:
- master
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Install dependencies
run: npm ci
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Run build
run: npm run build
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run build
run: pnpm run build

View file

@ -1,33 +1,38 @@
name: Release Obsidian plugin
on:
push:
tags:
- "*"
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Build plugin
run: |
npm install
npm run build
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${GITHUB_REF#refs/tags/}"
- name: Build plugin
run: |
pnpm install
pnpm run build
gh release create "$tag" \
--title="$tag" \
main.js manifest.json styles.css
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${GITHUB_REF#refs/tags/}"
gh release create "$tag" \
--title="$tag" \
main.js manifest.json styles.css

146
README.md
View file

@ -1,73 +1,73 @@
# Unit Conversion in Obsidian
This plugin allows you to seamlessly convert units directly within your Obsidian notes, making it easier to work with data across various systems and contexts without leaving your markdown editor.
## Basic Syntax
Use the following syntax within your notes to perform inline conversion:
`[<value><source unit>|<target unit>]`
## Example
<img src="https://github.com/user-attachments/assets/a191b540-decd-4f2e-930b-0aa8a9f71678" alt="obsidian-unit-converter" width="500">
## Features
- [Autosuggest](#autosuggest)
- [Convert units command](#convert-units-command)
### Autosuggest
The plugin will automatically suggest relevant units. This option can be disabled in settings.
<img src="https://github.com/user-attachments/assets/b6988894-83da-4a76-8ca9-87f335d72082" alt="autosuggest example" width="200">
### Convert units command
You can open a modal to perform a unit conversion. This is mostly done to improve the mobile experience.
<img src="https://github.com/user-attachments/assets/ca168075-8408-41e3-812f-181c511b1585" alt="convert units command example" width="350">
## Supported Units
**Length**: mm, cm, m, in, ft-us, ft, mi
**Area**: mm2 cm2, m2, ha, km2, in2, ft2, ac, mi2
**Mass**: mcg, mg, g, kg, oz, lb, mt, t
**Volume**: mm3, cm3, ml, l, kl, m3, km3, 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'
**Temperature**: C, F, K, R
**Time**: ns, mu, ms, s, min, h, d, week, month, year
**Frequency**: Hz, mHz, kHz, MHz, GHz, THz, rpm, deg/s, rad/s
**Speed**: m/s, km/h, m/h, knot, ft/s
**Pace**: s/m, min/km, s/ft, min/km
**Pressure**: Pa, hPa, kPa, MPa, 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
**Voltage**: V, mV, kV
**Current**: A, mA, kA
**Power**: W, mW, kW, MW, GW
**Apparent Power**: VA, mVA, kVA, MVA, GVA
**Reactive Power**: VAR, mVAR, kVAR, MVAR, GVAR
**Energy**: Wh, mWh, kWh, MWh, GWh, J, kJ
**Reactive Energy**: VARh, mVARh, kVARh, MVARh, GVARh
**Angle**: deg, rad, grad, arcmin, arcsec
# Unit Conversion in Obsidian
This plugin allows you to seamlessly convert units directly within your Obsidian notes, making it easier to work with data across various systems and contexts without leaving your markdown editor.
## Basic Syntax
Use the following syntax within your notes to perform inline conversion:
`[<value><source unit>|<target unit>]`
## Example
<img src="https://github.com/user-attachments/assets/a191b540-decd-4f2e-930b-0aa8a9f71678" alt="obsidian-unit-converter" width="500">
## Features
- [Autosuggest](#autosuggest)
- [Convert units command](#convert-units-command)
### Autosuggest
The plugin will automatically suggest relevant units. This option can be disabled in settings.
<img src="https://github.com/user-attachments/assets/b6988894-83da-4a76-8ca9-87f335d72082" alt="autosuggest example" width="200">
### Convert units command
You can open a modal to perform a unit conversion. This is mostly done to improve the mobile experience.
<img src="https://github.com/user-attachments/assets/ca168075-8408-41e3-812f-181c511b1585" alt="convert units command example" width="350">
## Supported Units
**Length**: mm, cm, m, in, ft-us, ft, mi
**Area**: mm2 cm2, m2, ha, km2, in2, ft2, ac, mi2
**Mass**: mcg, mg, g, kg, oz, lb, mt, t
**Volume**: mm3, cm3, ml, l, kl, m3, km3, 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'
**Temperature**: C, F, K, R
**Time**: ns, mu, ms, s, min, h, d, week, month, year
**Frequency**: Hz, mHz, kHz, MHz, GHz, THz, rpm, deg/s, rad/s
**Speed**: m/s, km/h, m/h, knot, ft/s
**Pace**: s/m, min/km, s/ft, min/km
**Pressure**: Pa, hPa, kPa, MPa, 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
**Voltage**: V, mV, kV
**Current**: A, mA, kA
**Power**: W, mW, kW, MW, GW
**Apparent Power**: VA, mVA, kVA, MVA, GVA
**Reactive Power**: VAR, mVAR, kVAR, MVAR, GVAR
**Energy**: Wh, mWh, kWh, MWh, GWh, J, kJ
**Reactive Energy**: VARh, mVARh, kVARh, MVARh, GVARh
**Angle**: deg, rad, grad, arcmin, arcsec

View file

@ -6,4 +6,4 @@
"description": "Converts units right in your Markdown files.",
"author": "Ruslan Zabarov",
"isDesktopOnly": false
}
}

View file

@ -14,6 +14,8 @@
"author": "Ruslan Zabarov",
"license": "MIT",
"devDependencies": {
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.3",
"@eslint/js": "^9.16.0",
"@types/convert-units": "^2.3.11",
"@types/node": "^22.13.1",

View file

@ -12,6 +12,12 @@ importers:
specifier: ^2.3.4
version: 2.3.4
devDependencies:
'@codemirror/state':
specifier: ^6.5.2
version: 6.5.2
'@codemirror/view':
specifier: ^6.36.3
version: 6.36.3
'@eslint/js':
specifier: ^9.16.0
version: 9.21.0
@ -38,7 +44,7 @@ importers:
version: 16.0.0
obsidian:
specifier: latest
version: 1.8.7(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)
version: 1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.36.3)
rolldown:
specifier: ^1.0.0-beta.3
version: 1.0.0-beta.3(typescript@5.7.3)
@ -54,11 +60,11 @@ importers:
packages:
'@codemirror/state@6.4.1':
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
'@codemirror/state@6.5.2':
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
'@codemirror/view@6.35.0':
resolution: {integrity: sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==}
'@codemirror/view@6.36.3':
resolution: {integrity: sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==}
'@emnapi/core@1.3.1':
resolution: {integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==}
@ -123,6 +129,9 @@ packages:
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
engines: {node: '>=18.18'}
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
'@napi-rs/wasm-runtime@0.2.6':
resolution: {integrity: sha512-z8YVS3XszxFTO73iwvFDNpQIzdMmSDTP/mB3E/ucR37V3Sx57hSExcXyMoNwaucWxnsWf4xfbZv0iZ30jr0M4Q==}
@ -737,11 +746,13 @@ packages:
snapshots:
'@codemirror/state@6.4.1': {}
'@codemirror/view@6.35.0':
'@codemirror/state@6.5.2':
dependencies:
'@codemirror/state': 6.4.1
'@marijn/find-cluster-break': 1.0.2
'@codemirror/view@6.36.3':
dependencies:
'@codemirror/state': 6.5.2
style-mod: 4.1.2
w3c-keyname: 2.2.8
@ -816,6 +827,8 @@ snapshots:
'@humanwhocodes/retry@0.4.2': {}
'@marijn/find-cluster-break@1.0.2': {}
'@napi-rs/wasm-runtime@0.2.6':
dependencies:
'@emnapi/core': 1.3.1
@ -1318,10 +1331,10 @@ snapshots:
natural-compare@1.4.0: {}
obsidian@1.8.7(@codemirror/state@6.4.1)(@codemirror/view@6.35.0):
obsidian@1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.36.3):
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.3
'@types/codemirror': 5.60.8
moment: 2.29.4

View file

@ -1,18 +1,18 @@
export default {
input: "src/main.ts",
output: {
dir: ".",
sourcemap: true,
format: "cjs",
exports: "default",
banner: 'if (typeof window !== "undefined" && typeof window.global === "undefined") { window.global = window; }'
},
external: ["obsidian"],
esbuild: {
target: "es2018",
platform: "browser",
format: "cjs",
bundle: true,
sourcemap: true
}
};
input: "src/main.ts",
output: {
dir: ".",
sourcemap: true,
format: "cjs",
exports: "default",
banner: 'if (typeof window !== "undefined" && typeof window.global === "undefined") { window.global = window; }',
},
external: ["obsidian", "@codemirror/state", "@codemirror/view"],
esbuild: {
target: "es2018",
platform: "browser",
format: "cjs",
bundle: true,
sourcemap: true,
},
};

136
src/editor/extension.ts Normal file
View file

@ -0,0 +1,136 @@
import {
EditorView,
ViewPlugin,
Decoration,
DecorationSet,
ViewUpdate,
WidgetType,
} from "@codemirror/view";
import { RangeSetBuilder } from "@codemirror/state";
import { Unit } from "convert-units";
import UnitConverterPlugin from "../main";
import {
convertValue,
formatConversion,
CONVERSION_REGEX,
} from "../utils/conversion";
type EditorMode = "source" | "live-preview";
class ConversionWidget extends WidgetType {
constructor(
private readonly value: number,
private readonly fromUnit: Unit,
private readonly toUnit: Unit,
private readonly plugin: UnitConverterPlugin
) {
super();
}
toDOM(): HTMLElement {
const span = document.createElement("span");
span.textContent = formatConversion(
this.value,
this.fromUnit,
this.toUnit,
this.plugin.settings.useDescriptiveNames
);
return span;
}
}
export function createUnitConversionExtension(plugin: UnitConverterPlugin) {
return ViewPlugin.fromClass(
class {
decorations: DecorationSet;
currentEditorMode: EditorMode = "source";
constructor(view: EditorView) {
this.currentEditorMode = this.detectEditorMode(view);
this.decorations = this.buildDecorations(view);
}
detectEditorMode(view: EditorView): EditorMode {
const editorElement = view.dom.closest(
".markdown-source-view.mod-cm6"
);
return editorElement?.classList.contains("is-live-preview")
? "live-preview"
: "source";
}
update(update: ViewUpdate) {
const newMode = this.detectEditorMode(update.view);
const modeChanged = newMode !== this.currentEditorMode;
if (
update.docChanged ||
update.selectionSet ||
update.viewportChanged ||
modeChanged
) {
this.currentEditorMode = newMode;
this.decorations = this.buildDecorations(update.view);
}
}
buildDecorations(view: EditorView) {
if (
!plugin.settings.livePreviewInEditMode ||
this.currentEditorMode === "source"
) {
return Decoration.none;
}
const builder = new RangeSetBuilder<Decoration>();
const cursorPos = view.state.selection.main.head;
const cursorLine = view.state.doc.lineAt(cursorPos).number;
for (const { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
let match;
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) {
const value = parseFloat(match[1]);
const fromUnit = match[2] as Unit;
const toUnit = match[3] as Unit;
try {
convertValue(value, fromUnit, toUnit);
builder.add(
start,
end,
Decoration.replace({
widget: new ConversionWidget(
value,
fromUnit,
toUnit,
plugin
),
})
);
} catch (e) {
console.debug("Invalid conversion:", e);
}
}
}
}
return builder.finish();
}
},
{
decorations: (v) => v.decorations,
}
);
}

View file

@ -0,0 +1,50 @@
import { MarkdownPostProcessor } from "obsidian";
import { Unit } from "convert-units";
import { UnitConverterSettings } from "../settings";
import { CONVERSION_REGEX, formatConversion } from "../utils/conversion";
export function createMarkdownPostProcessor(
settings: UnitConverterSettings
): MarkdownPostProcessor {
return (element: HTMLElement) => {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null
);
let node = walker.nextNode();
while (node) {
const text = node.nodeValue;
if (!text) {
node = walker.nextNode();
continue;
}
const newText = text.replace(
CONVERSION_REGEX,
(
match: string,
valueStr: string,
fromUnit: Unit,
toUnit: Unit
) => {
const value = parseFloat(valueStr);
return formatConversion(
value,
fromUnit,
toUnit,
settings.useDescriptiveNames
);
}
);
if (newText !== text) {
node.nodeValue = newText;
}
node = walker.nextNode();
}
};
}

View file

@ -1,5 +1,5 @@
import { Plugin, Editor } from "obsidian";
import convert, { Unit } from "convert-units";
import { Unit } from "convert-units";
import {
DEFAULT_SETTINGS,
UnitConverterSettings,
@ -7,9 +7,13 @@ import {
} from "./settings";
import DestinationUnitSuggest from "./suggest/to-unit-suggest";
import { ConversionModal } from "./modal/conversion-modal";
import { createUnitConversionExtension } from "./editor/extension";
import { createMarkdownPostProcessor } from "./editor/md-post-processor";
import { Extension } from "@codemirror/state";
export default class UnitConverterPlugin extends Plugin {
settings: UnitConverterSettings;
private editorExtension: Extension;
async onload() {
await this.loadSettings();
@ -20,6 +24,28 @@ export default class UnitConverterPlugin extends Plugin {
this.registerEditorSuggest(new DestinationUnitSuggest(this));
}
this.registerEvent(
this.app.workspace.on("layout-change", () => {
this.refreshEditorExtensions();
})
);
this.registerEvent(
this.app.workspace.on("active-leaf-change", () => {
this.refreshEditorExtensions();
})
);
this.registerEvent(
this.app.workspace.on("editor-change", () => {
this.refreshEditorExtensions();
})
);
this.registerMarkdownPostProcessor(
createMarkdownPostProcessor(this.settings)
);
this.addCommand({
id: "convert-units",
name: "Convert units",
@ -34,68 +60,19 @@ export default class UnitConverterPlugin extends Plugin {
},
});
this.registerMarkdownPostProcessor((element: HTMLElement) => {
const regex = /\[([\d.]+)([a-zA-Z0-9\-/]+)\|([a-zA-Z0-9\-/]+)\]/g;
this.refreshEditorExtensions();
}
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null
);
let node = walker.nextNode();
refreshEditorExtensions() {
// Unregister previous extension if it exists
if (this.editorExtension) {
this.app.workspace.updateOptions();
}
while (node) {
const text = node.nodeValue;
if (!text) {
continue;
}
const newText = text.replace(
regex,
(match, valueStr, fromUnit, toUnit) => {
const value = parseFloat(valueStr);
let convertedValue: number;
let displayUnit = toUnit;
try {
convertedValue = convert(value)
.from(fromUnit)
.to(toUnit);
if (this.settings.useDescriptiveNames) {
try {
const measure = convert().describe(toUnit);
if (measure && measure.plural) {
displayUnit = (
convertedValue === 1
? measure.singular
: measure.plural
).toLowerCase();
}
} catch (e) {
console.error(
"Error getting descriptive name:",
e
);
}
}
} catch (e) {
console.error("Conversion error:", e);
return match; // Return the original text if conversion fails
}
return `${convertedValue.toFixed(2)} ${displayUnit}`;
}
);
if (newText !== text) {
node.nodeValue = newText;
}
node = walker.nextNode();
}
});
if (this.settings.livePreviewInEditMode) {
this.editorExtension = createUnitConversionExtension(this);
this.registerEditorExtension(this.editorExtension);
}
}
async loadSettings() {

View file

@ -4,11 +4,13 @@ import UnitConverterPlugin from "./main";
export interface UnitConverterSettings {
useDescriptiveNames: boolean;
isAutosuggestEnabled: boolean;
livePreviewInEditMode: boolean;
}
export const DEFAULT_SETTINGS: UnitConverterSettings = {
useDescriptiveNames: false,
isAutosuggestEnabled: true,
livePreviewInEditMode: true,
};
export class UnitConverterSettingTab extends PluginSettingTab {
@ -48,5 +50,20 @@ export class UnitConverterSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Live Preview Conversions")
.setDesc(
"Show converted units in Live Preview mode only (not in Source mode)"
)
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.livePreviewInEditMode)
.onChange(async (value) => {
this.plugin.settings.livePreviewInEditMode = value;
await this.plugin.saveSettings();
this.plugin.refreshEditorExtensions();
})
);
}
}

View file

@ -1,4 +1,4 @@
import UnitConverterPlugin from "src/main";
import UnitConverterPlugin from "src/editor/post-processor";
import {
Editor,
EditorPosition,
@ -20,7 +20,7 @@ export default class DestinationUnitSuggest extends EditorSuggest<DestinationUni
onTrigger(
cursor: EditorPosition,
editor: Editor,
editor: Editor
): EditorSuggestTriggerInfo | null {
const line = editor.getLine(cursor.line);
const subString = line.substring(0, cursor.ch);
@ -29,8 +29,8 @@ export default class DestinationUnitSuggest extends EditorSuggest<DestinationUni
const match = subString.match(/\[([\d.]+)([a-zA-Z0-9\-/]+)\|$/);
if (!match) return null;
const [,, fromUnit] = match;
const [, , fromUnit] = match;
try {
// Verify if the fromUnit is valid
convert().from(fromUnit as Unit);
@ -53,7 +53,9 @@ export default class DestinationUnitSuggest extends EditorSuggest<DestinationUni
getSuggestions(context: EditorSuggestContext): DestinationUnitCompletion[] {
const fromUnit = context.query;
try {
const possibilities = convert().from(fromUnit as Unit).possibilities();
const possibilities = convert()
.from(fromUnit as Unit)
.possibilities();
return possibilities.map((unit) => {
const measure = convert().describe(unit);
return {
@ -80,14 +82,10 @@ export default class DestinationUnitSuggest extends EditorSuggest<DestinationUni
line: start.line,
ch: start.ch + value.value.length,
};
editor.replaceRange(
value.value,
start,
{
line: start.line,
ch: start.ch,
}
);
editor.replaceRange(value.value, start, {
line: start.line,
ch: start.ch,
});
editor.setCursor(endPos);
}
}

68
src/utils/conversion.ts Normal file
View file

@ -0,0 +1,68 @@
import convert, { Unit } from "convert-units";
/**
* Converts a value from one unit to another
*/
export function convertValue(
value: number,
fromUnit: Unit,
toUnit: Unit
): number {
return convert(value).from(fromUnit).to(toUnit);
}
/**
* Gets the display unit name based on the value and unit
*/
export function getDisplayUnit(
value: number,
unit: Unit,
useDescriptiveNames: boolean
): string {
if (!useDescriptiveNames) {
return unit.toString();
}
try {
const measure = convert().describe(unit);
if (measure && measure.plural) {
return (
value === 1 ? measure.singular : measure.plural
).toLowerCase();
}
} catch (e) {
console.error("Error getting descriptive name:", e);
}
return unit.toString();
}
/**
* Performs unit conversion and returns the formatted result
*/
export function formatConversion(
value: number,
fromUnit: Unit,
toUnit: Unit,
useDescriptiveNames: boolean,
precision: number = 2
): string {
try {
const convertedValue = convertValue(value, fromUnit, toUnit);
const displayUnit = getDisplayUnit(
convertedValue,
toUnit,
useDescriptiveNames
);
return `${convertedValue.toFixed(precision)} ${displayUnit}`;
} catch (e) {
console.error("Conversion error:", e);
return `[${value}${fromUnit}|${toUnit}]`; // Return original format on error
}
}
/**
* Regular expression to match unit conversion syntax
*/
export const CONVERSION_REGEX =
/\[([\d.]+)([a-zA-Z0-9\-/]+)\|([a-zA-Z0-9\-/]+)\]/g;

View file

@ -8,4 +8,4 @@
.measure-from-container .setting-item {
border-top: none;
padding: 0.75em 0;
}
}