Commits: 10
Show navigation bar in content only. WIP to show different navigation bar in home.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index d7e3485..9cc4789 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -166,8 +166,9 @@ view model =
= navigationBar =
= case Routes.parse model.location of
= Routes.Home ->
- -- TODO: Implement different navigation bar for home
- contentNavigationBar model
+ -- TODO: Implement fragment identifier navigation
+ -- homeNavigationBar
+ Element.none
=
= Routes.Content _ ->
= contentNavigationBar model
@@ -434,6 +435,35 @@ loadContent route =
= Cmd.none
=
=
+homeNavigationBar : Element Msg
+homeNavigationBar =
+ Element.row
+ [ Element.width Element.fill
+ , Element.padding 40
+ , Element.spacing 80
+ , Font.bold
+ , Background.color (Element.rgb 1 1 1)
+ ]
+ [ Element.link
+ [ Element.alignLeft
+ ]
+ { url = "/"
+ , label = Element.text "Home"
+ }
+ , Element.link
+ [ Element.alignRight
+ ]
+ { url = "#about-us"
+ , label = Element.text "About Us"
+ }
+ , Element.link
+ []
+ { url = "#contact"
+ , label = Element.text "Contact"
+ }
+ ]
+
+
=contentNavigationBar : Model -> Element Msg
=contentNavigationBar { location, scroll } =
= let
@@ -466,7 +496,7 @@ contentNavigationBar { location, scroll } =
= linkElement link =
= Element.link
= [ Element.width Element.fill
- , Element.padding 20
+ , Element.paddingEach { top = 40, right = 5, bottom = 20, left = 5 }
= ]
= { url = link.url
= , label = Element.el [ Element.centerX ] (Element.text link.label)Add support for Fold annotation to Editor
index 2553eb0..6c4cc3c 100644
--- a/src/Editor.elm
+++ b/src/Editor.elm
@@ -1,5 +1,6 @@
-module Editor exposing (Config, Highlight, defaults, editor)
+module Editor exposing (Annotation(..), Config, Region, defaults, editor)
=
+import Basics.Extra exposing (uncurry)
=import Browser
=import Dict exposing (Dict)
=import Element exposing (Element)
@@ -7,9 +8,11 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Extra as Element
=import Element.Font as Font
+import Element.Input as Input
=import FeatherIcons exposing (icons)
=import Html exposing (Html)
=import List.Extra as List
+import Set exposing (Set)
=import Svg exposing (Svg)
=import Svg.Attributes
=
@@ -41,34 +44,49 @@ defaults =
= }
=
=
-type alias Highlight =
- { from : Int
- , to : Int
- , offset : Int
+type Annotation
+ = None -- A dummy annotation to satisfy manyOf block. See https://github.com/mdgriffith/elm-markup/issues/12
+ | Highlight Region
+ | Fold Int Int
+
+
+type alias Region =
+ { top : Int
+ , left : Int
= , width : Int
+ , height : Int
= }
=
=
-highlight : Highlight -> Element msg
-highlight { from, to, offset, width } =
+highlight : Region -> Element msg
+highlight { top, left, width, height } =
= Element.row []
= [ " "
- |> String.repeat offset
+ |> String.repeat left
= |> Element.text
= , " "
= |> String.repeat width
- |> List.repeat (to - from + 1)
+ |> List.repeat height
= |> List.map Element.text
- |> List.map (Element.el [ Element.padding 10 ])
+ |> List.map (Element.el [ Element.paddingXY 0 10 ])
= |> Element.column
- [ Element.inFront
+ [ Element.behindContent
= (Element.el
= [ Element.width Element.fill
= , Element.height Element.fill
= , Border.color (Element.rgba 1 0 0 0.8)
- , Border.rounded 10
- , Border.width 2
- , Border.dashed
+ , -- Hand-drawn style borders. See https://codepen.io/tmrDevelops/pen/VeRvKX/
+ [ width, height ]
+ |> List.map ((*) 2)
+ |> List.map String.fromInt
+ |> List.map (\num -> num ++ "px")
+ |> List.repeat 2
+ |> List.concat
+ |> (\list -> [ list, List.reverse list ])
+ |> List.map (String.join " ")
+ |> String.join " / "
+ |> Element.css "border-radius"
+ , Border.width 3
= , Element.scale 1.1
= ]
= Element.none
@@ -84,91 +102,298 @@ highlight { from, to, offset, width } =
= ]
=
=
-editor : Config -> List Highlight -> String -> Element msg
-editor { path, offset, colors } highlights contents =
- let
- highlighted : Dict Int Highlight
- highlighted =
- highlights
- |> List.foldl extract Dict.empty
+type Fragment
+ = Folded Int
+ | Unfolded (List String)
=
- extract item memo =
- Dict.insert item.from item memo
- in
- Element.column
- [ Border.width 3
- , Border.rounded 5
- , Border.color colors.window
- , Element.css "page-break-inside" "avoid"
- , Font.family
- [ Font.typeface "Source Code Pro"
- , Font.monospace
- ]
- , Element.css "page-break-inside" "avoid"
- , Element.width Element.fill
- ]
- [ Element.row
- [ Element.width Element.fill
- , Background.color colors.window
- , Font.color colors.secondary
- ]
- [ FeatherIcons.fileText
- |> FeatherIcons.toHtml []
- |> Element.html
- |> Element.el
- [ Element.height (Element.px 35)
- , Element.padding 8
- ]
- , Element.el
+
+editor : Config -> List Annotation -> String -> Element msg
+editor { path, offset, colors } annotations code =
+ let
+ topBar =
+ Element.row
= [ Element.width Element.fill
- , Font.size 16
- , Font.family
- [ Font.typeface "Source Code Pro"
- , Font.monospace
+ , Background.color colors.window
+ , Font.color colors.secondary
+ ]
+ [ FeatherIcons.fileText
+ |> FeatherIcons.toHtml []
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 35)
+ , Element.padding 8
+ ]
+ , Element.el
+ [ Element.width Element.fill
+ , Font.size 16
+ , Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
+ ]
= ]
+ (Element.text path)
= ]
- (Element.text "src/Main.elm")
- ]
- , contents
- |> String.lines
- |> List.indexedMap
- (\n loc ->
- Element.row []
- [ Element.el
- [ Font.color colors.secondary
- , Font.extraLight
- , Element.width (Element.px 40)
- , Element.padding 10
- , Font.alignRight
- , Element.css "user-select" "none"
- , Element.css "-webkit-user-select" "none"
- , Element.css "-ms-user-select" "none"
- , Element.css "-webkit-touch-callout" "none"
- , Element.css "-o-user-select" "none"
- , Element.css "-moz-user-select" "none"
- ]
- ((n + 1)
- |> String.fromInt
+
+ contents : Element msg
+ contents =
+ code
+ |> String.lines
+ |> List.indexedMap Tuple.pair
+ |> extractFragments []
+ |> renderFragments 1 []
+ |> Element.column [ Element.width Element.fill ]
+
+ renderFragments :
+ Int
+ -> List (Element msg)
+ -> List Fragment
+ -> List (Element msg)
+ renderFragments start rendered fragments =
+ case fragments of
+ [] ->
+ -- We are done
+ List.reverse rendered
+
+ (Folded length) :: rest ->
+ let
+ lineNumbers =
+ [ start, start + length ]
+ |> List.map String.fromInt
+ |> String.join " - "
= |> Element.text
- )
- , Element.el
- [ Element.width Element.fill
- , Element.padding 10
- , highlighted
- |> Dict.get (n + 1)
- |> Maybe.map highlight
- |> Maybe.withDefault Element.none
- |> Element.inFront
- ]
- (Element.text loc)
- ]
- )
- |> Element.column
- [ Element.width Element.fill
- , Font.size 16
- , Element.scrollbarY
+ |> Element.el
+ [ Font.color colors.secondary
+ , Font.size 14
+ ]
+
+ button =
+ Input.button
+ [ Font.size 10
+ , Element.paddingXY 10 5
+ , Background.color colors.secondary
+ , Font.color colors.background
+ , Border.rounded 3
+ , Font.variant Font.smallCaps
+ ]
+ { onPress = Nothing
+ , label = Element.text "unfold"
+ }
+
+ shadow =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.innerShadow
+ { offset = ( 0, 3 )
+ , size = -6
+ , blur = 12
+ , color = colors.window
+ }
+ ]
+ Element.none
+
+ fold =
+ [ lineNumbers, button ]
+ |> Element.row
+ [ Element.spacing 20
+ , Element.padding 10
+ , Background.color colors.background
+ , Element.width Element.fill
+ ]
+ |> Element.el
+ [ Element.paddingXY 5 10
+ , Element.width Element.fill
+ , Element.paddingEach
+ { top = 0
+ , right = 1
+ , bottom = 0
+ , left = 1
+ }
+ , Background.color colors.window
+ , Element.inFront shadow
+ ]
+ in
+ fold
+ :: renderFragments (start + length) rendered rest
+
+ (Unfolded lines) :: rest ->
+ let
+ unfolded =
+ lines
+ |> List.indexedMap
+ (\n loc ->
+ Element.row []
+ [ Element.el
+ [ Font.color colors.secondary
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , Element.padding 10
+ , Font.alignRight
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ((n + start)
+ |> String.fromInt
+ |> Element.text
+ )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.padding 10
+ , highlighted
+ |> Dict.get (n + start)
+ |> Maybe.map highlight
+ |> Maybe.withDefault Element.none
+ |> Element.behindContent
+ ]
+ (Element.text loc)
+ ]
+ )
+ |> Element.column
+ [ Element.width Element.fill
+ , Font.size 16
+ , Element.scrollbarY
+ ]
+ in
+ unfolded
+ :: renderFragments (start + List.length lines) rendered rest
+
+ folded : Set Int
+ folded =
+ annotations
+ |> List.foldl folds []
+ |> List.map (uncurry List.range)
+ |> List.concat
+ |> Set.fromList
+
+ folds : Annotation -> List ( Int, Int ) -> List ( Int, Int )
+ folds item memo =
+ case item of
+ Fold start size ->
+ ( start, start + size ) :: memo
+
+ _ ->
+ memo
+
+ extractFragments :
+ List Fragment
+ -> List ( Int, String )
+ -> List Fragment
+ extractFragments accumulated lines =
+ case lines of
+ [] ->
+ -- We have reached the end of the code
+ case accumulated of
+ [] ->
+ -- It was empty. Add one empty line.
+ [ Unfolded [ "" ] ]
+
+ (Folded n) :: _ ->
+ List.reverse accumulated
+
+ (Unfolded linesReversed) :: previous ->
+ List.reverse
+ (Unfolded (List.reverse linesReversed)
+ :: previous
+ )
+
+ ( n, line ) :: rest ->
+ let
+ isFolded =
+ Set.member (n + 1) folded
+ in
+ case accumulated of
+ [] ->
+ -- It's the first fragment. Check if it's folded or not.
+ if isFolded then
+ extractFragments [ Folded 1 ] rest
+
+ else
+ extractFragments [ Unfolded [ line ] ] rest
+
+ (Folded length) :: previous ->
+ if isFolded then
+ -- Continue the fold
+ let
+ this =
+ Folded (length + 1)
+ in
+ extractFragments
+ (this :: previous)
+ rest
+
+ else
+ -- Fold is finished. Start a new unfolded fragment.
+ let
+ this =
+ Folded length
+
+ next =
+ Unfolded [ line ]
+ in
+ extractFragments
+ (next :: this :: previous)
+ rest
+
+ (Unfolded linesReversed) :: previous ->
+ if isFolded then
+ -- Unfolded fragment is finished. Reverse the lines and start a new fold.
+ let
+ this =
+ Unfolded (List.reverse linesReversed)
+
+ next =
+ Folded 1
+ in
+ extractFragments
+ (next
+ :: this
+ :: previous
+ )
+ rest
+
+ else
+ -- Continue the unfolded fragment
+ let
+ this =
+ Unfolded (line :: linesReversed)
+ in
+ extractFragments
+ (this :: previous)
+ rest
+
+ highlighted : Dict Int Region
+ highlighted =
+ annotations
+ |> List.foldl regions Dict.empty
+
+ regions : Annotation -> Dict Int Region -> Dict Int Region
+ regions item memo =
+ case item of
+ Highlight region ->
+ Dict.insert region.top region memo
+
+ _ ->
+ memo
+ in
+ [ topBar
+ , contents
+ ]
+ |> Element.column
+ [ Border.width 3
+ , Border.rounded 5
+ , Border.color colors.window
+ , Element.css "page-break-inside" "avoid"
+ , Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
= ]
- ]
+ , Element.css "page-break-inside" "avoid"
+ , Element.width Element.fill
+ ]
=
=
=main : Html msg
@@ -285,10 +510,11 @@ dot age color rotation =
= []
="""
= |> editor defaults
- [ Highlight 10 16 0 38
- , Highlight 3 3 0 15
- , Highlight 19 19 0 10
- , Highlight 20 20 15 10
+ [ Highlight { top = 10, left = 0, width = 38, height = 7 }
+ , Highlight { top = 3, left = 0, width = 15, height = 1 }
+ , Highlight { top = 19, left = 0, width = 10, height = 1 }
+ , Highlight { top = 20, left = 15, width = 10, height = 1 }
+ , Fold 35 11
= ]
= |> Element.layout
= [ Element.width Element.fillindex a96ce20..7185019 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -85,8 +85,14 @@ monospace =
=editor : Mark.Block (a -> Element msg)
=editor =
= let
- render contents highlights model =
- Editor.editor Editor.defaults highlights contents
+ render : List Editor.Annotation -> String -> model -> Element msg
+ render annotations_ contents model =
+ Editor.editor Editor.defaults annotations_ contents
+
+ annotations =
+ Mark.block "Annotations"
+ identity
+ (Mark.manyOf [ none, highlight, fold ])
=
= code : Mark.Block String
= code =
@@ -94,20 +100,32 @@ editor =
= identity
= Mark.multiline
=
- highlight : Mark.Block Editor.Highlight
+ none : Mark.Block Editor.Annotation
+ none =
+ Mark.stub "None" Editor.None
+
+ highlight : Mark.Block Editor.Annotation
= highlight =
= Mark.record4 "Highlight"
- Editor.Highlight
- (Mark.field "from" Mark.int)
- (Mark.field "to" Mark.int)
- (Mark.field "offset" Mark.int)
+ Editor.Region
+ (Mark.field "top" Mark.int)
+ (Mark.field "left" Mark.int)
= (Mark.field "width" Mark.int)
+ (Mark.field "height" Mark.int)
+ |> Mark.map Editor.Highlight
+
+ fold : Mark.Block Editor.Annotation
+ fold =
+ Mark.record2 "Fold"
+ Editor.Fold
+ (Mark.field "start" Mark.int)
+ (Mark.field "length" Mark.int)
= in
= Mark.block "Editor"
= identity
= (Mark.startWith render
+ annotations
= code
- (Mark.manyOf [ highlight ])
= )
=
=Make navigation bar respond to viewport width changes.
Use icons when viewport is < 600px.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 9cc4789..87fee45 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -8,7 +8,8 @@ It fetches the Elm Markup file at /index.txt and renders it. There are a number
=
=import Basics.Extra exposing (curry)
=import Browser
-import Browser.Dom as Dom
+import Browser.Dom as Dom exposing (Viewport)
+import Browser.Events
=import Browser.Navigation as Navigation
=import BrowserWindow
=import Dict
@@ -74,6 +75,7 @@ type alias Model =
= , content : Content
= , examples : Examples.Model
= , scroll : Float
+ , viewport : { width : Int, height : Int }
= }
=
=
@@ -84,6 +86,7 @@ type Msg
= | ContentFetched (Result Http.Error String)
= | ExamplesMsg Examples.Msg
= | Scroll Float
+ | ViewportMeasured { width : Int, height : Int }
=
=
=type Content
@@ -117,12 +120,22 @@ init flags url key =
= , key = key
= , content = Loading
= , examples = examplesModel
- , scroll = 1
+ , scroll = 0
+ , viewport =
+ { width = 800, height = 600 }
= }
= , Cmd.batch
= [ url
= |> Routes.parse
= |> loadContent
+ , Dom.getViewport
+ |> Task.map
+ (\{ viewport } ->
+ { width = round viewport.width
+ , height = round viewport.height
+ }
+ )
+ |> Task.perform ViewportMeasured
= , Cmd.map ExamplesMsg examplesCmd
= ]
= )
@@ -408,12 +421,23 @@ update msg model =
= , Cmd.none
= )
=
+ ViewportMeasured viewport ->
+ ( { model | viewport = viewport }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- model.examples
- |> Examples.subscriptions
- |> Sub.map ExamplesMsg
+ Sub.batch
+ [ model.examples
+ |> Examples.subscriptions
+ |> Sub.map ExamplesMsg
+ , Browser.Events.onResize
+ (\width height ->
+ ViewportMeasured { width = width, height = height }
+ )
+ ]
=
=
=loadContent : Route -> Cmd Msg
@@ -465,41 +489,53 @@ homeNavigationBar =
=
=
=contentNavigationBar : Model -> Element Msg
-contentNavigationBar { location, scroll } =
+contentNavigationBar { location, scroll, viewport } =
= let
- links : List { url : String, label : String }
+ links : List { url : String, label : String, icon : Element msg }
= links =
= [ { url = "http://localhost:8001"
= , label = "Home"
+ , icon = FeatherIcons.home |> FeatherIcons.toHtml [] |> Element.html
= }
= , { url = "/preparation.html"
= , label = "Get ready"
+ , icon = FeatherIcons.bookOpen |> FeatherIcons.toHtml [] |> Element.html
= }
= , { url = "/day-1.html"
= , label = "Day 1"
+ , icon = Element.text "1"
= }
= , { url = "/day-2.html"
= , label = "Day 2"
+ , icon = Element.text "2"
= }
= , { url = "/day-3.html"
= , label = "Day 3"
+ , icon = Element.text "3"
= }
= , { url = "/day-4.html"
= , label = "Day 4"
+ , icon = Element.text "4"
= }
= , { url = "/day-5.html"
= , label = "Day 5"
+ , icon = Element.text "5"
= }
= ]
=
- linkElement : { url : String, label : String } -> Element Msg
+ linkElement : { url : String, label : String, icon : Element Msg } -> Element Msg
= linkElement link =
= Element.link
= [ Element.width Element.fill
= , Element.paddingEach { top = 40, right = 5, bottom = 20, left = 5 }
= ]
= { url = link.url
- , label = Element.el [ Element.centerX ] (Element.text link.label)
+ , label =
+ if viewport.width < 600 then
+ Element.el [ Element.centerX ] link.icon
+
+ else
+ Element.el [ Element.centerX ] (Element.text link.label)
= }
=
= linksRow =Split the code from Editor module into Examples.Editor and Mark.Custom
index 6c4cc3c..a25c922 100644
--- a/src/Editor.elm
+++ b/src/Editor.elm
@@ -1,7 +1,13 @@
-module Editor exposing (Annotation(..), Config, Region, defaults, editor)
+module Editor exposing
+ ( Annotations
+ , Colors
+ , Config
+ , Region
+ , defaults
+ , editor
+ )
=
=import Basics.Extra exposing (uncurry)
-import Browser
=import Dict exposing (Dict)
=import Element exposing (Element)
=import Element.Background as Background
@@ -10,46 +16,39 @@ import Element.Extra as Element
=import Element.Font as Font
=import Element.Input as Input
=import FeatherIcons exposing (icons)
-import Html exposing (Html)
=import List.Extra as List
=import Set exposing (Set)
-import Svg exposing (Svg)
=import Svg.Attributes
=
=
=type alias Config =
= { path : String
- , offset : Int
- , colors :
- { annotations : Element.Color
- , background : Element.Color
- , primary : Element.Color
- , secondary : Element.Color
- , window : Element.Color
- }
+ , colors : Colors
+ }
+
+
+type alias Colors =
+ { annotations : Element.Color
+ , background : Element.Color
+ , primary : Element.Color
+ , secondary : Element.Color
+ , window : Element.Color
= }
=
=
=defaults : Config
=defaults =
= { path = "src/Main.elm"
- , offset = 1
= , colors =
= { primary = Element.rgb 0 0 0
= , secondary = Element.rgb 0.8 0.8 0.8
- , annotations = Element.rgb 1 0.6 0.6
+ , annotations = Element.rgb 0.7 0 0
= , background = Element.rgb 1 1 1
= , window = Element.rgb 0.2 0.2 0.2
= }
= }
=
=
-type Annotation
- = None -- A dummy annotation to satisfy manyOf block. See https://github.com/mdgriffith/elm-markup/issues/12
- | Highlight Region
- | Fold Int Int
-
-
=type alias Region =
= { top : Int
= , left : Int
@@ -58,57 +57,19 @@ type alias Region =
= }
=
=
-highlight : Region -> Element msg
-highlight { top, left, width, height } =
- Element.row []
- [ " "
- |> String.repeat left
- |> Element.text
- , " "
- |> String.repeat width
- |> List.repeat height
- |> List.map Element.text
- |> List.map (Element.el [ Element.paddingXY 0 10 ])
- |> Element.column
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
- , Border.color (Element.rgba 1 0 0 0.8)
- , -- Hand-drawn style borders. See https://codepen.io/tmrDevelops/pen/VeRvKX/
- [ width, height ]
- |> List.map ((*) 2)
- |> List.map String.fromInt
- |> List.map (\num -> num ++ "px")
- |> List.repeat 2
- |> List.concat
- |> (\list -> [ list, List.reverse list ])
- |> List.map (String.join " ")
- |> String.join " / "
- |> Element.css "border-radius"
- , Border.width 3
- , Element.scale 1.1
- ]
- Element.none
- )
- , Element.css "pointer-events" "none"
- , Element.css "user-select" "none"
- , Element.css "-webkit-user-select" "none"
- , Element.css "-ms-user-select" "none"
- , Element.css "-webkit-touch-callout" "none"
- , Element.css "-o-user-select" "none"
- , Element.css "-moz-user-select" "none"
- ]
- ]
-
-
=type Fragment
= = Folded Int
= | Unfolded (List String)
=
=
-editor : Config -> List Annotation -> String -> Element msg
-editor { path, offset, colors } annotations code =
+type alias Annotations =
+ { highlights : List Region
+ , folded : Set Int
+ }
+
+
+editor : Config -> Annotations -> String -> Element msg
+editor { path, colors } { highlights, folded } code =
= let
= topBar =
= Element.row
@@ -139,9 +100,12 @@ editor { path, offset, colors } annotations code =
= code
= |> String.lines
= |> List.indexedMap Tuple.pair
- |> extractFragments []
+ |> extractFragments folded []
= |> renderFragments 1 []
- |> Element.column [ Element.width Element.fill ]
+ |> Element.column
+ [ Element.width Element.fill
+ , Element.scrollbarX
+ ]
=
= renderFragments :
= Int
@@ -155,229 +119,17 @@ editor { path, offset, colors } annotations code =
= List.reverse rendered
=
= (Folded length) :: rest ->
- let
- lineNumbers =
- [ start, start + length ]
- |> List.map String.fromInt
- |> String.join " - "
- |> Element.text
- |> Element.el
- [ Font.color colors.secondary
- , Font.size 14
- ]
-
- button =
- Input.button
- [ Font.size 10
- , Element.paddingXY 10 5
- , Background.color colors.secondary
- , Font.color colors.background
- , Border.rounded 3
- , Font.variant Font.smallCaps
- ]
- { onPress = Nothing
- , label = Element.text "unfold"
- }
-
- shadow =
- Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
- , Border.innerShadow
- { offset = ( 0, 3 )
- , size = -6
- , blur = 12
- , color = colors.window
- }
- ]
- Element.none
-
- fold =
- [ lineNumbers, button ]
- |> Element.row
- [ Element.spacing 20
- , Element.padding 10
- , Background.color colors.background
- , Element.width Element.fill
- ]
- |> Element.el
- [ Element.paddingXY 5 10
- , Element.width Element.fill
- , Element.paddingEach
- { top = 0
- , right = 1
- , bottom = 0
- , left = 1
- }
- , Background.color colors.window
- , Element.inFront shadow
- ]
- in
- fold
+ fold colors start (length + start - 1)
= :: renderFragments (start + length) rendered rest
=
= (Unfolded lines) :: rest ->
- let
- unfolded =
- lines
- |> List.indexedMap
- (\n loc ->
- Element.row []
- [ Element.el
- [ Font.color colors.secondary
- , Font.extraLight
- , Element.width (Element.px 40)
- , Element.padding 10
- , Font.alignRight
- , Element.css "user-select" "none"
- , Element.css "-webkit-user-select" "none"
- , Element.css "-ms-user-select" "none"
- , Element.css "-webkit-touch-callout" "none"
- , Element.css "-o-user-select" "none"
- , Element.css "-moz-user-select" "none"
- ]
- ((n + start)
- |> String.fromInt
- |> Element.text
- )
- , Element.el
- [ Element.width Element.fill
- , Element.padding 10
- , highlighted
- |> Dict.get (n + start)
- |> Maybe.map highlight
- |> Maybe.withDefault Element.none
- |> Element.behindContent
- ]
- (Element.text loc)
- ]
- )
- |> Element.column
- [ Element.width Element.fill
- , Font.size 16
- , Element.scrollbarY
- ]
- in
- unfolded
+ unfolded colors highlighted start lines
= :: renderFragments (start + List.length lines) rendered rest
=
- folded : Set Int
- folded =
- annotations
- |> List.foldl folds []
- |> List.map (uncurry List.range)
- |> List.concat
- |> Set.fromList
-
- folds : Annotation -> List ( Int, Int ) -> List ( Int, Int )
- folds item memo =
- case item of
- Fold start size ->
- ( start, start + size ) :: memo
-
- _ ->
- memo
-
- extractFragments :
- List Fragment
- -> List ( Int, String )
- -> List Fragment
- extractFragments accumulated lines =
- case lines of
- [] ->
- -- We have reached the end of the code
- case accumulated of
- [] ->
- -- It was empty. Add one empty line.
- [ Unfolded [ "" ] ]
-
- (Folded n) :: _ ->
- List.reverse accumulated
-
- (Unfolded linesReversed) :: previous ->
- List.reverse
- (Unfolded (List.reverse linesReversed)
- :: previous
- )
-
- ( n, line ) :: rest ->
- let
- isFolded =
- Set.member (n + 1) folded
- in
- case accumulated of
- [] ->
- -- It's the first fragment. Check if it's folded or not.
- if isFolded then
- extractFragments [ Folded 1 ] rest
-
- else
- extractFragments [ Unfolded [ line ] ] rest
-
- (Folded length) :: previous ->
- if isFolded then
- -- Continue the fold
- let
- this =
- Folded (length + 1)
- in
- extractFragments
- (this :: previous)
- rest
-
- else
- -- Fold is finished. Start a new unfolded fragment.
- let
- this =
- Folded length
-
- next =
- Unfolded [ line ]
- in
- extractFragments
- (next :: this :: previous)
- rest
-
- (Unfolded linesReversed) :: previous ->
- if isFolded then
- -- Unfolded fragment is finished. Reverse the lines and start a new fold.
- let
- this =
- Unfolded (List.reverse linesReversed)
-
- next =
- Folded 1
- in
- extractFragments
- (next
- :: this
- :: previous
- )
- rest
-
- else
- -- Continue the unfolded fragment
- let
- this =
- Unfolded (line :: linesReversed)
- in
- extractFragments
- (this :: previous)
- rest
-
- highlighted : Dict Int Region
= highlighted =
- annotations
- |> List.foldl regions Dict.empty
-
- regions : Annotation -> Dict Int Region -> Dict Int Region
- regions item memo =
- case item of
- Highlight region ->
- Dict.insert region.top region memo
-
- _ ->
- memo
+ highlights
+ |> List.zip (List.map .top highlights)
+ |> Dict.fromList
= in
= [ topBar
= , contents
@@ -396,127 +148,240 @@ editor { path, offset, colors } annotations code =
= ]
=
=
-main : Html msg
-main =
- """module Main exposing (main)
-
-import Browser
-import Dict
-import Element
-import Svg exposing (Svg)
-import Svg.Attributes
-
-
-main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- }
+highlight : Element.Color -> Region -> Element msg
+highlight color { top, left, width, height } =
+ Element.row []
+ [ " "
+ |> String.repeat left
+ |> Element.text
+ , " "
+ |> String.repeat width
+ |> List.repeat height
+ |> List.map Element.text
+ |> List.map (Element.el [ Element.paddingXY 0 10 ])
+ |> Element.column
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.color color
+ , -- Hand-drawn style borders. See https://codepen.io/tmrDevelops/pen/VeRvKX/
+ [ width, height ]
+ |> List.map ((*) 2)
+ |> List.map String.fromInt
+ |> List.map (\num -> num ++ "px")
+ |> List.repeat 2
+ |> List.concat
+ |> (\list -> [ list, List.reverse list ])
+ |> List.map (String.join " ")
+ |> String.join " / "
+ |> Element.css "border-radius"
+ , Border.width 5
+ , Element.scale 1.1
+ , Element.alpha 0.7
+ ]
+ Element.none
+ )
+ , Element.css "pointer-events" "none"
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ]
=
=
-view age =
- [ segment (age / 5000) { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+fold : Colors -> Int -> Int -> Element msg
+fold colors start end =
+ let
+ lineNumbers =
+ [ start, end ]
+ |> List.map String.fromInt
+ |> String.join " - "
+ |> Element.text
+ |> Element.el
+ [ Font.color colors.secondary
+ , Font.size 14
+ ]
=
+ button =
+ Input.button
+ [ Font.size 10
+ , Element.paddingXY 10 5
+ , Background.color colors.secondary
+ , Font.color colors.background
+ , Border.rounded 3
+ , Font.variant Font.smallCaps
+ ]
+ { onPress = Nothing
+ , label = Element.text "unfold"
+ }
=
-rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "brown", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
+ shadow =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.innerShadow
+ { offset = ( 0, 3 )
+ , size = -6
+ , blur = 12
+ , color = colors.window
+ }
+ ]
+ Element.none
+ in
+ [ lineNumbers, button ]
+ |> Element.row
+ [ Element.spacing 20
+ , Element.padding 10
+ , Background.color colors.background
+ , Element.width Element.fill
= ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
+ |> Element.el
+ [ Element.paddingXY 5 10
+ , Element.width Element.fill
+ , Element.paddingEach
+ { top = 0
+ , right = 1
+ , bottom = 0
+ , left = 1
+ }
+ , Background.color colors.window
+ , Element.inFront shadow
= ]
=
=
-segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
-
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
+unfolded : Colors -> Dict Int Region -> Int -> List String -> Element msg
+unfolded colors highlighted start lines =
+ lines
+ |> List.indexedMap
+ (\n loc ->
+ Element.row []
+ [ Element.el
+ [ Font.color colors.secondary
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , Element.padding 10
+ , Font.alignRight
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ((n + start)
+ |> String.fromInt
+ |> Element.text
= )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.padding 10
+ , highlighted
+ |> Dict.get (n + start)
+ |> Maybe.map (highlight colors.annotations)
+ |> Maybe.withDefault Element.none
+ |> Element.behindContent
+ ]
+ (Element.text loc)
= ]
- , dot age color rotation
- , line age color rotation
- ]
-
-
-dot age color rotation =
- Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- []
-
-
- line age color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 (String.fromFloat (age * 10))
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
= )
- ]
- []
-"""
- |> editor defaults
- [ Highlight { top = 10, left = 0, width = 38, height = 7 }
- , Highlight { top = 3, left = 0, width = 15, height = 1 }
- , Highlight { top = 19, left = 0, width = 10, height = 1 }
- , Highlight { top = 20, left = 15, width = 10, height = 1 }
- , Fold 35 11
- ]
- |> Element.layout
+ |> Element.column
= [ Element.width Element.fill
- , Element.height Element.fill
+ , Font.size 16
+ , Element.clipY
= ]
+
+
+extractFragments :
+ Set Int
+ -> List Fragment
+ -> List ( Int, String )
+ -> List Fragment
+extractFragments folded accumulated lines =
+ case lines of
+ [] ->
+ -- We have reached the end of the code
+ case accumulated of
+ [] ->
+ -- It was empty. Add one empty line.
+ [ Unfolded [ "" ] ]
+
+ (Folded n) :: _ ->
+ List.reverse accumulated
+
+ (Unfolded linesReversed) :: previous ->
+ List.reverse
+ (Unfolded (List.reverse linesReversed)
+ :: previous
+ )
+
+ ( n, line ) :: rest ->
+ let
+ isFolded =
+ Set.member (n + 1) folded
+ in
+ case accumulated of
+ [] ->
+ -- It's the first fragment. Check if it's folded or not.
+ if isFolded then
+ extractFragments folded
+ [ Folded 1 ]
+ rest
+
+ else
+ extractFragments folded
+ [ Unfolded [ line ] ]
+ rest
+
+ (Folded length) :: previous ->
+ if isFolded then
+ -- Continue the fold
+ let
+ this =
+ Folded (length + 1)
+ in
+ extractFragments folded
+ (this :: previous)
+ rest
+
+ else
+ -- Fold is finished. Start a new unfolded fragment.
+ let
+ this =
+ Folded length
+
+ next =
+ Unfolded [ line ]
+ in
+ extractFragments folded
+ (next :: this :: previous)
+ rest
+
+ (Unfolded linesReversed) :: previous ->
+ if isFolded then
+ -- Unfolded fragment is finished. Reverse the lines and start a new fold.
+ let
+ this =
+ Unfolded (List.reverse linesReversed)
+
+ next =
+ Folded 1
+ in
+ extractFragments
+ folded
+ (next :: this :: previous)
+ rest
+
+ else
+ -- Continue the unfolded fragment
+ let
+ this =
+ Unfolded (line :: linesReversed)
+ in
+ extractFragments
+ folded
+ (this :: previous)
+ restnew file mode 100644
index 0000000..51fdac0
--- /dev/null
+++ b/src/Examples/Editor.elm
@@ -0,0 +1,64 @@
+module Examples.Editor exposing (main)
+
+import Editor exposing (editor)
+import Element exposing (Element)
+import Html exposing (Html)
+import Set exposing (Set)
+
+
+main : Html msg
+main =
+ """This is an example of how Editor module can be used.
+
+It supports highligs. Lines can be folded, like this
+
+This code is folded
+
+So is this.
+
+Highlights can span over multiple lines. Try:
+
+"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum."
+ |> editor Editor.defaults
+ { highlights =
+ [ { top = 10, left = 1, width = 40, height = 7 }
+ , { top = 3, left = 1, width = 15, height = 1 }
+ , { top = 19, left = 1, width = 10, height = 1 }
+ , { top = 20, left = 15, width = 12, height = 1 }
+ ]
+ , folded =
+ [ List.range 4 6
+ , List.range 74 91
+ ]
+ |> List.concat
+ |> Set.fromList
+ }
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+"""
+ |> editor Editor.defaults
+ { highlights =
+ [ { top = 3, left = 13, width = 8, height = 1 }
+ , { top = 19, left = 15, width = 45, height = 4 }
+ ]
+ , folded =
+ [ List.range 5 7
+ , List.range 74 91
+ , List.range 20 21
+ ]
+ |> List.concat
+ |> Set.fromList
+ }
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]index 7185019..07cf136 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -29,6 +29,7 @@ import Html exposing (Html)
=import Html.Attributes
=import Mark
=import Mark.Default
+import Set
=
=
=title : Mark.Block String
@@ -82,42 +83,91 @@ monospace =
= ]
=
=
+
+-- TODO: Separate to Mark.Editor ?
+
+
+type Annotation
+ = None
+ | Highlight Int Int Int Int
+ | Fold Int Int
+
+
=editor : Mark.Block (a -> Element msg)
=editor =
= let
- render : List Editor.Annotation -> String -> model -> Element msg
+ render : Editor.Annotations -> String -> model -> Element msg
= render annotations_ contents model =
= Editor.editor Editor.defaults annotations_ contents
=
= annotations =
= Mark.block "Annotations"
- identity
+ (extractAnnotations { highlights = [], folded = Set.empty })
= (Mark.manyOf [ none, highlight, fold ])
=
+ extractAnnotations : Editor.Annotations -> List Annotation -> Editor.Annotations
+ extractAnnotations ({ highlights, folded } as extracted) remaining =
+ case remaining of
+ [] ->
+ { extracted
+ | highlights = List.reverse highlights
+ }
+
+ None :: rest ->
+ extractAnnotations
+ { highlights = highlights
+ , folded = folded
+ }
+ rest
+
+ (Highlight top left width height) :: rest ->
+ extractAnnotations
+ { extracted
+ | highlights =
+ { top = top
+ , left = left
+ , width = width
+ , height = height
+ }
+ :: highlights
+ }
+ rest
+
+ (Fold start length) :: rest ->
+ extractAnnotations
+ { extracted
+ | folded =
+ (start + length)
+ |> List.range start
+ |> Set.fromList
+ |> Set.union folded
+ }
+ rest
+
= code : Mark.Block String
= code =
= Mark.block "Code"
= identity
= Mark.multiline
=
- none : Mark.Block Editor.Annotation
+ none : Mark.Block Annotation
= none =
- Mark.stub "None" Editor.None
+ -- A dummy annotation to satisfy manyOf block. See https://github.com/mdgriffith/elm-markup/issues/12
+ Mark.stub "None" None
=
- highlight : Mark.Block Editor.Annotation
+ highlight : Mark.Block Annotation
= highlight =
= Mark.record4 "Highlight"
- Editor.Region
+ Highlight
= (Mark.field "top" Mark.int)
= (Mark.field "left" Mark.int)
= (Mark.field "width" Mark.int)
= (Mark.field "height" Mark.int)
- |> Mark.map Editor.Highlight
=
- fold : Mark.Block Editor.Annotation
+ fold : Mark.Block Annotation
= fold =
= Mark.record2 "Fold"
- Editor.Fold
+ Fold
= (Mark.field "start" Mark.int)
= (Mark.field "length" Mark.int)
= inFix the fold over highlight in the Editor example
index 51fdac0..bbfb394 100644
--- a/src/Examples/Editor.elm
+++ b/src/Examples/Editor.elm
@@ -52,8 +52,7 @@ culpa qui officia deserunt mollit anim id est laborum."
= ]
= , folded =
= [ List.range 5 7
- , List.range 74 91
- , List.range 20 21
+ , List.range 12 14
= ]
= |> List.concat
= |> Set.fromListDisable CI when not on master branch
index 8d347ad..4472935 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,5 +13,5 @@ pages:
= paths:
= - public
=
- # only:
- # - master
+ only:
+ - masterMerge branch 'content' into about-fana
Merge branch 'content' into navigation
Add anchor icon to Fana's bio.
index c2939da..2547076 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -29,4 +29,4 @@ I love the creativity of the software development and hope to share that passion
=
=*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
=
-*Fana* is a junior software developer (an ex marine biologist) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
+*Fana* is a junior software developer (an ex marine biologist {Icon|name=anchor}) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.Fix home link in content navigation bar
index 87fee45..471756e 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -493,7 +493,7 @@ contentNavigationBar { location, scroll, viewport } =
= let
= links : List { url : String, label : String, icon : Element msg }
= links =
- [ { url = "http://localhost:8001"
+ [ { url = "/"
= , label = "Home"
= , icon = FeatherIcons.home |> FeatherIcons.toHtml [] |> Element.html
= }