Skip to content

Commit 111105c

Browse files
authored
Merge pull request #115 from Stardown-app/feature-languages
Add markup language setting with markdown & HTML options
2 parents 49f207d + d26b52d commit 111105c

File tree

15 files changed

+366
-191
lines changed

15 files changed

+366
-191
lines changed

chrome/config.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ export async function sleep(ms) {
3030

3131
/**
3232
* createContextMenus creates the context menu options.
33+
* @param {string} markupLanguage - the markup language the user chose.
3334
* @returns {void}
3435
*/
35-
export function createContextMenus() {
36+
export function createContextMenus(markupLanguage) {
3637
// This function should do nothing. It needs to exist because the Firefox extension
3738
// uses a function by the same name that is imported into the background script.
3839
}
@@ -48,9 +49,10 @@ let isTableSelected = false;
4849
* of HTML element the mouse is interacting with. This only has an effect if the context
4950
* menu is not currently visible.
5051
* @param {string} context - info about the element the mouse is interacting with.
52+
* @param {string} markupLanguage - the markup language the user chose.
5153
* @returns {Promise<void>}
5254
*/
53-
export async function updateContextMenu(context) {
55+
export async function updateContextMenu(context, markupLanguage) {
5456
// The `browser.contextMenus.update` method doesn't work well in Chromium because
5557
// when it's used to hide all but one context menu option, the one remaining would
5658
// appear under a "Stardown" parent menu option instead of being in the root of the
@@ -82,7 +84,40 @@ export async function updateContextMenu(context) {
8284
browser.contextMenus.create(menu.videoItem);
8385
browser.contextMenus.create(menu.audioItem);
8486
}
87+
88+
updateContextMenuLanguage(markupLanguage);
89+
}
90+
}
91+
92+
/**
93+
* updateContextMenuLanguage changes the context menu options to reflect the user's
94+
* chosen markup language.
95+
* @param {string} markupLanguage - the markup language the user chose.
96+
* @returns {void}
97+
*/
98+
export function updateContextMenuLanguage(markupLanguage) {
99+
if (markupLanguage === 'html') {
100+
markupLanguage = 'HTML';
85101
}
102+
103+
browser.contextMenus.update('page', {
104+
title: `Copy ${markupLanguage} link to here`,
105+
}, () => { if (browser.runtime.lastError) return; });
106+
browser.contextMenus.update('selection', {
107+
title: `Copy ${markupLanguage} of selection`,
108+
}, () => { if (browser.runtime.lastError) return; });
109+
browser.contextMenus.update('link', {
110+
title: `Copy ${markupLanguage} of link`,
111+
}, () => { if (browser.runtime.lastError) return; });
112+
browser.contextMenus.update('image', {
113+
title: `Copy ${markupLanguage} of image`,
114+
}, () => { if (browser.runtime.lastError) return; });
115+
browser.contextMenus.update('video', {
116+
title: `Copy ${markupLanguage} of video`,
117+
}, () => { if (browser.runtime.lastError) return; });
118+
browser.contextMenus.update('audio', {
119+
title: `Copy ${markupLanguage} of audio`,
120+
}, () => { if (browser.runtime.lastError) return; });
86121
}
87122

88123
/**

docs/troubleshooting.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Troubleshooting
22

3+
## The right-click options disappeared
4+
5+
Due to browser limitations, Stardown's context menu options cannot appear for certain kinds of links, images, and videos. Specifically, they cannot appear for canvases, background images, inline SVGs, HTML anchors that contain both text and image(s), videos that don't have a `<video>` HTML element, and videos with a `<video>` HTML element that's covered by other elements.
6+
7+
Besides those possibilities, browsers have an occasionally reoccuring bug that makes the context menu options disappear. Reinstalling Stardown should fix this.
8+
39
## The right-click option copied a link for the entire page, not a specific part
410

511
Stardown looks for an HTML element ID where you right-clicked, but some parts of websites don't have any IDs. If there is no HTML element ID where you right-click and you don't select text before right-clicking, the link Stardown creates will be for the entire page, not for the part of the page where you right-clicked. Most websites assign an ID to each section title.
@@ -8,12 +14,6 @@ It's also not possible to link to text within [HTML iframes](https://www.w3schoo
814

915
Lastly, a small number of sites allow creating text fragment links but don't allow using them.
1016

11-
## The right-click options disappeared
12-
13-
Due to browser limitations, Stardown's context menu options cannot appear for certain kinds of links, images, and videos. Specifically, they cannot appear for canvases, background images, inline SVGs, HTML anchors that contain both text and image(s), videos that don't have a `<video>` HTML element, and videos with a `<video>` HTML element that's covered by other elements.
14-
15-
Besides those possibilities, browsers have an occasionally reoccuring bug that makes the context menu options disappear. Reinstalling Stardown should fix this.
16-
1717
## Something else is wrong
1818

1919
If reinstalling Stardown doesn't fix it and [the issues page](https://github.com/Stardown-app/Stardown/issues?q=is%3Aissue) doesn't have an issue for it yet, please make a new issue.

firefox/config.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ export async function sleep(ms) {
3030

3131
/**
3232
* createContextMenus creates the context menu options.
33+
* @param {string} markupLanguage - the markup language the user chose.
3334
* @returns {void}
3435
*/
35-
export function createContextMenus() {
36+
export function createContextMenus(markupLanguage) {
3637
browser.runtime.onInstalled.addListener(() => {
3738
browser.contextMenus.create(menu.pageItem);
3839
browser.contextMenus.create(menu.selectionItem);
@@ -51,6 +52,8 @@ export function createContextMenus() {
5152
browser.contextMenus.update('csvTable', { visible: false });
5253
browser.contextMenus.update('jsonTable', { visible: false });
5354
browser.contextMenus.update('htmlTable', { visible: false });
55+
56+
updateContextMenuLanguage(markupLanguage);
5457
});
5558
}
5659

@@ -59,9 +62,10 @@ export function createContextMenus() {
5962
* of HTML element the mouse is interacting with. This only has an effect if the context
6063
* menu is not currently visible.
6164
* @param {string} context - info about the element the mouse is interacting with.
65+
* @param {string} markupLanguage - the markup language the user chose.
6266
* @returns {Promise<void>}
6367
*/
64-
export async function updateContextMenu(context) {
68+
export async function updateContextMenu(context, markupLanguage) {
6569
switch (context.mouseover) {
6670
case 'selection':
6771
browser.contextMenus.update('link', { visible: false });
@@ -96,6 +100,39 @@ export async function updateContextMenu(context) {
96100
browser.contextMenus.update('jsonTable', { visible: false });
97101
browser.contextMenus.update('htmlTable', { visible: false });
98102
}
103+
104+
updateContextMenuLanguage(markupLanguage);
105+
}
106+
107+
/**
108+
* updateContextMenuLanguage changes the context menu options to reflect the user's
109+
* chosen markup language.
110+
* @param {string} markupLanguage - the markup language the user chose.
111+
* @returns {void}
112+
*/
113+
export function updateContextMenuLanguage(markupLanguage) {
114+
if (markupLanguage === 'html') {
115+
markupLanguage = 'HTML';
116+
}
117+
118+
browser.contextMenus.update('page', {
119+
title: `Copy ${markupLanguage} link to here`,
120+
});
121+
browser.contextMenus.update('selection', {
122+
title: `Copy ${markupLanguage} of selection`,
123+
});
124+
browser.contextMenus.update('link', {
125+
title: `Copy ${markupLanguage} of link`,
126+
});
127+
browser.contextMenus.update('image', {
128+
title: `Copy ${markupLanguage} of image`,
129+
});
130+
browser.contextMenus.update('video', {
131+
title: `Copy ${markupLanguage} of video`,
132+
});
133+
browser.contextMenus.update('audio', {
134+
title: `Copy ${markupLanguage} of audio`,
135+
});
99136
}
100137

101138
/**

rollup.config.firefox.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ export default [
7272
src: ['src/images'],
7373
dest: 'firefox',
7474
},
75+
{
76+
// copy all html files
77+
src: ['src/*.html'],
78+
dest: 'firefox',
79+
},
7580
{
7681
// copy js files in the converters folder
7782
src: ['src/converters/*'],

src/background.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
1414
limitations under the License.
1515
*/
1616

17-
import { browser, sleep, createContextMenus, updateContextMenu } from './config.js';
17+
import { browser, sleep, createContextMenus, updateContextMenu, updateContextMenuLanguage } from './config.js';
1818
import { getSetting } from './common.js';
1919
import { createTabLink } from './generators/md.js';
2020

21-
createContextMenus();
22-
21+
let markupLanguage = 'markdown';
2322
let lastClick = new Date(0);
2423
let doubleClickInterval = 500;
2524
let jsonDestination = 'clipboard';
2625

26+
getSetting('markupLanguage').then(value => {
27+
markupLanguage = value;
28+
createContextMenus(value);
29+
});
2730
getSetting('doubleClickInterval').then(value => doubleClickInterval = value);
2831
getSetting('jsonDestination').then(value => jsonDestination = value);
2932

@@ -61,7 +64,10 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
6164
// These context menu updates are done with messages from a content script
6265
// because the contextMenus.update method cannot update a context menu that is
6366
// already open. The content script listens for mouseover and mouseup events.
64-
await updateContextMenu(message.context);
67+
await updateContextMenu(message.context, markupLanguage);
68+
} else if (message.markupLanguage) {
69+
markupLanguage = message.markupLanguage;
70+
updateContextMenuLanguage(markupLanguage);
6571
} else if (message.doubleClickInterval) {
6672
doubleClickInterval = message.doubleClickInterval;
6773
} else if (message.jsonDestination) {
@@ -219,17 +225,34 @@ async function handleIconDoubleClick(activeTab) {
219225
}
220226
}
221227

222-
const subBrackets = await getSetting('subBrackets');
223-
const links = await Promise.all(
224-
tabs.map(tab => createTabLink(tab, subBrackets))
225-
);
226-
const bulletPoint = await getSetting('bulletPoint');
227-
const linksListMd = links.map(link => `${bulletPoint} ${link}\n`).join('');
228+
let text = '';
229+
switch (markupLanguage) {
230+
case 'html':
231+
const result = ['<ul>'];
232+
for (let i = 0; i < tabs.length; i++) {
233+
const anchor = ` <li><a href="${tabs[i].url}">${tabs[i].title}</a></li>`;
234+
result.push(anchor);
235+
}
236+
result.push('</ul>');
237+
text = result.join('\n');
238+
break;
239+
case 'markdown':
240+
const mdSubBrackets = await getSetting('mdSubBrackets');
241+
const links = await Promise.all(
242+
tabs.map(tab => createTabLink(tab, mdSubBrackets))
243+
);
244+
const mdBulletPoint = await getSetting('mdBulletPoint');
245+
text = links.map(link => `${mdBulletPoint} ${link}\n`).join('');
246+
break;
247+
default:
248+
await showStatus(0, 'Error', 'Unsupported markup language');
249+
return;
250+
}
228251

229252
const {
230253
status, notifTitle, notifBody,
231254
} = await browser.tabs.sendMessage(activeTab.id, {
232-
category: 'copy', text: linksListMd,
255+
category: 'copy', text: text,
233256
});
234257

235258
if (status === 0) { // failure

src/common.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,26 @@
1717
import { browser } from './config.js';
1818

1919
const defaultSettings = {
20-
youtubeMd: 'almost everywhere',
20+
markupLanguage: 'markdown',
2121
createTextFragment: true,
2222
omitNav: true,
2323
omitFooter: true,
2424
notifyOnWarning: false,
2525
notifyOnSuccess: false,
26-
subBrackets: 'underlined',
27-
selectionFormat: 'source with link',
28-
selectionTemplate: `
29-
> [!note]
26+
doubleClickWindows: 'current',
27+
doubleClickInterval: 500,
28+
29+
mdSelectionFormat: 'source with link',
30+
mdYoutube: 'almost everywhere',
31+
mdSubBrackets: 'underlined',
32+
mdBulletPoint: '-',
33+
mdSelectionTemplate: `> [!note]
3034
> from [{{link.title}}]({{link.url}}) on {{date.YYYYMMDD}}
3135
32-
{{selection}}
33-
`.trim(),
36+
{{selection}}`,
37+
38+
jsonEmptyCell: 'null',
3439
jsonDestination: 'clipboard',
35-
emptyCellJson: 'null',
36-
bulletPoint: '-',
37-
doubleClickWindows: 'current',
38-
doubleClickInterval: 500,
3940
};
4041

4142
/**

src/content.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,18 @@ async function handleIconSingleClick() {
264264
// count as a selection click
265265
return await handleSelectionRightClick('', selection);
266266
} else {
267-
const linkMd = await md.createLink(document.title, location.href);
268-
return await handleCopyRequest(linkMd);
267+
const markupLanguage = await getSetting('markupLanguage');
268+
switch (markupLanguage) {
269+
case 'markdown':
270+
const linkMd = await md.createLink(document.title, location.href);
271+
return await handleCopyRequest(linkMd);
272+
case 'html':
273+
const anchor = `<a href="${location.href}">${document.title}</a>`;
274+
return await handleCopyRequest(anchor);
275+
default:
276+
console.error('Unknown markup language:', markupLanguage);
277+
throw new Error('Unknown markup language:', markupLanguage);
278+
}
269279
}
270280
}
271281

@@ -311,9 +321,9 @@ async function handleSelectionRightClick(htmlId, selection) {
311321
}
312322
}
313323

314-
const markdown = await htmlSelection.createText(title, url, selection);
324+
const text = await htmlSelection.createText(title, url, selection);
315325

316-
return await handleCopyRequest(markdown);
326+
return await handleCopyRequest(text);
317327
}
318328

319329
/**

src/converters/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ For example, settings and environment info could be put into `ctx` to be used th
7373
const ctx = {
7474
locationHref: location.href,
7575
document: document,
76-
subBrackets: await getSetting('subBrackets'),
77-
bulletPoint: await getSetting('bulletPoint'),
7876
omitNav: await getSetting('omitNav'),
7977
omitFooter: await getSetting('omitFooter'),
80-
youtubeMd: await getSetting('youtubeMd'),
8178
indent: '',
79+
mdSubBrackets: await getSetting('mdSubBrackets'),
80+
mdBulletPoint: await getSetting('mdBulletPoint'),
81+
mdYoutube: await getSetting('mdYoutube'),
8282
};
8383
```
8484

src/converters/json.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ import * as tables from './tables.js';
2222
/**
2323
* htmlTableToJson converts an HTML table to JSON.
2424
* @param {DocumentFragment} frag
25-
* @param {string|undefined} emptyCellJson - the JSON representation of an empty cell.
25+
* @param {string|undefined} jsonEmptyCell - the JSON representation of an empty cell.
2626
* @returns {Promise<string>}
2727
*/
28-
export async function htmlTableToJson(frag, emptyCellJson) {
28+
export async function htmlTableToJson(frag, jsonEmptyCell) {
2929
const table = frag.firstChild;
3030

3131
const ctx = {
3232
document: document,
33-
emptyCellJson: emptyCellJson || await getSetting('emptyCellJson') || 'null',
33+
jsonEmptyCell: jsonEmptyCell || await getSetting('jsonEmptyCell') || 'null',
3434
};
3535

3636
return convertTable(ctx, table);
@@ -71,21 +71,21 @@ function convertTable(ctx, table) {
7171
if (x === 0) { // if this is the first column
7272
if (cellStr.length === 0) { // if the cell is empty
7373
if ( // if the empty cell JSON is already wrapped with quotes
74-
ctx.emptyCellJson.length > 0 &&
75-
ctx.emptyCellJson[0] === '"' &&
76-
ctx.emptyCellJson[ctx.emptyCellJson.length - 1] === '"'
74+
ctx.jsonEmptyCell.length > 0 &&
75+
ctx.jsonEmptyCell[0] === '"' &&
76+
ctx.jsonEmptyCell[ctx.jsonEmptyCell.length - 1] === '"'
7777
) {
78-
json.push(ctx.emptyCellJson + ': [');
78+
json.push(ctx.jsonEmptyCell + ': [');
7979
} else { // if the empty cell JSON is not already wrapped with quotes
80-
cellStr = ctx.emptyCellJson.replaceAll('"', '\\"');
80+
cellStr = ctx.jsonEmptyCell.replaceAll('"', '\\"');
8181
json.push('"' + cellStr + '": [');
8282
}
8383
} else { // if the cell is not empty
8484
cellStr = cellStr.replaceAll('"', '\\"').replaceAll(/\s+/g, ' ');
8585
json.push('"' + cellStr + '": [');
8686
}
8787
} else if (cellStr.length === 0) {
88-
json.push(ctx.emptyCellJson);
88+
json.push(ctx.jsonEmptyCell);
8989
} else if (['true', 'false', 'null'].includes(cellStr)) {
9090
json.push(cellStr);
9191
} else if (canBeJsonNumber(cellStr)) {

0 commit comments

Comments
 (0)