feat(ui): enhance sidebar functionality with collapsible feature, improve styling, and add hash copy functionality across components

This commit is contained in:
2026-01-02 01:15:52 -06:00
parent ba61fab06a
commit 7746a2db98
7 changed files with 939 additions and 328 deletions

View File

@@ -116,22 +116,39 @@
<!-- sidebar -->
<div
class="fixed inset-y-0 left-0 z-[70] w-72 transform transition-transform duration-300 ease-in-out sm:relative sm:z-0 sm:flex sm:translate-x-0"
:class="isSidebarOpen ? 'translate-x-0' : '-translate-x-full'"
class="fixed inset-y-0 left-0 z-[70] transform transition-all duration-300 ease-in-out sm:relative sm:z-0 sm:flex sm:translate-x-0"
:class="[
isSidebarOpen ? 'translate-x-0' : '-translate-x-full',
isSidebarCollapsed ? 'w-20' : 'w-72',
]"
>
<div
class="flex h-full w-full flex-col overflow-y-auto border-r border-gray-200/70 bg-white dark:border-zinc-800 dark:bg-zinc-900 backdrop-blur"
>
<!-- toggle button for desktop -->
<div class="hidden sm:flex justify-end p-2 border-b border-gray-100 dark:border-zinc-800">
<button
type="button"
class="p-1.5 rounded-lg text-gray-500 hover:bg-gray-100 dark:text-zinc-400 dark:hover:bg-zinc-800 transition-colors"
@click="isSidebarCollapsed = !isSidebarCollapsed"
>
<MaterialDesignIcon
:icon-name="isSidebarCollapsed ? 'chevron-right' : 'chevron-left'"
class="size-5"
/>
</button>
</div>
<!-- navigation -->
<div class="flex-1">
<ul class="py-3 pr-2 space-y-1">
<!-- messages -->
<li>
<SidebarLink :to="{ name: 'messages' }">
<SidebarLink :to="{ name: 'messages' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon
icon-name="message-text"
class="w-6 h-6 dark:text-white"
class="w-6 h-6 text-gray-700 dark:text-white"
/>
</template>
<template #text>
@@ -145,9 +162,12 @@
<!-- nomad network -->
<li>
<SidebarLink :to="{ name: 'nomadnetwork' }">
<SidebarLink :to="{ name: 'nomadnetwork' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="earth" class="w-6 h-6" />
<MaterialDesignIcon
icon-name="earth"
class="w-6 h-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.nomad_network") }}</template>
</SidebarLink>
@@ -155,9 +175,12 @@
<!-- map -->
<li>
<SidebarLink :to="{ name: 'map' }">
<SidebarLink :to="{ name: 'map' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="map" class="w-6 h-6" />
<MaterialDesignIcon
icon-name="map"
class="w-6 h-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.map") }}</template>
</SidebarLink>
@@ -165,9 +188,12 @@
<!-- archives -->
<li>
<SidebarLink :to="{ name: 'archives' }">
<SidebarLink :to="{ name: 'archives' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="archive" class="w-6 h-6" />
<MaterialDesignIcon
icon-name="archive"
class="w-6 h-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.archives") }}</template>
</SidebarLink>
@@ -175,9 +201,12 @@
<!-- telephone -->
<li>
<SidebarLink :to="{ name: 'call' }">
<SidebarLink :to="{ name: 'call' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="phone" class="w-6 h-6" />
<MaterialDesignIcon
icon-name="phone"
class="w-6 h-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.audio_calls") }}</template>
</SidebarLink>
@@ -185,9 +214,12 @@
<!-- interfaces -->
<li>
<SidebarLink :to="{ name: 'interfaces' }">
<SidebarLink :to="{ name: 'interfaces' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="router" class="w-6 h-6" />
<MaterialDesignIcon
icon-name="router"
class="w-6 h-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.interfaces") }}</template>
</SidebarLink>
@@ -195,9 +227,15 @@
<!-- network visualiser -->
<li>
<SidebarLink :to="{ name: 'network-visualiser' }">
<SidebarLink
:to="{ name: 'network-visualiser' }"
:is-collapsed="isSidebarCollapsed"
>
<template #icon>
<MaterialDesignIcon icon-name="diagram-projector" class="w-6 h-6" />
<MaterialDesignIcon
icon-name="diagram-projector"
class="w-6 h-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.network_visualiser") }}</template>
</SidebarLink>
@@ -205,9 +243,12 @@
<!-- tools -->
<li>
<SidebarLink :to="{ name: 'tools' }">
<SidebarLink :to="{ name: 'tools' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="wrench" class="size-6" />
<MaterialDesignIcon
icon-name="wrench"
class="size-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.tools") }}</template>
</SidebarLink>
@@ -215,9 +256,12 @@
<!-- settings -->
<li>
<SidebarLink :to="{ name: 'settings' }">
<SidebarLink :to="{ name: 'settings' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="cog" class="size-6" />
<MaterialDesignIcon
icon-name="cog"
class="size-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.settings") }}</template>
</SidebarLink>
@@ -225,9 +269,12 @@
<!-- identities -->
<li>
<SidebarLink :to="{ name: 'identities' }">
<SidebarLink :to="{ name: 'identities' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="account-multiple" class="size-6" />
<MaterialDesignIcon
icon-name="account-multiple"
class="size-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.identities") }}</template>
</SidebarLink>
@@ -235,9 +282,12 @@
<!-- info -->
<li>
<SidebarLink :to="{ name: 'about' }">
<SidebarLink :to="{ name: 'about' }" :is-collapsed="isSidebarCollapsed">
<template #icon>
<MaterialDesignIcon icon-name="information" class="size-6" />
<MaterialDesignIcon
icon-name="information"
class="size-6 text-gray-700 dark:text-gray-200"
/>
</template>
<template #text>{{ $t("app.about") }}</template>
</SidebarLink>
@@ -255,7 +305,7 @@
class="flex text-gray-700 p-3 cursor-pointer"
@click="isShowingMyIdentitySection = !isShowingMyIdentitySection"
>
<div class="my-auto mr-2">
<div class="my-auto mr-2 shrink-0">
<RouterLink :to="{ name: 'profile.icon' }" @click.stop>
<LxmfUserIcon
:icon-name="config?.lxmf_user_icon_name"
@@ -264,8 +314,10 @@
/>
</RouterLink>
</div>
<div class="my-auto dark:text-white">{{ $t("app.my_identity") }}</div>
<div class="my-auto ml-auto">
<div v-if="!isSidebarCollapsed" class="my-auto dark:text-white truncate">
{{ $t("app.my_identity") }}
</div>
<div v-if="!isSidebarCollapsed" class="my-auto ml-auto shrink-0">
<button
type="button"
class="my-auto inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500"
@@ -276,7 +328,7 @@
</div>
</div>
<div
v-if="isShowingMyIdentitySection"
v-if="isShowingMyIdentitySection && !isSidebarCollapsed"
class="divide-y text-gray-900 border-t border-gray-200 dark:text-zinc-200 dark:border-zinc-800"
>
<div class="p-2">
@@ -287,19 +339,19 @@
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-800 dark:border-zinc-600 dark:text-zinc-200 dark:focus:ring-blue-400 dark:focus:border-blue-400"
/>
</div>
<div class="p-2 dark:border-zinc-900 overflow-hidden">
<div class="p-2 dark:border-zinc-900 overflow-hidden text-xs">
<div>{{ $t("app.identity_hash") }}</div>
<div
class="text-sm text-gray-700 dark:text-zinc-400 truncate font-mono"
class="text-[10px] text-gray-700 dark:text-zinc-400 truncate font-mono"
:title="config.identity_hash"
>
{{ config.identity_hash }}
</div>
</div>
<div class="p-2 dark:border-zinc-900 overflow-hidden">
<div class="p-2 dark:border-zinc-900 overflow-hidden text-xs">
<div>{{ $t("app.lxmf_address") }}</div>
<div
class="text-sm text-gray-700 dark:text-zinc-400 truncate font-mono"
class="text-[10px] text-gray-700 dark:text-zinc-400 truncate font-mono"
:title="config.lxmf_address_hash"
>
{{ config.lxmf_address_hash }}
@@ -317,11 +369,13 @@
class="flex text-gray-700 p-3 cursor-pointer dark:text-white"
@click="isShowingAnnounceSection = !isShowingAnnounceSection"
>
<div class="my-auto mr-2">
<div class="my-auto mr-2 shrink-0">
<MaterialDesignIcon icon-name="radio" class="size-6" />
</div>
<div class="my-auto">{{ $t("app.announce") }}</div>
<div class="ml-auto">
<div v-if="!isSidebarCollapsed" class="my-auto truncate">
{{ $t("app.announce") }}
</div>
<div v-if="!isSidebarCollapsed" class="ml-auto shrink-0">
<button
type="button"
class="my-auto inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500 dark:bg-zinc-800 dark:text-white dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500"
@@ -332,7 +386,7 @@
</div>
</div>
<div
v-if="isShowingAnnounceSection"
v-if="isShowingAnnounceSection && !isSidebarCollapsed"
class="divide-y text-gray-900 border-t border-gray-200 dark:text-zinc-200 dark:border-zinc-800"
>
<div class="p-2 dark:border-zinc-800">
@@ -350,7 +404,7 @@
<option value="43200">Every 12 Hours</option>
<option value="86400">Every 24 Hours</option>
</select>
<div class="text-sm text-gray-700 dark:text-zinc-100">
<div class="text-[10px] text-gray-700 dark:text-zinc-100 mt-1">
<span v-if="config.last_announced_at">{{
$t("app.last_announced", {
time: formatSecondsAgo(config.last_announced_at),
@@ -365,14 +419,14 @@
</div>
</div>
<div v-if="!isPopoutMode" class="flex flex-1 min-w-0 overflow-hidden">
<div class="flex flex-1 min-w-0 overflow-hidden">
<RouterView class="flex-1 min-w-0 h-full" />
</div>
</div>
</template>
</template>
<CallOverlay
v-if="activeCall || isCallEnded || wasDeclined"
v-if="(activeCall || isCallEnded || wasDeclined) && $route.name !== 'call'"
:active-call="activeCall || lastCall"
:is-ended="isCallEnded"
:was-declined="wasDeclined"
@@ -443,6 +497,7 @@ export default {
isShowingAnnounceSection: true,
isSidebarOpen: false,
isSidebarCollapsed: false,
isSwitchingIdentity: false,
@@ -496,6 +551,13 @@ export default {
if (newConfig && newConfig.custom_ringtone_enabled !== undefined) {
this.updateRingtonePlayer();
}
if (newConfig && newConfig.theme) {
if (newConfig.theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
},
deep: true,
},
@@ -551,6 +613,13 @@ export default {
case "config": {
this.config = json.config;
this.displayName = json.config.display_name;
if (this.config?.theme) {
if (this.config.theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
break;
}
case "announced": {
@@ -559,6 +628,9 @@ export default {
break;
}
case "telephone_ringing": {
if (this.config?.do_not_disturb_enabled) {
break;
}
NotificationUtils.showIncomingCallNotification();
this.updateTelephoneStatus();
this.playRingtone();
@@ -616,6 +688,13 @@ export default {
try {
const response = await window.axios.get(`/api/v1/config`);
this.config = response.data.config;
if (this.config?.theme) {
if (this.config.theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
} catch (e) {
// do nothing if failed to load config
console.log(e);
@@ -817,6 +896,11 @@ export default {
this.wasDeclined = false;
this.lastCall = null;
if (this.endedTimeout) clearTimeout(this.endedTimeout);
} else if (!this.endedTimeout) {
// If no call and no ended state timeout active, ensure everything is reset
this.isCallEnded = false;
this.wasDeclined = false;
this.lastCall = null;
}
} catch {
// do nothing on error

View File

@@ -8,13 +8,13 @@
? 'bg-blue-100 text-blue-800 group:text-blue-800 dark:bg-zinc-800 dark:text-blue-300'
: 'hover:bg-gray-100 dark:hover:bg-zinc-700',
]"
class="w-full text-gray-800 dark:text-zinc-200 group flex gap-x-3 rounded-r-full p-2 mr-2 text-sm leading-6 font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:focus-visible:outline-zinc-500"
class="w-full text-gray-800 dark:text-zinc-200 group flex gap-x-3 rounded-r-full p-2 mr-2 text-sm leading-6 font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:focus-visible:outline-zinc-500 overflow-hidden"
@click="handleNavigate($event, navigate)"
>
<span class="my-auto">
<span class="my-auto shrink-0">
<slot name="icon"></slot>
</span>
<span class="my-auto flex w-full">
<span v-if="!isCollapsed" class="my-auto flex w-full truncate">
<slot name="text"></slot>
</span>
</a>
@@ -29,6 +29,10 @@ export default {
type: Object,
required: true,
},
isCollapsed: {
type: Boolean,
default: false,
},
},
emits: ["click"],
methods: {

View File

@@ -3,33 +3,35 @@
<div class="flex flex-col flex-1 h-full overflow-hidden bg-slate-50 dark:bg-zinc-950">
<!-- header -->
<div
class="flex items-center px-4 py-4 bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 shadow-sm"
class="flex flex-col sm:flex-row sm:items-center px-4 py-4 bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 shadow-sm gap-4"
>
<div class="flex items-center gap-3">
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg shrink-0">
<MaterialDesignIcon icon-name="archive" class="size-6 text-blue-600 dark:text-blue-400" />
</div>
<div>
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{{ $t("app.archives") }}</h1>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $t("archives.description") }}</p>
<div class="min-w-0">
<h1 class="text-xl font-bold text-gray-900 dark:text-white truncate">{{ $t("app.archives") }}</h1>
<p class="text-xs sm:text-sm text-gray-500 dark:text-gray-400 truncate">
{{ $t("archives.description") }}
</p>
</div>
</div>
<div class="ml-auto flex items-center gap-2 sm:gap-4">
<div class="relative w-32 sm:w-64 md:w-80">
<div class="flex items-center gap-2 sm:ml-auto">
<div class="relative flex-1 sm:w-64 md:w-80">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<MaterialDesignIcon icon-name="magnify" class="size-5 text-gray-400" />
</div>
<input
v-model="searchQuery"
type="text"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-zinc-700 rounded-lg bg-gray-50 dark:bg-zinc-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-zinc-700 rounded-lg bg-gray-50 dark:bg-zinc-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-sm"
:placeholder="$t('archives.search_placeholder')"
@input="onSearchInput"
/>
</div>
<button
class="p-2 text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 transition-colors"
class="p-2 text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 transition-colors shrink-0"
:title="$t('common.refresh')"
@click="getArchives"
>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,13 @@
<div class="text-xs text-gray-500 dark:text-zinc-400 mt-0.5">
<!-- destination hash -->
<div class="inline-block mr-1">
<div>&lt;{{ selectedPeer.destination_hash }}&gt;</div>
<div
class="cursor-pointer hover:text-blue-500 transition-colors"
:title="selectedPeer.destination_hash"
@click="copyHash(selectedPeer.destination_hash)"
>
{{ formatDestinationHash(selectedPeer.destination_hash) }}
</div>
</div>
<div class="inline-block">
@@ -757,7 +763,7 @@
<div class="flex flex-wrap gap-2 items-center mt-2">
<button type="button" class="attachment-action-button" @click="addFilesToMessage">
<MaterialDesignIcon icon-name="paperclip-plus" class="w-4 h-4" />
<span>{{ $t("messages.add_files") }}</span>
<span class="hidden sm:inline">{{ $t("messages.add_files") }}</span>
</button>
<AddImageButton @add-image="onImageSelected" />
<AddAudioButton
@@ -774,7 +780,7 @@
@click="shareLocation"
>
<MaterialDesignIcon icon-name="map-marker" class="w-4 h-4" />
<span>{{ $t("messages.location") }}</span>
<span class="hidden sm:inline">{{ $t("messages.location") }}</span>
</button>
<button
type="button"
@@ -783,7 +789,7 @@
@click="requestLocation"
>
<MaterialDesignIcon icon-name="crosshairs-question" class="w-4 h-4" />
<span>{{ $t("messages.request") }}</span>
<span class="hidden sm:inline">{{ $t("messages.request") }}</span>
</button>
<div class="ml-auto my-auto">
<SendMessageButton
@@ -1951,6 +1957,18 @@ export default {
formatTimeAgo: function (datetimeString) {
return Utils.formatTimeAgo(datetimeString);
},
formatDestinationHash(hash) {
return Utils.formatDestinationHash(hash);
},
async copyHash(hash) {
try {
await navigator.clipboard.writeText(hash);
ToastUtils.success("Hash copied to clipboard");
} catch (e) {
console.error(e);
ToastUtils.error("Failed to copy hash");
}
},
formatBytes: function (bytes) {
return Utils.formatBytes(bytes);
},

View File

@@ -0,0 +1,245 @@
<template>
<div class="flex flex-col flex-1 overflow-hidden min-w-0 bg-slate-50 dark:bg-zinc-950">
<!-- Compact Header -->
<div
class="flex items-center justify-between px-4 py-2 border-b border-gray-200 dark:border-zinc-800 bg-white/50 dark:bg-zinc-900/50 backdrop-blur-sm shrink-0"
>
<div class="flex items-center gap-3">
<div class="bg-teal-100 dark:bg-teal-900/30 p-1.5 rounded-xl shrink-0">
<MaterialDesignIcon icon-name="code-tags" class="size-5 text-teal-600 dark:text-teal-400" />
</div>
<h1
class="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider hidden sm:block truncate"
>
{{ $t("tools.micron_editor.title") }}
</h1>
</div>
<div class="flex items-center gap-2">
<button type="button" class="secondary-chip !py-1 !px-3" @click="downloadFile">
<MaterialDesignIcon icon-name="download" class="w-3.5 h-3.5" />
<span class="hidden sm:inline">{{ $t("tools.micron_editor.download") }}</span>
</button>
<button v-if="isMobileView" type="button" class="primary-chip !py-1 !px-3" @click="toggleView">
<MaterialDesignIcon :icon-name="showEditor ? 'eye' : 'pencil'" class="w-3.5 h-3.5" />
{{ showEditor ? $t("tools.micron_editor.view_preview") : $t("tools.micron_editor.edit") }}
</button>
</div>
</div>
<div class="flex-1 flex overflow-hidden">
<!-- Editor Pane -->
<div
:class="[
'flex-1 overflow-hidden flex flex-col',
isMobileView && !showEditor ? 'hidden' : '',
!isMobileView ? 'border-r border-gray-200 dark:border-zinc-800' : '',
]"
>
<textarea
ref="editorRef"
v-model="content"
class="flex-1 w-full bg-white dark:bg-zinc-900 text-gray-900 dark:text-white p-4 font-mono text-sm resize-none focus:outline-none"
:placeholder="$t('tools.micron_editor.placeholder')"
@input="handleInput"
></textarea>
</div>
<!-- Preview Pane -->
<div
:class="[
'flex-1 overflow-hidden flex flex-col bg-slate-100 dark:bg-black',
isMobileView && showEditor ? 'hidden' : '',
]"
>
<div
ref="previewRef"
class="flex-1 overflow-auto text-gray-900 dark:text-white p-4 font-mono text-sm whitespace-pre-wrap break-words nodeContainer"
v-html="renderedContent"
></div>
</div>
</div>
</div>
</template>
<script>
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import MicronParser from "micron-parser";
export default {
name: "MicronEditorPage",
components: {
MaterialDesignIcon,
},
data() {
return {
content: "",
renderedContent: "",
showEditor: true,
isMobileView: false,
storageKey: "micron_editor_content",
};
},
mounted() {
this.loadContent();
this.handleResize();
window.addEventListener("resize", this.handleResize);
// Listen for theme changes to re-render micron
this.themeObserver = new MutationObserver(() => {
this.handleInput();
});
this.themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
this.handleInput();
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
if (this.themeObserver) {
this.themeObserver.disconnect();
}
},
methods: {
isDarkMode() {
return document.documentElement.classList.contains("dark");
},
handleResize() {
this.isMobileView = window.innerWidth < 1024;
if (!this.isMobileView) {
this.showEditor = true;
}
},
handleInput() {
try {
const parser = new MicronParser(this.isDarkMode());
this.renderedContent = parser.convertMicronToHtml(this.content);
} catch (error) {
console.error("Error rendering micron:", error);
this.renderedContent = `<p style="color: red;">Error rendering: ${error.message}</p>`;
}
this.saveContent();
},
toggleView() {
this.showEditor = !this.showEditor;
},
saveContent() {
try {
localStorage.setItem(this.storageKey, this.content);
} catch (error) {
console.warn("Failed to save content to localStorage:", error);
}
},
loadContent() {
try {
const saved = localStorage.getItem(this.storageKey);
if (saved) {
this.content = saved;
} else {
this.content = this.getDefaultContent();
}
} catch (error) {
console.warn("Failed to load content from localStorage:", error);
this.content = this.getDefaultContent();
}
},
getDefaultContent() {
const b = "`";
return `${b}Ffd0
${b}=
_ _
(_) (_)
_ __ ___ _ ___ _ __ ___ _ __ ______ _ __ __ _ _ __ ___ ___ _ __ _ ___
| '_ \` _ \\| |/ __| '__/ _ \\| '_ \\______| '_ \\ / _\` | '__/ __|/ _ \\ '__| / __|
| | | | | | | (__| | | (_) | | | | | |_) | (_| | | \\__ \\\\ __/ |_ | \\__ \\\\
|_| |_| |_|_|\\___|_| \\___/|_| |_| | .__/ \\__,_|_| |___/\\___|_(_)| |___/
| | _/ |
|_| |__/
${b}=
${b}f
${b}!Welcome to Micron Editor${b}!
-
Micron is a lightweight, terminal-friendly monospace markdown format used in Reticulum applications.
${b}!With Micron, you can${b}${b}:
${b}c Align${b}b
${b}r text,
${b}a
${b}c
set ${b}B005 backgrounds, ${b}b and ${b}*${b} ${b}B777${b}Ffffcombine any number of${b}f${b}b${b}_${b}_ ${b}Ff00f${b}Ff80o${b}Ffd0r${b}F9f0m${b}F0f2a${b}F0fdt${b}F07ft${b}F43fi${b}F70fn${b}Fe0fg ${b}ftags.
${b}${b}
>Getting Started
Start editing your Micron markup in the editor pane. The preview will update automatically.
>Formatting
Text can be ${b}!bold${b}! by using \\${b}!, \\${b}_, and \\${b}*.
>Colors
Foreground colors: ${b}Ff00${b}Ff80o${b}Ffd0r${b}F9f0m${b}F0f2a${b}F0fdt${b}F07ft${b}F43fi${b}F70fn${b}Fe0fg${b}f
Background colors: ${b}Bf00${b}Bf80o${b}Bfd0r${b}B9f0m${b}B0f2a${b}B0fdt${b}B07ft${b}B43fi${b}B70fn${b}Be0fg${b}b
>Links
Create links with \\${b}[ tag: ${b}_${b}[Example Link${b}example.com]${b}]${b}_
>Literals
Use \\${b}= to start/end literal blocks that won't be interpreted.
${b}=
This is a literal block
${b}=
`;
},
downloadFile() {
const blob = new Blob([this.content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "micron.mu";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
},
},
};
</script>
<style scoped>
.nodeContainer {
font-family:
Roboto Mono Nerd Font,
monospace;
line-height: normal;
}
:deep(.Mu-nl) {
cursor: pointer;
}
:deep(.Mu-mnt) {
display: inline-block;
width: 0.6em;
text-align: center;
white-space: pre;
text-decoration: inherit;
}
:deep(.Mu-mws) {
text-decoration: inherit;
display: inline-block;
}
:deep(a:hover) {
text-decoration: underline;
}
</style>

View File

@@ -98,6 +98,19 @@
<MaterialDesignIcon icon-name="chevron-right" class="tool-card__chevron" />
</RouterLink>
<RouterLink :to="{ name: 'micron-editor' }" class="tool-card glass-card">
<div class="tool-card__icon bg-teal-50 text-teal-500 dark:bg-teal-900/30 dark:text-teal-200">
<MaterialDesignIcon icon-name="code-tags" class="w-6 h-6" />
</div>
<div class="flex-1">
<div class="tool-card__title">{{ $t("tools.micron_editor.title") }}</div>
<div class="tool-card__description">
{{ $t("tools.micron_editor.description") }}
</div>
</div>
<MaterialDesignIcon icon-name="chevron-right" class="tool-card__chevron" />
</RouterLink>
<a target="_blank" href="/rnode-flasher/index.html" class="tool-card glass-card">
<div
class="tool-card__icon bg-purple-50 text-purple-500 dark:bg-purple-900/30 dark:text-purple-200"