diff --git a/build-system/compile/bundles.config.extensions.json b/build-system/compile/bundles.config.extensions.json index 34bd7833ca45..85dffbb1ff87 100644 --- a/build-system/compile/bundles.config.extensions.json +++ b/build-system/compile/bundles.config.extensions.json @@ -885,6 +885,14 @@ "hasCss": true } }, + { + "name": "amp-story-share-menu", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "hasCss": true + } + }, { "name": "amp-story-shopping", "version": "0.1", diff --git a/build-system/test-configs/dep-check-config.js b/build-system/test-configs/dep-check-config.js index eaf4a2dd45b9..acc463ffcbb4 100644 --- a/build-system/test-configs/dep-check-config.js +++ b/build-system/test-configs/dep-check-config.js @@ -264,6 +264,14 @@ exports.rules = [ 'extensions/amp-story-education/0.1/amp-story-education.js->extensions/amp-story/1.0/utils.js', 'extensions/amp-story-education/0.1/amp-story-education.js->extensions/amp-story/1.0/amp-story-localization-service.js', + // Story share menu + 'extensions/amp-story-share-menu/0.1/amp-story-share-menu.js->extensions/amp-story/1.0/utils.js', + 'extensions/amp-story-share-menu/0.1/amp-story-share-menu.js->extensions/amp-story/1.0/amp-story-share.js', + 'extensions/amp-story-share-menu/0.1/amp-story-share-menu.js->extensions/amp-story/1.0/amp-story-store-service.js', + 'extensions/amp-story-share-menu/0.1/amp-story-share-menu.js->extensions/amp-story/1.0/story-analytics.js', + 'extensions/amp-story/1.0/amp-story.js->extensions/amp-story-share-menu/0.1/amp-story-share-menu.js', + 'extensions/amp-story-share-menu/0.1/amp-story-share-menu.js->extensions/amp-story/1.0/amp-story-localization-service.js', + // Story Shopping 'extensions/amp-story-shopping/0.1/amp-story-shopping-config.js->extensions/amp-story/1.0/amp-story-store-service.js', 'extensions/amp-story-shopping/0.1/amp-story-shopping-config.js->extensions/amp-story/1.0/request-utils.js', diff --git a/css/Z_INDEX.md b/css/Z_INDEX.md index 9751b1aa798d..9dc25417589c 100644 --- a/css/Z_INDEX.md +++ b/css/Z_INDEX.md @@ -37,8 +37,8 @@ | `.i-amphtml-story-consent` | 100005 | [extensions/amp-story/1.0/amp-story-consent.css](/extensions/amp-story/1.0/amp-story-consent.css) | | `amp-story-access[type=blocking]` | 100004 | [extensions/amp-story/1.0/amp-story-access.css](/extensions/amp-story/1.0/amp-story-access.css) | | `.i-amphtml-story-toast` | 100004 | [extensions/amp-story/1.0/amp-story.css](/extensions/amp-story/1.0/amp-story.css) | +| `.i-amphtml-story-share-menu` | 100003 | [extensions/amp-story-share-menu/0.1/amp-story-share-menu.css](/extensions/amp-story-share-menu/0.1/amp-story-share-menu.css) | | `amp-story-access` | 100003 | [extensions/amp-story/1.0/amp-story-access.css](/extensions/amp-story/1.0/amp-story-access.css) | -| `.i-amphtml-story-share-menu` | 100003 | [extensions/amp-story/1.0/amp-story-share-menu.css](/extensions/amp-story/1.0/amp-story-share-menu.css) | | `.i-amphtml-story-has-new-page-notification-container` | 100002 | [extensions/amp-story/1.0/amp-story-system-layer.css](/extensions/amp-story/1.0/amp-story-system-layer.css) | | `amp-story[standalone] .i-amphtml-story-developer-log` | 100002 | [extensions/amp-story/1.0/amp-story.css](/extensions/amp-story/1.0/amp-story.css) | | `.i-amphtml-story-button-container` | 100002 | [extensions/amp-story/1.0/pagination-buttons.css](/extensions/amp-story/1.0/pagination-buttons.css) | diff --git a/extensions/amp-story/1.0/amp-story-share-menu.css b/extensions/amp-story-share-menu/0.1/amp-story-share-menu.css similarity index 98% rename from extensions/amp-story/1.0/amp-story-share-menu.css rename to extensions/amp-story-share-menu/0.1/amp-story-share-menu.css index 9977d162b35f..17e227b96ca8 100644 --- a/extensions/amp-story/1.0/amp-story-share-menu.css +++ b/extensions/amp-story-share-menu/0.1/amp-story-share-menu.css @@ -1,6 +1,6 @@ -@import './amp-story-shadow-reset.css'; +@import '../../amp-story/1.0/amp-story-shadow-reset.css'; @import './amp-story-share.css'; .i-amphtml-story-share-menu { diff --git a/extensions/amp-story/1.0/amp-story-share-menu.js b/extensions/amp-story-share-menu/0.1/amp-story-share-menu.js similarity index 66% rename from extensions/amp-story/1.0/amp-story-share-menu.js rename to extensions/amp-story-share-menu/0.1/amp-story-share-menu.js index 031b3ea8db2f..8f1a891c5de0 100644 --- a/extensions/amp-story/1.0/amp-story-share-menu.js +++ b/extensions/amp-story-share-menu/0.1/amp-story-share-menu.js @@ -1,80 +1,32 @@ +import {Keys_Enum} from '#core/constants/key-codes'; import * as Preact from '#core/dom/jsx'; -import { - ANALYTICS_TAG_NAME, - StoryAnalyticsEvent, - getAnalyticsService, -} from './story-analytics'; + +import {Services} from '#service'; +import {LocalizedStringId_Enum} from '#service/localization/strings'; + +import {user} from '#utils/log'; + +import {CSS} from '../../../build/amp-story-share-menu-0.1.css'; +import {getAmpdoc} from '../../../src/service-helpers'; +import {localize} from '../../amp-story/1.0/amp-story-localization-service'; +import {ShareWidget} from '../../amp-story/1.0/amp-story-share'; import { Action, StateProperty, UIType, getStoreService, -} from './amp-story-store-service'; -import {CSS} from '../../../build/amp-story-share-menu-1.0.css'; -import {Keys_Enum} from '#core/constants/key-codes'; -import {LocalizedStringId_Enum} from '#service/localization/strings'; -import {Services} from '#service'; -import {ShareWidget} from './amp-story-share'; -import {createShadowRootWithStyle} from './utils'; -import {getAmpdoc} from '../../../src/service-helpers'; -import {localize} from './amp-story-localization-service'; +} from '../../amp-story/1.0/amp-story-store-service'; +import { + ANALYTICS_TAG_NAME, + StoryAnalyticsEvent, + getAnalyticsService, +} from '../../amp-story/1.0/story-analytics'; +import {createShadowRootWithStyle} from '../../amp-story/1.0/utils'; /** @const {string} Class to toggle the share menu. */ export const VISIBLE_CLASS = 'i-amphtml-story-share-menu-visible'; -/** - * Quick share template, used as a fallback if native sharing is not supported. - * @param {!Element} element - * @param {function(Event)} close - * @param {?Array|?Element|?string|undefined} children - * @return {!Element} - */ -const renderForFallbackSharing = (element, close, children) => { - return ( - - ); -}; - -/** - * System amp-social-share button template. - * @return {!Element} - */ -const renderAmpSocialShareSystemElement = () => { - return ( - - ); -}; +const TAG = 'amp-story-share-menu'; /** * Share menu UI. @@ -134,15 +86,12 @@ export class ShareMenu { } /** - * Builds a hidden amp-social-share button that triggers the native system - * sharing UI. + * Builds a element used for analytics, since the sharing menu is not rendered. * @private * @return {!Element} */ buildForSystemSharing_() { - this.shareWidget_.loadRequiredExtensions(getAmpdoc(this.parentEl_)); - this.element_ = renderAmpSocialShareSystemElement(); - return this.element_; + return (this.element_ =
); } /** @@ -154,10 +103,11 @@ export class ShareMenu { const shareWidgetElement = this.shareWidget_.build( getAmpdoc(this.parentEl_) ); - this.element_ = renderForFallbackSharing( - this.parentEl_, - () => this.close_(), - shareWidgetElement + this.element_ = this.renderForFallbackSharing_(shareWidgetElement); + // TODO(mszylkowski): import '../../amp-social-share/0.1/amp-social-share' when this file is lazy loaded. + Services.extensionsFor(this.win_).installExtensionForDoc( + getAmpdoc(this.parentEl_), + 'amp-social-share' ); // Only listen for closing when system share is unsupported, since the @@ -203,7 +153,7 @@ export class ShareMenu { if (this.isSystemShareSupported_ && isOpen) { // Dispatches a click event on the amp-social-share button to trigger the // native system sharing UI. This has to be done upon user interaction. - this.element_.dispatchEvent(new Event('click')); + this.openSystemShare_(); // There is no way to know when the user dismisses the native system share // menu, so we pretend it is closed on the story end, and let the native @@ -217,7 +167,8 @@ export class ShareMenu { this.element_.setAttribute('aria-hidden', !isOpen); }); } - this.element_[ANALYTICS_TAG_NAME] = 'amp-story-share-menu'; + + this.element_[ANALYTICS_TAG_NAME] = TAG; this.analyticsService_.triggerEvent( isOpen ? StoryAnalyticsEvent.OPEN : StoryAnalyticsEvent.CLOSE, this.element_ @@ -244,4 +195,55 @@ export class ShareMenu { close_() { this.storeService_.dispatch(Action.TOGGLE_SHARE_MENU, false); } + + /** + * Opens the sharing dialog of native browsers. + * @private + */ + openSystemShare_() { + const {navigator} = this.win_; + const shareData = { + url: Services.documentInfoForDoc(this.parentEl_).canonicalUrl, + text: this.win_.document.title, + }; + navigator.share(shareData).catch((e) => { + user().warn(TAG, e.message, shareData); + }); + } + + /** + * Quick share template, used as a fallback if native sharing is not supported. + * @param {?Array|?Element|?string|undefined} children + * @return {!Element} + */ + renderForFallbackSharing_(children) { + return ( + + ); + } } diff --git a/extensions/amp-story/1.0/amp-story-share.css b/extensions/amp-story-share-menu/0.1/amp-story-share.css similarity index 100% rename from extensions/amp-story/1.0/amp-story-share.css rename to extensions/amp-story-share-menu/0.1/amp-story-share.css diff --git a/extensions/amp-story/1.0/amp-story-share.js b/extensions/amp-story/1.0/amp-story-share.js index 38045ca8ae85..367d76df67d6 100644 --- a/extensions/amp-story/1.0/amp-story-share.js +++ b/extensions/amp-story/1.0/amp-story-share.js @@ -17,7 +17,6 @@ import {isObject} from '#core/types'; * @const {!Object} */ const SHARE_PROVIDER_LOCALIZED_STRING_ID = map({ - 'system': LocalizedStringId_Enum.AMP_STORY_SHARING_PROVIDER_NAME_SYSTEM, 'email': LocalizedStringId_Enum.AMP_STORY_SHARING_PROVIDER_NAME_EMAIL, 'facebook': LocalizedStringId_Enum.AMP_STORY_SHARING_PROVIDER_NAME_FACEBOOK, 'line': LocalizedStringId_Enum.AMP_STORY_SHARING_PROVIDER_NAME_LINE, @@ -96,6 +95,10 @@ function buildProvider(doc, shareType) { SHARE_PROVIDER_LOCALIZED_STRING_ID[shareType]; if (!shareProviderLocalizedStringId) { + user().warn( + 'AMP-STORY', + `'${shareType}'is not a valid share provider type.` + ); return null; } @@ -170,7 +173,6 @@ export class ShareWidget {
); @@ -220,21 +222,6 @@ export class ShareWidget { Toast.show(this.storyEl_, buildCopySuccessfulToast(this.win.document, url)); } - /** - * @return {?Element} - * @private - */ - maybeRenderSystemShareButton_() { - if (!this.isSystemShareSupported()) { - // `amp-social-share` will hide `system` buttons when not supported, but - // we also need to avoid adding it for rendering reasons. - return null; - } - - this.loadRequiredExtensions(); - return buildProvider(this.win.document, 'system'); - } - /** * NOTE(alanorozco): This is a duplicate of the logic in the * `amp-social-share` component. @@ -256,8 +243,6 @@ export class ShareWidget { * @protected */ loadProviders() { - this.loadRequiredExtensions(); - const shareEl = this.storyEl_.querySelector( 'amp-story-social-share, amp-story-bookend' ); @@ -289,16 +274,6 @@ export class ShareWidget { delete params['provider']; } - if (provider == 'system') { - user().warn( - 'AMP-STORY', - '`system` is not a valid share provider type. Native sharing is ' + - 'enabled by default and cannot be turned off.', - provider - ); - return; - } - const element = buildProvider( this.win.document, /** @type {string} */ (provider) @@ -317,13 +292,4 @@ export class ShareWidget { // always be last in list list.insertBefore(item, list.lastElementChild); } - - /** - */ - loadRequiredExtensions() { - Services.extensionsFor(this.win).installExtensionForDoc( - this.getAmpDoc_(), - 'amp-social-share' - ); - } } diff --git a/extensions/amp-story/1.0/amp-story.js b/extensions/amp-story/1.0/amp-story.js index 964c7f8caaa3..6d37ce5cee48 100644 --- a/extensions/amp-story/1.0/amp-story.js +++ b/extensions/amp-story/1.0/amp-story.js @@ -53,7 +53,7 @@ import {LiveStoryManager} from './live-story-manager'; import {MediaPool, MediaType} from './media-pool'; import {PaginationButtons} from './pagination-buttons'; import {Services} from '#service'; -import {ShareMenu} from './amp-story-share-menu'; +import {ShareMenu} from '../../amp-story-share-menu/0.1/amp-story-share-menu'; import {SwipeXYRecognizer} from '../../../src/gesture-recognizers'; import {SystemLayer} from './amp-story-system-layer'; import {renderUnsupportedBrowserLayer} from './amp-story-unsupported-browser-layer'; diff --git a/extensions/amp-story/1.0/test/test-amp-story-share-menu.js b/extensions/amp-story/1.0/test/test-amp-story-share-menu.js index 79064b5b2f2e..b39ffc881d22 100644 --- a/extensions/amp-story/1.0/test/test-amp-story-share-menu.js +++ b/extensions/amp-story/1.0/test/test-amp-story-share-menu.js @@ -5,9 +5,11 @@ import { } from '../amp-story-store-service'; import {Keys_Enum} from '#core/constants/key-codes'; import {Services} from '#service'; -import {ShareMenu, VISIBLE_CLASS} from '../amp-story-share-menu'; +import { + ShareMenu, + VISIBLE_CLASS, +} from '../../../amp-story-share-menu/0.1/amp-story-share-menu'; import {ShareWidget} from '../amp-story-share'; -import {getStyle} from '#core/dom/style'; import {registerServiceBuilder} from '../../../../src/service-helpers'; describes.realWin('amp-story-share-menu', {amp: true}, (env) => { @@ -17,6 +19,7 @@ describes.realWin('amp-story-share-menu', {amp: true}, (env) => { let shareWidgetMock; let storeService; let win; + let installExtensionForDoc; beforeEach(() => { win = env.win; @@ -30,7 +33,6 @@ describes.realWin('amp-story-share-menu', {amp: true}, (env) => { const shareWidget = { build: () => win.document.createElement('div'), isSystemShareSupported: () => isSystemShareSupported, - loadRequiredExtensions: () => {}, }; shareWidgetMock = env.sandbox.mock(shareWidget); env.sandbox.stub(ShareWidget, 'create').returns(shareWidget); @@ -44,6 +46,12 @@ describes.realWin('amp-story-share-menu', {amp: true}, (env) => { }, }); + installExtensionForDoc = env.sandbox.spy(); + + env.sandbox.stub(Services, 'extensionsFor').returns({ + installExtensionForDoc, + }); + parentEl = win.document.createElement('div'); win.document.body.appendChild(parentEl); shareMenu = new ShareMenu(win, parentEl); @@ -126,54 +134,64 @@ describes.realWin('amp-story-share-menu', {amp: true}, (env) => { expect(clickCallbackSpy).to.have.been.calledOnce; }); - it('should render the amp-social-share button if system share', () => { + it('should not load the amp-social-share extension if system share', () => { isSystemShareSupported = true; shareMenu.build(); - expect(shareMenu.element_.tagName).to.equal('AMP-SOCIAL-SHARE'); - }); - - it('should hide the amp-social-share button if system share', () => { - isSystemShareSupported = true; + expect( + installExtensionForDoc.withArgs(env.sandbox.match.any, 'amp-social-share') + ).to.not.have.been.called; - shareMenu.build(); - - expect(getStyle(shareMenu.element_, 'visibility')).to.equal('hidden'); + shareWidgetMock.verify(); }); - it('should load the amp-social-share extension if system share', () => { - isSystemShareSupported = true; - shareWidgetMock.expects('loadRequiredExtensions').once(); + it('should load the amp-social-share extension if fallback share', () => { + isSystemShareSupported = false; shareMenu.build(); + expect( + installExtensionForDoc.withArgs(env.sandbox.match.any, 'amp-social-share') + ).to.have.been.called; + shareWidgetMock.verify(); }); - it('should dispatch an event on system share button if system share', () => { + // See ShareMenu.onShareMenuStateUpdate_ for details. + it('should close back the share menu right away if system share', () => { isSystemShareSupported = true; - shareMenu.build(); + // Simulate native sharing. + win.navigator.share = () => Promise.resolve(); - const clickCallbackSpy = env.sandbox.spy(); - shareMenu.element_.addEventListener('click', clickCallbackSpy); + shareMenu.build(); - // Toggling the share menu dispatches a click event on the amp-social-share - // button, which triggers the native sharing menu. storeService.dispatch(Action.TOGGLE_SHARE_MENU, true); - expect(clickCallbackSpy).to.have.been.calledOnce; + expect(storeService.get(StateProperty.SHARE_MENU_STATE)).to.be.false; }); - // See ShareMenu.onShareMenuStateUpdate_ for details. - it('should close back the share menu right away if system share', () => { + it('should share natively if available with the canonical url and window title', () => { isSystemShareSupported = true; + // Simulate native sharing. + win.navigator.share = () => Promise.resolve(); + const shareSpy = env.sandbox.spy(win.navigator, 'share'); + + // Set canonicalUrl and window title for sharing. + env.sandbox + .stub(Services, 'documentInfoForDoc') + .returns({canonicalUrl: 'https://amp.dev'}); + win.document.title = 'AMP'; + shareMenu.build(); storeService.dispatch(Action.TOGGLE_SHARE_MENU, true); - expect(storeService.get(StateProperty.SHARE_MENU_STATE)).to.be.false; + expect(shareSpy).to.be.calledWith({ + url: 'https://amp.dev', + text: 'AMP', + }); }); });