Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions .tags.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- tag: lesson-0
commit: "Getting Started"
- tag: lesson-1
commit: "Lesson 1"
- tag: lesson-2
commit: "Lesson 2"
- tag: lesson-3
commit: "Lesson 3"
- tag: lesson-4
commit: "Lesson 4"
24 changes: 24 additions & 0 deletions app/javascript/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,28 @@ const application = Application.start()
application.debug = false
window.Stimulus = application

application.registerActionOption("reload", ({ event, value }) => {
if (event.type == "submit") {
const { submitter, target: form } = event
const turboFrame = getTurboFrame(submitter) || getTurboFrame(form)
const action = submitter?.getAttribute("formaction") || form.action
const url = new URL(action, document.baseURI)
const reload = turboFrame ?
url.href == new URL(turboFrame.src, turboFrame.baseURI).href :
url.href == new URL(location.href).href

return reload == value
} else {
return true
}
})

function getTurboFrame(element) {
if (element) {
return document.getElementById(element.getAttribute("data-turbo-frame"))
} else {
return null
}
}

export { application }
7 changes: 7 additions & 0 deletions app/javascript/controllers/application_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
preventDefault(event) {
event.preventDefault()
}
}
11 changes: 11 additions & 0 deletions app/javascript/controllers/element_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ApplicationController from "controllers/application_controller"

export default class extends ApplicationController {
replaceWithChildren() {
this.element.replaceWith(...this.element.children)
}

requestSubmit() {
this.element.requestSubmit()
}
}
14 changes: 14 additions & 0 deletions app/javascript/controllers/hotkey_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import ApplicationController from "controllers/application_controller"
import { install, uninstall } from "@github/hotkey"

export default class extends ApplicationController {
static targets = ["shortcut"]

shortcutTargetConnected(target) {
install(target, target.getAttribute("aria-keyshortcuts"))
}

shortcutTargetDisconnected(target) {
uninstall(target)
}
}
39 changes: 39 additions & 0 deletions app/javascript/controllers/play_button_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import ApplicationController from "controllers/application_controller"

