Skip to content

Commit

Permalink
Merge pull request #161 from Stardown-app/feature-ctx-opts
Browse files Browse the repository at this point in the history
Create ctx menu opts: quote & selection w/ source
  • Loading branch information
wheelercj authored Nov 24, 2024
2 parents 0d0acc6 + 57f532c commit 0c01400
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 312 deletions.
46 changes: 4 additions & 42 deletions chrome/browserSpecific.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ export function getShortcutInstructions() {

/**
* createContextMenus creates the context menu options.
* @param {string} markupLanguage - the markup language the user chose.
* @returns {void}
*/
export function createContextMenus(markupLanguage) {
export function createContextMenus() {
// This function should do nothing. It needs to exist because the Firefox extension
// uses a function by the same name that is imported into the background script.
}
Expand All @@ -57,10 +56,9 @@ let isTableSelected = false;
* of HTML element the mouse is interacting with. This only has an effect if the context
* menu is not currently visible.
* @param {string} context - info about the element the mouse is interacting with.
* @param {string} markupLanguage - the markup language the user chose.
* @returns {Promise<void>}
*/
export async function updateContextMenu(context, markupLanguage) {
export async function updateContextMenu(context) {
// The `browser.contextMenus.update` method doesn't work well in Chromium because
// when it's used to hide all but one context menu option, the one remaining would
// appear under a "Stardown" parent menu option instead of being in the root of the
Expand Down Expand Up @@ -88,48 +86,12 @@ export async function updateContextMenu(context, markupLanguage) {
}

browser.contextMenus.create(menu.selectionItem);
browser.contextMenus.create(menu.selectionWithSourceItem);
browser.contextMenus.create(menu.selectionQuoteItem);
browser.contextMenus.create(menu.pageItem);
browser.contextMenus.create(menu.pageSectionItem);
browser.contextMenus.create(menu.videoItem);
browser.contextMenus.create(menu.audioItem);
}

updateContextMenuLanguage(markupLanguage);
}
}

/**
* updateContextMenuLanguage changes the context menu options to reflect the user's
* chosen markup language.
* @param {string} markupLanguage - the markup language the user chose.
* @returns {void}
*/
export function updateContextMenuLanguage(markupLanguage) {
if (markupLanguage === 'html') {
markupLanguage = 'HTML';
} else if (markupLanguage === 'markdown with some html') {
markupLanguage = 'markdown';
}

browser.contextMenus.update('page', {
title: `Copy ${markupLanguage} link for this page`,
}, () => { if (browser.runtime.lastError) return; });
browser.contextMenus.update('pageSection', {
title: `Copy ${markupLanguage} link for this section`,
}, () => { if (browser.runtime.lastError) return; });
browser.contextMenus.update('selection', {
title: `Copy ${markupLanguage} of selection`,
}, () => { if (browser.runtime.lastError) return; });
browser.contextMenus.update('link', {
title: `Copy ${markupLanguage} of link`,
}, () => { if (browser.runtime.lastError) return; });
browser.contextMenus.update('image', {
title: `Copy ${markupLanguage} of image`,
}, () => { if (browser.runtime.lastError) return; });
browser.contextMenus.update('video', {
title: `Copy ${markupLanguage} of video`,
}, () => { if (browser.runtime.lastError) return; });
browser.contextMenus.update('audio', {
title: `Copy ${markupLanguage} of audio`,
}, () => { if (browser.runtime.lastError) return; });
}
36 changes: 17 additions & 19 deletions docs/manual-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,22 @@ When the user right-clicks part of a web page, their browser detects the type of

- [ ] **Pressing any button in the popup** should do what the button's text describes.
- [ ] **Pressing Alt+C (Mac: ⌥+C)** copies a markdown link for the page, unless part of the page is selected in which case markdown of the selection is copied instead.
- [ ] **Right-clicking an empty part of a page** shows the "Copy markdown link for this page" and "Copy markdown link for this section" options.
- [ ] **Right-clicking a website's unselected header** shows the "Copy markdown link for this page" and "Copy markdown link for this section" options.
- [ ] **Right-clicking selected text** shows the "Copy markdown of selection" option.
- [ ] **Right-clicking an unselected image** shows the "Copy markdown of image" option.
- [ ] **Selecting text, then right-clicking an unselected image**, due to browser limitations, shows no context menu option in Chromium and shows "Copy markdown of selection" in Firefox.
- [ ] **Selecting text and image(s), then right-clicking the selected text** shows the "Copy markdown of selection" option.
- [ ] **Selecting text and image(s), then right-clicking a selected image** shows the "Copy markdown of selection" option.
- [ ] **Selecting text and link(s), then right-clicking a selected link** shows the "Copy markdown of selection" option.
- [ ] **Right-clicking a link that is not an image** shows the "Copy markdown of link" option.
- [ ] **Right-clicking a link that is an image** might not show any context menu options due to browser limitations. If a context menu option appears, it should be "Copy markdown of image".
- [ ] **Right-clicking a video** shows the "Copy markdown of video" option, but may require a second right-click for the correct context menu to appear because some videos (e.g. YouTube videos) have a special context menu.
- [ ] **Right-clicking an audio player** shows the "Copy markdown of audio" option.
- [ ] **Right-clicking an unselected part of a page** shows the "Copy link for this page" and "Copy link for this section" options.
- [ ] **Right-clicking an unselected image** shows the "Copy image" option.
- [ ] **Selecting text and/or image(s), then right-clicking the selection** shows the options "Copy selection", "Copy selection with source", and "Copy selection quote".
- [ ] **Right-clicking a link that is not an image** shows the "Copy link" option.
- [ ] **Right-clicking a link that is an image** might not show any context menu options due to browser limitations. If a context menu option appears, it should be "Copy image".
- [ ] **Right-clicking a video** shows the "Copy video" option, but may require a second right-click for the correct context menu to appear because some videos (e.g. YouTube videos) have a special context menu.
- [ ] **Right-clicking an audio player** shows the "Copy audio" option.
- [ ] **Selecting the contents of a table and right-clicking the selection** shows several options: "Copy markdown of table", "Copy TSV of table", "Copy CSV of table", "Copy JSON of table", and "Copy HTML of table". Each option should result in a table with everything aligned correctly, leaving some cells empty and others duplicated as necessary.
- [ ] "Copy markdown link for this page" copies a markdown link for the page. The link is guaranteed to not have an HTML element ID nor a text fragment.
- [ ] "Copy markdown link for this section" copies a markdown link for the page with the nearest HTML element ID above (*above* in the HTML element tree sense) where the page was right-clicked.
- [ ] "Copy markdown of selection", by default, copies markdown of the selected text (including all of the page's formatting that markdown supports), and a markdown link containing a text fragment and possibly an HTML element ID.
- [ ] "Copy markdown of image" copies markdown of the image using the image's URL and any alt text.
- [ ] "Copy markdown of link" copies markdown of the link, using the same title and URL as the link in the page (except for any character escapes or encodings).
- [ ] "Copy markdown of video" copies markdown of the video using the video source's URL.
- [ ] "Copy markdown of audio" copies a markdown link to either an audio file or a site that plays the audio.
- [ ] Context menu options
- [ ] "Copy link for this page" copies a markdown link for the page. The link is guaranteed to not have an HTML element ID nor a text fragment.
- [ ] "Copy link for this section" copies a markdown link for the page with the nearest HTML element ID above (*above* in the HTML element tree sense) where the page was right-clicked.
- [ ] "Copy selection" copies markdown of the selection (including all of the page's formatting that markdown supports).
- [ ] "Copy selection with source" does the same as "Copy selection" but also includes a markdown link containing a text fragment and possibly an HTML element ID.
- [ ] "Copy selection quote" does the same as "Copy selection with source" but formats the output as a markdown block quote.
- [ ] "Copy image" copies markdown of the image using the image's URL and any alt text.
- [ ] "Copy link" copies markdown of the link, using the same title and URL as the link in the page (except for any character escapes or encodings).
- [ ] "Copy video" copies markdown of the video using the video source's URL.
- [ ] "Copy audio" copies a markdown link to either an audio file or a site that plays the audio.
- [ ] **Change settings and repeat as necessary**
52 changes: 8 additions & 44 deletions firefox/browserSpecific.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ export function getShortcutInstructions() {

/**
* createContextMenus creates the context menu options.
* @param {string} markupLanguage - the markup language the user chose.
* @returns {void}
*/
export function createContextMenus(markupLanguage) {
export function createContextMenus() {
browser.runtime.onInstalled.addListener(() => {
browser.contextMenus.create(menu.pageItem);
browser.contextMenus.create(menu.pageSectionItem);
browser.contextMenus.create(menu.selectionItem);
browser.contextMenus.create(menu.selectionWithSourceItem);
browser.contextMenus.create(menu.selectionQuoteItem);
browser.contextMenus.create(menu.linkItem);
browser.contextMenus.create(menu.imageItem);
browser.contextMenus.create(menu.videoItem);
Expand All @@ -63,8 +64,6 @@ export function createContextMenus(markupLanguage) {
browser.contextMenus.update('csvTable', { visible: false });
browser.contextMenus.update('jsonTable', { visible: false });
browser.contextMenus.update('htmlTable', { visible: false });

updateContextMenuLanguage(markupLanguage);
});
}

Expand All @@ -73,10 +72,9 @@ export function createContextMenus(markupLanguage) {
* of HTML element the mouse is interacting with. This only has an effect if the context
* menu is not currently visible.
* @param {string} context - info about the element the mouse is interacting with.
* @param {string} markupLanguage - the markup language the user chose.
* @returns {Promise<void>}
*/
export async function updateContextMenu(context, markupLanguage) {
export async function updateContextMenu(context) {
switch (context.mouseover) {
case 'selection':
browser.contextMenus.update('link', { visible: false });
Expand All @@ -96,6 +94,8 @@ export async function updateContextMenu(context, markupLanguage) {
await sleep(100); // wait for the context menu

browser.contextMenus.update('selection', { visible: false });
browser.contextMenus.update('selectionWithSource', { visible: false });
browser.contextMenus.update('selectionQuote', { visible: false });

browser.contextMenus.update('markdownTable', { visible: true });
browser.contextMenus.update('tsvTable', { visible: true });
Expand All @@ -104,49 +104,13 @@ export async function updateContextMenu(context, markupLanguage) {
browser.contextMenus.update('htmlTable', { visible: true });
} else if (context.selectionchange) {
browser.contextMenus.update('selection', { visible: true });
browser.contextMenus.update('selectionWithSource', { visible: true });
browser.contextMenus.update('selectionQuote', { visible: true });

browser.contextMenus.update('markdownTable', { visible: false });
browser.contextMenus.update('tsvTable', { visible: false });
browser.contextMenus.update('csvTable', { visible: false });
browser.contextMenus.update('jsonTable', { visible: false });
browser.contextMenus.update('htmlTable', { visible: false });
}

updateContextMenuLanguage(markupLanguage);
}

/**
* updateContextMenuLanguage changes the context menu options to reflect the user's
* chosen markup language.
* @param {string} markupLanguage - the markup language the user chose.
* @returns {void}
*/
export function updateContextMenuLanguage(markupLanguage) {
if (markupLanguage === 'html') {
markupLanguage = 'HTML';
} else if (markupLanguage === 'markdown with some html') {
markupLanguage = 'markdown';
}

browser.contextMenus.update('page', {
title: `Copy ${markupLanguage} link for this page`,
});
browser.contextMenus.update('pageSection', {
title: `Copy ${markupLanguage} link for this section`,
});
browser.contextMenus.update('selection', {
title: `Copy ${markupLanguage} of selection`,
});
browser.contextMenus.update('link', {
title: `Copy ${markupLanguage} of link`,
});
browser.contextMenus.update('image', {
title: `Copy ${markupLanguage} of image`,
});
browser.contextMenus.update('video', {
title: `Copy ${markupLanguage} of video`,
});
browser.contextMenus.update('audio', {
title: `Copy ${markupLanguage} of audio`,
});
}
25 changes: 14 additions & 11 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
limitations under the License.
*/

import { browser, sleep, createContextMenus, updateContextMenu, updateContextMenuLanguage } from './browserSpecific.js';
import { browser, sleep, createContextMenus, updateContextMenu } from './browserSpecific.js';
import { getSetting } from './getSetting.js';
import { createTabLink } from './generators/md.js';

let markupLanguage = 'markdown';
let jsonDestination = 'clipboard';
let windowId = null;

Expand All @@ -28,10 +27,7 @@ browser.runtime.onInstalled.addListener(async details => {
}
});

getSetting('markupLanguage').then(value => {
markupLanguage = value;
createContextMenus(value);
});
createContextMenus();
getSetting('jsonDestination').then(value => jsonDestination = value);
browser.tabs.query({ currentWindow: true, active: true }).then(tabs => {
windowId = tabs[0].windowId;
Expand Down Expand Up @@ -85,7 +81,7 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
// because the contextMenus.update method cannot update a context menu that
// is already open. The content script listens for mouseover and mouseup
// events.
await updateContextMenu(message.context, markupLanguage);
await updateContextMenu(message.context);
break;
case 'downloadFile':
await downloadFile(message.file);
Expand Down Expand Up @@ -118,10 +114,6 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
case 'settingsButtonPressed':
browser.runtime.openOptionsPage();
break;
case 'markupLanguage':
markupLanguage = message.markupLanguage;
updateContextMenuLanguage(markupLanguage);
break;
case 'jsonDestination':
jsonDestination = message.jsonDestination;
break;
Expand All @@ -148,6 +140,16 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => {
tab, { category: 'selectionRightClick' }, { frameId: info.frameId },
);
break;
case 'selectionWithSource':
await handleInteraction(
tab, { category: 'selectionWithSourceRightClick' }, { frameId: info.frameId },
);
break;
case 'selectionQuote':
await handleInteraction(
tab, { category: 'selectionQuoteRightClick' }, { frameId: info.frameId },
);
break;
case 'link':
// In Chromium unlike in Firefox, `info.linkText` is undefined and no
// property in `info` has the link's text.
Expand Down Expand Up @@ -304,6 +306,7 @@ async function handleCopyMultipleTabs(activeTab) {

// create the links
let text = '';
const markupLanguage = await getSetting('markupLanguage');
switch (markupLanguage) {
case 'html':
const result = ['<ul>'];
Expand Down
18 changes: 11 additions & 7 deletions src/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ async function handleRequest(message) {
return await handleCopyRequest(message.text);
case 'copySelectionShortcut':
// respond to use of the copy keyboard shortcut or copy button
return await handleCopySelectionShortcut();
return await handleCopySelectionShortcut(message.category);
case 'copyEntirePageShortcut':
return await handleCopyPageRequest();
case 'pageRightClick':
Expand All @@ -214,9 +214,11 @@ async function handleRequest(message) {
const id1 = await getClickedElementId(clickedElement);
return await handlePageSectionRightClick(id1, selection);
case 'selectionRightClick':
case 'selectionWithSourceRightClick':
case 'selectionQuoteRightClick':
const selection1 = window.getSelection();
const id2 = await getClickedElementId(clickedElement);
return await handleSelectionCopyRequest(id2, selection1);
return await handleSelectionCopyRequest(id2, selection1, message.category);
case 'linkRightClick':
return await handleCreateLink(linkText, message.linkUrl);
case 'imageRightClick':
Expand All @@ -227,7 +229,7 @@ async function handleRequest(message) {
return await handleCreateAudio(message.srcUrl, message.pageUrl);
case 'markdownTableRightClick':
const id3 = await getClickedElementId(clickedElement);
return await handleSelectionCopyRequest(id3, tableSelection);
return await handleSelectionCopyRequest(id3, tableSelection, message.category);
case 'tsvTableRightClick':
return await handleCsvTableRightClick(tableSelection, '\t');
case 'csvTableRightClick':
Expand Down Expand Up @@ -274,15 +276,16 @@ async function getClickedElementId(clickedElement) {
/**
* handleCopySelectionShortcut handles a request from the user to copy a Selection or a
* link for the current tab.
* @param {string} messageCategory
* @returns {Promise<ContentResponse>}
*/
async function handleCopySelectionShortcut() {
async function handleCopySelectionShortcut(messageCategory) {
const selection = window.getSelection();
if (selection && selection.type === 'Range') {
// Only allow Range (and not Caret) Selections because the copy selection
// shortcut must copy a link for the current tab when there is no Range
// Selection (when none of the page is highlighted).
return await handleSelectionCopyRequest('', selection);
return await handleSelectionCopyRequest('', selection, messageCategory);
}

// none of the page is highlighted, so create a link for the page instead
Expand All @@ -308,12 +311,13 @@ async function handlePageSectionRightClick(htmlId, selection) {
* handleSelectionCopyRequest handles a request to copy a selection.
* @param {string} htmlId - the ID of the HTML element that was right-clicked.
* @param {Selection} selection - a selection object.
* @param {string} messageCategory
* @returns {Promise<ContentResponse>}
*/
async function handleSelectionCopyRequest(htmlId, selection) {
async function handleSelectionCopyRequest(htmlId, selection, messageCategory) {
const title = document.title;
const url = await addIdAndTextFragment(location.href, htmlId, selection);
const text = await htmlSelection.createText(title, url, selection);
const text = await htmlSelection.createText(title, url, selection, messageCategory);
return await handleCopyRequest(text);
}

Expand Down
2 changes: 1 addition & 1 deletion src/contentUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export async function applyTemplate(template, title, url, text) {
const templateVars = {
link: { title, url },
date: { YYYYMMDD },
text,
text: text.trim(),
};

try {
Expand Down
Loading

0 comments on commit 0c01400

Please sign in to comment.