Skip to content
ZANYANBUPublic

About

Native macOS study app: dark-mode viewer for slides, PDFs and notes, with an embedded Excalidraw board and a map of your material. Offline, no account.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

21 Commits

Folders and files

Repository files navigation

SlideView

A native macOS study app: every lecture file in one dark, offline library,
with a real Excalidraw board to explain ideas and a map of how your material connects.

CI Latest release macOS 13+ Swift Offline MIT licence

Website · Download for Mac

The SlideView library: decks, PDFs and drawings grouped by subject

Opens .pptx / .ppt / .pdf / .docx, Markdown, notebooks, spreadsheets and code full-screen with a smart dark mode, built for long revision sessions. No account, no cloud, no telemetry: the app talks only to 127.0.0.1.

Install

Download SlideView.zip, unzip it and drag SlideView.app to Applications. Apple Silicon, macOS 13 or later.

First launch: the app is not notarised by Apple, so macOS blocks it once. Open System Settings → Privacy & Security, scroll down and click Open Anyway, or run xattr -dr com.apple.quarantine /Applications/SlideView.app.

Or build it yourself: ./build.sh → ~/Applications/SlideView.app.

If SlideView saves you a late night, a ⭐ helps other students find it.

Features at a glance

Smart dark mode that keeps pictures true

Text and vector art are inverted; photographs and diagrams keep their real colours, and a red heading stays red. D cycles Smart, Invert, Dim and Light.

Smart Light (original)
A slide in Smart mode The same slide in Light mode

A drawing board to explain ideas

The real Excalidraw editor, embedded and offline. Sketch a concept next to the deck it belongs to; the drawing is an ordinary .excalidraw file in your library. More in Drawing board.

The embedded Excalidraw drawing board

A map of everything you study

Press M for a force-directed map: documents that share topics are linked automatically, and [[wikilinks]] in your notes become edges. More in Map.

Map of documents linked by shared topics

Notes in any format

⌘N creates plain text, Markdown, a CSV table or a drawing in any library folder, saved as a normal file. More in Notepad.

The New note sheet with type and folder

And also

  • Tabs, Chrome-style, each with its own page, zoom, stars and notes
  • Per-slide notes, starred slides, text search and a thumbnail rail
  • Zen mode (H): nothing on screen but the slide
  • Right-click a slide to copy it, share it, or send it to Google Lens or Gemini
  • 40+ formats normalised to PDF once, then cached, so re-opening is instant
  • Credentials files are never scanned or cached

What it opens

Everything is normalised to a PDF once, then cached — so every format gets the same treatment: thumbnails, filmstrip, tabs, text search, per-slide notes, smart invert, Google Lens and Gemini.

Formats Rendered by
Slides pptx ppt odp key pps ppsx LibreOffice
Documents pdf · docx doc odt rtf rtfd html · pages direct · TextKit · LibreOffice
Spreadsheets csv tsv · xlsx xls ods numbers in-process · LibreOffice
Notes md markdown rmd txt tex org rst in-process
Notebooks ipynb in-process
Images png jpg jpeg heic heif gif webp tiff bmp PDFKit — off by default, see below
Drawings excalidraw Excalidraw's own exporter — see Drawing board
Code swift py c cpp java js ts json yaml sql … in-process

Jupyter notebooks keep their structure: markdown cells, In [n] prompts, code cells, stream and error output, and inline plots (decoded from the notebook's own base64, so no kernel is needed).

Images are excluded from scanning by default, behind Include images in the sidebar. A folder of study material almost always also holds screenshots, scans and ID photos, and indexing those buries the decks. They stay openable on demand.

Credentials files are never scanned, converted or cached. Anything that looks like secrets — .env and .env.*, a file called env/env.txt, credentials.*, secrets.*, id_rsa, .npmrc, .netrc, and .pem .p12 .pfx .keystore .jks .ppk .asc .gpg — is skipped. Rendering one would leave a readable copy of your keys in ~/Library/Application Support/SlideView, which is exactly the thing you would not want. Open one deliberately and it goes straight to the text editor, which reads the file directly and caches nothing. Any render made before this rule existed is deleted the next time the library is scanned. (.key is not on the list — that is Keynote.)

Code and config files are opened on demand but never scanned. Indexing every .js and .json under a project folder would bury the actual material — so the library stays clean, while ⌘O, drag-and-drop and Finder's Open With will open anything in the table above. Build and dependency directories (node_modules, .git, venv, DerivedData, Pods, build, …) are skipped when scanning.

Map

Press M, or Map in the library header, for a force-directed view of everything you study from. Two kinds of edge:

  • Written links (blue) — [[wikilinks]] and ](file) references in your notes, resolved against the library.
  • Shared topics (grey) — significant vocabulary two documents have in common, scored with TF-IDF so that frequent words don't tie everything to everything. Each document keeps a signature of its ~28 strongest terms; edges are capped at four per node so the map stays readable.

