forked from TheOdinProject/theodinproject
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Convert lesson preview to Hotwire (TheOdinProject#4104)
Because: * We can simplify the preview feature code with Hotwire * New and improved share preview UX https://github.com/TheOdinProject/theodinproject/assets/7963776/5de1cdee-3301-4bc1-9b35-783b098aadfc This commit: * Moves input and html preview tabs to rails views. * Adds reusable tabs and autosubmit and clipboard controllers. * Adds a share modal that is handled by a new preview share controller to keep persisting separate from displaying. * Amends our modal to display center of the screen on mobile. * Increases the share preview rate limit to 5 per minute - it may be clicked more now that its easier to find above the preview.
- Loading branch information
1 parent
f4677f0
commit 20e2b2e
Showing
20 changed files
with
246 additions
and
40 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module Lessons | ||
module Previews | ||
class ShareController < ApplicationController | ||
def create | ||
@preview = LessonPreview.new(content: params[:content]) | ||
|
||
respond_to do |format| | ||
if @preview.save | ||
format.turbo_stream | ||
else | ||
flash.now[:alert] = 'Unable to share preview' | ||
|
||
format.turbo_stream { render turbo_stream: turbo_stream.update('flash-messages', partial: 'shared/flash') } | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Controller } from '@hotwired/stimulus'; | ||
import debounce from 'debounce'; | ||
|
||
export default class Autosubmit extends Controller { | ||
initialize() { | ||
this.debouncedSubmit = debounce(this.debouncedSubmit.bind(this), 300); | ||
} | ||
|
||
submit() { | ||
this.element.requestSubmit(); | ||
} | ||
|
||
debouncedSubmit() { | ||
this.submit(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Controller } from '@hotwired/stimulus'; | ||
|
||
export default class ClipboardController extends Controller { | ||
static targets = ['source', 'message']; | ||
|
||
copy() { | ||
navigator.clipboard.writeText(this.sourceTarget.value); | ||
|
||
if (this.hasMessageTarget) { | ||
this.messageTarget.classList.remove('hidden'); | ||
setTimeout(() => { | ||
this.messageTarget.classList.add('hidden'); | ||
}, 2000); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Controller } from '@hotwired/stimulus'; | ||
import { post } from '@rails/request.js'; | ||
|
||
export default class SharePreviewController extends Controller { | ||
static targets = ['input', 'button']; | ||
|
||
static values = { url: String }; | ||
|
||
connect() { | ||
this.inputTarget.addEventListener('input', this.toggleButton.bind(this)); | ||
} | ||
|
||
async share(e) { | ||
e.preventDefault(); | ||
|
||
const content = this.inputTarget.value; | ||
await post(this.urlValue, { body: JSON.stringify({ content }) }); | ||
} | ||
|
||
toggleButton() { | ||
this.buttonTarget.classList.toggle('hidden', this.inputTarget.value.length === 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Controller } from '@hotwired/stimulus'; | ||
|
||
export default class TabsController extends Controller { | ||
static targets = ['tab', 'panel']; | ||
|
||
static classes = ['active', 'inactive']; | ||
|
||
initialize() { | ||
this.showTab(); | ||
} | ||
|
||
change(event) { | ||
this.index = this.tabTargets.indexOf(event.target); | ||
this.showTab(this.index); | ||
} | ||
|
||
showTab() { | ||
this.panelTargets.forEach((el, i) => { | ||
if (i === this.index) { | ||
el.classList.remove(this.inactiveClass); | ||
} else { | ||
el.classList.add(this.inactiveClass); | ||
} | ||
}); | ||
|
||
this.tabTargets.forEach((el, i) => { | ||
if (i === this.index) { | ||
el.classList.add(...this.activeClasses); | ||
} else { | ||
el.classList.remove(...this.activeClasses); | ||
} | ||
}); | ||
} | ||
|
||
get index() { | ||
return parseInt(this.data.get('index'), 10); | ||
} | ||
|
||
set index(value) { | ||
this.data.set('index', value); | ||
this.showTab(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<div id="preview-container"> | ||
<%= render ContentContainerComponent.new(classes: 'pt-6') do %> | ||
<% if markdown.present? %> | ||
<%= MarkdownConverter.new(markdown).as_html.html_safe %> | ||
<% else %> | ||
<p>Nothing to preview yet!</p> | ||
<% end %> | ||
<% end %> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<%= turbo_stream.replace 'preview-container' do %> | ||
<%= render 'lessons/previews/markdown_preview', markdown: @preview.content %> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<%= turbo_stream.replace 'modal' do %> | ||
<%= render ModalComponent.new(title: 'Share preview') do %> | ||
<div class="w-full h-14"> | ||
<div data-controller="clipboard"> | ||
<div class="relative flex items-center"> | ||
<%= text_field_tag 'preview_url', lessons_preview_url(uuid: @preview), readonly: true, data: { clipboard_target: 'source' }, class: 'py-1.5 pr-10 block w-full border rounded-md py-2 px-3 focus:outline-none dark:bg-gray-700/50 dark:border-gray-500 dark:text-gray-300 dark:placeholder-gray-400 dark:focus:ring-2 dark:focus:border-transparent' %> | ||
|
||
<div class="absolute inset-y-0 right-0 flex py-1 pr-1"> | ||
<button class="inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-700 hover:bg-gray-50 focus:ring-gray-400 dark:text-gray-300 dark:hover:bg-gray-700 dark:border-gray-600 dark:focus:ring-gray-600 dark:bg-transparent" data-action="click->clipboard#copy"> | ||
<%= inline_svg_tag 'icons/clipboard.svg', class: 'h-5 w-5', aria: true, title: 'clipboard', desc: 'clipboard icon' %> | ||
</button> | ||
</div> | ||
</div> | ||
<span data-clipboard-target="message" class="hidden text-green-700 dark:text-green-600 text-sm pt-1 float-right">Copied!</span> | ||
</div> | ||
</div> | ||
<% end %> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,41 @@ | ||
<%= title('Lesson Preview Tool') %> | ||
<%= title('Markdown preview Tool') %> | ||
|
||
<div class="page-container"> | ||
<div class="max-w-4xl mx-auto"> | ||
<h1 class="page-heading-title">Lesson Preview Tool</h1> | ||
<p class="text-center mb-6">Paste markdown contents here to check how they'll look on the website!</p> | ||
<%= react_component('lesson-preview/index', { sharedContent: @preview.content.to_s }) %> | ||
<div class="bg-white dark:bg-gray-900"> | ||
<div class="page-container"> | ||
<div class="max-w-4xl mx-auto"> | ||
<h1 class="page-heading-title text-left pb-4">Markdown preview tool</h1> | ||
<p class="mb-6 text-gray-500">Paste markdown here to check how it will look on the website!</p> | ||
|
||
<div> | ||
<div data-controller='tabs share-preview' data-share-preview-url-value="<%= lessons_preview_share_path(format: :turbo_stream) %>" data-tabs-index='0' data-tabs-active-class="text-gray-700 bg-gray-300/50 hover:bg-gray-300 dark:bg-gray-700/90" data-tabs-inactive-class="hidden"> | ||
<div class="flex justify-between"> | ||
<div class="flex items-center mb-3"> | ||
<button data-action="tabs#change" class="text-gray-600 bg-gray-300/50 hover:text-gray-800 hover:bg-gray-200 dark:bg-gray-700/40 dark:text-gray-300 rounded-md border border-transparent px-3 py-1.5 font-medium cursor-pointer focus:outline-none" data-tabs-target='tab'> | ||
Write | ||
</button> | ||
<button data-action="tabs#change" class="ml-2 text-gray-600 hover:text-gray-800 hover:bg-gray-200 dark:bg-gray-700/40 dark:text-gray-300 rounded-md border border-transparent px-3 py-1.5 font-medium cursor-pointer focus:outline-none" data-tabs-target='tab'> | ||
Preview | ||
</button> | ||
</div> | ||
|
||
<%= button_to '#', class: 'hidden button button--secondary py-2 px-3', method: :get, data: { share_preview_target: 'button', action: 'share-preview#share' } do %> | ||
<%= inline_svg_tag 'icons/share.svg', class: 'h-3 w-3 mr-2', aria: true, title: 'share', desc: 'share icon' %> | ||
Share | ||
<% end %> | ||
</div> | ||
|
||
<div data-tabs-target="panel" class="mx-auto"> | ||
<%= form_with url: lessons_preview_path, data: { controller: 'autosubmit', action: 'input->autosubmit#debouncedSubmit' } do |form| %> | ||
<%= form.text_area :markdown, value: @preview.content, autofocus: true, maxlength: 70_000, placeholder: 'Enter markdown here...', data: { share_preview_target: 'input' }, class: 'w-full min-h-screen block rounded-md border-gray-300 dark:bg-gray-700/60 dark:focus:ring-2 dark:placeholder-gray-400 dark:border-gray-500 dark:focus:ring-gray-500 shadow-sm focus:border-blue-500 focus:ring-blue-500' %> | ||
<% end %> | ||
</div> | ||
|
||
<div data-tabs-target="panel" class="min-h-screen"> | ||
<%= render 'lessons/previews/markdown_preview', markdown: @preview.content %> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
</div> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
require 'rails_helper' | ||
|
||
RSpec.describe 'Share preview' do | ||
describe 'POST #create' do | ||
context 'when the preview is valid' do | ||
it 'creates a sharable preview' do | ||
expect do | ||
post lessons_preview_share_path(content: '# hello', format: :turbo_stream) | ||
end.to change { LessonPreview.count }.by(1) | ||
end | ||
end | ||
|
||
context 'when the preview is invalid' do | ||
it 'does not create a sharable preview' do | ||
expect do | ||
post lessons_preview_share_path(content: '', format: :turbo_stream) | ||
end.not_to change { LessonPreview.count } | ||
end | ||
|
||
it 'informs the user a sharable preview cannot be created' do | ||
post lessons_preview_share_path(content: '', format: :turbo_stream) | ||
expect(response.body).to include('Unable to share preview') | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,17 @@ | ||
require 'rails_helper' | ||
|
||
RSpec.describe 'Preview Pages' do | ||
RSpec.describe 'Lesson preview' do | ||
describe 'GET #show' do | ||
it 'renders a view' do | ||
it 'renders the preview input page' do | ||
get lessons_preview_path | ||
expect(response).to have_http_status(:success) | ||
end | ||
end | ||
|
||
describe 'POST #create' do | ||
it 'saves a preview_link' do | ||
expect do | ||
post lessons_preview_path(content: '# hello') | ||
end.to change { LessonPreview.count }.by(1) | ||
it 'converts the markdown to html' do | ||
post lessons_preview_path(markdown: 'hello world', format: :turbo_stream) | ||
expect(response.body).to include('<p>hello world</p>') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.