export default class extends ApplicationController {
static outlets = ["player"]

playerOutletConnected(controller, element) {
element.addEventListener("play", this.#press)
element.addEventListener("pause", this.#unpress)
element.addEventListener("ended", this.#unpress)

element.setAttribute("aria-controls", element.id)

if (element.paused) this.#unpress()
else this.#press()
}

playerOutletDisconnected(controller, element) {
element.removeEventListener("play", this.#press)
element.removeEventListener("pause", this.#unpress)
element.removeEventListener("ended", this.#unpress)

element.removeAttribute("aria-controls")
this.#unpress()
}

toggle() {
for (const playerOutlet of this.playerOutlets) {
playerOutlet.toggle()
}
}

#press = () => {
this.element.setAttribute("aria-pressed", true)
}

#unpress = () => {
this.element.setAttribute("aria-pressed", false)
}
}
12 changes: 12 additions & 0 deletions app/javascript/controllers/player_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ApplicationController from "controllers/application_controller"

export default class extends ApplicationController {
play() {
this.element.play()
}

toggle() {
if (this.element.paused) this.play()
else this.element.pause()
}
}
9 changes: 8 additions & 1 deletion app/views/episodes/_episode.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@
<div class="mt-4 flex items-center gap-4">
<%= tag.form(
action: podcast_episode_path(episode.podcast, episode),
data: {
action: "submit->application#preventDefault:reload",
turbo_frame: "audio"
}
) do %>
<button class="group flex items-center text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900">
<button class="group flex items-center text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900"
data-controller="play-button"
data-play-button-player-outlet="#<%= dom_id(episode, :audio) %>"
data-action="click->play-button#toggle">
<% with_options class: "h-2.5 w-2.5 fill-current" do |styled| %>
<div class="block group-aria-pressed:hidden">
<span class="sr-only">Play episode <%= episode.title %></span>
Expand Down
4 changes: 2 additions & 2 deletions app/views/episodes/_player.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div id="player" class="fixed inset-x-0 bottom-0 z-10 lg:left-112 xl:left-120">
<%= tag.div yield.presence, id: "audio", class: "flex items-center gap-6 bg-white/90 py-4 px-4 shadow shadow-slate-200/80 ring-1 ring-slate-900/5 backdrop-blur-sm empty:hidden md:px-6" %>
<div id="player" class="fixed inset-x-0 bottom-0 z-10 lg:left-112 xl:left-120" data-turbo-permanent>
<%= tag.turbo_frame yield.presence, id: "audio", class: "flex items-center gap-6 bg-white/90 py-4 px-4 shadow shadow-slate-200/80 ring-1 ring-slate-900/5 backdrop-blur-sm empty:hidden md:px-6", target: "_top" %>
</div>
13 changes: 7 additions & 6 deletions app/views/episodes/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
</div>

<div class="divide-y divide-slate-100 sm:mt-4 lg:mt-8 lg:border-t lg:border-slate-100">
<div>
<turbo-frame id="page_<%= @page.page %>" target="_top">
<% if @page.prev %>
<div class="py-10 sm:py-12">
<div class="py-10 sm:py-12 hidden first:block">
<div class="lg:px-8">
<div class="lg:max-w-4xl">
<div class="flex justify-center mx-auto px-4 sm:px-6 md:max-w-2xl md:px-4 lg:px-0">
Expand All @@ -29,8 +29,9 @@
<%= render @episodes %>

<% if @page.next %>
<div>
<div class="py-10 sm:py-12">
<%= tag.turbo_frame id: "page_#{@page.next}", src: pagy_url_for(@page, @page.next), loading: "lazy",
data: {turbo_action: "replace", controller: "element", action: "turbo:frame-load->element#replaceWithChildren"} do %>
<div class="py-10 sm:py-12 hidden last:block">
<div class="lg:px-8">
<div class="lg:max-w-4xl">
<div class="flex justify-center mx-auto px-4 sm:px-6 md:max-w-2xl md:px-4 lg:px-0">
Expand All @@ -43,9 +44,9 @@
</div>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
</turbo-frame>
</div>
</div>
<% end %>
8 changes: 7 additions & 1 deletion app/views/episodes/podcast/_frame.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@

<section class="mt-10 lg:mt-12">
<div class="border border-gray-300 bg-white rounded-md shadow-sm text-slate-500 focus-within:ring">
<%= link_to podcast_search_results_path(podcast), class: "outline-none" do %>
<%= link_to podcast_search_results_path(podcast), class: "outline-none",
aria: {keyshortcuts: "Meta+k"},
data: {hotkey_target: "shortcut"} do %>
<div class="flex items-center gap-5 px-3 text-start">
<%= inline_svg_tag "icons/search.svg", class: "h-2.5 w-2.5" %>

<div class="flex-1 py-1">
<span class="font-mono text-sm leading-7">Search</span>
</div>

<kbd class="inline-flex items-center gap-1 font-mono text-sm leading-7">
<abbr title="Command" class="no-underline text-slate-300">⌘</abbr> K
</kbd>
</div>
<% end %>
</div>
Expand Down
12 changes: 9 additions & 3 deletions app/views/episodes/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
<div class="mx-auto px-4 sm:px-6 md:max-w-2xl md:px-4 lg:px-0">
<header class="flex flex-col">
<div class="flex items-center gap-6">
<form class="relative flex-shrink-0">
<button class="group flex items-center justify-center rounded-full bg-slate-700 h-18 w-18 hover:bg-slate-900 focus:outline-none focus:ring-slate-700 focus:ring focus:ring-offset-4">
<form class="relative flex-shrink-0"
data-action="submit->application#preventDefault:reload"
data-turbo-frame="audio">
<button class="group flex items-center justify-center rounded-full bg-slate-700 h-18 w-18 hover:bg-slate-900 focus:outline-none focus:ring-slate-700 focus:ring focus:ring-offset-4"
data-controller="play-button"
data-play-button-player-outlet="#<%= dom_id(@episode, :audio) %>"
data-action="click->play-button#toggle">
<div class="absolute -inset-3 md:hidden"></div>

<% with_options class: "h-7 w-7 fill-white group-active:fill-white/80" do |styled| %>
Expand Down Expand Up @@ -49,7 +54,8 @@
<div class="mb-[env(safe-area-inset-bottom)] flex flex-1 flex-col gap-3 overflow-hidden p-1">
<%= link_to @episode.title, podcast_episode_path(@episode.podcast, @episode), class: "truncate text-center text-sm font-bold leading-6 md:text-left" %>

<%= audio_tag @episode.audio, id: dom_id(@episode, :audio), class: "w-full", controls: true %>
<%= audio_tag @episode.audio, id: dom_id(@episode, :audio), class: "w-full", controls: true, muted: Rails.env.test?,
data: {controller: "player", action: "loadeddata->player#play"} %>
</div>
<% end %>
<% end %>
11 changes: 8 additions & 3 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html data-controller="application">
<head>
<title>Botcasts</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
Expand All @@ -8,10 +8,15 @@
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%# javascript_importmap_tags %>
<%= javascript_importmap_tags %>
<%= javascript_tag type: "module", nonce: true do %>
import debounced from "debounced"

debounced.initialize(<%= raw Rails.configuration.debounced.to_json %>)
<% end %>
</head>

<body>
<body data-controller="hotkey">
<%= yield %>
</body>
</html>
9 changes: 7 additions & 2 deletions app/views/search_results/_search_result.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@
</p>

<div class="mt-4 flex items-center gap-4">
<form action="<%= url_for(search_result) %>">
<button class="group flex items-center text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900">
<form action="<%= url_for(search_result) %>"
data-action="submit->application#preventDefault:reload"
data-turbo-frame="audio">
<button class="group flex items-center text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900"
data-controller="play-button"
data-play-button-player-outlet="#<%= dom_id(search_result.episode, :audio) %>"
data-action="click->play-button#toggle">
<% with_options class: "h-2.5 w-2.5 fill-current" do |styled| %>
<div class="block group-aria-pressed:hidden">
<span class="sr-only">Play episode <%= search_result.title %></span>
Expand Down
21 changes: 14 additions & 7 deletions app/views/search_results/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,21 @@
<div class="lg:px-8">
<div class="lg:max-w-4xl">
<div class="mx-auto px-4 sm:px-6 md:max-w-2xl md:px-4 lg:px-0">
<%= form_with model: @search, scope: "", url: false, method: :get, class: "flex items-center gap-4" do |form| %>
<%= form_with model: @search, scope: "", url: false, method: :get, class: "flex items-center gap-4",
data: {
turbo_action: "replace",
controller: "element",
action: "debounced:input->element#requestSubmit",
} do |form| %>
<div class="relative mt-1 rounded-md shadow-sm">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<%= inline_svg_tag "icons/search.svg", class: "h-2.5 w-2.5" %>
</div>
<%= form.label :query, class: "sr-only" %>
<%= form.text_field :query, class: "w-full rounded-md border-gray-300 pl-10 text-sm placeholder:font-mono placeholder:text-sm placeholder:leading-7 placeholder:text-slate-500",
placeholder: "Search", autofocus: true,
aria: {describedby: dom_id(@search, :prompt)} %>
aria: {describedby: dom_id(@search, :prompt)},
data: {turbo_permanent: true} %>
</div>

<button class="text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900">
Expand All @@ -51,9 +57,9 @@
</div>

<div id="search_results_list" class="divide-y divide-slate-100 sm:mt-4 lg:mt-8 lg:border-t lg:border-slate-100">
<div>
<turbo-frame id="page_<%= @page.page %>" target="_top">
<% if @page.prev %>
<div class="py-10 sm:py-12">
<div class="py-10 sm:py-12 hidden first:block">
<div class="lg:px-8">
<div class="lg:max-w-4xl">
<div class="flex justify-center mx-auto px-4 sm:px-6 md:max-w-2xl md:px-4 lg:px-0">
Expand All @@ -73,7 +79,8 @@
<% end %>

<% if @page.next %>
<div>
<%= tag.turbo_frame id: "page_#{@page.next}", src: pagy_url_for(@page, @page.next), loading: "lazy",
data: {turbo_action: "replace", controller: "element", action: "turbo:frame-load->element#replaceWithChildren"} do %>
<div class="py-10 sm:py-12">
<div class="lg:px-8">
<div class="lg:max-w-4xl">
Expand All @@ -87,9 +94,9 @@
</div>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
</turbo-frame>
</div>
</div>
<% end %>
1 change: 1 addition & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ class Application < Rails::Application
# config.eager_load_paths << Rails.root.join("extras")

config.active_job.queue_adapter = :good_job
config.debounced = config_for(:debounced)
end
end
7 changes: 7 additions & 0 deletions config/debounced.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
shared:
input:
wait: 100

test:
input:
wait: 0
2 changes: 2 additions & 0 deletions config/importmap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
pin_all_from "app/javascript/controllers", under: "controllers"
pin "trix"
pin "@rails/actiontext", to: "actiontext.js"
pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/[email protected]/dist/index.js"
pin "debounced", to: "https://ga.jspm.io/npm:[email protected]/src/index.js"
9 changes: 9 additions & 0 deletions lessons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ To get started, make sure you've run `./bin/setup`. Then run `./ta/start-lesson
1` to start the first lesson. Refer to the table of contents below to help
guide you through each lesson.

To list all lessons, run `./ta/list-lessons`. You can jump between lessons at
anytime.

# Table of Contents

* [Lesson 1: Our first lines of JavaScript](./lesson-1.md)
* [Lesson 2: The Audio Player](./lesson-2.md)
* [Lesson 3: Infinite Scroll](./lesson-3.md)
* [Lesson 4: Typeahead Search](./lesson-4.md)
Binary file added lessons/assets/lesson-1/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lessons/assets/lesson-2/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lessons/assets/lesson-2/example-1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lessons/assets/lesson-2/example-2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lessons/assets/lesson-3/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lessons/assets/lesson-4/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lessons/lesson-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Getting Started
Loading
Loading