Signatures are cached per converted PDF, so the map is only expensive the first time. Hover a node for its top terms, click to open it, drag to rearrange, scroll or pinch to zoom, double-click to reframe. F searches, + − 0 zoom.

It is built to stay usable at a few hundred documents: labels are placed in screen space with collision rejection (and capped) rather than drawn for every node, repulsion uses a distance cutoff so the layout does not crawl, the subject legend hides behind a button, and a click only opens a document if the pointer did not move — dragging a node used to open it by accident.

In progress: an assistant that answers from your material

On the apple-intelligence-assistant branch, pressing A opens an assistant that answers questions from your own decks, writes study guides for a subject, keeps your chats and remembers what you tell it. It runs on the Mac itself, with Apple's on-device model or any Ollama model, and every answer shows the page it came from. It is not in a release yet: small models still misread diagrams and pad answers. The branch's README lists how it works and what is left to improve. To try it:

git checkout apple-intelligence-assistant
./build.sh

Prior art, and why none of it is vendored

Worth stating plainly, because it constrains what this repo can contain:

Project What it does Licence
NightPDF Dark-mode PDF reader, Electron + PDF.js GPLv2
Logseq Local notes with a graph view AGPL-3.0
Obsidian Notes with a graph view closed source — the org holds docs, API typings and sample plugins only; there is no app source or backend to connect to
Skim macOS PDF reader/annotator BSD
Sioyek Research-paper reader GPLv3

| Excalidraw | Hand-drawn style whiteboard | MIT |

Copying from the GPL/AGPL projects would force this MIT repo to relicense, and Obsidian has nothing to copy. So the ideas were worth studying and the code was not: the smart-invert pass, the map, and every converter here are written from scratch.

Excalidraw is the exception, and the licence is why: it is MIT, like this repo, so the drawing board embeds the real editor instead of imitating it. The vendored third-party code is PDF.js (Apache 2.0) and Excalidraw with its dependencies (MIT and other permissive licences), all credited in web/vendor/NOTICE.md.

Drawing board

New drawing on the library's front page (or ⇧⌘N) creates an .excalidraw file and opens it in the real Excalidraw editor, version 0.18.1, embedded in the app: every shape and tool, hand-drawn and architect styles, arrows that bind to shapes, elbow arrows, frames, images, the laser pointer, shape libraries, Mermaid-to-diagram, and PNG / SVG / clipboard export.

A drawing behaves like every other SlideView document:

  • It is an ordinary file in your library folder, in Excalidraw's standard format — the same file opens on excalidraw.com or in any other Excalidraw.
  • It saves itself as you draw, and again before the app quits.
  • It has a thumbnail in the library, the labels written on it feed the map, and Preview opens it as a slide with notes, stars and smart invert like any deck.
  • Rename, Trash, Share and Reveal work on it from the ⋯ menu.

Images can be pasted, picked from disk, or dropped straight onto the canvas. Your shape library is shared across all drawings and kept in ~/Library/Application Support/SlideView/library.excalidrawlib.

What is not there, and why

Three excalidraw.com features depend on Excalidraw's own servers rather than on the editor, so they are absent here by design:

  • Live collaboration needs a relay server and a shared room.
  • Shareable links upload the scene to Excalidraw's backend.
  • AI text-to-diagram calls Excalidraw's AI service. Mermaid-to-diagram, which runs locally, is included.

The 12 MB CJK handwriting font is also left out to keep the app small; those characters fall back to a system font. The drawing page refuses every request that is not to the app's own local server, so the editor cannot quietly reach for a CDN.

How it is wired

Excalidraw's published build leaves about thirty dependencies as bare imports, so it cannot be served as-is. Tools/vendor-excalidraw.sh bundles it once with esbuild into web/vendor/excalidraw/ — committed, so building the app still needs no Node. The editor runs in an iframe (web/draw.html, web/draw.js), so its React tree and stylesheet stay out of the rest of the app and nothing loads until a drawing is opened.

Thumbnails come from Excalidraw's own exporter, the only renderer that draws a scene exactly as the editor does. The editor posts a picture after each save; a drawing that arrives from elsewhere is rendered by the same page in an offscreen web view. A scene that fails to parse is never replaced with an empty one — the editor declines to open it and leaves the file alone.

