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

add Flickr support #4

Merged
merged 9 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
48 changes: 48 additions & 0 deletions acceptance/cypress/tests/flickrBlock.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
context('Flickr Block Acceptance Tests', () => {
describe('Flickr Block Tests', () => {
beforeEach(() => {
// given a logged in editor and a page in edit mode
cy.visit('/');
cy.autologin();
cy.createContent({
contentType: 'Document',
contentId: 'document',
contentTitle: 'Test document',
});
cy.createContent({
contentType: 'Document',
contentId: 'my-page',
contentTitle: 'My Page',
path: '/document',
});
cy.visit('/document');
cy.waitForResourceToLoad('@navigation');
cy.waitForResourceToLoad('@breadcrumbs');
cy.waitForResourceToLoad('@actions');
cy.waitForResourceToLoad('@types');
cy.waitForResourceToLoad('document');
cy.navigate('/document/edit');
});

it('As editor I can add a Flickr Block', function () {
cy.intercept('PATCH', '/**/document').as('edit');
cy.intercept('GET', '/**/document').as('content');
cy.intercept('GET', '/**/Document').as('schema');

cy.getSlate().click();
cy.get('.button .block-add-button').click({ force: true });
cy.get('.blocks-chooser .social .button.flickrBlock').click({
force: true,
});

cy.get('.block.inner.flickrBlock .input-wrapper .ui.input input')
.invoke(
'val',
'<a data-flickr-embed="true" data-header="true" data-footer="true" href="https://www.flickr.com/photos/plone-foundation/albums/72177720303069181" title="Plone Conference 2022 Namur"><img src="https://live.staticflickr.com/65535/52443622430_c442b75502.jpg" width="500" height="375" alt="Plone Conference 2022 Namur"/></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>',
)
.type('{enter}');

cy.get('#toolbar-save').click();
});
});
});
35 changes: 35 additions & 0 deletions src/components/Blocks/Flickr/Data.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { BlockDataForm } from '@plone/volto/components';
import { isValidFlickrId } from '../../../helpers';
import { flickrSchema } from './schema';
import { useIntl } from 'react-intl';

const FlickrBlockData = (props) => {
const { data, block, onChangeBlock } = props;
const intl = useIntl();
const schema = flickrSchema({ ...props, intl });

const onChangeField = (id, value) => {
if (id === 'flickrId' && value !== '') {
if (!isValidFlickrId(value)) {
return;
}
}
onChangeBlock(block, {
...data,
[id]: value,
});
};

return (
<BlockDataForm
schema={schema}
title={schema.title}
onChangeField={onChangeField}
formData={data}
block={block}
/>
);
};

export default FlickrBlockData;
71 changes: 71 additions & 0 deletions src/components/Blocks/Flickr/DefaultView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import SocialContentWrapper from '../../SocialContentWrapper/SocialContentWrapper';

const FlickrView = (props) => {
const { flickrId, align } = props;
const linkText = 'View post on Flickr';
const parser = __CLIENT__ ? new DOMParser() : undefined;
const galleryData = parser?.parseFromString(flickrId, 'text/html');
const scriptSrc = '//embedr.flickr.com/assets/client-code.js';

React.useEffect(() => {
const head = document.querySelector('head');
const script = document.createElement('script');

script.setAttribute('src', scriptSrc);
head.appendChild(script);

return () => {
head.removeChild(script);
};
}, [scriptSrc]);

return (
flickrId && (
<SocialContentWrapper
align={align}
tool="flickr"
url={flickrId}
linkText={linkText}
>
<figure className="flickr-content">
<a
data-flickr-embed={true}
data-footer={galleryData?.links[0].dataset.footer}
data-header={galleryData?.links[0].dataset.header}
href={galleryData?.links[0].href ?? ''}
title={galleryData?.links[0].title}
>
<img
src={galleryData?.images[0].src}
width="100%"
alt={galleryData?.images[0].alt ?? ''}
/>
danalvrz marked this conversation as resolved.
Show resolved Hide resolved
</a>
</figure>
</SocialContentWrapper>
)
);
};

/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
FlickrView.propTypes = {
flickrId: PropTypes.string.isRequired,
align: PropTypes.string,
};

/**
* Default properties.
* @property {Object} defaultProps Default properties.
* @static
*/
FlickrView.defaultProps = {
align: 'center',
};

export default FlickrView;
82 changes: 82 additions & 0 deletions src/components/Blocks/Flickr/Edit.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import iconSVG from '../../../icons/flickr.svg';
import { isValidFlickrId } from '../../../helpers';
import { EditForm } from '../../../components';
import { withBlockExtensions } from '@plone/volto/helpers';
import { SidebarPortal } from '@plone/volto/components';

import FlickrBlockData from './Data';
import FlickrBlockView from './View';

const messages = defineMessages({
editFormHeader: {
id: 'Embed a Flickr Gallery',
defaultMessage: 'Embed a Flickr Gallery',
},
editFormPlaceholder: {
id: 'Provide the embed code of the Flickr Gallery',
defaultMessage: 'Provide the embed code of the Flickr Gallery',
},
errorMessage: {
id: 'Please provide a valid Flickr Gallery embed code',
defaultMessage: 'Please provide a valid Flickr Gallery embed code',
},
});

