mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
v1.0.0 - Initial release
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import '../app.css';
|
||||
import { theme, appConfig, activeNotePath } from '$lib/stores/app';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { openFile } from '$lib/api';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
// Reactively apply theme class to <html> whenever $theme changes
|
||||
$effect(() => {
|
||||
const t = $theme;
|
||||
const root = document.documentElement;
|
||||
root.classList.remove('dark');
|
||||
if (
|
||||
t === 'dark' ||
|
||||
(t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
root.classList.add('dark');
|
||||
}
|
||||
});
|
||||
|
||||
function normalizePath(p: string): string {
|
||||
const parts = p.split('/');
|
||||
const resolved: string[] = [];
|
||||
for (const seg of parts) {
|
||||
if (seg === '..') resolved.pop();
|
||||
else if (seg !== '.') resolved.push(seg);
|
||||
}
|
||||
return resolved.join('/');
|
||||
}
|
||||
|
||||
// Intercept all link clicks in capture phase to prevent webview navigation
|
||||
onMount(() => {
|
||||
function handleLinkClick(e: MouseEvent) {
|
||||
const target = (e.target as HTMLElement)?.closest('a');
|
||||
if (!target) return;
|
||||
const href = target.getAttribute('href');
|
||||
if (!href) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('mailto:')) {
|
||||
openUrl(href).catch((err) => console.error('Failed to open URL:', err));
|
||||
} else if (!href.startsWith('#')) {
|
||||
const decoded = decodeURIComponent(href);
|
||||
const config = get(appConfig);
|
||||
const vaultRoot = config?.active_vault;
|
||||
let absPath = decoded;
|
||||
if (!decoded.startsWith('/') && vaultRoot) {
|
||||
const notePath = get(activeNotePath);
|
||||
if (notePath) {
|
||||
const noteDir = notePath.substring(0, notePath.lastIndexOf('/'));
|
||||
absPath = normalizePath(`${noteDir}/${decoded}`);
|
||||
} else {
|
||||
absPath = normalizePath(`${vaultRoot}/${decoded}`);
|
||||
}
|
||||
}
|
||||
openFile(absPath).catch((err) => console.error('Failed to open file:', err));
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleLinkClick, true);
|
||||
return () => document.removeEventListener('click', handleLinkClick, true);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:document oncontextmenu={(e) => e.preventDefault()} />
|
||||
|
||||
<svelte:head>
|
||||
<title>HelixNotes</title>
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const prerender = false;
|
||||
export const ssr = false;
|
||||
@@ -0,0 +1,118 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { appConfig, vaultReady, theme } from '$lib/stores/app';
|
||||
import { getAppConfig, openVault } from '$lib/api';
|
||||
import VaultPicker from '$lib/components/VaultPicker.svelte';
|
||||
import AppLayout from '$lib/components/AppLayout.svelte';
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const config = await getAppConfig();
|
||||
$appConfig = config;
|
||||
$theme = config.theme || 'system';
|
||||
|
||||
// Apply theme immediately to prevent flash
|
||||
const themeValue = config.theme || 'system';
|
||||
const root = document.documentElement;
|
||||
root.classList.remove('dark');
|
||||
if (themeValue === 'dark' || (themeValue === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
root.classList.add('dark');
|
||||
}
|
||||
|
||||
// Apply saved font settings
|
||||
if (config.font_size) {
|
||||
document.documentElement.style.setProperty('--editor-font-size', `${config.font_size}px`);
|
||||
}
|
||||
if (config.font_family) {
|
||||
const fontStacks: Record<string, string> = {
|
||||
system: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||
inter: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif',
|
||||
georgia: 'Georgia, "Times New Roman", serif',
|
||||
merriweather: '"Merriweather", Georgia, serif',
|
||||
lora: '"Lora", Georgia, serif',
|
||||
opensans: '"Open Sans", -apple-system, sans-serif',
|
||||
literata: '"Literata", Georgia, serif',
|
||||
mono: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
|
||||
};
|
||||
const stack = fontStacks[config.font_family];
|
||||
if (stack) document.documentElement.style.setProperty('--editor-font-family', stack);
|
||||
}
|
||||
// Apply saved accent
|
||||
if (config.accent_color) {
|
||||
const accentPresets: Record<string, { light: string; dark: string }> = {
|
||||
Indigo: { light: '#5b6abf', dark: '#7b9bd4' },
|
||||
Rose: { light: '#e11d48', dark: '#d4768a' },
|
||||
Emerald: { light: '#059669', dark: '#5bab8a' },
|
||||
Amber: { light: '#d97706', dark: '#c9a04e' },
|
||||
Purple: { light: '#7c3aed', dark: '#9a82c4' },
|
||||
Cyan: { light: '#0891b2', dark: '#5ba8b5' },
|
||||
Orange: { light: '#ea580c', dark: '#c98560' },
|
||||
Teal: { light: '#0d9488', dark: '#5fada5' },
|
||||
};
|
||||
const preset = accentPresets[config.accent_color];
|
||||
if (preset) {
|
||||
const themeVal = config.theme || 'light';
|
||||
const isDark = themeVal === 'dark' || (themeVal === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
const color = isDark ? preset.dark : preset.light;
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--accent', color);
|
||||
root.style.setProperty('--text-accent', color);
|
||||
root.style.setProperty('--accent-hover', color);
|
||||
root.style.setProperty('--accent-light', isDark
|
||||
? `color-mix(in srgb, ${color} 10%, transparent)`
|
||||
: `color-mix(in srgb, ${color} 8%, transparent)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-open last vault if available
|
||||
if (config.active_vault) {
|
||||
try {
|
||||
await openVault(config.active_vault);
|
||||
$vaultReady = true;
|
||||
} catch {
|
||||
// Vault path might not exist anymore
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// First launch or no config
|
||||
}
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
{:else if $vaultReady}
|
||||
<AppLayout />
|
||||
{:else}
|
||||
<VaultPicker />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user