A bare WKWebView ignores file pickers, downloads, confirm() and window.open(), all of which Excalidraw relies on, so the app implements them: insert image opens a panel, export opens a save dialog, and web links go to your browser. Excalidraw's own shortcuts (⌘Z, ⌘D, ⌘G…) take priority inside a drawing: WebKit offers a key chord to the page first, and only passes it on to the menu bar if the page does not use it.

Notepad

⌘N creates a note in any library folder and opens it in a simple full-window editor — type and it saves itself, ⌘S forces a save.

Pick the type in the sheet: Plain text .txt, Markdown .md, Table .csv, or Other for any other editable extension (.tex, .py, .json, …). Typing an extension into the name selects the matching type on its own, and the sheet always shows the filename you will end up with. A new Markdown note starts with its own # title; the rest start empty.

.txt files open straight into the editor rather than the formatted view; Markdown, CSV and code files open formatted with an Edit button (E) to switch. Preview flips back, re-rendering from what you just wrote.

Notes stay ordinary files on disk — no database, no proprietary format — so they show up in the map alongside everything else and can be opened by anything.

Renaming and deleting

Right-click any slide or card — or ⋯ in the editor — for Rename… and Move to Trash. Both confirm first, and deletion goes to the Trash, never straight to an unrecoverable delete, so a mistake is undone from Finder.

Renaming changes a document's identity (the id is a hash of its path), so the notes file is carried across and the UI migrates reading position and starred slides to the new id. Restoring a file from the Trash to its old path brings its notes back with it, since notes are deliberately not purged on delete.

Sharing

Share opens the standard macOS share sheet (AirDrop, Mail, Messages, Notes), ⇧⌘S anywhere, or right-click a slide for Share… / Share as PDF… — handy for sending a converted deck to someone who has no PowerPoint. Copy puts the note's text on the clipboard; Copy File puts the file itself there, so it can be pasted straight into Finder or a message.

macOS integration

  • Open With — SlideView registers for every type above, as an alternate handler, so it never steals your existing defaults.
  • Drag and drop — drop files anywhere on the window to open them, or drop a folder to add it to the library.
  • Open Recent in the File menu, backed by the system recents list.
  • Full menu bar — File, Edit, View (appearance, zen, zoom), Go (slides and starred), Window (tabs) and Help.
  • Files opened from outside your library folders appear under Opened files.

Menu shortcuts deliberately all carry ⌘, so the single-key shortcuts stay free for the viewer and cannot fire while you are typing a note.

How it works

  • PowerPoint files are converted to PDF once by LibreOffice in the background, then cached in ~/Library/Application Support/SlideView/pdf. Re-opening is instant; editing the source re-converts automatically.
  • Everything runs fully offline. PDF.js is vendored into the app bundle and the HTTP server binds to 127.0.0.1 only.
  • Conversion uses an isolated LibreOffice profile, so it works even while the LibreOffice app is open with the same file.

Appearance modes (D cycles)

Mode What it does
Smart Inverts text and vector art, leaves photographs in true colour
Invert Inverts the whole slide
Dim Keeps colours, lowers the light output — for already-dark decks
Light Original

Inversion is per-channel invert plus a 180° hue rotation, so a red heading stays red rather than turning cyan, and the result is compressed into a soft near-black / near-white range instead of harsh #000 / #fff.

Tabs

Decks open in tabs across the top of the window, Chrome-style, always visible — the library acts as the new-tab page.

⌘T open another deck · ⌘W close tab · ⌘1…⌘9 switch · ⌃⇥ next tab ⌘-click or middle-click a deck in the library to open it in a background tab.

Two macOS details worth knowing if you touch this code:

  • Tabs sit in the titlebar strip. Clicks do reach the web view there, but the window then loses its drag area — so the shell puts a real NSView with mouseDownCanMoveWindow in the empty space right of the tabs, and the page reports where that space begins (dragZone).
  • In full screen the strip folds into the toolbar row instead of keeping a row of its own, which would cost ~42px of slide for nothing.

Each tab keeps its own page, zoom, stars, search index and notes. Render caches are keyed per document, so switching back to a tab is instant.

Zen mode

H hides the tab strip, toolbar, thumbnail rail, notes and footer — the slide fills the screen with no chrome at all. Nudge the pointer to the top edge to peek at the toolbar; H or Esc brings everything back. Leaving full screen drops zen automatically, so you can never get stranded without controls.

Notes

N opens a notes panel docked to the right, holding one note per slide. Notes are saved on the server as JSON — one file per deck under ~/Library/Application Support/SlideView/notes/ — rather than in localStorage, so clearing a cache cannot take your revision notes with it. Each file records the deck's name and path, so it still makes sense on its own:

{ "name": "Unit 1",
  "path": "…/CN/Unit 1.pptx",
  "notes": { "71": "OFC = optical fibre cable…" } }

Writes are debounced and always flushed before the slide or tab changes. Slides carrying a note are dotted in the thumbnail rail and on the scrubber, and the deck's note count shows on its library card. ⧉ in the panel header copies every note in the deck as Markdown; ↑ ↓ jump between annotated slides.

Notes are keyed by page only — editing and re-converting a deck keeps them.

Keyboard

→ ␣ J next · ← ⇧␣ K prev · Home/End first/last · digits jump to a slide D appearance · F full screen · H zen · T thumbnail rail · + − zoom · 0 fit · W fit width S star slide · [ ] prev/next starred · ⇧S starred list · N notes ⌘F or / search · ⌘T ⌘W ⌘1-⌘9 tabs · ? help ⌘N new note · ⇧⌘N new drawing · E edit · ⌘S save · ⇧⌘S share · M map Esc back to library · ⌘R rescan

Reading position, stars and appearance are remembered per deck.

Right-click a slide

Slide 3 of Unit 1
─────────────────────
Copy Slide as Image
Copy Slide Text
─────────────────────
Search with   ▸   Google Lens · Google · Ask Gemini
Share         ▸   Share File… · Share as PDF… · Copy File
Open          ▸   PDF in Chrome · Original in Default App · Reveal in Finder
─────────────────────
Rename…
Move to Trash

Slide-level entries are hidden for anything not yet rendered — a credentials file or a deck still converting shows only the actions that can actually work.

Chrome cannot be embedded inside another app, and Google Lens cannot reach a 127.0.0.1 URL — so these put the slide (image or text) on the clipboard and open your real Chrome, where you are already signed in. Press ⌘V there.

Library

The + in the sidebar adds folders; each is scanned recursively and grouped by subfolder. Hover a folder to remove it.

Markdown

.md files are first-class: they are converted once to a paginated PDF and then behave exactly like any other deck — thumbnails, filmstrip, text search, per-slide notes and smart invert all work, and the text stays selectable.

Rendering is deliberately light (dark on white); smart invert darkens it the same way it does a lecture PDF, so notes and slides look consistent side by side.

Supported: headings, paragraphs, bold/italic/strike, inline and fenced code, blockquotes, ordered/unordered lists, task lists, tables, rules, links and images (local images are inlined as data URIs, so the render needs no file access).

Why pagination is done in JavaScript

The obvious route — NSPrintOperation on a WKWebView — is unusable here, and both failure modes block the main thread and freeze the whole app:

  • with the web view in no window, it spins forever inside -[NSView canDraw];
  • with an offscreen host window, it paginates into ~900 000 pages (a 240 MB PDF).

So instead a script in the page inserts spacers to push any block that would straddle a page boundary onto the next page, and each page is then captured with createPDF, which is asynchronous and cannot stall the UI. PDFKit stitches the slices together. Headings use padding-top rather than margin-top, because collapsing margins would make offsetTop disagree with where a block actually paints — and the paginator reads offsetTop.

Requirements

  • macOS 13+ (Apple Silicon; change -target in build.sh for Intel)

  • Xcode command line tools (swiftc)

  • LibreOffice in /Applications — only for slide decks (pptx, ppt, odp, key) and spreadsheets (xlsx, xls, ods, numbers) and pages. Everything else renders natively.

    Word, RTF, ODT and HTML go through Foundation and TextKit instead: drawn via NSLayoutManager so embedded images survive, and roughly 30x faster than shelling out (0.24s vs ~8s for a typical document). If Foundation can't parse a file, it falls back to LibreOffice; if LibreOffice is missing, the error says exactly what is needed and why.

Layout

Sources/    Swift — HTTP server, library scan, converters (LibreOffice,
            Markdown, notebooks, code, images), AppKit shell
web/        UI — index.html, app.css, app.js; draw.html + draw.js for the
            drawing board; vendored pdf.js and Excalidraw
Tests/      XCTest suites: Markdown, converters, scan rules, map links, server
Tools/      icon generator, and the script that re-vendors Excalidraw
build.sh    compile + assemble the .app

Contributing

Bug reports, ideas and pull requests are welcome. See CONTRIBUTING.md. swift test runs the tests; they also run on every push, with a full build of the app.

Licence

MIT — see LICENSE. Bundled PDF.js is Apache 2.0 and bundled Excalidraw is MIT; see web/vendor/NOTICE.md.

About

Native macOS study app: dark-mode viewer for slides, PDFs and notes, with an embedded Excalidraw board and a map of your material. Offline, no account.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages