client/tags: add summary view, add tag description

This commit is contained in:
rr- 2016-06-21 18:16:27 +02:00
parent 7eec347bca
commit f3049e5546
11 changed files with 217 additions and 98 deletions

View file

@ -156,6 +156,7 @@ textarea,
input[type=text],
input[type=email],
input[type=password]
vertical-align: top
font-family: 'Droid Sans', sans-serif
font-size: 100%
line-height: 150%

View file

@ -67,6 +67,30 @@
.tag-delete
.messages, .buttons
margin-left: 0 !important
.tag-edit
textarea
height: 10em
.tag-summary
section
&.description
margin: 1.5em 0 0 0
&.details
vertical-align: top
padding-right: 0.5em
ul
margin: 0
padding: 0
list-style-type: none
li
display: inline
margin: 0
padding: 0
li:not(:last-of-type):after
content: ', '
ul:empty:after
content: '(none)'
section
margin-bottom: 1em
.content-wrapper.tag-categories
width: 100%

View file

@ -3,6 +3,9 @@
<nav class='buttons'><!--
--><ul><!--
--><li data-name='summary'><a href='/tag/<%= ctx.tag.names[0] %>'>Summary</a></li><!--
--><% if (ctx.canMerge) { %><!--
--><li data-name='edit'><a href='/tag/<%= ctx.tag.names[0] %>/edit'>Edit</a></li><!--
--><% } %><!--
--><% if (ctx.canMerge) { %><!--
--><li data-name='merge'><a href='/tag/<%= ctx.tag.names[0] %>/merge'>Merge with&hellip;</a></li><!--
--><% } %><!--

29
client/html/tag_edit.tpl Normal file
View file

@ -0,0 +1,29 @@
<div class='content-wrapper tag-edit'>
<form class='tabular'>
<div class='input'>
<ul>
<li class='names'>
<%= ctx.makeTextInput({text: 'Names', value: ctx.tag.names.join(' '), required: true, readonly: !ctx.canEditNames, pattern: ctx.tagNamesPattern}) %>
</li>
<li class='category'>
<%= ctx.makeSelect({text: 'Category', keyValues: ctx.categories, selectedKey: ctx.tag.category, required: true, readonly: !ctx.canEditCategory}) %>
</li>
<li class='implications'>
<%= ctx.makeTextInput({text: 'Implications', value: ctx.tag.implications.join(' '), readonly: !ctx.canEditImplications}) %>
</li>
<li class='suggestions'>
<%= ctx.makeTextInput({text: 'Suggestions', value: ctx.tag.suggestions.join(' '), readonly: !ctx.canEditSuggestions}) %>
</li>
<li class='description'>
<%= ctx.makeTextarea({text: 'Description', value: ctx.tag.description, readonly: !ctx.canEditDescription}) %>
</li>
</ul>
</div>
<% if (ctx.canEditNames || ctx.canEditCategory || ctx.canEditImplications || ctx.canEditSuggestions) { %>
<div class='messages'></div>
<div class='buttons'>
<input type='submit' class='save' value='Save changes'>
</div>
<% } %>
</form>
</div>

View file

@ -1,26 +1,40 @@
<div class='content-wrapper tag-summary'>
<form class='tabular'>
<div class='input'>
<ul>
<li class='names'>
<%= ctx.makeTextInput({text: 'Names', value: ctx.tag.names.join(' '), required: true, readonly: !ctx.canEditNames, pattern: ctx.tagNamesPattern}) %>
</li>
<li class='category'>
<%= ctx.makeSelect({text: 'Category', keyValues: ctx.categories, selectedKey: ctx.tag.category, required: true, readonly: !ctx.canEditCategory}) %>
</li>
<li class='implications'>
<%= ctx.makeTextInput({text: 'Implications', value: ctx.tag.implications.join(' '), readonly: !ctx.canEditImplications}) %>
</li>
<li class='suggestions'>
<%= ctx.makeTextInput({text: 'Suggestions', value: ctx.tag.suggestions.join(' '), readonly: !ctx.canEditSuggestions}) %>
</li>
</ul>
</div>
<% if (ctx.canEditNames || ctx.canEditCategory || ctx.canEditImplications || ctx.canEditSuggestions) { %>
<div class='messages'></div>
<div class='buttons'>
<input type='submit' class='save' value='Save changes'>
</div>
<% } %>
</form>
<section class='details'>
<section>
Category:
<span class='<%= ctx.makeCssName(ctx.tag.category, 'tag') %>'><%= ctx.tag.category %></span>
</section>
<section>
Aliases:<br/>
<ul><!--
--><% for (let name of ctx.tag.names.slice(1)) { %><!--
--><li><%= ctx.makeTagLink(name) %></li><!--
--><% } %><!--
--></ul>
</section>
<section>
Implications:<br/>
<ul><!--
--><% for (let tag of ctx.tag.implications) { %><!--
--><li><%= ctx.makeTagLink(tag) %></li><!--
--><% } %><!--
--></ul>
</section>
<section>
Suggestions:<br/>
<ul><!--
--><% for (let tag of ctx.tag.suggestions) { %><!--
--><li><%= ctx.makeTagLink(tag) %></li><!--
--><% } %><!--
--></ul>
</section>
</section>
<section class='description'>
<hr/>
<%= ctx.makeMarkdown(ctx.tag.description || 'This tag has no description yet.') %>
</section>
</div>

