Skip to content
Permalink
Browse files

Get the embed form working

  • Loading branch information
harigopal committed Jan 23, 2020
1 parent 02d6525 commit 354c7e7697a5b0067b3232e195db7754abded789
@@ -0,0 +1,24 @@
module Mutations
class CreateEmbedContentBlock < GraphQL::Schema::Mutation
argument :target_id, ID, required: true
argument :above_content_block_id, ID, required: false
argument :url, String, required: true

description "Creates an embed content block."

field :content_block, Types::ContentBlockType, null: true

def resolve(params)
mutator = CreateEmbedContentBlockMutator.new(context, params)

content_block = if mutator.valid?
mutator.create_embed_content_block
else
mutator.notify_errors
nil
end

{ content_block: content_block }
end
end
end
@@ -3,7 +3,7 @@ class CreateMarkdownContentBlock < GraphQL::Schema::Mutation
argument :target_id, ID, required: true
argument :above_content_block_id, ID, required: false

description "Deletes a target content block."
description "Creates a markdown content block."

field :content_block, Types::ContentBlockType, null: true

@@ -42,6 +42,7 @@ class MutationType < Types::BaseObject
field :update_school, mutation: Mutations::UpdateSchool, null: false
field :archive_coach_note, mutation: Mutations::ArchiveCoachNote, null: false
field :create_markdown_content_block, mutation: Mutations::CreateMarkdownContentBlock, null: false
field :create_embed_content_block, mutation: Mutations::CreateEmbedContentBlock, null: false
field :update_target, mutation: Mutations::UpdateTarget, null: false
end
end
@@ -20,6 +20,18 @@ module CreateMarkdownContentBlock = [%graphql
|}
];

module CreateEmbedContentBlock = [%graphql
{|
mutation($targetId: ID!, $aboveContentBlockId: ID, $url: String!) {
createEmbedContentBlock(targetId: $targetId, aboveContentBlockId: $aboveContentBlockId, url: $url) {
contentBlock {
...ContentBlock.Fragments.AllFields
}
}
}
|}
];

type ui =
| Hidden
| BlockSelector
@@ -36,6 +48,7 @@ type action =
| ToggleSaving
| FinishSaving(bool)
| SetError(string)
| FailedToCreate
| FailToUpload
| ShowEmbedForm
| HideEmbedForm
@@ -59,6 +72,14 @@ let reducer = (state, action) =>
| ToggleSaving => {...state, saving: !state.saving, error: None}
| FinishSaving(isAboveTarget) => computeInitialState(isAboveTarget)
| SetError(error) => {...state, error: Some(error)}
| FailedToCreate => {
...state,
saving: false,
error:
Some(
"An unexpected error occured. Please reload the page and try again.",
),
}
| FailToUpload => {
...state,
saving: false,
@@ -77,6 +98,18 @@ let containerClasses = (visible, isAboveTarget) => {
classes ++ (visible || !isAboveTarget ? " content-block-creator--open" : "");
};

let handleGraphqlCreateResponse =
(aboveContentBlock, send, addContentBlockCB, contentBlock) => {
switch (contentBlock) {
| Some(contentBlock) =>
contentBlock |> ContentBlock.makeFromJs |> addContentBlockCB;
send(FinishSaving(aboveContentBlock != None));
| None => send(ToggleSaving)
};

Js.Promise.resolve();
};

let createMarkdownContentBlock =
(target, aboveContentBlock, send, addContentBlockCB) => {
send(ToggleSaving);
@@ -85,14 +118,16 @@ let createMarkdownContentBlock =
let targetId = target |> Target.id;
CreateMarkdownContentBlock.make(~targetId, ~aboveContentBlockId?, ())
|> GraphqlQuery.sendQuery2
|> Js.Promise.then_(result => {
switch (result##createMarkdownContentBlock##contentBlock) {
| Some(contentBlock) =>
contentBlock |> ContentBlock.makeFromJs |> addContentBlockCB;
send(FinishSaving(aboveContentBlock != None));
| None => send(ToggleSaving)
};

|> Js.Promise.then_(result =>
handleGraphqlCreateResponse(
aboveContentBlock,
send,
addContentBlockCB,
result##createMarkdownContentBlock##contentBlock,
)
)
|> Js.Promise.catch(_ => {
send(FailedToCreate);
Js.Promise.resolve();
})
|> ignore;
@@ -333,6 +368,52 @@ let creatorToggler = (state, send, contentBlock) => {
</div>;
};

let embedUrlRegexes = [|
[%bs.re "/https:\/\/.*slideshare\.net/"],
[%bs.re "/https:\/\/.*vimeo\.com/"],
[%bs.re "/https:\/\/.*youtube\.com/"],
[%bs.re "/https:\/\/.*youtu\.be/"],
|];

let validEmbedUrl = url =>
Belt.Array.some(embedUrlRegexes, regex => regex->Js.Re.test_(url));

let onEmbedFormSave =
(target, aboveContentBlock, url, send, addContentBlockCB, event) => {
event |> ReactEvent.Mouse.preventDefault;

if (url |> validEmbedUrl) {
send(ToggleSaving);

let aboveContentBlockId =
aboveContentBlock |> OptionUtils.map(ContentBlock.id);

let targetId = target |> Target.id;

CreateEmbedContentBlock.make(~targetId, ~aboveContentBlockId?, ~url, ())
|> GraphqlQuery.sendQuery2
|> Js.Promise.then_(result =>
handleGraphqlCreateResponse(
aboveContentBlock,
send,
addContentBlockCB,
result##createEmbedContentBlock##contentBlock,
)
)
|> Js.Promise.catch(_ => {
send(FailedToCreate);
Js.Promise.resolve();
})
|> ignore;
} else {
send(
SetError(
"The URL doesn't look valid. Please make sure that it starts with 'https://' and that it's one of the accepted websites.",
),
);
};
};

[@react.component]
let make = (~target, ~aboveContentBlock=?, ~addContentBlockCB) => {
let isAboveContentBlock = aboveContentBlock != None;
@@ -370,17 +451,31 @@ let make = (~target, ~aboveContentBlock=?, ~addContentBlockCB) => {
<label className="text-xs font-semibold">
{"URL to Embed" |> str}
</label>
<HelpIcon
className="ml-2 text-xs"
link="https://docs.pupilfirst.com/#/curriculum_editor?id=content-block-types">
{"We support YouTube, Vimeo, and Slideshare URLs. Just copy & paste the full URL to the page that contains the resource that you'd like to embed."
|> str}
</HelpIcon>
<div className="flex mt-1">
<input
placeholder="https://www.youtube.com/watch?v="
className="w-full mr-2 py-1 px-2 border rounded"
className="w-full py-1 px-2 border rounded"
type_="text"
value=url
onChange={updateEmbedUrl(send)}
/>
<div>
<button className="btn btn-success"> {"Save" |> str} </button>
</div>
<button
className="ml-2 btn btn-success"
onClick={onEmbedFormSave(
target,
aboveContentBlock,
url,
send,
addContentBlockCB,
)}>
{"Save" |> str}
</button>
</div>
</div>
}}
@@ -0,0 +1,21 @@
module ContentBlockCreatable
include ActiveSupport::Concern

def authorized?
target.present? && (current_school_admin.present? || current_user.course_authors.where(course: target.level.course).exists?)
end

def target
@target ||= Target.find_by(id: target_id)
end

def latest_version_date
@latest_version_date ||= target.latest_content_version_date
end

def above_content_block
@above_content_block ||= begin
target.content_blocks.find_by(id: above_content_block_id) if above_content_block_id.present?
end
end
end
@@ -0,0 +1,37 @@
class CreateEmbedContentBlockMutator < ApplicationQuery
include ContentBlockCreatable

property :target_id, validates: { presence: true }
property :url, validates: { presence: true, length: { maximum: 2048 } }
property :above_content_block_id

validate :embed_code_must_be_available

def embed_code_must_be_available
return if embed_code.present?

errors[:base] << "Failed to embed the given URL. Please check if this is a supported website and try again."
end

def create_embed_content_block
ContentBlock.transaction do
embed_block = create_embed_block
Targets::CreateContentVersionService.new(target, above_content_block).create(embed_block)
end
end

private

def embed_code
@embed_code ||= ::Oembed::Resolver.new(url).embed_code
rescue ::Oembed::Resolver::ProviderNotSupported
nil
end

def create_embed_block
ContentBlock.create!(
block_type: ContentBlock::BLOCK_TYPE_EMBED,
content: { url: url, embed_code: embed_code }
)
end
end
@@ -1,4 +1,6 @@
class CreateMarkdownContentBlockMutator < ApplicationQuery
include ContentBlockCreatable

property :target_id, validates: { presence: true }
property :above_content_block_id

@@ -17,22 +19,4 @@ def create_markdown_block
content: { markdown: "" }
)
end

def authorized?
target.present? && (current_school_admin.present? || current_user.course_authors.where(course: target.level.course).exists?)
end

def target
@target ||= Target.find_by(id: target_id)
end

def latest_version_date
@latest_version_date ||= target.latest_content_version_date
end

def above_content_block
@above_content_block ||= begin
target.content_blocks.find_by(id: above_content_block_id) if above_content_block_id.present?
end
end
end
@@ -4481,6 +4481,61 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createEmbedContentBlock",
"description": "",
"args": [
{
"name": "targetId",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "aboveContentBlockId",
"description": "",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "url",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CreateEmbedContentBlockPayload",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createEvaluationCriterion",
"description": "",
@@ -6765,6 +6820,29 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CreateEmbedContentBlockPayload",
"description": "",
"fields": [
{
"name": "contentBlock",
"description": "",
"args": [],
"type": {
"kind": "OBJECT",
"name": "ContentBlock",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "GradeAndLabelInput",

0 comments on commit 354c7e7

Please sign in to comment.
You can’t perform that action at this time.