diff --git a/README.md b/README.md new file mode 100644 index 0000000..c103bd7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Ulmus + +A proof of concept WordPress theme written in Elm. diff --git a/elm-package.json b/elm-package.json new file mode 100644 index 0000000..4ca8a0b --- /dev/null +++ b/elm-package.json @@ -0,0 +1,21 @@ +{ + "version": "1.0.0", + "summary": "helpful summary of your project, less than 80 characters", + "repository": "https://github.com/user/project.git", + "license": "BSD3", + "source-directories": [ + "src" + ], + "exposed-modules": [], + "native-modules": true, + "dependencies": { + "elm-lang/core": "5.0.0 <= v < 6.0.0", + "elm-lang/dom": "1.1.1 <= v < 2.0.0", + "elm-lang/html": "2.0.0 <= v < 3.0.0", + "elm-lang/http": "1.0.0 <= v < 2.0.0", + "elm-lang/navigation": "2.0.1 <= v < 3.0.0", + "evancz/elm-markdown": "3.0.1 <= v < 4.0.0", + "evancz/url-parser": "2.0.1 <= v < 3.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..f468738 --- /dev/null +++ b/functions.php @@ -0,0 +1,81 @@ + tag in the document head, and expect WordPress to + * provide it for us. + */ + add_theme_support( 'title-tag' ); + + /* + * Enable support for Post Thumbnails on posts and pages. + * + * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ + * + */ + add_theme_support( 'post-thumbnails' ); + } +endif; +add_action( 'after_setup_theme', 'ulmus_setup' ); + +/** + * Enqueue scripts and styles. + */ +function ulmus_scripts() { + wp_enqueue_style( 'ulmus-style', get_stylesheet_uri() ); + + wp_enqueue_script( 'ulmus-theme', get_template_directory_uri() . '/ulmus.js', array(), '20170118', true ); +} +add_action( 'wp_enqueue_scripts', 'ulmus_scripts' ); + +function get_post_data( $posts = null ) { + //if ( $posts === null && ! is_404() ) { + $posts = $GLOBALS['wp_query']->posts; + //} + //error_log( print_r( $GLOBALS['wp_query']->posts, true ) ); + global $wp_rest_server; + if ( empty( $wp_rest_server ) ) { + $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' ); + $wp_rest_server = new $wp_rest_server_class; + do_action( 'rest_api_init' ); + } + $data = array(); + $request = new \WP_REST_Request(); + $request['context'] = 'view'; + foreach ( (array) $posts as $post ) { + $controller = new \WP_REST_Posts_Controller( $post->post_type ); + $data[] = $wp_rest_server->response_to_data( $controller->prepare_item_for_response( $post, $request ), true ); + } + error_log( print_r( $data, true ) ); + return $data; +} + +add_action( 'wp_footer', 'get_post_data' ); diff --git a/index.php b/index.php new file mode 100644 index 0000000..cfb18b1 --- /dev/null +++ b/index.php @@ -0,0 +1,34 @@ + section and everything up until
+ * + * @link https://developer.wordpress.org/themes/basics/template-files/#template-partials + * + * @package Ulmus + */ + +?> +> + + + + + + + + +> + + + + + + + + diff --git a/src/Ulmus.elm b/src/Ulmus.elm new file mode 100644 index 0000000..100273d --- /dev/null +++ b/src/Ulmus.elm @@ -0,0 +1,247 @@ +port module Ulmus exposing (..) + +import Dom.Scroll exposing (toTop) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (onClick, onWithOptions) +import Http +import Json.Decode as Decode exposing (Decoder, field, at, succeed) +import Markdown +import Navigation +import UrlParser as Url exposing((), (), s, int, string, stringParam, top) + + +-- MODEL + +type alias Model = + { route : Route + , history : List (Maybe Route) + , posts : List Post + , havePosts : Bool + , apiUrl : String + } + + +type alias Post = + { id : Int + , date : String + , slug : String + , link : String + , content : String + , title : String + } + + +initialModel : Route -> Model +initialModel route = + { route = route + , history = [] + , posts = [] + , havePosts = False + , apiUrl = "" + } + +init : Navigation.Location -> ( Model, Cmd Msg ) +init location = + let + currentRoute = + parseLocation location + in + ( initialModel currentRoute, Cmd.none ) + + +-- URL PARSING + + +type Route + = Home + | BlogPost Int Int Int String + | NotFoundRoute + + +type alias PostRoute = { year : Int, month : Int, day : Int, slug : String } + + +rawPost : Url.Parser (Int -> Int -> Int -> String -> slug) slug +rawPost = + int int int string + + +route : Url.Parser (Route -> a) a +route = + Url.oneOf + [ Url.map Home top + , Url.map BlogPost rawPost + ] + + +parseLocation : Navigation.Location -> Route +parseLocation location = + case (Url.parsePath route location) of + Just route -> + route + + Nothing -> + NotFoundRoute + + +-- UPDATE + + +type Msg = + GetPosts (Result Http.Error (List Post)) + | ApiUrl (String) + | NewUrl String + | UrlChange Navigation.Location + | NoOp + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + GetPosts (Ok latestPosts) -> + ( { model | posts = latestPosts, havePosts = True }, Cmd.none ) + + GetPosts (Err error) -> + let + _ = Debug.log "Oops!" error + in + (model, Cmd.none) + + ApiUrl newApiUrl -> + ( { model | apiUrl = newApiUrl }, getPosts newApiUrl) + + NewUrl url -> + ( model + , Navigation.newUrl url + ) + + UrlChange location -> + let + newRoute = + parseLocation location + in + ( { model | route = newRoute }, Cmd.none ) + + NoOp -> + ( model, Cmd.none ) + + +-- DECODERS + +postDecoder : Decoder Post +postDecoder = + Decode.map6 Post + (field "id" Decode.int) + (field "date" Decode.string) + (field "slug" Decode.string) + (field "link" Decode.string) + (at ["content", "rendered"] Decode.string) + (at ["title", "rendered"] Decode.string) + + +-- COMMANDS + +getPosts : String -> Cmd Msg +getPosts apiUrl = + (Decode.list postDecoder) + |> Http.get (apiUrl ++ "posts") + |> Http.send GetPosts + + +onPrevDefClick : msg -> Attribute msg +onPrevDefClick message = + onWithOptions "click" { stopPropagation = True, preventDefault = True } (Decode.succeed message) + + +-- SUBSCRIPTIONS + +port apiUrl : (String -> msg) -> Sub msg + +subscriptions : Model -> Sub Msg +subscriptions model = + apiUrl ApiUrl + + +-- VIEW + +view : Model -> Html Msg +view model = + div [ id "page", class "site" ] + [ div [ class "content" ] + [ header [ id "masthead", class "site-header" ] + [ div [ class "site-branding" ] + [ h1 [ class "site-title" ] [ + a [ href "/", onPrevDefClick (NewUrl "/") ] + [ text "WordPress ♥ Elm ♥ REST API" ] + ] + ] + ] + , page model + ] + ] + +page : Model -> Html Msg +page model = + case model.route of + Home -> + viewPostList model.posts + + BlogPost year month day slug -> + viewSinglePost model slug + + NotFoundRoute -> + viewNotFound + + +viewPostList : List Post -> Html Msg +viewPostList posts = + let + listOfPosts = + List.map viewPost posts + in + div [] listOfPosts + + +viewSinglePost : Model -> String -> Html Msg +viewSinglePost model slug = + let + maybePost = + model.posts + |> List.filter (\post -> post.slug == slug) + |> List.head + in + case maybePost of + Just post -> + div [] [ viewPost post ] + + Nothing -> + viewNotFound + + +viewPost : Post -> Html Msg +viewPost post = + section [ class "post" ] + [ h2 [] [ + a [ href post.link, onPrevDefClick (NewUrl post.link) ] + [ text post.title ] + ] + , article [ class "content"] + [ Markdown.toHtml [] post.content ] + ] + + +viewNotFound : Html Msg +viewNotFound = + div [] + [ text "Not found" ] + + +main : Program Never Model Msg +main = + Navigation.program UrlChange + { init = init + , view = view + , update = update + , subscriptions = subscriptions + } diff --git a/style.css b/style.css new file mode 100644 index 0000000..5af5104 --- /dev/null +++ b/style.css @@ -0,0 +1,91 @@ +/* +Theme Name: Ulmus +Theme URI: http://underscores.me/ +Author: Jack Lenox +Author URI: http://jacklenox.com +Description: A WordPress theme built with Elm +Version: 1.0.0 +License: GNU General Public License v2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html +Text Domain: ulmus +Tags: + +This theme, like WordPress, is licensed under the GPL. +Use it to make something cool, have fun, and share what you've learned with others. + +Ulmus is based on Underscores http://underscores.me/, (C) 2012-2016 Automattic, Inc. +Underscores is distributed under the terms of the GNU GPL v2 or later. + +Normalizing styles have been helped along thanks to the fine work of +Nicolas Gallagher and Jonathan Neal http://necolas.github.com/normalize.css/ +*/ + +body { + font: 12pt/1.8 -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", sans-serif; + max-width: 720px; + padding: 60px 20px; + margin: 0 auto; + color: #1a1a1a; +} + +h1 { + font-size: 36pt; +} + +h1 a:link, h1 a:visited { + color: #1a1a1a; + text-decoration: none; +} + +h2 { + font-size: 28pt; + font-weight: 800; + margin: 0 0 20px 0; +} + +h2:before { + content: '#'; + display: inline-block; + width: 32px; + font-size: 24px; + position: relative; + left: -32px; + top: -2px; + margin-right: -32px; + color: #dddddd; +} + +p { + margin-bottom: 30px; +} + +a:link, +a:active { + color: #3d88f5; +} + +a:visited { + color: #5d9af7; +} + +img { + max-width: 100%; +} + +.avatar { + border-radius: 50%; + margin: 0 8px -6px 0; +} + +.post { + margin-bottom: 60px; + + footer { + margin: 40px 0 0 0; + } + + footer span { + display: inline-block; + margin-right: 20px; + } +}