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
4 changes: 2 additions & 2 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

## Supported versions

Only the [latest release](https://github.com/combwizard/folderite/releases/latest) gets security fixes. If you're on an older version, upgrade first and check whether the problem is still there.
Only the [latest release](https://github.com/combwizard/Folderite/releases/latest) gets security fixes. If you're on an older version, upgrade first and check whether the problem is still there.

## Reporting a vulnerability

Please don't open a public issue for security problems. Report them privately through GitHub instead:

1. Go to the [Security tab](https://github.com/combwizard/folderite/security) of this repository.
1. Go to the [Security tab](https://github.com/combwizard/Folderite/security) of this repository.
2. Click **Report a vulnerability**.

Include the Folderite version, your Windows version, the steps to reproduce, and what an attacker could do with it.
Expand Down
59 changes: 54 additions & 5 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,84 @@
name: pages

# Publishes combwizard.github.io/folderite from site/ plus the screenshots in docs/.
# Publishes combwizard.github.io/Folderite: the landing page from site/ at the root and the
# documentation (docs/ + mkdocs.yml) under /docs/.

on:
push:
branches: [main]
paths: ['site/**', 'docs/**', '.github/workflows/pages.yml']
paths:
- "site/**"
- "docs/**"
- "docs-theme/**"
- "mkdocs.yml"
- "requirements-docs.txt"
- ".github/workflows/pages.yml"
# Pull requests only build the docs (strict: broken links and anchors fail), they don't deploy.
pull_request:
paths:
- "docs/**"
- "docs-theme/**"
- "mkdocs.yml"
- "requirements-docs.txt"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v5
with:
python-version: "3.14"
cache: pip
cache-dependency-path: requirements-docs.txt

- name: Build docs
env:
NO_MKDOCS_2_WARNING: "1"
run: |
pip install -r requirements-docs.txt
mkdocs build --strict

deploy:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7

- name: Stage site
- uses: actions/setup-python@v5
with:
python-version: "3.14"
cache: pip
cache-dependency-path: requirements-docs.txt

- name: Stage landing page
shell: pwsh
run: ./site/stage.ps1 -Out _site

- name: Build docs
env:
NO_MKDOCS_2_WARNING: "1"
run: |
pip install -r requirements-docs.txt
mkdocs build --strict -d "$GITHUB_WORKSPACE/_site/docs"

- uses: actions/configure-pages@v5

- uses: actions/upload-pages-artifact@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ TestResults/
# Local
*.log
site/.preview/
_site/

# Screenshot harness output
tools/screenshots/.work/
46 changes: 46 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Folderite

A middle-click folder menu for Windows. [README.md](README.md) says what it does, and [docs/building.md](docs/building.md)
how to build and test it.

## Keep the docs in step with the code

The user docs are the Markdown pages in `docs/`. Every push to `main` that touches them publishes them to
<https://combwizard.github.io/Folderite/docs/>, so they describe `main`, not the last release.

- **A change a user can see or do updates its docs page in the same commit.** That covers settings, menu items, config
keys, defaults, supported apps, file names and locations, log lines, messages, requirements and install steps. A new
feature is documented where a user would look for it; a removed one is taken out of every page that mentions it.
- **Check `README.md` and the landing page (`site/index.html`)** when the change touches install, requirements, first
steps or the feature list.
- **Pictures:** `tools\screenshots\capture.ps1` makes everything in `docs/images/`, in a light and a dark version. When a
change alters something in a picture, retake it in both Windows themes and look it over before committing. Pages
show both versions, one after the other, with `#only-light` and `#only-dark` on the file names.
- **Build the docs** after editing them: `mkdocs build --strict` (`pip install -r requirements-docs.txt` first). It
fails on a broken link or anchor. Link between pages with relative `.md` links so they work on GitHub too.
- **Keep `site_dir` in `mkdocs.yml`.** MkDocs' default output folder is `site/`, which it empties first, and `site/` is
the landing page's source here. Don't pass `-d site` either.

Where each part is documented:

| Code | Page in `docs/` |
|---|---|
| `Menu/` (sections, order, accelerators, subfolders, drives) | `menu.md`, `config.md` |
| `Tray/` (the tray icon and its menu, Start with Windows) | `menu.md`, `install.md` |
| `Hooks/` (middle-click, keyboard shortcut) | `menu.md`, `troubleshooting.md`, `how-it-works.md` |
| `History/` (recent folders, frecency) | `menu.md`, `how-it-works.md` |
| `Settings/` (the Settings window) | `settings.md` |
| `Dialogs/`, `Targets/FileDialogAdapter.cs` (Open and Save dialogs, start folders) | `dialogs.md`, `settings.md` |
| `Targets/` (supported apps) | `apps.md`, `how-it-works.md` |
| `Config/AppConfig.cs` (config keys and defaults) | `config.md`, `settings.md` |
| `Config/ConfigLocation.cs`, `ConfigRelocator.cs`, `Settings/ConfigMove.cs`, `Log.cs` (where files live) | `files.md` |
| `Interop/` (Settings and tray app talking) | `how-it-works.md` |
| A new way for something to fail, or a new log line worth reading | `troubleshooting.md` |
| `tests/`, `tools/`, `.github/workflows/`, `Folderite.csproj` | `building.md`, and `install.md` for requirements |

Write like the existing pages: plain, short, concrete sentences, American spelling (favorites, color), UI labels and
keys in **bold**, no em dashes, no marketing words.

The docs theme is in `docs-theme/` and `docs/stylesheets/extra.css`. It copies the landing page's look and tokens from
`site/styles.css`, so a change to the landing page's look may need the same change there. Pages follow the system's
light or dark mode, with no switch, like the landing page.
47 changes: 19 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
# Folderite

[![CI](https://github.com/combwizard/folderite/actions/workflows/ci.yml/badge.svg)](https://github.com/combwizard/folderite/actions/workflows/ci.yml)
[![Latest release](https://img.shields.io/github/v/release/combwizard/folderite)](https://github.com/combwizard/folderite/releases/latest)
[![CI](https://github.com/combwizard/Folderite/actions/workflows/ci.yml/badge.svg)](https://github.com/combwizard/Folderite/actions/workflows/ci.yml)
[![Latest release](https://img.shields.io/github/v/release/combwizard/Folderite)](https://github.com/combwizard/Folderite/releases/latest)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Website](https://img.shields.io/badge/website-combwizard.github.io%2Ffolderite-f2b632)](https://combwizard.github.io/folderite/)
[![Website](https://img.shields.io/badge/website-combwizard.github.io%2FFolderite-f2b632)](https://combwizard.github.io/Folderite/)
[![Docs](https://img.shields.io/badge/docs-combwizard.github.io%2FFolderite%2Fdocs-3d9bff)](https://combwizard.github.io/Folderite/docs/)

Middle-click in a file manager or Open/Save dialog to pop up a folder menu: favorites, folders open in other windows, recent folders (frecency-ranked), and the folder on the clipboard. Picking one sends *that* window there.

<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/menu-dark.png">
<img src="docs/menu-light.png" alt="The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder">
<source media="(prefers-color-scheme: dark)" srcset="docs/images/menu-dark.png">
<img src="docs/images/menu-light.png" alt="The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder">
</picture>

Supported: Windows Explorer (incl. Win11 tabs), common file dialogs, Directory Opus, Total Commander, XYplorer, Blender's file browser window, and the desktop (opens a new Explorer window).

## Install

1. Install the [.NET 10 Desktop Runtime (x64)](https://dotnet.microsoft.com/download/dotnet/10.0) if you don't have it.
2. Download `Folderite-<version>-win-x64.zip` from the [latest release](https://github.com/combwizard/folderite/releases/latest), unzip it to a folder of your choice, and run `Folderite.exe`.
2. Download `Folderite-<version>-win-x64.zip` from the [latest release](https://github.com/combwizard/Folderite/releases/latest), unzip it to a folder of your choice, and run `Folderite.exe`.

To upgrade, quit Folderite from the tray and unzip the new release over the old folder. Your settings are kept.

Expand All @@ -33,6 +34,10 @@ dotnet test
src\Folderite\bin\Debug\net10.0-windows\win-x64\Folderite.exe
```

## Documentation

The full guide is at [combwizard.github.io/Folderite/docs](https://combwizard.github.io/Folderite/docs/), built from [`docs/`](docs/).

## Usage

Runs in the tray. Right-click the tray icon for Settings, Pause, Start with Windows, Edit config, and Exit. Left-click opens the folder menu; picking a folder there opens a new Explorer window.
Expand All @@ -42,24 +47,24 @@ You can also open the menu with a keyboard shortcut: set one under **Settings
Middle-click inside Explorer and pick a favorite or a recent folder:

<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/explorer-dark.gif">
<img src="docs/explorer-light.gif" alt="Middle-clicking in Explorer, picking Work > Specs, then a recent folder">
<source media="(prefers-color-scheme: dark)" srcset="docs/images/explorer-dark.gif">
<img src="docs/images/explorer-light.gif" alt="Middle-clicking in Explorer, picking Work > Specs, then a recent folder">
</picture>

In an Open or Save dialog, the file name you typed is kept:

<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/save-dialog-dark.gif">
<img src="docs/save-dialog-light.gif" alt="Middle-clicking in a Save As dialog and picking Work > Reports; the typed file name stays">
<source media="(prefers-color-scheme: dark)" srcset="docs/images/save-dialog-dark.gif">
<img src="docs/images/save-dialog-light.gif" alt="Middle-clicking in a Save As dialog and picking Work > Reports; the typed file name stays">
</picture>

## Settings

**Settings…** (tray menu, or the bottom of the folder menu) opens an editor for everything below: favorites (drag to reorder or nest, drop folders from Explorer), actions, menu sections, exclusions, start folders for Open/Save dialogs, your folder history, Start with Windows / Pause, and the keyboard shortcut. Changes save as you make them and the running app picks them up immediately. **Preview menu** shows the real menu with the current settings.

<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/settings-dark.png">
<img src="docs/settings-light.png" alt="The Settings window on the Favorites page">
<source media="(prefers-color-scheme: dark)" srcset="docs/images/settings-dark.png">
<img src="docs/images/settings-light.png" alt="The Settings window on the Favorites page">
</picture>

Settings rewrites `config.json` when it saves, so `//` comments in it are not kept. If the file has an error, Settings stops saving until it is fixed.
Expand Down Expand Up @@ -117,25 +122,11 @@ Recent folders are ranked by how often and how recently you used them, with a bo

## Manual test checklist

- [ ] Explorer: menu shows; favorite navigates; with several tabs, the *active* tab navigates
- [ ] Explorer (Win11): middle-click on a tab still closes it (tab row is exempt)
- [ ] Open/Save dialog (Notepad, Office, browser): navigates; typed filename is kept
- [ ] Folder picker dialog: navigates instead of accepting the folder
- [ ] Directory Opus dual pane: clicked side navigates
- [ ] Total Commander: left/right panel, non-ASCII path *(untested so far)*
- [ ] XYplorer: `::goto` with spaces *(untested so far)*
- [ ] Blender 5.x File View (Open, Import with options panel, folder picker): navigates from a click on the bookmarks and on the file list; clipboard (text and image) comes back; typed filename is kept
- [ ] Middle-click elsewhere (browser tab, editor) still works normally
- [ ] Elevated Explorer/dialog: fails gracefully unless Folderite runs elevated
- [ ] Keyboard shortcut: opens the menu for Explorer (active tab), a file dialog, Directory Opus (focused side) and the desktop; elsewhere picking a folder opens a new Explorer window; does nothing while paused; a shortcut that's taken shows a warning in Settings
- [ ] Favorite with subfolders: submenu lists subfolders (by name / newest first); the folder itself is the bold top item; a disconnected network folder doesn't freeze the menu
- [ ] Recent folders: folders used in the current app rank higher there
- [ ] Dialog start folder: off by default; when on, a new Open/Save dialog moves to the app's last folder and keeps the typed file name; a per-app folder wins; folder pickers work
- [ ] Drives submenu (when enabled): lists drives with free space; a disconnected network drive doesn't hang the menu
The list of what to try by hand before a release is in [Build from source](docs/building.md#manual-test-checklist).

## Screenshots

Every screenshot and animation in `docs/` exists in a light and a dark version (`<name>-light.*`, `<name>-dark.*`); the README and the website pick one by the viewer's theme. To retake them, switch Windows to light mode and run `pwsh tools\screenshots\capture.ps1`, then switch to dark mode and run it again. It drives a demo setup with made-up folders on a temporary `X:` drive and takes over the mouse for about a minute.
Every screenshot and animation in `docs/images/` exists in a light and a dark version (`<name>-light.*`, `<name>-dark.*`); the README and the website pick one by the viewer's theme. To retake them, switch Windows to light mode and run `pwsh tools\screenshots\capture.ps1`, then switch to dark mode and run it again. It drives a demo setup with made-up folders on a temporary `X:` drive and takes over the mouse for about a minute.

## Releasing

Expand Down
42 changes: 42 additions & 0 deletions docs-theme/partials/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{#- Material's previous/next links, then the landing page's footer. -#}
<footer class="md-footer">
{% if "navigation.footer" in features and (page.previous_page or page.next_page) %}
<nav class="md-footer__inner md-grid" aria-label="{{ lang.t('footer') }}">
{% if page.previous_page %}
{% set direction = lang.t("footer.previous") %}
<a href="{{ page.previous_page.url | url }}" class="md-footer__link md-footer__link--prev" aria-label="{{ direction }}: {{ page.previous_page.title | e }}">
<div class="md-footer__button md-icon">
{% include ".icons/material/arrow-left.svg" %}
</div>
<div class="md-footer__title">
<span class="md-footer__direction">{{ direction }}</span>
<div class="md-ellipsis">{{ page.previous_page.title }}</div>
</div>
</a>
{% endif %}
{% if page.next_page %}
{% set direction = lang.t("footer.next") %}
<a href="{{ page.next_page.url | url }}" class="md-footer__link md-footer__link--next" aria-label="{{ direction }}: {{ page.next_page.title | e }}">
<div class="md-footer__title">
<span class="md-footer__direction">{{ direction }}</span>
<div class="md-ellipsis">{{ page.next_page.title }}</div>
</div>
<div class="md-footer__button md-icon">
{% include ".icons/material/arrow-right.svg" %}
</div>
</a>
{% endif %}
</nav>
{% endif %}
<div class="fl-foot">
<span class="fl-foot-brand"><img src="{{ config.theme.favicon | url }}" alt="" width="24" height="24">Folderite</span>
<p>A middle-click folder menu for Windows. MIT license. Made by combwizard.</p>
<nav class="fl-foot-links" aria-label="Project links">
<a href="{{ config.repo_url }}">GitHub</a>
<a href="{{ config.extra.homepage }}">Website</a>
<a href="{{ config.repo_url }}/releases">Releases</a>
<a href="{{ config.repo_url }}/issues">Issues</a>
<a href="{{ config.repo_url }}/blob/main/LICENSE">License</a>
</nav>
</div>
</footer>
32 changes: 32 additions & 0 deletions docs-theme/partials/header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{#- Material's header, reshaped into the landing page's floating bar: icon and wordmark
(back to the landing page), a Docs link, search, and the GitHub and Download buttons.
The first include replaces Material's own palette script (system light or dark, no toggle). -#}
{% include "partials/javascripts/palette.html" %}
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="{{ lang.t('header') }}">
<label class="md-header__button md-icon fl-menu" for="__drawer">
{% set icon = config.theme.icon.menu or "material/menu" %}
{% include ".icons/" ~ icon ~ ".svg" %}
</label>
<a class="fl-brand" href="{{ config.extra.homepage }}" title="Folderite home">
{% include "partials/logo.html" %}
<span class="fl-wordmark">Folderite</span>
</a>
<a class="fl-docs" href="{{ nav.homepage.url | url }}">Docs</a>
<div class="fl-spacer"></div>
{% if "material/search" in config.plugins %}
{% set search = config.plugins["material/search"] | attr("config") %}
{% if search.enabled %}
<label class="md-header__button md-icon" for="__search">
{% set icon = config.theme.icon.search or "material/magnify" %}
{% include ".icons/" ~ icon ~ ".svg" %}
</label>
{% include "partials/search.html" %}
{% endif %}
{% endif %}
<a class="fl-btn fl-gh" href="{{ config.repo_url }}" aria-label="Folderite on GitHub">
{% include ".icons/fontawesome/brands/github.svg" %}<span class="fl-gh-label">GitHub</span>
</a>
<a class="fl-btn fl-primary" href="{{ config.repo_url }}/releases/latest">Download</a>
</nav>
</header>
15 changes: 15 additions & 0 deletions docs-theme/partials/javascripts/palette.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{#- Replaces Material's palette script. Like the landing page there is no theme switch: the page follows the
system's light or dark mode, also when it changes while the page is open. Material's only-light and only-dark
pictures hang off this attribute. Included from partials/header.html, so it runs before the page paints. -#}
<script>
(function () {
if (window.__flTheme) return;
window.__flTheme = true;
var dark = matchMedia("(prefers-color-scheme: dark)");
function apply() {
document.body.setAttribute("data-md-color-scheme", dark.matches ? "slate" : "default");
}
apply();
dark.addEventListener("change", apply);
})();
</script>
2 changes: 2 additions & 0 deletions docs-theme/partials/logo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{#- The Folderite icon, same file as the landing page's favicon (docs/assets/icon.png, set as theme.favicon). -#}
<img class="fl-logo" src="{{ config.theme.favicon | url }}" alt="" width="30" height="30">
Loading
Loading