adding embedly integration

This commit is contained in:
Sacha Greif 2016-04-04 16:50:07 +09:00
parent 8a2c9dbeed
commit a367ed426d
8 changed files with 220 additions and 86 deletions

View file

@ -0,0 +1,85 @@
import React, { PropTypes, Component } from 'react';
import Formsy from 'formsy-react';
import FRC from 'formsy-react-components';
const Input = FRC.Input;
class EmbedlyThumbnail extends Component {
constructor(props) {
super(props)
this.state = {
thumbnailUrl: props.value,
loading: false
};
}
// will trigger every time the context (i.e. form values) changes
shouldComponentUpdate(nextProps, nextState, nextContext) {
const nextUrl = nextContext.currentValues.url;
const currentUrl = this.context.currentValues.url;
if (nextUrl != currentUrl) {
this.setState({loading: true});
// the URL has changed, get a new thumbnail
Meteor.call("getEmbedlyData", nextUrl, (error, result) => {
this.setState({loading: false});
if (error) {
console.log(error)
this.context.throwError({content: error.message, type: "error"});
} else {
this.setState({
thumbnailUrl: result.thumbnailUrl
});
}
});
}
return true;
}
renderThumbnail() {
const currentUrl = this.context.currentValues && this.context.currentValues.url;
return currentUrl ? <img
className="embedly-thumbnail"
src={this.state.thumbnailUrl}
height={Telescope.settings.get('thumbnailHeight', 125)}
width={Telescope.settings.get('thumbnailWidth', 200)}
/> : null;
}
render() {
const {name, value, label} = this.props;
Loading = Telescope.components.Loading;
return (
<div className="form-group row">
<label className="control-label col-sm-3">{label}</label>
<div className="col-sm-9">
{this.state.loading ? <Loading /> : this.renderThumbnail()}
<Input name={name} type="hidden" readOnly value={this.state.thumbnailUrl} />
</div>
</div>
)
}
}
EmbedlyThumbnail.propTypes = {
name: React.PropTypes.string,
value: React.PropTypes.any,
label: React.PropTypes.string
}
EmbedlyThumbnail.contextTypes = {
currentValues: React.PropTypes.object,
throwError: React.PropTypes.func
}
export default EmbedlyThumbnail;

View file

