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
24 changes: 16 additions & 8 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
coverage:
status:
project:
src:
comment:
layout: "header, components"

component_management:
default_rules:
statuses:
- type: project
informational: true
paths: ["src/**/*", "!src/test/**/*"]
patch:
src:
- type: patch
informational: true
paths: ["src/**/*", "!src/test/**/*"]

individual_components:
- component_id: app
paths: ["src/app**"]
- component_id: ghosttext
paths: ["src/ghosttext**"]
- component_id: infra
paths: ["src/**", "!src/app**", "!src/ghosttext**", "!src/test/**", "!src/util/**"]
11 changes: 5 additions & 6 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@


____

* [ ] I agree that my contributions will be [dual-licensed under (MPL-2.0 OR GPL-3.0-or-later)](https://github.com/exteditor/ghostbird/blob/main/LICENSE).

## What kind of change does this PR introduce?
____

- [ ] New feature
- [ ] Bugfix
- [ ] Documentation
- [ ] Typo fix
- [ ] Other
<!-- This instructs let CodeRabbit generate summary -->
@coderabbitai

## What is the current behavior?

Expand Down
13 changes: 13 additions & 0 deletions .github/site/coverpage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

![logo](../ext/blue.svg)

# Ghostbird

> Compose email in your favorite text editor
>
> <video alt="logo" src="https://github.com/user-attachments/assets/150ef991-10b8-45e2-bb2c-690f1b45a7ea" controls muted style="max-width: min(30rem, 90vw)">

[GitHub](https://github.com/exteditor/ghostbird/#readme)
[Codeberg](https://codeberg.org/exteditor/ghostbird#readme)
[GitLab](https://gitlab.com/exteditor/ghostbird#readme)
[Installation](#installation)
151 changes: 151 additions & 0 deletions .github/site/homepage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Ghostbird: GhostText for Thunderbird

[![Supports Thunderbird ESR](https://img.shields.io/badge/supports-Thunderbird_140_ESR-0a84ff?logo=thunderbird&logoSize=auto)][tb]
[![Latest release](https://img.shields.io/github/v/release/exteditor/ghostbird?include_prereleases&logo=refinedgithub&logoColor=white&logoSize=auto)][rels]

[![Download on AMO](https://raw.githubusercontent.com/thunderbird/webext-support/refs/heads/master/images/get-the-addon.svg)](#installation)

Ghostbird is an Thunderbird add-on that lets you compose your emails in your favorite text editor - Vim, Neovim, VS Code, Sublime Text, Emacs, or any other editor with a GhostText add-on installed.

## Requirements

* [Thunderbird 140][tb] (We will mainly support the latest ESR)
* A text editor that has a GhostText server add-on installed and running:

[![Sublime Text][sublimetext-svg]](https://sublime.wbond.net/packages/GhostText)
[<img width="48" height="48" alt="VSCodium" title="VSCodium" src="https://raw.githubusercontent.com/VSCodium/vscodium.github.io/refs/heads/master/img/codium_cnl.svg" >](https://open-vsx.org/extension/fregante/ghost-text)
[![Visual Studio Code][vscode-svg]](https://marketplace.visualstudio.com/items?itemName=fregante.ghost-text)
[![GNU Emacs][emacs-svg]](https://melpa.org/#/atomic-chrome)
[![Vim][vim-svg]][vimghost]
[![Neovim][nvim-svg]](https://github.com/subnut/nvim-ghost.nvim)
[<img src="https://9fans.github.io/plan9port/dist/spaceglenda100.png" width="48" height="48" alt="Acme" title="Acme">](https://github.com/fhs/Ghost)
[<img src="https://github.com/user-attachments/assets/b0ca34ed-5508-458f-b7af-2642824bf7f7" width="48" height="48" alt="Helix" title="Helix">][helix]
[`$ANY_EDITOR`](https://github.com/newsch/GhostText-Any/)

The last two implementations can wrap any editor that blocks while editing.

## Installation

[<img src="./ext/blue.svg" width="48" height="48" border="1" align="right" alt="logo" title="Grey-headed bushshrike (Malaconotus blanchoti) is sometimes called 'ghostbird'">][bird]

* See [the help page in the Mozilla Support Center](https://support.mozilla.org/kb/installing-addon-thunderbird) for details.

You can install Ghostbird in several ways:

### Install within Thunderbird (Recommended)

1. In Thunderbird, open `Add-ons Manager`.
2. Search for "Ghostbird".
3. Click the <kbd>+ Add to Thunderbird</kbd> button.

### Download and install manually

1. Download the latest release from the [Thunderbird add-on website][amo] or [GitHub Releases][rels].
2. In Thunderbird, go to `Add-ons Manager`.
3. Drag and drop the downloaded `.xpi` file into the `Add-ons Manager` window.

Alternatively, you can:

3. Press :gear: button at the top-right.
4. Select `Install Add-on From File...` from the menu.
5. Select downloaded `.xpi` file.

### Build from source

* Basically, running `make` will do, which is roughly equivalent to `yarn install && yarn build`.
* See [CONTRIBUTING.md](./CONTRIBUTING.md) to get started.

## Usage

1. Launch your favorite text editor.
2. Start the GhostText server in your text editor (e.g., with `:GhostTextStart` in Vim).
3. Press the gray Ghostbird button <img alt="Gray button" src="./ext/gray.svg" width="24" height="24" border="1"> in the Thunderbird mail compose window. (The default shortcut is <kbd>^Ctrl</kbd>+<kbd>⇧Shift</kbd>+<kbd>H</kbd>)
* If the connection is successful, the button will turn blue <img alt="Blue button" src="./ext/blue.svg" width="24" height="24" border="1">.
* If the connection fails, the button will turn red <img alt="Red button" src="./ext/red.svg" width="24" height="24" border="1">. Make sure that the GhostText server is listening. See [Troubleshooting page of original GhostText](https://ghosttext.fregante.com/troubleshooting/#unable-to-connect).
4. Write your email in the text editor.
5. Close your text editor to stop Ghostbird.

* Example using [Vim-Ghost][vimghost]

[vimghost.webm](https://github.com/user-attachments/assets/150ef991-10b8-45e2-bb2c-690f1b45a7ea)

* Example using Notepad++ via GhostText-Any

<img width="600" height="302" alt="Screenshot using Notepad++ with GhostText-Any" src="https://github.com/user-attachments/assets/a4f92beb-a6f2-4a67-ae94-aa02af64539e" />

## How it works

* See [design.md](./doc/design.md) for details.

## Roadmap

* See [README.md](./README.md).
* See also [a list of milestones][milestones].

## Contributing

If you like the idea, please:

* [Star the repo](https://github.com/exteditor/ghostbird/#repository-container-header).
* [Rate the add-on][review].
* Feel free to open issues and submit pull requests. See [CONTRIBUTING.md](./CONTRIBUTING.md) to get started.
* See our [milestones page][milestones] or [issues](https://github.com/exteditor/ghostbird/issues) for tasks you can contribute to, and use the [Discussions](https://github.com/exteditor/ghostbird/discussions) page for general discussions.

## FAQ

### Is it safe to use?

* I'd say yes, but save your work frequently since it may contain bugs and eat your email.
* This add-on does not collect any personal data and can only connect to localhost. Attempts to send any data to external servers are blocked by Thunderbird's security model.
* You can look inside the released `.xpi` files (which are just zip files) to verify that they match the build from the source code.
* As for trustworthiness, the safest option is to wait until it reaches 1.0.0, as that indicates it has passed Mozilla's manual review.

### Why not just fork the original GhostText add-on, or contribute to it?

* See [faq.md](./doc/faq.md#why).

### How do I set up a development environment?

* See [CONTRIBUTING.md](./CONTRIBUTING.md) for details on the development workflow.

### How do I troubleshoot connection issues?

* Make sure that the GhostText server is listening. See the [Troubleshooting page of original GhostText](https://ghosttext.fregante.com/troubleshooting/#unable-to-connect).

### How do I switch between HTML mode and Plain Text mode?

* See [doc/faq.md](./doc/faq.md#mode).

### How can I support the project?

Please consider the following options:

* [Star the repo](https://github.com/exteditor/ghostbird/#repository-container-header)
* [Rate the add-on][review]
* [Donate to Thunderbird](https://www.thunderbird.net/en-US/donate/) (It's financially separate from Firefox)
* [Donate to @fregante, the author of the original GhostText](https://github.com/sponsors/fregante)
* [Contribute to the development](./CONTRIBUTING.md)

If you've looked at the options above and still want to motivate the maintainer [@snipsnipsnip](https://github.com/snipsnipsnip) specifically, you can tip some [ETH](https://gist.githubusercontent.com/snipsnipsnip/d0b0bd12045060c8f71fbc9ea936d886/raw/a840747caa6cac61d0bd3b59145e6ed7e9daf14f/eth.json).

## Credits

* [Federico Brigante](https://fregante.com/), the author of [GhostText][gt].
* [Alexandre Feblot](https://github.com/afeblot), the original author of [External Editor](https://github.com/exteditor/exteditor/), which I took over maintaining; it is now in a stale state. I intend to incorporate some of its features into Ghostbird.
* The button image is based on a [photo](https://commons.wikimedia.org/wiki/File:Grey-headed_Bushshrike_(Malaconotus_blanchoti)_in_tree,_crop.jpg) of a [Grey-headed Bushshrike][bird] by [Patty McGann](https://www.flickr.com/photos/10374910@N08/3093177192/) (CC-BY-2.0).

[proj]: https://github.com/exteditor/ghostbird/projects
[milestones]: https://github.com/exteditor/ghostbird/milestones
[tb]: https://www.thunderbird.net/download/esr/
[gt]: https://ghosttext.fregante.com/
[helix]: https://github.com/rahji/helix-ghost
[rels]: https://github.com/exteditor/ghostbird/releases
[sublimetext-svg]: ./doc/res/sublimetext.svg
[vscode-svg]: ./doc/res/vscode.svg
[emacs-svg]: ./doc/res/emacs.svg
[vim-svg]: ./doc/res/vim.svg
[nvim-svg]: ./doc/res/nvim.svg
[bird]: https://en.wikipedia.org/wiki/Grey-headed_bushshrike
[amo]: https://addons.thunderbird.net/addon/ghostbird/
[vimghost]: https://github.com/raghur/vim-ghost
[review]: https://addons.thunderbird.net/en-US/thunderbird/addon/ghostbird/reviews/add
16 changes: 16 additions & 0 deletions .github/site/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ghostbird: GhostText for Thunderbird</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Ghostbird is a Thunderbird add-on that connects your text editor with Thunderbird's email compose window, allowing you to edit the message in the text editor.">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4.13.1/lib/themes/vue.min.css">
<link rel="icon" href="ext/blue.svg" />
<script type="module" src="index.mjs"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
49 changes: 49 additions & 0 deletions .github/site/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11.12.0/+esm";
import "https://cdn.jsdelivr.net/npm/docsify@4.13.1/+esm";

mermaid.initialize({ startOnLoad: false });

// see https://docsify.js.org/#/configuration

window.$docsify = {
name: 'Ghostbird \u{1faba}\u{1f4eb}\u{1f47b}',
repo: 'exteditor/ghostbird',
logo: 'ext/blue.svg',
relativePath: true,
executeScript: true,
homepage: "homepage.md",
coverpage: "coverpage.md",
auto2top: true,
maxLevel: 2,
themeColor: '#0b9dd6',
routes: {
'/[-._/a-zA-Z]*[.][a-zA-Z]+': (route) => {
let url = `https://github.com/exteditor/ghostbird/blob/main/${encodeURIComponent(route.slice(1))}`
return `Redirecting to ${url}...\n\n<script>\nlocation.href = "${url}"${'</'}script>`
}
},
search: [
'/',
'/BUILDING',
'/CONTRIBUTING',
'/doc/faq',
'/doc/faq-architectural',
'/doc/building',
'/doc/testing',
],
markdown: {
renderer: {
code(code, lang) {
if (lang === "mermaid") {
return `<pre class="mermaid">${code}</pre>`;
}
return this.origin.code.apply(this, arguments);
}
}
},
plugins: [
(hook, _vm) => {
hook.doneEach(() => mermaid.run());
},
],
};
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build

on:
push:
branches-ignore: [ 'wip/*' ]
branches-ignore: [ 'coderabbit**', 'gh-**' ]

jobs:
build:
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,16 @@ jobs:
- name: Configure pages
uses: actions/configure-pages@v4

- name: Add typedoc to site
run: mv build/doc/ .github/site/typedoc/

- name: Add markdown files to site
run: mv *.md doc/ ext/ .github/site/

- name: Upload pages artifact
uses: actions/upload-pages-artifact@v4
with:
path: build/doc
path: .github/site/

- name: Deploy pages
uses: actions/deploy-pages@v4
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ Alternatively, you can:

1. Launch your favorite text editor.
2. Start the GhostText server in your text editor (e.g., with `:GhostTextStart` in Vim).
3. Press the gray Ghostbird button <img src="./ext/gray.svg" width="24" height="24" border="1"> in the Thunderbird mail compose window. (The default shortcut is <kbd>^Ctrl</kbd>+<kbd>⇧Shift</kbd>+<kbd>H</kbd>)
* If the connection is successful, the button will turn blue <img src="./ext/blue.svg" width="24" height="24" border="1">.
* If the connection fails, the button will turn red <img src="./ext/red.svg" width="24" height="24" border="1">. Make sure that the GhostText server is listening. See [Troubleshooting page of original GhostText](https://ghosttext.fregante.com/troubleshooting/#unable-to-connect).
3. Press the gray Ghostbird button <img alt="Gray button" src="./ext/gray.svg" width="24" height="24" border="1"> in the Thunderbird mail compose window. (The default shortcut is <kbd>^Ctrl</kbd>+<kbd>⇧Shift</kbd>+<kbd>H</kbd>)
* If the connection is successful, the button will turn blue <img alt="Blue button" src="./ext/blue.svg" width="24" height="24" border="1">.
* If the connection fails, the button will turn red <img alt="Red button" src="./ext/red.svg" width="24" height="24" border="1">. Make sure that the GhostText server is listening. See [Troubleshooting page of original GhostText](https://ghosttext.fregante.com/troubleshooting/#unable-to-connect).
4. Write your email in the text editor.
5. Close your text editor to stop Ghostbird.

Expand Down Expand Up @@ -268,13 +268,13 @@ If you've looked at the options above and still want to motivate the maintainer

## License

[![License: MPL-2.0](https://img.shields.io/badge/license-MPL--2.0-00d230?logo=mozilla)](./LICENSE.mpl)
[![License: GPLv3](https://img.shields.io/badge/license-GPLv3-bd0000?logo=gplv3&logoSize=auto)](./LICENSE.gpl)<br>
[![License: MPL-2.0](https://img.shields.io/badge/license-MPL--2.0-00d230?logo=mozilla)](https://www.mozilla.org/en-US/MPL/2.0/)
[![License: GPLv3](https://img.shields.io/badge/license-GPLv3-bd0000?logo=gplv3&logoSize=auto)](https://www.gnu.org/licenses/gpl-3.0.html)<br>
Ghostbird is [dual-licensed under (MPL-2.0 OR GPL-3.0-or-later)](./LICENSE). See also [NOTICE](./ext/NOTICE.md).

[proj]: https://github.com/exteditor/ghostbird/projects
[milestones]: https://github.com/exteditor/ghostbird/milestones
[tb]: https://thunderbird.net/
[tb]: https://www.thunderbird.net/download/esr/
[gt]: https://ghosttext.fregante.com/
[helix]: https://github.com/rahji/helix-ghost
[rels]: https://github.com/exteditor/ghostbird/releases
Expand Down
1 change: 1 addition & 0 deletions src/app-background/compose_action_notifier.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { EmailEditorFactory, IComposeWindow, IGhostServerPort } from "src/ghosttext-adaptor"
import type { GhostTextStarter } from "src/ghosttext-runner"

/** Responsible for interacting with compose windows */
export class ComposeActionNotifier {
static isSingleton = true
private readonly runners = new Map<number, IGhostServerPort>()
Expand Down
2 changes: 2 additions & 0 deletions src/app-background/menu_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export class MenuHandler {
// Compare the shown menu with menuItems and (re-)initialize the menu if necessary
console.debug(info)

// We've tried more sophisticated logic here, but corner cases like users changing their UI language often
// make it fail, so we ended up with a simple check: We (re-)initialize the menu when the user first opens the menu.
if (!this.buttonMenu.isInitialized()) {
console.debug("Initializing menu")
return this.buttonMenu.initItems(this.menuItems, info)
Expand Down
12 changes: 9 additions & 3 deletions src/root/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import type { BackgroundEventRouter, MenuItem, MenuShownInfo } from "src/app-background"
import type { Alarm } from "src/thunderbird"
import { type LazyThen, makeLazyThen } from "src/util/lazy_then"

console.info("starting", import.meta.url)
Expand All @@ -28,9 +29,16 @@ const prepareThen: LazyThen<BackgroundEventRouter> = makeLazyThen(async () => {
]
let heart = new AlarmHeart(messenger)

// Set the ready flag that promises the alarm event handler is registered
heart.ready(onAlarm)

return prepareBackgroundRouter({ messenger, heart, optionsSyncCtor, menuItems })
})

function onAlarm(alarm: Alarm): void {
console.debug("beep", alarm)
}

messenger.composeAction.onClicked.addListener((tab, _info): Promise<void> | void =>
prepareThen((router) => router.handleComposeAction(tab)),
)
Expand All @@ -51,9 +59,7 @@ messenger.runtime.onMessage.addListener((msg, sender, sendResponse): Promise<voi
}),
)

messenger.alarms.onAlarm.addListener((alarm) => {
console.debug("beep", alarm)
})
messenger.alarms.onAlarm.addListener(onAlarm)

messenger.menus.onShown.addListener((info, tab) =>
prepareThen((router) => {
Expand Down
3 changes: 0 additions & 3 deletions src/root/startup/startup_background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,5 @@ export const startupBackground = (consts: BackgroundConstants): WirelessInjector
export function prepareBackgroundRouter(consts: BackgroundConstants): BackgroundEventRouter {
let startup = startupBackground(consts)

// Set the ready flag that promises the alarm event handler is registered
consts.heart.assumeReady()

return startup(BackgroundEventRouter)
}
Loading