Initial commit
This commit is contained in:
commit
d07442ed66
49 changed files with 2570 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/_build
|
||||
/deps
|
||||
erl_crash.dump
|
||||
*.ez
|
||||
config.exs
|
||||
|
||||
/data
|
17
README.md
Normal file
17
README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Mebe
|
||||
|
||||
|
||||
Mebe -- _the Minimalistic Elixir Blog Engine_ -- is a simple blog engine written in [Elixir](https://elixir-lang.org),
|
||||
using the [Phoenix Framework](http://www.phoenixframework.org/).
|
||||
|
||||
The engine consists of two parts, both in the `apps/` directory:
|
||||
|
||||
1. MebeEngine, which handles parsing the data files into an ETS (_Erlang Term Storage_) in-memory database, and
|
||||
2. MebeWeb, which uses the Phoenix Framework to serve the blog data to clients.
|
||||
|
||||
## Installation for development
|
||||
|
||||
* `git clone`
|
||||
* Copy `*.exs.dist`, removing the `.dist` ending and go through the configs.
|
||||
* `npm install && bower install && gulp` to build the frontend.
|
||||
* `mix phoenix.server` to run the development server.
|
4
apps/mebe_engine/.gitignore
vendored
Normal file
4
apps/mebe_engine/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/_build
|
||||
/deps
|
||||
erl_crash.dump
|
||||
*.ez
|
4
apps/mebe_engine/README.md
Normal file
4
apps/mebe_engine/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
MebeEngine
|
||||
==========
|
||||
|
||||
** TODO: Add description **
|
27
apps/mebe_engine/config/config.exs.dist
Normal file
27
apps/mebe_engine/config/config.exs.dist
Normal file
|
@ -0,0 +1,27 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
# This configuration is loaded before any dependency and is restricted
|
||||
# to this project. If another project depends on this project, this
|
||||
# file won't be loaded nor affect the parent project. For this reason,
|
||||
# if you want to provide default values for your application for third-
|
||||
# party users, it should be done in your mix.exs file.
|
||||
|
||||
# Sample configuration:
|
||||
#
|
||||
# config :logger, :console,
|
||||
# level: :info,
|
||||
# format: "$date $time [$level] $metadata$message\n",
|
||||
# metadata: [:user_id]
|
||||
|
||||
# It is also possible to import configuration files, relative to this
|
||||
# directory. For example, you can emulate configuration per environment
|
||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
||||
# Configuration from the imported file will override the ones defined
|
||||
# here (which is why it is important to import them last).
|
||||
#
|
||||
# import_config "#{Mix.env}.exs"
|
||||
|
||||
config :mebe_engine,
|
||||
data_path: "CONFIGURE" # The path to crawl post and page data from. Relative to this file's path
|
55
apps/mebe_engine/lib/crawler.ex
Normal file
55
apps/mebe_engine/lib/crawler.ex
Normal file
|
@ -0,0 +1,55 @@
|
|||
defmodule MebeEngine.Crawler do
|
||||
@moduledoc """
|
||||
The crawler goes through the specified directory, opening and parsing all the matching files
|
||||
inside concurrently.
|
||||
"""
|
||||
|
||||
alias MebeEngine.Parser
|
||||
|
||||
alias MebeEngine.Models.Page
|
||||
alias MebeEngine.Models.Post
|
||||
|
||||
def crawl(path) do
|
||||
get_files(path)
|
||||
|> Enum.map(fn file -> Task.async MebeEngine.Crawler, :parse, [file] end)
|
||||
|> handle_responses
|
||||
|> construct_archives
|
||||
end
|
||||
|
||||
def get_files(path) do
|
||||
Path.wildcard path <> "/**/*.md"
|
||||
end
|
||||
|
||||
def parse(file) do
|
||||
File.read!(file)
|
||||
|> Parser.parse(Path.basename file)
|
||||
end
|
||||
|
||||
def handle_responses(tasklist) do
|
||||
Enum.map tasklist, fn task -> Task.await task end
|
||||
end
|
||||
|
||||
def construct_archives(datalist) do
|
||||
Enum.reduce datalist, %{pages: %{}, posts: [], years: %{}, months: %{}, tags: %{}}, fn pagedata, acc ->
|
||||
case pagedata.__struct__ do
|
||||
Page -> %{acc | pages: Map.put(acc.pages, pagedata.slug, pagedata)}
|
||||
|
||||
Post ->
|
||||
{year, month, _} = pagedata.date
|
||||
|
||||
tags = Enum.reduce pagedata.tags, acc.tags, fn tag, tagmap ->
|
||||
posts = Map.get(tagmap, tag, [])
|
||||
Map.put(tagmap, tag, [pagedata | posts])
|
||||
end
|
||||
|
||||
%{
|
||||
acc |
|
||||
posts: [pagedata | acc.posts],
|
||||
years: Map.put(acc.years, year, pagedata),
|
||||
months: Map.put(acc.months, {year, month}, pagedata),
|
||||
tags: tags
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
101
apps/mebe_engine/lib/db.ex
Normal file
101
apps/mebe_engine/lib/db.ex
Normal file
|
@ -0,0 +1,101 @@
|
|||
defmodule MebeEngine.DB do
|
||||
@moduledoc """
|
||||
Stuff related to storing the blog data to memory (ETS).
|
||||
"""
|
||||
|
||||
# Table for meta information, like the counts of posts
|
||||
@meta_table :meta
|
||||
|
||||
@page_table :pages
|
||||
|
||||
# Table for sequential retrieval of posts (for index page)
|
||||
@post_table :posts
|
||||
|
||||
# Table for quick retrieval of single post (with key)
|
||||
@single_post_table :single_posts
|
||||
|
||||
@year_table :years
|
||||
@month_table :months
|
||||
@tag_table :tags
|
||||
|
||||
def init() do
|
||||
:ets.new @meta_table, [:named_table, :set, :protected, read_concurrency: true]
|
||||
:ets.new @page_table, [:named_table, :set, :protected, read_concurrency: true]
|
||||
:ets.new @post_table, [:named_table, :ordered_set, :protected, read_concurrency: true]
|
||||
:ets.new @single_post_table, [:named_table, :set, :protected, read_concurrency: true]
|
||||
:ets.new @year_table, [:named_table, :set, :protected, read_concurrency: true]
|
||||
:ets.new @month_table, [:named_table, :set, :protected, read_concurrency: true]
|
||||
:ets.new @tag_table, [:named_table, :ordered_set, :protected, read_concurrency: true]
|
||||
end
|
||||
|
||||
def insert_count(key, count) do
|
||||
:ets.insert @meta_table {key, val}
|
||||
end
|
||||
|
||||
def insert_post(post) do
|
||||
{year, month, day} = post.date
|
||||
:ets.insert @post_table, {{year, month, day, post.order}, post}
|
||||
:ets.insert @single_post_table, {{year, month, day, post.slug}, post}
|
||||
end
|
||||
|
||||
def insert_page(page) do
|
||||
:ets.insert @page_table, {page.slug, page}
|
||||
end
|
||||
|
||||
def insert_year(year, posts) do
|
||||
:ets.insert @year_table, {year, posts}
|
||||
end
|
||||
|
||||
def insert_month(month, posts) do
|
||||
:ets.insert @month_table, {month, posts}
|
||||
end
|
||||
|
||||
def insert_tag(tag, posts) do
|
||||
:ets.insert @tag_table, {tag, posts}
|
||||
end
|
||||
|
||||
|
||||
def get_reg_posts(first \\ 0, limit \\ :all)
|
||||
|
||||
def get_reg_posts(first, limit) do
|
||||
get_posts(@post_table, first, limit)
|
||||
end
|
||||
|
||||
def get_count(:all) do
|
||||
[{_, count}] = :ets.match_object @meta_table, {:all, :"$1"}
|
||||
count
|
||||
end
|
||||
|
||||
def get_post(year, month, day, slug) do
|
||||
case :ets.match_object @single_post_table, {{year, month, day, slug}, :"$1"} do
|
||||
[{_, post}] -> post
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def get_pages() do
|
||||
:ets.match(@page_table, :"$1")
|
||||
|> ets_to_data
|
||||
end
|
||||
|
||||
|
||||
defp get_posts(table, first \\ 0, limit \\ :all)
|
||||
|
||||
# Optimization for getting all
|
||||
defp get_posts(table, 0, :all) do
|
||||
:ets.select_reverse(table, [{:"$1", [], [:"$_"]}])
|
||||
|> ets_to_data
|
||||
end
|
||||
|
||||
defp get_posts(table, first, limit) when is_integer(first) and is_integer(limit) do
|
||||
{result, _} = :ets.select_reverse table, [{:"$1", [], [:"$_"]}], first + limit
|
||||
{_, result} = Enum.split result, first
|
||||
ets_to_data result
|
||||
end
|
||||
|
||||
# Remove key from data returned from ETS
|
||||
defp ets_to_data(data) do
|
||||
Enum.map data, fn {_, actual} -> actual end
|
||||
end
|
||||
end
|
18
apps/mebe_engine/lib/mebe_engine.ex
Normal file
18
apps/mebe_engine/lib/mebe_engine.ex
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule MebeEngine do
|
||||
use Application
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec, warn: false
|
||||
|
||||
children = [
|
||||
worker(MebeEngine.Worker, [])
|
||||
]
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: MebeEngine.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
33
apps/mebe_engine/lib/models.ex
Normal file
33
apps/mebe_engine/lib/models.ex
Normal file
|
@ -0,0 +1,33 @@
|
|||
defmodule MebeEngine.Models do
|
||||
@moduledoc """
|
||||
This module contains the data models (not db models) of the blog engine.
|
||||
"""
|
||||
|
||||
defmodule PageData do
|
||||
defstruct filename: nil,
|
||||
title: nil,
|
||||
headers: [],
|
||||
content: nil
|
||||
end
|
||||
|
||||
defmodule Post do
|
||||
defstruct slug: nil,
|
||||
title: nil,
|
||||
date: nil,
|
||||
tags: [],
|
||||
content: nil,
|
||||
short_content: nil,
|
||||
order: 0
|
||||
end
|
||||
|
||||
defmodule Page do
|
||||
defstruct slug: nil,
|
||||
title: nil,
|
||||
content: nil
|
||||
end
|
||||
|
||||
defmodule Archive do
|
||||
defstruct key: nil,
|
||||
posts: nil
|
||||
end
|
||||
end
|
104
apps/mebe_engine/lib/parser.ex
Normal file
104
apps/mebe_engine/lib/parser.ex
Normal file
|
@ -0,0 +1,104 @@
|
|||
defmodule MebeEngine.Parser do
|
||||
@moduledoc """
|
||||
This module contains the parser, which parses page data and returns the contents in the correct format.
|
||||
"""
|
||||
|
||||
alias MebeEngine.Models.PageData
|
||||
alias MebeEngine.Models.Page
|
||||
alias MebeEngine.Models.Post
|
||||
|
||||
def parse(pagedata, filename) do
|
||||
split_lines(pagedata)
|
||||
|> parse_raw(%PageData{filename: filename})
|
||||
|> render_content
|
||||
|> format
|
||||
end
|
||||
|
||||
def split_lines(pagedata) do
|
||||
String.split pagedata, ~R/\r?\n/
|
||||
end
|
||||
|
||||
|
||||
def parse_raw(datalines, pagedata \\ %PageData{}, mode \\ :title)
|
||||
|
||||
def parse_raw([title | rest], pagedata, :title) do
|
||||
parse_raw rest, %{pagedata | title: title}, :headers
|
||||
end
|
||||
|
||||
def parse_raw(["" | rest], pagedata, :headers) do
|
||||
# Reverse the headers so they appear in the that they do in the file
|
||||
parse_raw rest, %{pagedata | headers: Enum.reverse(pagedata.headers)}, :content
|
||||
end
|
||||
|
||||
def parse_raw([header | rest], pagedata, :headers) do
|
||||
headers = [header | pagedata.headers]
|
||||
parse_raw rest, %{pagedata | headers: headers}, :headers
|
||||
end
|
||||
|
||||
def parse_raw(content, pagedata, :content) when is_list(content) do
|
||||
%{pagedata | content: Enum.join(content, "\n")}
|
||||
end
|
||||
|
||||
|
||||
|
||||
def render_content(pagedata) do
|
||||
%{pagedata | content: Earmark.to_html pagedata.content}
|
||||
end
|
||||
|
||||
|
||||
|
||||
def format(%PageData{
|
||||
filename: filename,
|
||||
title: title,
|
||||
headers: headers,
|
||||
content: content
|
||||
}) do
|
||||
|
||||
case Regex.run(~R/^(?:(\d{4})-(\d{2})-(\d{2})(?:-(\d{2}))?-)?(.*?).md$/iu, filename) do
|
||||
[_, "", "", "", "", slug] ->
|
||||
%Page{
|
||||
slug: slug,
|
||||
title: title,
|
||||
content: content
|
||||
}
|
||||
|
||||
[_, year, month, day, order, slug] ->
|
||||
[tags | _] = headers
|
||||
|
||||
order = format_order order
|
||||
|
||||
%Post{
|
||||
slug: slug,
|
||||
title: title,
|
||||
date: date_to_int_tuple({year, month, day}),
|
||||
tags: parse_tags(tags),
|
||||
content: content,
|
||||
short_content: hd(String.split content, ~R/<!--\s*SPLIT\s*-->/u),
|
||||
order: order
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp parse_tags(tagline) do
|
||||
String.split tagline, ~R/,\s*/iu
|
||||
end
|
||||
|
||||
defp date_to_int_tuple({year, month, day}) do
|
||||
{
|
||||
str_to_int(year),
|
||||
str_to_int(month),
|
||||
str_to_int(day)
|
||||
}
|
||||
end
|
||||
|
||||
defp str_to_int(str) do
|
||||
{int, _} = String.lstrip(str, ?0)
|
||||
|> Integer.parse
|
||||
|
||||
int
|
||||
end
|
||||
|
||||
defp format_order(""), do: 0
|
||||
defp format_order(order), do: str_to_int order
|
||||
end
|
46
apps/mebe_engine/lib/worker.ex
Normal file
46
apps/mebe_engine/lib/worker.ex
Normal file
|
@ -0,0 +1,46 @@
|
|||
defmodule MebeEngine.Worker do
|
||||
@moduledoc """
|
||||
This worker initializes the post database and keeps it alive while the server is running.
|
||||
"""
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
alias MebeEngine.Crawler
|
||||
alias MebeEngine.DB
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, :ok, opts)
|
||||
end
|
||||
|
||||
def init(:ok) do
|
||||
load_db
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Initialize the database by crawling the configured path and parsing data to the DB.
|
||||
"""
|
||||
def load_db() do
|
||||
Logger.info "Loading post database…"
|
||||
|
||||
%{
|
||||
pages: pages,
|
||||
posts: posts,
|
||||
tags: tags,
|
||||
years: years,
|
||||
months: months,
|
||||
} = MebeEngine.Crawler.crawl Application.get_env(:mebe_engine, :data_path)
|
||||
|
||||
Logger.info "Loaded #{Enum.count pages} pages and #{Enum.count posts} posts."
|
||||
|
||||
DB.init
|
||||
|
||||
Enum.each posts, fn post -> DB.insert_post post end
|
||||
Enum.each Map.keys(pages), fn page -> DB.insert_page pages[page] end
|
||||
Enum.each Map.keys(tags), fn tag -> DB.insert_tag(tag, tags[tag]) end
|
||||
Enum.each Map.keys(years), fn year -> DB.insert_year(year, years[year]) end
|
||||
Enum.each Map.keys(months), fn month -> DB.insert_month(month, months[month]) end
|
||||
|
||||
Logger.info "Posts loaded."
|
||||
end
|
||||
end
|
41
apps/mebe_engine/mix.exs
Normal file
41
apps/mebe_engine/mix.exs
Normal file
|
@ -0,0 +1,41 @@
|
|||
defmodule MebeEngine.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :mebe_engine,
|
||||
version: "0.0.1",
|
||||
deps_path: "../../deps",
|
||||
lockfile: "../../mix.lock",
|
||||
elixir: "~> 1.0",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
# Configuration for the OTP application
|
||||
#
|
||||
# Type `mix help compile.app` for more information
|
||||
def application do
|
||||
[applications: [:logger],
|
||||
mod: {MebeEngine, []}]
|
||||
end
|
||||
|
||||
# Dependencies can be Hex packages:
|
||||
#
|
||||
# {:mydep, "~> 0.3.0"}
|
||||
#
|
||||
# Or git/path repositories:
|
||||
#
|
||||
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
|
||||
#
|
||||
# To depend on another app inside the umbrella:
|
||||
#
|
||||
# {:myapp, in_umbrella: true}
|
||||
#
|
||||
# Type `mix help deps` for more examples and options
|
||||
defp deps do
|
||||
[
|
||||
{:earmark, "~> 0.1.16"}
|
||||
]
|
||||
end
|
||||
end
|
7
apps/mebe_engine/test/mebe_engine_test.exs
Normal file
7
apps/mebe_engine/test/mebe_engine_test.exs
Normal file
|
@ -0,0 +1,7 @@
|
|||
defmodule MebeEngineTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "the truth" do
|
||||
assert 1 + 1 == 2
|
||||
end
|
||||
end
|
1
apps/mebe_engine/test/test_helper.exs
Normal file
1
apps/mebe_engine/test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
0
apps/mebe_web/.bowerrc
Normal file
0
apps/mebe_web/.bowerrc
Normal file
19
apps/mebe_web/.gitignore
vendored
Normal file
19
apps/mebe_web/.gitignore
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Mix artifacts
|
||||
/_build
|
||||
/deps
|
||||
/*.ez
|
||||
|
||||
# Generate on crash by the VM
|
||||
erl_crash.dump
|
||||
|
||||
# Configs
|
||||
/config/*.exs
|
||||
|
||||
# Static artifacts
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# Since we are building js and css from web/static,
|
||||
# we ignore priv/static/{css,js}. You may want to
|
||||
# comment this depending on your deployment strategy.
|
||||
/priv/static/
|
8
apps/mebe_web/README.md
Normal file
8
apps/mebe_web/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# MebeWeb
|
||||
|
||||
To start your new Phoenix application:
|
||||
|
||||
1. Install dependencies with `mix deps.get`
|
||||
2. Start Phoenix endpoint with `mix phoenix.server`
|
||||
|
||||
Now you can visit `localhost:4000` from your browser.
|
20
apps/mebe_web/bower.json
Normal file
20
apps/mebe_web/bower.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "mebe_web",
|
||||
"version": "0.0.1",
|
||||
"authors": [
|
||||
"Mikko Ahlroth <mikko.ahlroth@gmail.com>"
|
||||
],
|
||||
"description": "Minimalistic Elixir Blog Engine",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"bootstrap-sass": "~3.3.4",
|
||||
"jquery": "~2.1.4"
|
||||
}
|
||||
}
|
28
apps/mebe_web/config/config.exs.dist
Normal file
28
apps/mebe_web/config/config.exs.dist
Normal file
|
@ -0,0 +1,28 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
#
|
||||
# This configuration file is loaded before any dependency and
|
||||
# is restricted to this project.
|
||||
use Mix.Config
|
||||
|
||||
# Configures the endpoint
|
||||
config :mebe_web, MebeWeb.Endpoint,
|
||||
url: [host: "localhost"],
|
||||
root: Path.expand("..", __DIR__),
|
||||
secret_key_base: "CONFIGURE",
|
||||
debug_errors: false
|
||||
|
||||
config :blog_config,
|
||||
blog_name: "CONFIGURE",
|
||||
blog_author: "CONFIGURE",
|
||||
|
||||
posts_per_page: 10
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, :console,
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
metadata: [:request_id]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env}.exs"
|
27
apps/mebe_web/config/dev.exs.dist
Normal file
27
apps/mebe_web/config/dev.exs.dist
Normal file
|
@ -0,0 +1,27 @@
|
|||
use Mix.Config
|
||||
|
||||
# For development, we disable any cache and enable
|
||||
# debugging and code reloading.
|
||||
#
|
||||
# The watchers configuration can be used to run external
|
||||
# watchers to your application. For example, we use it
|
||||
# with brunch.io to recompile .js and .css sources.
|
||||
config :mebe_web, MebeWeb.Endpoint,
|
||||
http: [port: 4000],
|
||||
debug_errors: true,
|
||||
code_reloader: true,
|
||||
cache_static_lookup: false,
|
||||
watchers: []
|
||||
|
||||
# Watch static and templates for browser reloading.
|
||||
config :mebe_web, MebeWeb.Endpoint,
|
||||
live_reload: [
|
||||
patterns: [
|
||||
~r{priv/static/.*(js|css|png|jpeg|jpg|gif)$},
|
||||
~r{web/views/.*(ex)$},
|
||||
~r{web/templates/.*(eex)$}
|
||||
]
|
||||
]
|
||||
|
||||
# Do not include metadata nor timestamps in development logs
|
||||
config :logger, :console, format: "[$level] $message\n"
|
46
apps/mebe_web/config/prod.exs.dist
Normal file
46
apps/mebe_web/config/prod.exs.dist
Normal file
|
@ -0,0 +1,46 @@
|
|||
use Mix.Config
|
||||
|
||||
# For production, we configure the host to read the PORT
|
||||
# from the system environment. Therefore, you will need
|
||||
# to set PORT=80 before running your server.
|
||||
#
|
||||
# You should also configure the url host to something
|
||||
# meaningful, we use this information when generating URLs.
|
||||
config :mebe_web, MebeWeb.Endpoint,
|
||||
http: [port: {:system, "PORT"}],
|
||||
url: [host: "example.com"],
|
||||
cache_static_manifest: "priv/static/manifest.json"
|
||||
|
||||
# ## SSL Support
|
||||
#
|
||||
# To get SSL working, you will need to add the `https` key
|
||||
# to the previous section:
|
||||
#
|
||||
# config:mebe_web, MebeWeb.Endpoint,
|
||||
# ...
|
||||
# https: [port: 443,
|
||||
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
|
||||
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
|
||||
#
|
||||
# Where those two env variables point to a file on
|
||||
# disk for the key and cert.
|
||||
|
||||
# Do not print debug messages in production
|
||||
config :logger, level: :info
|
||||
|
||||
# ## Using releases
|
||||
#
|
||||
# If you are doing OTP releases, you need to instruct Phoenix
|
||||
# to start the server for all endpoints:
|
||||
#
|
||||
# config :phoenix, :serve_endpoints, true
|
||||
#
|
||||
# Alternatively, you can configure exactly which server to
|
||||
# start per endpoint:
|
||||
#
|
||||
# config :mebe_web, MebeWeb.Endpoint, server: true
|
||||
#
|
||||
|
||||
# Finally import the config/prod.secret.exs
|
||||
# which should be versioned separately.
|
||||
import_config "prod.secret.exs"
|
7
apps/mebe_web/config/prod.secret.exs.dist
Normal file
7
apps/mebe_web/config/prod.secret.exs.dist
Normal file
|
@ -0,0 +1,7 @@
|
|||
use Mix.Config
|
||||
|
||||
# In this file, we keep production configuration that
|
||||
# you likely want to automate and keep it away from
|
||||
# your version control system.
|
||||
config :mebe_web, MebeWeb.Endpoint,
|
||||
secret_key_base: "CONFIGURE"
|
10
apps/mebe_web/config/test.exs.dist
Normal file
10
apps/mebe_web/config/test.exs.dist
Normal file
|
@ -0,0 +1,10 @@
|
|||
use Mix.Config
|
||||
|
||||
# We don't run a server during test. If one is required,
|
||||
# you can enable the server option below.
|
||||
config :mebe_web, MebeWeb.Endpoint,
|
||||
http: [port: 4001],
|
||||
server: false
|
||||
|
||||
# Print only warnings and errors during test
|
||||
config :logger, level: :warn
|
76
apps/mebe_web/gulpfile.js
Normal file
76
apps/mebe_web/gulpfile.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Node modules
|
||||
var fs = require('fs'), vm = require('vm'), chalk = require('chalk');
|
||||
|
||||
// Gulp and plugins
|
||||
var gulp = require('gulp'), concat = require('gulp-concat'),
|
||||
replace = require('gulp-replace'), uglify = require('gulp-uglify');
|
||||
|
||||
// Gulp minify for smallinizing our CSS
|
||||
var minify = require('gulp-minify-css');
|
||||
|
||||
// Gulp filesize for printing sizes before and after minification
|
||||
var size = require('gulp-size');
|
||||
|
||||
// SASS compiler
|
||||
var sass = require('gulp-sass');
|
||||
|
||||
// Deleting files
|
||||
var vinylPaths = require('vinyl-paths');
|
||||
var del = require('del');
|
||||
|
||||
// Source maps
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
var bower_path = 'bower_components/';
|
||||
|
||||
var dest_path = 'priv/static/';
|
||||
|
||||
// Minifies all JS files and copies to target path. Files are not concatenated
|
||||
gulp.task('js', function() {
|
||||
return gulp.src([
|
||||
// Uncomment these two lines if you need bootstrap or jQuery
|
||||
//bower_path + 'jquery/dist/jquery.js',
|
||||
//bower_path + 'bootstrap-sass/assets/javascripts/bootstrap.js',
|
||||
'web/static/js/*.js'
|
||||
])
|
||||
|
||||
//.pipe(concat('app.js'))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(size({title: 'Original JS'}))
|
||||
.pipe(uglify({ preserveComments: false }).on('error', console.error.bind(console)))
|
||||
.pipe(size({title: 'Minified JS'}))
|
||||
.pipe(sourcemaps.write('./'))
|
||||
.pipe(gulp.dest(dest_path + 'js/'));
|
||||
});
|
||||
|
||||
// Compiles SASS files. Will create one output file for each input file, but usually you
|
||||
// only have app.scss which will be output as app.css
|
||||
gulp.task('css', function() {
|
||||
return gulp.src([
|
||||
'web/static/css/*.scss'
|
||||
])
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(size({title: 'Compiled CSS'}))
|
||||
.pipe(minify())
|
||||
.pipe(size({title: 'Minified CSS'}))
|
||||
.pipe(sourcemaps.write('./'))
|
||||
.pipe(gulp.dest(dest_path + 'css/'));
|
||||
});
|
||||
|
||||
// Copies fonts
|
||||
gulp.task('fonts', function() {
|
||||
return gulp.src(bower_path + 'bootstrap-sass/assets/fonts/bootstrap/*')
|
||||
.pipe(gulp.dest(dest_path + 'fonts/'));
|
||||
});
|
||||
|
||||
// Removes all files from dest_path
|
||||
gulp.task('clean', function() {
|
||||
return gulp.src(dest_path + '**/*', { read: false })
|
||||
.pipe(vinylPaths(del));
|
||||
});
|
||||
|
||||
gulp.task('default', ['js', 'css', 'fonts'], function(callback) {
|
||||
callback();
|
||||
console.log('\nPlaced optimized files in ' + chalk.magenta(dest_path));
|
||||
});
|
26
apps/mebe_web/lib/mebe_web.ex
Normal file
26
apps/mebe_web/lib/mebe_web.ex
Normal file
|
@ -0,0 +1,26 @@
|
|||
defmodule MebeWeb do
|
||||
use Application
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec, warn: false
|
||||
|
||||
children = [
|
||||
# Start the endpoint when the application starts
|
||||
supervisor(MebeWeb.Endpoint, []),
|
||||
]
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: MebeWeb.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
# whenever the application is updated.
|
||||
def config_change(changed, _new, removed) do
|
||||
MebeWeb.Endpoint.config_change(changed, removed)
|
||||
:ok
|
||||
end
|
||||
end
|
33
apps/mebe_web/lib/mebe_web/endpoint.ex
Normal file
33
apps/mebe_web/lib/mebe_web/endpoint.ex
Normal file
|
@ -0,0 +1,33 @@
|
|||
defmodule MebeWeb.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :mebe_web
|
||||
|
||||
# Serve at "/" the given assets from "priv/static" directory
|
||||
plug Plug.Static,
|
||||
at: "/", from: :mebe_web,
|
||||
only: ~w(css images js favicon.ico robots.txt)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
plug Phoenix.LiveReloader
|
||||
plug Phoenix.CodeReloader
|
||||
end
|
||||
|
||||
plug Plug.Logger
|
||||
|
||||
plug Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Poison
|
||||
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
|
||||
plug Plug.Session,
|
||||
store: :cookie,
|
||||
key: "_mebe_web_key",
|
||||
signing_salt: "WWhqeGIn",
|
||||
encryption_salt: "z6/IRyg7"
|
||||
|
||||
plug :router, MebeWeb.Router
|
||||
end
|
41
apps/mebe_web/mix.exs
Normal file
41
apps/mebe_web/mix.exs
Normal file
|
@ -0,0 +1,41 @@
|
|||
defmodule MebeWeb.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :mebe_web,
|
||||
version: "0.0.1",
|
||||
deps_path: "../../deps",
|
||||
lockfile: "../../mix.lock",
|
||||
elixir: "~> 1.0",
|
||||
elixirc_paths: elixirc_paths(Mix.env),
|
||||
compilers: [:phoenix] ++ Mix.compilers,
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
# Configuration for the OTP application
|
||||
#
|
||||
# Type `mix help compile.app` for more information
|
||||
def application do
|
||||
[mod: {MebeWeb, []},
|
||||
applications: [:phoenix, :cowboy, :logger]]
|
||||
end
|
||||
|
||||
# Specifies which paths to compile per environment
|
||||
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib", "web"]
|
||||
|
||||
# Specifies your project dependencies
|
||||
#
|
||||
# Type `mix help deps` for examples and options
|
||||
defp deps do
|
||||
[
|
||||
{:phoenix, "~> 0.13"},
|
||||
{:phoenix_live_reload, "~> 0.4"},
|
||||
{:phoenix_html, "~> 1.0.1"},
|
||||
{:cowboy, "~> 1.0"},
|
||||
{:mebe_engine, in_umbrella: true}
|
||||
]
|
||||
end
|
||||
end
|
22
apps/mebe_web/package.json
Normal file
22
apps/mebe_web/package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "mebe_web",
|
||||
"version": "0.0.1",
|
||||
"repository": {},
|
||||
"devDependencies": {
|
||||
"bower": "~1.4.1",
|
||||
"chalk": "^1.0.0",
|
||||
"del": "^1.1.1",
|
||||
"end-of-stream": "~0.1.5",
|
||||
"event-stream": "~3.3.1",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-concat": "~2.5.2",
|
||||
"gulp-html-replace": "~1.4.5",
|
||||
"gulp-minify-css": "~1.1.1",
|
||||
"gulp-replace": "~0.5.3",
|
||||
"gulp-sass": "~2.0.0",
|
||||
"gulp-size": "~1.2.1",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-uglify": "~1.2.0",
|
||||
"vinyl-paths": "^1.0.0"
|
||||
}
|
||||
}
|
8
apps/mebe_web/test/controllers/page_controller_test.exs
Normal file
8
apps/mebe_web/test/controllers/page_controller_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
|||
defmodule MebeWeb.PageControllerTest do
|
||||
use MebeWeb.ConnCase
|
||||
|
||||
test "GET /" do
|
||||
conn = get conn(), "/"
|
||||
assert conn.resp_body =~ "Welcome to Phoenix!"
|
||||
end
|
||||
end
|
43
apps/mebe_web/test/support/conn_case.ex
Normal file
43
apps/mebe_web/test/support/conn_case.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defmodule MebeWeb.ConnCase do
|
||||
@moduledoc """
|
||||
This module defines the test case to be used by
|
||||
tests that require setting up a connection.
|
||||
|
||||
Such tests rely on `Phoenix.ConnTest` and also
|
||||
imports other functionalities to make it easier
|
||||
to build and query models.
|
||||
|
||||
Finally, if the test case interacts with the database,
|
||||
it cannot be async. For this reason, every test runs
|
||||
inside a transaction which is reset at the beginning
|
||||
of the test unless the test case is marked as async.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
||||
using do
|
||||
quote do
|
||||
# Import conveniences for testing with connections
|
||||
use Phoenix.ConnTest
|
||||
|
||||
# Alias the data repository and import query/model functions
|
||||
alias MebeWeb.Repo
|
||||
import Ecto.Model
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
# Import URL helpers from the router
|
||||
import MebeWeb.Router.Helpers
|
||||
|
||||
# The default endpoint for testing
|
||||
@endpoint MebeWeb.Endpoint
|
||||
end
|
||||
end
|
||||
|
||||
setup tags do
|
||||
unless tags[:async] do
|
||||
Ecto.Adapters.SQL.restart_test_transaction(MebeWeb.Repo, [])
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
6
apps/mebe_web/test/test_helper.exs
Normal file
6
apps/mebe_web/test/test_helper.exs
Normal file
|
@ -0,0 +1,6 @@
|
|||
ExUnit.start
|
||||
|
||||
# Create the database, run migrations, and start the test transaction.
|
||||
Mix.Task.run "ecto.create", ["--quiet"]
|
||||
Mix.Task.run "ecto.migrate", ["--quiet"]
|
||||
Ecto.Adapters.SQL.begin_test_transaction(MebeWeb.Repo)
|
80
apps/mebe_web/web/controllers/page_controller.ex
Normal file
80
apps/mebe_web/web/controllers/page_controller.ex
Normal file
|
@ -0,0 +1,80 @@
|
|||
defmodule MebeWeb.PageController do
|
||||
use MebeWeb.Web, :controller
|
||||
|
||||
alias MebeEngine.DB
|
||||
|
||||
plug :action
|
||||
|
||||
@posts_per_page Application.get_env(:blog_config, :posts_per_page)
|
||||
|
||||
def index(conn, params) do
|
||||
{first, last} = calculate_ranges get_page params
|
||||
|
||||
|
||||
|
||||
case DB.get_reg_posts first, last do
|
||||
[] ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> render(MebeWeb.ErrorView, :"404")
|
||||
|
||||
posts ->
|
||||
conn
|
||||
|> assign(:posts, posts)
|
||||
|> render("index.html")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def post(conn, %{"year" => year, "month" => month, "day" => day, "slug" => slug}) do
|
||||
case {
|
||||
Integer.parse(year),
|
||||
Integer.parse(month),
|
||||
Integer.parse(day)
|
||||
} do
|
||||
{{y_int, _}, {m_int, _}, {d_int, _}} ->
|
||||
case DB.get_post y_int, m_int, d_int, slug do
|
||||
nil ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> render(MebeWeb.ErrorView, :"404")
|
||||
|
||||
post ->
|
||||
conn
|
||||
|> assign(:post, post)
|
||||
|> render("post.html")
|
||||
end
|
||||
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> render(MebeWeb.ErrorView, :"404")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def tag(conn, params) do
|
||||
"tag"
|
||||
end
|
||||
|
||||
|
||||
#defp posts_per_page(), do:
|
||||
|
||||
defp get_page(params) do
|
||||
case params do
|
||||
%{"page" => page} ->
|
||||
{page_int, ""} = Integer.parse page
|
||||
page_int
|
||||
|
||||
_ -> 1
|
||||
end
|
||||
end
|
||||
|
||||
defp calculate_ranges(page) do
|
||||
IO.inspect @posts_per_page
|
||||
{
|
||||
(page - 1) * @posts_per_page,
|
||||
@posts_per_page
|
||||
}
|
||||
end
|
||||
end
|
28
apps/mebe_web/web/router.ex
Normal file
28
apps/mebe_web/web/router.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
defmodule MebeWeb.Router do
|
||||
use Phoenix.Router
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug :fetch_flash
|
||||
plug :protect_from_forgery
|
||||
end
|
||||
|
||||
scope "/", MebeWeb do
|
||||
pipe_through :browser
|
||||
|
||||
get "/", PageController, :index
|
||||
get "/p/:page", PageController, :index
|
||||
|
||||
get "/tag/:tag", PageController, :tag
|
||||
get "/tag/:tag/p/:page", PageController, :tag
|
||||
|
||||
get "/archive/:year", PageController, :year
|
||||
get "/archive/:year/p/:page", PageController, :year
|
||||
get "/archive/:year/:month", PageController, :month
|
||||
get "/archive/:year/:month/p/:page", PageController, :month
|
||||
|
||||
get "/:year/:month/:day/:slug", PageController, :post
|
||||
get "/:slug", PageController, :page
|
||||
end
|
||||
end
|
334
apps/mebe_web/web/static/css/_bootswatch.scss
Normal file
334
apps/mebe_web/web/static/css/_bootswatch.scss
Normal file
|
@ -0,0 +1,334 @@
|
|||
// Darkly 3.3.4
|
||||
// Bootswatch
|
||||
// -----------------------------------------------------
|
||||
|
||||
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
|
||||
|
||||
// Navbar =====================================================================
|
||||
|
||||
.navbar {
|
||||
border-width: 0;
|
||||
|
||||
&-default {
|
||||
|
||||
.badge {
|
||||
background-color: #fff;
|
||||
color: $navbar-default-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&-inverse {
|
||||
|
||||
.badge {
|
||||
background-color: #fff;
|
||||
color: $navbar-inverse-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&-brand {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&-form {
|
||||
.form-control {
|
||||
background-color: white;
|
||||
|
||||
&:focus {
|
||||
border-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons ====================================================================
|
||||
|
||||
.btn {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.btn-group.open .dropdown-toggle {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
// Typography =================================================================
|
||||
|
||||
.text-primary,
|
||||
.text-primary:hover {
|
||||
color: lighten($brand-primary, 10%);
|
||||
}
|
||||
|
||||
.text-success,
|
||||
.text-success:hover {
|
||||
color: $brand-success;
|
||||
}
|
||||
|
||||
.text-danger,
|
||||
.text-danger:hover {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
.text-warning,
|
||||
.text-warning:hover {
|
||||
color: $brand-warning;
|
||||
}
|
||||
|
||||
.text-info,
|
||||
.text-info:hover {
|
||||
color: $brand-info;
|
||||
}
|
||||
|
||||
// Tables =====================================================================
|
||||
|
||||
table,
|
||||
.table {
|
||||
|
||||
a:not(.btn) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dropdown-menu a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.success,
|
||||
.warning,
|
||||
.danger,
|
||||
.info {
|
||||
color: #fff;
|
||||
|
||||
> th > a,
|
||||
> td > a,
|
||||
> a {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
> thead > tr > th,
|
||||
> tbody > tr > th,
|
||||
> tfoot > tr > th,
|
||||
> thead > tr > td,
|
||||
> tbody > tr > td,
|
||||
> tfoot > tr > td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&-bordered > thead > tr > th,
|
||||
&-bordered > tbody > tr > th,
|
||||
&-bordered > tfoot > tr > th,
|
||||
&-bordered > thead > tr > td,
|
||||
&-bordered > tbody > tr > td,
|
||||
&-bordered > tfoot > tr > td {
|
||||
border: 1px solid $table-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Forms ======================================================================
|
||||
|
||||
input,
|
||||
textarea {
|
||||
color: $input-color;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
input,
|
||||
textarea {
|
||||
border: 2px hidden transparent;
|
||||
@include box-shadow(none);
|
||||
|
||||
&:focus {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
}
|
||||
|
||||
.has-warning {
|
||||
.help-block,
|
||||
.control-label,
|
||||
.radio,
|
||||
.checkbox,
|
||||
.radio-inline,
|
||||
.checkbox-inline,
|
||||
.form-control-feedback {
|
||||
color: $brand-warning;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-control:focus {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
border-color: $brand-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.has-error {
|
||||
.help-block,
|
||||
.control-label,
|
||||
.radio,
|
||||
.checkbox,
|
||||
.radio-inline,
|
||||
.checkbox-inline,
|
||||
.form-control-feedback {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-control:focus {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
border-color: $brand-danger;
|
||||
}
|
||||
}
|
||||
|
||||
.has-success {
|
||||
.help-block,
|
||||
.control-label,
|
||||
.radio,
|
||||
.checkbox,
|
||||
.radio-inline,
|
||||
.checkbox-inline,
|
||||
.form-control-feedback {
|
||||
color: $brand-success;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-control:focus {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
border-color: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
// Navs =======================================================================
|
||||
|
||||
.nav {
|
||||
.open > a,
|
||||
.open > a:hover,
|
||||
.open > a:focus {
|
||||
border-color: $nav-tabs-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs > li > a,
|
||||
.nav-pills > li > a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pager {
|
||||
a,
|
||||
a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
&>a,
|
||||
&>a:hover,
|
||||
&>a:focus,
|
||||
&>span {
|
||||
background-color: $pagination-disabled-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
// Indicators =================================================================
|
||||
|
||||
.close {
|
||||
text-decoration: none;
|
||||
text-shadow: none;
|
||||
opacity: 0.4;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
.alert-link {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bars ==============================================================
|
||||
|
||||
.progress {
|
||||
height: 10px;
|
||||
@include box-shadow(none);
|
||||
.progress-bar {
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// Containers =================================================================
|
||||
|
||||
.well {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
a.list-group-item {
|
||||
|
||||
&.active,
|
||||
&.active:hover,
|
||||
&.active:focus {
|
||||
border-color: $list-group-border;
|
||||
}
|
||||
|
||||
&-success {
|
||||
&.active {
|
||||
background-color: $state-success-bg;
|
||||
}
|
||||
|
||||
&.active:hover,
|
||||
&.active:focus {
|
||||
background-color: darken($state-success-bg, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
&-warning {
|
||||
&.active {
|
||||
background-color: $state-warning-bg;
|
||||
}
|
||||
|
||||
&.active:hover,
|
||||
&.active:focus {
|
||||
background-color: darken($state-warning-bg, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
&-danger {
|
||||
&.active {
|
||||
background-color: $state-danger-bg;
|
||||
}
|
||||
|
||||
&.active:hover,
|
||||
&.active:focus {
|
||||
background-color: darken($state-danger-bg, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
.panel-default > .panel-heading {
|
||||
background-color: $panel-footer-bg;
|
||||
}
|
861
apps/mebe_web/web/static/css/_variables.scss
Normal file
861
apps/mebe_web/web/static/css/_variables.scss
Normal file
|
@ -0,0 +1,861 @@
|
|||
// Darkly 3.3.4
|
||||
// Variables
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
//== Colors
|
||||
//
|
||||
//## Gray and brand colors for use across Bootstrap.
|
||||
|
||||
$gray-base: #000;
|
||||
$gray-darker: lighten($gray-base, 13.5%); // #222
|
||||
$gray-dark: #303030; // #333
|
||||
$gray: #464545;
|
||||
$gray-light: #999; // #999
|
||||
$gray-lighter: #EBEBEB; // #eee
|
||||
|
||||
$brand-primary: #375a7f;
|
||||
$brand-success: #00bc8c;
|
||||
$brand-info: #3498DB;
|
||||
$brand-warning: #F39C12;
|
||||
$brand-danger: #E74C3C;
|
||||
|
||||
|
||||
//== Scaffolding
|
||||
//
|
||||
//## Settings for some of the most global styles.
|
||||
|
||||
//** Background color for `<body>`.
|
||||
$body-bg: $gray-darker;
|
||||
//** Global text color on `<body>`.
|
||||
$text-color: #fff;
|
||||
|
||||
//** Global textual link color.
|
||||
$link-color: desaturate(lighten($brand-success, 10%),10%);
|
||||
//** Link hover color set via `darken()` function.
|
||||
$link-hover-color: $link-color;
|
||||
//** Link hover decoration.
|
||||
$link-hover-decoration: underline;
|
||||
|
||||
|
||||
//== Typography
|
||||
//
|
||||
//## Font, line-height, and color for body text, headings, and more.
|
||||
|
||||
$font-family-sans-serif: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
|
||||
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
$font-family-base: $font-family-sans-serif;
|
||||
|
||||
$font-size-base: 15px;
|
||||
$font-size-large: ceil(($font-size-base * 1.25)); // ~18px
|
||||
$font-size-small: ceil(($font-size-base * 0.85)); // ~12px
|
||||
|
||||
$font-size-h1: floor(($font-size-base * 2.6)); // ~36px
|
||||
$font-size-h2: floor(($font-size-base * 2.15)); // ~30px
|
||||
$font-size-h3: ceil(($font-size-base * 1.7)); // ~24px
|
||||
$font-size-h4: ceil(($font-size-base * 1.25)); // ~18px
|
||||
$font-size-h5: $font-size-base;
|
||||
$font-size-h6: ceil(($font-size-base * 0.85)); // ~12px
|
||||
|
||||
//** Unit-less `line-height` for use in components like buttons.
|
||||
$line-height-base: 1.428571429; // 20/14
|
||||
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
|
||||
$line-height-computed: floor(($font-size-base * $line-height-base)); // ~20px
|
||||
|
||||
//** By default, this inherits from the `<body>`.
|
||||
$headings-font-family: $font-family-base;
|
||||
$headings-font-weight: 400;
|
||||
$headings-line-height: 1.1;
|
||||
$headings-color: inherit;
|
||||
|
||||
|
||||
//== Iconography
|
||||
//
|
||||
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
|
||||
|
||||
//** Load fonts from this directory.
|
||||
$icon-font-path: "../fonts/";
|
||||
//** File name for all font files.
|
||||
$icon-font-name: "glyphicons-halflings-regular";
|
||||
//** Element ID within SVG icon file.
|
||||
$icon-font-svg-id: "glyphicons_halflingsregular";
|
||||
|
||||
|
||||
//== Components
|
||||
//
|
||||
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
|
||||
|
||||
$padding-base-vertical: 10px;
|
||||
$padding-base-horizontal: 15px;
|
||||
|
||||
$padding-large-vertical: 18px;
|
||||
$padding-large-horizontal: 27px;
|
||||
|
||||
$padding-small-vertical: 6px;
|
||||
$padding-small-horizontal: 9px;
|
||||
|
||||
$padding-xs-vertical: 1px;
|
||||
$padding-xs-horizontal: 5px;
|
||||
|
||||
$line-height-large: 1.3333333; // extra decimals for Win 8.1 Chrome
|
||||
$line-height-small: 1.5;
|
||||
|
||||
$border-radius-base: 4px;
|
||||
$border-radius-large: 6px;
|
||||
$border-radius-small: 3px;
|
||||
|
||||
//** Global color for active items (e.g., navs or dropdowns).
|
||||
$component-active-color: #fff;
|
||||
//** Global background color for active items (e.g., navs or dropdowns).
|
||||
$component-active-bg: $brand-primary;
|
||||
|
||||
//** Width of the `border` for generating carets that indicator dropdowns.
|
||||
$caret-width-base: 4px;
|
||||
//** Carets increase slightly in size for larger components.
|
||||
$caret-width-large: 5px;
|
||||
|
||||
|
||||
//== Tables
|
||||
//
|
||||
//## Customizes the `.table` component with basic values, each used across all table variations.
|
||||
|
||||
//** Padding for `<th>`s and `<td>`s.
|
||||
$table-cell-padding: 8px;
|
||||
//** Padding for cells in `.table-condensed`.
|
||||
$table-condensed-cell-padding: 5px;
|
||||
|
||||
//** Default background color used for all tables.
|
||||
$table-bg: transparent;
|
||||
//** Background color used for `.table-striped`.
|
||||
$table-bg-accent: lighten($gray-dark, 5%);
|
||||
//** Background color used for `.table-hover`.
|
||||
$table-bg-hover: $gray;
|
||||
$table-bg-active: $table-bg-hover;
|
||||
|
||||
//** Border color for table and cell borders.
|
||||
$table-border-color: $gray;
|
||||
|
||||
|
||||
//== Buttons
|
||||
//
|
||||
//## For each of Bootstrap's buttons, define text, background and border color.
|
||||
|
||||
$btn-font-weight: normal;
|
||||
|
||||
$btn-default-color: $text-color;
|
||||
$btn-default-bg: $gray;
|
||||
$btn-default-border: $btn-default-bg;
|
||||
|
||||
$btn-primary-color: #fff;
|
||||
$btn-primary-bg: $brand-primary;
|
||||
$btn-primary-border: $btn-primary-bg;
|
||||
|
||||
$btn-success-color: $btn-primary-color;
|
||||
$btn-success-bg: $brand-success;
|
||||
$btn-success-border: $btn-success-bg;
|
||||
|
||||
$btn-info-color: $btn-success-color;
|
||||
$btn-info-bg: $brand-info;
|
||||
$btn-info-border: $btn-info-bg;
|
||||
|
||||
$btn-warning-color: $btn-success-color;
|
||||
$btn-warning-bg: $brand-warning;
|
||||
$btn-warning-border: $btn-warning-bg;
|
||||
|
||||
$btn-danger-color: $btn-success-color;
|
||||
$btn-danger-bg: $brand-danger;
|
||||
$btn-danger-border: $btn-danger-bg;
|
||||
|
||||
$btn-link-disabled-color: $gray-light;
|
||||
|
||||
|
||||
//== Forms
|
||||
//
|
||||
//##
|
||||
|
||||
//** `<input>` background color
|
||||
$input-bg: #fff;
|
||||
//** `<input disabled>` background color
|
||||
$input-bg-disabled: $gray-lighter;
|
||||
|
||||
//** Text color for `<input>`s
|
||||
$input-color: $gray;
|
||||
//** `<input>` border color
|
||||
$input-border: #f1f1f1;
|
||||
|
||||
// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
|
||||
//** Default `.form-control` border radius
|
||||
// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
|
||||
$input-border-radius: $border-radius-base;
|
||||
//** Large `.form-control` border radius
|
||||
$input-border-radius-large: $border-radius-large;
|
||||
//** Small `.form-control` border radius
|
||||
$input-border-radius-small: $border-radius-small;
|
||||
|
||||
//** Border color for inputs on focus
|
||||
$input-border-focus: #fff;
|
||||
|
||||
//** Placeholder text color
|
||||
$input-color-placeholder: $gray-light;
|
||||
|
||||
//** Default `.form-control` height
|
||||
$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 4);
|
||||
//** Large `.form-control` height
|
||||
$input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 4);
|
||||
//** Small `.form-control` height
|
||||
$input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 4);
|
||||
|
||||
//** `.form-group` margin
|
||||
$form-group-margin-bottom: 15px;
|
||||
|
||||
$legend-color: $text-color;
|
||||
$legend-border-color: transparent;
|
||||
|
||||
//** Background color for textual input addons
|
||||
$input-group-addon-bg: $gray;
|
||||
//** Border color for textual input addons
|
||||
$input-group-addon-border-color: transparent;
|
||||
|
||||
//** Disabled cursor for form controls and buttons.
|
||||
$cursor-disabled: not-allowed;
|
||||
|
||||
|
||||
//== Dropdowns
|
||||
//
|
||||
//## Dropdown menu container and contents.
|
||||
|
||||
//** Background for the dropdown menu.
|
||||
$dropdown-bg: $gray-dark;
|
||||
//** Dropdown menu `border-color`.
|
||||
$dropdown-border: rgba(0,0,0,.15);
|
||||
//** Dropdown menu `border-color` **for IE8**.
|
||||
$dropdown-fallback-border: #ccc;
|
||||
//** Divider color for between dropdown items.
|
||||
$dropdown-divider-bg: $gray;
|
||||
|
||||
//** Dropdown link text color.
|
||||
$dropdown-link-color: $gray-lighter;
|
||||
//** Hover color for dropdown links.
|
||||
$dropdown-link-hover-color: #fff;
|
||||
//** Hover background for dropdown links.
|
||||
$dropdown-link-hover-bg: $component-active-bg;
|
||||
|
||||
//** Active dropdown menu item text color.
|
||||
$dropdown-link-active-color: #fff;
|
||||
//** Active dropdown menu item background color.
|
||||
$dropdown-link-active-bg: $component-active-bg;
|
||||
|
||||
//** Disabled dropdown menu item background color.
|
||||
$dropdown-link-disabled-color: $gray-light;
|
||||
|
||||
//** Text color for headers within dropdown menus.
|
||||
$dropdown-header-color: $gray-light;
|
||||
|
||||
//** Deprecated `$dropdown-caret-color` as of v3.1.0
|
||||
$dropdown-caret-color: #000;
|
||||
|
||||
|
||||
//-- Z-index master list
|
||||
//
|
||||
// Warning: Avoid customizing these values. They're used for a bird's eye view
|
||||
// of components dependent on the z-axis and are designed to all work together.
|
||||
//
|
||||
// Note: These variables are not generated into the Customizer.
|
||||
|
||||
$zindex-navbar: 1000;
|
||||
$zindex-dropdown: 1000;
|
||||
$zindex-popover: 1060;
|
||||
$zindex-tooltip: 1070;
|
||||
$zindex-navbar-fixed: 1030;
|
||||
$zindex-modal-background: 1040;
|
||||
$zindex-modal: 1050;
|
||||
|
||||
|
||||
//== Media queries breakpoints
|
||||
//
|
||||
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
|
||||
|
||||
// Extra small screen / phone
|
||||
//** Deprecated `$screen-xs` as of v3.0.1
|
||||
$screen-xs: 480px;
|
||||
//** Deprecated `$screen-xs-min` as of v3.2.0
|
||||
$screen-xs-min: $screen-xs;
|
||||
//** Deprecated `$screen-phone` as of v3.0.1
|
||||
$screen-phone: $screen-xs-min;
|
||||
|
||||
// Small screen / tablet
|
||||
//** Deprecated `$screen-sm` as of v3.0.1
|
||||
$screen-sm: 768px;
|
||||
$screen-sm-min: $screen-sm;
|
||||
//** Deprecated `$screen-tablet` as of v3.0.1
|
||||
$screen-tablet: $screen-sm-min;
|
||||
|
||||
// Medium screen / desktop
|
||||
//** Deprecated `$screen-md` as of v3.0.1
|
||||
$screen-md: 992px;
|
||||
$screen-md-min: $screen-md;
|
||||
//** Deprecated `$screen-desktop` as of v3.0.1
|
||||
$screen-desktop: $screen-md-min;
|
||||
|
||||
// Large screen / wide desktop
|
||||
//** Deprecated `$screen-lg` as of v3.0.1
|
||||
$screen-lg: 1200px;
|
||||
$screen-lg-min: $screen-lg;
|
||||
//** Deprecated `$screen-lg-desktop` as of v3.0.1
|
||||
$screen-lg-desktop: $screen-lg-min;
|
||||
|
||||
// So media queries don't overlap when required, provide a maximum
|
||||
$screen-xs-max: ($screen-sm-min - 1);
|
||||
$screen-sm-max: ($screen-md-min - 1);
|
||||
$screen-md-max: ($screen-lg-min - 1);
|
||||
|
||||
|
||||
//== Grid system
|
||||
//
|
||||
//## Define your custom responsive grid.
|
||||
|
||||
//** Number of columns in the grid.
|
||||
$grid-columns: 12;
|
||||
//** Padding between columns. Gets divided in half for the left and right.
|
||||
$grid-gutter-width: 30px;
|
||||
// Navbar collapse
|
||||
//** Point at which the navbar becomes uncollapsed.
|
||||
$grid-float-breakpoint: $screen-sm-min;
|
||||
//** Point at which the navbar begins collapsing.
|
||||
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1);
|
||||
|
||||
|
||||
//== Container sizes
|
||||
//
|
||||
//## Define the maximum width of `.container` for different screen sizes.
|
||||
|
||||
// Small screen / tablet
|
||||
$container-tablet: (720px + $grid-gutter-width);
|
||||
//** For `$screen-sm-min` and up.
|
||||
$container-sm: $container-tablet;
|
||||
|
||||
// Medium screen / desktop
|
||||
$container-desktop: (940px + $grid-gutter-width);
|
||||
//** For `$screen-md-min` and up.
|
||||
$container-md: $container-desktop;
|
||||
|
||||
// Large screen / wide desktop
|
||||
$container-large-desktop: (1140px + $grid-gutter-width);
|
||||
//** For `$screen-lg-min` and up.
|
||||
$container-lg: $container-large-desktop;
|
||||
|
||||
|
||||
//== Navbar
|
||||
//
|
||||
//##
|
||||
|
||||
// Basics of a navbar
|
||||
$navbar-height: 60px;
|
||||
$navbar-margin-bottom: $line-height-computed;
|
||||
$navbar-border-radius: $border-radius-base;
|
||||
$navbar-padding-horizontal: floor(($grid-gutter-width / 2));
|
||||
$navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2);
|
||||
$navbar-collapse-max-height: 340px;
|
||||
|
||||
$navbar-default-color: #777;
|
||||
$navbar-default-bg: $brand-primary;
|
||||
$navbar-default-border: transparent;
|
||||
|
||||
// Navbar links
|
||||
$navbar-default-link-color: #fff;
|
||||
$navbar-default-link-hover-color: $brand-success;
|
||||
$navbar-default-link-hover-bg: transparent;
|
||||
$navbar-default-link-active-color: #fff;
|
||||
$navbar-default-link-active-bg: darken($navbar-default-bg, 10%);
|
||||
$navbar-default-link-disabled-color: #ccc;
|
||||
$navbar-default-link-disabled-bg: transparent;
|
||||
|
||||
// Navbar brand label
|
||||
$navbar-default-brand-color: $navbar-default-link-color;
|
||||
$navbar-default-brand-hover-color: $navbar-default-link-hover-color;
|
||||
$navbar-default-brand-hover-bg: transparent;
|
||||
|
||||
// Navbar toggle
|
||||
$navbar-default-toggle-hover-bg: darken($navbar-default-bg, 10%);
|
||||
$navbar-default-toggle-icon-bar-bg: #fff;
|
||||
$navbar-default-toggle-border-color: darken($navbar-default-bg, 10%);
|
||||
|
||||
|
||||
// Inverted navbar
|
||||
// Reset inverted navbar basics
|
||||
$navbar-inverse-color: #fff;
|
||||
$navbar-inverse-bg: $brand-success;
|
||||
$navbar-inverse-border: transparent;
|
||||
|
||||
// Inverted navbar links
|
||||
$navbar-inverse-link-color: $navbar-inverse-color;
|
||||
$navbar-inverse-link-hover-color: $brand-primary;
|
||||
$navbar-inverse-link-hover-bg: transparent;
|
||||
$navbar-inverse-link-active-color: $navbar-inverse-color;
|
||||
$navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 5%);
|
||||
$navbar-inverse-link-disabled-color: #aaa;
|
||||
$navbar-inverse-link-disabled-bg: transparent;
|
||||
|
||||
// Inverted navbar brand label
|
||||
$navbar-inverse-brand-color: $navbar-inverse-link-color;
|
||||
$navbar-inverse-brand-hover-color: $navbar-inverse-link-hover-color;
|
||||
$navbar-inverse-brand-hover-bg: transparent;
|
||||
|
||||
// Inverted navbar toggle
|
||||
$navbar-inverse-toggle-hover-bg: darken($navbar-inverse-bg, 10%);
|
||||
$navbar-inverse-toggle-icon-bar-bg: #fff;
|
||||
$navbar-inverse-toggle-border-color: darken($navbar-inverse-bg, 10%);
|
||||
|
||||
|
||||
//== Navs
|
||||
//
|
||||
//##
|
||||
|
||||
//=== Shared nav styles
|
||||
$nav-link-padding: 10px 15px;
|
||||
$nav-link-hover-bg: $gray-dark;
|
||||
|
||||
$nav-disabled-link-color: lighten($gray, 10%);
|
||||
$nav-disabled-link-hover-color: lighten($gray, 10%);
|
||||
|
||||
//== Tabs
|
||||
$nav-tabs-border-color: $gray;
|
||||
|
||||
$nav-tabs-link-hover-border-color: $gray;
|
||||
|
||||
$nav-tabs-active-link-hover-bg: $body-bg;
|
||||
$nav-tabs-active-link-hover-color: $brand-success;
|
||||
$nav-tabs-active-link-hover-border-color: $nav-tabs-link-hover-border-color;
|
||||
|
||||
$nav-tabs-justified-link-border-color: $gray-lighter;
|
||||
$nav-tabs-justified-active-link-border-color: $body-bg;
|
||||
|
||||
//== Pills
|
||||
$nav-pills-border-radius: $border-radius-base;
|
||||
$nav-pills-active-link-hover-bg: $component-active-bg;
|
||||
$nav-pills-active-link-hover-color: $component-active-color;
|
||||
|
||||
|
||||
//== Pagination
|
||||
//
|
||||
//##
|
||||
|
||||
$pagination-color: #fff;
|
||||
$pagination-bg: $brand-success;
|
||||
$pagination-border: transparent;
|
||||
|
||||
$pagination-hover-color: #fff;
|
||||
$pagination-hover-bg: lighten($brand-success, 6%);
|
||||
$pagination-hover-border: transparent;
|
||||
|
||||
$pagination-active-color: #fff;
|
||||
$pagination-active-bg: lighten($brand-success, 6%);
|
||||
$pagination-active-border: transparent;
|
||||
|
||||
$pagination-disabled-color: #fff;
|
||||
$pagination-disabled-bg: darken($brand-success, 15%);
|
||||
$pagination-disabled-border: transparent;
|
||||
|
||||
|
||||
//== Pager
|
||||
//
|
||||
//##
|
||||
|
||||
$pager-bg: $pagination-bg;
|
||||
$pager-border: $pagination-border;
|
||||
$pager-border-radius: 15px;
|
||||
|
||||
$pager-hover-bg: $pagination-hover-bg;
|
||||
|
||||
$pager-active-bg: $pagination-active-bg;
|
||||
$pager-active-color: $pagination-active-color;
|
||||
|
||||
$pager-disabled-color: #ddd;
|
||||
|
||||
|
||||
//== Jumbotron
|
||||
//
|
||||
//##
|
||||
|
||||
$jumbotron-padding: 30px;
|
||||
$jumbotron-color: inherit;
|
||||
$jumbotron-bg: $gray-dark;
|
||||
$jumbotron-heading-color: inherit;
|
||||
$jumbotron-font-size: ceil(($font-size-base * 1.5));
|
||||
|
||||
|
||||
//== Form states and alerts
|
||||
//
|
||||
//## Define colors for form feedback states and, by default, alerts.
|
||||
|
||||
$state-success-text: #fff;
|
||||
$state-success-bg: $brand-success;
|
||||
$state-success-border: $brand-success;
|
||||
|
||||
$state-info-text: #fff;
|
||||
$state-info-bg: $brand-info;
|
||||
$state-info-border: $brand-info;
|
||||
|
||||
$state-warning-text: #fff;
|
||||
$state-warning-bg: $brand-warning;
|
||||
$state-warning-border: $brand-warning;
|
||||
|
||||
$state-danger-text: #fff;
|
||||
$state-danger-bg: $brand-danger;
|
||||
$state-danger-border: $brand-danger;
|
||||
|
||||
|
||||
//== Tooltips
|
||||
//
|
||||
//##
|
||||
|
||||
//** Tooltip max width
|
||||
$tooltip-max-width: 200px;
|
||||
//** Tooltip text color
|
||||
$tooltip-color: #fff;
|
||||
//** Tooltip background color
|
||||
$tooltip-bg: #000;
|
||||
$tooltip-opacity: .9;
|
||||
|
||||
//** Tooltip arrow width
|
||||
$tooltip-arrow-width: 5px;
|
||||
//** Tooltip arrow color
|
||||
$tooltip-arrow-color: $tooltip-bg;
|
||||
|
||||
|
||||
//== Popovers
|
||||
//
|
||||
//##
|
||||
|
||||
//** Popover body background color
|
||||
$popover-bg: $gray-dark;
|
||||
//** Popover maximum width
|
||||
$popover-max-width: 276px;
|
||||
//** Popover border color
|
||||
$popover-border-color: rgba(0,0,0,.2);
|
||||
//** Popover fallback border color
|
||||
$popover-fallback-border-color: #999;
|
||||
|
||||
//** Popover title background color
|
||||
$popover-title-bg: darken($popover-bg, 3%);
|
||||
|
||||
//** Popover arrow width
|
||||
$popover-arrow-width: 10px;
|
||||
//** Popover arrow color
|
||||
$popover-arrow-color: $popover-bg;
|
||||
|
||||
//** Popover outer arrow width
|
||||
$popover-arrow-outer-width: ($popover-arrow-width + 1);
|
||||
//** Popover outer arrow color
|
||||
$popover-arrow-outer-color: fadein($popover-border-color, 5%);
|
||||
//** Popover outer arrow fallback color
|
||||
$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%);
|
||||
|
||||
|
||||
//== Labels
|
||||
//
|
||||
//##
|
||||
|
||||
//** Default label background color
|
||||
$label-default-bg: $gray;
|
||||
//** Primary label background color
|
||||
$label-primary-bg: $brand-primary;
|
||||
//** Success label background color
|
||||
$label-success-bg: $brand-success;
|
||||
//** Info label background color
|
||||
$label-info-bg: $brand-info;
|
||||
//** Warning label background color
|
||||
$label-warning-bg: $brand-warning;
|
||||
//** Danger label background color
|
||||
$label-danger-bg: $brand-danger;
|
||||
|
||||
//** Default label text color
|
||||
$label-color: #fff;
|
||||
//** Default text color of a linked label
|
||||
$label-link-hover-color: #fff;
|
||||
|
||||
|
||||
//== Modals
|
||||
//
|
||||
//##
|
||||
|
||||
//** Padding applied to the modal body
|
||||
$modal-inner-padding: 20px;
|
||||
|
||||
//** Padding applied to the modal title
|
||||
$modal-title-padding: 15px;
|
||||
//** Modal title line-height
|
||||
$modal-title-line-height: $line-height-base;
|
||||
|
||||
//** Background color of modal content area
|
||||
$modal-content-bg: $gray-dark;
|
||||
//** Modal content border color
|
||||
$modal-content-border-color: rgba(0,0,0,.2);
|
||||
//** Modal content border color **for IE8**
|
||||
$modal-content-fallback-border-color: #999;
|
||||
|
||||
//** Modal backdrop background color
|
||||
$modal-backdrop-bg: #000;
|
||||
//** Modal backdrop opacity
|
||||
$modal-backdrop-opacity: .7;
|
||||
//** Modal header border color
|
||||
$modal-header-border-color: $gray;
|
||||
//** Modal footer border color
|
||||
$modal-footer-border-color: $modal-header-border-color;
|
||||
|
||||
$modal-lg: 900px;
|
||||
$modal-md: 600px;
|
||||
$modal-sm: 300px;
|
||||
|
||||
|
||||
//== Alerts
|
||||
//
|
||||
//## Define alert colors, border radius, and padding.
|
||||
|
||||
$alert-padding: 15px;
|
||||
$alert-border-radius: $border-radius-base;
|
||||
$alert-link-font-weight: bold;
|
||||
|
||||
$alert-success-bg: $state-success-bg;
|
||||
$alert-success-text: $state-success-text;
|
||||
$alert-success-border: $state-success-border;
|
||||
|
||||
$alert-info-bg: $state-info-bg;
|
||||
$alert-info-text: $state-info-text;
|
||||
$alert-info-border: $state-info-border;
|
||||
|
||||
$alert-warning-bg: $state-warning-bg;
|
||||
$alert-warning-text: $state-warning-text;
|
||||
$alert-warning-border: $state-warning-border;
|
||||
|
||||
$alert-danger-bg: $state-danger-bg;
|
||||
$alert-danger-text: $state-danger-text;
|
||||
$alert-danger-border: $state-danger-border;
|
||||
|
||||
|
||||
//== Progress bars
|
||||
//
|
||||
//##
|
||||
|
||||
//** Background color of the whole progress component
|
||||
$progress-bg: $gray-lighter;
|
||||
//** Progress bar text color
|
||||
$progress-bar-color: #fff;
|
||||
//** Variable for setting rounded corners on progress bar.
|
||||
$progress-border-radius: $border-radius-base;
|
||||
|
||||
//** Default progress bar color
|
||||
$progress-bar-bg: $brand-primary;
|
||||
//** Success progress bar color
|
||||
$progress-bar-success-bg: $brand-success;
|
||||
//** Warning progress bar color
|
||||
$progress-bar-warning-bg: $brand-warning;
|
||||
//** Danger progress bar color
|
||||
$progress-bar-danger-bg: $brand-danger;
|
||||
//** Info progress bar color
|
||||
$progress-bar-info-bg: $brand-info;
|
||||
|
||||
|
||||
//== List group
|
||||
//
|
||||
//##
|
||||
|
||||
//** Background color on `.list-group-item`
|
||||
$list-group-bg: $gray-dark;
|
||||
//** `.list-group-item` border color
|
||||
$list-group-border: $gray;
|
||||
//** List group border radius
|
||||
$list-group-border-radius: $border-radius-base;
|
||||
|
||||
//** Background color of single list items on hover
|
||||
$list-group-hover-bg: transparent;
|
||||
//** Text color of active list items
|
||||
$list-group-active-color: $component-active-color;
|
||||
//** Background color of active list items
|
||||
$list-group-active-bg: $component-active-bg;
|
||||
//** Border color of active list elements
|
||||
$list-group-active-border: $list-group-active-bg;
|
||||
//** Text color for content within active list items
|
||||
$list-group-active-text-color: lighten($list-group-active-bg, 40%);
|
||||
|
||||
//** Text color of disabled list items
|
||||
$list-group-disabled-color: $gray-light;
|
||||
//** Background color of disabled list items
|
||||
$list-group-disabled-bg: $gray-lighter;
|
||||
//** Text color for content within disabled list items
|
||||
$list-group-disabled-text-color: $list-group-disabled-color;
|
||||
|
||||
$list-group-link-color: $link-color;
|
||||
$list-group-link-hover-color: $list-group-link-color;
|
||||
$list-group-link-heading-color: darken($link-color, 5%);
|
||||
|
||||
|
||||
//== Panels
|
||||
//
|
||||
//##
|
||||
|
||||
$panel-bg: $gray-dark;
|
||||
$panel-body-padding: 15px;
|
||||
$panel-heading-padding: 10px 15px;
|
||||
$panel-footer-padding: $panel-heading-padding;
|
||||
$panel-border-radius: $border-radius-base;
|
||||
|
||||
//** Border color for elements within panels
|
||||
$panel-inner-border: $gray;
|
||||
$panel-footer-bg: $gray;
|
||||
|
||||
$panel-default-text: $text-color;
|
||||
$panel-default-border: $gray;
|
||||
$panel-default-heading-bg: $gray-dark;
|
||||
|
||||
$panel-primary-text: #fff;
|
||||
$panel-primary-border: $brand-primary;
|
||||
$panel-primary-heading-bg: $brand-primary;
|
||||
|
||||
$panel-success-text: $state-success-text;
|
||||
$panel-success-border: $state-success-border;
|
||||
$panel-success-heading-bg: $state-success-bg;
|
||||
|
||||
$panel-info-text: $state-info-text;
|
||||
$panel-info-border: $state-info-border;
|
||||
$panel-info-heading-bg: $state-info-bg;
|
||||
|
||||
$panel-warning-text: $state-warning-text;
|
||||
$panel-warning-border: $state-warning-border;
|
||||
$panel-warning-heading-bg: $state-warning-bg;
|
||||
|
||||
$panel-danger-text: $state-danger-text;
|
||||
$panel-danger-border: $state-danger-border;
|
||||
$panel-danger-heading-bg: $state-danger-bg;
|
||||
|
||||
|
||||
//== Thumbnails
|
||||
//
|
||||
//##
|
||||
|
||||
//** Padding around the thumbnail image
|
||||
$thumbnail-padding: 2px;
|
||||
//** Thumbnail background color
|
||||
$thumbnail-bg: $body-bg;
|
||||
//** Thumbnail border color
|
||||
$thumbnail-border: $gray;
|
||||
//** Thumbnail border radius
|
||||
$thumbnail-border-radius: $border-radius-base;
|
||||
|
||||
//** Custom text color for thumbnail captions
|
||||
$thumbnail-caption-color: $text-color;
|
||||
//** Padding around the thumbnail caption
|
||||
$thumbnail-caption-padding: 9px;
|
||||
|
||||
|
||||
//== Wells
|
||||
//
|
||||
//##
|
||||
|
||||
$well-bg: $gray-dark;
|
||||
$well-border: transparent;
|
||||
|
||||
|
||||
//== Badges
|
||||
//
|
||||
//##
|
||||
|
||||
$badge-color: #fff;
|
||||
//** Linked badge text color on hover
|
||||
$badge-link-hover-color: #fff;
|
||||
$badge-bg: $gray;
|
||||
|
||||
//** Badge text color in active nav link
|
||||
$badge-active-color: $brand-primary;
|
||||
//** Badge background color in active nav link
|
||||
$badge-active-bg: #fff;
|
||||
|
||||
$badge-font-weight: bold;
|
||||
$badge-line-height: 1;
|
||||
$badge-border-radius: 10px;
|
||||
|
||||
|
||||
//== Breadcrumbs
|
||||
//
|
||||
//##
|
||||
|
||||
$breadcrumb-padding-vertical: 8px;
|
||||
$breadcrumb-padding-horizontal: 15px;
|
||||
//** Breadcrumb background color
|
||||
$breadcrumb-bg: $gray;
|
||||
//** Breadcrumb text color
|
||||
$breadcrumb-color: $text-color;
|
||||
//** Text color of current page in the breadcrumb
|
||||
$breadcrumb-active-color: $gray-light;
|
||||
//** Textual separator for between breadcrumb elements
|
||||
$breadcrumb-separator: "/";
|
||||
|
||||
|
||||
//== Carousel
|
||||
//
|
||||
//##
|
||||
|
||||
$carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
|
||||
|
||||
$carousel-control-color: #fff;
|
||||
$carousel-control-width: 15%;
|
||||
$carousel-control-opacity: .5;
|
||||
$carousel-control-font-size: 20px;
|
||||
|
||||
$carousel-indicator-active-bg: #fff;
|
||||
$carousel-indicator-border-color: #fff;
|
||||
|
||||
$carousel-caption-color: #fff;
|
||||
|
||||
|
||||
//== Close
|
||||
//
|
||||
//##
|
||||
|
||||
$close-font-weight: bold;
|
||||
$close-color: #fff;
|
||||
$close-text-shadow: none;
|
||||
|
||||
|
||||
//== Code
|
||||
//
|
||||
//##
|
||||
|
||||
$code-color: #c7254e;
|
||||
$code-bg: #f9f2f4;
|
||||
|
||||
$kbd-color: #fff;
|
||||
$kbd-bg: #333;
|
||||
|
||||
$pre-bg: $gray-lighter;
|
||||
$pre-color: $gray-dark;
|
||||
$pre-border-color: #ccc;
|
||||
$pre-scrollable-max-height: 340px;
|
||||
|
||||
|
||||
//== Type
|
||||
//
|
||||
//##
|
||||
|
||||
//** Horizontal offset for forms and lists.
|
||||
$component-offset-horizontal: 180px;
|
||||
//** Text muted color
|
||||
$text-muted: $gray-light;
|
||||
//** Abbreviations and acronyms border color
|
||||
$abbr-border-color: $gray-light;
|
||||
//** Headings small color
|
||||
$headings-small-color: $gray-light;
|
||||
//** Blockquote small color
|
||||
$blockquote-small-color: $gray-light;
|
||||
//** Blockquote font size
|
||||
$blockquote-font-size: ($font-size-base * 1.25);
|
||||
//** Blockquote border color
|
||||
$blockquote-border-color: $gray;
|
||||
//** Page header border color
|
||||
$page-header-border-color: transparent;
|
||||
//** Width of horizontal description list titles
|
||||
$dl-horizontal-offset: $component-offset-horizontal;
|
||||
//** Horizontal line color.
|
||||
$hr-border: $gray;
|
97
apps/mebe_web/web/static/css/app.scss
Normal file
97
apps/mebe_web/web/static/css/app.scss
Normal file
|
@ -0,0 +1,97 @@
|
|||
@import "../../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/variables";
|
||||
@import "variables";
|
||||
@import "../../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
@import "bootswatch";
|
||||
|
||||
/* Scale images with the width of the site */
|
||||
.postimage, p>img, figure>img {
|
||||
max-width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Style images and their captions */
|
||||
figure>img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
margin: 10px;
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 1.1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* YouTube videos */
|
||||
iframe.youtube {
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
min-height: 315px;
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
table {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 10px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
|
||||
/* Fix heading */
|
||||
.navbar-default {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin: 30px;
|
||||
margin-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
|
||||
border-top: 1px solid #444;
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Post styles */
|
||||
|
||||
.post-header {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h1 {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.container .row:first-child .post-header h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul.post-tags {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
|
||||
padding: 3px;
|
||||
background-color: #123456;
|
||||
border-radius: 3px;
|
||||
|
||||
a {
|
||||
color: #abcdef;
|
||||
}
|
||||
}
|
||||
}
|
1
apps/mebe_web/web/static/js/app.js
Normal file
1
apps/mebe_web/web/static/js/app.js
Normal file
|
@ -0,0 +1 @@
|
|||
|
55
apps/mebe_web/web/static/js/old_hash_redirector.js
Normal file
55
apps/mebe_web/web/static/js/old_hash_redirector.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* This script will check old Laine-style hashes when the page has been loaded and redirects
|
||||
* to the correct address if such a hash is found.
|
||||
* This is done in JS because the hash is not sent to the server.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
var RE = [
|
||||
[
|
||||
/^\#\!\/(\d{4})\/(\d\d)\/(\d\d)\/(.*)$/,
|
||||
'/$1/$2/$3/$4'
|
||||
],
|
||||
[
|
||||
/^\#\![^\/]+$/,
|
||||
'/$1'
|
||||
],
|
||||
[
|
||||
/^\#\!\/tag\/([^\/]+)$/,
|
||||
'/tag/$1'
|
||||
],
|
||||
[
|
||||
/^\#\!\/tag\/([^\/]+)(\/\d+)$/,
|
||||
'/tag/$1/p/$2'
|
||||
],
|
||||
[
|
||||
/^\#\!\/archives\/(\d{4})$/,
|
||||
'/archive/$1'
|
||||
],
|
||||
[
|
||||
/^\#\!\/archives\/(\d{4})(\/\d+)$/,
|
||||
'/archive/$1/p/$2'
|
||||
],
|
||||
[
|
||||
/^\#\!\/archives\/(\d{4})\/(\d\d)$/,
|
||||
'/archive/$1/$2'
|
||||
],
|
||||
[
|
||||
/^\#\!\/archives\/(\d{4})\/(\d\d)(\/\d+)$/,
|
||||
'/archive/$1/$2/p/$3'
|
||||
]
|
||||
];
|
||||
|
||||
var currentHash = window.location.hash;
|
||||
|
||||
if (currentHash) {
|
||||
for (var i = 0; i < RE.length; ++i) {
|
||||
var results = RE[i].exec(currentHash);
|
||||
|
||||
if (results !== null) {
|
||||
window.location.replace(currentHash.replace(RE[i][0], RE[i][1]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
11
apps/mebe_web/web/templates/error/not_found.html.eex
Normal file
11
apps/mebe_web/web/templates/error/not_found.html.eex
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="jumbotron">
|
||||
<h1>HTTP/1.1 404 Not Found</h1>
|
||||
|
||||
<p>
|
||||
You tried to find something that doesn’t exist. Maybe it did at some point, but it sure doesn’t anymore.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="<%= page_path @conn, :index %>">Go away</a>.
|
||||
</p>
|
||||
</div>
|
51
apps/mebe_web/web/templates/layout/application.html.eex
Normal file
51
apps/mebe_web/web/templates/layout/application.html.eex
Normal file
|
@ -0,0 +1,51 @@
|
|||
<!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>Hello Phoenix!</title>
|
||||
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a href="<%= page_path @conn, :index %>" class="navbar-brand">
|
||||
Random notes
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="<%= page_path @conn, :page, "about" %>">About</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<%= @inner %>
|
||||
|
||||
<div class="footer row">
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<p>
|
||||
© Mikko Ahlroth
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<p>
|
||||
<a href="<%= page_path @conn, :page, "mebe" %>">
|
||||
Powered by Elixir + Phoenix + Mebe
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /container -->
|
||||
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
|
||||
<script src="<%= static_path(@conn, "/js/old_hash_redirector.js") %>"></script>
|
||||
</body>
|
||||
</html>
|
7
apps/mebe_web/web/templates/page/index.html.eex
Normal file
7
apps/mebe_web/web/templates/page/index.html.eex
Normal file
|
@ -0,0 +1,7 @@
|
|||
<%= for post <- @posts do %>
|
||||
<div class="row">
|
||||
<div class="col-xs-10 col-xs-push-1">
|
||||
<%= render "post_elem.html", conn: @conn, post: post, short: true %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
1
apps/mebe_web/web/templates/page/post.html.eex
Normal file
1
apps/mebe_web/web/templates/page/post.html.eex
Normal file
|
@ -0,0 +1 @@
|
|||
<%= render "post_elem.html", conn: @conn, post: @post, short: false %>
|
52
apps/mebe_web/web/templates/page/post_elem.html.eex
Normal file
52
apps/mebe_web/web/templates/page/post_elem.html.eex
Normal file
|
@ -0,0 +1,52 @@
|
|||
<% {year, month, day} = @post.date %>
|
||||
|
||||
<%
|
||||
# Justified versions of the month and day
|
||||
{j_month, j_day} = {
|
||||
String.rjust(Integer.to_string(month), 2, ?0),
|
||||
String.rjust(Integer.to_string(day), 2, ?0)
|
||||
}
|
||||
%>
|
||||
|
||||
<div class="post">
|
||||
|
||||
<div class="post-header">
|
||||
|
||||
<%= if @short do %>
|
||||
<a href="<%= page_path @conn, :post, year, j_month, j_day, @post.slug %>">
|
||||
<h1><%= @post.title %></h1>
|
||||
</a>
|
||||
<%= else %>
|
||||
<h1><%= @post.title %></h1>
|
||||
<% end %>
|
||||
|
||||
<div class="post-meta">
|
||||
<span>
|
||||
Posted on <%= year %>-<%= j_month %>-<%= j_day %>.
|
||||
</span>
|
||||
|
||||
<%= if @post.tags do %>
|
||||
<ul class="post-tags">
|
||||
<%= for tag <- @post.tags do %>
|
||||
<li>
|
||||
<a href="<%= page_path @conn, :tag, tag %>">
|
||||
<%= tag %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= if @short do %>
|
||||
<div class="post-content post-short-content">
|
||||
<%= raw @post.short_content %>
|
||||
</div>
|
||||
<%= else %>
|
||||
<div class="post-content">
|
||||
<%= raw @post.content %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
17
apps/mebe_web/web/views/error_view.ex
Normal file
17
apps/mebe_web/web/views/error_view.ex
Normal file
|
@ -0,0 +1,17 @@
|
|||
defmodule MebeWeb.ErrorView do
|
||||
use MebeWeb.Web, :view
|
||||
|
||||
def render("404.html", %{conn: conn}) do
|
||||
render "not_found.html", %{conn: conn}
|
||||
end
|
||||
|
||||
def render("500.html", _assigns) do
|
||||
"HTTP/1.1 500 Internal Server Error"
|
||||
end
|
||||
|
||||
# In case no render clause matches or no
|
||||
# template is found, let's render it as 500
|
||||
def template_not_found(_template, assigns) do
|
||||
render "500.html", assigns
|
||||
end
|
||||
end
|
3
apps/mebe_web/web/views/layout_view.ex
Normal file
3
apps/mebe_web/web/views/layout_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule MebeWeb.LayoutView do
|
||||
use MebeWeb.Web, :view
|
||||
end
|
3
apps/mebe_web/web/views/page_view.ex
Normal file
3
apps/mebe_web/web/views/page_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule MebeWeb.PageView do
|
||||
use MebeWeb.Web, :view
|
||||
end
|
48
apps/mebe_web/web/web.ex
Normal file
48
apps/mebe_web/web/web.ex
Normal file
|
@ -0,0 +1,48 @@
|
|||
defmodule MebeWeb.Web do
|
||||
@moduledoc """
|
||||
A module that keeps using definitions for controllers,
|
||||
views and so on.
|
||||
|
||||
This can be used in your application as:
|
||||
|
||||
use MebeWeb.Web, :controller
|
||||
use MebeWeb.Web, :view
|
||||
|
||||
The definitions below will be executed for every view,
|
||||
controller, etc, so keep them short and clean, focused
|
||||
on imports, uses and aliases.
|
||||
|
||||
Do NOT define functions inside the quoted expressions
|
||||
below.
|
||||
"""
|
||||
def controller do
|
||||
quote do
|
||||
use Phoenix.Controller
|
||||
|
||||
# Import URL helpers from the router
|
||||
import MebeWeb.Router.Helpers
|
||||
end
|
||||
end
|
||||
|
||||
def view do
|
||||
quote do
|
||||
use Phoenix.View, root: "web/templates"
|
||||
|
||||
# Import convenience functions from controllers
|
||||
import Phoenix.Controller, only: [get_flash: 2]
|
||||
|
||||
# Import URL helpers from the router
|
||||
import MebeWeb.Router.Helpers
|
||||
|
||||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate controller/view/etc.
|
||||
"""
|
||||
defmacro __using__(which) when is_atom(which) do
|
||||
apply(__MODULE__, which, [])
|
||||
end
|
||||
end
|
26
mix.exs
Normal file
26
mix.exs
Normal file
|
@ -0,0 +1,26 @@
|
|||
defmodule Mebe.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[apps_path: "apps",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
# Dependencies can be Hex packages:
|
||||
#
|
||||
# {:mydep, "~> 0.3.0"}
|
||||
#
|
||||
# Or git/path repositories:
|
||||
#
|
||||
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
|
||||
#
|
||||
# Type `mix help deps` for more examples and options.
|
||||
#
|
||||
# Dependencies listed here are available only for this project
|
||||
# and cannot be accessed from applications inside the apps folder
|
||||
defp deps do
|
||||
[]
|
||||
end
|
||||
end
|
10
mix.lock
Normal file
10
mix.lock
Normal file
|
@ -0,0 +1,10 @@
|
|||
%{"cowboy": {:hex, :cowboy, "1.0.0"},
|
||||
"cowlib": {:hex, :cowlib, "1.0.1"},
|
||||
"earmark": {:hex, :earmark, "0.1.16"},
|
||||
"fs": {:hex, :fs, "0.9.2"},
|
||||
"phoenix": {:hex, :phoenix, "0.13.0"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "1.0.1"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "0.4.0"},
|
||||
"plug": {:hex, :plug, "0.12.2"},
|
||||
"poison": {:hex, :poison, "1.4.0"},
|
||||
"ranch": {:hex, :ranch, "1.0.0"}}
|
Reference in a new issue