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
27 changes: 25 additions & 2 deletions krillnotes-desktop/android-dev.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
# Launch Android emulator dev build
# Usage: ./android-dev.sh
# Launch Android dev build on emulator or physical device
# Usage: ./android-dev.sh # auto-detect (prefers running emulator)
# ./android-dev.sh --device # target USB-connected physical device
# ./android-dev.sh --emulator # target emulator only

# Kill any lingering Vite dev server
lsof -ti:1420 | xargs kill -9 2>/dev/null
Expand All @@ -14,4 +16,25 @@ export NDK_HOME="$ANDROID_HOME/ndk/26.3.11579264"
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
export PATH="$JAVA_HOME/bin:$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin:$ANDROID_HOME/platform-tools:$PATH"

case "${1:-}" in
--device)
if ! adb devices | grep -qw "device$"; then
echo "❌ No physical device found. Check USB connection and USB debugging." >&2
exit 1
fi
SERIAL=$(adb devices | awk '/\tdevice$/ {print $1; exit}')
echo "📱 Targeting physical device: $SERIAL"
;;
--emulator)
if ! adb devices | grep -q "^emulator-"; then
echo "❌ No emulator found. Start one from Android Studio first." >&2
exit 1
fi
echo "📱 Targeting emulator"
;;
*)
echo "📱 Auto-detecting target (use --device or --emulator to override)"
;;
esac

npm run tauri android dev
2 changes: 2 additions & 0 deletions krillnotes-desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub struct AppState {
#[cfg(desktop)]
pub export_menu_item: Arc<Mutex<Option<tauri::menu::MenuItem<tauri::Wry>>>>,
/// Handle to the "Manage Scripts" menu item, toggled based on ownership.
#[cfg(desktop)]
pub manage_scripts_menu_item: Arc<Mutex<Option<tauri::menu::MenuItem<tauri::Wry>>>>,
/// Window labels that have been approved for closing by the frontend.
/// When a label is in this set, the next `CloseRequested` event for
Expand Down Expand Up @@ -222,6 +223,7 @@ pub fn run() {
workspace_menu_items: Arc::new(Mutex::new(HashMap::new())),
#[cfg(desktop)]
export_menu_item: Arc::new(Mutex::new(None)),
#[cfg(desktop)]
manage_scripts_menu_item: Arc::new(Mutex::new(None)),
closing_windows: Arc::new(Mutex::new(HashSet::new())),
})
Expand Down
4 changes: 2 additions & 2 deletions krillnotes-desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2",
"productName": "Krillnotes",
"version": "1.1.1",
"identifier": "com.2pisoftware.krillnotes",
"identifier": "com.twopisoftware.krillnotes",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
Expand All @@ -14,7 +14,7 @@
{
"title": "Krillnotes",
"width": 800,
"height": 600,
"height": 700,
"dragDropEnabled": false
}
],
Expand Down
35 changes: 31 additions & 4 deletions krillnotes-desktop/src/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useLayout } from '../hooks/useLayout';
import type { Note, SchemaInfo } from '../types';

