{"id":13834967,"url":"https://github.com/infinitered/thesis-phoenix","last_synced_at":"2025-10-21T15:07:23.036Z","repository":{"id":62430403,"uuid":"52914213","full_name":"infinitered/thesis-phoenix","owner":"infinitered","description":"A lightweight, bolt-on, intuitive content editing system for Elixir/Phoenix websites. Star this repo and follow along with our progress!","archived":true,"fork":false,"pushed_at":"2021-03-09T17:18:31.000Z","size":4016,"stargazers_count":645,"open_issues_count":35,"forks_count":62,"subscribers_count":38,"default_branch":"master","last_synced_at":"2024-11-20T20:38:57.298Z","etag":null,"topics":["content-management-system","elixir","elixir-lang","elixir-library","elixir-phoenix"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/infinitered.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-01T22:15:53.000Z","updated_at":"2024-10-27T03:11:36.000Z","dependencies_parsed_at":"2022-11-01T20:19:33.829Z","dependency_job_id":null,"html_url":"https://github.com/infinitered/thesis-phoenix","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/infinitered/thesis-phoenix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fthesis-phoenix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fthesis-phoenix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fthesis-phoenix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fthesis-phoenix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/infinitered","download_url":"https://codeload.github.com/infinitered/thesis-phoenix/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fthesis-phoenix/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264545157,"owners_count":23625403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["content-management-system","elixir","elixir-lang","elixir-library","elixir-phoenix"],"created_at":"2024-08-04T14:00:54.314Z","updated_at":"2025-10-21T15:07:17.653Z","avatar_url":"https://github.com/infinitered.png","language":"Elixir","funding_links":[],"categories":["Elixir"],"sub_categories":[],"readme":"# Thesis\n\n## Why is this archived?\n\nWe really appreciate all the community support in the years since we first released thesis-phoenix. Our focus has shifted from Elixer/Phoenix to React Native. Feel free to fork this library and continue on its legacy if you want. \n\n\n## What Is Thesis?\n\n**A CMS for Elixir/Phoenix that doesn't hijack your development workflow.**\n\nThesis is a lightweight and flexible Elixir/Phoenix CMS for quickly and easily\nadding content editing to any page on a Phoenix website, as well as creating new\ndynamically routed pages. It's ideal for either adding limited editing support to\nexisting Phoenix websites or building dynamic websites.\n\nWatch Jamon Holmgren give a 5-minute lightning talk about Thesis at ElixirConf 2017: https://www.youtube.com/watch?time_continue=2656\u0026v=YqOwzXNkOyg\n\n## Thesis Features\n\n1. Elixir/Phoenix hex package, uses React.js for its user interface\n2. Lightweight, bolt-on, doesn't hijack your development workflow\n3. On-page rich text editing\n4. On-page plain text editing\n5. Raw HTML editing for Youtube embeds or other flexible uses\n6. Image URL editing, both `img` tag and `div` with background image\n7. Page meta title and description editing\n8. Easily bring your own authentication system in one tiny function\n9. Create new dynamic pages, delete dynamic pages\n\n## What Thesis Isn't\n\nThesis isn't the same as other -bloated- full-function\ncontent management systems out there. This is a list of what it's not now and\nnot likely to be in the future.\n\n* Not a complete WordPress Replacement\n* Not a full featured CMS\n* Not a full featured WYSIWYG editor\n* Not an authentication or permission system\n* Not supported outside of a Phoenix app\n\n![screen capture on 2016-04-20 at 15-11-10 copy](https://cloud.githubusercontent.com/assets/1775841/24872094/0118ef42-1dd1-11e7-975a-26de44c9d9dc.gif)\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20Installation+%26+Configuration%20-)\n\n_If you are having problems, view `README_INSTALL.md` for manual instructions._\n\n##### 1. Add thesis to your `mix.exs`:\n\n```elixir\ndef deps do\n  [{:thesis, \"~\u003e 0.3.4\"}]\nend\n```\n\n##### 2. Run `mix thesis.install`\n\nThis install script will add Thesis to your `config/config.exs` and `lib/yourapp_web.ex`, as well\nas generate migrations and an authorization module in your `lib/thesis_auth.ex`.\n\n##### 3. Add the Thesis editor to your layout\n\n```eex\n  \u003cbody\u003e\n    \u003c%= thesis_editor(@conn) %\u003e\n```\n\n##### 4. Run `mix ecto.migrate`\n\n```\n$ mix ecto.migrate\n```\n\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20%20%20%20%20Demo+App%20%20%20%20%20%20%20%20%20%20-)\n\nCheck out the example apps in `examples/` to see how Thesis can be implemented.\nWe'll keep this up to date with examples of the latest features as we develop Thesis.\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20Making+Pages+Editable%20%20%20%20-)\n\nUse the `Thesis.View.content/4` view helper function to make a content area\neditable. If you have `use Thesis.View` in your `lib/yourapp_web.ex` file, this function\nis already available on all of your views.\n\nThesis will add a wrapper `\u003cdiv\u003e` around editable HTML and plain-text content\nareas, both in read mode and edit mode, so plan your CSS accordingly.\n\n# Rich Text Areas\n\nSimply wrap your HTML in a `content` function call, specifying `html` as the content type.\n\n```eex\n\u003ch1\u003eTitle\u003c/h1\u003e\n\u003cp\u003e\n  Here's my default description!\n\u003c/p\u003e\n```\n\nbecomes...\n\n```eex\n\u003c%= content(@conn, \"Section identifier\", :html) do %\u003e\n  \u003ch1\u003eTitle\u003c/h1\u003e\n  \u003cp\u003e\n    Here's my default description!\n  \u003c/p\u003e\n\u003c% end %\u003e\n```\n\n### Custom HTML Editor\n\nDon't like the MediumEditor? Write your own custom editor implementing the common editor interface.\n\n```js\nclass MyCustomEditor {\n  constructor(opts) {\n    this.onChange = opts.onChange;\n  }\n  enable() {} // Setup Editor\n  disable() {} // Teardown Editor\n  content(editor) {} // Return content\n  set(name, data) {} // Set content\n}\n```\n\nFor more detail, check out [HtmlEditor](https://github.com/infinitered/thesis-phoenix/tree/master/assets/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/).\n\nTo enable, add this in your config/config.exs file:\n\n```elixir\nconfig :thesis,\n  html_editor: \"MyCustomEditor\"\n```\n\n\u003cbr/\u003e\n\n---\n\n# Plain Text Areas\n\nFor plain-text, provide a `do:` option for default text.\n\n```eex\n\u003ch1\u003eMy Title\u003c/h1\u003e\n```\n\nbecomes...\n\n```eex\n\u003ch1\u003e\u003c%= content(@conn, \"Title identifier\", :text, do: \"My Title\") %\u003e\u003c/h1\u003e\n```\n\n\u003cbr/\u003e\n\n---\n\n# Custom HTML Areas\n\nFor video embeds, iframes, and any other custom HTML, use the `:raw_html` content type:\n\n```eex\n\u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/5SVLs_NN_uY\" frameborder=\"0\" allowfullscreen\u003e\u003c/iframe\u003e\n```\n\nbecomes...\n\n```eex\n\u003c%= content(@conn, \"Section identifier\", :raw_html) do %\u003e\n  \u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/5SVLs_NN_uY\" frameborder=\"0\" allowfullscreen\u003e\u003c/iframe\u003e\n\u003c% end %\u003e\n```\n\n\u003cbr/\u003e\n\n---\n\n# Images\n\nYou can have the user specify an image URL and display the image with the `image` content type.\n\n```eex\n\u003cimg src=\"http://placekitten.com/200/300\"\u003e\n```\n\nbecomes...\n\n```eex\n\u003c%= content(@conn, \"Image identifier\", :image, alt: \"My alt tag\", do: \"http://placekitten.com/200/300\") %\u003e\n```\n\nIf you prefer to use a `div` with a background image, you can use the `background_image`\ncontent type.\n\n```eex\n\u003cdiv style=\"background-image: url(http://placekitten.com/200/300)\"\u003e\u003c/div\u003e\n```\n\nbecomes...\n\n```eex\n\u003c%= content(@conn, \"Image identifier\", :background_image, do: \"http://placekitten.com/200/300\") %\u003e\n```\n\n### Image Uploads\n\nThesis offers support for a few different ways to handle image uploads: store files in the database,\npoint to an uploader/adapter inside your custom app, or use one of the prebuilt adapters (in progress).\n\n##### Store Files in Database\n\nFor smaller websites and/or website that are hosted on the cloud, thesis offers a no-setup-required image uploader.\nFiles are stored in a separate table and contain all of the needed metadata (name, file type, and blobs themselves).\nKeep in mind as you upload more and more files, your database will grow quickly. Don't use this for high-traffic,\ncontent-heavy web applications. Smaller personal websites are probably fine.\n\nTo enable, add this in your config/config.exs file:\n\n```elixir\nconfig :thesis,\n  uploader: Thesis.RepoUploader\n```\n\n##### Use Your Own Uploader Module\n\nIf you already set up file uploads in your custom app, point thesis to a module that can handle a `%Plug.Upload{}`\nstruct.\n\n```elixir\nconfig :thesis,\n  uploader: \u003cMyApp\u003e.\u003cCustomUploaderModule\u003e\n```\n\nThe module should have an `upload/1` function that accepts a `%Plug.Upload{}` struct. This function should return either `{:ok, \"path/to/file.jpg\"}` tuple with an image url or path, or `{:error, _}`. You can view\n[/lib/thesis/uploaders/repo_uploader.ex](https://github.com/infinitered/thesis-phoenix/blob/master/lib/thesis/uploaders/repo_uploader.ex)\nfor an example.\n\nThat's it! Restart your server and image content areas will now contain a\nfile upload field.\n\u003cbr/\u003e\n\n---\n\n# Global Content Areas\n\nContent areas in Thesis are page-specific. However, if you want an editable\narea that can be displayed on multiple pages, use the\n`Thesis.View.global_content/4` function. Any page using that content area identifier\nwill display the edited content across the whole website.\n\n```eex\n\u003c%= global_content(@conn, \"Footer Text\", :html) do %\u003e\n  \u003ch4\u003eContact Info\u003c/h4\u003e\n  \u003cul\u003e\n    \u003cli\u003eCall us at (800) 555-1212\u003c/li\u003e\n    \u003cli\u003eEmail us at hello@example.com.\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c% end %\u003e\n```\n\n# Custom Classes or ID\n\nThesis adds an additional `\u003cdiv\u003e` around your editable content areas. We suggest that\nyou not style these divs heavily, since Thesis uses them as editors and adds its own styles\nin edit-mode. However, sometimes, you need to modify that markup slightly for better presentation.\nYou can provide an ID and additional classes by specifying `id` and `classes`, respectively.\n\n```eex\n\u003c%= content(@conn, \"Ident\", :html, id: \"my-id\", classes: \"more classes\") do %\u003e\n  \u003ch1\u003eTitle\u003c/h1\u003e\n\u003c% end %\u003e\n```\n\n\u003cbr/\u003e\n\n---\n\n# Page Meta Title and Description\n\nThesis provides a settings tray to edit each page's title and description. In your\nlayout, you can output the current title and description like so:\n\n```eex\n\u003ctitle\u003e\u003c%= page_title(@conn, \"Default Title\") %\u003e\u003c/title\u003e\n\u003cmeta name=\"description\" content=\"\u003c%= page_description(@conn, \"Default Description\") %\u003e\" /\u003e\n```\n\nSome prefer to set the page title and description as assigns in their controller actions:\n\n```eex\ndef about(conn, params) do\n  @title = Thesis.View.page_title(conn, \"About My Company\")\n  @description = Thesis.View.page_description(conn, \"A relevant description here.\")\nend\n```\n\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20%20Dynamic+Pages%20%20%20%20%20%20%20-)\n\nThesis supports users creating and deleting dynamically routed pages. These\ndiffer from static pages in that they are routed by Thesis rather than Phoenix,\nand live only in your database. They can be rendered with different templates.\n\n![add new page screenshot](http://d.pr/i/XLBq+)\n\nTo enable dynamic pages, add (or uncomment) this in your `config/config.exs` file:\n\n```elixir\nconfig :thesis, :dynamic_pages,\n  view: \u003cMyApp\u003e.PageView,\n  templates: [\"index.html\", \"otherview.html\"],\n  not_found_view: \u003cMyApp\u003e.ErrorView,\n  not_found_template: \"404.html\"\n```\n\nReplace `\u003cMyApp\u003e` with your app name. Use any view you want, and put any templates\ncontained in that view that you want to make available in the `templates` list.\nThese will be displayed as a drop-down to the user when they are creating the new\ndynamic page.\n\nYou'll also need to make one change to your router.ex and a controller of your\nchoice.\n\n```elixir\n# web/router.ex\n# should be added as the last route\n\n  get \"/*path\", \u003cMyApp\u003e.PageController, :dynamic\n\n# web/controllers/page_controller.ex (or similar)\n\n  def dynamic(conn, _params) do\n    render_dynamic(conn)\n  end\n```\n\nYou can pass in a default template (otherwise, it'll use the first template\noption in your config) with `render_dynamic(conn, template: \"index.html\")`.\n\nYou can choose to make only a portion of your website support dynamic pages by\nrouting more specifically. For example, if you want a blog section:\n\n```elixir\n# web/router.ex\n\n  get \"/blog/*path\", \u003cMyApp\u003e.BlogController, :dynamic\n```\n\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20%20Authorization%20%20%20%20%20%20%20-)\n\nYou probably don't want your website editable by the world. Thesis doesn't\nforce you to use any particular authorization strategy.\n\nInstead, Thesis will call your auth module's `page_is_editable?/1` function\nand provide the current `conn`, which can be used to extract current user\nsession data as well as the current page, and then you can decide how that\nshould affect authorization.\n\nHere's an example which we use on our own website, [https://infinite.red](https://infinite.red):\n\n```elixir\ndefmodule IrWebsite.ThesisAuth do\n  @behaviour Thesis.Auth\n\n  def page_is_editable?(conn) do\n    IrWebsite.AuthController.logged_in?(conn)\n  end\nend\n```\n\nIn our `auth_controller.ex` file, the `logged_in?/1` function looks something\nlike this:\n\n```elixir\n  def logged_in?(conn) do\n    !!current_user(conn)\n  end\n\n  def current_user(conn) do\n    get_session(conn, :current_user)\n  end\n```\n\nSo, in this case, we're simply checking to see if the user has been logged in\nor not. Since only Infinite Red employees have logins, it's safe for us to\nassume that if they're logged in, they have permission to edit the page.\n\nIf you use [Guardian](https://github.com/ueberauth/guardian) or something similar,\nyou may need additional manipulations to your `conn` to properly authenticate the\nuser. Add those to your auth module like this:\n\n```eex\ndefmodule MyApp.ThesisAuth do\n  @moduledoc \"\"\"\n  Contains functions for handling Thesis authorization.\n  \"\"\"\n\n  def page_is_editable?(conn) do\n    conn\n    |\u003e Guardian.Plug.VerifySession.call(%{})\n    |\u003e Guardian.Plug.LoadResource.call(%{})\n    |\u003e MyApp.SessionController.logged_in_and_admin?\n  end\nend\n```\n\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20%20Notifications%20%20%20%20%20%20%20-)\n\nNotifications/alerts allow us to talk with the user about the various aspects of the editing experience. For us, the contributors, this means that we may warn the user or developer of a breaking change that requires migrations (if they were forgotten to be executed). Since this is configurable, the developer may elect to push custom notifications to various parts of the Thesis editor.\n\n![](https://user-images.githubusercontent.com/1775841/38072424-07657800-32db-11e8-8fe1-bdea4bfc76ab.png)\n\nNotifications can be configured to be static:\n\n```elixir\nconfig :thesis, :notifications,\n  page_settings: [\"The changes made here will affect your SEO\", \"Example notification 2\"],\n  add_page: [\"You are about to add a new page to the product site!\"],\n  import_export_restore: [\"Example notification 4\"]\n```\n\nOr, you may elect to add some logic and make them more dynamic:\n\n```elixir\nconfig :thesis, :notifications,\n  page_settings: [\"Example notification 1\", \"Example notification 2\"],\n  add_page: \u0026MyApp.CustomModule.generate_notifications/1,\n  import_export_restore: \u0026MyApp.CustomModule.import_warning/1\n```\n\nIn either case, there are only 2 things that matter: 1 - you must provide a List of String(s) for each notification type, whether static or the result of a custom function; 2 - if you are using a custom function, it must be able to accept 1 argument: a `%Plug.Conn{}` struct. You can see an example [here](https://github.com/infinitered/thesis-phoenix/blob/master/examples/example-phx-1_3/lib/example_phx/notifications.ex).\n\n\u003cstrong\u003eNote\u003c/strong\u003e: right now, there are 3 spots to which you can push notifications: the '[Add New Page](https://user-images.githubusercontent.com/1775841/38072348-acfb3d46-32da-11e8-8fed-97b5d17ba027.png)' tray, '[Page Settings](https://user-images.githubusercontent.com/1775841/38072314-8b056a86-32da-11e8-8e75-df4383eabdee.png)' tray, and '[Import/Export/Restore](https://user-images.githubusercontent.com/1775841/38072274-67b43a62-32da-11e8-8752-664930d4ce9d.png)' tray. As more features are developed, the notifications will be extended to support those features as well.\n\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20%20Common+Issues%20%20%20%20%20%20%20-)\n\n#### Thesis's menu/editor/tray is borked\n\nThis is pretty common. While we try to be good citizens by properly namespacing\nall Thesis elements, we embed Thesis code into your existing web page, and so\nwe're at the mercy of your application's existing CSS.\n\nInspect the element(s) that are screwed up and see if any of your styles are\nconflicting. For example, here's a screenshot of an issue:\n\n[borked Thesis editor](https://cloud.githubusercontent.com/assets/1479215/18256127/223706ca-7366-11e6-971e-018ef8e656cb.png)\n\nNote that there is a `.tooltip` CSS rule originating in a different CSS file\nthat is affecting our editor.\n\nIn future releases, we will namespace all Thesis classes and IDs. But if your\napplication is overriding whole elements (like `div` or `img`), it's up to you\nto fix the issue in your own CSS.\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20%20Contributing%20%20%20%20%20%20%20-)\n\nWe're committed to making Thesis the go-to content editing system for Phoenix\nwebsites. Please help us improve!\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Run `npm run webpack` during development\n5. Use the `apps/example` Phoenix app to manually test your feature\n6. Write tests for your new feature\n7. Run `./bin/ci` in the root directory to ensure that Thesis tests pass.\n8. Push to the branch (`git push origin my-new-feature`)\n9. Create new Pull Request\n   \u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20Premium+Support%20%20%20%20%20%20-)\n\n[Thesis Phoenix](https://github.com/infinitered/thesis-phoenix), as an open source project, is free to use and always will be. [Infinite Red](https://infinite.red/) offers premium Thesis Phoenix support and general web app design/development services. Email us at [hello@infinite.red](mailto:hello@infinite.red) to get in touch with us for more details.\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20Key+Contributors%20%20%20%20%20%20-)\n\n| [Jamon Holmgren](https://twitter.com/jamonholmgren)                                 | [Yulian Glukhenko](https://github.com/yulolimum)                                     | [Ken Miller](https://github.com/kemiller)                                         | [Daniel Berkompas](https://twitter.com/dberkom)                                      |\n| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |\n| \u003cimg src=\"https://infinite.red/images/avatars/img-avatar-jamon@2x.jpg\" width=\"165\"\u003e | \u003cimg src=\"https://infinite.red/images/avatars/img-avatar-yulian@2x.jpg\" width=\"165\"\u003e | \u003cimg src=\"https://infinite.red/images/avatars/img-avatar-ken@2x.jpg\" width=\"165\"\u003e | \u003cimg src=\"https://infinite.red/images/avatars/img-avatar-daniel@2x.jpg\" width=\"165\"\u003e |\n\nAlso supported by others on the [Infinite Red](https://infinite.red) team.\n\u003cbr/\u003e\n\n---\n\n# ![](http://placehold.it/890x200/2b1e34/ffffff?text=-%20%20%20%20%20%20License:+MIT%20%20%20%20%20%20-)\n\nCopyright (c) 2016 Infinite Red, Inc.\n\nThesis depends on Elixir, which is under the Apache 2 license, and\nPhoenix, which is MIT.\n\nSee LICENSE.md for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfinitered%2Fthesis-phoenix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finfinitered%2Fthesis-phoenix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfinitered%2Fthesis-phoenix/lists"}