OSDN Git Service

change story related api endpoint to /api/v1/admin/story
[newslash/newslash.git] / src / newslash_web / public / js / article-editor.js
1 /* article-editor.js */
2
3 var articleEditor = {
4   _initialized: 0,
5   instances: [],
6 };
7
8 articleEditor.init = function init () {
9   if (this._initialized) { return; }
10   
11   function data() {
12     var data = {
13       // only properties of editor will be send when preview/post 
14       editor: {
15         title: "",
16         intro_text: "",
17         body_text: "",
18         create_time: "",
19         author: "",
20         dept: "",
21         comment_status: "enabled",
22         submissioncopy: 0,
23         url: "",
24         email: "",
25         tags_string: "",
26         related_urls: "",
27         display: "1",
28         posttype: 1,
29         formatter: "modern",
30         id: "",
31       },
32
33       message: "",
34       createdUrl: "",
35       currentTopics: [],
36       
37       enableAutoPreview: 1,
38       mode: "editing",
39
40       primaryTopicIconURL: "",
41       isUpdate: 0,
42     };
43     return data;
44   }
45
46   var props = {
47     item: {
48       type: Object,
49       default: function () {return new Content();},
50     },
51     contentType: { type: String, required: true },
52     contentId: { type: Number, dafault: 0 },
53     sourceType: { type: String },
54     sourceId: { type: Number, default: 0 },
55     csrfToken: { type: String },
56   };
57
58   /*
59    * watch
60    */
61   var watch = {
62     'editor.intro_text': function watchIntrotext(val, oldVal) {
63       this.item.intro_text = (val.length > 0) ? _quoteHtml(val) : "";
64     },
65     'editor.body_text': function watchBodytext(val, oldVal) {
66       this.item.body_text = (val.length > 0) ? _quoteHtml(val) : "";
67     },
68     'editor.title': function watchTitle(val, oldVal) {
69       this.item.title = (val.length > 0) ? _quoteTitle(val) : "";
70     },
71     'editor.url': function watchURL(val, oldVal) {
72       this.item.url = (val.length > 0) ? val : "";
73     },
74     'editor.create_time': function watchCreatetime(val, oldVal) {
75       this.item.time_string = newslash.util.formatToLocalDateTime(new Date(val));
76     },
77     'editor.author': function watchAuthor(val, oldVal) {
78       this.item.author = val;
79     },
80     'editor.dept': function watchDept(val, oldVal) {
81       this.item.dept = val;
82     },
83     'editor.tags_string': _updateTopics,
84   };
85
86   function _quoteHtml(html) {
87     return newslash.util.quoteHtml(html, siteConfig.editorConfig.allowed_tags);
88   }
89
90   function _quoteTitle(html) {
91     return newslash.util.quoteTitle(html, {});
92   }
93
94   /*
95    * computed
96    */
97   var computed = {};
98
99
100   /*
101    * methods and relative private functions
102    */
103   var methods = {
104     showPreview: _showPreview,
105     postItem: _postItem,
106     leavePreview: function leavePreview(event) {
107       this.mode = "editing";
108       this.item.intro_text = _quoteHtml(this.editor.intro_text);
109       this.item.body_text = _quoteHtml(this.editor.body_text);
110       this.item.title = _quoteTitle(this.editor.title);
111     },
112     startEdit: function startEdit() {
113       _startEdit.call(this);
114     },
115     cancelEdit: function cancelEdit() {
116       for (k in this.originalItem) {
117         this.item[k] = this.originalItem[k];
118       }
119       this.mode = "show_all";
120     },
121     updateTopics: _updateTopics,
122     topicIconURL: function topicIconURL(topic) {
123       return newslash.util.topicIconURL(topic);
124     },
125     updatePrimaryTopicIconURL: function updatePrimaryTopicIconURL() {
126       if (this.item.primary_topic) {
127         this.primaryTopicIconURL = newslash.util.topicIconURL(this.item.primary_topic.keyword);
128         return;
129       }
130       this.primaryTopicIconURL = "";
131     },
132   };
133
134
135   function _showPreview(event) {
136     var postData = _preparePostData.call(this, "preview");
137
138     if (this.contentType == "journal") {
139       newslash.previewJournal(postData).then(
140         (resp) => { // success
141           this.message = "";
142           this.item.title = resp.item.title;
143           this.item.intro_text = resp.item.intro_text;
144           this.item.url = this.editor.url;
145           this.item.primary_topic = resp.item.topic;
146           this.updatePrimaryTopicIconURL();
147
148           this.mode = "preview";
149         },
150         (resp) => { // fail
151           if (resp.message) {
152             this.message = resp.message;
153           }
154         }
155       );
156       return;
157     }
158
159     if (this.contentType == "story") {
160       newslash.previewStory(postData).then(
161         (resp) => { // success
162           this.message = "";
163           this.previewTitle = resp.item.title;
164           this.previewIntro = resp.item.intro_text;
165           this.item.url = this.editor.url;
166           this.item.primary_topic = resp.item.topic;
167           this.updatePrimaryTopicIconURL();
168
169           this.mode = "preview";
170         },
171         (resp) => { // fail
172           if (resp.message) {
173             this.message = resp.message;
174           }
175         }
176       );
177       return;
178     }
179
180     if (this.contentType == "submission") {
181       newslash.previewSubmission(postData).then(
182         (resp) => { // success
183           this.message = "";
184           this.item.title = resp.item.title;
185           this.item.intro_text = resp.item.intro_text;
186           this.item.url = this.editor.url;
187           this.item.primary_topic = resp.item.topic;
188           this.updatePrimaryTopicIconURL();
189
190           this.mode = "preview";
191         },
192         (resp) => { // fail
193           if (resp.message) {
194             this.message = resp.message;
195           }
196         }
197       );
198       return;
199     }
200
201     console.log("no contentType defined!");
202   }
203
204   function _postItem(event) {
205     this.mode = "posting";
206     var postData = _preparePostData.call(this, "post");
207
208     // journal post
209     if (this.contentType == "journal") {
210       postData.csrf_token = this.csrfToken;
211       newslash.postJournal(postData).then(
212         (resp) => { // success
213           this.message = "";
214           var id = resp.id;
215           var type = resp.type;
216           var url = '/' + type + '/' + id;
217           
218           // check if new post
219           if (!postData.id) {
220             this.createdUrl = url;
221             this.item.subid = id;
222           }
223           this.message = "";
224           this.mode = "posted";
225         },
226         (resp) => { // fail
227           this.message = resp.message;
228           this.mode = "preview";
229         }
230       );
231       return;
232     }
233
234     // submission post
235     if (this.contentType == "submission") {
236       postData.csrf_token = this.csrfToken;
237       newslash.postSubmission(postData).then(
238         (resp) => { // success
239           this.message = "";
240           var id = resp.id;
241           var type = resp.type;
242           var url = '/' + type + '/' + id;
243           
244           // check if new post
245           if (!postData.id) {
246             this.createdUrl = url;
247             this.item.subid = id;
248           }
249           this.message = "";
250           this.mode = "posted";
251         },
252         (resp) => { // fail
253           this.message = resp.message;
254           this.mode = "preview";
255         }
256       );
257       return;
258     }
259
260     // story post
261     if (this.contentType == "story") {
262       postData.csrf_token = this.csrfToken;
263       newslash.postStory(postData).then(
264         (resp) => { // success
265           this.message = "";
266           var id = resp.id;
267           var type = resp.type;
268           var url = '/' + type + '/' + id;
269           
270           // check if new post
271           if (!postData.id) {
272             this.createdUrl = url;
273             this.item.subid = id;
274           }
275           this.message = "";
276           this.mode = "posted";
277         },
278         (resp) => { // fail
279           this.message = resp.message;
280           this.mode = "preview";
281         }
282       );
283       return;
284     }
285     
286   }
287
288   function _preparePostData(action) {
289     action = action || "preview";
290
291     var postData = {};
292     postData.item = {};
293     postData.action = action;
294     
295     if (articleEditor.recaptchaToken) {
296       postData.recaptcha_token = articleEditor.recaptchaToken;
297     }
298
299     Object.keys(this.editor).forEach(k => {
300       postData.item[k] = this.editor[k];
301     });
302
303     // prepare sourceId
304     if (this.contentType == "story") {
305       if (this.sourceType == "submission") {
306         postData.item.submission_id = "this.sourceId";
307       } else if (this.sourceType == "journal") {
308         postData.item.journal_id = "this.sourceId";
309       }
310     }
311
312     return postData;
313   }
314
315   function _updateTopics() {
316     var tags = this.editor.tags_string.split(" ");
317     this.currentTopics.splice(0);
318     if (tags.length == 0) {
319       return;
320     }
321     var pushed = {};
322     tags.forEach((tag) => {
323       if (siteConfig.topics[tag]) {
324         var keyword = siteConfig.topics[tag].keyword;
325         if (!pushed[keyword]) {
326           this.currentTopics.push(keyword);
327           pushed[keyword] = 1;
328         }
329       }
330     });
331   }
332
333   /*
334    * created and related private functions
335    */
336   function created() {
337     _startEdit.call(this);
338   }
339
340   function _startEdit() {
341     this.mode = "editing";
342     // backup original item
343     // copy item property to c (component)
344     this.originalItem = {};
345     for (k in this.$data.item) {
346       if (this.item[k]) {
347         this.originalItem[k] = this.item[k];
348       }
349     }
350
351     // load item data
352     if (this.contentType == "journal") {
353       if (this.contentId) {
354         _loadJournal.call(this);
355       } else {
356         // create new journal
357         this.mode = "editing";
358       }
359       return;
360     }
361
362     if (this.contentType == "story") {
363       if (this.contentId) {
364         _loadStory.call(this);
365       } else {
366         if (this.sourceType == "submission") {
367           _loadStoryFromSubmission.call(this);
368         } else if (this.sourceType == "journal") {
369           _loadStoryFromJournal.call(this);
370         } else {
371           // create new story
372           _createNewStory.call(this);
373         }
374       }
375       return;
376     }
377    
378     if (this.contentType == "submission") {
379       // create new submission
380       this.mode = "editing";
381       return;
382     }
383
384   }
385
386   function _loadJournal() {
387     newslash.getJournal(this.contentId).then(
388       resp => { // succeed
389         var item = resp.journal;
390         this.isUpdate = 1;
391         this.editor.title = item.title;
392         this.editor.intro_text = item.article;
393         this.editor.tags_string = item.tags_string || "";
394         this.editor.comment_status = item.comment_status;
395         this.editor.formatter = item.formatter;
396         this.editor.posttype = item.posttype;
397         this.editor.id = item.id;
398           
399         // check formatter
400         if (this.editor.formatter == "legacy") {
401           this.enableAutoPreview = 0;
402         }
403           
404         // start editing
405         this.updatePrimaryTopicIconURL();
406         this.mode = "editing";
407       },
408       resp => { // failed
409         this.message = resp.body.message;
410       }
411     );
412   }
413
414   function _createNewStory() {
415     // set createtime
416     this.editor.createtime = newslash.util.formatToLocalISOString(new Date());
417     this.editor.author = user.nickname;
418
419     // start editing
420     this.mode = "editing";
421     return;
422   }
423
424   function _prepareStory(item) {
425     for (var k in this.editor) {
426       if (item[k] !== undefined) {
427         this.editor[k] = item[k];
428       }
429     }
430     // convert tags to tags_string
431     if (item.tags) {
432       var tagnames = item.tags.map(tag => tag.tagname);
433       this.editor.tags_string = tagnames.join(" ");
434       this.updateTopics();
435     }
436
437     // TODO: related stories
438     // convert public to display
439     this.editor.display = (item.public == "yes");
440     if (item.create_time) {
441       this.editor.createtime = newslash.util.formatToLocalISOString(newslash.util.decodeMySQLDateTime(item.create_time));
442     } else {
443       this.editor.createtime = newslash.util.formatToLocalISOString(new Date());
444     }
445     this.editor.id = item.stoid;
446     this.editor.author = user.nickname;
447   }
448
449   function _loadStory() {
450     newslash.admin.getStory(this.contentId).then(
451       resp => {
452         _prepareStory.call(this, resp.item);
453       },
454       fail => {
455         this.message = fail.body.message;
456         console.log("story fetch failed");
457       }
458     );
459   }
460
461   function _loadStoryFromSubmission() {
462     newslash.admin.getStoryFromSubmission(this.sourceId).then(
463       resp => {
464         _prepareStory.call(this, resp.item);
465       },
466       fail => {
467         this.message = fail.body.message;
468         console.log("story fetch failed");
469       }
470     );
471   }
472
473   function _loadStoryFromJournal() {
474     newslash.admin.getStoryFromJournal(this.sourceId).then(
475       resp => {
476         _prepareStory.call(this, resp.item);
477       },
478       fail => {
479         this.message = fail.body.message;
480         console.log("story fetch failed");
481       }
482     );
483   }
484
485
486   /*
487    * register component
488    */
489   this._component = Vue.component('article-editor', {
490     template: '#article-editor-template',
491     props: props,
492     data: data,
493     computed: computed,
494     watch: watch,
495     methods: methods,
496     created: created,
497   });
498
499   this._initialized = 1;
500 };
501
502 /*
503  * utilities
504  */
505   
506 articleEditor.ArticleEditor = class ArticleEditor {
507   constructor(targetElement) {
508     if (!targetElement) {
509       console.error('error in ArticleEditor(): no element given');
510       return;
511     }
512     this.vm = new Vue({el: targetElement});
513   }
514 };
515
516 articleEditor.addTrigger = function addTrigger (selector, targetElement) {
517   if (!targetElement) {
518     console.error('error in articleEditor.addTrigger(): no element given');
519     return;
520   }
521   if (!selector) {
522     console.error('error in articleEditor.addTrigger(): no selector given');
523     return;
524   }
525
526   var els = document.querySelectorAll(selector);
527   for (var i = 0; i < els.length; i++) {
528     els[i].addEventListener('click', function (e) {
529       articleEditor.init();
530       articleEditor.instances.push(new articleEditor.ArticleEditor(targetElement));
531     });
532   }
533 };
534
535