interface ContextMenuProps {
x: number;
Expand All @@ -31,6 +32,12 @@ interface ContextMenuProps {
onShareSubtree?: (noteId: string) => void;
onDelete: () => void;
onClose: () => void;
notes?: Note[];
schemas?: Record<string, SchemaInfo>;
onMoveUp?: (noteId: string) => void;
onMoveDown?: (noteId: string) => void;
onIndent?: (noteId: string) => void;
onOutdent?: (noteId: string) => void;
}

function ContextMenu({
Expand All @@ -39,6 +46,7 @@ function ContextMenu({
onAddChild, onAddSibling, onAddRoot,
onEdit, onCopy, onPasteAsChild, onPasteAsSibling,
onTreeAction, onInviteToSubtree, onShareSubtree, onDelete, onClose,
notes, schemas, onMoveUp, onMoveDown, onIndent, onOutdent,
}: ContextMenuProps) {
const { t } = useTranslation();
const layout = useLayout();
Expand Down Expand Up @@ -72,6 +80,16 @@ function ContextMenu({
const canAddSibling = canWrite && !(isRootNote && isRootOwner === false);
const itemClass = isPhone ? 'py-3 px-4 text-base' : 'py-1.5 px-3 text-sm';

const showMoveActions = layout !== 'desktop' && canWrite && !!noteId && !!notes && !!onMoveUp;
const moveNote = showMoveActions ? notes!.find(n => n.id === noteId) : null;
const moveSiblings = moveNote ? notes!.filter(n => n.parentId === moveNote.parentId).sort((a, b) => a.position - b.position) : [];
const moveIdx = moveNote ? moveSiblings.findIndex(n => n.id === noteId) : -1;
const canMoveUp = moveIdx > 0;
const canMoveDown = moveIdx >= 0 && moveIdx < moveSiblings.length - 1;
const siblingAbove = moveIdx > 0 ? moveSiblings[moveIdx - 1] : null;
const canIndentNote = siblingAbove != null && !(schemas?.[siblingAbove.schema ?? '']?.isLeaf);
const canOutdentNote = !!moveNote?.parentId;

return createPortal(
<>
{isPhone && <div className="fixed inset-0 bg-black/40 z-40" />}
Expand Down Expand Up @@ -128,7 +146,7 @@ function ContextMenu({
{t('common.edit')}
</button>
<button
className="w-full text-left ${itemClass} hover:bg-secondary"
className={`w-full text-left ${itemClass} hover:bg-secondary`}
onClick={() => { onCopy(); onClose(); }}
>
{t('notes.copyNote')}
Expand All @@ -145,13 +163,22 @@ function ContextMenu({
>
{t('notes.pasteAsSibling')}
</button>
{showMoveActions && noteId && onMoveUp && onMoveDown && onIndent && onOutdent && (
<>
<div className="border-t border-secondary my-1" />
<button disabled={!canMoveUp} className={`w-full text-left ${itemClass} ${canMoveUp ? 'hover:bg-secondary' : 'opacity-40 cursor-not-allowed'}`} onClick={canMoveUp ? () => onMoveUp(noteId) : undefined}>{t('contextMenu.moveUp')}</button>
<button disabled={!canMoveDown} className={`w-full text-left ${itemClass} ${canMoveDown ? 'hover:bg-secondary' : 'opacity-40 cursor-not-allowed'}`} onClick={canMoveDown ? () => onMoveDown(noteId) : undefined}>{t('contextMenu.moveDown')}</button>
<button disabled={!canIndentNote} className={`w-full text-left ${itemClass} ${canIndentNote ? 'hover:bg-secondary' : 'opacity-40 cursor-not-allowed'}`} onClick={canIndentNote ? () => onIndent(noteId) : undefined}>{t('contextMenu.indent')}</button>
<button disabled={!canOutdentNote} className={`w-full text-left ${itemClass} ${canOutdentNote ? 'hover:bg-secondary' : 'opacity-40 cursor-not-allowed'}`} onClick={canOutdentNote ? () => onOutdent(noteId) : undefined}>{t('contextMenu.outdent')}</button>
</>
)}
{treeActions.length > 0 && (
<>
<div className="border-t border-secondary my-1" />
{treeActions.map((label) => (
<button
key={label}
className="w-full text-left ${itemClass} hover:bg-secondary"
className={`w-full text-left ${itemClass} hover:bg-secondary`}
onClick={() => { onTreeAction(label); onClose(); }}
>
{label}
Expand All @@ -163,7 +190,7 @@ function ContextMenu({
<>
<div className="border-t border-secondary my-1" />
<button
className="w-full text-left ${itemClass} hover:bg-secondary"
className={`w-full text-left ${itemClass} hover:bg-secondary`}
onClick={() => { onInviteToSubtree(noteId); onClose(); }}
>
{t('contextMenu.inviteToSubtree', 'Invite to this subtree\u2026')}
Expand All @@ -173,7 +200,7 @@ function ContextMenu({
{noteId && canManage && onShareSubtree && (
<button
onClick={() => { onShareSubtree(noteId); onClose(); }}
className="w-full text-left ${itemClass} hover:bg-secondary"
className={`w-full text-left ${itemClass} hover:bg-secondary`}
>
{t('contextMenu.shareSubtree', 'Share subtree\u2026')}
</button>
Expand Down
4 changes: 2 additions & 2 deletions krillnotes-desktop/src/components/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function TreeView({
if (tree.length === 0) {
return (
<div
className="flex items-center justify-center h-full text-muted-foreground text-sm focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary"
className="flex items-center justify-center h-full text-muted-foreground text-sm outline-none"
tabIndex={0}
onKeyDown={onKeyDown}
onDragOver={handleRootDragOver}
Expand All @@ -92,7 +92,7 @@ function TreeView({

return (
<div
className="h-full focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary"
className="h-full outline-none"
tabIndex={0}
onKeyDown={onKeyDown}
onDragOver={handleRootDragOver}
Expand Down
47 changes: 47 additions & 0 deletions krillnotes-desktop/src/components/WorkspaceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,47 @@ function WorkspaceView({ workspaceInfo, onOpenWorkspacePeers, sharingIndicatorMo
}
};

const handleMoveUp = async (noteId: string) => {
const note = notes.find(n => n.id === noteId);
if (!note) return;
const siblings = notes.filter(n => n.parentId === note.parentId).sort((a, b) => a.position - b.position);
const idx = siblings.findIndex(n => n.id === noteId);
if (idx <= 0) return;
await handleMoveNote(noteId, note.parentId, siblings[idx - 1].position);
};

const handleMoveDown = async (noteId: string) => {
const note = notes.find(n => n.id === noteId);
if (!note) return;
const siblings = notes.filter(n => n.parentId === note.parentId).sort((a, b) => a.position - b.position);
const idx = siblings.findIndex(n => n.id === noteId);
if (idx < 0 || idx >= siblings.length - 1) return;
await handleMoveNote(noteId, note.parentId, siblings[idx + 1].position);
};

const handleIndent = async (noteId: string) => {
const note = notes.find(n => n.id === noteId);
if (!note) return;
const siblings = notes.filter(n => n.parentId === note.parentId).sort((a, b) => a.position - b.position);
const idx = siblings.findIndex(n => n.id === noteId);
if (idx <= 0) return;
const newParent = siblings[idx - 1];
const newParentSchema = schemas[newParent.schema ?? ''];
if (newParentSchema?.isLeaf) return;
const children = notes.filter(n => n.parentId === newParent.id);
await handleMoveNote(noteId, newParent.id, children.length);
};

const handleOutdent = async (noteId: string) => {
const note = notes.find(n => n.id === noteId);
if (!note || !note.parentId) return;
const parent = notes.find(n => n.id === note.parentId);
if (!parent) return;
const parentSiblings = notes.filter(n => n.parentId === parent.parentId).sort((a, b) => a.position - b.position);
const parentIdx = parentSiblings.findIndex(n => n.id === parent.id);
await handleMoveNote(noteId, parent.parentId, parentIdx + 1);
};

const handleNoteCreated = async (noteId: string) => {
const fetchedNotes = await loadNotes();
await loadPermissionState();
Expand Down Expand Up @@ -788,6 +829,12 @@ function WorkspaceView({ workspaceInfo, onOpenWorkspacePeers, sharingIndicatorMo
onShareSubtree={handleShareSubtree}
onDelete={() => contextMenu.noteId && handleContextDelete(contextMenu.noteId)}
onClose={() => setContextMenu(null)}
notes={notes}
schemas={schemas}
onMoveUp={(id) => { handleMoveUp(id); setContextMenu(null); }}
onMoveDown={(id) => { handleMoveDown(id); setContextMenu(null); }}
onIndent={(id) => { handleIndent(id); setContextMenu(null); }}
onOutdent={(id) => { handleOutdent(id); setContextMenu(null); }}
/>
)}

Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "Zu diesem Teilbaum einladen…",
"shareSubtree": "Teilbaum teilen…",
"noAccess": "Kein Zugang"
"noAccess": "Kein Zugang",
"moveUp": "Nach oben",
"moveDown": "Nach unten",
"indent": "Einrücken",
"outdent": "Ausrücken"
},
"invite": {
"scope": "Teilbaum-Bereich",
Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "Invite to this subtree…",
"shareSubtree": "Share subtree...",
"noAccess": "No access"
"noAccess": "No access",
"moveUp": "Move up",
"moveDown": "Move down",
"indent": "Move into above",
"outdent": "Move out of parent"
},
"invite": {
"scope": "Subtree scope",
Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "Invitar a este subárbol…",
"shareSubtree": "Compartir subárbol…",
"noAccess": "Sin acceso"
"noAccess": "Sin acceso",
"moveUp": "Mover arriba",
"moveDown": "Mover abajo",
"indent": "Mover dentro",
"outdent": "Mover fuera"
},
"invite": {
"scope": "Alcance del subárbol",
Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "Inviter à ce sous-arbre…",
"shareSubtree": "Partager le sous-arbre…",
"noAccess": "Pas d'accès"
"noAccess": "Pas d'accès",
"moveUp": "Déplacer vers le haut",
"moveDown": "Déplacer vers le bas",
"indent": "Déplacer à l'intérieur",
"outdent": "Déplacer à l'extérieur"
},
"invite": {
"scope": "Portée du sous-arbre",
Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "このサブツリーに招待…",
"shareSubtree": "サブツリーを共有…",
"noAccess": "アクセス権なし"
"noAccess": "アクセス権なし",
"moveUp": "上に移動",
"moveDown": "下に移動",
"indent": "内側に移動",
"outdent": "外側に移動"
},
"invite": {
"scope": "サブツリーの範囲",
Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "이 하위 트리에 초대…",
"shareSubtree": "하위 트리 공유...",
"noAccess": "접근 불가"
"noAccess": "접근 불가",
"moveUp": "위로 이동",
"moveDown": "아래로 이동",
"indent": "안으로 이동",
"outdent": "밖으로 이동"
},
"invite": {
"scope": "하위 트리 범위",
Expand Down
6 changes: 5 additions & 1 deletion krillnotes-desktop/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,11 @@
"contextMenu": {
"inviteToSubtree": "邀请到此子树…",
"shareSubtree": "共享子树…",
"noAccess": "无权访问"
"noAccess": "无权访问",
"moveUp": "上移",
"moveDown": "下移",
"indent": "移入",
"outdent": "移出"
},
"invite": {
"scope": "子树范围",
Expand Down
Loading