561 lines
17 KiB
JavaScript
561 lines
17 KiB
JavaScript
"use strict";
|
|
|
|
const api = require("../api.js");
|
|
const events = require("../events.js");
|
|
const misc = require("../util/misc.js");
|
|
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");
|
|
|
|
const template = views.getTemplate("post-edit-sidebar");
|
|
|
|
class PostEditSidebarControl extends events.EventTarget {
|
|
constructor(hostNode, post, postContentControl, postNotesOverlayControl) {
|
|
super();
|
|
this._hostNode = hostNode;
|
|
this._post = post;
|
|
this._postContentControl = postContentControl;
|
|
this._postNotesOverlayControl = postNotesOverlayControl;
|
|
this._newPostContent = null;
|
|
|
|
this._postNotesOverlayControl.switchToPassiveEdit();
|
|
|
|
views.replaceContent(
|
|
this._hostNode,
|
|
template({
|
|
post: this._post,
|
|
enableSafety: api.safetyEnabled(),
|
|
hasClipboard: document.queryCommandSupported("copy"),
|
|
canEditPostSafety: api.hasPrivilege("posts:edit:safety"),
|
|
canEditPostSource: api.hasPrivilege("posts:edit:source"),
|
|
canEditPostTags: api.hasPrivilege("posts:edit:tags"),
|
|
canEditPostRelations: api.hasPrivilege("posts:edit:relations"),
|
|
canEditPostNotes:
|
|
api.hasPrivilege("posts:edit:notes") &&
|
|
post.type !== "video" &&
|
|
post.type !== "flash",
|
|
canEditPostFlags: api.hasPrivilege("posts:edit:flags"),
|
|
canEditPostContent: api.hasPrivilege("posts:edit:content"),
|
|
canEditPostThumbnail: api.hasPrivilege("posts:edit:thumbnail"),
|
|
canEditPoolPosts: api.hasPrivilege("pools:edit:posts"),
|
|
canCreateAnonymousPosts: api.hasPrivilege(
|
|
"posts:create:anonymous"
|
|
),
|
|
canDeletePosts: api.hasPrivilege("posts:delete"),
|
|
canFeaturePosts: api.hasPrivilege("posts:feature"),
|
|
canMergePosts: api.hasPrivilege("posts:merge"),
|
|
})
|
|
);
|
|
|
|
new ExpanderControl(
|
|
"post-info",
|
|
"Basic info",
|
|
this._hostNode.querySelectorAll(
|
|
".safety, .relations, .flags, .post-source"
|
|
)
|
|
);
|
|
this._tagsExpander = new ExpanderControl(
|
|
"post-tags",
|
|
`Tags (${this._post.tags.length})`,
|
|
this._hostNode.querySelectorAll(".tags")
|
|
);
|
|
this._notesExpander = new ExpanderControl(
|
|
"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",
|
|
this._hostNode.querySelectorAll(".post-content, .post-thumbnail")
|
|
);
|
|
new ExpanderControl(
|
|
"post-management",
|
|
"Management",
|
|
this._hostNode.querySelectorAll(".management")
|
|
);
|
|
|
|
this._syncExpanderTitles();
|
|
|
|
if (this._formNode) {
|
|
this._formNode.addEventListener("submit", (e) =>
|
|
this._evtSubmit(e)
|
|
);
|
|
}
|
|
|
|
if (this._tagInputNode) {
|
|
this._tagControl = new TagInputControl(
|
|
this._tagInputNode,
|
|
post.tags
|
|
);
|
|
}
|
|
|
|
if (this._poolInputNode) {
|
|
this._poolControl = new PoolInputControl(
|
|
this._poolInputNode,
|
|
post.pools
|
|
);
|
|
}
|
|
|
|
if (this._contentInputNode) {
|
|
this._contentFileDropper = new FileDropperControl(
|
|
this._contentInputNode,
|
|
{
|
|
allowUrls: true,
|
|
lock: true,
|
|
urlPlaceholder: "...or paste an URL here.",
|
|
}
|
|
);
|
|
this._contentFileDropper.addEventListener("fileadd", (e) => {
|
|
this._newPostContent = e.detail.files[0];
|
|
});
|
|
this._contentFileDropper.addEventListener("urladd", (e) => {
|
|
this._newPostContent = e.detail.urls[0];
|
|
});
|
|
}
|
|
|
|
if (this._thumbnailInputNode) {
|
|
this._thumbnailFileDropper = new FileDropperControl(
|
|
this._thumbnailInputNode,
|
|
{ lock: true }
|
|
);
|
|
this._thumbnailFileDropper.addEventListener("fileadd", (e) => {
|
|
this._newPostThumbnail = e.detail.files[0];
|
|
this._thumbnailRemovalLinkNode.style.display = "block";
|
|
});
|
|
}
|
|
|
|
if (this._thumbnailRemovalLinkNode) {
|
|
this._thumbnailRemovalLinkNode.addEventListener("click", (e) =>
|
|
this._evtRemoveThumbnailClick(e)
|
|
);
|
|
this._thumbnailRemovalLinkNode.style.display = this._post
|
|
.hasCustomThumbnail
|
|
? "block"
|
|
: "none";
|
|
}
|
|
|
|
if (this._addNoteLinkNode) {
|
|
this._addNoteLinkNode.addEventListener("click", (e) =>
|
|
this._evtAddNoteClick(e)
|
|
);
|
|
}
|
|
|
|
if (this._copyNotesLinkNode) {
|
|
this._copyNotesLinkNode.addEventListener("click", (e) =>
|
|
this._evtCopyNotesClick(e)
|
|
);
|
|
}
|
|
|
|
if (this._pasteNotesLinkNode) {
|
|
this._pasteNotesLinkNode.addEventListener("click", (e) =>
|
|
this._evtPasteNotesClick(e)
|
|
);
|
|
}
|
|
|
|
if (this._deleteNoteLinkNode) {
|
|
this._deleteNoteLinkNode.addEventListener("click", (e) =>
|
|
this._evtDeleteNoteClick(e)
|
|
);
|
|
}
|
|
|
|
if (this._featureLinkNode) {
|
|
this._featureLinkNode.addEventListener("click", (e) =>
|
|
this._evtFeatureClick(e)
|
|
);
|
|
}
|
|
|
|
if (this._mergeLinkNode) {
|
|
this._mergeLinkNode.addEventListener("click", (e) =>
|
|
this._evtMergeClick(e)
|
|
);
|
|
}
|
|
|
|
if (this._deleteLinkNode) {
|
|
this._deleteLinkNode.addEventListener("click", (e) =>
|
|
this._evtDeleteClick(e)
|
|
);
|
|
}
|
|
|
|
this._postNotesOverlayControl.addEventListener("blur", (e) =>
|
|
this._evtNoteBlur(e)
|
|
);
|
|
|
|
this._postNotesOverlayControl.addEventListener("focus", (e) =>
|
|
this._evtNoteFocus(e)
|
|
);
|
|
|
|
this._post.addEventListener("changeContent", (e) =>
|
|
this._evtPostContentChange(e)
|
|
);
|
|
|
|
this._post.addEventListener("changeThumbnail", (e) =>
|
|
this._evtPostThumbnailChange(e)
|
|
);
|
|
|
|
if (this._formNode) {
|
|
const inputNodes =
|
|
this._formNode.querySelectorAll("input, textarea");
|
|
for (let node of inputNodes) {
|
|
node.addEventListener("change", (e) =>
|
|
this.dispatchEvent(new CustomEvent("change"))
|
|
);
|
|
}
|
|
this._postNotesOverlayControl.addEventListener("change", (e) =>
|
|
this.dispatchEvent(new CustomEvent("change"))
|
|
);
|
|
}
|
|
|
|
for (let eventType of ["add", "remove"]) {
|
|
this._post.notes.addEventListener(eventType, (e) => {
|
|
this._syncExpanderTitles();
|
|
});
|
|
this._post.pools.addEventListener(eventType, (e) => {
|
|
this._syncExpanderTitles();
|
|
});
|
|
}
|
|
|
|
this._tagControl.addEventListener("change", (e) => {
|
|
this.dispatchEvent(new CustomEvent("change"));
|
|
this._syncExpanderTitles();
|
|
});
|
|
|
|
if (this._noteTextareaNode) {
|
|
this._noteTextareaNode.addEventListener("change", (e) =>
|
|
this._evtNoteTextChangeRequest(e)
|
|
);
|
|
}
|
|
|
|
if (this._poolControl) {
|
|
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) {
|
|
this._contentFileDropper.reset();
|
|
}
|
|
|
|
_evtPostThumbnailChange(e) {
|
|
this._thumbnailFileDropper.reset();
|
|
}
|
|
|
|
_evtRemoveThumbnailClick(e) {
|
|
e.preventDefault();
|
|
this._thumbnailFileDropper.reset();
|
|
this._newPostThumbnail = null;
|
|
this._thumbnailRemovalLinkNode.style.display = "none";
|
|
}
|
|
|
|
_evtFeatureClick(e) {
|
|
e.preventDefault();
|
|
if (confirm("Are you sure you want to feature this post?")) {
|
|
this.dispatchEvent(
|
|
new CustomEvent("feature", {
|
|
detail: {
|
|
post: this._post,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
_evtMergeClick(e) {
|
|
e.preventDefault();
|
|
this.dispatchEvent(
|
|
new CustomEvent("merge", {
|
|
detail: {
|
|
post: this._post,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
|
|
_evtDeleteClick(e) {
|
|
e.preventDefault();
|
|
if (confirm("Are you sure you want to delete this post?")) {
|
|
this.dispatchEvent(
|
|
new CustomEvent("delete", {
|
|
detail: {
|
|
post: this._post,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
_evtNoteTextChangeRequest(e) {
|
|
if (this._editedNote) {
|
|
this._editedNote.text = this._noteTextareaNode.value;
|
|
}
|
|
}
|
|
|
|
_evtNoteFocus(e) {
|
|
this._editedNote = e.detail.note;
|
|
this._addNoteLinkNode.classList.remove("inactive");
|
|
this._deleteNoteLinkNode.classList.remove("inactive");
|
|
this._noteTextareaNode.removeAttribute("disabled");
|
|
this._noteTextareaNode.value = e.detail.note.text;
|
|
}
|
|
|
|
_evtNoteBlur(e) {
|
|
this._evtNoteTextChangeRequest(null);
|
|
this._addNoteLinkNode.classList.remove("inactive");
|
|
this._deleteNoteLinkNode.classList.add("inactive");
|
|
this._noteTextareaNode.blur();
|
|
this._noteTextareaNode.setAttribute("disabled", "disabled");
|
|
this._noteTextareaNode.value = "";
|
|
}
|
|
|
|
_evtAddNoteClick(e) {
|
|
e.preventDefault();
|
|
if (e.target.classList.contains("inactive")) {
|
|
return;
|
|
}
|
|
this._addNoteLinkNode.classList.add("inactive");
|
|
this._postNotesOverlayControl.switchToDrawing();
|
|
}
|
|
|
|
_evtCopyNotesClick(e) {
|
|
e.preventDefault();
|
|
let textarea = document.createElement("textarea");
|
|
textarea.style.position = "fixed";
|
|
textarea.style.opacity = "0";
|
|
textarea.value = JSON.stringify(
|
|
[...this._post.notes].map((note) => ({
|
|
polygon: [...note.polygon].map((point) => [point.x, point.y]),
|
|
text: note.text,
|
|
}))
|
|
);
|
|
document.body.appendChild(textarea);
|
|
textarea.select();
|
|
|
|
let success = false;
|
|
try {
|
|
success = document.execCommand("copy");
|
|
} catch (err) {
|
|
// continue regardless of error
|
|
}
|
|
textarea.blur();
|
|
document.body.removeChild(textarea);
|
|
alert(
|
|
success
|
|
? "Notes copied to clipboard."
|
|
: "Failed to copy the text to clipboard. Sorry."
|
|
);
|
|
}
|
|
|
|
_evtPasteNotesClick(e) {
|
|
e.preventDefault();
|
|
const text = window.prompt(
|
|
"Please enter the exported notes snapshot:"
|
|
);
|
|
if (!text) {
|
|
return;
|
|
}
|
|
const notesObj = JSON.parse(text);
|
|
this._post.notes.clear();
|
|
for (let noteObj of notesObj) {
|
|
let note = new Note();
|
|
for (let pointObj of noteObj.polygon) {
|
|
note.polygon.add(new Point(pointObj[0], pointObj[1]));
|
|
}
|
|
note.text = noteObj.text;
|
|
this._post.notes.add(note);
|
|
}
|
|
}
|
|
|
|
_evtDeleteNoteClick(e) {
|
|
e.preventDefault();
|
|
if (e.target.classList.contains("inactive")) {
|
|
return;
|
|
}
|
|
this._post.notes.remove(this._editedNote);
|
|
this._postNotesOverlayControl.switchToPassiveEdit();
|
|
}
|
|
|
|
_evtSubmit(e) {
|
|
e.preventDefault();
|
|
this.dispatchEvent(
|
|
new CustomEvent("submit", {
|
|
detail: {
|
|
post: this._post,
|
|
|
|
safety: this._safetyButtonNodes.length
|
|
? Array.from(this._safetyButtonNodes)
|
|
.filter((node) => node.checked)[0]
|
|
.value.toLowerCase()
|
|
: undefined,
|
|
|
|
flags: this._videoFlags,
|
|
|
|
tags: this._tagInputNode
|
|
? 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))
|
|
: undefined,
|
|
|
|
content: this._newPostContent
|
|
? this._newPostContent
|
|
: undefined,
|
|
|
|
thumbnail:
|
|
this._newPostThumbnail !== undefined
|
|
? this._newPostThumbnail
|
|
: undefined,
|
|
|
|
source: this._sourceInputNode
|
|
? this._sourceInputNode.value
|
|
: undefined,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
|
|
get _formNode() {
|
|
return this._hostNode.querySelector("form");
|
|
}
|
|
|
|
get _submitButtonNode() {
|
|
return this._hostNode.querySelector(".submit");
|
|
}
|
|
|
|
get _safetyButtonNodes() {
|
|
return this._formNode.querySelectorAll(".safety input");
|
|
}
|
|
|
|
get _tagInputNode() {
|
|
return this._formNode.querySelector(".tags input");
|
|
}
|
|
|
|
get _poolInputNode() {
|
|
return this._formNode.querySelector(".pools input");
|
|
}
|
|
|
|
get _loopVideoInputNode() {
|
|
return this._formNode.querySelector(".flags input[name=loop]");
|
|
}
|
|
|
|
get _soundVideoInputNode() {
|
|
return this._formNode.querySelector(".flags input[name=sound]");
|
|
}
|
|
|
|
get _videoFlags() {
|
|
if (!this._loopVideoInputNode) {
|
|
return undefined;
|
|
}
|
|
let ret = [];
|
|
if (this._loopVideoInputNode.checked) {
|
|
ret.push("loop");
|
|
}
|
|
if (this._soundVideoInputNode.checked) {
|
|
ret.push("sound");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
get _relationsInputNode() {
|
|
return this._formNode.querySelector(".relations input");
|
|
}
|
|
|
|
get _contentInputNode() {
|
|
return this._formNode.querySelector(
|
|
".post-content .dropper-container"
|
|
);
|
|
}
|
|
|
|
get _thumbnailInputNode() {
|
|
return this._formNode.querySelector(
|
|
".post-thumbnail .dropper-container"
|
|
);
|
|
}
|
|
|
|
get _thumbnailRemovalLinkNode() {
|
|
return this._formNode.querySelector(".post-thumbnail a");
|
|
}
|
|
|
|
get _sourceInputNode() {
|
|
return this._formNode.querySelector(".post-source textarea");
|
|
}
|
|
|
|
get _featureLinkNode() {
|
|
return this._formNode.querySelector(".management .feature");
|
|
}
|
|
|
|
get _mergeLinkNode() {
|
|
return this._formNode.querySelector(".management .merge");
|
|
}
|
|
|
|
get _deleteLinkNode() {
|
|
return this._formNode.querySelector(".management .delete");
|
|
}
|
|
|
|
get _addNoteLinkNode() {
|
|
return this._formNode.querySelector(".notes .add");
|
|
}
|
|
|
|
get _copyNotesLinkNode() {
|
|
return this._formNode.querySelector(".notes .copy");
|
|
}
|
|
|
|
get _pasteNotesLinkNode() {
|
|
return this._formNode.querySelector(".notes .paste");
|
|
}
|
|
|
|
get _deleteNoteLinkNode() {
|
|
return this._formNode.querySelector(".notes .delete");
|
|
}
|
|
|
|
get _noteTextareaNode() {
|
|
return this._formNode.querySelector(".notes textarea");
|
|
}
|
|
|
|
enableForm() {
|
|
views.enableForm(this._formNode);
|
|
}
|
|
|
|
disableForm() {
|
|
views.disableForm(this._formNode);
|
|
}
|
|
|
|
clearMessages() {
|
|
views.clearMessages(this._hostNode);
|
|
}
|
|
|
|
showSuccess(message) {
|
|
views.showSuccess(this._hostNode, message);
|
|
}
|
|
|
|
showError(message) {
|
|
views.showError(this._hostNode, message);
|
|
}
|
|
}
|
|
|
|
module.exports = PostEditSidebarControl;
|