client/general: replace handlebars with underscore

This commit is contained in:
rr- 2016-05-09 20:07:54 +02:00
parent c34b1e3ccf
commit 6405fbe9f2
24 changed files with 242 additions and 231 deletions

View File

@ -1,4 +1,4 @@
<div class='page'>
<p><span>Page {{this.page}} of {{this.totalPages}}</span></p>
<p><span>Page <%= page %> of <%= totalPages %></span></p>
<div class='page-content-holder'></div>
</div>

View File

@ -1,11 +1,11 @@
<div class='file-dropper-holder'>
<input type='file' name='{{this.name}}' id='{{this.id}}'/>
<label class='file-dropper' for='{{this.id}}'>
{{#if this.allowMultiple}}
<input type='file' id='<%= id %>'/>
<label class='file-dropper' for='<%= id %>'>
<% if (allowMultiple) { %>
Drop files here!
{{else}}
<% } else { %>
Drop file here!
{{/if}}
<% } %>
<br/>
Or just click on this box.
</label>

View File

@ -1,4 +1,4 @@
<p>By accessing {{ name }} (&ldquo;Site&rdquo;) you agree to the following
<p>By accessing <%= name %> (&ldquo;Site&rdquo;) you agree to the following
Terms of Service. If you do not agree to these terms, then please do not access
the Site.</p>

View File

@ -1,5 +1,5 @@
<div class='content-wrapper transparent' id='home'>
<div class='messages'></div>
<h1>{{name}}</h1>
<footer>Version: <span class='version'>{{version}}</span> (built {{reltime buildDate}})</footer>
<h1><%= name %></h1>
<footer>Version: <span class='version'><%= version %></span> (built <%= makeRelativeTime(buildDate) %>)</footer>
</div>

View File

@ -4,22 +4,22 @@
<div class='input'>
<ul>
<li>
{{textInput text='User name' id='user-name' name='name' required=true pattern=this.userNamePattern}}
<%= makeTextInput({text: 'User name', id: 'user-name', name: 'name', required: true, pattern: userNamePattern}) %>
</li>
<li>
{{passwordInput text='Password' id='user-password' name='password' required=true pattern=this.passwordPattern}}
<%= makePasswordInput({text: 'Password', id: 'user-password', name: 'password', required: true, pattern: passwordPattern}) %>
</li>
<li>
{{checkbox text='Remember me' id='remember-user' name='remember-user'}}
<%= makeCheckbox({text: 'Remember me', id: 'remember-user', name: 'remember-user'}) %>
</li>
</ul>
</div>
<div class='messages'></div>
<div class='buttons'>
<input type='submit' value='Log in'/>
{{#if this.canSendMails}}
<% if (canSendMails) { %>
<a class='append' href='/password-reset'>Forgot the password?</a>
{{/if}}
<% } %>
</div>
</form>
</div>

View File

@ -1,36 +1,36 @@
<nav class='text-nav'>
<ul>
<li>
{{#if this.prevLinkActive}}
<a class='prev' href='{{this.prevLink}}'>
{{else}}
<a class='prev disabled'>
{{/if}}
<% if (prevLinkActive) { %>
<a class='prev' href='<%= prevLink %>'>
<% } else { %>
<a class='prev disabled'>
<% } %>
<i class='fa fa-chevron-left'></i>
<span>Previous page</span>
</a>
</li>
{{#each this.pages}}
{{#if this.ellipsis}}
<% _.each(pages, page => { %>
<% if (page.ellipsis) { %>
<li>&hellip;</li>
{{else}}
{{#if this.active}}
<% } else { %>
<% if (page.active) { %>
<li class='active'>
{{else}}
<% } else { %>
<li>
{{/if}}
<a href='{{this.link}}'>{{this.number}}</a>
<% } %>
<a href='<%= page.link %>'><%= page.number %></a>
</li>
{{/if}}
{{/each}}
<% } %>
<% }) %>
<li>
{{#if this.nextLinkActive}}
<a class='next' href='{{this.nextLink}}'>
{{else}}
<a class='next disabled'>
{{/if}}
<% if (nextLinkActive) { %>
<a class='next' href='<%= nextLink %>'>
<% } else { %>
<a class='next disabled'>
<% } %>
<i class='fa fa-chevron-right'></i>
<span>Next page</span>
</a>

View File

@ -4,7 +4,7 @@
<div class='input'>
<ul>
<li>
{{textInput text='User name or e-mail address' id='user-name' name='user-name' required=true}}
<%= makeTextInput({text: 'User name or e-mail address', id: 'user-name', name: 'user-name', required: true}) %>
</li>
</ul>
</div>

View File

@ -5,11 +5,11 @@
<div class='input'>
<ul>
<li>
{{checkbox text='Endless scroll' id='endless-scroll' name='endless-scroll' checked=this.browsingSettings.endlessScroll}}
<%= makeCheckbox({text: 'Endless scroll', id: 'endless-scroll', name: 'endless-scroll', checked: browsingSettings.endlessScroll}) %>
<p class='hint'>Rather than using a paged navigation, smoothly scroll through the content.</p>
</li>
<li>
{{checkbox text='Enable keyboard shortcuts' id='keyboard-shortcuts' name='keyboard-shortcuts' checked=this.browsingSettings.keyboardShortcuts}}
<%= makeCheckbox({text: 'Enable keyboard shortcuts', id: 'keyboard-shortcuts', name: 'keyboard-shortcuts', checked: browsingSettings.keyboardShortcuts}) %>
<a class='append icon' href='/help/keyboard'><i class='fa fa-question-circle-o'></i></a>
</li>
</ul>

View File

@ -3,7 +3,7 @@
<div class='input'>
<ul>
<li>
{{textInput id='search-text' name='search-text' value=this.searchQuery.text}}
<%= makeTextInput({id: 'search-text', name: 'search-text', value: searchQuery.text}) %>
</li>
</ul>
</div>

View File

@ -1,5 +1,5 @@
<div class='tag-list'>
{{#if this.results}}
<% if (results) { %>
<table>
<thead>
<th class='names'>Tag name(s)</th>
@ -8,43 +8,43 @@
<th class='usages'>Usages</th>
</thead>
<tbody>
{{#each this.results}}
<% _.each(results, tag => { %>
<tr>
<td class='names'>
<ul>
{{#each this.names}}
<li><a href='/tag/{{this}}'>{{this}}</a></li>
{{/each}}
<% _.each(tag.names, name => { %>
<li><a href='/tag/<%= name %>'><%= name %></a></li>
<% }) %>
</ul>
</td>
<td class='implications'>
{{#if this.implications}}
<% if (tag.implications.length) { %>
<ul>
{{#each this.implications}}
<li><a href='/tag/{{this}}'>{{this}}</a></li>
{{/each}}
<% _.each(tag.implications, name => { %>
<li><a href='/tag/<%= name %>'><%= name %></a></li>
<% }) %>
</ul>
{{else}}
<% } else { %>
-
{{/if}}
<% } %>
</td>
<td class='suggestions'>
{{#if this.suggestions}}
<% if (tag.suggestions.length) { %>
<ul>
{{#each this.suggestions}}
<li><a href='/tag/{{this}}'>{{this}}</a></li>
{{/each}}
<% _.each(tag.suggestions, name => { %>
<li><a href='/tag/<%= name %>'><%= name %></a></li>
<% }) %>
</ul>
{{else}}
<% } else { %>
-
{{/if}}
<% } %>
</td>
<td class='usages'>
{{this.usages}}
<%= tag.usages %>
</td>
</tr>
{{/each}}
<% }) %>
</tbody>
</table>
{{/if}}
<% } %>
</div>

View File

@ -1,14 +1,14 @@
<nav id='top-nav' class='text-nav'>
<ul><!--
-->{{#each items}}<!--
-->{{#if this.available}}<!--
--><li data-name='{{@key}}'><!--
--><a href='{{this.url}}' accesskey='{{this.accessKey}}'><!--
-->{{#if this.imageUrl}}{{thumbnail this.imageUrl}}{{/if}}<!--
--><span class='text'>{{this.name}}</span><!--
--><% _.each(items, (item, key) => { %><!--
--><% if (item.available) { %><!--
--><li data-name='<%= key %>'><!--
--><a href='<%= item.url %>' accesskey='<%= item.accessKey %>'><!--
--><% if (item.imageUrl) { print(makeThumbnail(item.imageUrl)); } %><!--
--><span class='text'><%- item.name %></span><!--
--></a><!--
--></li><!--
-->{{/if}}<!--
-->{{/each}}<!--
--><% } %><!--
--><% }) %><!--
--></ul>
</nav>

View File

@ -1,14 +1,14 @@
<div class='content-wrapper' id='user'>
<h1>{{this.user.name}}</h1>
<h1><%= user.name %></h1>
<nav class='text-nav'><!--
--><ul><!--
--><li data-name='summary'><a href='/user/{{this.user.name}}'>Summary</a></li><!--
-->{{#if this.canEditAnything}}<!--
--><li data-name='edit'><a href='/user/{{this.user.name}}/edit'>Account settings</a></li><!--
-->{{/if}}<!--
-->{{#if this.canDelete}}<!--
--><li data-name='delete'><a href='/user/{{this.user.name}}/delete'>Account deletion</a></li><!--
-->{{/if}}<!--
--><li data-name='summary'><a href='/user/<%= user.name %>'>Summary</a></li><!--
--><% if (canEditAnything) { %><!--
--><li data-name='edit'><a href='/user/<%= user.name %>/edit'>Account settings</a></li><!--
--><% } %><!--
--><% if (canDelete) { %><!--
--><li data-name='delete'><a href='/user/<%= user.name %>/delete'>Account deletion</a></li><!--
--><% } %><!--
--></ul><!--
--></nav>
<div id='user-content-holder'></div>

View File

@ -3,7 +3,7 @@
<div class='input'>
<ul>
<li>
{{checkbox id='confirm-deletion' name='confirm-deletion' required=true text='I confirm that I want to delete this account.'}}
<%= makeCheckbox({id: 'confirm-deletion', name: 'confirm-deletion', required: true, text: 'I confirm that I want to delete this account.'}) %>
</li>
</ul>
</div>

View File

@ -3,29 +3,29 @@
<div class='left'>
<div class='input'>
<ul>
{{#if this.canEditName}}
<% if (canEditName) { %>
<li>
{{textInput text='User name' id='user-name' name='name' value=this.user.name pattern=this.userNamePattern}}
<%= makeTextInput({text: 'User name', id: 'user-name', name: 'name', value: user.name, pattern: userNamePattern}) %>
</li>
{{/if}}
<% } %>
{{#if this.canEditPassword}}
<% if (canEditPassword) { %>
<li>
{{passwordInput text='Password' id='user-password' name='password' placeholder='leave blank if not changing' pattern=this.passwordPattern}}
<%= makePasswordInput({text: 'Password', id: 'user-password', name: 'password', placeholder: 'leave blank if not changing', pattern: passwordPattern}) %>
</li>
{{/if}}
<% } %>
{{#if this.canEditEmail}}
<% if (canEditEmail) { %>
<li>
{{emailInput text='Email' id='user-email' name='email' value=this.user.email}}
<%= makeEmailInput({text: 'Email', id: 'user-email', name: 'email', value: user.email}) %>
</li>
{{/if}}
<% } %>
{{#if this.canEditRank}}
<% if (canEditRank) { %>
<li>
{{select text='Rank' id='user-rank' name='rank' keyValues=this.ranks selectedKey=this.user.rank}}
<%= makeSelect({text: 'Rank', id: 'user-rank', name: 'rank', keyValues: ranks, selectedKey: user.rank}) %>
</li>
{{/if}}
<% } %>
</ul>
</div>
<div class='messages'></div>
@ -34,18 +34,18 @@
</div>
</div>
{{#if this.canEditAvatar}}
<% if (canEditAvatar) { %>
<div class='right'>
<ul>
<li>
{{radio text='Gravatar' id='gravatar-radio' name='avatar-style' value='gravatar' selectedValue=this.user.avatarStyle}}
<%= makeRadio({text: 'Gravatar', id: 'gravatar-radio', name: 'avatar-style', value: 'gravatar', selectedValue: user.avatarStyle}) %>
</li>
<li>
{{radio text='Manual avatar' id='avatar-radio' name='avatar-style' value='manual' selectedValue=this.user.avatarStyle}}
<%= makeRadio({text: 'Manual avatar', id: 'avatar-radio', name: 'avatar-style', value: 'manual', selectedValue: user.avatarStyle}) %>
<div id='avatar-content'></div>
</li>
</ul>
</div>
{{/if}}
<% } %>
</form>
</div>

View File

@ -3,7 +3,7 @@
<div class='input'>
<ul>
<li>
{{textInput id='search-text' name='search-text' value=this.searchQuery.text}}
<%= makeTextInput({id: 'search-text', name: 'search-text', value: searchQuery.text}) %>
</li>
</ul>
</div>

View File

@ -1,17 +1,17 @@
<div class='user-list'>
<ul><!--
-->{{#each this.results}}<!--
--><% _.each(results, user => { %><!--
--><li>
<div class='wrapper'>
<a class='image' href='/user/{{this.name}}'>{{thumbnail this.avatarUrl}}</a>
<a class='image' href='/user/<%= user.name %>'><%= makeThumbnail(user.avatarUrl) %></a>
<div class='details'>
<a href='/user/{{this.name}}'>{{this.name}}</a><br/>
Registered: {{reltime this.creationTime}}<br/>
Last seen: {{reltime this.lastLoginTime}}
<a href='/user/<%= user.name %>'><%= user.name %></a><br/>
Registered: <%= makeRelativeTime(user.creationTime) %><br/>
Last seen: <%= makeRelativeTime(user.lastLoginTime) %>
</div>
</div>
</li><!--
-->{{/each}}<!--
-->{{alignFlexbox}}<!--
--><% }) %><!--
--><%= makeFlexboxAlign() %><!--
--></ul>
</div>

View File

@ -4,13 +4,13 @@
<div class='input'>
<ul>
<li>
{{textInput text='User name' id='user-name' name='user-name' placeholder='letters, digits, _, -' required=true pattern=this.userNamePattern}}
<%= makeTextInput({text: 'User name', id: 'user-name', name: 'user-name', placeholder: 'letters, digits, _, -', required: true, pattern: userNamePattern}) %>
</li>
<li>
{{passwordInput text='Password' id='user-password' name='user-password' placeholder='5+ characters' required=true pattern=this.passwordPattern}}
<%= makePasswordInput({text: 'Password', id: 'user-password', name: 'user-password', placeholder: '5+ characters', required: true, pattern: passwordPattern}) %>
</li>
<li>
{{emailInput text='Email' id='user-email' name='user-email' placeholder='optional'}}
<%= makeEmailInput({text: 'Email', id: 'user-email', name: 'user-email', placeholder: 'optional'}) %>
<p class='hint'>Used for password reminder and to show a <a href='http://gravatar.com/'>Gravatar</a>. Leave blank for random Gravatar.</p>
</li>
</ul>

View File

@ -1,22 +1,22 @@
<div id='user-summary'>
{{thumbnail this.user.avatarUrl}}
<%= makeThumbnail(user.avatarUrl) %>
<ul class='basic-info'>
<li>Registered: {{reltime this.user.creationTime}}</li>
<li>Last seen: {{reltime this.user.lastLoginTime}}</li>
<li>Rank: {{toLowerCase this.user.rankName}}</li>
<li>Registered: <%= makeRelativeTime(user.creationTime) %></li>
<li>Last seen: <%= makeRelativeTime(user.lastLoginTime) %></li>
<li>Rank: <%= user.rankName.toLowerCase() %></li>
</ul>
<div>
<nav class='plain-nav'>
<p><strong>Quick links</strong></p>
<ul>
<li><a href='/posts/submit:{{this.user.name}}'>Uploads</a></li>
<li><a href='/posts/fav:{{this.user.name}}'>Favorites</a></li>
<li><a href='/posts/commented:{{this.user.name}}'>Posts commented on</a></li>
<li><a href='/posts/submit:<%= user.name %>'>Uploads</a></li>
<li><a href='/posts/fav:<%= user.name %>'>Favorites</a></li>
<li><a href='/posts/comment:<%= user.name %>'>Posts commented on</a></li>
</ul>
</nav>
{{#if this.isLoggedIn}}
<% if (isLoggedIn) { %>
<nav class='plain-nav'>
<p><strong>Only visible to you</strong></p>
<ul>
@ -24,6 +24,6 @@
<li><a href='/posts/special:disliked'>Disliked posts</a></li>
</ul>
</nav>
{{/if}}
<% } %>
</div>
</div>

View File

@ -1,113 +0,0 @@
'use strict';
const views = require('../util/views.js');
const handlebars = require('handlebars');
const misc = require('./misc.js');
function makeLabel(options, attrs) {
if (!options.hash.text) {
return '';
}
if (!attrs) {
attrs = {};
}
attrs.for = options.hash.id;
return views.makeNonVoidElement('label', attrs, options.hash.text);
}
handlebars.registerHelper('reltime', function(time) {
return new handlebars.SafeString(
views.makeNonVoidElement(
'time',
{datetime: time, title: time},
misc.formatRelativeTime(time)));
});
handlebars.registerHelper('thumbnail', function(url) {
return new handlebars.SafeString(
views.makeNonVoidElement('span', {
class: 'thumbnail',
style: 'background-image: url(\'{0}\')'.format(url)
}, views.makeVoidElement('img', {alt: 'thumbnail', src: url})));
});
handlebars.registerHelper('toLowerCase', function(str) {
return str.toLowerCase();
});
handlebars.registerHelper('radio', function(options) {
return new handlebars.SafeString('{0}{1}'.format(
views.makeVoidElement('input', {
id: options.hash.id,
name: options.hash.name,
value: options.hash.value,
type: 'radio',
checked: options.hash.selectedValue === options.hash.value,
required: options.hash.required,
}),
makeLabel(options, {class: 'radio'})));
});
handlebars.registerHelper('checkbox', function(options) {
return new handlebars.SafeString('{0}{1}'.format(
views.makeVoidElement('input', {
id: options.hash.id,
name: options.hash.name,
value: options.hash.value,
type: 'checkbox',
checked: options.hash.checked !== undefined ?
options.hash.checked : false,
required: options.hash.required,
}),
makeLabel(options, {class: 'checkbox'})));
});
handlebars.registerHelper('select', function(options) {
return new handlebars.SafeString('{0}{1}'.format(
makeLabel(options),
views.makeNonVoidElement(
'select',
{id: options.hash.id, name: options.hash.name},
Object.keys(options.hash.keyValues).map(key => {
return views.makeNonVoidElement(
'option',
{value: key, selected: key === options.hash.selectedKey},
options.hash.keyValues[key]);
}).join(''))));
});
handlebars.registerHelper('input', function(options) {
return new handlebars.SafeString('{0}{1}'.format(
makeLabel(options),
views.makeVoidElement(
'input', {
type: options.hash.inputType,
name: options.hash.name,
id: options.hash.id,
value: options.hash.value || '',
required: options.hash.required,
pattern: options.hash.pattern,
placeholder: options.hash.placeholder,
})));
});
handlebars.registerHelper('textInput', function(options) {
options.hash.inputType = 'text';
return handlebars.helpers.input(options);
});
handlebars.registerHelper('passwordInput', function(options) {
options.hash.inputType = 'password';
return handlebars.helpers.input(options);
});
handlebars.registerHelper('emailInput', function(options) {
options.hash.inputType = 'email';
return handlebars.helpers.input(options);
});
handlebars.registerHelper('alignFlexbox', function(options) {
return new handlebars.SafeString(
Array.from(misc.range(20))
.map(() => '<li class="flexbox-dummy"></li>').join(''));
});

View File

@ -1,9 +1,114 @@
'use strict';
require('../util/polyfill.js');
const handlebars = require('handlebars');
const underscore = require('underscore');
const events = require('../events.js');
const domParser = new DOMParser();
const misc = require('./misc.js');
function _makeLabel(options, attrs) {
if (!options.text) {
return '';
}
if (!attrs) {
attrs = {};
}
attrs.for = options.id;
return makeNonVoidElement('label', attrs, options.text);
}
function makeRelativeTime(time) {
return makeNonVoidElement(
'time',
{datetime: time, title: time},
misc.formatRelativeTime(time));
}
function makeThumbnail(url) {
return makeNonVoidElement(
'span',
{
class: 'thumbnail',
style: 'background-image: url(\'{0}\')'.format(url)
},
makeVoidElement('img', {alt: 'thumbnail', src: url}));
}
function makeRadio(options) {
return makeVoidElement(
'input',
{
id: options.id,
name: options.name,
value: options.value,
type: 'radio',
checked: options.selectedValue === options.value,
required: options.required,
}) +
_makeLabel(options, {class: 'radio'});
}
function makeCheckbox(options) {
return makeVoidElement(
'input',
{
id: options.id,
name: options.name,
value: options.value,
type: 'checkbox',
checked: options.checked !== undefined ?
options.checked : false,
required: options.required,
}) +
_makeLabel(options, {class: 'checkbox'});
}
function makeSelect(options) {
return _makeLabel(options) +
makeNonVoidElement(
'select',
{id: options.id, name: options.name},
Object.keys(options.keyValues).map(key => {
return makeNonVoidElement(
'option',
{value: key, selected: key === options.selectedKey},
options.keyValues[key]);
}).join(''));
}
function makeInput(options) {
return _makeLabel(options)
+ makeVoidElement(
'input', {
type: options.inputType,
name: options.name,
id: options.id,
value: options.value || '',
required: options.required,
pattern: options.pattern,
placeholder: options.placeholder,
});
}
function makeTextInput(options) {
options.inputType = 'text';
return makeInput(options);
}
function makePasswordInput(options) {
options.inputType = 'password';
return makeInput(options);
}
function makeEmailInput(options) {
options.inputType = 'email';
return makeInput(options);
}
function makeFlexboxAlign(options) {
return Array.from(misc.range(20))
.map(() => '<li class="flexbox-dummy"></li>').join('');
}
function _messageHandler(target, message, className) {
if (!message) {
@ -81,9 +186,24 @@ function getTemplate(templatePath) {
return null;
}
const templateText = templates[templatePath].trim();
const templateFactory = handlebars.compile(templateText);
return (...args) => {
return htmlToDom(templateFactory(...args));
const templateFactory = underscore.template(templateText);
return ctx => {
if (!ctx) {
ctx = {};
}
underscore.extend(ctx, {
makeRelativeTime: makeRelativeTime,
makeThumbnail: makeThumbnail,
makeRadio: makeRadio,
makeCheckbox: makeCheckbox,
makeSelect: makeSelect,
makeInput: makeInput,
makeTextInput: makeTextInput,
makePasswordInput: makePasswordInput,
makeEmailInput: makeEmailInput,
makeFlexboxAlign: makeFlexboxAlign,
});
return htmlToDom(templateFactory(ctx));
};
}

View File

@ -10,6 +10,7 @@ class FileDropperControl {
render(ctx) {
const target = ctx.target;
const source = this.template({
allowMultiple: ctx.allowMultiple,
id: 'file-' + Math.random().toString(36).substring(7),
});

View File

@ -10,7 +10,11 @@ class LoginView {
render(ctx) {
const target = document.getElementById('content-holder');
const source = this.template({canSendMails: config.canSendMails});
const source = this.template({
userNamePattern: config.userNameRegex,
passwordPattern: config.passwordRegex,
canSendMails: config.canSendMails,
});
const form = source.querySelector('form');
const userNameField = source.querySelector('#user-name');

View File

@ -1,6 +1,5 @@
'use strict';
require('../util/handlebars-helpers.js');
const views = require('../util/views.js');
class TopNavView {

View File

@ -14,7 +14,6 @@
"camelcase-keys": "*",
"csso": "^1.8.0",
"glob": "^7.0.3",
"handlebars": "^4.0.5",
"html-minifier": "^1.3.1",
"js-cookie": "^2.1.0",
"js-yaml": "^3.5.5",
@ -24,7 +23,8 @@
"page": "^1.7.1",
"stylus": "^0.54.2",
"superagent": "^1.8.3",
"uglify-js": "git://github.com/mishoo/UglifyJS2.git#harmony"
"uglify-js": "git://github.com/mishoo/UglifyJS2.git#harmony",
"underscore": "^1.8.3"
},
"devDependencies": {
"watch": "latest"