diff options
-rw-r--r-- | src/guff.cr | 397 | ||||
-rw-r--r-- | src/guff/views/admin-page.cr | 202 | ||||
-rw-r--r-- | src/guff/views/blog/list-item.cr | 7 | ||||
-rw-r--r-- | src/guff/views/blog/list.cr | 20 | ||||
-rw-r--r-- | src/guff/views/dropdown/icon.cr | 11 | ||||
-rw-r--r-- | src/guff/views/dropdown/item.cr | 21 | ||||
-rw-r--r-- | src/guff/views/dropdown/menu.cr | 32 | ||||
-rw-r--r-- | src/guff/views/html.cr | 53 | ||||
-rw-r--r-- | src/guff/views/login-page.cr | 13 | ||||
-rw-r--r-- | src/guff/views/logout-page.cr | 5 | ||||
-rw-r--r-- | src/guff/views/tab.cr | 21 | ||||
-rw-r--r-- | src/guff/views/view.cr | 8 |
12 files changed, 394 insertions, 396 deletions
diff --git a/src/guff.cr b/src/guff.cr index f0e2671..4fde62b 100644 --- a/src/guff.cr +++ b/src/guff.cr @@ -38,401 +38,6 @@ private macro api_method_dispatch(modules) end module Guff - module Views - abstract class View - def initialize(@context : Context) - end - - def h(s : String) : String - HTML.escape(s) - end - end - - class TabView < View - def initialize( - context : Context, - @prefix : String, - @tab : Hash(Symbol, String) - ) - super(context) - @id = h("%s-tab-%s" % [@prefix, @tab[:id]]) as String - @target = h("%s-pane-%s" % [@prefix, @tab[:id]]) as String - end - - private def v(id : Symbol) : String - raise "unknown id: #{id}" unless @tab.has_key?(id) - h(@tab[id]) - end - - ECR.def_to_s("src/views/tab.ecr") - end - - module Dropdown - module Icon - ICON_TEMPLATE = "<i class='fa fa-fw %s'></i>" - - def self.icon(id : String?) - if id && id.size > 0 - ICON_TEMPLATE % [HTML.escape(id.not_nil!)] - else - "" - end - end - end - - class ItemView < View - def initialize( - context : Context, - @active : Bool, - @item : Hash(Symbol, String) - ) - super(context) - end - - private def v(id : Symbol) - h(@item[id]) - end - - private def li_css - @active ? "class='active'" : "" - end - - ECR.def_to_s("src/views/dropdown/item.ecr") - end - - class MenuView < View - def initialize( - context : Context, - @id : String, - @name : String, - @text : String, - @css : String, - @icon : String, - @default : String, - @items : Array(Hash(Symbol, String)) - ) - super(context) - - @default_name = @items.reduce("") do |r, row| - (row[:id]? == @default) ? row[:name] : r - end as String - end - - private def items - String.build do |io| - @items.each do |item| - io << ItemView.new( - context: @context, - active: @default == item[:id]?, - item: item - ).to_s - end - end - end - - ECR.def_to_s("src/views/dropdown/menu.ecr") - end - end - - abstract class HTMLView < View - TEMPLATES = { - script: "<script type='text/javascript' src='%s'></script>", - style: "<link rel='stylesheet' type='text/css' href='%s'/>", - } - - private def assets(key : Symbol, paths : Array(String)) - String.build do |io| - paths.each do |path| - io << TEMPLATES[key] % [h(path)] - end - end - end - - def scripts(paths : Array(String)) - assets(:script, paths) - end - - def styles(paths : Array(String)) - assets(:style, paths) - end - - def tabs(prefix : String, rows : Array(Hash(Symbol, String))) - String.build do |io| - rows.each do |row| - TabView.new(@context, prefix, row).to_s(io) - end - end - end - - def dropdown( - id : String, - name : String, - text : String, - icon : String, - css : String, - default : String, - items : Array(Hash(Symbol, String)) - ) - Dropdown::MenuView.new( - context: @context, - id: id, - name: name, - text: text, - icon: icon, - css: css, - default: default, - items: items - ).to_s - end - end - - class AdminPageView < HTMLView - TITLE = "Guff Admin" - - TABS = { - "admin" => [{ - :id => "home", - :css => "active", - :icon => "fa-home", - :name => "Home", - :text => "View home tab.", - }, { - :id => "posts", - :css => "", - :icon => "fa-cubes", - :name => "Posts", - :text => "Manage blog, pages, and projects.", - }, { - :id => "files", - :css => "", - :icon => "fa-files-o", - :name => "Files", - :text => "Manage files.", - }, { - :id => "settings", - :css => "", - :icon => "fa-cogs", - :name => "Settings", - :text => "Configure settings.", - }], - - "settings" => [{ - :id => "general", - :css => "active", - :icon => "fa-cog", - :name => "General", - :text => "Manage general settings.", - }, { - :id => "backups", - :css => "", - :icon => "fa-archive", - :name => "Backups", - :text => "Manage backups.", - }, { - :id => "import", - :css => "", - :icon => "fa-upload", - :name => "Import / Export", - :text => "Import and export posts.", - }, { - :id => "sites", - :css => "", - :icon => "fa-sitemap", - :name => "Sites", - :text => "Manage sites and domains.", - }, { - :id => "themes", - :css => "", - :icon => "fa-eye", - :name => "Themes", - :text => "Manage themes.", - }, { - :id => "users", - :css => "", - :icon => "fa-users", - :name => "Users", - :text => "Manage users and permissions.", - }], - } - - TEMPLATES = { - :option => " - <option value='%s'>%s</option> - ", - - :new_post_button => " - <a - href='#' - class='btn btn-primary' - title='Create new blog post, page, or project.' - data-toggle='dropdown' - > - <i class='fa fa-plus-circle'></i> - Create - <i class='fa fa-fw fa-caret-down'></i> - </a> - - <ul class='dropdown-menu'> - <li> - <a - href='#' - title='Create new blog post.' - class='add-post' - data-type='blog' - > - <i class='fa fa-fw fa-sticky-note-o'></i> - Blog Post - </a> - </li> - - <li> - <a - href='#' - title='Create new page.' - class='add-post' - data-type='page' - > - <i class='fa fa-fw fa-bookmark-o'></i> - Page - </a> - </li> - - <li> - <a - href='#' - title='Create new project.' - class='add-post' - data-type='project' - > - <i class='fa fa-fw fa-cube'></i> - Project - </a> - </li> - </ul> - ", - - :state_button => " - <a - href='#' - class='btn btn-default' - title='Mark as %s.' - data-val='%s' - > - <i class='fa %s'></i> - %s - </a> - ", - } - - def tabs(id : String) - super(id, TABS[id]) - end - - private def new_post_button - TEMPLATES[:new_post_button] - end - - private def role_options - @role_options ||= String.build do |io| - @context.models.role.get_roles.each do |row| - io << TEMPLATES[:option] % %w{role name}.map { |key| h(row[key]) } - end - end - end - - private def state_buttons - @state_buttons ||= String.build do |io| - @context.models.state.get_states.each do |row| - io << TEMPLATES[:state_button] % [ - h(row["name"]), - h(row["state"]), - h(row["icon"]), - h(row["name"]) - ] - end - end - end - - private def authors_menu_items - @context.models.user.get_users.map do |row| - { - :id => row["user_id"], - :name => row["name"], - :text => "Show author \"%s\"." % [row["name"]], - } - end - end - - private def sites_menu_items - @context.models.site.get_sites.map do |row| - { - :id => row["site_id"], - :name => row["name"], - :text => "Show site \"%s\"." % [row["name"]], - } - end - end - - private def states_menu_items - @context.models.state.get_states.map do |row| - { - :id => row["state"], - :name => row["name"], - :icon => row["icon"], - :text => "Show state \"%s\"." % [row["name"]], - } - end - end - - ECR.def_to_s("src/views/admin-page.ecr") - end - - class LoginPageView < HTMLView - def initialize(context : Context, @error : String? = nil) - super(context) - end - - def get_csrf_token - @context.models.csrf.create_token - end - - ECR.def_to_s("src/views/login-page.ecr") - end - - class LogoutPageView < HTMLView - ECR.def_to_s("src/views/logout-page.ecr") - end - - class BlogListItemView < HTMLView - def initialize(context : Context, @post_id : Int64) - super(context) - end - - ECR.def_to_s("src/views/blog/list-item.ecr") - end - - # - # TODO: add y/m/d/page - # - class BlogListView < HTMLView - TITLE = "Blog List" - - def initialize(context : Context, @post_ids : Array(Int64)) - super(context) - end - - def posts - String.build do |io| - @post_ids.each do |id| - BlogListItemView.new(@context, id).to_s(io) - end - end - end - - ECR.def_to_s("src/views/blog/list.ecr") - end - end - module Handlers abstract class Handler < HTTP::Handler def initialize(@context : Context) @@ -906,7 +511,7 @@ module Guff context.response.content_type = "text/html; charset=utf-8" context.response.status_code = 200 - Views::BlogListView.new(@context, ids).to_s(context.response) + Views::Blog::ListView.new(@context, ids).to_s(context.response) else # unknown page call_next(context) diff --git a/src/guff/views/admin-page.cr b/src/guff/views/admin-page.cr new file mode 100644 index 0000000..094b5f7 --- /dev/null +++ b/src/guff/views/admin-page.cr @@ -0,0 +1,202 @@ +require "./html" + +class Guff::Views::AdminPageView < Guff::Views::HTMLView + TITLE = "Guff Admin" + + TABS = { + "admin" => [{ + :id => "home", + :css => "active", + :icon => "fa-home", + :name => "Home", + :text => "View home tab.", + }, { + :id => "posts", + :css => "", + :icon => "fa-cubes", + :name => "Posts", + :text => "Manage blog, pages, and projects.", + }, { + :id => "files", + :css => "", + :icon => "fa-files-o", + :name => "Files", + :text => "Manage files.", + }, { + :id => "settings", + :css => "", + :icon => "fa-cogs", + :name => "Settings", + :text => "Configure settings.", + }], + + "settings" => [{ + :id => "general", + :css => "active", + :icon => "fa-cog", + :name => "General", + :text => "Manage general settings.", + }, { + :id => "backups", + :css => "", + :icon => "fa-archive", + :name => "Backups", + :text => "Manage backups.", + }, { + :id => "import", + :css => "", + :icon => "fa-upload", + :name => "Import / Export", + :text => "Import and export posts.", + }, { + :id => "sites", + :css => "", + :icon => "fa-sitemap", + :name => "Sites", + :text => "Manage sites and domains.", + }, { + :id => "themes", + :css => "", + :icon => "fa-eye", + :name => "Themes", + :text => "Manage themes.", + }, { + :id => "users", + :css => "", + :icon => "fa-users", + :name => "Users", + :text => "Manage users and permissions.", + }], + } + + TEMPLATES = { + :option => " + <option value='%s'>%s</option> + ", + + :new_post_button => " + <a + href='#' + class='btn btn-primary' + title='Create new blog post, page, or project.' + data-toggle='dropdown' + > + <i class='fa fa-plus-circle'></i> + Create + <i class='fa fa-fw fa-caret-down'></i> + </a> + + <ul class='dropdown-menu'> + <li> + <a + href='#' + title='Create new blog post.' + class='add-post' + data-type='blog' + > + <i class='fa fa-fw fa-sticky-note-o'></i> + Blog Post + </a> + </li> + + <li> + <a + href='#' + title='Create new page.' + class='add-post' + data-type='page' + > + <i class='fa fa-fw fa-bookmark-o'></i> + Page + </a> + </li> + + <li> + <a + href='#' + title='Create new project.' + class='add-post' + data-type='project' + > + <i class='fa fa-fw fa-cube'></i> + Project + </a> + </li> + </ul> + ", + + :state_button => " + <a + href='#' + class='btn btn-default' + title='Mark as %s.' + data-val='%s' + > + <i class='fa %s'></i> + %s + </a> + ", + } + + def tabs(id : String) + super(id, TABS[id]) + end + + private def new_post_button + TEMPLATES[:new_post_button] + end + + private def role_options + @role_options ||= String.build do |io| + @context.models.role.get_roles.each do |row| + io << TEMPLATES[:option] % %w{role name}.map { |key| h(row[key]) } + end + end + end + + private def state_buttons + @state_buttons ||= String.build do |io| + @context.models.state.get_states.each do |row| + io << TEMPLATES[:state_button] % [ + h(row["name"]), + h(row["state"]), + h(row["icon"]), + h(row["name"]) + ] + end + end + end + + private def authors_menu_items + @context.models.user.get_users.map do |row| + { + :id => row["user_id"], + :name => row["name"], + :text => "Show author \"%s\"." % [row["name"]], + } + end + end + + private def sites_menu_items + @context.models.site.get_sites.map do |row| + { + :id => row["site_id"], + :name => row["name"], + :text => "Show site \"%s\"." % [row["name"]], + } + end + end + + private def states_menu_items + @context.models.state.get_states.map do |row| + { + :id => row["state"], + :name => row["name"], + :icon => row["icon"], + :text => "Show state \"%s\"." % [row["name"]], + } + end + end + + ECR.def_to_s("src/views/admin-page.ecr") +end diff --git a/src/guff/views/blog/list-item.cr b/src/guff/views/blog/list-item.cr new file mode 100644 index 0000000..3ebe411 --- /dev/null +++ b/src/guff/views/blog/list-item.cr @@ -0,0 +1,7 @@ +class Guff::Views::Blog::ListItemView < Guff::Views::HTMLView + def initialize(context : Context, @post_id : Int64) + super(context) + end + + ECR.def_to_s("src/views/blog/list-item.ecr") +end diff --git a/src/guff/views/blog/list.cr b/src/guff/views/blog/list.cr new file mode 100644 index 0000000..31437a6 --- /dev/null +++ b/src/guff/views/blog/list.cr @@ -0,0 +1,20 @@ +# +# TODO: add y/m/d/page +# +class Guff::Views::Blog::ListView < Guff::Views::HTMLView + TITLE = "Blog List" + + def initialize(context : Context, @post_ids : Array(Int64)) + super(context) + end + + def posts + String.build do |io| + @post_ids.each do |id| + ListItemView.new(@context, id).to_s(io) + end + end + end + + ECR.def_to_s("src/views/blog/list.ecr") +end diff --git a/src/guff/views/dropdown/icon.cr b/src/guff/views/dropdown/icon.cr new file mode 100644 index 0000000..8d30b6e --- /dev/null +++ b/src/guff/views/dropdown/icon.cr @@ -0,0 +1,11 @@ +module Guff::Views::Dropdown::Icon + ICON_TEMPLATE = "<i class='fa fa-fw %s'></i>" + + def self.icon(id : String?) + if id && id.size > 0 + ICON_TEMPLATE % [HTML.escape(id.not_nil!)] + else + "" + end + end +end diff --git a/src/guff/views/dropdown/item.cr b/src/guff/views/dropdown/item.cr new file mode 100644 index 0000000..22c8d21 --- /dev/null +++ b/src/guff/views/dropdown/item.cr @@ -0,0 +1,21 @@ +require "../view" + +class Guff::Views::Dropdown::ItemView < Guff::Views::View + def initialize( + context : Context, + @active : Bool, + @item : Hash(Symbol, String) + ) + super(context) + end + + private def v(id : Symbol) + h(@item[id]) + end + + private def li_css + @active ? "class='active'" : "" + end + + ECR.def_to_s("src/views/dropdown/item.ecr") +end diff --git a/src/guff/views/dropdown/menu.cr b/src/guff/views/dropdown/menu.cr new file mode 100644 index 0000000..f533fc5 --- /dev/null +++ b/src/guff/views/dropdown/menu.cr @@ -0,0 +1,32 @@ +class Guff::Views::Dropdown::MenuView < Guff::Views::View + def initialize( + context : Context, + @id : String, + @name : String, + @text : String, + @css : String, + @icon : String, + @default : String, + @items : Array(Hash(Symbol, String)) + ) + super(context) + + @default_name = @items.reduce("") do |r, row| + (row[:id]? == @default) ? row[:name] : r + end as String + end + + private def items + String.build do |io| + @items.each do |item| + io << ItemView.new( + context: @context, + active: @default == item[:id]?, + item: item + ).to_s + end + end + end + + ECR.def_to_s("src/views/dropdown/menu.ecr") +end diff --git a/src/guff/views/html.cr b/src/guff/views/html.cr new file mode 100644 index 0000000..d31f183 --- /dev/null +++ b/src/guff/views/html.cr @@ -0,0 +1,53 @@ +require "./view" + +abstract class Guff::Views::HTMLView < Guff::Views::View + TEMPLATES = { + script: "<script type='text/javascript' src='%s'></script>", + style: "<link rel='stylesheet' type='text/css' href='%s'/>", + } + + private def assets(key : Symbol, paths : Array(String)) + String.build do |io| + paths.each do |path| + io << TEMPLATES[key] % [h(path)] + end + end + end + + def scripts(paths : Array(String)) + assets(:script, paths) + end + + def styles(paths : Array(String)) + assets(:style, paths) + end + + def tabs(prefix : String, rows : Array(Hash(Symbol, String))) + String.build do |io| + rows.each do |row| + TabView.new(@context, prefix, row).to_s(io) + end + end + end + + def dropdown( + id : String, + name : String, + text : String, + icon : String, + css : String, + default : String, + items : Array(Hash(Symbol, String)) + ) + Dropdown::MenuView.new( + context: @context, + id: id, + name: name, + text: text, + icon: icon, + css: css, + default: default, + items: items + ).to_s + end +end diff --git a/src/guff/views/login-page.cr b/src/guff/views/login-page.cr new file mode 100644 index 0000000..892f3c1 --- /dev/null +++ b/src/guff/views/login-page.cr @@ -0,0 +1,13 @@ +require "./view" + +class Guff::Views::LoginPageView < Guff::Views::HTMLView + def initialize(context : Context, @error : String? = nil) + super(context) + end + + def get_csrf_token + @context.models.csrf.create_token + end + + ECR.def_to_s("src/views/login-page.ecr") +end diff --git a/src/guff/views/logout-page.cr b/src/guff/views/logout-page.cr new file mode 100644 index 0000000..164faf7 --- /dev/null +++ b/src/guff/views/logout-page.cr @@ -0,0 +1,5 @@ +require "./view" + +class Guff::Views::LogoutPageView < Guff::Views::HTMLView + ECR.def_to_s("src/views/logout-page.ecr") +end diff --git a/src/guff/views/tab.cr b/src/guff/views/tab.cr new file mode 100644 index 0000000..6a354b4 --- /dev/null +++ b/src/guff/views/tab.cr @@ -0,0 +1,21 @@ +require "./view" + +class Guff::Views::TabView < Guff::Views::View + def initialize( + context : Context, + @prefix : String, + @tab : Hash(Symbol, String) + ) + super(context) + @id = h("%s-tab-%s" % [@prefix, @tab[:id]]) as String + @target = h("%s-pane-%s" % [@prefix, @tab[:id]]) as String + end + + private def v(id : Symbol) : String + raise "unknown id: #{id}" unless @tab.has_key?(id) + h(@tab[id]) + end + + ECR.def_to_s("src/views/tab.ecr") +end + diff --git a/src/guff/views/view.cr b/src/guff/views/view.cr new file mode 100644 index 0000000..0fc92d7 --- /dev/null +++ b/src/guff/views/view.cr @@ -0,0 +1,8 @@ +abstract class Guff::Views::View + def initialize(@context : Context) + end + + def h(s : String) : String + HTML.escape(s) + end +end |