@ -1,4 +1,5 @@
import PublicationUtils from 'meteor/utilities:smart-publications';
import EmbedlyThumbnail from './components/EmbedlyThumbnail.jsx';
Posts.addField([
{
@ -9,10 +10,7 @@ Posts.addField([
insertableIf: Users.is.memberOrAdmin,
editableIf: Users.is.ownerOrAdmin,
publish: true,
autoform: {
type: 'bootstrap-postthumbnail',
order: 40
}
control: EmbedlyThumbnail
}
},
{
@ -44,3 +42,40 @@ Posts.addField([
PublicationUtils.addToFields(Posts.publishedFields.list, ["thumbnailUrl", "media", "sourceName", "sourceUrl"]);
PublicationUtils.addToFields(Posts.publishedFields.single, ["thumbnailUrl", "media", "sourceName", "sourceUrl"]);
if (typeof Telescope.settings.collection !== "undefined") {
Telescope.settings.collection.addField([
{
fieldName: 'embedlyKey',
fieldSchema: {
type: String,
optional: true,
private: true,
autoform: {
group: 'embedly',
class: 'private-field'
}
}
},
{
fieldName: 'thumbnailWidth',
fieldSchema: {
type: Number,
optional: true,
autoform: {
group: 'embedly'
}
}
},
{
fieldName: 'thumbnailHeight',
fieldSchema: {
type: Number,
optional: true,
autoform: {
group: 'embedly'
}
}
}
]);
}

View file

@ -16,39 +16,3 @@ function checkIfPreviouslyPosted (data) {
return data;
}
Telescope.callbacks.add("afterEmbedlyPrefill", checkIfPreviouslyPosted);
// Settings.addField([
// {
// fieldName: 'embedlyKey',
// fieldSchema: {
// type: String,
// optional: true,
// private: true,
// autoform: {
// group: 'embedly',
// class: 'private-field'
// }
// }
// },
// {
// fieldName: 'thumbnailWidth',
// fieldSchema: {
// type: Number,
// optional: true,
// autoform: {
// group: 'embedly'
// }
// }
// },
// {
// fieldName: 'thumbnailHeight',
// fieldSchema: {
// type: Number,
// optional: true,
// autoform: {
// group: 'embedly'
// }
// }
// }
// ]);

View file

@ -1,9 +1,9 @@
getEmbedlyData = function (url) {
var data = {};
var extractBase = 'http://api.embed.ly/1/extract';
var embedlyKey = Settings.get('embedlyKey');
var thumbnailWidth = Settings.get('thumbnailWidth', 200);
var thumbnailHeight = Settings.get('thumbnailHeight', 125);
var embedlyKey = Telescope.settings.get('embedlyKey');
var thumbnailWidth = Telescope.settings.get('thumbnailWidth', 200);
var thumbnailHeight = Telescope.settings.get('thumbnailHeight', 125);
if(!embedlyKey) {
// fail silently to still let the post be submitted as usual
@ -115,7 +115,7 @@ Meteor.methods({
return getEmbedlyData(url);
},
embedlyKeyExists: function () {
return !!Settings.get('embedlyKey');
return !!Telescope.settings.get('embedlyKey');
},
regenerateThumbnail: function (post) {
check(post, Posts.simpleSchema());

View file

@ -17,12 +17,12 @@ Package.onUse( function(api) {
api.addFiles([
// 'package-tap.i18n',
// 'lib/embedly.js',
'lib/embedly.js',
'lib/custom_fields.js'
], ['client', 'server']);
api.addFiles([
// 'lib/server/get_embedly_data.js'
'lib/server/get_embedly_data.js'
], ['server']);
api.addFiles([

View file

@ -25,8 +25,13 @@ class FormComponent extends Component {
const value = document && Utils.deepValue(document, fieldName) ? Utils.deepValue(document, fieldName) : "";
const label = typeof labelFunction === "function" ? labelFunction(fieldName) : fieldName;
switch (field.control) {
if (typeof field.control === "function") {
return <field.control key={fieldName} name={fieldName} value={value} label={label}/>
} else {
switch (field.control) {
case "text":
return <Input key={fieldName} name={fieldName} value={value} label={label} type="text" />;
case "textarea":
@ -43,6 +48,8 @@ class FormComponent extends Component {
default:
return <Input key={fieldName} name={fieldName} value={value} label={label} type="text" />;
}
}
}
render() {

View file

@ -7,26 +7,74 @@ import Utils from './utils.js';
class NovaForm extends Component{
constructor() {
super();
constructor(props) {
super(props);
this.submitForm = this.submitForm.bind(this);
this.methodCallback = this.methodCallback.bind(this);
this.updateState = this.updateState.bind(this);
this.throwError = this.throwError.bind(this);
this.clearErrors = this.clearErrors.bind(this);
this.state = {
disabled: false,
errors: []
errors: [],
currentValues: this.props.document
};
}
getFormType() { // if a document is being passed, this is an edit form
// if a document is being passed, this is an edit form
getFormType() {
return this.props.document ? "edit" : "new";
}
getFields() { // get relevant fields
// get relevant fields
getFields() {
const collection = this.props.collection;
const fields = this.getFormType() === "edit" ? collection.getEditableFields(this.props.currentUser) : collection.getInsertableFields(this.props.currentUser);
return fields;
}
// add error to state
throwError(error) {
this.setState({
errors: [error]
});
}
// clear all errors
clearErrors() {
this.setState({
errors: []
});
}
// render errors
renderErrors() {
Flash = Telescope.components.Flash;
return <div className="form-errors">{this.state.errors.map(message => <Flash key={message} message={message}/>)}</div>
}
// whenever the form values change, keep track of them in the state
updateState(e) {
// e can sometimes be event, sometims be currentValue
// see https://github.com/christianalfoni/formsy-react/issues/203
if (e.stopPropagation) {
e.stopPropagation();
} else {
this.setState({
currentValues: e
});
}
}
// pass on form values as context to all child components for easy access
getChildContext() {
return {
throwError: this.throwError,
currentValues: this.state.currentValues
};
}
// common callback for both new and edit forms
methodCallback(error, document) {
this.setState({disabled: false});
@ -36,11 +84,9 @@ class NovaForm extends Component{
console.log(error)
// add error to state
this.setState({
errors: [{
this.throwError({
content: error.message,
type: "error"
}]
});
// run error callback if it exists
@ -48,9 +94,7 @@ class NovaForm extends Component{
} else { // success
this.setState({
errors: []
});
this.clearErrors();
// reset form if this is a new document form
if (this.getFormType() === "new") this.refs.form.reset();
@ -64,8 +108,8 @@ class NovaForm extends Component{
}
}
// submit form handler
submitForm(data) {
this.setState({disabled: true});
const fields = this.getFields();
@ -101,7 +145,6 @@ class NovaForm extends Component{
// build modifier
const modifier = {$set: set};
if (!_.isEmpty(unset)) modifier.$unset = unset;
// call method with _id of document being edited and modifier
Meteor.call(this.props.methodName, document._id, modifier, this.methodCallback);
@ -109,11 +152,6 @@ class NovaForm extends Component{
}
renderErrors() {
Flash = Telescope.components.Flash;
return <div className="form-errors">{this.state.errors.map(message => <Flash key={message} message={message}/>)}</div>
}
render() {
const document = this.props.document;
@ -127,7 +165,7 @@ class NovaForm extends Component{
return (
<div className={"document-"+this.getFormType()} style={style}>
<Formsy.Form onSubmit={this.submitForm} disabled={this.state.disabled} ref="form">
<Formsy.Form onSubmit={this.submitForm} onChange={this.updateState} disabled={this.state.disabled} ref="form">
{this.renderErrors()}
{fields.map(fieldName => <FormComponent
key={fieldName}
@ -161,5 +199,10 @@ NovaForm.contextTypes = {
closeCallback: React.PropTypes.func
}
NovaForm.childContextTypes = {
currentValues: React.PropTypes.object,
throwError: React.PropTypes.func
}
module.exports = NovaForm;
export default NovaForm;

View file

@ -25,8 +25,8 @@ SimpleSchema.extendOptions({
profile: Match.Optional(Boolean), // profile: true means the field is shown on user profiles
template: Match.Optional(String), // template used to display the field
autoform: Match.Optional(Object), // autoform placeholder
control: Match.Optional(String), // autoform placeholder
// editableBy: Match.Optional(String)
control: Match.Optional(Match.Any), // NovaForm control (String or React component)
position: Match.Optional(Number) // position in the form
});
// ------------------------------------- Components -------------------------------- //