1
1
mirror of https://github.com/aelve/guide.git synced 2024-12-24 21:35:06 +03:00
guide/src/Main.hs

571 lines
18 KiB
Haskell
Raw Normal View History

{-# LANGUAGE
OverloadedStrings,
TemplateHaskell,
RecordWildCards,
RankNTypes,
2016-02-19 22:12:23 +03:00
FlexibleInstances,
QuasiQuotes,
2016-02-20 01:01:14 +03:00
ScopedTypeVariables,
NoImplicitPrelude
#-}
2016-02-02 12:35:39 +03:00
module Main (main) where
-- General
import BasePrelude hiding (Category)
2016-02-14 15:19:36 +03:00
-- Monads and monad transformers
import Control.Monad.State
-- Lenses
import Lens.Micro.Platform
-- Text
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
2016-02-19 22:12:23 +03:00
import qualified Data.Text.Lazy.Builder as TL
import Data.Text.Format hiding (format)
import qualified Data.Text.Format as Format
2016-02-19 22:12:23 +03:00
import qualified Data.Text.Format.Params as Format
import NeatInterpolation
2016-02-20 01:01:14 +03:00
-- Randomness
import System.Random
-- Web
import Lucid hiding (for_)
2016-02-19 22:12:23 +03:00
import Web.Spock hiding (get, text)
2016-02-14 15:19:36 +03:00
import qualified Web.Spock as Spock
import Network.Wai.Middleware.Static
-- | Unique id, used for many things categories, items, and anchor ids.
-- Note that in HTML 5 using numeric ids for divs, spans, etc is okay.
2016-02-14 15:19:36 +03:00
type UID = Int
randomUID :: MonadIO m => m UID
randomUID = liftIO $ randomRIO (0, 10^(9::Int))
2016-02-20 10:31:14 +03:00
data ProCon = ProCon {
_proConId :: UID,
_proConText :: Text }
makeLenses ''ProCon
2016-02-14 14:03:39 +03:00
data ItemKind = HackageLibrary | Library | Unknown
data Item = Item {
2016-02-14 15:19:36 +03:00
_itemId :: UID,
_name :: Text,
2016-02-20 10:31:14 +03:00
_pros :: [ProCon],
_cons :: [ProCon],
2016-02-14 15:19:36 +03:00
_link :: Maybe Text,
_kind :: ItemKind }
makeLenses ''Item
2016-02-20 10:31:14 +03:00
proConById :: UID -> Lens' Item ProCon
proConById uid = singular $
(pros.each . filtered ((== uid) . view proConId)) `failing`
(cons.each . filtered ((== uid) . view proConId))
data Category = Category {
2016-02-14 15:19:36 +03:00
_catId :: UID,
2016-02-14 14:03:39 +03:00
_title :: Text,
_items :: [Item] }
makeLenses ''Category
data S = S {
_categories :: [Category] }
makeLenses ''S
categoryById :: UID -> Lens' S Category
categoryById uid = singular $
categories.each . filtered ((== uid) . view catId)
2016-02-14 15:19:36 +03:00
itemById :: UID -> Lens' S Item
itemById uid = singular $
categories.each . items.each . filtered ((== uid) . view itemId)
2016-02-14 14:03:39 +03:00
2016-02-14 14:10:54 +03:00
emptyState :: S
emptyState = S {
_categories = [] }
sampleState :: S
sampleState = S {
_categories = [
Category {
_catId = 1,
2016-02-14 14:10:54 +03:00
_title = "lenses",
_items = [
Item {
_itemId = 2,
2016-02-14 15:19:36 +03:00
_name = "lens",
2016-02-20 10:31:14 +03:00
_pros = [ProCon 3 "the standard lenses library",
ProCon 4 "batteries included"],
_cons = [ProCon 5 "huge"],
2016-02-14 15:19:36 +03:00
_link = Nothing,
_kind = HackageLibrary },
2016-02-14 14:10:54 +03:00
Item {
2016-02-20 10:31:14 +03:00
_itemId = 6,
2016-02-14 15:19:36 +03:00
_name = "microlens",
2016-02-20 10:31:14 +03:00
_pros = [ProCon 7 "very small",
ProCon 8 "good for libraries"],
_cons = [ProCon 9 "doesn't have advanced features"],
2016-02-14 15:19:36 +03:00
_link = Nothing,
_kind = HackageLibrary }
2016-02-14 14:10:54 +03:00
] }
] }
2016-02-02 12:35:39 +03:00
main :: IO ()
main = runSpock 8080 $ spockT id $ do
middleware (staticPolicy (addBase "static"))
2016-02-14 14:10:54 +03:00
stateVar <- liftIO $ newIORef sampleState
2016-02-19 22:12:23 +03:00
let withS :: MonadIO m => State S a -> m a
withS f = liftIO $ atomicModifyIORef' stateVar (swap . runState f)
2016-02-14 14:19:32 +03:00
2016-02-19 22:34:51 +03:00
-- Render the main page.
2016-02-14 15:19:36 +03:00
Spock.get root $ do
s <- liftIO $ readIORef stateVar
lucid $ renderRoot s
2016-02-14 14:19:32 +03:00
2016-02-14 15:19:36 +03:00
-- The “/add” methods return rendered parts of the structure (added
-- categories, changed items, etc) so that the Javascript part could take
-- them and inject into the page. We don't want to duplicate rendering on
-- server side and on client side.
2016-02-19 22:27:27 +03:00
-- (category|item)/action
-- (category|item)/id/action
-- (category|item)/id/thing/action
2016-02-19 22:34:51 +03:00
-- Create a new category, with its title submitted via a POST request.
2016-02-19 22:27:27 +03:00
Spock.post "/category/add" $ do
2016-02-14 14:03:39 +03:00
title' <- param' "title"
uid <- randomUID
let newCategory = Category {
_catId = uid,
2016-02-14 14:03:39 +03:00
_title = title',
_items = [] }
2016-02-14 15:19:36 +03:00
withS $
categories %= (++ [newCategory])
lucid $ renderCategory newCategory
2016-02-14 14:19:32 +03:00
2016-02-19 22:34:51 +03:00
-- Create a new library in the specified category, with the library name
-- and category id submitted via a POST request.
2016-02-19 22:27:27 +03:00
Spock.post ("/category" <//> var <//> "library/add") $ \catId' -> do
2016-02-14 14:03:39 +03:00
name' <- param' "name"
uid <- randomUID
2016-02-14 14:03:39 +03:00
let newItem = Item {
_itemId = uid,
2016-02-14 15:19:36 +03:00
_name = name',
_pros = [],
_cons = [],
_link = Nothing,
_kind = HackageLibrary }
-- TODO: maybe do something if the category doesn't exist (e.g. has been
-- already deleted)
2016-02-14 15:19:36 +03:00
withS $
categoryById catId' . items %= (++ [newItem])
2016-02-20 10:31:14 +03:00
lucid $ renderItem Normal newItem
2016-02-19 22:34:51 +03:00
-- Add a pro (argument in favor of a library).
2016-02-19 22:27:27 +03:00
Spock.post ("/item" <//> var <//> "pros/add") $ \itemId' -> do
2016-02-20 10:31:14 +03:00
content <- param' "content"
uid <- randomUID
let newThing = ProCon uid content
withS $ do
itemById itemId' . pros %= (++ [newThing])
lucid $ renderProCon Editable itemId' newThing
2016-02-14 15:19:36 +03:00
2016-02-19 22:34:51 +03:00
-- Add a con (argument against a library).
2016-02-19 22:27:27 +03:00
Spock.post ("/item" <//> var <//> "cons/add") $ \itemId' -> do
2016-02-20 10:31:14 +03:00
content <- param' "content"
uid <- randomUID
let newThing = ProCon uid content
withS $ do
itemById itemId' . cons %= (++ [newThing])
lucid $ renderProCon Editable itemId' newThing
2016-02-17 19:43:35 +03:00
2016-02-19 22:34:51 +03:00
-- Set the title of a category (returns rendered new title).
2016-02-19 22:27:27 +03:00
Spock.post ("/category" <//> var <//> "title/set") $ \catId' -> do
2016-02-17 19:43:35 +03:00
title' <- param' "title"
changedCategory <- withS $ do
categoryById catId' . title .= title'
use (categoryById catId')
2016-02-17 19:43:35 +03:00
lucid $ renderCategoryHeading changedCategory
2016-02-19 22:34:51 +03:00
-- Return rendered title of a category.
2016-02-19 22:27:27 +03:00
Spock.get ("/category" <//> var <//> "title/render-normal") $ \catId' -> do
category <- withS $ use (categoryById catId')
2016-02-19 22:27:27 +03:00
lucid $ renderCategoryHeading category
2016-02-17 19:43:35 +03:00
2016-02-19 22:34:51 +03:00
-- Return rendered title of a category the way it should look when the
-- category is being edited.
2016-02-19 22:27:27 +03:00
Spock.get ("/category" <//> var <//> "title/render-edit") $ \catId' -> do
category <- withS $ use (categoryById catId')
2016-02-19 22:27:27 +03:00
lucid $ renderCategoryHeadingEdit category
2016-02-14 15:19:36 +03:00
2016-02-20 02:28:18 +03:00
-- Return rendered item the way it should normally look.
Spock.get ("/item" <//> var <//> "render-normal") $ \itemId' -> do
item <- withS $ use (itemById itemId')
2016-02-20 10:31:14 +03:00
lucid $ renderItem Normal item
2016-02-20 02:28:18 +03:00
2016-02-20 10:31:14 +03:00
-- Return rendered item the way it should look when it's editable.
2016-02-20 02:28:18 +03:00
Spock.get ("/item" <//> var <//> "render-edit") $ \itemId' -> do
item <- withS $ use (itemById itemId')
lucid $ renderItem Editable item
2016-02-20 10:31:14 +03:00
-- Return rendered pro/con the way it should normally look.
Spock.get ("/item" <//> var <//> "pro-con" <//> var <//> "render-normal") $
\itemId' proConId' -> do
thing <- withS $ use (itemById itemId' . proConById proConId')
lucid $ renderProCon Editable itemId' thing
-- Return rendered pro/con the way it should look when it's being edited.
Spock.get ("/item" <//> var <//> "pro-con" <//> var <//> "render-edit") $
\itemId' proConId' -> do
thing <- withS $ use (itemById itemId' . proConById proConId')
lucid $ renderProCon InEdit itemId' thing
-- Change a pro/con.
Spock.post ("/item" <//> var <//> "pro-con" <//> var <//> "set") $
\itemId' proConId' -> do
content <- param' "content"
changedThing <- withS $ do
itemById itemId' . proConById proConId' . proConText .= content
use (itemById itemId' . proConById proConId')
lucid $ renderProCon Editable itemId' changedThing
2016-02-20 01:01:14 +03:00
renderRoot :: S -> HtmlT IO ()
renderRoot s = do
2016-02-14 14:03:39 +03:00
includeJS "https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"
includeCSS "/css.css"
2016-02-19 22:34:51 +03:00
-- Include definitions of all Javascript functions that we have defined in
-- this file.
2016-02-19 22:12:23 +03:00
script_ $ T.unlines (map snd (allJSFunctions :: [(Text, Text)]))
2016-02-20 01:51:33 +03:00
categoriesNode <- div_ [id_ "categories"] $ do
mapM_ renderCategory (s ^. categories)
2016-02-20 01:51:33 +03:00
thisNode
2016-02-19 22:12:23 +03:00
input_ [type_ "text", placeholder_ "new category",
2016-02-20 01:51:33 +03:00
submitFunc (js_addCategory (categoriesNode, js_this_value))]
2016-02-20 01:01:14 +03:00
renderCategoryHeading :: Category -> HtmlT IO ()
2016-02-17 19:43:35 +03:00
renderCategoryHeading category =
h2_ $ do
2016-02-20 01:01:14 +03:00
headerNode <- thisNode
2016-02-17 19:43:35 +03:00
-- TODO: make category headings anchor links
toHtml (category^.title)
2016-02-20 00:13:20 +03:00
textButton "edit" $
2016-02-20 01:01:14 +03:00
js_startCategoryHeadingEdit (headerNode, category^.catId)
2016-02-17 19:43:35 +03:00
2016-02-20 01:01:14 +03:00
renderCategoryHeadingEdit :: Category -> HtmlT IO ()
2016-02-17 19:43:35 +03:00
renderCategoryHeadingEdit category =
h2_ $ do
2016-02-20 01:01:14 +03:00
headerNode <- thisNode
2016-02-19 22:34:51 +03:00
let handler = js_submitCategoryHeadingEdit
2016-02-20 01:01:14 +03:00
(headerNode, category^.catId, js_this_value)
2016-02-17 19:43:35 +03:00
input_ [type_ "text", value_ (category^.title), submitFunc handler]
2016-02-20 01:01:14 +03:00
textButton "cancel" $
js_cancelCategoryHeadingEdit (headerNode, category^.catId)
2016-02-17 19:43:35 +03:00
2016-02-20 01:01:14 +03:00
renderCategory :: Category -> HtmlT IO ()
2016-02-14 14:03:39 +03:00
renderCategory category =
div_ [id_ (tshow (category^.catId))] $ do
2016-02-17 19:43:35 +03:00
renderCategoryHeading category
2016-02-20 01:51:33 +03:00
itemsNode <- div_ [class_ "items"] $ do
2016-02-20 10:31:14 +03:00
mapM_ (renderItem Normal) (category^.items)
2016-02-20 01:51:33 +03:00
thisNode
let handler = js_addLibrary (itemsNode, category^.catId, js_this_value)
2016-02-14 15:19:36 +03:00
input_ [type_ "text", placeholder_ "new item", submitFunc handler]
2016-02-14 14:22:47 +03:00
-- TODO: when the link for a HackageLibrary isn't empty, show it separately
-- (as “site”), don't replace the Hackage link
2016-02-20 02:28:18 +03:00
renderItem
:: Editable -- ^ Show edit buttons?
-> Item
-> HtmlT IO ()
renderItem editable item =
div_ [class_ "item", id_ (tshow (item^.itemId))] $ do
2016-02-20 01:01:14 +03:00
itemNode <- thisNode
2016-02-20 02:28:18 +03:00
h3_ $ do
itemHeader
case editable of
2016-02-20 10:31:14 +03:00
Normal -> textButton "edit" $
2016-02-20 02:28:18 +03:00
js_enableItemEdit (itemNode, item^.itemId)
Editable -> textButton "edit off" $
js_disableItemEdit (itemNode, item^.itemId)
2016-02-14 14:03:39 +03:00
div_ [class_ "pros-cons"] $ do
2016-02-14 14:19:32 +03:00
div_ [class_ "pros"] $ do
2016-02-14 14:03:39 +03:00
p_ "Pros:"
2016-02-20 02:28:18 +03:00
case editable of
2016-02-20 10:31:14 +03:00
Normal ->
ul_ $ mapM_ (renderProCon Normal (item^.itemId)) (item^.pros)
2016-02-20 02:28:18 +03:00
Editable -> do
listNode <- ul_ $ do
mapM_ (renderProCon Editable (item^.itemId)) (item^.pros)
thisNode
let handler = js_addPros (listNode, item^.itemId, js_this_value)
2016-02-20 02:28:18 +03:00
input_ [type_ "text", placeholder_ "add pros", submitFunc handler]
2016-02-14 14:19:32 +03:00
div_ [class_ "cons"] $ do
2016-02-14 14:03:39 +03:00
p_ "Cons:"
2016-02-20 02:28:18 +03:00
case editable of
2016-02-20 10:31:14 +03:00
Normal ->
ul_ $ mapM_ (renderProCon Normal (item^.itemId)) (item^.cons)
2016-02-20 02:28:18 +03:00
Editable -> do
listNode <- ul_ $ do
mapM_ (renderProCon Editable (item^.itemId)) (item^.cons)
thisNode
let handler = js_addCons (listNode, item^.itemId, js_this_value)
2016-02-20 02:28:18 +03:00
input_ [type_ "text", placeholder_ "add cons", submitFunc handler]
where
2016-02-14 14:03:39 +03:00
hackageLink = format "https://hackage.haskell.org/package/{}"
[item^.name]
itemHeader = case (item^.link, item^.kind) of
(Just l, _) ->
a_ [href_ l] (toHtml (item^.name))
(Nothing, HackageLibrary) ->
a_ [href_ hackageLink] (toHtml (item^.name))
_otherwise -> toHtml (item^.name)
2016-02-20 10:31:14 +03:00
renderProCon :: Editable -> UID -> ProCon -> HtmlT IO ()
renderProCon Normal _ thing = li_ (toHtml (thing^.proConText))
renderProCon Editable itemId' thing = li_ $ do
this <- thisNode
toHtml (thing^.proConText)
textButton "edit" $
js_startProConEdit (this, itemId', thing^.proConId)
renderProCon InEdit itemId' thing = li_ $ do
this <- thisNode
let handler = js_submitProConEdit
(this, itemId', thing^.proConId, js_this_value)
input_ [type_ "text", value_ (thing^.proConText), submitFunc handler]
textButton "cancel" $
js_cancelProConEdit (this, itemId', thing^.proConId)
-- Utils
2016-02-20 01:01:14 +03:00
includeJS :: Monad m => Text -> HtmlT m ()
2016-02-14 14:03:39 +03:00
includeJS url = with (script_ "") [src_ url]
2016-02-20 01:01:14 +03:00
includeCSS :: Monad m => Text -> HtmlT m ()
2016-02-14 14:22:47 +03:00
includeCSS url = link_ [rel_ "stylesheet", type_ "text/css", href_ url]
2016-02-20 02:30:04 +03:00
submitFunc :: JS -> Attribute
2016-02-14 15:19:36 +03:00
submitFunc f = onkeyup_ $ format
"if (event.keyCode == 13) {\
\ {}\
\ this.value = ''; }"
[f]
2016-02-19 22:12:23 +03:00
-- Javascript
2016-02-20 02:30:04 +03:00
js_this_value :: JS
2016-02-20 00:13:20 +03:00
js_this_value = "this.value"
2016-02-19 22:12:23 +03:00
2016-02-20 02:28:18 +03:00
-- TODO: try to make them more type-safe somehow?
2016-02-19 22:12:23 +03:00
class JSFunction a where
makeJSFunction
:: Text -- Name
2016-02-20 02:30:04 +03:00
-> JS -- Definition
2016-02-19 22:12:23 +03:00
-> a
-- This generates function name
instance JSFunction Text where
makeJSFunction fName _ = fName
-- This generates function definition and direct dependencies
2016-02-20 02:30:04 +03:00
instance JSFunction (Text, JS) where
2016-02-19 22:12:23 +03:00
makeJSFunction fName fDef = (fName, fDef)
-- This generates a function that takes arguments and produces a Javascript
-- function call
2016-02-20 02:30:04 +03:00
instance Format.Params a => JSFunction (a -> JS) where
2016-02-19 22:12:23 +03:00
makeJSFunction fName _ = \args -> do
let argsText = map (TL.toStrict . TL.toLazyText) (Format.buildParams args)
fName <> "(" <> T.intercalate "," argsText <> ");"
allJSFunctions :: JSFunction a => [a]
allJSFunctions = [
js_addLibrary, js_addCategory,
2016-02-19 22:34:51 +03:00
js_startCategoryHeadingEdit, js_submitCategoryHeadingEdit, js_cancelCategoryHeadingEdit,
2016-02-20 02:28:18 +03:00
js_addPros, js_addCons,
2016-02-20 10:31:14 +03:00
js_enableItemEdit, js_disableItemEdit,
js_startProConEdit, js_submitProConEdit, js_cancelProConEdit ]
2016-02-19 22:12:23 +03:00
-- | Create a new category.
js_addCategory :: JSFunction a => a
js_addCategory = makeJSFunction "addCategory" [text|
2016-02-20 01:51:33 +03:00
function addCategory(node, s) {
2016-02-19 22:27:27 +03:00
$.post("/category/add", {title: s})
2016-02-19 22:12:23 +03:00
.done(function(data) {
2016-02-20 01:51:33 +03:00
$(node).append(data);
2016-02-19 22:12:23 +03:00
});
}
|]
2016-02-19 22:27:27 +03:00
-- | Add a new library to some category.
js_addLibrary :: JSFunction a => a
js_addLibrary = makeJSFunction "addLibrary" [text|
2016-02-20 01:51:33 +03:00
function addLibrary(node, catId, s) {
2016-02-19 22:27:27 +03:00
$.post("/category/"+catId+"/library/add", {name: s})
.done(function(data) {
2016-02-20 01:51:33 +03:00
$(node).append(data);
2016-02-19 22:27:27 +03:00
});
}
|]
2016-02-19 22:12:23 +03:00
{- |
Start category heading editing (this happens when you click on [edit]).
This turns the heading into an editbox, and adds a [cancel] link.
-}
2016-02-19 22:34:51 +03:00
js_startCategoryHeadingEdit :: JSFunction a => a
js_startCategoryHeadingEdit = makeJSFunction "startCategoryHeadingEdit" [text|
2016-02-20 01:01:14 +03:00
function startCategoryHeadingEdit(node, catId) {
2016-02-19 22:27:27 +03:00
$.get("/category/"+catId+"/title/render-edit")
2016-02-19 22:12:23 +03:00
.done(function(data) {
2016-02-20 01:01:14 +03:00
$(node).replaceWith(data);
2016-02-19 22:12:23 +03:00
});
}
|]
{- |
2016-02-19 22:27:27 +03:00
Cancel category heading editing.
2016-02-19 22:12:23 +03:00
This turns the heading with the editbox back into a simple text heading.
-}
2016-02-19 22:34:51 +03:00
js_cancelCategoryHeadingEdit :: JSFunction a => a
js_cancelCategoryHeadingEdit = makeJSFunction "cancelCategoryHeadingEdit" [text|
2016-02-20 01:01:14 +03:00
function cancelCategoryHeadingEdit(node, catId) {
2016-02-19 22:27:27 +03:00
$.get("/category/"+catId+"/title/render-normal")
2016-02-19 22:12:23 +03:00
.done(function(data) {
2016-02-20 01:01:14 +03:00
$(node).replaceWith(data);
2016-02-19 22:12:23 +03:00
});
}
|]
{- |
2016-02-19 22:27:27 +03:00
Finish category heading editing (this happens when you submit the field).
2016-02-19 22:12:23 +03:00
This turns the heading with the editbox back into a simple text heading.
-}
2016-02-19 22:34:51 +03:00
js_submitCategoryHeadingEdit :: JSFunction a => a
js_submitCategoryHeadingEdit = makeJSFunction "submitCategoryHeadingEdit" [text|
2016-02-20 01:01:14 +03:00
function submitCategoryHeadingEdit(node, catId, s) {
2016-02-19 22:27:27 +03:00
$.post("/category/"+catId+"/title/set", {title: s})
2016-02-19 22:12:23 +03:00
.done(function(data) {
2016-02-20 01:01:14 +03:00
$(node).replaceWith(data);
2016-02-19 22:12:23 +03:00
});
}
|]
-- | Add pros to some item.
js_addPros :: JSFunction a => a
js_addPros = makeJSFunction "addPros" [text|
2016-02-20 01:01:14 +03:00
function addPros(node, itemId, s) {
2016-02-19 22:27:27 +03:00
$.post("/item/"+itemId+"/pros/add", {content: s})
2016-02-19 22:12:23 +03:00
.done(function(data) {
$(node).append(data);
2016-02-19 22:12:23 +03:00
});
}
|]
-- | Add cons to some item.
js_addCons :: JSFunction a => a
js_addCons = makeJSFunction "addCons" [text|
2016-02-20 01:01:14 +03:00
function addCons(node, itemId, s) {
2016-02-19 22:27:27 +03:00
$.post("/item/"+itemId+"/cons/add", {content: s})
2016-02-20 02:28:18 +03:00
.done(function(data) {
$(node).append(data);
2016-02-20 02:28:18 +03:00
});
}
|]
-- | Add “[edit]” buttons to everything in an item.
js_enableItemEdit :: JSFunction a => a
js_enableItemEdit = makeJSFunction "enableItemEdit" [text|
function enableItemEdit (node, itemId) {
$.get("/item/"+itemId+"/render-edit")
2016-02-19 22:12:23 +03:00
.done(function(data) {
2016-02-20 01:01:14 +03:00
$(node).replaceWith(data);
2016-02-19 22:12:23 +03:00
});
}
|]
2016-02-20 02:28:18 +03:00
-- | Remove “[edit]” buttons from everything in an item.
js_disableItemEdit :: JSFunction a => a
js_disableItemEdit = makeJSFunction "disableItemEdit" [text|
function disableItemEdit (node, itemId) {
$.get("/item/"+itemId+"/render-normal")
.done(function(data) {
$(node).replaceWith(data);
});
}
|]
2016-02-20 10:31:14 +03:00
js_startProConEdit :: JSFunction a => a
js_startProConEdit = makeJSFunction "startProConEdit" [text|
function startProConEdit(node, itemId, thingId) {
$.get("/item/"+itemId+"/pro-con/"+thingId+"/render-edit")
.done(function(data) {
$(node).replaceWith(data);
});
}
|]
js_cancelProConEdit :: JSFunction a => a
js_cancelProConEdit = makeJSFunction "cancelProConEdit" [text|
function cancelProConEdit(node, itemId, thingId) {
$.get("/item/"+itemId+"/pro-con/"+thingId+"/render-normal")
.done(function(data) {
$(node).replaceWith(data);
});
}
|]
js_submitProConEdit :: JSFunction a => a
js_submitProConEdit = makeJSFunction "submitProConEdit" [text|
function submitProConEdit(node, itemId, thingId, s) {
$.post("/item/"+itemId+"/pro-con/"+thingId+"/set", {content: s})
.done(function(data) {
$(node).replaceWith(data);
});
}
|]
2016-02-20 02:28:18 +03:00
-- When adding a function, don't forget to add it to 'allJSFunctions'!
2016-02-20 02:30:04 +03:00
type JS = Text
2016-02-20 02:28:18 +03:00
2016-02-17 19:43:35 +03:00
-- A text button looks like “[cancel]”
textButton
2016-02-20 01:01:14 +03:00
:: Text -- ^ Button text
2016-02-20 02:30:04 +03:00
-> JS -- ^ Onclick handler
2016-02-20 01:01:14 +03:00
-> HtmlT IO ()
2016-02-20 00:09:49 +03:00
textButton caption handler =
span_ [class_ "textButton"] $
a_ [href_ "javascript:void(0)", onclick_ handler] (toHtml caption)
2016-02-17 19:43:35 +03:00
2016-02-20 01:01:14 +03:00
type JQuerySelector = Text
thisNode :: HtmlT IO JQuerySelector
thisNode = do
uid <- randomUID
span_ [id_ (tshow uid)] mempty
return (T.pack (show (format ":has(> #{})" [uid])))
2016-02-20 01:01:14 +03:00
lucid :: HtmlT IO a -> ActionT IO a
lucid h = do
htmlText <- liftIO (renderTextT h)
html (TL.toStrict htmlText)
-- | Format a string (a bit 'Text.Printf.printf' but with different syntax).
2016-02-19 22:12:23 +03:00
format :: Format.Params ps => Format -> ps -> Text
format f ps = TL.toStrict (Format.format f ps)
tshow :: Show a => a -> Text
tshow = T.pack . show
2016-02-20 02:28:18 +03:00
2016-02-20 10:31:14 +03:00
data Editable = Normal | Editable | InEdit