Current section
37 Versions
Jump to
Current section
37 Versions
Compare versions
32
files changed
+612
additions
-294
deletions
| @@ -64,9 +64,9 @@ | |
| 64 64 | <tr><td><h4>Caveats</h4></td></tr> |
| 65 65 | <tr> |
| 66 66 | <td> |
| 67 | - Thesis is currently considered <em>Alpha-quality</em>, but is in production on several |
| 68 | - websites, including <a href="https://infinite.red">Infinite Red</a>. The API is unstable and |
| 69 | - changing but we are committed to providing a clear upgrade path and documentation. |
| 67 | + Thesis is currently considered <em>Beta-quality</em>, but is in production on several |
| 68 | + websites, including <a href="https://infinite.red">Infinite Red</a>. The API is |
| 69 | + largely stable and we are committed to providing a clear upgrade path and documentation. |
| 70 70 | </td> |
| 71 71 | </tr> |
| 72 72 | </table> |
| @@ -83,7 +83,7 @@ | |
| 83 83 | |
| 84 84 | ```elixir |
| 85 85 | def deps do |
| 86 | - [{:thesis, "~> 0.0.30"}] |
| 86 | + [{:thesis, "~> 0.1.0"}] |
| 87 87 | end |
| 88 88 | |
| 89 89 | def application do |
| @@ -150,6 +150,30 @@ becomes... | |
| 150 150 | </p> |
| 151 151 | <% end %> |
| 152 152 | ``` |
| 153 | + |
| 154 | + ### Custom HTML Editor |
| 155 | + Don't like the MediumEditor? Write your own custom editor implementing the common editor interface. |
| 156 | + |
| 157 | + ``` |
| 158 | + class MyCustomEditor { |
| 159 | + constructor (opts) { |
| 160 | + this.onChange = opts.onChange |
| 161 | + } |
| 162 | + enable () {} // Setup Editor |
| 163 | + disable () {} // Teardown Editor |
| 164 | + content (editor) {} // Return content |
| 165 | + set (name, data) {} // Set content |
| 166 | + } |
| 167 | + ``` |
| 168 | + |
| 169 | + For more detail checkout [HtmlEditor](web/static/js/content_types/html_editor.js) or [this gist](https://gist.github.com/ryanlntn/ac346d361d9e10a8f1888bf59cea0e37) implementing a custom editor using [Trumbowyg](https://alex-d.github.io/Trumbowyg/). |
| 170 | + |
| 171 | + To enable, add this in your config/config.exs file: |
| 172 | + |
| 173 | + ```elixir |
| 174 | + config :thesis, |
| 175 | + html_editor: "MyCustomEditor" |
| 176 | + ``` |
| 153 177 | <br/> |
| 154 178 | |
| 155 179 | --- |
| @@ -458,7 +482,7 @@ websites. Please help us improve! | |
| 458 482 | 1. Fork it |
| 459 483 | 2. Create your feature branch (`git checkout -b my-new-feature`) |
| 460 484 | 3. Commit your changes (`git commit -am 'Add some feature'`) |
| 461 | - 4. Run `brunch watch -p` during development |
| 485 | + 4. Run `npm run webpack` during development |
| 462 486 | 5. Use the `apps/example` Phoenix app to manually test your feature |
| 463 487 | 6. Write tests for your new feature |
| 464 488 | 7. Run `./bin/ci` in the root directory to ensure that Thesis tests pass. |
| @@ -6,7 +6,7 @@ For automatic setup, see `README.md`. | |
| 6 6 | |
| 7 7 | ```elixir |
| 8 8 | def deps do |
| 9 | - [{:thesis, "~> 0.0.30"}] |
| 9 | + [{:thesis, "~> 0.1.0"}] |
| 10 10 | end |
| 11 11 | |
| 12 12 | def application do |
| @@ -2,15 +2,16 @@ | |
| 2 2 | {<<"build_tools">>,[<<"mix">>]}. |
| 3 3 | {<<"description">>, |
| 4 4 | <<"Thesis is a lightweight bolt-on content editing system\nfor Phoenix websites.">>}. |
| 5 | - {<<"elixir">>,<<"~> 1.2">>}. |
| 5 | + {<<"elixir">>,<<">= 1.4.0">>}. |
| 6 6 | {<<"files">>, |
| 7 7 | [<<"lib/mix/tasks/thesis.install.ex">>,<<"lib/mix/utils.ex">>, |
| 8 8 | <<"lib/thesis.ex">>,<<"lib/thesis/api_controller.ex">>, |
| 9 9 | <<"lib/thesis/auth.ex">>,<<"lib/thesis/config.ex">>, |
| 10 | - <<"lib/thesis/controller.ex">>,<<"lib/thesis/models/file.ex">>, |
| 11 | - <<"lib/thesis/models/page.ex">>,<<"lib/thesis/models/page_content.ex">>, |
| 12 | - <<"lib/thesis/render.ex">>,<<"lib/thesis/router.ex">>, |
| 13 | - <<"lib/thesis/stores/ecto_store.ex">>,<<"lib/thesis/stores/store.ex">>, |
| 10 | + <<"lib/thesis/controller.ex">>,<<"lib/thesis/models/backup.ex">>, |
| 11 | + <<"lib/thesis/models/file.ex">>,<<"lib/thesis/models/page.ex">>, |
| 12 | + <<"lib/thesis/models/page_content.ex">>,<<"lib/thesis/render.ex">>, |
| 13 | + <<"lib/thesis/router.ex">>,<<"lib/thesis/stores/ecto_store.ex">>, |
| 14 | + <<"lib/thesis/stores/store.ex">>, |
| 14 15 | <<"lib/thesis/uploaders/repo_uploader.ex">>, |
| 15 16 | <<"lib/thesis/uploaders/s3_uploader.ex">>, |
| 16 17 | <<"lib/thesis/uploaders/uploader.ex">>,<<"lib/thesis/utilities.ex">>, |
| @@ -20,6 +21,7 @@ | |
| 20 21 | <<"priv/templates/thesis.install/add_meta_to_thesis_page_contents.exs">>, |
| 21 22 | <<"priv/templates/thesis.install/add_template_and_redirect_url_to_thesis_pages.exs">>, |
| 22 23 | <<"priv/templates/thesis.install/change_content_default_for_page_content.exs">>, |
| 24 | + <<"priv/templates/thesis.install/create_thesis_backups_table.exs">>, |
| 23 25 | <<"priv/templates/thesis.install/create_thesis_files_table.exs">>, |
| 24 26 | <<"priv/templates/thesis.install/create_thesis_tables.exs">>, |
| 25 27 | <<"priv/templates/thesis.install/thesis_auth.exs">>, |
| @@ -124,8 +126,8 @@ | |
| 124 126 | <<"web/static/js/components/cancel_button.js">>, |
| 125 127 | <<"web/static/js/components/delete_button.js">>, |
| 126 128 | <<"web/static/js/components/edit_button.js">>, |
| 127 | - <<"web/static/js/components/import_export_button.js">>, |
| 128 | - <<"web/static/js/components/import_export_tray.js">>, |
| 129 | + <<"web/static/js/components/import_export_restore_button.js">>, |
| 130 | + <<"web/static/js/components/import_export_restore_tray.js">>, |
| 129 131 | <<"web/static/js/components/save_button.js">>, |
| 130 132 | <<"web/static/js/components/settings_button.js">>, |
| 131 133 | <<"web/static/js/components/settings_tray.js">>, |
| @@ -142,39 +144,51 @@ | |
| 142 144 | <<"README_INSTALL.md">>,<<"LICENSE.md">>,<<"package.json">>]}. |
| 143 145 | {<<"licenses">>,[<<"MIT">>]}. |
| 144 146 | {<<"links">>, |
| 145 | - [{<<"Docs">>,<<"https://hexdocs.pm/thesis/0.0.30/api-reference.html">>}, |
| 147 | + [{<<"Docs">>,<<"https://hexdocs.pm/thesis/0.1.0/api-reference.html">>}, |
| 146 148 | {<<"GitHub">>,<<"https://github.com/infinitered/thesis-phoenix">>}]}. |
| 147 149 | {<<"maintainers">>, |
| 148 150 | [<<"Jamon Holmgren">>,<<"Yulian Glukhenko">>,<<"Ken Miller">>, |
| 149 151 | <<"Daniel Berkompas">>]}. |
| 150 152 | {<<"name">>,<<"thesis">>}. |
| 151 153 | {<<"requirements">>, |
| 152 | - [[{<<"app">>,<<"phoenix">>}, |
| 153 | - {<<"name">>,<<"phoenix">>}, |
| 154 | + [[{<<"app">>,<<"lz_string">>}, |
| 155 | + {<<"name">>,<<"lz_string">>}, |
| 154 156 | {<<"optional">>,false}, |
| 155 | - {<<"requirement">>,<<">= 1.0.0">>}], |
| 156 | - [{<<"app">>,<<"phoenix_html">>}, |
| 157 | - {<<"name">>,<<"phoenix_html">>}, |
| 158 | - {<<"optional">>,false}, |
| 159 | - {<<"requirement">>,<<">= 2.0.0">>}], |
| 160 | - [{<<"app">>,<<"ecto">>}, |
| 161 | - {<<"name">>,<<"ecto">>}, |
| 162 | - {<<"optional">>,false}, |
| 163 | - {<<"requirement">>,<<">= 2.0.0">>}], |
| 164 | - [{<<"app">>,<<"plug">>}, |
| 165 | - {<<"name">>,<<"plug">>}, |
| 166 | - {<<"optional">>,false}, |
| 167 | - {<<"requirement">>,<<">= 1.0.0">>}], |
| 157 | + {<<"repository">>,<<"hexpm">>}, |
| 158 | + {<<"requirement">>,<<"~> 0.0.7">>}], |
| 168 159 | [{<<"app">>,<<"poison">>}, |
| 169 160 | {<<"name">>,<<"poison">>}, |
| 170 161 | {<<"optional">>,false}, |
| 162 | + {<<"repository">>,<<"hexpm">>}, |
| 171 163 | {<<"requirement">>,<<">= 1.0.0">>}], |
| 172 164 | [{<<"app">>,<<"httpoison">>}, |
| 173 165 | {<<"name">>,<<"httpoison">>}, |
| 174 166 | {<<"optional">>,false}, |
| 167 | + {<<"repository">>,<<"hexpm">>}, |
| 175 168 | {<<"requirement">>,<<"~> 0.11.0">>}], |
| 169 | + [{<<"app">>,<<"ecto">>}, |
| 170 | + {<<"name">>,<<"ecto">>}, |
| 171 | + {<<"optional">>,false}, |
| 172 | + {<<"repository">>,<<"hexpm">>}, |
| 173 | + {<<"requirement">>,<<">= 2.0.0">>}], |
| 176 174 | [{<<"app">>,<<"html_sanitize_ex">>}, |
| 177 175 | {<<"name">>,<<"html_sanitize_ex">>}, |
| 178 176 | {<<"optional">>,false}, |
| 179 | - {<<"requirement">>,<<">= 1.0.1">>}]]}. |
| 180 | - {<<"version">>,<<"0.0.30">>}. |
| 177 | + {<<"repository">>,<<"hexpm">>}, |
| 178 | + {<<"requirement">>,<<">= 1.3.0">>}], |
| 179 | + [{<<"app">>,<<"plug">>}, |
| 180 | + {<<"name">>,<<"plug">>}, |
| 181 | + {<<"optional">>,false}, |
| 182 | + {<<"repository">>,<<"hexpm">>}, |
| 183 | + {<<"requirement">>,<<">= 1.0.0">>}], |
| 184 | + [{<<"app">>,<<"phoenix_html">>}, |
| 185 | + {<<"name">>,<<"phoenix_html">>}, |
| 186 | + {<<"optional">>,false}, |
| 187 | + {<<"repository">>,<<"hexpm">>}, |
| 188 | + {<<"requirement">>,<<">= 2.0.0">>}], |
| 189 | + [{<<"app">>,<<"phoenix">>}, |
| 190 | + {<<"name">>,<<"phoenix">>}, |
| 191 | + {<<"optional">>,false}, |
| 192 | + {<<"repository">>,<<"hexpm">>}, |
| 193 | + {<<"requirement">>,<<">= 1.2.0">>}]]}. |
| 194 | + {<<"version">>,<<"0.1.0">>}. |
| @@ -8,7 +8,8 @@ defmodule Mix.Tasks.Thesis.Install do | |
| 8 8 | "add_indexes_to_tables", |
| 9 9 | "add_template_and_redirect_url_to_thesis_pages", |
| 10 10 | "change_content_default_for_page_content", |
| 11 | - "create_thesis_files_table" |
| 11 | + "create_thesis_files_table", |
| 12 | + "create_thesis_backups_table" |
| 12 13 | ] |
| 13 14 | @template_files [ |
| 14 15 | {"priv/templates/thesis.install/thesis_auth.exs", "lib/thesis_auth.ex"} |
| @@ -30,9 +31,9 @@ defmodule Mix.Tasks.Thesis.Install do | |
| 30 31 | |
| 31 32 | @doc false |
| 32 33 | def run(_args) do |
| 33 | - thesis_templates |
| 34 | - thesis_config |
| 35 | - thesis_web |
| 34 | + thesis_templates() |
| 35 | + thesis_config() |
| 36 | + thesis_web() |
| 36 37 | |
| 37 38 | status_msg("done", |
| 38 39 | "Now run #{IO.ANSI.blue}mix ecto.migrate#{IO.ANSI.reset} to ensure your database is up to date.") |
| @@ -58,7 +59,7 @@ defmodule Mix.Tasks.Thesis.Install do | |
| 58 59 | dest_file_path = Path.join [File.cwd! | ~w(config config.exs)] |
| 59 60 | dest_file_path |
| 60 61 | |> File.read!() |
| 61 | - |> insert_thesis |
| 62 | + |> insert_thesis() |
| 62 63 | |> overwrite_file(dest_file_path) |
| 63 64 | end |
| 64 65 | |
| @@ -128,7 +129,7 @@ defmodule Mix.Tasks.Thesis.Install do | |
| 128 129 | end |
| 129 130 | |
| 130 131 | defp migration_tuple({filename, i}) do |
| 131 | - ts = String.to_integer(timestamp) + i |
| 132 | + ts = String.to_integer(timestamp()) + i |
| 132 133 | {"priv/templates/thesis.install/#{filename}.exs", "priv/repo/migrations/#{ts}_#{filename}.exs"} |
| 133 134 | end |
| 134 135 | end |
| @@ -3,22 +3,43 @@ defmodule Thesis.ApiController do | |
| 3 3 | |
| 4 4 | use Phoenix.Controller |
| 5 5 | import Thesis.Config |
| 6 | - alias Thesis.Utilities |
| 6 | + alias Thesis.{Utilities, Backup} |
| 7 7 | |
| 8 8 | plug :ensure_authorized! when not action in [:show_file] |
| 9 9 | |
| 10 10 | def assets(conn, _params), do: conn |
| 11 11 | |
| 12 12 | def update(conn, %{"contents" => contents, "page" => page}) do |
| 13 | - :ok = store.update(page, contents) |
| 13 | + {:ok, _page} = store().update(page, contents) |
| 14 14 | json conn, %{} |
| 15 15 | end |
| 16 16 | |
| 17 17 | def delete(conn, %{"path" => path}) do |
| 18 | - :ok = store.delete(%{"slug" => path}) |
| 18 | + {:ok, _page} = store().delete(%{"slug" => path}) |
| 19 19 | json conn, %{} |
| 20 20 | end |
| 21 21 | |
| 22 | + def backups_for_page(conn, %{"page_slug" => page_slug}) do |
| 23 | + backups = |
| 24 | + page_slug |
| 25 | + |> store().backups() |
| 26 | + |> Enum.map(&Backup.with_pretty_datetime/1) |
| 27 | + |> Enum.map(fn b -> |
| 28 | + %{ |
| 29 | + id: b.id, |
| 30 | + page_revision: b.page_revision, |
| 31 | + inserted_at: b.inserted_at, |
| 32 | + pretty_date: b.pretty_date |
| 33 | + } end |
| 34 | + ) |
| 35 | + json conn, backups |
| 36 | + end |
| 37 | + |
| 38 | + def restore(conn, %{"backup_id" => backup_id}) do |
| 39 | + backup = store().restore(String.to_integer(backup_id)) |
| 40 | + json conn, %{revision: backup.page_json} |
| 41 | + end |
| 42 | + |
| 22 43 | def import_file(conn, %{"image_url" => ""}), do: json conn, %{path: ""} |
| 23 44 | def import_file(conn, %{"image_url" => image_url}) do |
| 24 45 | image = HTTPoison.get!(image_url) |
| @@ -37,12 +58,12 @@ defmodule Thesis.ApiController do | |
| 37 58 | def upload_file(conn, _), do: json conn, %{path: ""} |
| 38 59 | |
| 39 60 | def show_file(conn, %{"slug" => slug}) do |
| 40 | - file = store.file(slug) |
| 61 | + file = store().file(slug) |
| 41 62 | do_show_file(conn, file) |
| 42 63 | end |
| 43 64 | |
| 44 65 | defp do_upload_file(conn, file) do |
| 45 | - case uploader.upload(file) do |
| 66 | + case uploader().upload(file) do |
| 46 67 | {:ok, path} -> json conn, %{path: path} |
| 47 68 | {:error, _} -> json conn, %{path: ""} |
| 48 69 | end |
| @@ -61,7 +82,7 @@ defmodule Thesis.ApiController do | |
| 61 82 | end |
| 62 83 | |
| 63 84 | defp ensure_authorized!(conn, _params) do |
| 64 | - if auth.page_is_editable?(conn), do: conn, else: put_unauthorized(conn) |
| 85 | + if auth().page_is_editable?(conn), do: conn, else: put_unauthorized(conn) |
| 65 86 | end |
| 66 87 | |
| 67 88 | defp put_unauthorized(conn) do |
| @@ -69,4 +90,6 @@ defmodule Thesis.ApiController do | |
| 69 90 | |> put_status(:unauthorized) |
| 70 91 | |> halt |
| 71 92 | end |
| 93 | + |
| 94 | + |
| 72 95 | end |
Loading more files…