LinkMenu.jsx (4813B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { actionCreators as ac } from "common/Actions.mjs"; 6 import { connect } from "react-redux"; 7 import { ContextMenu } from "content-src/components/ContextMenu/ContextMenu"; 8 import { LinkMenuOptions } from "content-src/lib/link-menu-options"; 9 import React from "react"; 10 11 const DEFAULT_SITE_MENU_OPTIONS = [ 12 "CheckPinTopSite", 13 "EditTopSite", 14 "Separator", 15 "OpenInNewWindow", 16 "OpenInPrivateWindow", 17 "Separator", 18 "BlockUrl", 19 ]; 20 21 export class _LinkMenu extends React.PureComponent { 22 getOptions() { 23 const { props } = this; 24 const { 25 site, 26 index, 27 source, 28 isPrivateBrowsingEnabled, 29 siteInfo, 30 platform, 31 dispatch, 32 options, 33 shouldSendImpressionStats, 34 userEvent = ac.UserEvent, 35 } = props; 36 37 // Handle special case of default site 38 const propOptions = 39 site.isDefault && !site.searchTopSite && !site.sponsored_position 40 ? DEFAULT_SITE_MENU_OPTIONS 41 : options; 42 43 const linkMenuOptions = propOptions 44 .map(o => 45 LinkMenuOptions[o]( 46 site, 47 index, 48 source, 49 isPrivateBrowsingEnabled, 50 siteInfo, 51 platform 52 ) 53 ) 54 .map(option => { 55 const { action, impression, id, type, userEvent: eventName } = option; 56 if (!type && id) { 57 option.onClick = (event = {}) => { 58 const { ctrlKey, metaKey, shiftKey, button } = event; 59 // Only send along event info if there's something non-default to send 60 if (ctrlKey || metaKey || shiftKey || button === 1) { 61 action.data = Object.assign( 62 { 63 event: { ctrlKey, metaKey, shiftKey, button }, 64 }, 65 action.data 66 ); 67 } 68 dispatch(action); 69 if (eventName) { 70 let value; 71 // Bug 1958135: Pass additional info to ac.OPEN_NEW_WINDOW event 72 if (action.type === "OPEN_NEW_WINDOW") { 73 const { 74 card_type, 75 corpus_item_id, 76 event_source, 77 fetchTimestamp, 78 firstVisibleTimestamp, 79 format, 80 is_section_followed, 81 received_rank, 82 recommendation_id, 83 recommended_at, 84 scheduled_corpus_item_id, 85 section_position, 86 section, 87 selected_topics, 88 tile_id, 89 topic, 90 } = action.data; 91 92 value = { 93 card_type, 94 corpus_item_id, 95 event_source, 96 fetchTimestamp, 97 firstVisibleTimestamp, 98 format, 99 received_rank, 100 recommendation_id, 101 recommended_at, 102 scheduled_corpus_item_id, 103 ...(section 104 ? { is_section_followed, section_position, section } 105 : {}), 106 selected_topics: selected_topics ? selected_topics : "", 107 tile_id, 108 topic, 109 }; 110 } else { 111 value = { card_type: site.flight_id ? "spoc" : "organic" }; 112 } 113 const userEventData = Object.assign( 114 { 115 event: eventName, 116 source, 117 action_position: index, 118 value, 119 }, 120 siteInfo 121 ); 122 dispatch(userEvent(userEventData)); 123 if (impression && shouldSendImpressionStats) { 124 dispatch(impression); 125 } 126 } 127 }; 128 } 129 return option; 130 }); 131 132 // This is for accessibility to support making each item tabbable. 133 // We want to know which item is the first and which item 134 // is the last, so we can close the context menu accordingly. 135 linkMenuOptions[0].first = true; 136 linkMenuOptions[linkMenuOptions.length - 1].last = true; 137 return linkMenuOptions; 138 } 139 140 render() { 141 return ( 142 <ContextMenu 143 onUpdate={this.props.onUpdate} 144 onShow={this.props.onShow} 145 options={this.getOptions()} 146 keyboardAccess={this.props.keyboardAccess} 147 /> 148 ); 149 } 150 } 151 152 const getState = state => ({ 153 isPrivateBrowsingEnabled: state.Prefs.values.isPrivateBrowsingEnabled, 154 platform: state.Prefs.values.platform, 155 }); 156 export const LinkMenu = connect(getState)(_LinkMenu);