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
2 changes: 1 addition & 1 deletion app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions app/src/screens/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReactMarkdown from 'react-markdown'
import type { Components } from 'react-markdown'
import remarkGfm from 'remark-gfm'
import remarkBreaks from 'remark-breaks'
import { openUrl } from '@tauri-apps/plugin-opener'
import { NoteApiError, noteDiscard, noteRead, noteSave } from '../api/notes'
import { toggleTodo } from '../api/todos'
import { workspaceActions, useWorkspaceStore } from '../stores/workspaceStore'
Expand All @@ -11,6 +12,7 @@ import { Toast } from '../components/Toast'
import { TodoRow } from '../components/TodoRow'
import { workspaceUpdateNoteTags, workspaceGetAllTags } from '../api/workspace'
import { extractMarkdownTodos, normalizePreviewTodoLines, toggleMarkdownTodoInBody, type MarkdownTodo } from '../utils/markdownTodo'
import { shouldOpenMarkdownLinkExternally } from '../utils/markdownLink'
import { noteColorSlot } from '../utils/noteColor'
import { isTypingTarget } from '../utils/keyboard'

Expand Down Expand Up @@ -391,6 +393,25 @@ export function Editor(props: EditorProps) {

const markdownComponents = useMemo<Components>(
() => ({
a: ({ href, children, onClick, ...props }) => (
<a
{...props}
href={href}
onClick={(event) => {
onClick?.(event)
if (event.defaultPrevented || !href || !shouldOpenMarkdownLinkExternally(href)) {
return
}

event.preventDefault()
void openUrl(href).catch((error) => {
console.error('Failed to open markdown link externally:', error)
})
}}
>
{children}
</a>
),
li: ({ node, children, className, ...props }) => {
const startLine = node?.position?.start?.line
const todo = typeof startLine === 'number' ? previewTodosByLine.get(startLine - 1) : undefined
Expand Down
15 changes: 13 additions & 2 deletions app/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
--color-slot-number-bg: oklch(from var(--color-text) l c h / 0.08);
--color-slot-number-border: oklch(from var(--color-text) l c h / 0.14);
--color-chip-bg: oklch(from var(--color-text) l c h / 0.10);
--color-unchecked-entry-bg: var(--color-chip-bg);
--color-autocomplete-bg: oklch(0.99 0.01 285);
--color-autocomplete-border: oklch(from var(--color-text) l c h / 0.14);
--color-autocomplete-hover: oklch(from var(--color-text) l c h / 0.08);
Expand Down Expand Up @@ -233,6 +234,16 @@ body {
line-height: var(--line-height-base);
}

::-moz-selection {
background: var(--color-text);
color: var(--color-slot-1);
}

::selection {
background: var(--color-text);
color: var(--color-slot-1);
}

/* ── App shell ──────────────────────────────────────────────────────── */

.appRoot {
Expand Down Expand Up @@ -1069,7 +1080,7 @@ body {
}

.editorMarkdown th {
background: var(--color-surface-strong);
background: var(--color-unchecked-entry-bg);
}

.editorMarkdown hr {
Expand Down Expand Up @@ -1451,7 +1462,7 @@ body {
display: flex;
align-items: center;
justify-content: center;
background: var(--color-chip-bg);
background: var(--color-unchecked-entry-bg);
}

.todoText {
Expand Down
35 changes: 35 additions & 0 deletions app/src/utils/markdownLink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest'
import { shouldOpenMarkdownLinkExternally } from './markdownLink'

describe('shouldOpenMarkdownLinkExternally', () => {
it('opens https links externally', () => {
expect(shouldOpenMarkdownLinkExternally('https://example.com')).toBe(true)
})

it('opens http links externally', () => {
expect(shouldOpenMarkdownLinkExternally('http://example.com')).toBe(true)
})

it('opens mailto links externally', () => {
expect(shouldOpenMarkdownLinkExternally('mailto:hello@example.com')).toBe(true)
})

it('opens tel links externally', () => {
expect(shouldOpenMarkdownLinkExternally('tel:+49123456789')).toBe(true)
})

it('keeps fragment links inside the preview', () => {
expect(shouldOpenMarkdownLinkExternally('#fn1')).toBe(false)
})

it('keeps relative links inside the app', () => {
expect(shouldOpenMarkdownLinkExternally('docs/page.md')).toBe(false)
})

it('returns false for empty or invalid href values', () => {
expect(shouldOpenMarkdownLinkExternally('')).toBe(false)
expect(shouldOpenMarkdownLinkExternally(' ')).toBe(false)
expect(shouldOpenMarkdownLinkExternally('https://')).toBe(false)
expect(shouldOpenMarkdownLinkExternally(undefined)).toBe(false)
})
})
13 changes: 13 additions & 0 deletions app/src/utils/markdownLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const EXTERNAL_MARKDOWN_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:'])

export function shouldOpenMarkdownLinkExternally(href: string | null | undefined): boolean {
const value = href?.trim()
if (!value || value.startsWith('#')) return false

try {
const url = new URL(value)
return EXTERNAL_MARKDOWN_PROTOCOLS.has(url.protocol)
} catch {
return false
}
}
28 changes: 28 additions & 0 deletions build-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ if ((${#TAURI_ARGS[@]} > 0)); then
mode="$2"
shift 2

export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
export DEVELOPER_DIR="$(xcode-select -p)"
export CC="$(xcrun --sdk macosx -f clang)"
export CXX="$(xcrun --sdk macosx -f clang++)"
export AR="$(xcrun --sdk macosx -f ar)"
export RANLIB="$(xcrun --sdk macosx -f ranlib)"
export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="$CC"
export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="$CC"

cd "$app_dir"
pnpm install

Expand All @@ -93,6 +102,15 @@ else
app_dir="$1"
mode="$2"

export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
export DEVELOPER_DIR="$(xcode-select -p)"
export CC="$(xcrun --sdk macosx -f clang)"
export CXX="$(xcrun --sdk macosx -f clang++)"
export AR="$(xcrun --sdk macosx -f ar)"
export RANLIB="$(xcrun --sdk macosx -f ranlib)"
export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="$CC"
export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="$CC"

cd "$app_dir"
pnpm install

Expand All @@ -107,3 +125,13 @@ else
echo "Build artifacts: $out_dir"
' bash "$APP_DIR" "$BUILD_MODE"
fi

if [[ "$(uname -s)" == "Darwin" ]]; then
if [[ "$BUILD_MODE" = "release" ]]; then
APP_BUNDLE="$APP_DIR/src-tauri/target/release/bundle/macos/Ponder.app"
else
APP_BUNDLE="$APP_DIR/src-tauri/target/debug/bundle/macos/Ponder.app"
fi

"$ROOT_DIR/scripts/macos-vendor-dylibs.sh" "$APP_BUNDLE"
fi
6 changes: 6 additions & 0 deletions docs/developer/BUILD_AND_RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ From repository root:
./build-app.sh
```

On macOS, the build flow prefers Apple toolchain linkers inside the Nix shell
to avoid introducing `/nix/store` runtime dependencies. As a safeguard, the
build script also vendors any remaining external non-system dylibs into the
generated `.app` bundle and fails the build if absolute library paths still
point outside the bundle.

Release build:

```bash
Expand Down
6 changes: 6 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
# macOS SDK aus Xcode CLT verfügbar machen
export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
export DEVELOPER_DIR="$(xcode-select -p)"
export CC="$(xcrun --sdk macosx -f clang)"
export CXX="$(xcrun --sdk macosx -f clang++)"
export AR="$(xcrun --sdk macosx -f ar)"
export RANLIB="$(xcrun --sdk macosx -f ranlib)"
export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="$CC"
export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="$CC"

# Komfort
export RUST_BACKTRACE=1
Expand Down
Loading
Loading