Add pool input box in post details

This commit is contained in:
Ruin0x11
2020-05-04 02:20:23 -07:00
parent e6bf102bc0
commit 8795279a73
11 changed files with 337 additions and 6 deletions

View File

@ -0,0 +1,8 @@
<div class='pool-input'>
<div class='main-control'>
<input type='text' placeholder='type to add…'/>
<!-- <button>Add</button> -->
</div>
<ul class='compact-pools'></ul>
</div>

View File

@ -87,6 +87,12 @@
</section>
<% } %>
<% if (ctx.canEditPoolPosts) { %>
<section class='pools'>
<%= ctx.makeTextInput({}) %>
</section>
<% } %>
<% if (ctx.canEditPostContent) { %>
<section class='post-content'>
<label>Content</label>

View File

@ -0,0 +1,230 @@
'use strict';
const api = require('../api.js');
const pools = require('../pools.js');
const misc = require('../util/misc.js');
const uri = require('../util/uri.js');
const Pool = require('../models/pool.js');
const settings = require('../models/settings.js');
const events = require('../events.js');
const views = require('../util/views.js');
const PoolAutoCompleteControl = require('./pool_auto_complete_control.js');
const KEY_SPACE = 32;
const KEY_RETURN = 13;
const SOURCE_INIT = 'init';
const SOURCE_IMPLICATION = 'implication';
const SOURCE_USER_INPUT = 'user-input';
const SOURCE_CLIPBOARD = 'clipboard';
const template = views.getTemplate('pool-input');
function _fadeOutListItemNodeStatus(listItemNode) {
if (listItemNode.classList.length) {
if (listItemNode.fadeTimeout) {
window.clearTimeout(listItemNode.fadeTimeout);
}
listItemNode.fadeTimeout = window.setTimeout(() => {
while (listItemNode.classList.length) {
listItemNode.classList.remove(
listItemNode.classList.item(0));
}
listItemNode.fadeTimeout = null;
}, 2500);
}
}
class PoolInputControl extends events.EventTarget {
constructor(hostNode, poolList) {
super();
this.pools = poolList;
this._hostNode = hostNode;
this._poolToListItemNode = new Map();
// dom
const editAreaNode = template();
this._editAreaNode = editAreaNode;
this._poolInputNode = editAreaNode.querySelector('input');
this._poolListNode = editAreaNode.querySelector('ul.compact-pools');
this._autoCompleteControl = new PoolAutoCompleteControl(
this._poolInputNode, {
getTextToFind: () => {
return this._poolInputNode.value;
},
confirm: pool => {
this._poolInputNode.value = '';
this.addPool(pool, SOURCE_USER_INPUT);
},
delete: pool => {
this._poolInputNode.value = '';
this.deletePool(pool);
},
verticalShift: -2
});
// dom events
this._poolInputNode.addEventListener(
'keydown', e => this._evtInputKeyDown(e));
// show
this._hostNode.style.display = 'none';
this._hostNode.parentNode.insertBefore(
this._editAreaNode, hostNode.nextSibling);
// add existing pools
for (let pool of [...this.pools]) {
const listItemNode = this._createListItemNode(pool);
this._poolListNode.appendChild(listItemNode);
}
}
addPoolByText(text, source) {
for (let poolName of text.split(/\s+/).filter(word => word).reverse()) {
this.addPoolByName(poolName, source);
}
}
addPoolByName(name, source) {
name = name.trim();
if (!name) {
return;
}
return Pool.get(name).then(pool => {
return this.addPool(pool, source);
}, () => {
const pool = new Pool();
pool.names = [name];
pool.category = null;
return this.addPool(pool, source);
});
}
addPool(pool, source) {
if (source != SOURCE_INIT && this.pools.hasPoolId(pool.id)) {
return Promise.resolve();
}
this.pools.add(pool, false)
const listItemNode = this._createListItemNode(pool);
if (!pool.category) {
listItemNode.classList.add('new');
}
this._poolListNode.prependChild(listItemNode);
_fadeOutListItemNodeStatus(listItemNode);
this.dispatchEvent(new CustomEvent('add', {
detail: {pool: pool, source: source},
}));
this.dispatchEvent(new CustomEvent('change'));
return Promise.resolve();
}
deletePool(pool) {
if (!this.pools.hasPoolId(pool.id)) {
return;
}
this.pools.removeById(pool.id);
this._hideAutoComplete();
this._deleteListItemNode(pool);
this.dispatchEvent(new CustomEvent('remove', {
detail: {pool: pool},
}));
this.dispatchEvent(new CustomEvent('change'));
}
_evtAddPoolButtonClick(e) {
// TODO
// e.preventDefault();
// this.addPoolByName(this._poolInputNode.value, SOURCE_USER_INPUT);
// this._poolInputNode.value = '';
}
_evtInputKeyDown(e) {
// TODO
if (e.which == KEY_RETURN || e.which == KEY_SPACE) {
e.preventDefault();
// this._hideAutoComplete();
// this.addPoolByText(this._poolInputNode.value, SOURCE_USER_INPUT);
// this._poolInputNode.value = '';
}
}
_createListItemNode(pool) {
const className = pool.category ?
misc.makeCssName(pool.category, 'pool') :
null;
const poolLinkNode = document.createElement('a');
if (className) {
poolLinkNode.classList.add(className);
}
poolLinkNode.setAttribute(
'href', uri.formatClientLink('pool', pool.names[0]));
const poolIconNode = document.createElement('i');
poolIconNode.classList.add('fa');
poolIconNode.classList.add('fa-pool');
poolLinkNode.appendChild(poolIconNode);
const searchLinkNode = document.createElement('a');
if (className) {
searchLinkNode.classList.add(className);
}
searchLinkNode.setAttribute(
'href', uri.formatClientLink(
'posts', {query: uri.escapeColons(pool.names[0])}));
searchLinkNode.textContent = pool.names[0] + ' ';
searchLinkNode.addEventListener('click', e => {
e.preventDefault();
});
const usagesNode = document.createElement('span');
usagesNode.classList.add('pool-usages');
usagesNode.setAttribute('data-pseudo-content', pool.postCount);
const removalLinkNode = document.createElement('a');
removalLinkNode.classList.add('remove-pool');
removalLinkNode.setAttribute('href', '');
removalLinkNode.setAttribute('data-pseudo-content', '×');
removalLinkNode.addEventListener('click', e => {
e.preventDefault();
this.deletePool(pool);
});
const listItemNode = document.createElement('li');
listItemNode.appendChild(removalLinkNode);
listItemNode.appendChild(poolLinkNode);
listItemNode.appendChild(searchLinkNode);
listItemNode.appendChild(usagesNode);
for (let name of pool.names) {
this._poolToListItemNode.set(name, listItemNode);
}
return listItemNode;
}
_deleteListItemNode(pool) {
const listItemNode = this._getListItemNode(pool);
if (listItemNode) {
listItemNode.parentNode.removeChild(listItemNode);
}
for (let name of pool.names) {
this._poolToListItemNode.delete(name);
}
}
_getListItemNode(pool) {
return this._poolToListItemNode.get(pool.names[0]);
}
_hideAutoComplete() {
this._autoCompleteControl.hide();
}
}
module.exports = PoolInputControl;

View File

@ -7,6 +7,7 @@ const views = require('../util/views.js');
const Note = require('../models/note.js');
const Point = require('../models/point.js');
const TagInputControl = require('./tag_input_control.js');
const PoolInputControl = require('./pool_input_control.js');
const ExpanderControl = require('../controls/expander_control.js');
const FileDropperControl = require('../controls/file_dropper_control.js');
@ -37,7 +38,8 @@ class PostEditSidebarControl extends events.EventTarget {
canEditPostFlags: api.hasPrivilege('posts:edit:flags'),
canEditPostContent: api.hasPrivilege('posts:edit:content'),
canEditPostThumbnail: api.hasPrivilege('posts:edit:thumbnail'),
canEditPostSource : api.hasPrivilege('posts:edit:source'),
canEditPostSource: api.hasPrivilege('posts:edit:source'),
canEditPoolPosts: api.hasPrivilege('pools:edit:posts'),
canCreateAnonymousPosts: api.hasPrivilege('posts:create:anonymous'),
canDeletePosts: api.hasPrivilege('posts:delete'),
canFeaturePosts: api.hasPrivilege('posts:feature'),
@ -56,6 +58,10 @@ class PostEditSidebarControl extends events.EventTarget {
'post-notes',
'Notes',
this._hostNode.querySelectorAll('.notes'));
this._poolsExpander = new ExpanderControl(
'post-pools',
`Pools (${this._post.pools.length})`,
this._hostNode.querySelectorAll('.pools'));
new ExpanderControl(
'post-content',
'Content',
@ -76,6 +82,11 @@ class PostEditSidebarControl extends events.EventTarget {
this._tagInputNode, post.tags);
}
if (this._poolInputNode) {
this._poolControl = new PoolInputControl(
this._poolInputNode, post.pools);
}
if (this._contentInputNode) {
this._contentFileDropper = new FileDropperControl(
this._contentInputNode, {
@ -170,6 +181,9 @@ class PostEditSidebarControl extends events.EventTarget {
this._post.notes.addEventListener(eventType, e => {
this._syncExpanderTitles();
});
this._post.pools.addEventListener(eventType, e => {
this._syncExpanderTitles();
});
}
this._tagControl.addEventListener(
@ -182,11 +196,18 @@ class PostEditSidebarControl extends events.EventTarget {
this._noteTextareaNode.addEventListener(
'change', e => this._evtNoteTextChangeRequest(e));
}
this._poolControl.addEventListener(
'change', e => {
this.dispatchEvent(new CustomEvent('change'));
this._syncExpanderTitles();
});
}
_syncExpanderTitles() {
this._notesExpander.title = `Notes (${this._post.notes.length})`;
this._tagsExpander.title = `Tags (${this._post.tags.length})`;
this._poolsExpander.title = `Pools (${this._post.pools.length})`;
}
_evtPostContentChange(e) {
@ -338,6 +359,10 @@ class PostEditSidebarControl extends events.EventTarget {
misc.splitByWhitespace(this._tagInputNode.value) :
undefined,
pools: this._poolInputNode ?
misc.splitByWhitespace(this._poolInputNode.value) :
undefined,
relations: this._relationsInputNode ?
misc.splitByWhitespace(this._relationsInputNode.value)
.map(x => parseInt(x)) :
@ -374,6 +399,10 @@ class PostEditSidebarControl extends events.EventTarget {
return this._formNode.querySelector('.tags input');
}
get _poolInputNode() {
return this._formNode.querySelector('.pools input');
}
get _loopVideoInputNode() {
return this._formNode.querySelector('.flags input[name=loop]');
}

View File

@ -22,6 +22,23 @@ class PoolList extends AbstractList {
{results: PoolList.fromResponse(response.results)}));
});
}
hasPoolId(poolId) {
for (let pool of this._list) {
if (pool.id === poolId) {
return true;
}
}
return false;
}
removeById(poolId) {
for (let pool of this._list) {
if (pool.id === poolId) {
this.remove(pool);
}
}
}
}
PoolList._itemClass = Pool;

View File

@ -7,6 +7,7 @@ const events = require('../events.js');
const TagList = require('./tag_list.js');
const NoteList = require('./note_list.js');
const CommentList = require('./comment_list.js');
const PoolList = require('./pool_list.js');
const misc = require('../util/misc.js');
class Post extends events.EventTarget {
@ -18,6 +19,7 @@ class Post extends events.EventTarget {
obj._tags = new TagList();
obj._notes = new NoteList();
obj._comments = new CommentList();
obj._pools = new PoolList();
}
this._updateFromResponse({});
@ -46,6 +48,7 @@ class Post extends events.EventTarget {
get notes() { return this._notes; }
get comments() { return this._comments; }
get relations() { return this._relations; }
get pools() { return this._pools; }
get score() { return this._score; }
get commentCount() { return this._commentCount; }
@ -128,6 +131,7 @@ class Post extends events.EventTarget {
if (this._source !== this._orig._source) {
detail.source = this._source;
}
// TODO pools
let apiPromise = this._id ?
api.put(uri.formatApiLink('post', this.id), detail, files) :
@ -304,6 +308,7 @@ class Post extends events.EventTarget {
obj._tags.sync(response.tags);
obj._notes.sync(response.notes);
obj._comments.sync(response.comments);
obj._pools.sync(response.pools);
}
Object.assign(this, map());