{"action":"created","issue":{"url":"https://api.github.com/repos/TryGhost/Ghost/issues/9060","repository_url":"https://api.github.com/repos/TryGhost/Ghost","labels_url":"https://api.github.com/repos/TryGhost/Ghost/issues/9060/labels{/name}","comments_url":"https://api.github.com/repos/TryGhost/Ghost/issues/9060/comments","events_url":"https://api.github.com/repos/TryGhost/Ghost/issues/9060/events","html_url":"https://github.com/TryGhost/Ghost/issues/9060","id":260547859,"number":9060,"title":"Custom Post Templates","user":{"login":"kevinansfield","id":415,"avatar_url":"https://avatars2.githubusercontent.com/u/415?v=4","gravatar_id":"","url":"https://api.github.com/users/kevinansfield","html_url":"https://github.com/kevinansfield","followers_url":"https://api.github.com/users/kevinansfield/followers","following_url":"https://api.github.com/users/kevinansfield/following{/other_user}","gists_url":"https://api.github.com/users/kevinansfield/gists{/gist_id}","starred_url":"https://api.github.com/users/kevinansfield/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kevinansfield/subscriptions","organizations_url":"https://api.github.com/users/kevinansfield/orgs","repos_url":"https://api.github.com/users/kevinansfield/repos","events_url":"https://api.github.com/users/kevinansfield/events{/privacy}","received_events_url":"https://api.github.com/users/kevinansfield/received_events","type":"User","site_admin":false},"labels":[{"id":82875493,"url":"https://api.github.com/repos/TryGhost/Ghost/labels/admin-client","name":"admin-client","color":"e1563f","default":false},{"id":126218048,"url":"https://api.github.com/repos/TryGhost/Ghost/labels/feature","name":"feature","color":"9FBB58","default":false},{"id":695470008,"url":"https://api.github.com/repos/TryGhost/Ghost/labels/in%20review","name":"in review","color":"0e8a16","default":false},{"id":82875520,"url":"https://api.github.com/repos/TryGhost/Ghost/labels/server","name":"server","color":"0B939C","default":false}],"state":"closed","locked":false,"assignee":{"login":"kevinansfield","id":415,"avatar_url":"https://avatars2.githubusercontent.com/u/415?v=4","gravatar_id":"","url":"https://api.github.com/users/kevinansfield","html_url":"https://github.com/kevinansfield","followers_url":"https://api.github.com/users/kevinansfield/followers","following_url":"https://api.github.com/users/kevinansfield/following{/other_user}","gists_url":"https://api.github.com/users/kevinansfield/gists{/gist_id}","starred_url":"https://api.github.com/users/kevinansfield/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kevinansfield/subscriptions","organizations_url":"https://api.github.com/users/kevinansfield/orgs","repos_url":"https://api.github.com/users/kevinansfield/repos","events_url":"https://api.github.com/users/kevinansfield/events{/privacy}","received_events_url":"https://api.github.com/users/kevinansfield/received_events","type":"User","site_admin":false},"assignees":[{"login":"kevinansfield","id":415,"avatar_url":"https://avatars2.githubusercontent.com/u/415?v=4","gravatar_id":"","url":"https://api.github.com/users/kevinansfield","html_url":"https://github.com/kevinansfield","followers_url":"https://api.github.com/users/kevinansfield/followers","following_url":"https://api.github.com/users/kevinansfield/following{/other_user}","gists_url":"https://api.github.com/users/kevinansfield/gists{/gist_id}","starred_url":"https://api.github.com/users/kevinansfield/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kevinansfield/subscriptions","organizations_url":"https://api.github.com/users/kevinansfield/orgs","repos_url":"https://api.github.com/users/kevinansfield/repos","events_url":"https://api.github.com/users/kevinansfield/events{/privacy}","received_events_url":"https://api.github.com/users/kevinansfield/received_events","type":"User","site_admin":false},{"login":"kirrg001","id":1160712,"avatar_url":"https://avatars0.githubusercontent.com/u/1160712?v=4","gravatar_id":"","url":"https://api.github.com/users/kirrg001","html_url":"https://github.com/kirrg001","followers_url":"https://api.github.com/users/kirrg001/followers","following_url":"https://api.github.com/users/kirrg001/following{/other_user}","gists_url":"https://api.github.com/users/kirrg001/gists{/gist_id}","starred_url":"https://api.github.com/users/kirrg001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kirrg001/subscriptions","organizations_url":"https://api.github.com/users/kirrg001/orgs","repos_url":"https://api.github.com/users/kirrg001/repos","events_url":"https://api.github.com/users/kirrg001/events{/privacy}","received_events_url":"https://api.github.com/users/kirrg001/received_events","type":"User","site_admin":false}],"milestone":null,"comments":5,"created_at":"2017-09-26T09:08:43Z","updated_at":"2018-02-06T21:17:04Z","closed_at":"2017-10-10T12:36:40Z","author_association":"CONTRIBUTOR","body":"# Feature overview\r\n\r\nSome blogs have various \"post types\" that should be rendered differently to standard blog posts. \r\n\r\nIt's currently possible to do this in a round-a-bout way by using (internal) tags, partials, and conditionals in `post.hbs` but it's not ideal for certain site structures and can become unwieldy with many post types. When using such a setup the availability of different post types is hidden from authors by overloading the tags feature with theme/site-specific functionality.\r\n\r\nWith the Custom Post Templates feature theme authors will be able to create individual templates that are recognised as custom templates and exposed in the admin interface for selection by post authors.\r\n\r\n# Design\r\n\r\n## UI:\r\n- display a \"Template\" section with a select dropdown (only when custom templates are available) after the page/feature checkboxes in the PSM\r\n\r\n![ghost-custom-template](https://user-images.githubusercontent.com/415/30602820-a29f78fc-9d5d-11e7-9a81-b9ec0ee6fb8b.gif)\r\n\r\n## Technical: <a name=\"technical\"></a>\r\n- custom templates will be identified by the format `custom-{{dasherized-name}}.hbs`\r\n  - they will be applicable to both posts and pages for now\r\n  - the `dasherized-name` will be titleized to `Dasherized Name` for display in the UI\r\n  - later iterations could incorporate [front-matter](https://www.npmjs.com/package/front-matter) in the templates themselves to provide theme authors a way to define a different name for the UI or to limit the template to posts or pages\r\n- list of available custom templates for each theme provided as an attribute on the `/themes/` and `/themes/:name/` endpoints\r\n  - client should ensure this info is cached to avoid unnecessary requests each time a post is opened\r\n  - a differentiation needs to be made between `post/page-{{slug}}.hbs` templates and \"shared\" custom templates so that the client dropdown can display only the shared templates\r\n  - available templates should also be returned when uploading/activating a theme\r\n- if a `post-{{slug}}.hbs` or `page-{{slug}}.hbs` template exists and matches the post slug then that template has precedence and will override any custom template selection\r\n  - if the client detects that the post slug matches a template then it should disable the custom template select with a message indicating which template will be used\r\n- if a post has a custom template selected but that template is not available in the theme then rendering should fall back to the default `post.hbs` or `page.hbs` template\r\n\r\n### `/themes/` & `/themes/:name/` endpoint format\r\n\r\nWhen fetching multiple or single themes the available templates will be included as an attribute on each member in the collection. Example fetching themes where one theme is installed and it contains a single custom, post, and page template:\r\n\r\n`GET /themes/`\r\n\r\n```json\r\n{\r\n    \"themes\": [\r\n        {\r\n            \"active\": true,\r\n            \"name\": \"example-theme\",\r\n            \"package\": {\r\n                \"name\": \"Example Theme\",\r\n                \"version\": \"0.1\"\r\n            },\r\n            \"templates\": [\r\n                {\r\n                    \"filename\": \"custom-news-bulletin.hbs\",\r\n                    \"name\": \"News Bulletin\",\r\n                    \"for\": [\"post\", \"page\"],\r\n                    \"slug\": null\r\n                },\r\n                {\r\n                    \"filename\": \"post-one.hbs\",\r\n                    \"name\": \"One\",\r\n                    \"for\": [\"post\"],\r\n                    \"slug\": \"one\"\r\n                }\r\n                {\r\n                    \"filename\": \"page-about.hbs\",\r\n                    \"name\": \"About\",\r\n                    \"for\": [\"page\"],\r\n                    \"slug\": \"about\"\r\n                }\r\n            ]\r\n        }\r\n    ]\r\n}\r\n```\r\n\r\n`custom-*.hbs` templates are automatically assigned to both posts and pages\r\n`post-*.hbs` templates are automatically assigned to posts, with the slug calculated from the filename\r\n`page-*.hbs` templates are automatically assigned to pages, with the slug calculated from the filename\r\n\r\nThe `filename` attribute acts as a unique identifier.\r\n\r\nThe `name` attribute will be auto-generated from the filename by titleizing after removing the `custom/post/page-` prefix and file extension.\r\n\r\nThe `for` attribute will be used by the client to determine when custom templates aren't available due to a slug-based template (`post-*.hbs`, `page-*.hbs`) taking precedence.\r\n\r\n# TODOs\r\n\r\n- [x] server\r\n  - [x] update GScan to recognise and expose custom templates\r\n  - [x] return `templates` attribute containing list of custom templates and any `post/page-*` templates as part of the `/themes/` and `/themes/:name/` responses\r\n  - [x] add `custom-template` field to the post model\r\n  - [x] allow `custom-template` to be set during saving\r\n  - [x] render custom template on front-end if specified and template exists\r\n  - [x] finish TODO's from #9073 \r\n  - [x] cache clear happens?\r\n- [x] client\r\n  - [x] fetch active theme with templates when editor is loaded - do not block rendering\r\n  - [x] display list of available templates in PSM when templates are available\r\n  - [x] disable template select if post slug matches known post/page template\r\n  - [x] test against server implementation + check styles\r\n- [x] documentation\r\n  - [x] publish new version on themes.ghost.org\r\n    - [x] update templates list on [Structure](https://themes.ghost.org/docs/structure) page\r\n    - [x] update templates list and add custom templates description on [Templates](https://themes.ghost.org/docs/templates) page\r\n    - [x] update changelog\r\n  - [x] notify Sarah (help.ghost.org)\r\n  - [x] dev blog post"},"comment":{"url":"https://api.github.com/repos/TryGhost/Ghost/issues/comments/363567296","html_url":"https://github.com/TryGhost/Ghost/issues/9060#issuecomment-363567296","issue_url":"https://api.github.com/repos/TryGhost/Ghost/issues/9060","id":363567296,"user":{"login":"edarioq","id":1882457,"avatar_url":"https://avatars2.githubusercontent.com/u/1882457?v=4","gravatar_id":"","url":"https://api.github.com/users/edarioq","html_url":"https://github.com/edarioq","followers_url":"https://api.github.com/users/edarioq/followers","following_url":"https://api.github.com/users/edarioq/following{/other_user}","gists_url":"https://api.github.com/users/edarioq/gists{/gist_id}","starred_url":"https://api.github.com/users/edarioq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edarioq/subscriptions","organizations_url":"https://api.github.com/users/edarioq/orgs","repos_url":"https://api.github.com/users/edarioq/repos","events_url":"https://api.github.com/users/edarioq/events{/privacy}","received_events_url":"https://api.github.com/users/edarioq/received_events","type":"User","site_admin":false},"created_at":"2018-02-06T21:17:04Z","updated_at":"2018-02-06T21:17:04Z","author_association":"NONE","body":"So is this feature available now in production build?"}}
{"actor":{"display_login":"edarioq"}}