View file

@ -28,6 +28,7 @@ class TagController {
canEditCategory: api.hasPrivilege('tags:edit:category'),
canEditImplications: api.hasPrivilege('tags:edit:implications'),
canEditSuggestions: api.hasPrivilege('tags:edit:suggestions'),
canEditDescription: api.hasPrivilege('tags:edit:description'),
canMerge: api.hasPrivilege('tags:delete'),
canDelete: api.hasPrivilege('tags:merge'),
categories: categories,
@ -55,6 +56,7 @@ class TagController {
e.detail.tag.category = e.detail.category;
e.detail.tag.implications = e.detail.implications;
e.detail.tag.suggestions = e.detail.suggestions;
e.detail.tag.description = e.detail.description;
e.detail.tag.save().then(() => {
this._view.showSuccess('Tag saved.');
this._view.enableForm();
@ -94,6 +96,9 @@ module.exports = router => {
router.enter('/tag/:name', (ctx, next) => {
ctx.controller = new TagController(ctx, 'summary');
});
router.enter('/tag/:name/edit', (ctx, next) => {
ctx.controller = new TagController(ctx, 'edit');
});
router.enter('/tag/:name/merge', (ctx, next) => {
ctx.controller = new TagController(ctx, 'merge');
});

View file

@ -9,6 +9,7 @@ class Tag extends events.EventTarget {
this._origName = null;
this._names = null;
this._category = null;
this._description = null;
this._suggestions = null;
this._implications = null;
this._postCount = null;
@ -18,6 +19,7 @@ class Tag extends events.EventTarget {
get names() { return this._names; }
get category() { return this._category; }
get description() { return this._description; }
get suggestions() { return this._suggestions; }
get implications() { return this._implications; }
get postCount() { return this._postCount; }
@ -26,6 +28,7 @@ class Tag extends events.EventTarget {
set names(value) { this._names = value; }
set category(value) { this._category = value; }
set description(value) { this._description = value; }
set implications(value) { this._implications = value; }
set suggestions(value) { this._suggestions = value; }
@ -48,6 +51,7 @@ class Tag extends events.EventTarget {
const detail = {
names: this.names,
category: this.category,
description: this.description,
implications: this.implications,
suggestions: this.suggestions,
};
@ -103,6 +107,7 @@ class Tag extends events.EventTarget {
this._origName = response.names ? response.names[0] : null;
this._names = response.names;
this._category = response.category;
this._description = response.description;
this._implications = response.implications;
this._suggestions = response.suggestions;
this._creationTime = response.creationTime;

View file

@ -112,6 +112,12 @@ function makeTextInput(options) {
return makeInput(options);
}
function makeTextarea(options) {
const value = options.value || '';
delete options.value;
return _makeLabel(options) + makeNonVoidElement('textarea', options, value);
}
function makePasswordInput(options) {
options.type = 'password';
return makeInput(options);
@ -303,6 +309,7 @@ function getTemplate(templatePath) {
makeSelect: makeSelect,
makeInput: makeInput,
makeButton: makeButton,
makeTextarea: makeTextarea,
makeTextInput: makeTextInput,
makePasswordInput: makePasswordInput,
makeEmailInput: makeEmailInput,

View file

@ -0,0 +1,95 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const views = require('../util/views.js');
const TagInputControl = require('../controls/tag_input_control.js');
const template = views.getTemplate('tag-edit');
function _split(str) {
return str.split(/\s+/).filter(s => s);
}
class TagEditView extends events.EventTarget {
constructor(ctx) {
super();
this._tag = ctx.tag;
this._hostNode = ctx.hostNode;
const baseRegex = config.tagNameRegex.replace(/[\^\$]/g, '');
ctx.tagNamesPattern = '^((' + baseRegex + ')\\s+)*(' + baseRegex + ')$';
views.replaceContent(this._hostNode, template(ctx));
views.decorateValidator(this._formNode);
if (this._implicationsFieldNode) {
new TagInputControl(this._implicationsFieldNode);
}
if (this._suggestionsFieldNode) {
new TagInputControl(this._suggestionsFieldNode);
}
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
}
clearMessages() {
views.clearMessages(this._hostNode);
}
enableForm() {
views.enableForm(this._formNode);
}
disableForm() {
views.disableForm(this._formNode);
}
showSuccess(message) {
views.showSuccess(this._hostNode, message);
}
showError(message) {
views.showError(this._hostNode, message);
}
_evtSubmit(e) {
e.preventDefault();
this.dispatchEvent(new CustomEvent('submit', {
detail: {
tag: this._tag,
names: _split(this._namesFieldNode.value),
category: this._categoryFieldNode.value,
implications: _split(this._implicationsFieldNode.value),
suggestions: _split(this._suggestionsFieldNode.value),
description: this._descriptionFieldNode.value,
},
}));
}
get _formNode() {
return this._hostNode.querySelector('form');
}
get _namesFieldNode() {
return this._formNode.querySelector('.names input');
}
get _categoryFieldNode() {
return this._formNode.querySelector('.category select');
}
get _implicationsFieldNode() {
return this._formNode.querySelector('.implications input');
}
get _suggestionsFieldNode() {
return this._formNode.querySelector('.suggestions input');
}
get _descriptionFieldNode() {
return this._formNode.querySelector('.description textarea');
}
}
module.exports = TagEditView;

View file

@ -1,48 +1,14 @@
'use strict';
const config = require('../config.js');
const events = require('../events.js');
const views = require('../util/views.js');
const TagInputControl = require('../controls/tag_input_control.js');
const template = views.getTemplate('tag-summary');
function _split(str) {
return str.split(/\s+/).filter(s => s);
}
class TagSummaryView extends events.EventTarget {
class TagSummaryView {
constructor(ctx) {
super();
this._tag = ctx.tag;
this._hostNode = ctx.hostNode;
const baseRegex = config.tagNameRegex.replace(/[\^\$]/g, '');
ctx.tagNamesPattern = '^((' + baseRegex + ')\\s+)*(' + baseRegex + ')$';
views.replaceContent(this._hostNode, template(ctx));
views.decorateValidator(this._formNode);
if (this._implicationsFieldNode) {
new TagInputControl(this._implicationsFieldNode);
}
if (this._suggestionsFieldNode) {
new TagInputControl(this._suggestionsFieldNode);
}
this._formNode.addEventListener('submit', e => this._evtSubmit(e));
}
clearMessages() {
views.clearMessages(this._hostNode);
}
enableForm() {
views.enableForm(this._formNode);
}
disableForm() {
views.disableForm(this._formNode);
}
showSuccess(message) {
@ -52,39 +18,6 @@ class TagSummaryView extends events.EventTarget {
showError(message) {
views.showError(this._hostNode, message);
}
_evtSubmit(e) {
e.preventDefault();
this.dispatchEvent(new CustomEvent('submit', {
detail: {
tag: this._tag,
names: _split(this._namesFieldNode.value),
category: this._categoryFieldNode.value,
implications: _split(this._implicationsFieldNode.value),
suggestions: _split(this._suggestionsFieldNode.value),
},
}));
}
get _formNode() {
return this._hostNode.querySelector('form');
}
get _namesFieldNode() {
return this._formNode.querySelector('.names input');
}
get _categoryFieldNode() {
return this._formNode.querySelector('.category select');
}
get _implicationsFieldNode() {
return this._formNode.querySelector('.implications input');
}
get _suggestionsFieldNode() {
return this._formNode.querySelector('.suggestions input');
}
}
module.exports = TagSummaryView;

View file

@ -3,6 +3,7 @@
const events = require('../events.js');
const views = require('../util/views.js');
const TagSummaryView = require('./tag_summary_view.js');
const TagEditView = require('./tag_edit_view.js');
const TagMergeView = require('./tag_merge_view.js');
const TagDeleteView = require('./tag_delete_view.js');
@ -30,13 +31,19 @@ class TagView extends events.EventTarget {
}
ctx.hostNode = this._hostNode.querySelector('.tag-content-holder');
if (ctx.section == 'merge') {
if (ctx.section === 'edit') {
this._view = new TagEditView(ctx);
this._view.addEventListener('submit', e => {
this.dispatchEvent(
new CustomEvent('change', {detail: e.detail}));
});
} else if (ctx.section === 'merge') {
this._view = new TagMergeView(ctx);
this._view.addEventListener('submit', e => {
this.dispatchEvent(
new CustomEvent('merge', {detail: e.detail}));
});
} else if (ctx.section == 'delete') {
} else if (ctx.section === 'delete') {
this._view = new TagDeleteView(ctx);
this._view.addEventListener('submit', e => {
this.dispatchEvent(
@ -44,10 +51,6 @@ class TagView extends events.EventTarget {
});
} else {
this._view = new TagSummaryView(ctx);
this._view.addEventListener('submit', e => {
this.dispatchEvent(
new CustomEvent('change', {detail: e.detail}));
});
}
}