Merge branch 'f-content' into 'master'
feat: Add basic content management system See merge request mythic-insight/legendary!5
This commit is contained in:
commit
5a629a3860
136 changed files with 2073 additions and 501 deletions
|
@ -1,9 +1,15 @@
|
||||||
image: "elixir:1.10"
|
image: "elixir:1.10"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- _build
|
||||||
|
- deps
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
POSTGRES_PASSWORD: "postgres"
|
POSTGRES_PASSWORD: "postgres"
|
||||||
POSTGRES_USER: "postgres"
|
POSTGRES_USER: "postgres"
|
||||||
DATABASE_URL: "postgres"
|
DATABASE_URL: "postgres"
|
||||||
|
MIX_ENV: "test"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: postgres:12
|
- name: postgres:12
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
defmodule Auth.Users.User do
|
defmodule Auth.User do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
The baseline user schema module.
|
The baseline user schema module.
|
||||||
"""
|
"""
|
||||||
|
@ -13,6 +13,8 @@ defmodule Auth.Users.User do
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field :roles, {:array, :string}
|
field :roles, {:array, :string}
|
||||||
|
field :display_name, :string
|
||||||
|
field :homepage_url, :string
|
||||||
|
|
||||||
pow_user_fields()
|
pow_user_fields()
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule Mix.Tasks.Legendary.CreateAdmin do
|
||||||
"""
|
"""
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
alias Auth.Users.User
|
alias Auth.User
|
||||||
alias Auth.Repo
|
alias Auth.Repo
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Auth.Repo.Migrations.AddNicenameToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add :display_name, :string, default: nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Auth.Repo.Migrations.AddUrlToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add :homepage_url, :string, default: nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
2
apps/content/.gitignore
vendored
2
apps/content/.gitignore
vendored
|
@ -20,7 +20,7 @@ erl_crash.dump
|
||||||
*.ez
|
*.ez
|
||||||
|
|
||||||
# Ignore package tarball (built via "mix hex.build").
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
auth_web-*.tar
|
content-*.tar
|
||||||
|
|
||||||
# If NPM crashes, it generates a log, let's ignore it too.
|
# If NPM crashes, it generates a log, let's ignore it too.
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
@ -18,7 +18,7 @@ config :logger, :console,
|
||||||
|
|
||||||
config :content, Content.Scheduler,
|
config :content, Content.Scheduler,
|
||||||
jobs: [
|
jobs: [
|
||||||
{"@hourly", {ContentWeb.Sitemaps, :generate, []}}
|
{"@hourly", {Content.Sitemaps, :generate, []}}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
|
|
|
@ -4,7 +4,7 @@ use Mix.Config
|
||||||
config :content, Content.Repo,
|
config :content, Content.Repo,
|
||||||
username: "postgres",
|
username: "postgres",
|
||||||
password: "postgres",
|
password: "postgres",
|
||||||
database: "content_dev",
|
database: "legendary_dev",
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
show_sensitive_data_on_connection_error: true,
|
show_sensitive_data_on_connection_error: true,
|
||||||
pool_size: 10
|
pool_size: 10
|
||||||
|
@ -15,7 +15,7 @@ config :content, Content.Repo,
|
||||||
# The watchers configuration can be used to run external
|
# The watchers configuration can be used to run external
|
||||||
# watchers to your application. For example, we use it
|
# watchers to your application. For example, we use it
|
||||||
# with webpack to recompile .js and .css sources.
|
# with webpack to recompile .js and .css sources.
|
||||||
config :content, ContentWeb.Endpoint,
|
config :content, Content.Endpoint,
|
||||||
http: [port: 4000],
|
http: [port: 4000],
|
||||||
debug_errors: true,
|
debug_errors: true,
|
||||||
code_reloader: true,
|
code_reloader: true,
|
||||||
|
@ -55,7 +55,7 @@ config :content, ContentWeb.Endpoint,
|
||||||
# different ports.
|
# different ports.
|
||||||
|
|
||||||
# Watch static and templates for browser reloading.
|
# Watch static and templates for browser reloading.
|
||||||
config :content, ContentWeb.Endpoint,
|
config :content, Content.Endpoint,
|
||||||
live_reload: [
|
live_reload: [
|
||||||
patterns: [
|
patterns: [
|
||||||
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||||
|
|
|
@ -2,11 +2,3 @@ use Mix.Config
|
||||||
|
|
||||||
# Print only warnings and errors during test
|
# Print only warnings and errors during test
|
||||||
config :logger, level: :warn
|
config :logger, level: :warn
|
||||||
|
|
||||||
config :content, Content.Repo,
|
|
||||||
username: "postgres",
|
|
||||||
password: "postgres",
|
|
||||||
database: "content_test#{System.get_env("MIX_TEST_PARTITION")}",
|
|
||||||
hostname: System.get_env("DATABASE_URL") || "localhost",
|
|
||||||
show_sensitive_data_on_connection_error: true,
|
|
||||||
pool: Ecto.Adapters.SQL.Sandbox
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Content.Application do
|
defmodule Content.Application do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
The base module of the CMS application.
|
The base module of the Content application.
|
||||||
"""
|
"""
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ defmodule Content.Application do
|
||||||
# Start your own worker by calling: Content.Worker.start_link(arg1, arg2, arg3)
|
# Start your own worker by calling: Content.Worker.start_link(arg1, arg2, arg3)
|
||||||
# worker(Content.Worker, [arg1, arg2, arg3]),
|
# worker(Content.Worker, [arg1, arg2, arg3]),
|
||||||
worker(Content.Scheduler, []),
|
worker(Content.Scheduler, []),
|
||||||
|
Content.Telemetry,
|
||||||
|
Content.Endpoint,
|
||||||
]
|
]
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
@ -28,7 +30,7 @@ defmodule Content.Application do
|
||||||
# Tell Phoenix to update the endpoint configuration
|
# Tell Phoenix to update the endpoint configuration
|
||||||
# whenever the application is updated.
|
# whenever the application is updated.
|
||||||
def config_change(changed, _new, removed) do
|
def config_change(changed, _new, removed) do
|
||||||
Endpoint.config_change(changed, removed)
|
Content.Endpoint.config_change(changed, removed)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -37,7 +37,7 @@ defmodule Content.Post do
|
||||||
has_many :categories, through: [:term_relationships, :category, :term]
|
has_many :categories, through: [:term_relationships, :category, :term]
|
||||||
has_many :tags, through: [:term_relationships, :tag, :term]
|
has_many :tags, through: [:term_relationships, :tag, :term]
|
||||||
has_one :post_format, through: [:term_relationships, :post_format, :term]
|
has_one :post_format, through: [:term_relationships, :post_format, :term]
|
||||||
belongs_to :author, Content.User, foreign_key: :post_author, references: :ID
|
belongs_to :author, Auth.User, foreign_key: :post_author
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, params \\ %{}) do
|
def changeset(struct, params \\ %{}) do
|
||||||
|
@ -123,14 +123,14 @@ defmodule Content.Post do
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_put_guid(changeset) do
|
def maybe_put_guid(changeset) do
|
||||||
import ContentWeb.Router.Helpers, only: [posts_url: 3]
|
import Content.Router.Helpers, only: [posts_url: 3]
|
||||||
slug = changeset |> get_field(:post_name)
|
slug = changeset |> get_field(:post_name)
|
||||||
|
|
||||||
case slug do
|
case slug do
|
||||||
nil -> changeset
|
nil -> changeset
|
||||||
_ ->
|
_ ->
|
||||||
changeset
|
changeset
|
||||||
|> put_default(:guid, posts_url(ContentWeb.Endpoint, :show, slug))
|
|> put_default(:guid, posts_url(CoreWeb.Endpoint, :show, slug))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -159,6 +159,18 @@ defmodule Content.Posts do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def get_posts!(slug) do
|
def get_posts!(slug) do
|
||||||
|
slug
|
||||||
|
|> get_post_scope()
|
||||||
|
|> Repo.one!()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_post(slug) do
|
||||||
|
slug
|
||||||
|
|> get_post_scope()
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_post_scope(slug) do
|
||||||
id_filter = fn scope, id ->
|
id_filter = fn scope, id ->
|
||||||
|
|
||||||
case Integer.parse(id, 10) do
|
case Integer.parse(id, 10) do
|
||||||
|
@ -172,7 +184,6 @@ defmodule Content.Posts do
|
||||||
post_scope()
|
post_scope()
|
||||||
|> where([p], p.post_type != "nav_menu_item")
|
|> where([p], p.post_type != "nav_menu_item")
|
||||||
|> id_filter.(slug)
|
|> id_filter.(slug)
|
||||||
|> Repo.one!()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
|
@ -1,12 +1,12 @@
|
||||||
defmodule ContentWeb do
|
defmodule Content do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
The entrypoint for defining your web interface, such
|
The entrypoint for defining your web interface, such
|
||||||
as controllers, views, channels and so on.
|
as controllers, views, channels and so on.
|
||||||
|
|
||||||
This can be used in your application as:
|
This can be used in your application as:
|
||||||
|
|
||||||
use ContentWeb, :controller
|
use Content, :controller
|
||||||
use ContentWeb, :view
|
use Content, :view
|
||||||
|
|
||||||
The definitions below will be executed for every view,
|
The definitions below will be executed for every view,
|
||||||
controller, etc, so keep them short and clean, focused
|
controller, etc, so keep them short and clean, focused
|
||||||
|
@ -19,11 +19,11 @@ defmodule ContentWeb do
|
||||||
|
|
||||||
def controller do
|
def controller do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.Controller, namespace: ContentWeb
|
use Phoenix.Controller, namespace: Content
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import ContentWeb.Gettext
|
import Content.Gettext
|
||||||
alias ContentWeb.Router.Helpers, as: Routes
|
alias Content.Router.Helpers, as: Routes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,13 +31,21 @@ defmodule ContentWeb do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.View,
|
use Phoenix.View,
|
||||||
root: "lib/content_web/templates",
|
root: "lib/content_web/templates",
|
||||||
namespace: ContentWeb,
|
namespace: Content,
|
||||||
pattern: "**/*"
|
pattern: "**/*"
|
||||||
|
|
||||||
|
use PhoenixHtmlSanitizer, :basic_html
|
||||||
|
|
||||||
# Import convenience functions from controllers
|
# Import convenience functions from controllers
|
||||||
import Phoenix.Controller,
|
import Phoenix.Controller,
|
||||||
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
||||||
|
|
||||||
|
def process_content(text) do
|
||||||
|
text
|
||||||
|
|> Content.Shortcodes.expand_shortcodes()
|
||||||
|
|> Earmark.as_html!()
|
||||||
|
end
|
||||||
|
|
||||||
# Include shared imports and aliases for views
|
# Include shared imports and aliases for views
|
||||||
unquote(view_helpers())
|
unquote(view_helpers())
|
||||||
end
|
end
|
||||||
|
@ -55,7 +63,7 @@ defmodule ContentWeb do
|
||||||
def channel do
|
def channel do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.Channel
|
use Phoenix.Channel
|
||||||
import ContentWeb.Gettext
|
import Content.Gettext
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,9 +75,9 @@ defmodule ContentWeb do
|
||||||
# Import basic rendering functionality (render, render_layout, etc)
|
# Import basic rendering functionality (render, render_layout, etc)
|
||||||
import Phoenix.View
|
import Phoenix.View
|
||||||
|
|
||||||
import ContentWeb.ErrorHelpers
|
import Content.ErrorHelpers
|
||||||
import ContentWeb.Gettext
|
import Content.Gettext
|
||||||
alias ContentWeb.Router.Helpers, as: Routes
|
alias Content.Router.Helpers, as: Routes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
defmodule ContentWeb.UserSocket do
|
defmodule Content.UserSocket do
|
||||||
use Phoenix.Socket
|
use Phoenix.Socket
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
# channel "room:*", ContentWeb.RoomChannel
|
# channel "room:*", Content.RoomChannel
|
||||||
|
|
||||||
# Socket params are passed from the client and can
|
# Socket params are passed from the client and can
|
||||||
# be used to verify and authenticate a user. After
|
# be used to verify and authenticate a user. After
|
||||||
|
@ -27,7 +27,7 @@ defmodule ContentWeb.UserSocket do
|
||||||
# Would allow you to broadcast a "disconnect" event and terminate
|
# Would allow you to broadcast a "disconnect" event and terminate
|
||||||
# all active sockets and channels for a given user:
|
# all active sockets and channels for a given user:
|
||||||
#
|
#
|
||||||
# ContentWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
|
# Content.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
|
||||||
#
|
#
|
||||||
# Returning `nil` makes this socket anonymous.
|
# Returning `nil` makes this socket anonymous.
|
||||||
@impl true
|
@impl true
|
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Content.AdminHomeController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
render conn, "index.html"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Content.AdminPostsController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
alias Content.Posts
|
||||||
|
|
||||||
|
require Ecto.Query
|
||||||
|
|
||||||
|
def index(conn, %{"page" => page, "post_type" => post_type}) do
|
||||||
|
posts = Posts.list_admin_posts(page, post_type)
|
||||||
|
thumbs = posts |> Posts.thumbs_for_posts()
|
||||||
|
last_page = Posts.last_page
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
"index.html",
|
||||||
|
[
|
||||||
|
posts: posts,
|
||||||
|
page: String.to_integer(page),
|
||||||
|
last_page: last_page,
|
||||||
|
thumbs: thumbs,
|
||||||
|
post_type: post_type,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
def index(conn, %{"page" => page} = params), do: index(conn, params |> Map.merge(%{"page" => page, "post_type" => "post"}))
|
||||||
|
def index(conn, %{"post_type" => post_type} = params), do: index(conn, params |> Map.merge(%{"page" => "1", "post_type" => post_type}))
|
||||||
|
def index(conn, params), do: index(conn, params |> Map.merge(%{"page" => "1", "post_type" => "post"}))
|
||||||
|
end
|
|
@ -0,0 +1,71 @@
|
||||||
|
defmodule Content.CommentController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
alias Content
|
||||||
|
alias Content.Comments
|
||||||
|
alias Content.Post
|
||||||
|
alias Content.Repo
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def create(conn, %{"comment" => comment_params}) do
|
||||||
|
comment_params = comment_params |> Map.merge(%{"comment_author_IP" => to_string(:inet_parse.ntoa(conn.remote_ip))})
|
||||||
|
|
||||||
|
case Comments.create_comment(comment_params) do
|
||||||
|
{:ok, comment} ->
|
||||||
|
post =
|
||||||
|
Post
|
||||||
|
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Comment created successfully.")
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :show, post))
|
||||||
|
{:error, _} ->
|
||||||
|
post =
|
||||||
|
Post
|
||||||
|
|> where([p], p.'ID' == ^comment_params["comment_post_ID"])
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :show, post))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, %{"id" => id, "comment" => comment_params}) do
|
||||||
|
comment = Comments.get_comment!(id)
|
||||||
|
|
||||||
|
case Comments.update_comment(comment, comment_params) do
|
||||||
|
{:ok, comment} ->
|
||||||
|
post =
|
||||||
|
Post
|
||||||
|
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Comment updated successfully.")
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :show, post))
|
||||||
|
{:error, _} ->
|
||||||
|
post =
|
||||||
|
Post
|
||||||
|
|> where([p], p.'ID' == ^comment_params["comment_post_ID"])
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :show, post))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, %{"id" => id}) do
|
||||||
|
comment = Comments.get_comment!(id)
|
||||||
|
{:ok, comment} = Comments.delete_comment(comment)
|
||||||
|
post =
|
||||||
|
Post
|
||||||
|
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Comment deleted successfully.")
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :show, post))
|
||||||
|
end
|
||||||
|
end
|
33
apps/content/lib/content_web/controllers/feeds_controller.ex
Normal file
33
apps/content/lib/content_web/controllers/feeds_controller.ex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule Content.FeedsController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
alias Content.{Posts}
|
||||||
|
|
||||||
|
plug :put_layout, false when action in [:preview]
|
||||||
|
|
||||||
|
def index(conn, params) do
|
||||||
|
category = params |> Map.get("category")
|
||||||
|
posts = Posts.list_posts(params)
|
||||||
|
thumbs = posts |> Posts.thumbs_for_posts()
|
||||||
|
|
||||||
|
feed_url =
|
||||||
|
case category do
|
||||||
|
nil ->
|
||||||
|
Routes.index_feed_path(conn, :index)
|
||||||
|
_ ->
|
||||||
|
Routes.category_feed_path(conn, :index, category)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("text/xml")
|
||||||
|
|> render(
|
||||||
|
"index.rss",
|
||||||
|
[
|
||||||
|
posts: posts,
|
||||||
|
thumbs: thumbs,
|
||||||
|
category: category,
|
||||||
|
feed_url: feed_url,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
39
apps/content/lib/content_web/controllers/menus_controller.ex
Normal file
39
apps/content/lib/content_web/controllers/menus_controller.ex
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
defmodule Content.MenusController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
alias Content.Repo
|
||||||
|
|
||||||
|
def edit(conn, %{"id" => id}) do
|
||||||
|
menu = id |> Content.Menu.get_menu_from_id()
|
||||||
|
posts =
|
||||||
|
Content.Posts.post_scope
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(fn post ->
|
||||||
|
post |> Map.take([:ID, :post_title, :post_name])
|
||||||
|
end)
|
||||||
|
categories =
|
||||||
|
Content.Terms.categories
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(fn cat ->
|
||||||
|
cat |> Map.take([:name, :slug, :term_group, :term_id])
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> render(
|
||||||
|
"edit.html",
|
||||||
|
[
|
||||||
|
id: id,
|
||||||
|
menu: menu,
|
||||||
|
posts: posts,
|
||||||
|
categories: categories,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, %{"id" => id, "menu" => menu}) do
|
||||||
|
Content.UpdateMenu.run(id, menu |> Phoenix.json_library().decode!())
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> redirect(to: Routes.menus_path(conn, :edit, id))
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,5 @@
|
||||||
defmodule ContentWeb.PageController do
|
defmodule Content.PageController do
|
||||||
use ContentWeb, :controller
|
use Content, :controller
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
render(conn, "index.html")
|
render(conn, "index.html")
|
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Content.PostPasswordController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
def create(conn, %{"post_password" => post_password}) do
|
||||||
|
conn = put_session(conn, "post_password", post_password)
|
||||||
|
|
||||||
|
redirect_path =
|
||||||
|
if Enum.count(get_req_header(conn, "referer")) > 0 do
|
||||||
|
conn
|
||||||
|
|> get_req_header("referer")
|
||||||
|
|> Enum.at(0)
|
||||||
|
|> URI.parse()
|
||||||
|
|> (&(&1.path)).()
|
||||||
|
else
|
||||||
|
"/"
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect conn, to: redirect_path
|
||||||
|
end
|
||||||
|
end
|
176
apps/content/lib/content_web/controllers/posts_controller.ex
Normal file
176
apps/content/lib/content_web/controllers/posts_controller.ex
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
defmodule Content.PostsController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
alias Auth.User
|
||||||
|
alias Content.{Options, Post, Posts, Repo}
|
||||||
|
|
||||||
|
plug :put_layout, false when action in [:preview]
|
||||||
|
|
||||||
|
def index(conn, params) do
|
||||||
|
show_on_front = Options.get_value("show_on_front") || "posts"
|
||||||
|
|
||||||
|
case show_on_front do
|
||||||
|
"posts" ->
|
||||||
|
conn |> index_posts(params)
|
||||||
|
"page" ->
|
||||||
|
conn |> index_page(params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_posts(conn, params) do
|
||||||
|
page = params |> Map.get("page", "1")
|
||||||
|
params = params |> Map.merge(%{"page" => page})
|
||||||
|
category = params |> Map.get("category")
|
||||||
|
posts = Posts.list_posts(params)
|
||||||
|
thumbs = posts |> Posts.thumbs_for_posts()
|
||||||
|
last_page = Posts.last_page(params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> render(
|
||||||
|
"index.html",
|
||||||
|
[
|
||||||
|
posts: posts,
|
||||||
|
page: String.to_integer(page),
|
||||||
|
last_page: last_page,
|
||||||
|
thumbs: thumbs,
|
||||||
|
category: category,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_page(conn, _params) do
|
||||||
|
page_id = Options.get("page_on_front") |> (&(&1.option_value)).()
|
||||||
|
|
||||||
|
post = Posts.get_posts!(page_id)
|
||||||
|
page = String.to_integer("1")
|
||||||
|
thumbs = [post] |> Posts.thumbs_for_posts()
|
||||||
|
render(conn, "front.html", post: post, page: page, thumbs: thumbs)
|
||||||
|
end
|
||||||
|
|
||||||
|
def new(conn, params) do
|
||||||
|
changeset = Posts.change_posts(%Post{})
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
"new.html",
|
||||||
|
changeset: changeset,
|
||||||
|
post_type: params["post_type"] || "post",
|
||||||
|
author_options: User |> Repo.all()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"post" => post_params}) do
|
||||||
|
case Posts.create_posts(post_params) do
|
||||||
|
{:ok, post} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Posts created successfully.")
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :show, post))
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
"new.html",
|
||||||
|
changeset: changeset,
|
||||||
|
post_type: post_params["post_type"] || "post",
|
||||||
|
author_options: User |> Repo.all()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def preview(conn, %{"post" => post_params}) do
|
||||||
|
post = Posts.preview_post(post_params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> render("show.html", post: post, page: 1, thumbs: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(conn, %{"id" => id, "page" => page_string}) do
|
||||||
|
{page_id_for_posts, _} = Options.get_value_as_int("page_for_posts")
|
||||||
|
|
||||||
|
post = Posts.get_post(id)
|
||||||
|
|
||||||
|
if is_nil(post) do
|
||||||
|
try_static_post(conn, id)
|
||||||
|
else
|
||||||
|
if post.'ID' == page_id_for_posts do
|
||||||
|
conn |> index_posts(%{"id" => id, "page" => page_string})
|
||||||
|
else
|
||||||
|
conn |> show_one(post, page_string)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "page" => "1"})
|
||||||
|
|
||||||
|
defp try_static_post(conn, id) do
|
||||||
|
try do
|
||||||
|
render(conn, "static_pages/#{id}.html")
|
||||||
|
rescue
|
||||||
|
Phoenix.Template.UndefinedError ->
|
||||||
|
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: Content.Router)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_one(conn, post, page_string) do
|
||||||
|
{front_page_id, _} = Options.get_value_as_int("page_on_front")
|
||||||
|
|
||||||
|
template =
|
||||||
|
if post.'ID' == front_page_id do
|
||||||
|
"front.html"
|
||||||
|
else
|
||||||
|
"show.html"
|
||||||
|
end
|
||||||
|
|
||||||
|
page = String.to_integer(page_string)
|
||||||
|
thumbs = [post] |> Posts.thumbs_for_posts()
|
||||||
|
case post.post_type do
|
||||||
|
"attachment" ->
|
||||||
|
{:ok, decoded} = post.post_content |> Base.decode64
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type(post.post_mime_type, "binary")
|
||||||
|
|> send_resp(conn.status || 200, decoded)
|
||||||
|
_ ->
|
||||||
|
render(conn, template, post: post, page: page, thumbs: thumbs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit(conn, %{"id" => id}) do
|
||||||
|
posts = Posts.get_post_with_drafts!(id)
|
||||||
|
changeset = Posts.change_posts(posts)
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
"edit.html",
|
||||||
|
posts: posts,
|
||||||
|
changeset: changeset,
|
||||||
|
post_type: posts.post_type || "post",
|
||||||
|
author_options: User |> Repo.all()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, %{"id" => id, "post" => posts_params}) do
|
||||||
|
posts = Posts.get_post_with_drafts!(id)
|
||||||
|
|
||||||
|
case Posts.update_posts(posts, posts_params) do
|
||||||
|
{:ok, posts} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Posts updated successfully.")
|
||||||
|
|> redirect(to: Routes.posts_path(conn, :edit, posts))
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
"edit.html",
|
||||||
|
posts: posts,
|
||||||
|
changeset: changeset,
|
||||||
|
post_type: posts.post_type || "post",
|
||||||
|
author_options: User |> Repo.all()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, %{"id" => id}) do
|
||||||
|
posts = Posts.get_post_with_drafts!(id)
|
||||||
|
{:ok, _posts} = Posts.delete_posts(posts)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Posts deleted successfully.")
|
||||||
|
|> redirect(to: Routes.admin_posts_path(conn, :index))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Content.SitemapController do
|
||||||
|
use Content, :controller
|
||||||
|
|
||||||
|
alias Content.{Posts, Repo, Terms}
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
posts =
|
||||||
|
Posts.post_scope
|
||||||
|
|> where([p], p.post_type not in ["nav_menu_item", "attachment"])
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
categories =
|
||||||
|
Terms.categories
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
render(conn, "index.html", conn: conn, posts: posts, categories: categories)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,5 @@
|
||||||
defmodule ContentWeb.Endpoint do
|
defmodule Content.Endpoint do
|
||||||
use Phoenix.Endpoint, otp_app: :content_web
|
use Phoenix.Endpoint, otp_app: :content
|
||||||
|
|
||||||
# The session will be stored in the cookie and signed,
|
# The session will be stored in the cookie and signed,
|
||||||
# this means its contents can be read but not tampered with.
|
# this means its contents can be read but not tampered with.
|
||||||
|
@ -10,7 +10,7 @@ defmodule ContentWeb.Endpoint do
|
||||||
signing_salt: "wfYQp84C"
|
signing_salt: "wfYQp84C"
|
||||||
]
|
]
|
||||||
|
|
||||||
socket "/socket", ContentWeb.UserSocket,
|
socket "/socket", Content.UserSocket,
|
||||||
websocket: true,
|
websocket: true,
|
||||||
longpoll: false
|
longpoll: false
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ defmodule ContentWeb.Endpoint do
|
||||||
# when deploying your static files in production.
|
# when deploying your static files in production.
|
||||||
plug Plug.Static,
|
plug Plug.Static,
|
||||||
at: "/",
|
at: "/",
|
||||||
from: :content_web,
|
from: :content,
|
||||||
gzip: false,
|
gzip: false,
|
||||||
only: ~w(css fonts images js favicon.ico robots.txt)
|
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ defmodule ContentWeb.Endpoint do
|
||||||
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
||||||
plug Phoenix.LiveReloader
|
plug Phoenix.LiveReloader
|
||||||
plug Phoenix.CodeReloader
|
plug Phoenix.CodeReloader
|
||||||
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :content_web
|
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :content
|
||||||
end
|
end
|
||||||
|
|
||||||
plug Phoenix.LiveDashboard.RequestLogger,
|
plug Phoenix.LiveDashboard.RequestLogger,
|
||||||
|
@ -50,6 +50,6 @@ defmodule ContentWeb.Endpoint do
|
||||||
plug Plug.MethodOverride
|
plug Plug.MethodOverride
|
||||||
plug Plug.Head
|
plug Plug.Head
|
||||||
plug Plug.Session, @session_options
|
plug Plug.Session, @session_options
|
||||||
plug Pow.Plug.Session, otp_app: :content_web
|
plug Pow.Plug.Session, otp_app: :content
|
||||||
plug ContentWeb.Router
|
plug Content.Router
|
||||||
end
|
end
|
|
@ -1,11 +1,11 @@
|
||||||
defmodule ContentWeb.Gettext do
|
defmodule Content.Gettext do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
A module providing Internationalization with a gettext-based API.
|
A module providing Internationalization with a gettext-based API.
|
||||||
|
|
||||||
By using [Gettext](https://hexdocs.pm/gettext),
|
By using [Gettext](https://hexdocs.pm/gettext),
|
||||||
your module gains a set of macros for translations, for example:
|
your module gains a set of macros for translations, for example:
|
||||||
|
|
||||||
import ContentWeb.Gettext
|
import Content.Gettext
|
||||||
|
|
||||||
# Simple translation
|
# Simple translation
|
||||||
gettext("Here is the string to translate")
|
gettext("Here is the string to translate")
|
||||||
|
@ -20,5 +20,5 @@ defmodule ContentWeb.Gettext do
|
||||||
|
|
||||||
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||||
"""
|
"""
|
||||||
use Gettext, otp_app: :content_web
|
use Gettext, otp_app: :content
|
||||||
end
|
end
|
36
apps/content/lib/content_web/plugs/load_user.ex
Normal file
36
apps/content/lib/content_web/plugs/load_user.ex
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
defmodule Content.LoadUser do
|
||||||
|
@moduledoc """
|
||||||
|
Loads user into connection if user has session
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
alias Content.Users
|
||||||
|
|
||||||
|
def init(opts) do
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
handle_auth(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_auth(conn) do
|
||||||
|
user_id = get_session(conn, :user_id)
|
||||||
|
|
||||||
|
if user = (user_id && Users.get_user(user_id)) do
|
||||||
|
build_conn(conn, user)
|
||||||
|
else
|
||||||
|
build_conn(conn, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_conn(conn, nil) do
|
||||||
|
conn
|
||||||
|
|> assign(:current_user, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_conn(conn, user) do
|
||||||
|
conn
|
||||||
|
|> assign(:current_user, user)
|
||||||
|
end
|
||||||
|
end
|
22
apps/content/lib/content_web/plugs/require_admin.ex
Normal file
22
apps/content/lib/content_web/plugs/require_admin.ex
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
defmodule Content.RequireAdmin do
|
||||||
|
@moduledoc """
|
||||||
|
A plug that returns 403 unauthorized if the user is not an admin. Used
|
||||||
|
to block out logged-in-only routes.
|
||||||
|
"""
|
||||||
|
import Plug.Conn
|
||||||
|
alias Auth.User
|
||||||
|
|
||||||
|
def init(opts) do
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
if conn.assigns[:current_user] && User.is_admin?(conn.assigns[:current_user]) do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> send_resp(403, "Unauthorized")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
22
apps/content/lib/content_web/plugs/require_auth.ex
Normal file
22
apps/content/lib/content_web/plugs/require_auth.ex
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
defmodule Content.RequireAuth do
|
||||||
|
@moduledoc """
|
||||||
|
A plug that returns 403 unauthorized if the user is not authenticated. Used
|
||||||
|
to block out logged-in-only routes.
|
||||||
|
"""
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
def init(opts) do
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
case conn.assigns[:current_user] do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> send_resp(403, "Unauthorized")
|
||||||
|
|> halt()
|
||||||
|
_user ->
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
72
apps/content/lib/content_web/router.ex
Normal file
72
apps/content/lib/content_web/router.ex
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
defmodule Content.Router do
|
||||||
|
use Content, :router
|
||||||
|
alias Content.{RequireAdmin, RequireAuth}
|
||||||
|
|
||||||
|
pipeline :browser do
|
||||||
|
plug :accepts, ["html"]
|
||||||
|
plug :fetch_session
|
||||||
|
plug :fetch_flash
|
||||||
|
plug :protect_from_forgery
|
||||||
|
plug :put_secure_browser_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
plug :accepts, ["json"]
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :feed do
|
||||||
|
plug :accepts, ["rss"]
|
||||||
|
plug :fetch_session
|
||||||
|
plug :protect_from_forgery
|
||||||
|
plug :put_secure_browser_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :require_auth do
|
||||||
|
plug(RequireAuth)
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :require_admin do
|
||||||
|
plug(RequireAdmin)
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :admin_layout do
|
||||||
|
plug :put_layout, {Content.LayoutView, :admin}
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", Content do
|
||||||
|
pipe_through([:browser, :require_auth, :require_admin, :admin_layout])
|
||||||
|
|
||||||
|
get "/wp-admin/", AdminHomeController, :index
|
||||||
|
get "/posts", AdminPostsController, :index, as: :admin_posts
|
||||||
|
post "/posts", PostsController, :create
|
||||||
|
get "/posts/new", PostsController, :new
|
||||||
|
put "/posts/preview", PostsController, :preview
|
||||||
|
post "/posts/preview", PostsController, :preview
|
||||||
|
get "/posts/:id/edit", PostsController, :edit
|
||||||
|
put "/posts/:id", PostsController, :update
|
||||||
|
delete "/posts/:id", PostsController, :delete
|
||||||
|
get "/menus/:id/edit", MenusController, :edit
|
||||||
|
put "/menus/:id", MenusController, :update
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", Content do
|
||||||
|
pipe_through :feed # Use the default browser stack
|
||||||
|
|
||||||
|
get "/category/:category/feed.rss", FeedsController, :index, as: :category_feed
|
||||||
|
get "/feed.rss", FeedsController, :index, as: :index_feed
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", Content do
|
||||||
|
pipe_through :browser # Use the default browser stack
|
||||||
|
|
||||||
|
resources "/comments", CommentController, as: :comment, only: [:create, :delete, :update]
|
||||||
|
get "/page/:page", PostsController, :index_posts, as: :blog_page
|
||||||
|
get "/category/:category", PostsController, :index_posts, as: :category
|
||||||
|
get "/category/:category/page/:page", PostsController, :index, as: :category_page
|
||||||
|
post "/wp-login.php", PostPasswordController, :create
|
||||||
|
get "/", PostsController, :index
|
||||||
|
resources "/sitemap", SitemapController, only: [:index]
|
||||||
|
get "/:id", PostsController, :show
|
||||||
|
get "/:id/:page", PostsController, :show, as: :paged_post
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
defmodule ContentWeb.Telemetry do
|
defmodule Content.Telemetry do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Collects metrics for the application and allows them to be transmitted using the Telemetry framework.
|
Collects metrics for the application and allows them to be transmitted using the Telemetry framework.
|
||||||
"""
|
"""
|
||||||
|
@ -34,11 +34,11 @@ defmodule ContentWeb.Telemetry do
|
||||||
),
|
),
|
||||||
|
|
||||||
# Database Metrics
|
# Database Metrics
|
||||||
summary("content_web.repo.query.total_time", unit: {:native, :millisecond}),
|
summary("content.repo.query.total_time", unit: {:native, :millisecond}),
|
||||||
summary("content_web.repo.query.decode_time", unit: {:native, :millisecond}),
|
summary("content.repo.query.decode_time", unit: {:native, :millisecond}),
|
||||||
summary("content_web.repo.query.query_time", unit: {:native, :millisecond}),
|
summary("content.repo.query.query_time", unit: {:native, :millisecond}),
|
||||||
summary("content_web.repo.query.queue_time", unit: {:native, :millisecond}),
|
summary("content.repo.query.queue_time", unit: {:native, :millisecond}),
|
||||||
summary("content_web.repo.query.idle_time", unit: {:native, :millisecond}),
|
summary("content.repo.query.idle_time", unit: {:native, :millisecond}),
|
||||||
|
|
||||||
# VM Metrics
|
# VM Metrics
|
||||||
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
||||||
|
@ -52,7 +52,7 @@ defmodule ContentWeb.Telemetry do
|
||||||
[
|
[
|
||||||
# A module, function and arguments to be invoked periodically.
|
# A module, function and arguments to be invoked periodically.
|
||||||
# This function must call :telemetry.execute/3 and a metric must be added above.
|
# This function must call :telemetry.execute/3 and a metric must be added above.
|
||||||
# {ContentWeb, :count_users, []}
|
# {Content, :count_users, []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>Dashboard</h1>
|
|
@ -0,0 +1,93 @@
|
||||||
|
<div class="grid">
|
||||||
|
<div class="column">
|
||||||
|
<h1>
|
||||||
|
<%= humanize(@post_type) %>s
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="column admin-actions">
|
||||||
|
<%= link "New " <> humanize(@post_type), to: Routes.posts_path(@conn, :new, post_type: @post_type), class: "button" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="admin-table small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Categories</th>
|
||||||
|
<th>Tags</th>
|
||||||
|
<th>Comments</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for post <- @posts do %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<%= link post.post_title, to: Routes.posts_path(@conn, :edit, post) %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= if !is_nil(post.author) do %>
|
||||||
|
<%= post.author.display_name %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= post.categories |> Enum.map(&(&1.name)) |> Enum.join(", ") %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= post.tags |> Enum.map(&(&1.name)) |> Enum.join(", ") %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= post.comments |> Enum.count() %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= case post.post_status do %>
|
||||||
|
<% "publish" -> %>
|
||||||
|
<%= "Published" %>
|
||||||
|
<% "future" -> %>
|
||||||
|
<%= "Scheduled" %>
|
||||||
|
<% "draft" -> %>
|
||||||
|
<%= "Last Modified" %>
|
||||||
|
<% "pending" -> %>
|
||||||
|
<%= "Scheduled" %>
|
||||||
|
<% "private" -> %>
|
||||||
|
<%= "Published Privately" %>
|
||||||
|
<% "inherit" -> %>
|
||||||
|
<%= "Inherit" %>
|
||||||
|
<% end %>
|
||||||
|
<div>
|
||||||
|
<%= post.post_date |> Timex.format!("%F", :strftime) %>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<%= if @page > 1 do %>
|
||||||
|
<%= link 1, to: Routes.admin_posts_path(@conn, :index, page: 1) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page > 3 do %>
|
||||||
|
...
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page > 2 do %>
|
||||||
|
<%= link @page - 1, to: Routes.admin_posts_path(@conn, :index, page: @page - 1) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= @page %>
|
||||||
|
|
||||||
|
<%= if @page + 1 < @last_page do %>
|
||||||
|
<%= link @page + 1, to: Routes.admin_posts_path(@conn, :index, page: @page + 1) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page + 2 < @last_page do %>
|
||||||
|
...
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page < @last_page do %>
|
||||||
|
<%= link @last_page, to: Routes.admin_posts_path(@conn, :index, page: @last_page) %>
|
||||||
|
<% end %>
|
24
apps/content/lib/content_web/templates/feeds/index.rss.eex
Normal file
24
apps/content/lib/content_web/templates/feeds/index.rss.eex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title><%= LayoutView.title(@view_module, @view_template, assigns) %></title>
|
||||||
|
<description><%= LayoutView.excerpt(@view_module, @view_template, assigns) %></description>
|
||||||
|
<link>https://pre.hn</link>
|
||||||
|
<atom:link href="https://pre.hn<%= @feed_url %>" rel="self" type="application/rss+xml" />
|
||||||
|
|
||||||
|
<%= for post <- @posts do %>
|
||||||
|
<item>
|
||||||
|
<title><%= post.post_title |> HtmlSanitizeEx.strip_tags() %></title>
|
||||||
|
<description>
|
||||||
|
<%= post.post_content |> process_content |> html_escape |> safe_to_string %>
|
||||||
|
</description>
|
||||||
|
<pubDate><%=
|
||||||
|
post.post_date
|
||||||
|
|> DateTime.from_naive!("Etc/UTC")
|
||||||
|
|> Timex.format!("{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} {Z}")
|
||||||
|
%></pubDate>
|
||||||
|
<guid isPermaLink="true"><%= post.guid %></guid>
|
||||||
|
</item>
|
||||||
|
<% end %>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
61
apps/content/lib/content_web/templates/layout/admin.html.eex
Normal file
61
apps/content/lib/content_web/templates/layout/admin.html.eex
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<title><%= title(@view_module, @view_template, assigns) %></title>
|
||||||
|
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>">
|
||||||
|
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/admin.css") %>">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<main role="main" class="grid">
|
||||||
|
<nav class="column admin-nav">
|
||||||
|
<menu type="list">
|
||||||
|
<li>
|
||||||
|
<%= link "🏠 Home", to: Routes.admin_home_path(@conn, :index) %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link "📝 Posts", to: Routes.admin_posts_path(@conn, :index) %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link "📄 Pages", to: Routes.admin_posts_path(@conn, :index, post_type: "page") %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link "👋 Logout", to: AuthWeb.Router.Helpers.pow_session_path(@conn, :delete), method: :delete %>
|
||||||
|
</li>
|
||||||
|
</menu>
|
||||||
|
</nav>
|
||||||
|
<section class="column four with-gutters ">
|
||||||
|
<%= if get_flash(@conn, :info) do %>
|
||||||
|
<input type="checkbox" id="dismiss-alert-info" class="alert-dismisser" />
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<p class="alert-text">
|
||||||
|
<%= get_flash(@conn, :info) %>
|
||||||
|
<label for="dismiss-alert-info">Dismiss</label>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<%= if get_flash(@conn, :error) do %>
|
||||||
|
<input type="checkbox" id="dismiss-alert-error" class="alert-dismisser" />
|
||||||
|
<div class="alert alert-error" role="alert">
|
||||||
|
<p class="alert-text">
|
||||||
|
<%= get_flash(@conn, :error) %>
|
||||||
|
<label for="dismiss-alert-error">Dismiss</label>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<%= @inner_content %>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div> <!-- /container -->
|
||||||
|
<script src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||||
|
<script src="<%= Routes.static_path(@conn, "/js/admin.js") %>"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div data-react-component="MenuEditor" data-react-props="<%= %{menu: @menu, posts: @posts, categories: @categories, id: @id} |> Phoenix.json_library().encode!() |> Base.encode64() %>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= form_for @conn, Routes.menus_path(@conn, :update, @id), [method: :put], fn _f -> %>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<%= Enum.map(Content.Comments.children(@parent_id, @post.comments), fn comment -> %>
|
||||||
|
<li class="Comment">
|
||||||
|
<div class="Comment-topmatter">
|
||||||
|
<span class="Gravatar" style="background-image: url(<%= comment.comment_author_email |> gravatar_url_for_email %>)">
|
||||||
|
</span>
|
||||||
|
<h4>
|
||||||
|
<%= comment.comment_author || "Anonymous" %>
|
||||||
|
</h4>
|
||||||
|
<h5>
|
||||||
|
<%= comment.comment_date |> Timex.format!("%F", :strftime) %>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="Comment-content">
|
||||||
|
<%= sanitize comment.comment_content |> auto_paragraph_tags |> elem(1) |> IO.iodata_to_binary() %>
|
||||||
|
<p class="Comment-actions">
|
||||||
|
<a href="#reply-to-<%= comment.comment_ID %>" class="Comment--replyLink">
|
||||||
|
➦ Reply
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="CommentList">
|
||||||
|
<%= render "comments.html", post: @post, parent_id: comment.comment_ID, conn: @conn %>
|
||||||
|
<li class="Comment Comment--replyForm" id="reply-to-<%= comment.comment_ID %>">
|
||||||
|
<h4>
|
||||||
|
Reply to <%= comment.comment_author || "Anonymous" %>
|
||||||
|
</h4>
|
||||||
|
<%=
|
||||||
|
render "reply_form.html",
|
||||||
|
comment_changeset: comment_changeset_for_parent(comment),
|
||||||
|
conn: @conn
|
||||||
|
%>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<% end) %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "form.html", Map.put(assigns, :action, Routes.posts_path(@conn, :update, @posts)) %>
|
82
apps/content/lib/content_web/templates/posts/form.html.eex
Normal file
82
apps/content/lib/content_web/templates/posts/form.html.eex
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<%= form_for @changeset, @action, fn f -> %>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="column three">
|
||||||
|
<%= if @changeset.action do %>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<%= for {error_key, error} <- @changeset.errors |> Keyword.drop([:post_title, :post_content]) do %>
|
||||||
|
<%= if error do %>
|
||||||
|
<li><%= error_key %>: <%= error_tag f, error_key %></li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= label f, :post_title do %>
|
||||||
|
Title
|
||||||
|
<%= text_input f, :post_title %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= label f, :post_content, "data-react-class": "Editor" do %>
|
||||||
|
Content
|
||||||
|
<%= textarea f, :post_content, "data-simplemde": true %>
|
||||||
|
<% end %>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="form-group input-group column uLeft">
|
||||||
|
<%= submit "Delete", class: "btn btn-warning", form: "deleteForm" %>
|
||||||
|
</div>
|
||||||
|
<div class="form-group input-group column">
|
||||||
|
<%= submit "Save", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="column post-admin-sidebar">
|
||||||
|
<%= label f, :post_status, class: "column input-group" do %>
|
||||||
|
<div>Status</div>
|
||||||
|
<%= select f, :post_status, [{"Publish", :publish}, {"Draft", :draft}] %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :post_author, class: "input-group" do %>
|
||||||
|
<div>Author</div>
|
||||||
|
<%= select f, :post_author, @author_options |> Enum.map(&({&1.display_name, &1."ID"})) %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :post_excerpt, class: "input-group" do %>
|
||||||
|
<div>Excerpt</div>
|
||||||
|
<%= textarea f, :post_excerpt %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :sticky, class: "input-group" do %>
|
||||||
|
<div>Sticky?</div>
|
||||||
|
<%= checkbox f, :sticky %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :comment_status, class: "input-group" do %>
|
||||||
|
<div>Comment Status</div>
|
||||||
|
<%= select f, :comment_status, ["open", "closed"] %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :ping_status, class: "input-group" do %>
|
||||||
|
<div>Ping Status</div>
|
||||||
|
<%= select f, :ping_status, ["open", "closed"] %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :post_password, class: "input-group" do %>
|
||||||
|
<div>Post Password</div>
|
||||||
|
<%= text_input f, :post_password %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :post_name, class: "input-group" do %>
|
||||||
|
<div>Slug</div>
|
||||||
|
<%= text_input f, :post_name %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :post_order, class: "input-group" do %>
|
||||||
|
<div>Post Order</div>
|
||||||
|
<%= number_input f, :post_order %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= hidden_input f, :post_type, value: @post_type %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if assigns[:posts] do %>
|
||||||
|
<%= form_for @changeset, Routes.posts_path(@conn, :delete, @posts), [method: :delete, id: "deleteForm"], fn _f -> %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
15
apps/content/lib/content_web/templates/posts/front.html.eex
Normal file
15
apps/content/lib/content_web/templates/posts/front.html.eex
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<article class="<%= post_class(@post) %> h-entry">
|
||||||
|
<div class="uHidden">
|
||||||
|
<h1 class="p-name">
|
||||||
|
<%= link to: Routes.posts_path(@conn, :show, @post), class: "u-url" do %>
|
||||||
|
<%= raw @post.post_title %>
|
||||||
|
<% end %>
|
||||||
|
</h1>
|
||||||
|
<%= post_topmatter(@conn, @post) %>
|
||||||
|
</div>
|
||||||
|
<div class="Article-content <%= if @post.post_format, do: @post.post_format.slug %> e-content">
|
||||||
|
<%= render "thumb.html", post: @post, thumbs: @thumbs %>
|
||||||
|
<%= @post |> Content.Post.content_page(@page) |> process_content |> raw %>
|
||||||
|
</div>
|
||||||
|
<%= render "pagination.html", conn: @conn, post: @post, current_page: @page %>
|
||||||
|
</article>
|
62
apps/content/lib/content_web/templates/posts/index.html.eex
Normal file
62
apps/content/lib/content_web/templates/posts/index.html.eex
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<%= for post <- @posts do %>
|
||||||
|
<article class="<%= post_class(post) %> h-entry">
|
||||||
|
<h2 class="entry-title p-name">
|
||||||
|
<%= link to: Routes.posts_path(@conn, :show, post), class: "u-url" do %>
|
||||||
|
<%= raw post.post_title %>
|
||||||
|
<% end %>
|
||||||
|
</h2>
|
||||||
|
<%= post_topmatter(@conn, post) %>
|
||||||
|
<div class="Article-content <%= if post.post_format, do: post.post_format.slug %> e-content">
|
||||||
|
<%= if authenticated_for_post?(@conn, post) do %>
|
||||||
|
<%= render "thumb.html", post: post, thumbs: @thumbs %>
|
||||||
|
<div class="Article-content-words">
|
||||||
|
<%= raw post |> Content.Post.content_page(1) |> Content.Post.before_more |> process_content |> raw %>
|
||||||
|
<%= if post.post_content =~ "<!--more-->" do %>
|
||||||
|
<p>
|
||||||
|
<%= link "Keep Reading", to: Routes.posts_path(@conn, :show, post) %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
<%= render "pagination.html", conn: @conn, post: post %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<%= render "password_form.html", post: post, conn: @conn %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<p class="CategoryBlock">
|
||||||
|
Categories:
|
||||||
|
<%= for term <- post.categories do %>
|
||||||
|
<%= link term.name, to: Routes.category_path(@conn, :index_posts, term.slug), class: "p-category" %>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
</article>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<nav class="paginator">
|
||||||
|
Page:
|
||||||
|
<%= if @page > 1 do %>
|
||||||
|
<%= link 1, to: paginated_posts_path(@conn, @category, 1) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page > 3 do %>
|
||||||
|
<span class="paginator-page">...</span>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page > 2 do %>
|
||||||
|
<%= link @page - 1, to: paginated_posts_path(@conn, @category, @page - 1) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<span class="paginator-page"><%= @page %></span>
|
||||||
|
|
||||||
|
<%= if @page + 1 < @last_page do %>
|
||||||
|
<%= link @page + 1, to: paginated_posts_path(@conn, @category, @page + 1) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page + 2 < @last_page do %>
|
||||||
|
<span class="paginator-page">...</span>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @page < @last_page do %>
|
||||||
|
<%= link @last_page, to: paginated_posts_path(@conn, @category, @last_page) %>
|
||||||
|
<% end %>
|
||||||
|
</nav>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "form.html", Map.put(assigns, :action, Routes.posts_path(@conn, :create)) %>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<%= if Content.Post.paginated_post?(@post) do %>
|
||||||
|
<nav class="paginator">
|
||||||
|
Page:
|
||||||
|
<%= Enum.map(
|
||||||
|
1..Content.Post.content_page_count(@post),
|
||||||
|
fn page ->
|
||||||
|
if assigns[:current_page] == nil || assigns[:current_page] != page do
|
||||||
|
link page, to: Routes.paged_post_path(@conn, :show, @post, page)
|
||||||
|
else
|
||||||
|
content_tag :span, page, class: "paginator-page"
|
||||||
|
end
|
||||||
|
end) %>
|
||||||
|
</nav>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<%= form_for @conn, "/wp-login.php?action=postpass", [class: "post-password-form"], fn f -> %>
|
||||||
|
<p>This content is password protected. To view it please enter your password below:</p>
|
||||||
|
<p>
|
||||||
|
<%= label f, :post_password do %>
|
||||||
|
Password: <input name="post_password" id="pwbox-131" type="password" size="20">
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
<p class="form-group">
|
||||||
|
<input type="submit" name="Submit" value="Enter">
|
||||||
|
</p>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
<%= form_for @comment_changeset, Routes.comment_path(@conn, :create), fn f -> %>
|
||||||
|
<%= hidden_input f, :comment_parent %>
|
||||||
|
<%= hidden_input f, :comment_post_ID %>
|
||||||
|
<%= label f, :comment_author do %>
|
||||||
|
Your Name
|
||||||
|
<%= text_input f, :comment_author %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :comment_author_email do %>
|
||||||
|
Your Email
|
||||||
|
<%= email_input f, :comment_author_email %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :comment_author_url do %>
|
||||||
|
Your Website
|
||||||
|
<%= text_input f, :comment_author_url %>
|
||||||
|
<% end %>
|
||||||
|
<%= label f, :comment_content do %>
|
||||||
|
Comment
|
||||||
|
<%= textarea f, :comment_content %>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-group">
|
||||||
|
<%= submit "\u27A6 Reply", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
39
apps/content/lib/content_web/templates/posts/show.html.eex
Normal file
39
apps/content/lib/content_web/templates/posts/show.html.eex
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<article class="<%= post_class(@post) %> h-entry">
|
||||||
|
<div>
|
||||||
|
<h1 class="p-name">
|
||||||
|
<%= link to: Routes.posts_path(@conn, :show, @post), class: "u-url" do %>
|
||||||
|
<%= raw @post.post_title %>
|
||||||
|
<% end %>
|
||||||
|
</h1>
|
||||||
|
<%= post_topmatter(@conn, @post) %>
|
||||||
|
</div>
|
||||||
|
<div class="Article-content <%= if @post.post_format, do: @post.post_format.slug %> e-content">
|
||||||
|
<%= render "thumb.html", post: @post, thumbs: @thumbs %>
|
||||||
|
<%= @post |> Content.Post.content_page(@page) |> process_content |> raw %>
|
||||||
|
<p class="CategoryBlock">
|
||||||
|
Categories:
|
||||||
|
<%= for term <- @post.categories do %>
|
||||||
|
<%= link term.name, to: Routes.category_path(@conn, :index_posts, term.slug), class: "p-category" %>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<%= render "pagination.html", conn: @conn, post: @post, current_page: @page %>
|
||||||
|
<%= if @post.comment_status == "open" do %>
|
||||||
|
<h3>Comments</h3>
|
||||||
|
<a href="#reply-to-post">
|
||||||
|
➦ Reply to "<%= @post.post_title %>"
|
||||||
|
</a>
|
||||||
|
<ul class="CommentList">
|
||||||
|
<%= render "comments.html", post: @post, parent_id: 0, conn: @conn %>
|
||||||
|
<li class="Comment" id="reply-to-post">
|
||||||
|
<%=
|
||||||
|
render "reply_form.html",
|
||||||
|
comment_changeset: comment_changeset_for_post(@post),
|
||||||
|
post: @post,
|
||||||
|
conn: @conn
|
||||||
|
%>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
<hr />
|
||||||
|
</article>
|
13
apps/content/lib/content_web/templates/posts/thumb.html.eex
Normal file
13
apps/content/lib/content_web/templates/posts/thumb.html.eex
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<%= case @thumbs[@post.'ID'] do %>
|
||||||
|
<% thumb = %Content.Post{} -> %>
|
||||||
|
<%= if thumb |> Content.Attachment.vertical?() do %>
|
||||||
|
<div class="post-thumbnail post-thumbnail--vertical">
|
||||||
|
<%= img_tag thumb.guid %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="post-thumbnail">
|
||||||
|
<%= img_tag thumb.guid %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% nil -> %>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<h1>Site Index</h1>
|
||||||
|
<h2>Posts and Pages</h2>
|
||||||
|
<ul>
|
||||||
|
<%= for post <- @posts do %>
|
||||||
|
<li>
|
||||||
|
<%= link to: Routes.posts_path(@conn, :show, post) do %>
|
||||||
|
<%= raw post.post_title %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<h2>Categories</h2>
|
||||||
|
<ul>
|
||||||
|
<%= for category <- @categories do %>
|
||||||
|
<li>
|
||||||
|
<%= link to: Routes.category_path(@conn, :index_posts, category.slug)do %>
|
||||||
|
<%= raw category.name %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<h2>Edit comment</h2>
|
||||||
|
|
||||||
|
<%= render "form.html", Map.put(assigns, :action, Routes.comment_path(@conn, :update, @comment)) %>
|
||||||
|
|
||||||
|
<span><%= link "Back", to: Routes.comment_path(@conn, :index) %></span>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<%= form_for @changeset, @action, fn f -> %>
|
||||||
|
<%= if @changeset.action do %>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= submit "Submit", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<h2>Listing comments</h2>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for comment <- @comments do %>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td class="text-right">
|
||||||
|
<span><%= link "Show", to: Routes.comment_path(@conn, :show, comment), class: "btn btn-default btn-xs" %></span>
|
||||||
|
<span><%= link "Edit", to: Routes.comment_path(@conn, :edit, comment), class: "btn btn-default btn-xs" %></span>
|
||||||
|
<span><%= link "Delete", to: Routes.comment_path(@conn, :delete, comment), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<span><%= link "New comment", to: Routes.comment_path(@conn, :new) %></span>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<h2>New comment</h2>
|
||||||
|
|
||||||
|
<%= render "form.html", Map.put(assigns, :action, Routes.comment_path(@conn, :create)) %>
|
||||||
|
|
||||||
|
<span><%= link "Back", to: Routes.comment_path(@conn, :index) %></span>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<h2>Show comment</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<span><%= link "Edit", to: Routes.comment_path(@conn, :edit, @comment) %></span>
|
||||||
|
<span><%= link "Back", to: Routes.comment_path(@conn, :index) %></span>
|
3
apps/content/lib/content_web/views/admin_home_view.ex
Normal file
3
apps/content/lib/content_web/views/admin_home_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Content.AdminHomeView do
|
||||||
|
use Content, :view
|
||||||
|
end
|
3
apps/content/lib/content_web/views/admin_posts_view.ex
Normal file
3
apps/content/lib/content_web/views/admin_posts_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Content.AdminPostsView do
|
||||||
|
use Content, :view
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
defmodule ContentWeb.ErrorHelpers do
|
defmodule Content.ErrorHelpers do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Conveniences for translating and building error messages.
|
Conveniences for translating and building error messages.
|
||||||
"""
|
"""
|
||||||
|
@ -39,9 +39,9 @@ defmodule ContentWeb.ErrorHelpers do
|
||||||
# should be written to the errors.po file. The :count option is
|
# should be written to the errors.po file. The :count option is
|
||||||
# set by Ecto and indicates we should also apply plural rules.
|
# set by Ecto and indicates we should also apply plural rules.
|
||||||
if count = opts[:count] do
|
if count = opts[:count] do
|
||||||
Gettext.dngettext(ContentWeb.Gettext, "errors", msg, msg, count, opts)
|
Gettext.dngettext(Content.Gettext, "errors", msg, msg, count, opts)
|
||||||
else
|
else
|
||||||
Gettext.dgettext(ContentWeb.Gettext, "errors", msg, opts)
|
Gettext.dgettext(Content.Gettext, "errors", msg, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -1,5 +1,5 @@
|
||||||
defmodule ContentWeb.ErrorView do
|
defmodule Content.ErrorView do
|
||||||
use ContentWeb, :view
|
use Content, :view
|
||||||
|
|
||||||
# If you want to customize a particular status code
|
# If you want to customize a particular status code
|
||||||
# for a certain format, you may uncomment below.
|
# for a certain format, you may uncomment below.
|
70
apps/content/lib/content_web/views/feeds_view.ex
Normal file
70
apps/content/lib/content_web/views/feeds_view.ex
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
defmodule Content.FeedsView do
|
||||||
|
use Content, :view
|
||||||
|
use Phoenix.HTML
|
||||||
|
alias Phoenix.HTML
|
||||||
|
alias Phoenix.HTML.Tag
|
||||||
|
alias Content.LayoutView
|
||||||
|
|
||||||
|
def gravatar_url_for_email(email) do
|
||||||
|
email
|
||||||
|
|> Kernel.||("noreply@example.com")
|
||||||
|
|> String.trim()
|
||||||
|
|> String.downcase()
|
||||||
|
|> (&(:crypto.hash(:md5, &1))).()
|
||||||
|
|> Base.encode16()
|
||||||
|
|> String.downcase()
|
||||||
|
|> (&("https://www.gravatar.com/avatar/#{&1}")).()
|
||||||
|
end
|
||||||
|
|
||||||
|
def auto_paragraph_tags(string) do
|
||||||
|
string
|
||||||
|
|> Kernel.||("")
|
||||||
|
|> String.split(["\n\n", "\r\n\r\n"], trim: true)
|
||||||
|
|> Enum.map(fn text ->
|
||||||
|
[Tag.content_tag(:p, text |> HTML.raw(), []), ?\n]
|
||||||
|
end)
|
||||||
|
|> HTML.html_escape()
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_class(post) do
|
||||||
|
sticky =
|
||||||
|
if post.sticky do
|
||||||
|
"sticky"
|
||||||
|
end
|
||||||
|
"post post-#{post.'ID'} #{sticky}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_topmatter(conn, post) do
|
||||||
|
author =
|
||||||
|
post.author ||
|
||||||
|
%Auth.User{
|
||||||
|
email: "example@example.org",
|
||||||
|
display_name: "Anonymous",
|
||||||
|
homepage_url: "#"
|
||||||
|
}
|
||||||
|
assigns = %{post: post, author: author, conn: conn}
|
||||||
|
~E"""
|
||||||
|
<% _ = assigns # suppress unused assigns warning %>
|
||||||
|
<div class="Comment-topmatter">
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
<%= link to: author.homepage_url, rel: "author", class: "p-author h-card" do %>
|
||||||
|
<%= author.display_name %>
|
||||||
|
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo" %>
|
||||||
|
<% end %>
|
||||||
|
</h4>
|
||||||
|
<h5>
|
||||||
|
<%= link to: Routes.posts_path(conn, :show, post) do %>
|
||||||
|
<time class="dt-published" datetime="<%= post.post_date %>">
|
||||||
|
<%= post.post_date |> Timex.format!("%F", :strftime) %>
|
||||||
|
</time>
|
||||||
|
<% end %>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def unauthenticated_post?(_conn, post) do
|
||||||
|
post.post_password == nil || String.length(post.post_password) == 0
|
||||||
|
end
|
||||||
|
end
|
92
apps/content/lib/content_web/views/layout_view.ex
Normal file
92
apps/content/lib/content_web/views/layout_view.ex
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
defmodule Content.LayoutView do
|
||||||
|
use Content, :view
|
||||||
|
|
||||||
|
alias Content.{Option, Options}
|
||||||
|
|
||||||
|
def title(Content.PostsView, "index.html", assigns) do
|
||||||
|
"Page #{assigns.page} | #{title(nil, nil, nil)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def title(Content.FeedsView, "index.rss", %{category: category}) do
|
||||||
|
"#{category} | #{title(nil, nil, nil)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def title(Content.PostsView, "show.html", assigns) do
|
||||||
|
(assigns.post.post_title |> HtmlSanitizeEx.strip_tags()) <> " | " <> title(nil, nil, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def title(_, _, _) do
|
||||||
|
case Options.get("blogname") do
|
||||||
|
opt = %Option{} ->
|
||||||
|
opt.option_value
|
||||||
|
_ ->
|
||||||
|
"Hello"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def excerpt(Content.PostsView, "show.html", assigns) do
|
||||||
|
assigns.post.post_excerpt
|
||||||
|
|> HtmlSanitizeEx.strip_tags()
|
||||||
|
end
|
||||||
|
|
||||||
|
def excerpt(Content.FeedsView, "index.rss", %{category: category}) do
|
||||||
|
"#{category} | #{excerpt(nil, nil, nil)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def excerpt(_, _, _) do
|
||||||
|
case Options.get("blogdescription") do
|
||||||
|
opt = %Option{} ->
|
||||||
|
opt.option_value
|
||||||
|
_ ->
|
||||||
|
"Yet another website"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def author(Content.PostsView, "show.html", assigns) do
|
||||||
|
case assigns do
|
||||||
|
%{author: %{display_name: name}} ->
|
||||||
|
name
|
||||||
|
_ ->
|
||||||
|
"Anonymous"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def author(_, _, _) do
|
||||||
|
"Anonymous"
|
||||||
|
end
|
||||||
|
|
||||||
|
def corresponding_feed_url(conn, _, _, %{category: nil}) do
|
||||||
|
Routes.index_feed_url(conn, :index)
|
||||||
|
end
|
||||||
|
|
||||||
|
def corresponding_feed_url(conn, Content.PostsView, "index.html", %{category: category}) do
|
||||||
|
Routes.category_feed_url(conn, :index, category)
|
||||||
|
end
|
||||||
|
|
||||||
|
def corresponding_feed_url(conn, _, _, _) do
|
||||||
|
Routes.index_feed_url(conn, :index)
|
||||||
|
end
|
||||||
|
|
||||||
|
def menu_markup(menu_items, conn), do: menu_markup(menu_items, conn, 0)
|
||||||
|
def menu_markup(nil, _, _), do: ""
|
||||||
|
def menu_markup([], _, _), do: ""
|
||||||
|
def menu_markup(menu_items, conn, level) do
|
||||||
|
~E"""
|
||||||
|
<ul style="--menu-level: <%= level %>;">
|
||||||
|
<%= for item <- menu_items do %>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<%= case item[:type] do %>
|
||||||
|
<% "category" -> %>
|
||||||
|
<%= link item[:related_item].title, to: Routes.category_path(conn, :index_posts, item[:related_item].slug) %>
|
||||||
|
<% _ -> %>
|
||||||
|
<%= link item[:related_item].title, to: Routes.posts_path(conn, :show, item[:related_item].slug) %>
|
||||||
|
<% end %>
|
||||||
|
<%= menu_markup(item[:children], conn, level + 1) %>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
3
apps/content/lib/content_web/views/menus_view.ex
Normal file
3
apps/content/lib/content_web/views/menus_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Content.MenusView do
|
||||||
|
use Content, :view
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue