Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements for Adding to IPFS via Context Menu #585

Merged
merged 8 commits into from
Oct 2, 2018
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
52 changes: 38 additions & 14 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@
"description": "A menu item in Browser Action pop-up (panel_unpinCurrentIpfsAddress)"
},
"panelCopy_currentIpfsAddress": {
"message": "Copy Canonical Address",
"message": "Copy IPFS Path",
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_currentIpfsAddress)"
},
"panelCopy_copyRawCid": {
"message": "Copy CID",
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_copyRawCid)"
},
"panel_copyCurrentPublicGwUrl": {
"message": "Copy Public Gateway URL",
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_copyCurrentPublicGwUrl)"
Expand All @@ -83,18 +87,42 @@
"message": "Non-IPFS resource",
"description": "Default label for icon hidden in Page Action menu (pageAction_titleNonIpfs)"
},
"contextMenu_AddToIpfsSelection": {
"message": "Add selected text to IPFS",
"description": "An item in right-click context menu (contextMenu_AddToIpfsSelection)"
"contextMenu_parentImage": {
"message": "Selected Image",
"description": "An item in right-click context menu (contextMenu_parentImage)"
},
"contextMenu_AddToIpfsRawCid": {
"message": "Add to IPFS",
"description": "An item in right-click context menu (contextMenu_AddToIpfsRawCid)"
"contextMenu_parentVideo": {
"message": "Selected Video",
"description": "An item in right-click context menu (contextMenu_parentVideo)"
},
"contextMenu_parentAudio": {
"message": "Selected Audio",
"description": "An item in right-click context menu (contextMenu_parentAudio)"
},
"contextMenu_parentLink": {
"message": "Linked Content",
"description": "An item in right-click context menu (contextMenu_parentLink)"
},
"contextMenu_parentText": {
"message": "Selected Text",
"description": "An item in right-click context menu (contextMenu_parentText)"
},
"contextMenu_parentPage": {
"message": "This Page",
"description": "An item in right-click context menu (contextMenu_parentPage)"
},
"contextMenu_AddToIpfsKeepFilename": {
"message": "Add to IPFS (Keep Filename)",
"description": "An item in right-click context menu (contextMenu_AddToIpfsKeepFilename)"
},
"contextMenu_AddToIpfsRawCid": {
"message": "Add to IPFS",
"description": "An item in right-click context menu (contextMenu_AddToIpfsRawCid)"
},
"contextMenu_AddToIpfsSelection": {
"message": "Add Selected Text to IPFS",
"description": "An item in right-click context menu (contextMenu_AddToIpfsSelection)"
},
"notify_addonIssueTitle": {
"message": "IPFS Add-on Issue",
"description": "A title of system notification (notify_addonIssueTitle)"
Expand All @@ -103,13 +131,9 @@
"message": "See Browser Console for more details",
"description": "A message in system notification (notify_addonIssueMsg)"
},
"notify_copiedPublicURLTitle": {
"message": "Copied Public URL",
"description": "A title of system notification (notify_copiedPublicURLTitle)"
},
"notify_copiedCanonicalAddressTitle": {
"message": "Copied Canonical Address",
"description": "A title of system notification (notify_copiedCanonicalAddressTitle)"
"notify_copiedTitle": {
"message": "Copied",
"description": "A title of system notification (notify_copiedTitle)"
},
"notify_pinnedIpfsResourceTitle": {
"message": "IPFS Resource is now pinned",
Expand Down
195 changes: 130 additions & 65 deletions add-on/src/lib/context-menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@

const browser = require('webextension-polyfill')

async function findUrlForContext (context) {
// mapping between context name and field with data for it
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/menus/ContextType
const contextSources = {
selection: 'selectionText',
image: 'srcUrl',
video: 'srcUrl',
audio: 'srcUrl',
link: 'linkUrl',
page: 'pageUrl'
}

async function findValueForContext (context, contextType) {
if (context) {
if (context.linkUrl) {
// present when clicked on a link
return context.linkUrl
if (contextType) {
const field = contextSources[contextType]
return context[field]
}
if (context.srcUrl) {
// present when clicked on page element such as image or video
return context.srcUrl
}
if (context.linkUrl) {
// present when clicked on a link
return context.linkUrl
}
if (context.pageUrl) {
// pageUrl is the root frame
return context.pageUrl
Expand All @@ -22,65 +37,110 @@ async function findUrlForContext (context) {
return currentTab.url
}

module.exports.findUrlForContext = findUrlForContext
module.exports.findValueForContext = findValueForContext

const contextMenuAddToIpfsSelection = 'contextMenu_AddToIpfsSelection'
// Context Roots
const menuParentImage = 'contextMenu_parentImage'
const menuParentVideo = 'contextMenu_parentVideo'
const menuParentAudio = 'contextMenu_parentAudio'
const menuParentLink = 'contextMenu_parentLink'
const menuParentPage = 'contextMenu_parentPage'
// const menuParentText = 'contextMenu_parentText'
// Generic Add to IPFS
const contextMenuAddToIpfsRawCid = 'contextMenu_AddToIpfsRawCid'
const contextMenuAddToIpfsKeepFilename = 'contextMenu_AddToIpfsKeepFilename'
// Add X to IPFS
const contextMenuAddToIpfsSelection = 'contextMenu_AddToIpfsSelection'
// Copy X
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpfsAddress'
const contextMenuCopyRawCid = 'panelCopy_copyRawCid'
const contextMenuCopyAddressAtPublicGw = 'panel_copyCurrentPublicGwUrl'
module.exports.contextMenuCopyCanonicalAddress = contextMenuCopyCanonicalAddress
module.exports.contextMenuCopyRawCid = contextMenuCopyRawCid
module.exports.contextMenuCopyAddressAtPublicGw = contextMenuCopyAddressAtPublicGw

function createContextMenus (getState, runtime, ipfsPathValidator, { onAddToIpfs, onAddToIpfsKeepFilename, onCopyCanonicalAddress, onCopyAddressAtPublicGw }) {
let copyAddressContexts = ['page', 'image', 'video', 'audio', 'link']
if (runtime.isFirefox) {
// https://github.com/ipfs-shipyard/ipfs-companion/issues/398
copyAddressContexts.push('page_action')
}
try {
browser.contextMenus.create({
id: contextMenuAddToIpfsSelection,
title: browser.i18n.getMessage(contextMenuAddToIpfsSelection),
contexts: ['selection'],
documentUrlPatterns: ['<all_urls>'],
enabled: false,
onclick: onAddToIpfs
})

browser.contextMenus.create({
id: contextMenuAddToIpfsRawCid,
title: browser.i18n.getMessage(contextMenuAddToIpfsRawCid),
contexts: ['image', 'video', 'audio', 'link'],
documentUrlPatterns: ['<all_urls>'],
enabled: false,
onclick: onAddToIpfs
})
// menu items that are enabled only when API is online
const apiMenuItems = new Set()
// menu items enabled only in IPFS context
const ipfsContextItems = new Set()

browser.contextMenus.create({
id: contextMenuAddToIpfsKeepFilename,
title: browser.i18n.getMessage(contextMenuAddToIpfsKeepFilename),
contexts: ['image', 'video', 'audio', 'link'],
documentUrlPatterns: ['<all_urls>'],
enabled: false,
onclick: onAddToIpfsKeepFilename
})

browser.contextMenus.create({
id: contextMenuCopyCanonicalAddress,
title: browser.i18n.getMessage(contextMenuCopyCanonicalAddress),
contexts: copyAddressContexts,
documentUrlPatterns: ['*://*/ipfs/*', '*://*/ipns/*'],
onclick: onCopyCanonicalAddress
})
function createContextMenus (getState, runtime, ipfsPathValidator, { onAddFromContext, onCopyCanonicalAddress, onCopyRawCid, onCopyAddressAtPublicGw }) {
try {
const createSubmenu = (id, contextType, menuBuilder) => {
browser.contextMenus.create({
id,
title: browser.i18n.getMessage(id),
documentUrlPatterns: ['<all_urls>'],
contexts: [contextType]
})
}
const createSeparator = (parentId, id, contextType) => {
return browser.contextMenus.create({
id: `${parentId}_${id}`,
parentId,
type: 'separator',
contexts: ['all']
})
}
const createAddToIpfsMenuItem = (parentId, id, contextType, ipfsAddOptions) => {
const itemId = `${parentId}_${id}`
apiMenuItems.add(itemId)
return browser.contextMenus.create({
id: itemId,
parentId,
title: browser.i18n.getMessage(id),
contexts: [contextType],
documentUrlPatterns: ['<all_urls>'],
enabled: false,
/* no support for 'icons' in Chrome
icons: {
'48': '/ui-kit/icons/stroke_cube.svg'
}, */
onclick: (context) => onAddFromContext(context, contextType, ipfsAddOptions)
})
}
const createCopierMenuItem = (parentId, id, contextType, handler) => {
const itemId = `${parentId}_${id}`
ipfsContextItems.add(itemId)
// some items also require API access
if (id === contextMenuCopyRawCid) {
apiMenuItems.add(itemId)
}
return browser.contextMenus.create({
id: itemId,
parentId,
title: browser.i18n.getMessage(id),
contexts: [contextType],
documentUrlPatterns: ['*://*/ipfs/*', '*://*/ipns/*'],
/* no support for 'icons' in Chrome
icons: {
'48': '/ui-kit/icons/stroke_copy.svg'
}, */
onclick: (context) => handler(context, contextType)
})
}
const buildSubmenu = (parentId, contextType) => {
createSubmenu(parentId, contextType)
createAddToIpfsMenuItem(parentId, contextMenuAddToIpfsKeepFilename, contextType, { wrapWithDirectory: true })
createAddToIpfsMenuItem(parentId, contextMenuAddToIpfsRawCid, contextType, { wrapWithDirectory: false })
createSeparator(parentId, 'separator-1', contextType)
createCopierMenuItem(parentId, contextMenuCopyAddressAtPublicGw, contextType, onCopyAddressAtPublicGw)
createCopierMenuItem(parentId, contextMenuCopyCanonicalAddress, contextType, onCopyCanonicalAddress)
createCopierMenuItem(parentId, contextMenuCopyRawCid, contextType, onCopyRawCid)
}

browser.contextMenus.create({
id: contextMenuCopyAddressAtPublicGw,
title: browser.i18n.getMessage(contextMenuCopyAddressAtPublicGw),
contexts: copyAddressContexts,
documentUrlPatterns: ['*://*/ipfs/*', '*://*/ipns/*'],
onclick: onCopyAddressAtPublicGw
})
/*
createSubmenu(menuParentText, 'selection')
createAddToIpfsMenuItem(menuParentText, contextMenuAddToIpfsSelection, 'selection')
*/
createAddToIpfsMenuItem(null, contextMenuAddToIpfsSelection, 'selection')
buildSubmenu(menuParentImage, 'image')
buildSubmenu(menuParentVideo, 'video')
buildSubmenu(menuParentAudio, 'audio')
buildSubmenu(menuParentLink, 'link')
buildSubmenu(menuParentPage, 'page')
} catch (err) {
// documentUrlPatterns is not supported in Brave
// documentUrlPatterns is not supported in Muon-Brave
if (err.message.indexOf('createProperties.documentUrlPatterns of contextMenus.create is not supported yet') > -1) {
console.warn('[ipfs-companion] Context menus disabled - createProperties.documentUrlPatterns of contextMenus.create is not supported yet')
return { update: () => Promise.resolve() }
Expand All @@ -93,26 +153,31 @@ function createContextMenus (getState, runtime, ipfsPathValidator, { onAddToIpfs
throw err
}

// enabled only when ipfsContext is shown when API is up
const apiAndIpfsContextItems = new Set([...apiMenuItems].filter(i => ipfsContextItems.has(i)))
// state to avoid async tab lookups
let ipfsContext = false

return {
async update (changedTabId) {
try {
const canUpload = getState().peerCount > 0
const items = [ contextMenuAddToIpfsSelection,
contextMenuAddToIpfsRawCid,
contextMenuAddToIpfsKeepFilename
]
for (let item of items) {
await browser.contextMenus.update(item, { enabled: canUpload })
}
if (changedTabId) {
// recalculate tab-dependant menu items
const currentTab = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0])
if (currentTab && currentTab.id === changedTabId) {
const ipfsContext = ipfsPathValidator.isIpfsPageActionsContext(currentTab.url)
browser.contextMenus.update(contextMenuCopyCanonicalAddress, { enabled: ipfsContext })
browser.contextMenus.update(contextMenuCopyAddressAtPublicGw, { enabled: ipfsContext })
ipfsContext = ipfsPathValidator.isIpfsPageActionsContext(currentTab.url)
}
}
const ifApi = getState().peerCount > 0
for (let item of apiMenuItems) {
await browser.contextMenus.update(item, { enabled: ifApi })
}
for (let item of ipfsContextItems) {
await browser.contextMenus.update(item, { enabled: ipfsContext })
}
for (let item of apiAndIpfsContextItems) {
await browser.contextMenus.update(item, { enabled: (ifApi && ipfsContext) })
}
} catch (err) {
console.log('[ipfs-companion] Error updating context menus', err)
}
Expand Down
42 changes: 33 additions & 9 deletions add-on/src/lib/copier.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict'

const browser = require('webextension-polyfill')
const { safeIpfsPath } = require('./ipfs-path')
const { findUrlForContext } = require('./context-menus')
const { safeIpfsPath, trimHashAndSearch } = require('./ipfs-path')
const { findValueForContext } = require('./context-menus')

async function copyTextToClipboard (copyText) {
const currentTab = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0])
Expand Down Expand Up @@ -34,21 +34,45 @@ async function copyTextToClipboard (copyText) {
}
}

function createCopier (getState, notify) {
function createCopier (getState, getIpfs, notify) {
return {
async copyCanonicalAddress (context) {
const url = await findUrlForContext(context)
async copyCanonicalAddress (context, contextType) {
const url = await findValueForContext(context, contextType)
const rawIpfsAddress = safeIpfsPath(url)
copyTextToClipboard(rawIpfsAddress)
notify('notify_copiedCanonicalAddressTitle', rawIpfsAddress)
notify('notify_copiedTitle', rawIpfsAddress)
},

async copyAddressAtPublicGw (context) {
const url = await findUrlForContext(context)
async copyRawCid (context, contextType) {
try {
const ipfs = getIpfs()
const url = await findValueForContext(context, contextType)
const rawIpfsAddress = trimHashAndSearch(safeIpfsPath(url))
const directCid = (await ipfs.resolve(rawIpfsAddress, { recursive: true, dhtt: '5s', dhtrc: 1 })).split('/')[2]
copyTextToClipboard(directCid)
notify('notify_copiedTitle', directCid)
} catch (error) {
console.error('Unable to resolve/copy direct CID:', error.message)
if (notify) {
const errMsg = error.toString()
if (errMsg.startsWith('Error: no link')) {
// Sharding support is limited:
// - https://github.com/ipfs/js-ipfs/issues/1279
// - https://github.com/ipfs/go-ipfs/issues/5270
notify('notify_addonIssueTitle', 'Unable to resolve CID within HAMT-sharded directory, sorry! Will be fixed soon.')
} else {
notify('notify_addonIssueTitle', 'notify_inlineErrorMsg', error.message)
}
}
}
},

async copyAddressAtPublicGw (context, contextType) {
const url = await findValueForContext(context, contextType)
const state = getState()
const urlAtPubGw = url.replace(state.gwURLString, state.pubGwURLString)
copyTextToClipboard(urlAtPubGw)
notify('notify_copiedPublicURLTitle', urlAtPubGw)
notify('notify_copiedTitle', urlAtPubGw)
}
}
}
Expand Down
Loading