Skip to content

Commit

Permalink
Merge pull request #585 from ipfs-shipyard/fix/add-image-link-to-ipfs
Browse files Browse the repository at this point in the history
Improvements for Adding to IPFS via Context Menu
  • Loading branch information
lidel authored Oct 2, 2018
2 parents 329199b + 1bcaa9d commit 33f5598
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 132 deletions.
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

0 comments on commit 33f5598

Please sign in to comment.