const FlickrBlockEdit = (props) => {
const { data, onChangeBlock, block, selected } = props;
const [flickrId, setFlickrId] = useState(data.flickrId);
const [hasError, setHasError] = useState(false);
const intl = useIntl();

const onKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
updateFlickrId(e.target.value);
}
};

const onChange = (e) => {
e.preventDefault();
e.stopPropagation();
updateFlickrId(e.target.value);
};

const updateFlickrId = (value) => {
if (isValidFlickrId(value)) {
setHasError(false);
setFlickrId(value);
onChangeBlock(block, { ...data, flickrId: value });
} else {
setHasError(true);
}
};

return flickrId ? (
<>
<FlickrBlockView {...props} isEditMode />
<SidebarPortal selected={selected}>
<FlickrBlockData
data={data}
block={block}
onChangeBlock={onChangeBlock}
/>
</SidebarPortal>
</>
) : (
<EditForm
formHeader={intl.formatMessage(messages.editFormHeader)}
formPlaceholder={intl.formatMessage(messages.editFormPlaceholder)}
formErrorMessage={intl.formatMessage(messages.errorMessage)}
formIcon={iconSVG}
onKeyDown={onKeyDown}
onChange={onChange}
value={flickrId}
invalidValue={hasError}
/>
);
};

export default withBlockExtensions(FlickrBlockEdit);
10 changes: 10 additions & 0 deletions src/components/Blocks/Flickr/View.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { withBlockExtensions } from '@plone/volto/helpers';
import FlickrView from './DefaultView';

const FlickrBlockView = (props) => {
const { data } = props;
return <FlickrView {...data} />;
};

export default withBlockExtensions(FlickrBlockView);
49 changes: 49 additions & 0 deletions src/components/Blocks/Flickr/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { defineMessages } from 'react-intl';

const messages = defineMessages({
flickrBlock: {
id: 'Flickr',
defaultMessage: 'Flickr',
},
flickrId: {
id: 'Flickr url',
defaultMessage: 'Flickr url',
},
align: {
id: 'Alignment',
defaultMessage: 'Alignment',
},
styleFieldset: {
id: 'Style',
defaultMessage: 'Style',
},
});

export const flickrSchema = (props) => {
return {
title: props.intl.formatMessage(messages.flickrBlock),
fieldsets: [
{
id: 'default',
title: 'Default',
fields: ['flickrId'],
},
{
id: 'style',
title: props.intl.formatMessage(messages.styleFieldset),
fields: ['align'],
},
],

properties: {
flickrId: {
title: props.intl.formatMessage(messages.flickrId),
},
align: {
title: props.intl.formatMessage(messages.align),
widget: 'align',
},
},
required: ['flickrId'],
};
};
10 changes: 8 additions & 2 deletions src/components/SocialContentWrapper/Style.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
clear: both;

&.left {
.rsme-embed {
.rsme-embed,
.flickr-embed-frame {
margin: 0 1rem 1rem 0 !important;
float: left;
}
}

&.right {
.rsme-embed {
.rsme-embed,
.flickr-embed-frame {
margin: 0 1rem 0 1rem !important;
float: right;
}
Expand All @@ -20,4 +22,8 @@
display: flex;
justify-content: center;
}

.flickr-content {
margin: 0;
}
}
14 changes: 14 additions & 0 deletions src/helpers/Flickr/Flickr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const isValidFlickrId = (value) => {
danalvrz marked this conversation as resolved.
Show resolved Hide resolved
if (!__CLIENT__) {
throw new Error('isValidFlickrId is only implemented on the client');
}
const parser = __CLIENT__ ? new DOMParser() : undefined;
const inputData = parser?.parseFromString(value, 'text/html');
const linkHref = inputData?.links[0]?.href ?? '';
const imgSrc = inputData?.images[0]?.src ?? '';

const linkRegex = /https:\/\/www.flickr.com\/photos\/.*/gim;
const imgRegex = /https:\/\/live.staticflickr.com\/.*/gim;

return linkRegex.test(linkHref) && imgRegex.test(imgSrc);
};
20 changes: 20 additions & 0 deletions src/helpers/Flickr/Flickr.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { isValidFlickrId } from './Flickr';

describe('FLickr', () => {
describe('isValidFlickrId', () => {
it('validates input', () => {
expect(
isValidFlickrId(
'<a data-flickr-embed="true" data-header="true" data-footer="true" href="https://www.flickr.com/photos/plone-foundation/albums/72177720303069181" title="Plone Conference 2022 Namur"><img src="https://live.staticflickr.com/65535/52443622430_c442b75502.jpg" width="500" height="375" alt="Plone Conference 2022 Namur"/></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>',
),
).toBe(true);
});
it('fails with invalid input', () => {
expect(
isValidFlickrId(
'<a data-flickr-embed="true" data-header="true" data-footer="true" href="https://www.flickr.com/plone-foundation/albums/72177720303069181" title="Plone Conference 2022 Namur"><img src="https://staticflickr.com/65535/52443622430_c442b75502.jpg" width="500" height="375" alt="Plone Conference 2022 Namur"/></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>',
),
).toBe(false);
});
});
});
1 change: 1 addition & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
* export { Api } from './Api/Api';
*/
export { isValidFacebookId } from './Facebook/Facebook';
export { isValidFlickrId } from './Flickr/Flickr';
export { isValidInstagramId } from './Instagram/Instagram';
export { extractTweetId } from './Tweet/Tweet';
10 changes: 10 additions & 0 deletions src/icons/flickr.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading