Skip to content

Commit

Permalink
Merge pull request #870 from adam-p/manifestv3
Browse files Browse the repository at this point in the history
Support WebExtensions V3
  • Loading branch information
adam-p authored Oct 12, 2024
2 parents 85017ca + d11c42a commit f48a41e
Show file tree
Hide file tree
Showing 14 changed files with 1,198 additions and 554 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

*Markdown Here* is a Google Chrome, Firefox, Safari, Opera, and Thunderbird extension that lets you write email<sup>&dagger;</sup> in Markdown<sup>&Dagger;</sup> and render them before sending. It also supports syntax highlighting (just specify the language in a fenced code block).

Writing email with code in it is pretty tedious. Writing Markdown with code in it is easy. I found myself writing email in Markdown in the Github in-browser editor, then copying the preview into email. This is a pretty absurd workflow, so I decided create a tool to write and render Markdown right in the email.
Writing email with code in it is pretty tedious. Writing Markdown with code in it is easy. I found myself writing email in Markdown in the GitHub in-browser editor, then copying the preview into email. This is a pretty absurd workflow, so I decided create a tool to write and render Markdown right in the email.

To discover what can be done with Markdown in *Markdown Here*, check out the [Markdown Here Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Here-Cheatsheet) and the other [wiki pages](https://github.com/adam-p/markdown-here/wiki).

Expand Down Expand Up @@ -62,7 +62,7 @@ After installing, make sure to restart Firefox/Thunderbird!
#### Manual/Development

1. Clone this repo.
2. Follow the instructions in the MDN ["Setting up an extension development environment"](https://developer.mozilla.org/en/Setting_up_extension_development_environment) article.
2. Follow the instructions in the MDN ["Your first WebExtension"](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#installing) article.

### Safari

Expand Down Expand Up @@ -184,17 +184,18 @@ node build.js
```


### Chrome and Opera extension
### Chrome, Opera, and Firefox (WebExtension) extension

Create a file with a `.zip` extension containing these files and directories:

```
manifest.json
common/
chrome/
_locales
```

### Firefox/Thunderbird extension
### Thunderbird (XUL) extension

Create a file with a `.xpi` extension containing these files and directories:

Expand Down
101 changes: 72 additions & 29 deletions src/chrome/backgroundscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,64 @@
marked:false, hljs:false, Utils:false, CommonLogic:false */
/*jshint devel:true, browser:true*/

/*
* Chrome background script.
*/
if (typeof browser === "undefined") {
// Chrome does not support the browser namespace yet.
// See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background
globalThis.browser = chrome;
}

// On each load, check if we should show the options/changelist page.
function onLoad() {
// This timeout is a dirty hack to fix bug #119: "Markdown Here Upgrade
// Notification every time I open Chrome". That issue on Github for details.
// https://github.com/adam-p/markdown-here/issues/119
window.setTimeout(upgradeCheck, 30000);
// We supply a #hash to the background page, so that we know when we're
// loaded via `background.page` (manifest V2 and Firefox manifest V3) vs
// `background.service_worker` (manifest V3 in Chrome and Safari).
var backgroundPage = !!location.hash;

if (!backgroundPage) {
// When loaded via a background page, the support scripts are already
// present. When loaded via a service worker, we need to import them.
// (`importScripts` is only available in service workers.)
importScripts('../common/utils.js');
importScripts('../common/common-logic.js');
importScripts('../common/marked.js');
importScripts('../common/highlightjs/highlight.js');
importScripts('../common/markdown-render.js');
importScripts('../common/options-store.js');
}

// In the interest of improved browser load performace, call `onLoad` after a tick.
window.addEventListener('load', Utils.nextTickFn(onLoad), false);
// Note that this file is both the script for a background page _and_ for a service
// worker. The way these things work are quite different, and we must be cognizant of that
// while writing this file.
//
// The key difference is that a background page is loaded once per browser session; a
// service worker is loaded when extension-related events occur, and then is torn down
// after 30 seconds of inactivity (with lifecycle caveats). This means that we can't rely
// on global variables to store state, and we must be mindful about how we handle
// messages.

// For the background page, this listener is added once and remains active for the browser
// session; for the service worker, this listener is added every time the service worker
// is loaded, and is torn down when the service worker is torn down.
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason !== 'install' && details.reason !== 'update') {
return;
}

// Create the context menu that will signal our main code.
// This must be called only once, when installed or updated, so we do it here.
chrome.contextMenus.create({
id: 'markdown-here-context-menu',
contexts: ['editable'],
title: Utils.getMessage('context_menu_item')
});

// Note: If we find that the upgrade info page opens too often, we may
// need to add delays. See: https://github.com/adam-p/markdown-here/issues/119
upgradeCheck();
});

function upgradeCheck() {
// DISABLED FOR THIS RELEASE
return;

OptionsStore.get(function(options) {
var appManifest = chrome.runtime.getManifest();

Expand All @@ -35,7 +77,7 @@ function upgradeCheck() {
OptionsStore.set({ 'last-version': appManifest.version }, function() {
// This is the very first time the extensions has been run, so show the
// options page.
chrome.tabs.create({ url: chrome.extension.getURL(optionsURL) });
chrome.tabs.create({ url: chrome.runtime.getURL(optionsURL) });
});
}
else if (options['last-version'] !== appManifest.version) {
Expand All @@ -45,23 +87,20 @@ function upgradeCheck() {
// The extension has been newly updated
optionsURL += '?prevVer=' + options['last-version'];

showUpgradeNotification(chrome.extension.getURL(optionsURL));
showUpgradeNotification(chrome.runtime.getURL(optionsURL));
});
}
});
}

// Create the context menu that will signal our main code.
chrome.contextMenus.create({
contexts: ['editable'],
title: Utils.getMessage('context_menu_item'),
onclick: function(info, tab) {
chrome.tabs.sendMessage(tab.id, {action: 'context-click'});
}
// Handle context menu clicks.
chrome.contextMenus.onClicked.addListener(function(info, tab) {
chrome.tabs.sendMessage(tab.id, {action: 'context-click'});
});

// Handle rendering requests from the content script.
// See the comment in markdown-render.js for why we do this.
// Handle rendering requests from the content script. Note that incoming messages will
// revive the service worker, then process the message, then tear down the service worker.
// See the comment in markdown-render.js for why we use these requests.
chrome.runtime.onMessage.addListener(function(request, sender, responseCallback) {
// The content script can load in a not-real tab (like the search box), which
// has an invalid `sender.tab` value. We should just ignore these pages.
Expand Down Expand Up @@ -89,11 +128,11 @@ chrome.runtime.onMessage.addListener(function(request, sender, responseCallback)
}
else if (request.action === 'show-toggle-button') {
if (request.show) {
chrome.browserAction.enable(sender.tab.id);
chrome.browserAction.setTitle({
chrome.action.enable(sender.tab.id);
chrome.action.setTitle({
title: Utils.getMessage('toggle_button_tooltip'),
tabId: sender.tab.id });
chrome.browserAction.setIcon({
chrome.action.setIcon({
path: {
"16": Utils.getLocalURL('/common/images/icon16-button-monochrome.png'),
"19": Utils.getLocalURL('/common/images/icon19-button-monochrome.png'),
Expand All @@ -105,11 +144,11 @@ chrome.runtime.onMessage.addListener(function(request, sender, responseCallback)
return false;
}
else {
chrome.browserAction.disable(sender.tab.id);
chrome.browserAction.setTitle({
chrome.action.disable(sender.tab.id);
chrome.action.setTitle({
title: Utils.getMessage('toggle_button_tooltip_disabled'),
tabId: sender.tab.id });
chrome.browserAction.setIcon({
chrome.action.setIcon({
path: {
"16": Utils.getLocalURL('/common/images/icon16-button-disabled.png'),
"19": Utils.getLocalURL('/common/images/icon19-button-disabled.png'),
Expand Down Expand Up @@ -148,7 +187,7 @@ chrome.runtime.onMessage.addListener(function(request, sender, responseCallback)
});

// Add the browserAction (the button in the browser toolbar) listener.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.action.onClicked.addListener(function(tab) {
chrome.tabs.sendMessage(tab.id, {action: 'button-click', });
});

Expand Down Expand Up @@ -191,6 +230,10 @@ function showUpgradeNotification(optionsURL) {
});
};

// TODO: This interval won't keep the service worker alive, so if a content script
// doesn't reload in about 30 seconds, we'll lose the interval and the notification
// won't show.
// Maybe use the Alarms API? Maybe restructure this so that it's less hacky?
showUpgradeNotificationInterval = setInterval(askTabsToShowNotification, 5000);
});
}
Expand Down
9 changes: 8 additions & 1 deletion src/common/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
Change Log
==========

2024-10-03: v2.14.0
-------------------

* Fixed [bug #722](https://github.com/adam-p/markdown-here/issues/722): Added support for WebExtensions manifest V3. (If you've seen warnings about the extension lately, this was why.)
- Thanks to [Andrew M. MacFie](https://github.com/amacfie) and [Alexander Popov](https://github.com/AlexWayfer).
* Fixed [bug #865](https://github.com/adam-p/markdown-here/issues/865): Add Chrome Store privacy information.

2018-09-30: v2.13.4
--------------------

* Fixed [bug #524] and [bug #526]: Due to v2.13.3 fix, Markdown Here didn't work in Thunderbird with a non-English UI.
* Fixed [bug #524](https://github.com/adam-p/markdown-here/issues/524) and [bug #526](https://github.com/adam-p/markdown-here/issues/526): Due to v2.13.3 fix, Markdown Here didn't work in Thunderbird with a non-English UI.
- Thanks to [KSR-Yasuda](https://github.com/KSR-Yasuda), [ensleep](https://github.com/ensleep), [Pedro Silva](https://github.com/pmanu93), [Christophe Meyer](https://github.com/stombi), [littdky](https://github.com/littdky), [Michael Lashkevich](https://github.com/lashkevi), [morsedl](https://github.com/morsedl).


Expand Down
10 changes: 6 additions & 4 deletions src/common/common-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ function getUpgradeNotification(optionsURL, responseCallback) {

Utils.getLocalFile(
Utils.getLocalURL('/common/upgrade-notification.html'),
'text/html',
'text',
function(html) {
// Get the logo image data
Utils.getLocalFileAsBase64(
Utils.getLocalFile(
Utils.getLocalURL('/common/images/icon32.png'),
'base64',
function(logoBase64) {
// Do some rough template replacement
html = html.replace('{{optionsURL}}', optionsURL)
Expand Down Expand Up @@ -72,7 +73,7 @@ function getForgotToRenderPromptContent(responseCallback) {

Utils.getLocalFile(
Utils.getLocalURL('/common/forgot-to-render-prompt.html'),
'text/html',
'text',
function(html) {
html = html.replace('{{forgot_to_render_prompt_title}}', Utils.getMessage('forgot_to_render_prompt_title'))
.replace('{{forgot_to_render_prompt_info}}', Utils.getMessage('forgot_to_render_prompt_info'))
Expand All @@ -82,8 +83,9 @@ function getForgotToRenderPromptContent(responseCallback) {
.replace('{{forgot_to_render_send_button}}', Utils.getMessage('forgot_to_render_send_button'));

// Get the logo image data
Utils.getLocalFileAsBase64(
Utils.getLocalFile(
Utils.getLocalURL('/common/images/icon48.png'),
'base64',
function(logoBase64) {
// Do some rough template replacement
html = html.replace('{{logoBase64}}', logoBase64);
Expand Down
40 changes: 14 additions & 26 deletions src/common/options-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ var ChromeOptionsStore = {

// The default values or URLs for our various options.
defaults: {
'main-css': {'__defaultFromFile__': '/common/default.css', '__mimeType__': 'text/css'},
'syntax-css': {'__defaultFromFile__': '/common/highlightjs/styles/github.css', '__mimeType__': 'text/css'},
'main-css': {'__defaultFromFile__': '/common/default.css', '__dataType__': 'text'},
'syntax-css': {'__defaultFromFile__': '/common/highlightjs/styles/github.css', '__dataType__': 'text'},
'math-enabled': DEFAULTS['math-enabled'],
'math-value': DEFAULTS['math-value'],
'hotkey': DEFAULTS['hotkey'],
Expand Down Expand Up @@ -286,8 +286,8 @@ var MozillaOptionsStore = {
// The default values or URLs for our various options.
defaults: {
'local-first-run': true,
'main-css': {'__defaultFromFile__': 'resource://markdown_here_common/default.css', '__mimeType__': 'text/css'},
'syntax-css': {'__defaultFromFile__': 'resource://markdown_here_common/highlightjs/styles/github.css', '__mimeType__': 'text/css'},
'main-css': {'__defaultFromFile__': 'resource://markdown_here_common/default.css', '__dataType__': 'text/css'},
'syntax-css': {'__defaultFromFile__': 'resource://markdown_here_common/highlightjs/styles/github.css', '__dataType__': 'text/css'},
'math-enabled': DEFAULTS['math-enabled'],
'math-value': DEFAULTS['math-value'],
'hotkey': DEFAULTS['hotkey'],
Expand Down Expand Up @@ -461,8 +461,8 @@ var SafariOptionsStore = {

// The default values or URLs for our various options.
defaults: {
'main-css': {'__defaultFromFile__': (typeof(safari) !== 'undefined' ? safari.extension.baseURI : '')+'markdown-here/src/common/default.css', '__mimeType__': 'text/css'},
'syntax-css': {'__defaultFromFile__': (typeof(safari) !== 'undefined' ? safari.extension.baseURI : '')+'markdown-here/src/common/highlightjs/styles/github.css', '__mimeType__': 'text/css'},
'main-css': {'__defaultFromFile__': (typeof(safari) !== 'undefined' ? safari.extension.baseURI : '')+'markdown-here/src/common/default.css', '__dataType__': 'text/css'},
'syntax-css': {'__defaultFromFile__': (typeof(safari) !== 'undefined' ? safari.extension.baseURI : '')+'markdown-here/src/common/highlightjs/styles/github.css', '__dataType__': 'text/css'},
'math-enabled': DEFAULTS['math-enabled'],
'math-value': DEFAULTS['math-value'],
'hotkey': DEFAULTS['hotkey'],
Expand Down Expand Up @@ -524,31 +524,19 @@ this.OptionsStore._fillDefaults = function(prefsObj, callback) {
}

// This function may be asynchronous (if XHR occurs) or it may be a straight
// recursion.
// synchronous callback invocation.
function doDefaultForKey(key, callback) {
// Only take action if the key doesn't already have a value set.
if (typeof(prefsObj[key]) === 'undefined') {
if (that.defaults[key].hasOwnProperty('__defaultFromFile__')) {
var xhr = new window.XMLHttpRequest();

if (that.defaults[key]['__mimeType__']) {
xhr.overrideMimeType(that.defaults[key]['__mimeType__']);
}

// Get the default value from the indicated file.
xhr.open('GET', that.defaults[key]['__defaultFromFile__']);

xhr.onreadystatechange = function() {
if (this.readyState === this.DONE) {
// Assume 200 OK -- it's just a local call
prefsObj[key] = this.responseText;

Utils.getLocalFile(
that.defaults[key]['__defaultFromFile__'],
that.defaults[key]['__dataType__'] || 'text',
function(data) {
prefsObj[key] = data;
callback();
return;
}
};

xhr.send();
});
return;
}
else {
// Set the default.
Expand Down
4 changes: 4 additions & 0 deletions src/common/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
display: none;
}

input[type="checkbox"], input[type="checkbox"] + label {
cursor: pointer;
}

/* Custom PayPal button styles from http://visitsteve.com/made/diy-paypal-buttons/ */
.paypal-button {
color: #2e3192;
Expand Down
Loading

0 comments on commit f48a41e

Please sign in to comment.