Skip to content

Instantly share code, notes, and snippets.

@Agnishom
Created July 12, 2018 05:52
Show Gist options
  • Select an option

  • Save Agnishom/1cd540c83195de2138b093259858f7d9 to your computer and use it in GitHub Desktop.

Select an option

Save Agnishom/1cd540c83195de2138b093259858f7d9 to your computer and use it in GitHub Desktop.
elm-ui
module AnalyticPage exposing (..)
import AutoGen exposing (..)
import Navigation
import Http
import Array
import Html exposing (..)
import Html.Attributes exposing (..)
import FileReader
import Json.Decode
import Json.Encode
import Bootstrap.Grid as Grid
import Bootstrap.Grid.Row as Row
import Bootstrap.Grid.Col as Col
import Bootstrap.Card as Card
import Bootstrap.Card.Block as Block
import Bootstrap.Button as Button
import Bootstrap.Badge as Badge
import Bootstrap.ListGroup as ListGroup
import Bootstrap.Form as Form
import Bootstrap.Form.Input as Input
import Bootstrap.Form.Select as Select
import Bootstrap.Utilities.Spacing as Spacing
import Bootstrap.Form.Checkbox as Checkbox
import Bootstrap.Alert as Alert
import Bootstrap.Text as Text
import Ace
type alias Model =
{ pageState : Maybe AnalyticPageState
, snippets : List ConvenientSnippet
, files: List FileReader.NativeFile -- the list of files to be uploaded on pressing save
, propro : Bool
}
type HeaderFooter = Header | Footer
type alias ConvenientSnippet =
{ headerFooter : HeaderFooter
, conditionalSnippet : ConditionalSnippet
}
init : (Model, Cmd Msg)
init =
{ pageState = Nothing
, snippets = []
, files = []
, propro = False
} ! [getState]
type Msg
= AddSnippet
| RemoveSnippet Int
| ChangeConditionKind Int String
| ChangeConditionString Int String
| ChangeHF Int HeaderFooter
| ChangeSnippet Int String
| ChangeGlobalHeader String
| ChangeGlobalFooter String
| AddFiles (List FileReader.NativeFile)
| RemoveFile Int -- this is supposed to remove a file from the "to be uploaded" list of files and NOT the already uploaded files
| Save
| Reload String (Result Http.Error Bool)
| UpdatePageState (Result Http.Error AnalyticPageState)
| SnippetSource String
| ToggleProNess
view : Model -> Html Msg
view model = div []
<| thisisgoingtobethemostawesomeanalyticspagever model
update : Msg -> Model -> (Model, Cmd Msg)
update msg model = case msg of
Reload url (Ok _) -> model ! [Navigation.load url]
Reload url (Err e) -> (Debug.log ("Error in Reload" ++ (toString e)) model) ! []
UpdatePageState (Ok pageState) -> {model | pageState = Just pageState, snippets = populateSnippets pageState } ! []
UpdatePageState (Err e) -> (Debug.log ("Error in UpdatePageState" ++ (toString e)) model) ! []
AddSnippet -> ({ model | snippets = model.snippets ++ [defaultSnippet] }, Cmd.none)
RemoveSnippet j -> (removeSnippet j model) ! []
ChangeHF j hf -> (changeHF j hf model) ! []
ChangeSnippet j s -> (changeSnippet j s model) ! []
ChangeConditionString j s -> (changeConditionString j s model) ! []
ChangeConditionKind j s -> (changeConditionKind j s model) ! []
ChangeGlobalHeader s -> (changeGlobalHeader s model) ! []
ChangeGlobalFooter s -> (changeGlobalFooter s model) ! []
AddFiles files -> (addFiles files model) ! []
RemoveFile j -> (removeFile j model) ! []
ToggleProNess -> ({ model | propro = not model.propro }, Cmd.none)
Save -> model ! [save model]
_ -> (model, Cmd.none)
removeFile : Int -> Model -> Model
removeFile j model =
let
files = Array.toIndexedList <| Array.fromList model.files
newFiles = List.map Tuple.second <| List.filter (\(i, s) -> i /= j) files
in
{ model | files = newFiles }
addFiles : List FileReader.NativeFile -> Model -> Model
addFiles files model =
let
newFiles = model.files ++ files
-- newFileNames = List.map (\file -> "/" ++ file.name) files
-- newPageState =
-- case model.pageState of
-- Just state ->
-- let
-- oldSettings = state.analytic_settings
-- oldFileNames = oldSettings.file_names
-- in
-- Just <| { state |
-- analytic_settings =
-- { oldSettings |
-- file_names =
-- oldFileNames ++ newFileNames
-- }
-- }
-- Nothing -> Nothing
in
{ model | files = newFiles }
extractString : UrlMatchingCondition -> String
extractString urlmatchingcondition =
case urlmatchingcondition of
UMStartsWith s -> s
UMEndsWith s -> s
UMDoesNotStartWith s -> s
UMDoesNotEndWith s -> s
UMContains s -> s
UMDoesNotContain s -> s
UMMatchesRegex s -> s
UMDoesNotMatchRegex s -> s
changeConditionKind : Int -> String -> Model -> Model
changeConditionKind i s model =
let
snippets = Array.toIndexedList <| Array.fromList model.snippets
f snip =
let
oldConditionalSnippet = snip.conditionalSnippet
newCondition =
case s of
"UMStartsWith" -> UMStartsWith (extractString oldConditionalSnippet.condition)
"UMEndsWith" -> UMEndsWith (extractString oldConditionalSnippet.condition)
"UMDoesNotStartWith" -> UMDoesNotStartWith (extractString oldConditionalSnippet.condition)
"UMDoesNotEndWith" -> UMDoesNotEndWith (extractString oldConditionalSnippet.condition)
"UMContains" -> UMContains (extractString oldConditionalSnippet.condition)
"UMDoesNotContain" -> UMDoesNotContain (extractString oldConditionalSnippet.condition)
"UMMatchesRegex" -> UMMatchesRegex (extractString oldConditionalSnippet.condition)
"UMDoesNotMatchRegex" -> UMDoesNotMatchRegex (extractString oldConditionalSnippet.condition)
_ -> UMStartsWith "Error"
in
{ snip | conditionalSnippet = { oldConditionalSnippet | condition = newCondition } }
newSnippets = List.map (\(j, s) -> if i == j then f s else s) snippets
in
{ model | snippets = newSnippets }
changeConditionString : Int -> String -> Model -> Model
changeConditionString i s model =
let
snippets = Array.toIndexedList <| Array.fromList model.snippets
f snip =
let
oldConditionalSnippet = snip.conditionalSnippet
newCondition =
case oldConditionalSnippet.condition of
UMStartsWith _ -> UMStartsWith s
UMEndsWith _ -> UMEndsWith s
UMDoesNotStartWith _ -> UMDoesNotStartWith s
UMDoesNotEndWith _ -> UMDoesNotEndWith s
UMContains _ -> UMContains s
UMDoesNotContain _ -> UMDoesNotContain s
UMMatchesRegex _ -> UMMatchesRegex s
UMDoesNotMatchRegex _ -> UMDoesNotMatchRegex s
in
{ snip | conditionalSnippet = { oldConditionalSnippet | condition = newCondition } }
newSnippets = List.map (\(j, s) -> if i == j then f s else s) snippets
in
{ model | snippets = newSnippets }
changeGlobalFooter : String -> Model -> Model
changeGlobalFooter newFooter model =
let
newPageState =
case model.pageState of
Nothing -> Nothing
Just state -> let
oldSettings = state.analytic_settings
oldCjs = state.analytic_settings.custom_js_snippets
in
Just <| { state |
analytic_settings =
{ oldSettings |
custom_js_snippets =
{ oldCjs |
common_footer = Just newFooter
}
}
}
in
{ model | pageState = newPageState }
changeGlobalHeader : String -> Model -> Model
changeGlobalHeader newHeader model =
let
newPageState =
case model.pageState of
Nothing -> Nothing
Just state -> let
oldSettings = state.analytic_settings
oldCjs = state.analytic_settings.custom_js_snippets
in
Just <| { state |
analytic_settings =
{ oldSettings |
custom_js_snippets =
{ oldCjs |
common_header = Just newHeader
}
}
}
in
{ model | pageState = newPageState }
changeSnippet : Int -> String -> Model -> Model
changeSnippet j str model =
let
snippets = Array.toIndexedList <| Array.fromList model.snippets
f (i, s) = if i /= j
then s
else let old = s.conditionalSnippet
in {s | conditionalSnippet = { old | snippet = Just str } }
newSnippets = List.map f snippets
in
{ model | snippets = newSnippets }
changeHF : Int -> HeaderFooter -> Model -> Model
changeHF j hf model =
let
snippets = Array.toIndexedList <| Array.fromList model.snippets
newSnippets = List.map (\(i, s) -> if i == j then {s | headerFooter = hf} else s) snippets
in
{ model | snippets = newSnippets }
removeSnippet : Int -> Model -> Model
removeSnippet j model =
let
snippets = Array.toIndexedList <| Array.fromList model.snippets
newSnippets = List.map Tuple.second <| List.filter (\(i, s) -> i /= j) snippets
in
{ model | snippets = newSnippets }
populateSnippets : AnalyticPageState -> List ConvenientSnippet
populateSnippets state =
let
headers = state.analytic_settings.custom_js_snippets.conditional_headers
footers = state.analytic_settings.custom_js_snippets.conditional_footers
f = (\header -> ConvenientSnippet Header header)
g = (\footer -> ConvenientSnippet Footer footer)
in
List.map f headers ++ List.map g footers
populateCjsSnippets : List ConvenientSnippet -> AnalyticPageState -> AnalyticPageState
populateCjsSnippets snippets state =
let
headers = List.map .conditionalSnippet <| List.filter (\snip -> snip.headerFooter == Header) snippets
footers = List.map .conditionalSnippet <| List.filter (\snip -> snip.headerFooter == Footer) snippets
oldSettings = state.analytic_settings
oldSnippets = state.analytic_settings.custom_js_snippets
in
{ state | analytic_settings = {
oldSettings | custom_js_snippets = {
oldSnippets | conditional_headers = headers,
conditional_footers = footers
}
}
}
getState : Cmd Msg
getState =
let
url = "/lambda/plugins/analytics/admin/"
request = Http.post url Http.emptyBody jsonDecAnalyticPageState
in
Http.send UpdatePageState request
makeMultiPart : Model -> Http.Body
makeMultiPart model =
let
newState = Maybe.map (populateCjsSnippets model.snippets) model.pageState
in
case newState of
Nothing -> Http.emptyBody
Just state -> Http.multipartBody <|
[ Http.stringPart "snippet_data" (Json.Encode.encode 0 <| jsonEncCustomJsSnippets state.analytic_settings.custom_js_snippets)]
++ (List.map (\file -> FileReader.filePart file.name file) model.files)
save : Model -> Cmd Msg
save model =
let
url = "/lambda/plugins/analytics/admin/upload/"
request = Http.post url (makeMultiPart model) Json.Decode.bool
in
Http.send (Reload "/lambda/plugins/analytics/admin/") request
subscriptions : Model -> Sub Msg
subscriptions model = Sub.none
-- tells if there is a tracking id present
isIntegrated : Model -> Bool
isIntegrated model =
case model.pageState of
Nothing -> False
Just state -> case state.analytic_settings.google_settings.tracking_id of
Nothing -> False
Just _ -> True
-- =========
-- UI DESIGN
-- =========
-- Only comments can help you here
-- Google and facebook stuff
googleknowsallaboutyou : Model -> Html Msg
googleknowsallaboutyou model =
Card.config [ Card.light ]
|> Card.footer []
[ (conditionallyRender model)
, text " or"
, (
if isIntegrated model
then
Button.button
[ Button.roleLink, Button.onClick (Reload "#/setup" (Ok True)) ]
[ text "Change/Remove Integration" ]
else
Button.button
[ Button.roleLink, Button.onClick (Reload "#/manual" (Ok True)) ]
[ text "Add tracking ID Manually" ]
)
]
|> conditionallyRenderGAData model
|> Card.view
conditionallyRenderGAData model =
let
tracking_id = model.pageState |> Maybe.andThen (.analytic_settings >> .google_settings >> .tracking_id)
website_name = model.pageState |> Maybe.andThen (.analytic_settings >> .google_settings >> .website_name)
website_url = model.pageState |> Maybe.andThen (.analytic_settings >> .google_settings >> .website_url)
in
case (tracking_id, website_name, website_url) of
(Just tid, Just wname, Just wurl) ->
Card.block []
[ Block.titleH4 [] [ text "Google Analytics" ]
, Block.text [] [ text wname ]
, Block.text [] [ royalchallenge wurl ]
, Block.text [] [ text tid ]
]
_ ->
Card.block []
[ Block.titleH4 [] [ text "Google Analytics" ]
, Block.text [] [ text "Connect your Google Account to quickly link your website to Google Analytics. Alternatively, you can add your tracking ID manually, or directly paste the code in 'Add code to site' block below" ]
]
markzuckerbergiswatchingyou : Html Msg
markzuckerbergiswatchingyou =
Card.config [ Card.light ]
|> Card.footer []
[ Button.button [ Button.primary ] [ text "Connect Facebook Account" ]
, text " or"
, Button.button [ Button.roleLink ] [ text "Add tracking ID manually" ]
]
|> Card.block [ Block.light ]
[ Block.titleH4 [] [ text "Facebook Pixel" ]
, Block.text [] [ text "Connect your Facebook Account to quickly link your website to Facebook Pixel. Alternatively, you can visit this link to create a pixel and paste the pixel code in 'Add code to site' block below" ]
]
|> Card.view
-- Things that go everywhere?
globalheaderandfootersection : Model -> Html Msg
globalheaderandfootersection model =
iwantacontainerwithpaddingplease
[ Grid.simpleRow
[ Grid.col []
[ h4 [] [text "Add code to site"]
]
]
, Grid.simpleRow
[ Grid.col []
[ p [] [royalchallenge "Header", text " (This code will be added in the header of every page of your website)"]
, pre [] [uglify ChangeGlobalHeader (model.pageState |> Maybe.andThen (.analytic_settings >> .custom_js_snippets >> .common_header))]
]
]
, Grid.simpleRow
[ Grid.col []
[ p [] [royalchallenge "Footer", text " (This code will be added in the footer of every page of your website)"]
, pre [] [uglify ChangeGlobalFooter ( model.pageState |> Maybe.andThen (.analytic_settings >> .custom_js_snippets >> .common_footer) )]
]
]
]
-- How much more flexibility do you want?
customsnippetsection : Model -> Html Msg
customsnippetsection model =
if model.propro then
iwantacontainerwithpaddingplease
[ Grid.simpleRow
[ Grid.col []
[ p [] [royalchallenge "Set Rules", text " (To add code on specific places, you can setup rules with respect to the paths of different pages on your website)"]
]
]
, case (List.isEmpty model.snippets) of
True -> div [] []
False -> div [] (makeSnippets model)
, Grid.simpleRow
[ Grid.col [ Col.textAlign Text.alignMdCenter ]
[ Button.button
[ Button.info
, Button.onClick AddSnippet
, Button.attrs [ class "round-button" ]
] [ text "+" ]
]
]
, Button.button
[ Button.roleLink
, Button.onClick ToggleProNess
]
[ text "Hide Advanced Options" ]
]
else
iwantacontainerwithpaddingplease
[ Button.button
[ Button.roleLink
, Button.onClick ToggleProNess
]
[ text "Show Advanced Options" ]
]
makeSnippets : Model -> List (Html Msg)
makeSnippets model =
let
snips = Array.toIndexedList <| Array.fromList model.snippets
f (i, snip) = customsnippet i snip
in
List.map f snips
customsnippet : Int -> ConvenientSnippet -> Html Msg
customsnippet i convsnip =
let
condsnippet = convsnip.conditionalSnippet
hf = convsnip.headerFooter
selectifmatches cond1 =
case (cond1, condsnippet.condition) of
(UMStartsWith _, UMStartsWith _) -> True
(UMEndsWith _, UMEndsWith _) -> True
(UMDoesNotStartWith _, UMDoesNotStartWith _) -> True
(UMDoesNotEndWith _, UMDoesNotEndWith _) -> True
(UMContains _, UMContains _) -> True
(UMDoesNotContain _, UMDoesNotContain _) -> True
(UMMatchesRegex _, UMMatchesRegex _) -> True
(UMDoesNotMatchRegex _, UMDoesNotMatchRegex _) -> True
_ -> False
in
iwantacontainerwithlittlepaddingplease
[ Grid.simpleRow
[ Grid.col []
[ Card.config [ Card.light ]
|> Card.block []
[ Block.custom
<| Form.formInline []
[ text "If the path"
, Select.select
[ Select.onChange (ChangeConditionKind i)
, Select.attrs [ class "extra-margin" ]
]
[ Select.item
[ value "UMStartsWith", selected (selectifmatches (UMStartsWith ""))]
[ text "starts with" ]
, Select.item
[ value "UMDoesNotStartWith", selected (selectifmatches (UMDoesNotStartWith "")) ]
[ text "doesn't start with" ]
, Select.item
[ value "UMEndsWith", selected (selectifmatches (UMEndsWith "")) ]
[ text "ends with" ]
, Select.item
[ value "UMDoesNotEndWith", selected (selectifmatches (UMDoesNotEndWith "")) ]
[ text "doesn't end with" ]
, Select.item
[ value "UMContains", selected (selectifmatches (UMContains "")) ]
[ text "contains" ]
, Select.item
[ value "UMDoesNotContain", selected (selectifmatches (UMDoesNotContain "")) ]
[ text "doesn't contain" ]
, Select.item
[ value "UMMatchesRegex", selected (selectifmatches (UMMatchesRegex "")) ]
[ text "matches regex" ]
, Select.item
[ value "UMDoesNotMatchRegex", selected (selectifmatches (UMDoesNotMatchRegex "")) ]
[ text "doesn't matches regex" ]
]
, Input.text
[ Input.onInput (ChangeConditionString i)
, Input.placeholder "e.g. /tours"
, Input.defaultValue (extractString condsnippet.condition)
, Input.attrs [ class "extra-margin" ]
]
, text "add the following code to the"
, Select.select
[ Select.onChange (ChangeHF i << (\x -> if x == "Header" then Header else Footer))
, Select.attrs [ class "extra-margin" ]
]
[ Select.item
[ value "Header"
, selected ((==) Header hf)
]
[ text "header"]
, Select.item
[ value "Footer"
, selected ((==) Footer hf)
]
[ text "footer"]
]
, text "of each page"
]
, Block.text [] [ royalchallenge "Add Code" ]
, Block.custom <| uglify (ChangeSnippet i) condsnippet.snippet
]
|> Card.view
]
, Grid.col [ Col.md1 ]
[ Button.button
[ Button.outlineDanger
, Button.attrs [ class "round-button" ]
, Button.onClick (RemoveSnippet i)
]
[ text "x" ]
]
]
]
-- Someone's trash is someone's treasure
uglify : (String -> Msg) -> Snippet -> Html Msg
uglify action snippet =
let
extractSnippet = Maybe.withDefault defaultSnippetText snippet
-- action =
-- case j of
-- Maybe i -> (ChangeSnippet i)
-- Nothing ->
-- case hf of
-- Header -> ChangeGlobalHeader
-- Footer -> ChangeGlobalFooter
in
div []
[ Ace.toHtml
[ Ace.onSourceChange action
, Ace.value extractSnippet
, Ace.mode "javascript"
, Ace.theme "textmate"
, Ace.enableBasicAutocompletion True
, Ace.enableLiveAutocompletion True
, Ace.enableSnippets True
, Ace.tabSize 2
, Ace.useSoftTabs False
, Ace.extensions [ "language_tools" ]
]
[]
]
-- I mean, literally!
fillupmystorage : Model -> Html Msg
fillupmystorage model =
let
domain = .domain_name <| Maybe.withDefault emptyAnalyticPageState model.pageState
filename = "/" ++ (Maybe.withDefault "" <| Maybe.map .name <| List.head model.files)
in
iwantacontainerwithpaddingplease
[ Grid.simpleRow
[ Grid.col []
[ h4 [] [text "Add files to root directory"]
]
]
, Grid.simpleRow
[ Grid.col [ Col.md6 ]
[ p [] [ royalchallenge "Upload files"]
, input [ type_ "file", FileReader.onFileChange AddFiles ] []
]
, Grid.col [ Col.md6 ]
[ p [] [ royalchallenge "File path"]
, Input.text
[ Input.defaultValue (domain ++ filename)
, Input.disabled True
]
]
]
]
-- Convince me that it isn't garbage and I shall change the function name
mygarbage : Model -> Html Msg
mygarbage model =
let
xs = (Maybe.withDefault [] <| Maybe.map (.analytic_settings >> .file_names) model.pageState)
domain = .domain_name <| Maybe.withDefault emptyAnalyticPageState model.pageState
show file =
ListGroup.li
[ ListGroup.light ]
[ a [ href ("//" ++ file ) ] [ text file ] ]
-- show file =
-- Grid.simpleRow
-- [ Grid.col [ Col.md11 ]
-- [ ListGroup.ul
-- [ ListGroup.li
-- [ ListGroup.light ]
-- [ a [ href ("//" ++ file ) ] [ text file ] ]
-- ]
-- ]
-- , Grid.col [ Col.md1, Col.middleMd ]
-- [ Button.button [ Button.attrs [ class "roundButton" ], Button.danger, Button.small ] [ text "x" ]
-- ]
-- ]
in
iwantacontainerwithpaddingplease
[ Grid.simpleRow
[ Grid.col []
[ h4 [] [text "Uploaded files"]
, case (List.isEmpty xs) of
True -> text "No files have been uploaded yet"
False -> ListGroup.ul
<| List.map (\x -> show <| domain ++ x) xs
]
]
]
-- Go, save the world instead.
destroy : Html Msg
destroy =
iwantacontainerwithpaddingplease
[ Grid.simpleRow
[ Grid.col []
[ Button.button [ Button.primary, Button.onClick Save ] [ text "Save" ]
]
]
]
-- Debate?
privacytheives : Model -> Html Msg
privacytheives model =
iwantacontainerwithpaddingplease
[ Grid.simpleRow
[ Grid.col [] [googleknowsallaboutyou model]
, Grid.col [] [markzuckerbergiswatchingyou]
]
]
-- You have to agree to this
thisisgoingtobethemostawesomeanalyticspagever : Model -> List (Html Msg)
thisisgoingtobethemostawesomeanalyticspagever model =
[ privacytheives model
, letusdrawalinenow
, globalheaderandfootersection model
, letusdrawalinenow
, customsnippetsection model
, letusdrawalinenow
, fillupmystorage model
, letusdrawalinenow
, mygarbage model
, letusdrawalinenow
, destroy
]
conditionallyRender model =
case model.pageState of
Nothing ->
Button.button [ Button.success, Button.disabled True ] [ text "Connect Google Account" ]
Just state ->
case state.google_integration_state of
Integrated ->
case state.analytic_settings.google_settings.tracking_id of
Nothing ->
Button.button
[ Button.success, Button.onClick (Reload "#/setup" (Ok True)) ]
[ text "Connect Google Account" ]
Just _ ->
Button.button
[ Button.success, Button.onClick (Reload "https://analytics.google.com" (Ok True)) ]
[ text "View Analytics Dashboard" ]
NotIntegrated ->
case state.consent_flow_link of
Just url ->
Button.button
[ Button.success, Button.onClick (Reload url (Ok True)) ]
[ text "Connect Google Account" ]
Nothing ->
Button.button
[ Button.success, Button.disabled True ]
[ text "Connect Google Account" ]
-- Helper UI functions
-- Saying please always helps
iwantacontainerwithpaddingplease : List (Html Msg) -> Html Msg
iwantacontainerwithpaddingplease = Grid.container [ style [("padding", "25px 0 10px")] ]
iwantacontainerwithlittlepaddingplease : List (Html Msg) -> Html Msg
iwantacontainerwithlittlepaddingplease = Grid.container [ style [("padding", "12px 0 8px")] ]
-- Make a "bold" move
royalchallenge : String -> Html Msg
royalchallenge str = strong [] [text str]
-- Blank lines are all I want
letusdrawalinenow : Html Msg
letusdrawalinenow = iwantacontainerwithpaddingplease [ hr [] [] ]
-- Otherwise they won't know what to do
defaultSnippetText : String
defaultSnippetText = """function sayhello() {
console.log(\"Hello, World!\");
}"""
defaultSnippet : ConvenientSnippet
defaultSnippet = ConvenientSnippet Header { condition = UMStartsWith "Hello", snippet = Just defaultSnippetText }
emptyAnalyticPageState : AnalyticPageState
emptyAnalyticPageState =
{ domain_name = "Domain Name"
, consent_flow_link = Nothing
, google_integration_state = NotIntegrated
, analytic_settings = emptyAnalyticSettings
}
emptyAnalyticSettings : AnalyticSettings
emptyAnalyticSettings =
{ google_settings = emptyGoogleSettings
, custom_js_snippets = emptyCustomJsSnippet
, file_names = []
}
emptyGoogleSettings : GoogleSettings
emptyGoogleSettings =
{ ecommerce_tracking = False
, tracking_id = Nothing
, website_name = Nothing
, website_url = Nothing
}
emptyCustomJsSnippet : CustomJsSnippets
emptyCustomJsSnippet =
{ common_header = Nothing
, common_footer = Nothing
, conditional_headers = []
, conditional_footers = []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment