Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions src/lib/Installer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { getCurrentWindow } from '@tauri-apps/api/window';
import { onMount } from 'svelte';
import iconUrl from '../assets/icon.png';
import { t } from './utils/i18n.js';

let installing = $state(false);
let error = $state('');
Expand Down Expand Up @@ -65,7 +66,7 @@
error = e.toString();
installing = false;
if (error.includes('Access is denied') && (isInstalled ? installedAllUsers : allUsers)) {
error = 'Access denied. Please run as Administrator.';
error = t('installer.accessDenied');
}
}
}
Expand Down Expand Up @@ -97,23 +98,23 @@

<div class="installer-container" data-tauri-drag-region>
<div class="window-controls">
<button class="control-btn close-btn" onclick={closeApp} aria-label="Close">
<button class="control-btn close-btn" onclick={closeApp} aria-label={t('common.close')}>
<svg width="12" height="12" viewBox="0 0 12 12"><path fill="currentColor" d="M11 1.7L10.3 1 6 5.3 1.7 1 1 1.7 5.3 6 1 10.3 1.7 11 6 6.7 10.3 11 11 10.3 6.7 6z" /></svg>
</button>
</div>

<div class="content">
<div class="header">
<img src={iconUrl} alt="App Icon" class="app-icon" />
<h1>Markdown Viewer</h1>
<h1>{t('installer.markdownViewer')}</h1>
{#if isInstalled}
<div class="version-comparison">
<span class="v-label">Current:</span> v{installedVersion}
<span class="v-label">{t('installer.current')}</span> v{installedVersion}
<span class="v-arrow">→</span>
<span class="v-label">Target:</span> v{installerVersion}
<span class="v-label">{t('installer.target')}</span> v{installerVersion}
</div>
{:else}
<p class="subtitle">A simple markdown viewer <span class="v-lite">v{installerVersion}</span></p>
<p class="subtitle">{t('installer.simpleMarkdownViewer')} <span class="v-lite">v{installerVersion}</span></p>
{/if}
</div>

Expand All @@ -123,8 +124,8 @@
<div class="setup-box">
{#if !isInstalled}
<div class="scope-toggle">
<button class:active={!allUsers} onclick={() => (allUsers = false)}>Just Me</button>
<button class:active={allUsers} onclick={() => (allUsers = true)}>All Users</button>
<button class:active={!allUsers} onclick={() => (allUsers = false)}>{t('installer.justMe')}</button>
<button class:active={allUsers} onclick={() => (allUsers = true)}>{t('installer.allUsers')}</button>
</div>
{/if}

Expand All @@ -134,39 +135,39 @@
<label class="checkbox-container">
<input type="checkbox" bind:checked={registerMd} />
<span class="checkmark"></span>
Register as default for .md files
{t('installer.registerMd')}
</label>
<label class="checkbox-container">
<input type="checkbox" bind:checked={desktopShortcut} />
<span class="checkmark"></span>
Create desktop shortcut
{t('installer.createDesktopShortcut')}
</label>
<label class="checkbox-container">
<input type="checkbox" bind:checked={startMenu} />
<span class="checkmark"></span>
Add to Start Menu
{t('installer.addToStartMenu')}
</label>
<label class="checkbox-container">
<input type="checkbox" bind:checked={launchAfter} />
<span class="checkmark"></span>
Launch after installation
{t('installer.launchAfterInstallation')}
</label>
</div>
{:else}
<div class="maintenance-options">
<p class="status-msg">
Installed for: <strong>{installedAllUsers ? 'All Users' : 'Current User'}</strong>
{t('installer.installedFor')} <strong>{installedAllUsers ? t('installer.allUsers') : 'Current User'}</strong>
</p>
<div class="options">
<label class="checkbox-container">
<input type="checkbox" bind:checked={registerMd} />
<span class="checkmark"></span>
Repair file associations
{t('installer.repairFileAssociations')}
</label>
<label class="checkbox-container">
<input type="checkbox" bind:checked={launchAfter} />
<span class="checkmark"></span>
Launch after update
{t('installer.launchAfterUpdate')}
</label>
</div>
</div>
Expand All @@ -181,25 +182,25 @@

<div class="actions">
{#if isInstalled}
<button class="uninstall-btn" onclick={handleUninstall}>Uninstall</button>
<button class="install-btn" onclick={handleInstall}>Update / Repair</button>
<button class="uninstall-btn" onclick={handleUninstall}>{t('installer.uninstall')}</button>
<button class="install-btn" onclick={handleInstall}>{t('installer.updateRepair')}</button>
{:else}
<button class="install-btn" onclick={handleInstall}>
Install {allUsers ? 'for All Users' : 'Now'}
{t(allUsers ? 'installer.installForAllUsers' : 'installer.installNow')}
</button>
{/if}
</div>

<div class="notice-container">
{#if allUsers || (isInstalled && installedAllUsers)}
<p class="admin-notice">Requires Administrator privileges</p>
<p class="admin-notice">{t('installer.requiresAdmin')}</p>
{/if}
</div>
</div>
{:else}
<div class="installing-state">
<div class="spinner"></div>
<p>{isInstalled ? 'Updating' : 'Installing'} Markpad...</p>
<p>{t(isInstalled ? 'installer.updating' : 'installer.installing')} {t('installer.markpad')}</p>
</div>
{/if}
</div>
Expand Down
95 changes: 51 additions & 44 deletions src/lib/MarkdownViewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import { processMarkdownHtml } from './utils/markdown';

import DOMPurify from 'dompurify';
import HomePage from './components/HomePage.svelte';
import { tabManager } from './stores/tabs.svelte.js';
import { settings } from './stores/settings.svelte.js';
import { tabManager } from './stores/tabs.svelte.js';
import { settings } from './stores/settings.svelte.js';
import { t } from './utils/i18n.js';

// syntax highlighting & latex
let hljs: any = $state(null);
Expand All @@ -40,6 +41,12 @@ import { processMarkdownHtml } from './utils/markdown';

let showSettings = $state(false);

let uiLanguage = $state(settings.language);

$effect(() => {
uiLanguage = settings.language;
});

let recentFiles = $state<string[]>([]);
let isFocused = $state(true);

Expand Down Expand Up @@ -238,8 +245,8 @@ import { processMarkdownHtml } from './utils/markdown';
if (settings.restoreStateOnReopen) {
const hasUnsaved = tabManager.tabs.some((t) => t.isDirty || (t.path === '' && t.rawContent.trim() !== ''));
if (hasUnsaved) {
const response = await askCustom(`Are you sure you want to exit? All unsaved tabs and local history will be lost.`, {
title: 'Confirm Exit',
const response = await askCustom(t('modal.exit.unsaved.message'), {
title: t('modal.exit.unsaved.title'),
kind: 'warning',
showSave: false,
});
Expand Down Expand Up @@ -1048,8 +1055,8 @@ import { processMarkdownHtml } from './utils/markdown';

if (!tab.isDirty) return true;

const response = await askCustom(`You have unsaved changes in "${tab.title}". Do you want to save them before closing?`, {
title: 'Unsaved Changes',
const response = await askCustom(t('modal.youHaveUnsavedChanges', settings.language).replace('{title}', tab.title), {
title: t('modal.unsavedChanges.title'),
kind: 'warning',
showSave: true,
});
Expand All @@ -1070,14 +1077,14 @@ import { processMarkdownHtml } from './utils/markdown';
// Switch back to view
if (tab.isDirty && tab.path !== '') {
if (autoSave) {
const success = await saveContent();
if (!success) return; // If save fails, stay in edit mode?
} else {
const response = await askCustom('You have unsaved changes. Do you want to save them before returning to view mode?', {
title: 'Unsaved Changes',
kind: 'warning',
showSave: true,
});
const success = await saveContent();
if (!success) return; // If save fails, stay in edit mode?
} else {
const response = await askCustom(t('modal.unsavedChanges.viewMode.message'), {
title: t('modal.unsavedChanges.title'),
kind: 'warning',
showSave: true,
});

if (response === 'cancel') return;
if (response === 'save') {
Expand Down Expand Up @@ -1319,7 +1326,7 @@ import { processMarkdownHtml } from './utils/markdown';
const filename = tab?.path ? tab.path.split(/[/\\]/).pop()?.replace(/\.[^.]+$/, '') || '' : '';
const ref = filename ? `[[${filename}#${text}]]` : `#${text}`;
copyRefItem = [
{ label: 'Copy Reference', onClick: () => invoke('clipboard_write_text', { text: ref }) },
{ label: t('menu.copyReference', uiLanguage), onClick: () => invoke('clipboard_write_text', { text: ref }) },
{ separator: true },
];
}
Expand All @@ -1328,15 +1335,15 @@ import { processMarkdownHtml } from './utils/markdown';
let mediaItems: any[] = [];
if (img) {
mediaItems = [
{ label: 'Save Image As...', onClick: () => saveImageAs(img.src) },
{ label: t('menu.saveImageAs', uiLanguage), onClick: () => saveImageAs(img.src) },
{ separator: true }
];
}

const mermaidDiag = (e.target as HTMLElement).closest('.mermaid-diagram');
if (mermaidDiag) {
mediaItems = [
{ label: 'Save Diagram As SVG...', onClick: () => saveDiagramAs(mermaidDiag as HTMLElement) },
{ label: t('menu.saveDiagramAsSvg', uiLanguage), onClick: () => saveDiagramAs(mermaidDiag as HTMLElement) },
{ separator: true }
];
}
Expand All @@ -1350,16 +1357,16 @@ import { processMarkdownHtml } from './utils/markdown';
...mediaItems,
...(isEditing && isInsideEditor
? [
{ label: 'Undo', shortcut: 'Ctrl+Z', onClick: () => editorPane?.undo() },
{ label: 'Redo', shortcut: 'Ctrl+Y', onClick: () => editorPane?.redo() },
{ label: t('menu.undo', uiLanguage), shortcut: 'Ctrl+Z', onClick: () => editorPane?.undo() },
{ label: t('menu.redo', uiLanguage), shortcut: 'Ctrl+Y', onClick: () => editorPane?.redo() },
{ separator: true }
]
: []),
...(hasSelection ? [{ label: 'Copy', onClick: () => {
...(hasSelection ? [{ label: t('menu.copy', uiLanguage), onClick: () => {
const selection = window.getSelection()?.toString();
if (selection) invoke('clipboard_write_text', { text: selection });
} }] : []),
{ label: 'Select All', onClick: () => {
{ label: t('menu.selectAll', uiLanguage), onClick: () => {
if (!markdownBody) return;
const range = document.createRange();
range.selectNodeContents(markdownBody);
Expand All @@ -1368,10 +1375,10 @@ import { processMarkdownHtml } from './utils/markdown';
selection?.addRange(range);
} },
{ separator: true },
{ label: 'Open File Location', onClick: openFileLocation, disabled: !currentFile },
{ label: 'Edit', onClick: () => toggleEdit() },
{ label: t('menu.openLocation', uiLanguage), onClick: openFileLocation, disabled: !currentFile },
{ label: t('menu.edit', uiLanguage), onClick: () => toggleEdit() },
{ separator: true },
{ label: 'Close File', onClick: closeFile },
{ label: t('menu.closeFile', uiLanguage), onClick: closeFile },
],
};
}
Expand Down Expand Up @@ -1521,8 +1528,8 @@ import { processMarkdownHtml } from './utils/markdown';
const success = await saveContent();
if (!success) return;
} else {
const response = await askCustom('You have unsaved changes. Do you want to save them before closing split view?', {
title: 'Unsaved Changes',
const response = await askCustom(t('modal.unsavedChanges.splitView.message'), {
title: t('modal.unsavedChanges.title'),
kind: 'warning',
showSave: true,
});
Expand Down Expand Up @@ -1797,7 +1804,7 @@ import { processMarkdownHtml } from './utils/markdown';
const tab = tabManager.tabs.find((t) => t.id === tabId);
if (!tab || !tab.path) return;

const newName = window.prompt('Rename file:', tab.title);
const newName = window.prompt(t('menu.renameFile', settings.language), tab.title);
if (newName && newName !== tab.title) {
const oldPath = tab.path;
const newPath = oldPath.replace(/[/\\][^/\\]+$/, (m) => m.charAt(0) + newName);
Expand Down Expand Up @@ -1865,12 +1872,12 @@ import { processMarkdownHtml } from './utils/markdown';
console.log('Dirty tabs:', dirtyTabs.length);
if (dirtyTabs.length > 0) {
console.log('Preventing default close');
event.preventDefault();
const response = await askCustom(`You have ${dirtyTabs.length} unsaved file(s). Do you want to save your changes?`, {
title: 'Unsaved Changes',
kind: 'warning',
showSave: true,
});
event.preventDefault();
const response = await askCustom(t('modal.youHaveUnsavedFiles', settings.language).replace('{{count}}', dirtyTabs.length.toString()), {
title: t('modal.unsavedChanges', settings.language),
kind: 'warning',
showSave: true,
});

if (response === 'save') {
// Attempt to save all dirty tabs
Expand Down Expand Up @@ -1940,8 +1947,8 @@ import { processMarkdownHtml } from './utils/markdown';
if (ext && ['md', 'markdown', 'txt'].includes(ext)) {
loadMarkdown(path);
} else {
const filename = path.split(/[/\\]/).pop() || 'File';
addToast(`Unsupported file type: ${filename}`, 'error');
const filename = path.split(/[\/\\]/).pop() || 'File';
addToast(t('toast.unsupportedFile').replace('{{filename}}', filename), 'error');
}
});
}
Expand Down Expand Up @@ -2191,7 +2198,7 @@ import { processMarkdownHtml } from './utils/markdown';
y: e.clientY,
items: [
{
label: 'Copy Reference',
label: t('menu.copyReference', uiLanguage),
onClick: () => {
const tab = tabManager.activeTab;
const fn = tab?.path ? tab.path.split(/[/\\]/).pop()?.replace(/\.[^.]+$/, '') || '' : '';
Expand Down Expand Up @@ -2272,17 +2279,17 @@ import { processMarkdownHtml } from './utils/markdown';
<div class="drag-zones" class:split={isSplit}>
{#if isSplit || isEditing}
<div class="drag-zone editor-zone" class:active={dragTarget === 'editor'}>
<div class="drag-message">
<span>Drop to Embed</span>
</div>
</div>
<div class="drag-message">
<span>{t('dragAndDrop.embed')}</span>
</div>
</div>
{/if}
{#if isSplit || !isEditing}
<div class="drag-zone viewer-zone" class:active={dragTarget === 'preview'}>
<div class="drag-message">
<span>Drop to Open</span>
</div>
</div>
<div class="drag-message">
<span>{t('dragAndDrop.open')}</span>
</div>
</div>
{/if}
</div>
</div>
Expand Down
13 changes: 7 additions & 6 deletions src/lib/Uninstaller.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import iconUrl from '../assets/icon.png';
import { t } from './utils/i18n.js';

let uninstalling = $state(false);
let error = $state('');
Expand All @@ -27,16 +28,16 @@

<div class="installer-container" data-tauri-drag-region>
<div class="window-controls">
<button class="control-btn close-btn" onclick={closeApp} aria-label="Close">
<button class="control-btn close-btn" onclick={closeApp} aria-label={t('common.close')}>
<svg width="12" height="12" viewBox="0 0 12 12"><path fill="currentColor" d="M11 1.7L10.3 1 6 5.3 1.7 1 1 1.7 5.3 6 1 10.3 1.7 11 6 6.7 10.3 11 11 10.3 6.7 6z" /></svg>
</button>
</div>

<div class="content">
<div class="header">
<img src={iconUrl} alt="App Icon" class="app-icon" />
<h1>Uninstall Markpad?</h1>
<p class="subtitle">This will remove the application and all its shortcuts.</p>
<h1>{t('uninstaller.uninstallMarkpad')}</h1>
<p class="subtitle">{t('uninstaller.removeApplication')}</p>
</div>

{#if !uninstalling}
Expand All @@ -46,14 +47,14 @@
{/if}

<div class="actions">
<button class="cancel-btn" onclick={closeApp}>Cancel</button>
<button class="uninstall-btn" onclick={handleUninstall}> Uninstall </button>
<button class="cancel-btn" onclick={closeApp}>{t('uninstaller.cancel')}</button>
<button class="uninstall-btn" onclick={handleUninstall}>{t('uninstaller.uninstall')}</button>
</div>
</div>
{:else}
<div class="installing-state">
<div class="spinner"></div>
<p>Removing Markpad...</p>
<p>{t('uninstaller.removingMarkpad')}</p>
</div>
{/if}
</div>
Expand Down
Loading
Loading