feat: youtube timestamp embed (#2810)

feat: youtube timestamp
pull/2815/head
Weihua 2021-09-13 22:43:10 +08:00 committed by GitHub
parent e978a8f972
commit 15cafea8c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 11 deletions

View File

@ -7,6 +7,7 @@
[frontend.handler.draw :as draw]
[frontend.handler.notification :as notification]
[frontend.handler.plugin :as plugin-handler]
[frontend.extensions.video.youtube :as youtube]
[frontend.search :as search]
[frontend.state :as state]
[frontend.util :as util]
@ -269,6 +270,8 @@
["Embed Youtube Video" [[:editor/input "{{youtube }}" {:last-pattern slash
:backward-pos 2}]]]
["Embed Youtube Timestamp" [[:youtube/insert-timestamp]]]
["Embed Vimeo Video" [[:editor/input "{{vimeo }}" {:last-pattern slash
:backward-pos 2}]]]
@ -563,6 +566,11 @@
(defmethod handle-step :editor/show-zotero [[_]]
(state/set-editor-show-zotero! true))
(defmethod handle-step :youtube/insert-timestamp [[_]]
(let [input-id (state/get-edit-input-id)
macro (youtube/gen-youtube-ts-macro)]
(insert! input-id macro {})))
(defmethod handle-step :editor/show-date-picker [[_ type]]
(if (and
(contains? #{:scheduled :deadline} type)

View File

@ -26,6 +26,7 @@
[frontend.extensions.sci :as sci]
[frontend.extensions.pdf.assets :as pdf-assets]
[frontend.extensions.zotero :as zotero]
[frontend.extensions.video.youtube :as youtube]
[frontend.format.block :as block]
[frontend.format.mldoc :as mldoc]
[frontend.components.plugins :as plugins]
@ -1084,17 +1085,11 @@
:else
(nth (util/safe-re-find YouTube-regex url) 5))]
(when-not (string/blank? youtube-id)
(let [width (min (- (util/get-width) 96)
560)
height (int (* width (/ 315 560)))]
[:iframe
{:allow-full-screen "allowfullscreen"
:allow
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
:frame-border "0"
:src (str "https://www.youtube.com/embed/" youtube-id)
:height height
:width width}])))))
(youtube/youtube-video youtube-id)))))
(= name "youtube-timestamp")
(when-let [seconds (first arguments)]
(youtube/timestamp seconds))
(= name "tutorial-video")
(tutorial-video)

View File

@ -518,6 +518,15 @@
[:path {:d "M2 0.5H6.78272L13.5 7.69708V18C13.5 18.8284 12.8284 19.5 12 19.5H2C1.17157 19.5 0.5 18.8284 0.5 18V2C0.5 1.17157 1.17157 0.5 2 0.5Z", :fill "var(--ls-active-primary-color)"}]
[:path {:d "M7 5.5V0L14 7.5H9C7.89543 7.5 7 6.60457 7 5.5Z", :fill "var(--ls-active-secondary-color)"}]])
(def clock
[:svg.h-5.w-5
{:fill "currentColor", :viewBox "0 0 20 20"}
[:path
{:clip-rule "evenodd",
:d
"M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z",
:fill-rule "evenodd"}]])
(def online
(hero-icon "M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"))

View File

@ -0,0 +1,98 @@
(ns frontend.extensions.video.youtube
(:require [rum.core :as rum]
[cljs.core.async :refer [<! >! chan go go-loop] :as a]
[frontend.components.svg :as svg]
[frontend.state :as state]
[frontend.util :as util]
[goog.object :as gobj]
[clojure.string :as str]))
(defn- load-yt-script []
(js/console.log "load yt script")
(let [tag (js/document.createElement "script")
first-script-tag (first (js/document.getElementsByTagName "script"))
parent-node (.-parentNode first-script-tag)]
(set! (.-src tag) "https://www.youtube.com/iframe_api")
(.insertBefore parent-node tag first-script-tag)))
(defn load-youtube-api []
(let [c (chan)]
(if js/window.YT
(a/close! c)
(do
(set! js/window.onYouTubeIframeAPIReady #(a/close! c))
(load-yt-script)))
c))
(defn register-player [state]
(let [id (first (:rum/args state))
player (js/window.YT.Player.
(rum/dom-node state)
(clj->js
{:events
{"onReady" (fn [e] (js/console.log id " ready"))}}))]
(state/update-state! [:youtube/players]
(fn [players]
(assoc players id player)))))
(rum/defcs youtube-video <
rum/reactive
(rum/local nil ::player)
{:did-mount
(fn [state]
(go
(<! (load-youtube-api))
(register-player state))
state)}
[state id]
(let [width (min (- (util/get-width) 96)
560)
height (int (* width (/ 315 560)))]
[:iframe
{:id (str "youtube-player-" id)
:allow-full-screen "allowfullscreen"
:allow "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
:frame-border "0"
:src (str "https://www.youtube.com/embed/" id "?enablejsapi=1")
:height height
:width width}]))
(defn seconds->display [seconds]
(let [seconds (int seconds)
minutes (Math/floor (/ seconds 60))
remaining-seconds (- seconds (* 60 minutes))
remaining-seconds (if (zero? remaining-seconds) "00" remaining-seconds)]
(str minutes ":" remaining-seconds)))
(defn dom-after-video-node? [video-node target]
(not (zero?
(bit-and
(.compareDocumentPosition video-node target)
js/Node.DOCUMENT_POSITION_FOLLOWING))))
(defn get-player [target]
(when-let [iframe (->> (js/document.getElementsByTagName "iframe")
(filter
(fn [node]
(let [src (gobj/get node "src" "")]
(str/includes? src "youtube.com"))))
(filter #(dom-after-video-node? % target))
last)]
(let [id (gobj/get iframe "id" "")
id (str/replace-first id #"youtube-player-" "")]
(get (get @state/state :youtube/players) id))))
(rum/defc timestamp
[seconds]
[:a.svg-small.youtube-timestamp
{:on-click (fn [e]
(util/stop e)
(when-let [player (get-player (.-target e))]
(.seekTo ^js player seconds true)))}
svg/clock
(seconds->display seconds)])
(defn gen-youtube-ts-macro []
(when-let [player (get-player (state/get-input))]
(util/format "{{youtube-timestamp %s}}" (Math/floor (.getCurrentTime ^js player)))))

View File

@ -160,6 +160,8 @@
#{})
:date-picker/date nil
:youtube/players {}
;; command palette
:command-palette/commands []