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

✨[amp-social-share][bento][preact] New version of preact social-share component #28137

Merged
merged 25 commits into from May 14, 2020

Conversation

krdwan
Copy link
Contributor

@krdwan krdwan commented Apr 30, 2020

Partial for #28283

Moved logic to configure the share endpoint with search params into preact component. This does not include changes needed to amp component.

Open questions

  1. Number.isInteger() causing an Es-lint issue. What is the preferred way of doing this?
  2. Proper Usage of UseResourcesNotify() confirm?
  3. Proper Usage of window.navigator.share (will this cause a problem in server side render?). From y research window appears available to react components.

4/30 --
Latest commit updates the Number.isInteger to use Math.floor()
Adds in a storybook for this component

5/11 --
Renamed from 0.2 to 1.0 for directory name

const size = {
width: props['width'] || DEFAULT_WIDTH,
height: props['height'] || DEFAULT_HEIGHT,
width: checkedWidth,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's floor these instead of checking if they're integers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 140 to 142
combinedBindings[name.toUpperCase()] = bindings[name]
? bindings[name]
: '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
combinedBindings[name.toUpperCase()] = bindings[name]
? bindings[name]
: '';
combinedBindings[name.toUpperCase()] = bindings[name] ?? '';

const bindingVars = typeConfig['bindings'];
if (bindingVars) {
bindingVars.forEach((name) => {
combinedBindings[name.toUpperCase()] = params[name] ? params[name] : '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
combinedBindings[name.toUpperCase()] = params[name] ? params[name] : '';
combinedBindings[name.toUpperCase()] = params[name] ?? '';

Comment on lines 127 to 128
const combinedParams = dict();
Object.assign(combinedParams, typeConfig['defaultParams'], params);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the linter complain using this form?

Suggested change
const combinedParams = dict();
Object.assign(combinedParams, typeConfig['defaultParams'], params);
const combinedParams = dict({...typeConfig['defaultParams'], ...params});

}

// Throw warning if type is obsolete
const obsoleteType = typeConfig['obsolete'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the conditions that lead to an obsolete type?

Since this is a new version of the component, let's get rid of types we don't support if possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only one obsolete type, it's 'gplus'.

Yes, we can just remove support for this type and remove this functionality. It's included here: https://github.com/ampproject/amphtml/blob/master/extensions/amp-social-share/0.2/amp-social-share-config.js

So wasn't sure if there might be a future need for this type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's remove :)

@@ -30,63 +32,196 @@ const DEFAULT_HEIGHT = 44;
* @param {!JsonObject} props
* @return {PreactDef.Renderable}
*/
export function SocialShare(props) {
export function SocialShare2(props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"2"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the name, because I was concerned that I would break the amp component. The old version of the preact component has different behavior, and the amp component relies on the old version. Should I remove the 2?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Let's break.

event.preventDefault();
handleActivation();
function checkProps_(props, name) {
const {type, shareEndpoint, params, bindings, width, height} = props;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, very unfortunately, these would have to be defined with quotes. E.g.

const {'type': type, ...} = props;

if (key == Keys.SPACE || key == Keys.ENTER) {
event.preventDefault();
handleActivation();
function checkProps_(props, name) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a static function? If so, let's make it a module-level function. Also, let's drop _ for this and others.


// Verify type is valid and cannot contain spaces
if (type === undefined) {
throwError_(`The type attribute is required. ${name}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just do throw new Error() here? If we actually throw errors, it'd be better to be explicit here, since the compiler understands that the function exits with the throw statement.

But, I'm not yet sold we should be throwing errors here. I think they are somewhat unidiomatic in React development. But let's do it for now, until we figure out a better way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I wrapped the actual throwing of an error inside it's own function in case we want to change it throughout the component, since I am not sure what is the idiomatic way to throw an error in React and specifically for bento.

Sure I can make it explicit here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just need to be consistent here. If it's a method call that may or may not throw in different cases, we have to propagate null/undefined values, and then skip rendering by returning null. If we throw errors and don't want to deal with this yet, we should make throw explicit to compiler and the only way to do it is to actually have throw ... at the call site. I recommend we start by throwing actual errors explicitly - it's less effort for us for now and we'd likely have to pass through the errors again at some point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, let's throw the error explicitly.

throwError_(`The type attribute is required. ${name}`);
}
if (/\s/.test(type)) {
throwError_(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error seems way to detailed and too obscure. Is there a specific reason why we should worry about spaces here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a carry over from the previous version of social-share. I wanted to keep the behavior the same. I can remove if desired.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed!

extensions/amp-social-share/0.2/social-share.js Outdated Show resolved Hide resolved
function handleActivation_() {
if (finalEndpoint.split(':', 1)[0] === 'navigator-share') {
if (window && window.navigator && window.navigator.share) {
const dataStr = finalEndpoint.substr(finalEndpoint.indexOf('?'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a util for this in the url.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there is getFragment() which gets params after '#' symbol but nothing for '?' unless I'm missing somehting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's parseUrl().queryString.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'm not strictly against doing URL splitting here directly. But then we need to handle edge-cases, such as what if there's also a # in this URL, what if there's no ?, etc.

Copy link
Contributor Author

@krdwan krdwan May 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the calling this out. I was only able to find parseUrlDeprecated which I am hesitant to use because of the Deprecation. I have attempted to write my own helper function (getQueryString, line 192), which handles the corner cases that I can think of.

  1. No ? -> returns '' (empty string)
  2. ? and # -> only returns the substring from ? to #
  3. # then ? (reversed order) -> returns substring from ? to the end of string
  4. ? and no #-> returns substring from ? to the end of string

const backgroundStyle = CSS[type];
//Default to gray (d3d3d3) background if type is not preconfigured
const backgroundStyle = CSS[type.toUpperCase()] || {
'backgroundColor': 'd3d3d3',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have # in front?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll consolidate into CSS file to make it easier to understand. The current formatting does not require #.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not CSS values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is a CSS value, but this is the format that the component consumes it in, (does not require the #.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I don't like it. But I guess let's run with what they accept and we'll see if that causes problems later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

style={{...size, ...props['style']}}
{...props}
>
<SocialShareIcon
style={{...backgroundStyle, ...baseStyle, ...size}}
type={type}
type={type.toUpperCase()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get the type.toUpperCase() value once somewhere up top and use it elsewhere.

import {withA11y} from '@storybook/addon-a11y';

export default {
title: 'Social Share 2',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.2

@krdwan
Copy link
Contributor Author

krdwan commented May 1, 2020

Thanks for review @alanorozco @dvoytenko . Next revision is up!

Copy link
Contributor

@caroqliu caroqliu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking forward to see how this impacts AMP mode!

@@ -54,3 +54,6 @@ export const SMS = {'backgroundColor': 'ca2b63'};

/* "system" styling */
export const SYSTEM = {'backgroundColor': '000000'};

/* "default" styling */
export const DEFAULT = {'backgroundColor': 'd3d3d3'};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default color is currently white, which is invisible in most cases against a white background (including in storybook). So I felt it made sense to explicitly set some visible color.

const endpointWithParams = addParamsToUrl(baseEndpoint, combinedParams);

const combinedBindings = dict();
const bindingVars = typeConfig['bindings'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are examples of the bindings right? https://github.com/ampproject/amphtml/blob/master/spec/amp-var-substitutions.md Are they useful outside of an AMP document?

}

// Verify width and height are valid integers
let checkedWidth = Math.floor(width || DEFAULT_WIDTH);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting case: Math.floor(null) yields 0 but Math.floor(undefined) yields NaN -- if the prop isn't present it's undefined, meaning it would be reasonable to rewrite this: Math.floor(width) || DEFAULT_WIDTH

Do we ever expect width to be null? That would be a reasonable case for the current order.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: Just saw the positive integer check below, in which case even if it is null we would prefer to use the DEFAULT_WIDTH.

tldr supernit: Math.floor(width) || DEFAULT_WIDTH

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh thanks for the input. Right, I agree there are many tricky corner cases for this. I've included your input in the new version which now follows the below behavior!

// Verify width and height are valid integers
// Silently assigns default for undefined, null
// Throws Warning for booleans, strings, 0, negative numbers
// No errors when positive integer or equivalent string

// Verify width and height are valid integers
let checkedWidth = Math.floor(width || DEFAULT_WIDTH);
let checkedHeight = Math.floor(height || DEFAULT_HEIGHT);
if (!(checkedWidth > 0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: checkedWidth <= 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've re-written this seciton, although kept this part with > 0 because when Math.floor returns NaN, it always return false in a comparison so the recommended update would return a different behavior.

);
checkedWidth = DEFAULT_WIDTH;
}
if (!(checkedHeight > 0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkedHeight <= 0

@krdwan
Copy link
Contributor Author

krdwan commented May 11, 2020

Thanks again for the review @caroqliu @dvoytenko @alanorozco

I've made all the changes besides the ones for params / bindings. My proposal for next steps is the following

  1. Merge the current version of preact component
  2. Work on amp component updates
  3. Understand with regard to both preact and amp what makes the most sense for us to do
  4. Create a separate PR that will include amp component and changes to preact if needed (which may include a change to the config)

Let me know what you guys think!


Also I moved everything into a directory called 1.0, but this makes it hard to diff. Let me know if you want me to move it back to 0.2 and then make the 1.0 change in a separate PR.


Here's my latest thoughts on the params / bindings discussion. #28137 (comment)

@krdwan krdwan marked this pull request as ready for review May 12, 2020 14:51
@dvoytenko
Copy link
Contributor

@krdwan merging this soon and iterating is a good idea. However, please make sure the remaining tasks are listed in #28283.

Copy link
Contributor

@dvoytenko dvoytenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few pre-LGTM comments.

/**
* @param {!JsonObject} props
* @param {?string} name
* @return {!JsonObject}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return a struct type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

useResourcesNotify();

const {
'typeConfig': typeConfig,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkProps should return a struct type and no quotes should be used here. E.g.

const {baseEndpoint, ...} = checkProps(props);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I will make the change. Per your previous comment: #28137 (comment)

Is the correct convention to use quotes when unpacking the props only? And then for unpacking any other object do not use quotes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. For now this is the convention. Any internal structure should have a type declaration that could be a struct. Then we don't need quotes. Unfortunately we can't control a value that's passed to us from outside, such as component props. Another way to think about it is obfuscation. I.e. do we want an obfuscation for any structure? We can't obfuscate component props or component output. For instance, one of such props could be disabled. If we obfuscate it to d and output it to JSX - a DOM element expects disabled. But the structures you use internally are ok to obfuscate. E.g. typeConfig will become tc or such. But it's not a visible value to anyone but our internal code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Thanks for clarifying how the obfuscater interacts with properties passed from outside the component. I have removed the quotes here, and included quotes inside of checkProps where the props are unpacked.

* @return {PreactDef.Renderable}
*/
export function SocialShare(props) {
const name = 'SocialShare';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make a top-level const.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

* @param {!JsonObject} props
* @return {?string}
*/
function createEndpoint(typeConfig, baseEndpoint, props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to a top-level function. It's a noticeable cost to have these inlined w/o good reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, sounds good. I'll move all static functions outside of Component going forward.

function createEndpoint(typeConfig, baseEndpoint, props) {
const {params, bindings} = props;
const combinedParams = dict();
Object.assign(combinedParams, typeConfig['defaultParams'], params);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use const combinedParams = ({...typeConfig['defaultParams'], params}).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

/**
* @private
*/
function handleActivation() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: move to top level and just pass finalEndpoint as an arg.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

extensions/amp-social-share/1.0/social-share.js Outdated Show resolved Hide resolved
// Throws Warning for booleans, strings, 0, negative numbers
// No errors when positive integer or equivalent string
let checkedWidth =
width === null || width === undefined ? DEFAULT_WIDTH : width;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x === null || x === undefined is x == null. But also we should be able to use ?? now. E.g. this expression can be done as width ?? DEFAULT_WIDTH.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to ?? and removed the if statement underneath.

width === null || width === undefined ? DEFAULT_WIDTH : width;
let checkedHeight =
height === null || height === undefined ? DEFAULT_HEIGHT : height;
if (typeof checkedWidth === 'boolean' || !(Math.floor(checkedWidth) > 0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this if, tbh. Why would we expect the width to be a boolean vs, say, a function or an object? What would math.floor() do if the width was indeed a string? IMHO, this whole warning we can do without.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the reason I have this check for boolean is because Math.floor(true) actually returns 1, which would then not trigger the warning. Math.floor on a string converts the string to a number if it is a valid number or returns NaN which would trigger the warning.

So in combination with the above, null and undefined would silently be reassigned to be DEFAULT. Strings, objects, booleans, functions would trigger the warning. Valid positive strings and numbers will pass through unchanged and not trigger the warning.


The reasoning for including this was to be thorough so that in the case where the user accidentally passes an invalid width (or height), the component would resize to default and the user may not understand why.

Sounds like you think this is too much detail to check for. I am fine with removing the whole thing if you think so.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, unless the logic differs by type, we shouldn't check types. Eventually we'll have to look into PropTypes or d.ts to define the prop types. Additionally, fractional values are fine as well, so we don't need to do Math.floor().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I have updated this. There is no type checking now. It will only do the null coalescing operation (??). We can update the type checking in the future when we get PropTypes

const type = select('type', knobConfigurations, knobConfigurations[0]);
const href = text('shareEndpoint', 'Not Specified');
const params = object('params', {'subject': 'test'});
const bindings = object('bindings', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, ok to defer to a follow up PR, but this prop bugs me the most. Putting on a user's hat on, I really don't understand why there are two params and bindings, and what's the difference between them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I have created a comment in the parent issue to track outstanding items.

#28283 (comment)

@krdwan
Copy link
Contributor Author

krdwan commented May 13, 2020

@krdwan merging this soon and iterating is a good idea. However, please make sure the remaining tasks are listed in #28283.

Thanks for the very thorough review @dvoytenko . I have noted that we will re-visit the way params and bindings are structured in this component as we design out the higher level amp component.

#28283 (comment)

if (q < 0) {
return '';
} else if (h < q) {
return endpoint.substr(q);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't account for foo.html#this?is-a-hash-string. Query must come before a hash. We could normalize if (h === -1) { h = endpoint.length } to solve this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Justin, I've updated this function with your recommendation. Are there any other use cases you can think of? Or a util that I'm not aware of that already does this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I mentioned it elsewhere, but url.js's parseUrl().queryString should be able to do this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point on parseUrlDeprecated deprecation. @jridgewell do you know what we deprecated it with? Is there something else we should be using now?

}
if (bindings) {
Object.keys(bindings).forEach((name) => {
combinedBindings[name.toUpperCase()] = bindings[name] || '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we intended for any binding to be used here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on your question? In this section, the bindings object coming from props is being added into the combinedBindings. Then in the next part we replace variables in finalEndpoint with bindings from combinedBindings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can pass in bindings: { foobar: 'x' }, where foobar is some random binding name, and not one of the typeConfig['bindings']. I think this is part of the confusion with having params and binding, we're allowing generic find-and-replace to happen where we meant to just allow you to replace a few strings in our static config JSON with something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your concern. The API from the original component behaves as you described.

In the previous version of the component, the binding find and replace happens asynchronously (in urlReplacements.expandUrlAsync()) and was limited to only those bindings included in our config.

Now the logic is split into two parts, the (synchronous) find and replace logic lives inside the preact component and we will would have to do an asynchronous call within the amp component (since this needs to be within the amp environment) to get our binding values. Then pass the bindings into our preact component.

As a side effect to this, someone using the preact component in isolation would be able to add additional generic bindings like you described (bindings: {foobar: 'x' }. I don't see this as a problem though since the user actually gets more flexibility with how they want to use the component and could potentially add other variables specific to the preact environment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the model we should try to get to is that the component only has a notion of props/params. And the whole async binding->params mapping is done wholly on the AMP side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, let's work toward this model in the AMP component design!

@@ -194,7 +194,7 @@ function getQueryString(endpoint) {
let h = endpoint.indexOf('#');
q = q === -1 ? endpoint.length : q;
h = h === -1 ? endpoint.length : h;
return h < q ? '' : endpoint.substr(q, h - q);
return endpoint.substr(q, h - q);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We can use slice since it uses endIndex param instead of length:

Suggested change
return endpoint.substr(q, h - q);
return endpoint.slice(q, h);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is much cleaner, updated!

}
if (bindings) {
Object.keys(bindings).forEach((name) => {
combinedBindings[name.toUpperCase()] = bindings[name] || '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can pass in bindings: { foobar: 'x' }, where foobar is some random binding name, and not one of the typeConfig['bindings']. I think this is part of the confusion with having params and binding, we're allowing generic find-and-replace to happen where we meant to just allow you to replace a few strings in our static config JSON with something else.

@krdwan krdwan changed the title ✨ First draft, new version of preact social-share component ✨ New version of preact social-share component May 14, 2020
Copy link
Contributor

@dvoytenko dvoytenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with two comments. The next step here should definitely be unwinding the confusion between bindings and params. It's certainly ok to do a detour to AMP part first, but we'll likely bump into the same issue there right away.

/**
* @param {!JsonObject} props
* @param {?string} name
* @return {struct}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will look like this:

@return {{
  typeConfig: !JsonObject,
  baseEndpoint: string,
  checkedWidth: number,
  checkedHeight: number,
}}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I have updated the struct definition!

* {number} checkedWidth
* {number} checkedHeight
*/
function checkProps(props, name) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove name from args and just use NAME everywhere instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, updated!

@alanorozco alanorozco merged commit 62de49f into ampproject:master May 14, 2020
@krdwan krdwan changed the title ✨ New version of preact social-share component ✨[bento][amp-social-share] New version of preact social-share component May 14, 2020
@krdwan krdwan changed the title ✨[bento][amp-social-share] New version of preact social-share component ✨[bento][amp-social-share][preact] New version of preact social-share component May 14, 2020
@krdwan krdwan changed the title ✨[bento][amp-social-share][preact] New version of preact social-share component ✨[amp-social-share][bento][preact] New version of preact social-share component May 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants