Add youtube embeds
This commit is contained in:
parent
2176e55bf6
commit
02c7a48770
23
TODO.txt
23
TODO.txt
|
@ -1,19 +1,22 @@
|
||||||
- need to display the current time you are at (or Latest)
|
- get embeds working
|
||||||
- Need to add server-side rendering
|
|
||||||
- need to implement search
|
- need to implement search
|
||||||
- need a control to manually put in the datetime instead of using the slider
|
- change urls / history when time-travelling
|
||||||
|
- remove duplicate threads from view (duplicate because the OP has multiple pictures)
|
||||||
|
- server-side rendering
|
||||||
|
- control to manually put in the datetime instead of using the slider
|
||||||
for fine-grained control
|
for fine-grained control
|
||||||
- need to change urls / history when time-travelling
|
- have some process respond to http calls from the board to inform the db of new posts
|
||||||
- need to be able to view board, board should support timetravel
|
- board view, boards should support timetravel
|
||||||
- need toolbar to navigate and select which boards to view
|
- need toolbar to navigate and select which boards to view
|
||||||
- need to scrape other websites and add them in
|
- choose boards to display
|
||||||
|
- time slider should align times to the hour
|
||||||
|
- scrape other websites and add them in
|
||||||
- i think this is more useful than the archival property, which is really
|
- i think this is more useful than the archival property, which is really
|
||||||
just there to preserve links. the time travel thing is a bit of a gimmick
|
just there to preserve links. the time travel thing is a bit of a gimmick
|
||||||
- need to have some process respond to http calls from the board to inform
|
|
||||||
the db of new posts
|
|
||||||
- "infinite" scrolling
|
- "infinite" scrolling
|
||||||
- just load more!
|
- just load more!
|
||||||
- need to get embeds working
|
- more granular actions to display what is causing slowdowns (esp on mobile)
|
||||||
|
- fix thumbnails for older posts ✓
|
||||||
- need to support flags
|
- need to support flags
|
||||||
- need to support mod actions like saging a thread or deleting a post
|
- need to support mod actions like saging a thread or deleting a post
|
||||||
- fix thumbnails for older posts
|
- need to display the current time you are at (or Latest) ✓
|
||||||
|
|
|
@ -79,9 +79,10 @@ executable chandlr
|
||||||
Component.BodyRender
|
Component.BodyRender
|
||||||
Routes
|
Routes
|
||||||
Common.AttachmentType
|
Common.AttachmentType
|
||||||
BodyParser
|
Parsing.BodyParser
|
||||||
QuoteLinkParser
|
Parsing.QuoteLinkParser
|
||||||
PostPartType
|
Parsing.EmbedParser
|
||||||
|
Parsing.PostPartType
|
||||||
Component.TimeControl
|
Component.TimeControl
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="video-container" data-video="6lQcKiFy_DM">
|
||||||
|
<a href="https://youtu.be/6lQcKiFy_DM" target="_blank" class="file">
|
||||||
|
<img style="width:255px;height:190px;" src="/vi/6lQcKiFy_DM/0.jpg" class="post-image">
|
||||||
|
</a>
|
||||||
|
<span>[Embed]</span>
|
||||||
|
</div>
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="postgrest-root" content="http://10.4.0.96:3000">
|
<meta name="postgrest-root" content="http://10.4.0.96:3000">
|
||||||
<meta name="postgrest-fetch-count" content="200">
|
<meta name="postgrest-fetch-count" content="500">
|
||||||
<meta name="media-root" content="http://10.4.0.1:8888">
|
<meta name="media-root" content="http://10.4.0.1:8888">
|
||||||
<title>Chandlr</title>
|
<title>Chandlr</title>
|
||||||
<link href="static/style.css" rel="stylesheet" />
|
<link href="static/style.css" rel="stylesheet" />
|
||||||
|
@ -22,6 +22,11 @@
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page_heading * {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -25,8 +25,8 @@ import Text.Parsec (ParseError)
|
||||||
import GHCJS.DOM.Types (JSString)
|
import GHCJS.DOM.Types (JSString)
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
|
|
||||||
import BodyParser (PostPart (..))
|
import Parsing.BodyParser (PostPart (..))
|
||||||
import QuoteLinkParser
|
import Parsing.QuoteLinkParser
|
||||||
import qualified Component.Thread.Model as Model
|
import qualified Component.Thread.Model as Model
|
||||||
import qualified Network.SiteType as Site
|
import qualified Network.SiteType as Site
|
||||||
import qualified Network.BoardType as Board
|
import qualified Network.BoardType as Board
|
||||||
|
|
|
@ -25,6 +25,7 @@ import Miso.String (toMisoString, MisoString)
|
||||||
|
|
||||||
import Network.CatalogPostType (CatalogPost)
|
import Network.CatalogPostType (CatalogPost)
|
||||||
import qualified Network.CatalogPostType as CatalogPost
|
import qualified Network.CatalogPostType as CatalogPost
|
||||||
|
import Parsing.EmbedParser (extractVideoId)
|
||||||
|
|
||||||
data Model = Model
|
data Model = Model
|
||||||
{ display_items :: [ CatalogPost ]
|
{ display_items :: [ CatalogPost ]
|
||||||
|
@ -115,12 +116,19 @@ gridItem iface m post =
|
||||||
post_count_str :: MisoString
|
post_count_str :: MisoString
|
||||||
post_count_str = toMisoString $ (CatalogPost.estimated_post_count post) - 1
|
post_count_str = toMisoString $ (CatalogPost.estimated_post_count post) - 1
|
||||||
|
|
||||||
|
embed_url :: Maybe String
|
||||||
|
embed_url =
|
||||||
|
(CatalogPost.embed post) >>= Just . (\(Right r) -> r) . extractVideoId . T.unpack
|
||||||
|
|
||||||
thumb_url :: MisoString
|
thumb_url :: MisoString
|
||||||
thumb_url =
|
thumb_url =
|
||||||
case mthumb_path of
|
case embed_url of
|
||||||
-- TODO: what about embeds!?
|
Nothing ->
|
||||||
Nothing -> "/static/default_thumbnail.png"
|
case mthumb_path of
|
||||||
Just thumb_path -> (media_root m) `append` (toMisoString thumb_path)
|
-- TODO: what about embeds!?
|
||||||
|
Nothing -> "/static/default_thumbnail.png"
|
||||||
|
Just thumb_path -> (media_root m) `append` (toMisoString thumb_path)
|
||||||
|
Just u -> "https://leftychan.net/vi/" <> toMisoString u <> "/0.jpg"
|
||||||
|
|
||||||
mthumb_path :: Maybe Text
|
mthumb_path :: Maybe Text
|
||||||
mthumb_path = do
|
mthumb_path = do
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Component.Thread.Embed where
|
||||||
|
|
||||||
|
import Miso
|
||||||
|
( View
|
||||||
|
, div_
|
||||||
|
, class_
|
||||||
|
, a_
|
||||||
|
, href_
|
||||||
|
, target_
|
||||||
|
, img_
|
||||||
|
, style_
|
||||||
|
, src_
|
||||||
|
, span_
|
||||||
|
)
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
|
import Data.Text (unpack)
|
||||||
|
import Data.JSString (JSString, pack)
|
||||||
|
|
||||||
|
import qualified Network.PostType as Post
|
||||||
|
import Network.PostType (Post)
|
||||||
|
import Parsing.EmbedParser (extractVideoId)
|
||||||
|
|
||||||
|
embed :: Post -> View a
|
||||||
|
embed post = div_
|
||||||
|
[ class_ "video-container" ]
|
||||||
|
[ a_
|
||||||
|
[ href_ $ "https://youtu.be/" <> video_id
|
||||||
|
, target_ "_blank"
|
||||||
|
, class_ "file"
|
||||||
|
]
|
||||||
|
[ img_
|
||||||
|
[ style_ $ Map.fromList
|
||||||
|
[
|
||||||
|
( "height"
|
||||||
|
, "190px"
|
||||||
|
)
|
||||||
|
,
|
||||||
|
( "width"
|
||||||
|
, "255px"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, src_ ("https://leftychan.net/vi/" <> video_id <> "/0.jpg")
|
||||||
|
, class_ "post-image"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, span_ [][ "[Embed]" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
where
|
||||||
|
video_id :: JSString
|
||||||
|
video_id = pack $ fromJust $
|
||||||
|
(Post.embed post) >>= Just . (\(Right r) -> r) . extractVideoId . unpack
|
|
@ -30,7 +30,7 @@ import Network.BoardType (Board)
|
||||||
import qualified Network.BoardType as Board
|
import qualified Network.BoardType as Board
|
||||||
import qualified Network.ThreadType as Thread
|
import qualified Network.ThreadType as Thread
|
||||||
import Network.ThreadType (Thread)
|
import Network.ThreadType (Thread)
|
||||||
import BodyParser (Backlinks)
|
import Parsing.BodyParser (Backlinks)
|
||||||
|
|
||||||
|
|
||||||
formatUTC :: UTCTime -> JSString
|
formatUTC :: UTCTime -> JSString
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Component.Thread.Model where
|
||||||
import GHCJS.DOM.Types (JSString)
|
import GHCJS.DOM.Types (JSString)
|
||||||
import Network.SiteType (Site)
|
import Network.SiteType (Site)
|
||||||
import Network.PostType (Post)
|
import Network.PostType (Post)
|
||||||
import PostPartType (PostPart)
|
import Parsing.PostPartType (PostPart)
|
||||||
import Data.Time.Clock (UTCTime)
|
import Data.Time.Clock (UTCTime)
|
||||||
|
|
||||||
type PostWithBody = (Post, [ PostPart ])
|
type PostWithBody = (Post, [ PostPart ])
|
||||||
|
|
|
@ -40,8 +40,9 @@ import qualified Network.ThreadType as Thread
|
||||||
import Network.ThreadType (Thread)
|
import Network.ThreadType (Thread)
|
||||||
import Component.Thread.Files (files)
|
import Component.Thread.Files (files)
|
||||||
import Component.Thread.Intro (intro)
|
import Component.Thread.Intro (intro)
|
||||||
|
import Component.Thread.Embed (embed)
|
||||||
import Component.Thread.Model
|
import Component.Thread.Model
|
||||||
import BodyParser
|
import Parsing.BodyParser
|
||||||
import qualified Component.BodyRender as Body
|
import qualified Component.BodyRender as Body
|
||||||
|
|
||||||
initialModel :: JSString -> Site -> Model
|
initialModel :: JSString -> Site -> Model
|
||||||
|
@ -116,7 +117,7 @@ view m =
|
||||||
|
|
||||||
op :: Model -> Post -> Backlinks -> [ View a ]
|
op :: Model -> Post -> Backlinks -> [ View a ]
|
||||||
op m op_post backlinks =
|
op m op_post backlinks =
|
||||||
[ files (media_root m) site_ op_post
|
[ files_or_embed_view
|
||||||
, div_
|
, div_
|
||||||
(
|
(
|
||||||
[ class_ "post op"
|
[ class_ "post op"
|
||||||
|
@ -131,6 +132,13 @@ op m op_post backlinks =
|
||||||
]
|
]
|
||||||
|
|
||||||
where
|
where
|
||||||
|
files_or_embed_view :: View a
|
||||||
|
files_or_embed_view =
|
||||||
|
case (Post.embed op_post) of
|
||||||
|
Just _ -> embed op_post
|
||||||
|
Nothing -> files (media_root m) site_ op_post
|
||||||
|
|
||||||
|
|
||||||
site_ :: Site
|
site_ :: Site
|
||||||
site_ = site m
|
site_ = site m
|
||||||
|
|
||||||
|
@ -165,7 +173,7 @@ reply m backlinks (post, parts) = div_
|
||||||
] ++ multi post
|
] ++ multi post
|
||||||
)
|
)
|
||||||
[ intro site_ board thread post backlinks $ current_time m
|
[ intro site_ board thread post backlinks $ current_time m
|
||||||
, files (media_root m) site_ post
|
, files_or_embed_view
|
||||||
, div_
|
, div_
|
||||||
[ class_ "body" ]
|
[ class_ "body" ]
|
||||||
(Body.render m parts)
|
(Body.render m parts)
|
||||||
|
@ -173,6 +181,12 @@ reply m backlinks (post, parts) = div_
|
||||||
]
|
]
|
||||||
|
|
||||||
where
|
where
|
||||||
|
files_or_embed_view :: View a
|
||||||
|
files_or_embed_view =
|
||||||
|
case (Post.embed post) of
|
||||||
|
Just txt -> embed post
|
||||||
|
Nothing -> files (media_root m) site_ post
|
||||||
|
|
||||||
site_ :: Site
|
site_ :: Site
|
||||||
site_ = site m
|
site_ = site m
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import Data.Time.Clock
|
||||||
, addUTCTime
|
, addUTCTime
|
||||||
, secondsToDiffTime
|
, secondsToDiffTime
|
||||||
)
|
)
|
||||||
import Data.Time.Calendar (fromGregorian, Day)
|
import Data.Time.Calendar (fromGregorian)
|
||||||
|
|
||||||
data Time
|
data Time
|
||||||
= Now
|
= Now
|
||||||
|
@ -76,8 +76,8 @@ update
|
||||||
-> Time
|
-> Time
|
||||||
-> Model
|
-> Model
|
||||||
-> Effect a Model
|
-> Effect a Model
|
||||||
update iface (SlideInput time) m = m <# do
|
update iface (SlideInput nstr) m = m <# do
|
||||||
consoleLog $ "Input: " <> time
|
consoleLog $ "Input: " <> nstr
|
||||||
|
|
||||||
return $ (passAction iface) NoAction
|
return $ (passAction iface) NoAction
|
||||||
|
|
||||||
|
@ -86,24 +86,27 @@ update iface (SlideChange nstr) m = m { whereAt = n } <# do
|
||||||
|
|
||||||
now <- getCurrentTime
|
now <- getCurrentTime
|
||||||
|
|
||||||
return $ (goTo iface) $ interpolateTimeHours n now
|
let newTime = interpolateTimeHours n now
|
||||||
|
|
||||||
|
return $ (goTo iface) newTime
|
||||||
|
|
||||||
where
|
where
|
||||||
n :: Integer
|
n :: Integer
|
||||||
n = read $ fromMisoString nstr
|
n = read $ fromMisoString nstr
|
||||||
|
|
||||||
|
|
||||||
update _ _ m = noEff m
|
update _ _ m = noEff m
|
||||||
|
|
||||||
|
|
||||||
earliest :: UTCTime
|
earliest :: UTCTime
|
||||||
earliest = UTCTime (fromGregorian 2020 12 21) (secondsToDiffTime 19955)
|
--earliest = UTCTime (fromGregorian 2020 12 20) (secondsToDiffTime 82643)
|
||||||
|
earliest = UTCTime (fromGregorian 2020 12 20) (secondsToDiffTime 82644)
|
||||||
|
|
||||||
|
|
||||||
-- Linear interpolation function using hours
|
-- Linear interpolation function using hours
|
||||||
interpolateTimeHours :: Integer -> UTCTime -> UTCTime
|
interpolateTimeHours :: Integer -> UTCTime -> UTCTime
|
||||||
interpolateTimeHours n currentTime
|
interpolateTimeHours n currentTime
|
||||||
| n == 0 = currentTime
|
| n == 0 = currentTime
|
||||||
|
| n == -500 = earliest
|
||||||
| otherwise = addUTCTime (fromIntegral hoursToAdjust * secondsInHour) currentTime
|
| otherwise = addUTCTime (fromIntegral hoursToAdjust * secondsInHour) currentTime
|
||||||
|
|
||||||
where
|
where
|
||||||
|
|
|
@ -33,6 +33,8 @@ import Miso
|
||||||
, consoleLog
|
, consoleLog
|
||||||
, pushURI
|
, pushURI
|
||||||
, uriSub
|
, uriSub
|
||||||
|
, time_
|
||||||
|
, class_
|
||||||
)
|
)
|
||||||
import GHCJS.DOM (currentDocument)
|
import GHCJS.DOM (currentDocument)
|
||||||
import GHCJS.DOM.Types (toJSString, fromJSString, Element, JSString)
|
import GHCJS.DOM.Types (toJSString, fromJSString, Element, JSString)
|
||||||
|
@ -156,7 +158,11 @@ mainView model = view
|
||||||
|
|
||||||
catalog_view :: Model -> View Action
|
catalog_view :: Model -> View Action
|
||||||
catalog_view m = div_ []
|
catalog_view m = div_ []
|
||||||
[ h1_ [] [ text "Overboard Catalog" ]
|
[ div_
|
||||||
|
[ class_ "page_heading" ]
|
||||||
|
[ h1_ [] [ text "Overboard Catalog" ]
|
||||||
|
, time_ [] [ text $ pack $ show $ current_time model ]
|
||||||
|
]
|
||||||
, TC.view iTime (tc_model m)
|
, TC.view iTime (tc_model m)
|
||||||
, Grid.view iGrid (grid_model model)
|
, Grid.view iGrid (grid_model model)
|
||||||
]
|
]
|
||||||
|
|
|
@ -24,6 +24,7 @@ data CatalogPost = CatalogPost
|
||||||
, email :: Maybe Text
|
, email :: Maybe Text
|
||||||
, thread_id :: Int
|
, thread_id :: Int
|
||||||
-- , post_count :: Int
|
-- , post_count :: Int
|
||||||
|
, embed :: Maybe Text
|
||||||
, estimated_post_count :: Int
|
, estimated_post_count :: Int
|
||||||
, site_name :: Text
|
, site_name :: Text
|
||||||
, pathpart :: Text
|
, pathpart :: Text
|
||||||
|
|
|
@ -19,6 +19,7 @@ data Post = Post
|
||||||
, email :: Maybe Text
|
, email :: Maybe Text
|
||||||
, body_search_index :: Text
|
, body_search_index :: Text
|
||||||
, thread_id :: Integer
|
, thread_id :: Integer
|
||||||
|
, embed :: Maybe Text
|
||||||
, attachments :: [ Attachment ]
|
, attachments :: [ Attachment ]
|
||||||
} deriving (Show, Generic, FromJSON, ToJSON, Eq)
|
} deriving (Show, Generic, FromJSON, ToJSON, Eq)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{-# LANGUAGE MultiWayIf #-}
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
module BodyParser
|
module Parsing.BodyParser
|
||||||
( PostPart (..)
|
( PostPart (..)
|
||||||
, parsePostBody
|
, parsePostBody
|
||||||
, collectBacklinks
|
, collectBacklinks
|
||||||
|
@ -32,8 +32,8 @@ import Miso.String (fromMisoString)
|
||||||
import qualified Network.PostType as Post
|
import qualified Network.PostType as Post
|
||||||
import Component.Thread.Model (PostWithBody)
|
import Component.Thread.Model (PostWithBody)
|
||||||
|
|
||||||
import PostPartType
|
import Parsing.PostPartType
|
||||||
import QuoteLinkParser
|
import Parsing.QuoteLinkParser
|
||||||
|
|
||||||
|
|
||||||
nodeListToList :: NodeList -> IO [ Node ]
|
nodeListToList :: NodeList -> IO [ Node ]
|
|
@ -0,0 +1,21 @@
|
||||||
|
module Parsing.EmbedParser
|
||||||
|
( extractVideoId
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Text.Parsec
|
||||||
|
import Text.Parsec.String
|
||||||
|
|
||||||
|
-- Parser to extract the video ID
|
||||||
|
videoIdParser :: Parser String
|
||||||
|
videoIdParser = do
|
||||||
|
-- Look for the data-video attribute
|
||||||
|
_ <- manyTill anyChar (try (string "data-video=\"") <|> string "href=\"https://youtu.be/")
|
||||||
|
-- Capture the video ID
|
||||||
|
videoId <- manyTill anyChar (try (char '\"') <|> (char '"' >> char ' '))
|
||||||
|
-- Return the captured ID
|
||||||
|
return videoId
|
||||||
|
|
||||||
|
-- Function to apply the parser and extract the video ID
|
||||||
|
extractVideoId :: String -> Either ParseError String
|
||||||
|
extractVideoId input = parse videoIdParser "" input
|
|
@ -1,9 +1,9 @@
|
||||||
module PostPartType where
|
module Parsing.PostPartType where
|
||||||
|
|
||||||
import GHCJS.DOM.Types (JSString)
|
import GHCJS.DOM.Types (JSString)
|
||||||
import Text.Parsec (ParseError)
|
import Text.Parsec (ParseError)
|
||||||
|
|
||||||
import QuoteLinkParser (ParsedURL)
|
import Parsing.QuoteLinkParser (ParsedURL)
|
||||||
|
|
||||||
data PostPart
|
data PostPart
|
||||||
= SimpleText JSString
|
= SimpleText JSString
|
|
@ -1,4 +1,4 @@
|
||||||
module QuoteLinkParser
|
module Parsing.QuoteLinkParser
|
||||||
( parseURL
|
( parseURL
|
||||||
, ParsedURL (..)
|
, ParsedURL (..)
|
||||||
)
|
)
|
Loading…
Reference in New Issue