FEATURE: Native theme support

This feature introduces the concept of themes. Themes are an evolution
of site customizations.

Themes introduce two very big conceptual changes:

- A theme may include other "child themes", children can include grand
children and so on.

- A theme may specify a color scheme

The change does away with the idea of "enabled" color schemes.

It also adds a bunch of big niceties like

- You can source a theme from a git repo

- History for themes is much improved

- You can only have a single enabled theme. Themes can be selected by
    users, if you opt for it.

On a technical level this change comes with a whole bunch of goodies

- All CSS is now compiled using a custom pipeline that uses libsass
    see /lib/stylesheet

- There is a single pipeline for css compilation (in the past we used
    one for customizations and another one for the rest of the app

- The stylesheet pipeline is now divorced of sprockets, there is no
   reliance on sprockets for CSS bundling

- CSS is generated with source maps everywhere (including themes) this
    makes debugging much easier

- Our "live reloader" is smarter and avoid a flash of unstyled content
   we run a file watcher in "puma" in dev so you no longer need to run
   rake autospec to watch for CSS changes
This commit is contained in:
Sam
2017-04-12 10:52:52 -04:00
parent 1a9afa976d
commit a3e8c3cd7b
163 changed files with 4415 additions and 2424 deletions

View File

@@ -1,5 +0,0 @@
<li>
<a href="/admin/customize/css_html/{{customization.id}}/css" class="{{if active 'active'}}">
{{customization.description}}
</a>
</li>

View File

@@ -0,0 +1,8 @@
<label class='checkbox-label'>
{{input type="checkbox" disabled=disabled checked=checkedInternal}}
{{label}}
</label>
{{#if changed}}
{{d-button action="finished" class="btn-primary btn-small submit-edit" icon="check"}}
{{d-button action="cancelled" class="btn-small cancel-edit" icon="times"}}
{{/if}}

View File

@@ -0,0 +1 @@
<p class="about">{{i18n 'admin.customize.colors.about'}}</p>

View File

@@ -0,0 +1,53 @@
<div class="color-scheme show-current-style">
<div class="admin-container">
<h1>{{text-field class="style-name" value=model.name}}</h1>
<div class="controls">
<button {{action "save"}} disabled={{model.disableSave}} class='btn'>{{i18n 'admin.customize.save'}}</button>
<button {{action "copy" model}} class='btn'><i class="fa fa-copy"></i> {{i18n 'admin.customize.copy'}}</button>
<button {{action "destroy"}} class='btn btn-danger'><i class="fa fa-trash-o"></i> {{i18n 'admin.customize.delete'}}</button>
<span class="saving {{unless model.savingStatus 'hidden'}}">{{model.savingStatus}}</span>
</div>
<br/>
<div class='admin-controls'>
<div class='search controls'>
<label>
{{input type="checkbox" checked=onlyOverridden}}
{{i18n 'admin.site_settings.show_overriden'}}
</label>
</div>
</div>
{{#if colors.length}}
<table class="table colors">
<thead>
<tr>
<th></th>
<th class="hex">{{i18n 'admin.customize.color'}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each colors as |c|}}
<tr class="{{if c.changed 'changed'}} {{if c.valid 'valid' 'invalid'}}">
<td class="name" title={{c.name}}>
<b>{{c.translatedName}}</b>
<br/>
<span class="description">{{c.description}}</span>
</td>
<td class="hex">{{color-input hexValue=c.hex brightnessValue=c.brightness valid=c.valid}}</td>
<td class="actions">
<button class="btn revert {{unless c.savedIsOverriden 'invisible'}}" {{action "revert" c}} title="{{i18n 'admin.customize.colors.revert_title'}}">{{i18n 'revert'}}</button>
<button class="btn undo {{unless c.changed 'invisible'}}" {{action "undo" c}} title="{{i18n 'admin.customize.colors.undo_title'}}">{{i18n 'undo'}}</button>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<p>{{i18n 'search.no_results'}}</p>
{{/if}}
</div>
</div>

View File

@@ -3,76 +3,16 @@
<ul>
{{#each model as |scheme|}}
{{#unless scheme.is_base}}
<li><a {{action "selectColorScheme" scheme}} class="{{if scheme.selected 'active'}}">{{scheme.description}}</a></li>
<li>
{{#link-to 'adminCustomize.colors.show' scheme replace=true}}{{scheme.description}}{{/link-to}}
</li>
{{/unless}}
{{/each}}
</ul>
<button {{action "newColorScheme"}} class='btn'><i class="fa fa-plus"></i>{{i18n 'admin.customize.new'}}</button>
<button {{action "newColorScheme"}} class='btn'>{{fa-icon 'plus'}}{{i18n 'admin.customize.new'}}</button>
</div>
{{#if selectedItem}}
<div class="current-style color-scheme">
<div class="admin-container">
<h1>{{text-field class="style-name" value=selectedItem.name}}</h1>
<div class="controls">
<button {{action "save"}} disabled={{selectedItem.disableSave}} class='btn'>{{i18n 'admin.customize.save'}}</button>
<button {{action "toggleEnabled"}} disabled={{selectedItem.disableEnable}} class="btn">
{{#if selectedItem.enabled}}
{{i18n 'disable'}}
{{else}}
{{i18n 'enable'}}
{{/if}}
</button>
<button {{action "copy" selectedItem}} class='btn'><i class="fa fa-copy"></i> {{i18n 'admin.customize.copy'}}</button>
<button {{action "destroy"}} class='btn btn-danger'><i class="fa fa-trash-o"></i> {{i18n 'admin.customize.delete'}}</button>
<span class="saving {{unless selectedItem.savingStatus 'hidden'}}">{{selectedItem.savingStatus}}</span>
</div>
<br/>
<div class='admin-controls'>
<div class='search controls'>
<label>
{{input type="checkbox" checked=onlyOverridden}}
{{i18n 'admin.site_settings.show_overriden'}}
</label>
</div>
</div>
{{#if colors.length}}
<table class="table colors">
<thead>
<tr>
<th></th>
<th class="hex">{{i18n 'admin.customize.color'}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each colors as |c|}}
<tr class="{{if c.changed 'changed'}} {{if c.valid 'valid' 'invalid'}}">
<td class="name" title={{c.name}}>
<b>{{c.translatedName}}</b>
<br/>
<span class="description">{{c.description}}</span>
</td>
<td class="hex">{{color-input hexValue=c.hex brightnessValue=c.brightness valid=c.valid}}</td>
<td class="actions">
<button class="btn revert {{unless c.savedIsOverriden 'invisible'}}" {{action "revert" c}} title="{{i18n 'admin.customize.colors.revert_title'}}">{{i18n 'revert'}}</button>
<button class="btn undo {{unless c.changed 'invisible'}}" {{action "undo" c}} title="{{i18n 'admin.customize.colors.undo_title'}}">{{i18n 'undo'}}</button>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<p>{{i18n 'search.no_results'}}</p>
{{/if}}
</div>
</div>
{{else}}
<p class="about">{{i18n 'admin.customize.colors.about'}}</p>
{{/if}}
{{outlet}}
<div class="clearfix"></div>

View File

@@ -1,75 +0,0 @@
<div class="current-style {{if maximized 'maximized'}}">
<div class='wrapper'>
{{text-field class="style-name" value=model.name}}
<a class="btn export" target="_blank" href={{downloadUrl}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
<div class='admin-controls'>
<ul class="nav nav-pills">
{{#if mobile}}
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-css' replace=true}}{{i18n "admin.customize.css"}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-header' replace=true}}{{i18n "admin.customize.header"}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-top' replace=true}}{{i18n "admin.customize.top"}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-footer' replace=true}}{{i18n "admin.customize.footer"}}{{/link-to}}</li>
{{else}}
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'css' replace=true}}{{i18n "admin.customize.css"}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'header' replace=true}}{{i18n "admin.customize.header"}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'top' replace=true}}{{i18n "admin.customize.top"}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'footer' replace=true}}{{i18n "admin.customize.footer"}}{{/link-to}}</li>
<li>
{{#link-to 'adminCustomizeCssHtml.show' model.id 'head-tag'}}
{{fa-icon "file-text-o"}}&nbsp;{{i18n 'admin.customize.head_tag.text'}}
{{/link-to}}
</li>
<li>
{{#link-to 'adminCustomizeCssHtml.show' model.id 'body-tag'}}
{{fa-icon "file-text-o"}}&nbsp;{{i18n 'admin.customize.body_tag.text'}}
{{/link-to}}
</li>
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'embedded-css' replace=true}}{{i18n "admin.customize.embedded_css"}}{{/link-to}}</li>
{{/if}}
<li class='toggle-mobile'>
<a class="{{if mobile 'active'}}" {{action "toggleMobile"}}>{{fa-icon "mobile"}}</a>
</li>
<li class='toggle-maximize'>
<a {{action "toggleMaximize"}}>
<i class="fa fa-{{maximizeIcon}}"></i>
</a>
</li>
</ul>
</div>
<div class="admin-container">
{{#if cssActive}}{{ace-editor content=model.stylesheet mode="scss"}}{{/if}}
{{#if headerActive}}{{ace-editor content=model.header mode="html"}}{{/if}}
{{#if topActive}}{{ace-editor content=model.top mode="html"}}{{/if}}
{{#if footerActive}}{{ace-editor content=model.footer mode="html"}}{{/if}}
{{#if headTagActive}}{{ace-editor content=model.head_tag mode="html"}}{{/if}}
{{#if bodyTagActive}}{{ace-editor content=model.body_tag mode="html"}}{{/if}}
{{#if embeddedCssActive}}{{ace-editor content=model.embedded_css mode="css"}}{{/if}}
{{#if mobileCssActive}}{{ace-editor content=model.mobile_stylesheet mode="scss"}}{{/if}}
{{#if mobileHeaderActive}}{{ace-editor content=model.mobile_header mode="html"}}{{/if}}
{{#if mobileTopActive}}{{ace-editor content=model.mobile_top mode="html"}}{{/if}}
{{#if mobileFooterActive}}{{ace-editor content=model.mobile_footer mode="html"}}{{/if}}
</div>
<div class='admin-footer'>
<div class='status-actions'>
<span>{{i18n 'admin.customize.enabled'}} {{input type="checkbox" checked=model.enabled}}</span>
{{#unless model.changed}}
<a class='preview-link' href={{previewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
|
<a href={{undoPreviewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_undo_preview'}}">{{i18n 'admin.customize.undo_preview'}}</a>
|
<a href={{defaultStyleUrl}} target='_blank' title="{{i18n 'admin.customize.explain_rescue_preview'}}">{{i18n 'admin.customize.rescue_preview'}}</a><br>
{{/unless}}
</div>
<div class='buttons'>
{{#d-button action="save" disabled=saveDisabled class='btn-primary'}}
{{saveButtonText}}
{{/d-button}}
{{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}}
</div>
</div>
</div>
</div>

View File

@@ -1,13 +0,0 @@
<div class='content-list span6'>
<h3>{{i18n 'admin.customize.css_html.long_title'}}</h3>
<ul>
{{#each model as |c|}}
{{customize-link customization=c}}
{{/each}}
</ul>
{{d-button label="admin.customize.new" icon="plus" action="newCustomization" class="btn-primary"}}
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
</div>
{{outlet}}

View File

@@ -0,0 +1,62 @@
<div class="current-style {{if maximized 'maximized'}}">
<div class='wrapper'>
<h2>{{i18n 'admin.customize.theme.edit_css_html'}} {{#link-to 'adminCustomizeThemes.show' model.id replace=true}}{{model.name}}{{/link-to}}</h2>
<ul class='nav nav-pills target'>
<li>
{{#link-to 'adminCustomizeThemes.edit' model.id 'common' fieldName replace=true title=field.title}}
{{i18n 'admin.customize.theme.common'}}
{{/link-to}}
</li>
<li>
{{#link-to 'adminCustomizeThemes.edit' model.id 'desktop' fieldName replace=true title=field.title}}
{{i18n 'admin.customize.theme.desktop'}}
{{fa-icon 'desktop'}}
{{/link-to}}
</li>
<li>
{{#link-to 'adminCustomizeThemes.edit' model.id 'mobile' fieldName replace=true title=field.title}}
{{i18n 'admin.customize.theme.mobile'}}
{{fa-icon 'mobile'}}
{{/link-to}}
</li>
</ul>
<div class='admin-controls'>
<ul class='nav nav-pills fields'>
{{#each fields as |field|}}
<li>
{{#link-to 'adminCustomizeThemes.edit' model.id currentTargetName field.name replace=true title=field.title}}
{{#if field.icon}}{{fa-icon field.icon}} {{/if}}
{{i18n field.key}}
{{/link-to}}
</li>
{{/each}}
<li class='toggle-maximize'>
<a {{action "toggleMaximize"}}>
<i class="fa fa-{{maximizeIcon}}"></i>
</a>
</li>
</ul>
</div>
<div>
<div class='custom-ace-gutter'></div>
{{ace-editor content=activeSection mode=activeSectionMode}}
</div>
<div class='admin-footer'>
<div class='status-actions'>
{{#unless model.changed}}
<a class='preview-link' href={{previewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
{{/unless}}
</div>
<div class='buttons'>
{{#d-button action="save" disabled=saveDisabled class='btn-primary'}}
{{saveButtonText}}
{{/d-button}}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,112 @@
<div class="show-current-style">
<h2>
{{#if editingName}}
{{text-field value=model.name autofocus="true"}}
{{d-button action="finishedEditingName" class="btn-primary btn-small submit-edit" icon="check"}}
{{d-button action="cancelEditingName" class="btn-small cancel-edit" icon="times"}}
{{else}}
{{model.name}} <a {{action "startEditingName"}}>{{fa-icon "pencil"}}</a>
{{/if}}
</h2>
{{#if model.remote_theme}}
<p>
<a href="{{model.remote_theme.about_url}}">{{i18n "admin.customize.theme.about_theme"}}</a>
</p>
{{#if model.remote_theme.license_url}}
<p>
<a href="{{model.remote_theme.license_url}}">{{i18n "admin.customize.theme.license"}} {{fa-icon "copyright"}}</a>
</p>
{{/if}}
{{/if}}
<p>
{{inline-edit-checkbox action="applyDefault" labelKey="admin.customize.theme.is_default" checked=model.default}}
{{inline-edit-checkbox action="applyUserSelectable" labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
</p>
{{#if showSchemes}}
<h3>{{i18n "admin.customize.theme.color_scheme"}}</h3>
<p>{{i18n "admin.customize.theme.color_scheme_select"}}</p>
<p>{{combo-box content=colorSchemes
nameProperty="name"
value=colorSchemeId
valueAttribute="id"}}
{{#if colorSchemeChanged}}
{{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}}
{{d-button action="cancelChangeScheme" class="btn-small cancel-edit" icon="times"}}
{{/if}}
</p>
{{#link-to 'adminCustomize.colors' class="btn edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}}
{{/if}}
<h3>{{i18n "admin.customize.theme.css_html"}}</h3>
{{#if hasEditedFields}}
<p>{{i18n "admin.customize.theme.custom_sections"}}</p>
<ul>
{{#each editedDescriptions as |desc|}}
<li>{{desc}}</li>
{{/each}}
</ul>
{{else}}
<p>
{{i18n "admin.customize.theme.edit_css_html_help"}}
</p>
{{/if}}
<p>
{{#if model.remote_theme}}
{{#if model.remote_theme.commits_behind}}
{{#d-button action="updateToLatest" icon="download"}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
{{else}}
{{#d-button action="checkForThemeUpdates" icon="refresh"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
{{/if}}
{{/if}}
{{#d-button action="editTheme" class="btn edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}}
{{#if model.remote_theme}}
<span class='status-message'>
{{#if updatingRemote}}
{{i18n 'admin.customize.theme.updating'}}
{{else}}
{{#if model.remote_theme.commits_behind}}
{{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}}
{{else}}
{{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
{{/if}}
{{/if}}
</span>
{{/if}}
</p>
{{#if availableChildThemes}}
<h3>{{i18n "admin.customize.theme.included_themes"}}</h3>
{{#unless model.childThemes.length}}
<p>
<label class='checkbox-label'>
{{input type="checkbox" checked=allowChildThemes}}
{{i18n "admin.customize.theme.child_themes_check"}}
</label>
</p>
{{else}}
<ul>
{{#each model.childThemes as |child|}}
<li>{{child.name}} {{d-button action="removeChildTheme" actionParam=child class="btn-small cancel-edit" icon="times"}}</li>
{{/each}}
</ul>
{{/unless}}
{{#if selectableChildThemes}}
<p>{{combo-box content=selectableChildThemes
nameProperty="name"
value=selectedChildThemeId
valueAttribute="id"}}
{{#d-button action="addChildTheme" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
</p>
{{/if}}
{{/if}}
<a class="btn export" target="_blank" href={{downloadUrl}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
{{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}}
</div>

View File

@@ -0,0 +1,24 @@
{{#unless editingTheme}}
<div class='content-list span6'>
<h3>{{i18n 'admin.customize.theme.long_title'}}</h3>
<ul>
{{#each model as |theme|}}
<li>
{{#link-to 'adminCustomizeThemes.show' theme replace=true}}
{{theme.name}}
{{#if theme.user_selectable}}
{{fa-icon "user"}}
{{/if}}
{{#if theme.default}}
{{fa-icon "asterisk"}}
{{/if}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{d-button label="admin.customize.new" icon="plus" action="newTheme" class="btn-primary"}}
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
</div>
{{/unless}}
{{outlet}}

View File

@@ -1,7 +1,7 @@
<div class='customize'>
{{#admin-nav}}
{{nav-item route='adminCustomizeThemes' label='admin.customize.theme.title'}}
{{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
{{nav-item route='adminCustomizeCssHtml' label='admin.customize.css_html.title'}}
{{nav-item route='adminSiteText' label='admin.site_text.title'}}
{{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}}
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}

View File

@@ -0,0 +1,12 @@
<div>
{{#d-modal-body title="admin.customize.colors.select_base.title"}}
{{i18n "admin.customize.colors.select_base.description"}}
{{combo-box content=model
nameProperty="name"
value=selectedBaseThemeId
valueAttribute="base_scheme_id"}}
{{/d-modal-body}}
<div class="modal-footer">
<button class='btn btn-primary' {{action "selectBase"}}>{{fa-icon 'plus'}}{{i18n 'admin.customize.new'}}</button>
</div>
</div>

View File

@@ -0,0 +1,27 @@
{{#d-modal-body class='upload-selector' title="admin.customize.theme.import_theme"}}
<div class="radios">
{{radio-button name="upload" id="local" value="local" selection=selection}}
<label class="radio" for="local">{{i18n 'upload_selector.from_my_computer'}}</label>
{{#if local}}
<div class="inputs">
<input type="file" id="file-input" accept='.dcstyle.json'><br>
<span class="description">{{i18n 'admin.customize.theme.import_file_tip'}}</span>
</div>
{{/if}}
</div>
<div class="radios">
{{radio-button name="upload" id="remote" value="remote" selection=selection}}
<label class="radio" for="remote">{{i18n 'upload_selector.from_the_web'}}</label>
{{#if remote}}
<div class="inputs">
{{input value=uploadUrl placeholder="https://github.com/discourse/discourse/sample_theme"}}
<span class="description">{{i18n 'admin.customize.theme.import_web_tip'}}</span>
</div>
{{/if}}
</div>
{{/d-modal-body}}
<div class="modal-footer">
{{d-button action="importTheme" disabled=loading class='btn btn-primary' icon='upload' label='admin.customize.import'}}
<a href {{action "closeModal"}}>{{i18n 'cancel'}}</a>
</div>

View File

@@ -0,0 +1,8 @@
<div>
{{#d-modal-body title="admin.logs.staff_actions.modal_title"}}
{{{diff}}}
{{/d-modal-body}}
<div class="modal-footer">
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
</div>
</div>

View File

@@ -1,29 +0,0 @@
<div>
<ul class="nav nav-pills">
<li class="{{if newSelected 'active'}}">
<a href {{action "selectNew"}}>{{i18n 'admin.logs.staff_actions.new_value'}}</a>
</li>
<li class="{{if previousSelected 'active'}}">
<a href {{action "selectPrevious"}}>{{i18n 'admin.logs.staff_actions.previous_value'}}</a>
</li>
</ul>
{{#d-modal-body title="admin.logs.staff_actions.modal_title"}}
<div class="modal-tab new-tab {{unless newSelected 'invisible'}}">
{{#if model.new_value}}
{{site-customization-change-details change=model.new_value}}
{{else}}
{{i18n 'admin.logs.staff_actions.deleted'}}
{{/if}}
</div>
<div class="modal-tab previous-tab {{unless previousSelected 'invisible'}}">
{{#if model.previous_value}}
{{site-customization-change-details change=model.previous_value}}
{{else}}
{{i18n 'admin.logs.staff_actions.no_previous'}}
{{/if}}
</div>
{{/d-modal-body}}
<div class="modal-footer">
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
</div>
</div>