mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-23 23:35:57 +02:00
Fix #63: decide layout from compile-time platform, not the webview user-agent
This commit is contained in:
@@ -38,7 +38,21 @@ pub fn run() {
|
|||||||
let close_to_tray = config.close_to_tray && show_tray;
|
let close_to_tray = config.close_to_tray && show_tray;
|
||||||
let app_state = AppState::new(config);
|
let app_state = AppState::new(config);
|
||||||
|
|
||||||
|
// Inject the compile-time platform so the frontend never sniffs the (sometimes
|
||||||
|
// mobile-looking) WebKitGTK user-agent. (#63)
|
||||||
|
let platform_init = format!(
|
||||||
|
"window.__HELIX_PLATFORM__={{mobile:{},android:{},ios:{}}};",
|
||||||
|
cfg!(mobile),
|
||||||
|
cfg!(target_os = "android"),
|
||||||
|
cfg!(target_os = "ios"),
|
||||||
|
);
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
|
.plugin(
|
||||||
|
tauri::plugin::Builder::<tauri::Wry>::new("helix-platform")
|
||||||
|
.js_init_script(platform_init)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
|||||||
@@ -58,9 +58,8 @@
|
|||||||
const appWindow = getCurrentWindow();
|
const appWindow = getCurrentWindow();
|
||||||
const isMac = navigator.platform.startsWith('Mac');
|
const isMac = navigator.platform.startsWith('Mac');
|
||||||
const isMobile = $derived($platformIsMobile);
|
const isMobile = $derived($platformIsMobile);
|
||||||
const isAndroid = /android/i.test(navigator.userAgent);
|
|
||||||
import { loadVaultState, saveVaultState, readNote, createDailyNote, createBackup, getPendingOpenFile, addQuickAccess, removeQuickAccess, getQuickAccess, setTheme, syncNow, setTaskDone, setTaskPriority, setTaskDue } from '$lib/api';
|
import { loadVaultState, saveVaultState, readNote, createDailyNote, createBackup, getPendingOpenFile, addQuickAccess, removeQuickAccess, getQuickAccess, setTheme, syncNow, setTaskDone, setTaskPriority, setTaskDue } from '$lib/api';
|
||||||
import { darkThemes } from '$lib/platform';
|
import { darkThemes, isAndroid } from '$lib/platform';
|
||||||
import { debounce } from '$lib/utils/debounce';
|
import { debounce } from '$lib/utils/debounce';
|
||||||
import { openNoteWindow } from '$lib/utils/window';
|
import { openNoteWindow } from '$lib/utils/window';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|||||||
@@ -49,9 +49,9 @@
|
|||||||
import { wrapTextareaSelection } from '$lib/editor/source/selectionPairs';
|
import { wrapTextareaSelection } from '$lib/editor/source/selectionPairs';
|
||||||
import GraphView from './GraphView.svelte';
|
import GraphView from './GraphView.svelte';
|
||||||
import TagSuggestInput from './TagSuggestInput.svelte';
|
import TagSuggestInput from './TagSuggestInput.svelte';
|
||||||
|
import { isMobile, isAndroid } from '$lib/platform';
|
||||||
|
|
||||||
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
// Track virtual keyboard height on mobile via visualViewport
|
// Track virtual keyboard height on mobile via visualViewport
|
||||||
let keyboardHeight = $state(0);
|
let keyboardHeight = $state(0);
|
||||||
@@ -1028,7 +1028,6 @@
|
|||||||
function addToolbar(container: HTMLElement, source: string) {
|
function addToolbar(container: HTMLElement, source: string) {
|
||||||
const toolbar = document.createElement('div');
|
const toolbar = document.createElement('div');
|
||||||
toolbar.className = 'mermaid-render-toolbar';
|
toolbar.className = 'mermaid-render-toolbar';
|
||||||
const isAndroid = /android/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
if (!isAndroid) {
|
if (!isAndroid) {
|
||||||
const copyBtn = document.createElement('button');
|
const copyBtn = document.createElement('button');
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { getGraphData } from '$lib/api';
|
import { getGraphData } from '$lib/api';
|
||||||
import { activeNotePath, appConfig } from '$lib/stores/app';
|
import { activeNotePath, appConfig } from '$lib/stores/app';
|
||||||
|
import { isMobile } from '$lib/platform';
|
||||||
|
|
||||||
let { onclose, onnavigate }: {
|
let { onclose, onnavigate }: {
|
||||||
onclose: () => void;
|
onclose: () => void;
|
||||||
onnavigate: (path: string, title: string) => void;
|
onnavigate: (path: string, title: string) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
let canvas = $state<HTMLCanvasElement>(null!);
|
let canvas = $state<HTMLCanvasElement>(null!);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let searchQuery = $state('');
|
let searchQuery = $state('');
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
bindingsEqual,
|
bindingsEqual,
|
||||||
type ActionDef,
|
type ActionDef,
|
||||||
} from '$lib/keybindings';
|
} from '$lib/keybindings';
|
||||||
|
import { isMobile } from '$lib/platform';
|
||||||
|
|
||||||
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
// Customizable shortcuts, grouped for display.
|
// Customizable shortcuts, grouped for display.
|
||||||
const actionGroups: { title: string; actions: ActionDef[] }[] = [
|
const actionGroups: { title: string; actions: ActionDef[] }[] = [
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
import type { NoteEntry, TrashNotebookEntry, SortMode, TaskItem } from '$lib/types';
|
import type { NoteEntry, TrashNotebookEntry, SortMode, TaskItem } from '$lib/types';
|
||||||
import TasksView from './TasksView.svelte';
|
import TasksView from './TasksView.svelte';
|
||||||
import TagSuggestInput from './TagSuggestInput.svelte';
|
import TagSuggestInput from './TagSuggestInput.svelte';
|
||||||
|
import { isMobile, isAndroid } from '$lib/platform';
|
||||||
|
|
||||||
let { onNoteSelected = (_path: string, _content: string) => {}, onNoteMoved = () => {}, onBeforeNoteSwitch = () => {}, onNoteCreated = () => {}, onToggleTask = async (_t: TaskItem) => {}, onSetTaskPriority = async (_t: TaskItem, _p: string | null) => {}, onSetTaskDue = async (_t: TaskItem, _d: string | null) => {} }: {
|
let { onNoteSelected = (_path: string, _content: string) => {}, onNoteMoved = () => {}, onBeforeNoteSwitch = () => {}, onNoteCreated = () => {}, onToggleTask = async (_t: TaskItem) => {}, onSetTaskPriority = async (_t: TaskItem, _p: string | null) => {}, onSetTaskDue = async (_t: TaskItem, _d: string | null) => {} }: {
|
||||||
onNoteSelected?: (path: string, content: string) => void;
|
onNoteSelected?: (path: string, content: string) => void;
|
||||||
@@ -55,8 +56,6 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
const isAndroid = /android/i.test(navigator.userAgent);
|
|
||||||
let multiSelectMode = $state(false);
|
let multiSelectMode = $state(false);
|
||||||
let trashNotebooks = $state<TrashNotebookEntry[]>([]);
|
let trashNotebooks = $state<TrashNotebookEntry[]>([]);
|
||||||
let trashBusy = $state<string | null>(null);
|
let trashBusy = $state<string | null>(null);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { searchNotes, readNote } from '$lib/api';
|
import { searchNotes, readNote } from '$lib/api';
|
||||||
import { debounce } from '$lib/utils/debounce';
|
import { debounce } from '$lib/utils/debounce';
|
||||||
import type { SearchResult } from '$lib/types';
|
import type { SearchResult } from '$lib/types';
|
||||||
|
import { isMobile } from '$lib/platform';
|
||||||
|
|
||||||
let query = $state('');
|
let query = $state('');
|
||||||
let results = $state<SearchResult[]>([]);
|
let results = $state<SearchResult[]>([]);
|
||||||
@@ -83,8 +84,6 @@
|
|||||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
async function openResult(result: SearchResult) {
|
async function openResult(result: SearchResult) {
|
||||||
try {
|
try {
|
||||||
const content = await readNote(result.path);
|
const content = await readNote(result.path);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { showSettings, theme, appConfig, updateAvailable as globalUpdateAvailable, updateObj as globalUpdateObj, installType, settingsTab, vaultReady, androidApkUrl, checkForUpdateMobile, notebookSortMode, isManagedInstall } from '$lib/stores/app';
|
import { showSettings, theme, appConfig, updateAvailable as globalUpdateAvailable, updateObj as globalUpdateObj, installType, settingsTab, vaultReady, androidApkUrl, checkForUpdateMobile, notebookSortMode, isManagedInstall } from '$lib/stores/app';
|
||||||
import { setTheme, setAccentColor, setFontSize, setFontFamily, setLineHeight, setUiScale, setContentWidth, setGeneralSettings, importObsidian, createBackup, listBackups, restoreBackup, deleteBackup, setBackupSettings, setAiSettings, testAiConnection, setSyncSettings, testSyncConnection, syncNow } from '$lib/api';
|
import { setTheme, setAccentColor, setFontSize, setFontFamily, setLineHeight, setUiScale, setContentWidth, setGeneralSettings, importObsidian, createBackup, listBackups, restoreBackup, deleteBackup, setBackupSettings, setAiSettings, testAiConnection, setSyncSettings, testSyncConnection, syncNow } from '$lib/api';
|
||||||
import { darkThemes } from '$lib/platform';
|
import { darkThemes, isMobile } from '$lib/platform';
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { getVersion } from '@tauri-apps/api/app';
|
import { getVersion } from '@tauri-apps/api/app';
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
import { openUrl } from '$lib/api';
|
import { openUrl } from '$lib/api';
|
||||||
import type { ImportResult, BackupEntry } from '$lib/types';
|
import type { ImportResult, BackupEntry } from '$lib/types';
|
||||||
|
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
||||||
|
|
||||||
type Tab = 'general' | 'editor' | 'styling' | 'import' | 'backup' | 'ai' | 'sync' | 'updates';
|
type Tab = 'general' | 'editor' | 'styling' | 'import' | 'backup' | 'ai' | 'sync' | 'updates';
|
||||||
|
|||||||
@@ -26,13 +26,13 @@
|
|||||||
import { readFile } from '@tauri-apps/plugin-fs';
|
import { readFile } from '@tauri-apps/plugin-fs';
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
import type { NotebookEntry } from '$lib/types';
|
import type { NotebookEntry } from '$lib/types';
|
||||||
|
import { isMobile } from '$lib/platform';
|
||||||
|
|
||||||
let { onViewChanged = () => {} }: {
|
let { onViewChanged = () => {} }: {
|
||||||
onViewChanged?: () => void;
|
onViewChanged?: () => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
// Whether any top nav item (All Notes, Quick Access, Tasks, Daily Notes, Trash) is visible.
|
// Whether any top nav item (All Notes, Quick Access, Tasks, Daily Notes, Trash) is visible.
|
||||||
// When all are hidden we drop the empty <nav> and its divider so the tree sits flush.
|
// When all are hidden we drop the empty <nav> and its divider so the tree sits flush.
|
||||||
|
|||||||
+14
-5
@@ -1,10 +1,19 @@
|
|||||||
// Centralized platform detection. iOS WebViews report iPhone/iPad/iPod in the user
|
// Platform is the compile-time build target, injected by the backend into
|
||||||
// agent (not the literal "ios"), so they must be matched explicitly.
|
// window.__HELIX_PLATFORM__ before app scripts run (see src-tauri/src/lib.rs). The UA is only a
|
||||||
|
// fallback when that global is absent (SSR/prerender, plain-browser dev): some desktop WebKitGTK
|
||||||
|
// builds report a mobile-looking user-agent. (#63)
|
||||||
|
type HelixPlatform = { mobile: boolean; android: boolean; ios: boolean };
|
||||||
|
|
||||||
|
const injected: HelixPlatform | undefined =
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
? (window as unknown as { __HELIX_PLATFORM__?: HelixPlatform }).__HELIX_PLATFORM__
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
||||||
|
|
||||||
export const isAndroid = /android/i.test(ua);
|
export const isAndroid = injected ? injected.android : /android/i.test(ua);
|
||||||
export const isIOS = /iphone|ipad|ipod/i.test(ua);
|
export const isIOS = injected ? injected.ios : /iphone|ipad|ipod/i.test(ua);
|
||||||
export const isMobile = isAndroid || isIOS;
|
export const isMobile = injected ? injected.mobile : isAndroid || isIOS;
|
||||||
|
|
||||||
// Themes that use the dark color scheme. Used by applyTheme() to toggle the
|
// Themes that use the dark color scheme. Used by applyTheme() to toggle the
|
||||||
// `dark` class on the root element. Keep this in sync when adding new themes.
|
// `dark` class on the root element. Keep this in sync when adding new themes.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { writable, derived, get } from "svelte/store";
|
import { writable, derived, get } from "svelte/store";
|
||||||
|
import { isMobile } from "$lib/platform";
|
||||||
import type {
|
import type {
|
||||||
AppConfig,
|
AppConfig,
|
||||||
NoteEntry,
|
NoteEntry,
|
||||||
@@ -87,11 +88,8 @@ export const updateAvailable = writable<{
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
export const updateObj = writable<any>(null);
|
export const updateObj = writable<any>(null);
|
||||||
export const installType = writable<string>("native");
|
export const installType = writable<string>("native");
|
||||||
// True only on the Android/iOS build. Defaults to a user-agent guess for the first paint, then
|
// True only on the Android/iOS build. Reconfirmed from the backend at startup (see +layout). (#63)
|
||||||
// gets the authoritative compile-time value from the backend at startup (see +layout). (#63)
|
export const platformIsMobile = writable<boolean>(isMobile);
|
||||||
export const platformIsMobile = writable<boolean>(
|
|
||||||
typeof navigator !== "undefined" && /android|iphone|ipad|ipod/i.test(navigator.userAgent),
|
|
||||||
);
|
|
||||||
export const androidApkUrl = writable<string | null>(null);
|
export const androidApkUrl = writable<string | null>(null);
|
||||||
|
|
||||||
// Install types that handle their own updates (in-app auto-updater, or a
|
// Install types that handle their own updates (in-app auto-updater, or a
|
||||||
|
|||||||
@@ -4,13 +4,11 @@
|
|||||||
import { theme, appConfig, activeNote, activeNotePath, installType, platformIsMobile, checkForUpdate, checkForUpdateMobile, isManagedInstall } from '$lib/stores/app';
|
import { theme, appConfig, activeNote, activeNotePath, installType, platformIsMobile, checkForUpdate, checkForUpdateMobile, isManagedInstall } from '$lib/stores/app';
|
||||||
import { openFile, openUrl, readNote, getInstallType, isMobilePlatform } from '$lib/api';
|
import { openFile, openUrl, readNote, getInstallType, isMobilePlatform } from '$lib/api';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { darkThemes } from '$lib/platform';
|
import { darkThemes, isMobile, isAndroid } from '$lib/platform';
|
||||||
import ResizeHandles from '$lib/components/ResizeHandles.svelte';
|
import ResizeHandles from '$lib/components/ResizeHandles.svelte';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
const isMobile = /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
// Reactively apply theme class to <html> whenever $theme changes
|
// Reactively apply theme class to <html> whenever $theme changes
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
applyTheme($theme);
|
applyTheme($theme);
|
||||||
@@ -46,8 +44,6 @@
|
|||||||
return resolved.join('/');
|
return resolved.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAndroid = /android/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
function openLocalFile(path: string) {
|
function openLocalFile(path: string) {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
const bridge = (window as any).Android;
|
const bridge = (window as any).Android;
|
||||||
|
|||||||
Reference in New Issue
Block a user