client/tags: add summary view, add tag description
This commit is contained in:
parent
7eec347bca
commit
f3049e5546
11 changed files with 217 additions and 98 deletions
|
@ -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%
|
||||
|
|
|
@ -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%
|
||||
|
|
|
@ -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…</a></li><!--
|
||||
--><% } %><!--
|
||||
|
|
29
client/html/tag_edit.tpl
Normal file
29
client/html/tag_edit.tpl
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
95
client/js/views/tag_edit_view.js
Normal file
95
client/js/views/tag_edit_view.js
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue