--- /dev/null
+/* article-editor.js */
+
+var articleEditor = { _initialized: 0 };
+
+articleEditor.init = function init () {
+ if (this._initialized) { return; }
+
+ function data() {
+ var data = {
+ // only editor's property will be send when preview/post
+ editor: {
+ title: "",
+ intro_text: "",
+ body_text: "",
+ create_time: "",
+ author: "",
+ dept: "",
+ comment_status: "enabled",
+ submissioncopy: 0,
+ url: "",
+ email: "",
+ tags_string: "",
+ related_urls: "",
+ display: "1",
+ posttype: 1,
+ formatter: "modern",
+ id: "",
+ },
+
+ message: "",
+ createdUrl: "",
+ currentTopics: [],
+
+ enableAutoPreview: 1,
+ mode: "editing",
+
+ primaryTopicIconURL: "",
+ cancelable: 0,
+ };
+ return data;
+ }
+
+ var props = {
+ item: {
+ type: Object,
+ default: function () {return new Content();},
+ },
+ contentType: { type: String },
+ contentId: { type: String },
+ csrfToken: String,
+ };
+
+ /*
+ * watch
+ */
+ var watch = {
+ 'editor.intro_text': watchIntrotext,
+ 'editor.body_text': watchBodytext,
+ 'editor.title': watchTitle,
+ 'editor.url': watchURL,
+ 'editor.create_time': watchCreatetime,
+ 'editor.author': watchAuthor,
+ 'editor.dept': watchDept,
+ 'editor.tags_string': updateTopics,
+ };
+
+ function watchIntrotext(val, oldVal) {
+ this.item.intro_text = (val.length > 0) ? quoteHtml(val) : "";
+ };
+
+ function watchBodytext(val, oldVal) {
+ this.item.body_text = (val.length > 0) ? quoteHtml(val) : "";
+ };
+
+ function watchTitle(val, oldVal) {
+ this.item.title = (val.length > 0) ? quoteTitle(val) : "";
+ };
+
+ function watchURL(val, oldVal) {
+ this.item.url = (val.length > 0) ? val : "";
+ };
+
+ function watchCreatetime(val, oldVal) {
+ this.item.time_string = nsUtil.formatToLocalDateTime(new Date(val));
+ };
+
+ function watchAuthor(val, oldVal) {
+ this.item.author = val;
+ };
+
+ function watchDept(val, oldVal) {
+ this.item.dept = val;
+ };
+
+ /*
+ * computed
+ */
+ var computed = {
+ editable: editable,
+ url: url,
+ };
+
+ function editable() {
+ return (user.uid == this.item.uid);
+ }
+
+ function url() {
+ if (this.item.content_type == 'story') {
+ return "/story/" + this.item.sid + "/";
+ } else if (this.item.content_type == 'journal') {
+ return "/journal/" + this.item.id + "/";
+ } else if (this.item.content_type == 'submission') {
+ return "/submission/" + this.item.subid + "/";
+ }
+ return "";
+ }
+
+ /*
+ * methods and relative private functions
+ */
+ var methods = {
+ showPreview: showPreview,
+ postItem: postItem,
+ leavePreview: leavePreview,
+ startEdit: startEdit,
+ cancelEdit: cancelEdit,
+ updateTopics: updateTopics,
+ topicIconURL: topicIconURL,
+ updatePrimaryTopicIconURL: updatePrimaryTopicIconURL,
+ };
+
+ function updatePrimaryTopicIconURL() {
+ if (this.item.primary_topic) {
+ this.primaryTopicIconURL = nsUtil.topicIconURL(this.item.primary_topic.keyword);
+ return;
+ }
+ this.primaryTopicIconURL = "";
+ }
+
+ function showPreview(event) {
+ var postData = _preparePostData(this, "preview");
+
+ if (this.contentType == "journal") {
+ newslash.previewJournal(postData).then(
+ (resp) => { // success
+ this.message = "";
+ this.previewTitle = resp.item.title;
+ this.previewIntro = resp.item.intro_text;
+ this.item.url = this.editor.url;
+ this.item.primary_topic = resp.item.topic;
+ this.updatePrimaryTopicIconURL();
+
+ this.mode = "preview";
+ },
+ (resp) => { // fail
+ if (resp.message) {
+ this.message = resp.message;
+ }
+ }
+ );
+ return;
+ }
+
+ this.$newslash.post(type,
+ postData,
+ { csrfToken: this.csrfToken,
+ noCaptcha: 1 },
+ (response) => { // success
+ this.message = "";
+ this.previewTitle = response.body.item.title;
+ this.previewIntro = response.body.item.intro_text;
+ this.item.primary_topic = response.body.item.topic;
+ this.item.url = response.body.item.url;
+ this.item.dept = response.body.item.dept;
+ this.updatePrimaryTopicIconURL();
+
+ this.mode = "preview";
+ },
+ (response) => { // fail
+ if (response.body.message) {
+ this.message = response.body.message;
+ }
+ }
+ );
+ }
+
+ function postItem(event) {
+ this.mode = "posting";
+ var postData = _preparePostData(this, "post");
+
+ // journal post
+ if (this.contentType == "journal") {
+ postData.csrf_token = this.csrfToken;
+ newslash.postJournal(postData).then(
+ (resp) => { // success
+ this.message = "";
+ var id = resp.id;
+ var type = resp.type;
+ var url = '/' + type + '/' + id;
+
+ // check if new post
+ if (!postData.id) {
+ this.createdUrl = url;
+ this.item.subid = id;
+ }
+ this.message = "";
+ this.mode = "posted";
+ },
+ (resp) => { // fail
+ this.message = resp.message;
+ this.mode = "preview";
+ }
+ );
+ return;
+ }
+
+ // submission post
+ this.$newslash.post(type, postData, {csrfToken: this.csrfToken},
+ (response) => { // success
+ this.message = "";
+ var id = response.body.url_id || response.body.id;
+ var type = response.body.type;
+ var url = '/' + type + '/' + id;
+
+ // check if new post
+ if (!postData.id) {
+ this.createdUrl = url;
+ if (page.type == "submission") {
+ this.item.subid = id;
+ }
+ this.editor.id = response.body.id;
+ }
+ this.message = "";
+ this.mode = "posted";
+ },
+ (response) => { // fail
+ this.message = response.body.message;
+ this.mode = "preview";
+ }
+ );
+
+ }
+
+ function _preparePostData(vm, action) {
+ action = action || "preview";
+
+ vm.message = "";
+ var postData = {};
+ postData.item = {};
+ postData.action = action;
+
+ if (articleEditor.recaptchaToken) {
+ postData.recaptcha_token = articleEditor.recaptchaToken;
+ }
+
+ Object.keys(vm.editor).forEach(k => {
+ postData.item[k] = vm.editor[k];
+ });
+
+ // prepare ID
+ if (vm.editor.id) {
+ if (vm.contentType == "story") {
+ postData.item.stoid = vm.editor.id;
+ }
+ }
+
+ return postData;
+ }
+
+ function leavePreview(event) {
+ this.mode = "editing";
+ }
+
+ function quoteHtml(html) {
+ return nsUtil.quoteHtml(html, siteConfig.editorConfig.allowed_tags);
+ }
+
+ function quoteTitle(html) {
+ return nsUtil.quoteTitle(html, {});
+ }
+
+ function startEdit() {
+ _startEdit(this);
+ }
+
+ function cancelEdit() {
+ for (k in this.originalItem) {
+ this.item[k] = this.originalItem[k];
+ }
+ this.mode = "show_all";
+ }
+
+ function updateTopics() {
+ var tags = this.editor.tags_string.split(" ");
+ this.currentTopics.splice(0);
+ if (tags.length == 0) {
+ return;
+ }
+ var pushed = {};
+ tags.forEach((tag) => {
+ if (siteConfig.topics[tag]) {
+ var keyword = siteConfig.topics[tag].keyword;
+ if (!pushed[keyword]) {
+ this.currentTopics.push(keyword);
+ pushed[keyword] = 1;
+ }
+ }
+ });
+ }
+
+ function topicIconURL(topic) {
+ return nsUtil.topicIconURL(topic);
+ }
+
+ /*
+ * created and related private functions
+ */
+ function created() {
+ _startEdit(this);
+ }
+
+ function _startEdit(c) {
+ c.mode = "editing";
+ // backup original item
+ // copy item property to c (component)
+ c.originalItem = {};
+ for (k in c.$data.item) {
+ if (c.item[k]) {
+ c.originalItem[k] = c.item[k];
+ }
+ }
+
+ // load item data
+ _loadItem(c);
+
+ c.updatePrimaryTopicIconURL();
+
+ }
+
+ function _loadItem(c) {
+ if (c.contentType == "journal") {
+ if (c.contentId) {
+ // edit already existing journal
+ newslash.getJournal(c.contentId).then(
+ resp => { // succeed
+ var item = resp.journal;
+ c.cancelable = 1;
+ c.editor.title = item.title;
+ c.editor.intro_text = item.article;
+ c.editor.tags_string = item.tags_string || "";
+ c.editor.comment_status = item.comment_status;
+ c.editor.formatter = item.formatter;
+ c.editor.posttype = item.posttype;
+ c.editor.id = item.id;
+
+ // check formatter
+ if (c.editor.formatter == "legacy") {
+ c.enableAutoPreview = 0;
+ }
+
+ c.updatePrimaryTopicIconURL();
+
+ // start editing
+ c.mode = "editing";
+ },
+ resp => { // failed
+ c.message = resp.body.message;
+ });
+ } else {
+ // create new journal
+ // start editing
+ c.mode = "editing";
+ }
+ }
+ }
+
+ function _initEditor(c) {
+
+ // set createtime
+ if (c.item.createtime) {
+ var dt = nsUtil.decodeMySQLDateTime(c.item.createtime);
+ c.editor.createtime = nsUtil.formatToLocalISOString(dt);
+ } else {
+ c.editor.createtime = nsUtil.formatToLocalISOString(new Date());
+ }
+ c.editor.author = user.nickname;
+
+ if (c.item.tags) {
+ var tagnames = c.item.tags.map(x => {return x.tagname});
+ c.editor.tags_string = tagnames.join(" ");
+ }
+ updateTopics.call(c);
+
+ // journal edit mode
+ if (page.content_type == 'journal') {
+ c.item.content_type = "story";
+ if (page.id) {
+ // edit mode
+ newslash.getJournal(page.id).then(
+ resp => { // succeed
+ var item = resp.journal;
+ c.cancelable = 1;
+ c.editor.title = item.title;
+ c.editor.intro_text = item.article;
+ c.editor.tags_string = item.tags_string || "";
+ c.editor.comment_status = item.comment_status;
+ c.editor.formatter = item.formatter;
+ c.editor.posttype = item.posttype;
+ c.editor.id = item.id;
+ },
+ resp => { // failed
+ c.message = resp.body.message;
+ });
+ } else {
+ // create
+ }
+ }
+
+ // story edit mode
+ if (page.content_type == 'story' && page.stoid) {
+ c.cancelable = 1;
+ c.item.content_type = "story";
+
+ c.$newslash.getStoryByID(page.stoid,
+ (resp) => {
+ // succeed
+ var item = resp.body.item;
+ for (var k in c.editor) {
+ if (item[k] !== undefined) {
+ c.editor[k] = item[k];
+ }
+ }
+ // convert tags to tags_string
+ if (item.tags) {
+ var tagnames = item.tags.map(tag => tag.tagname);
+ c.editor.tags_string = tagnames.join(" ");
+ }
+ // TODO: related stories
+ // convert public to display
+ c.editor.display = (item.public == "yes");
+ c.editor.createtime = nsUtil.formatToLocalISOString(nsUtil.decodeMySQLDateTime(item.create_time));
+ c.editor.id = item.stoid;
+ },
+ (resp) => { //failed
+ c.message = resp.body.message;
+ });
+ }
+
+ // submission to story mode
+ if (c.item.submission_id) {
+ c.$newslash.getSubmissionByID(c.item.submission_id,
+ (resp) => { // succeed
+ c.editor.title = resp.body.item.title;
+ c.editor.intro_text = resp.body.item.drafttext;
+ c.item.content_type = "story";
+ },
+ (resp) => { //failed
+ c.message = resp.body.message;
+ });
+ }
+
+ // check formatter
+ if (c.editor.formatter == "legacy") {
+ c.enableAutoPreview = 0;
+ }
+ c.mode = "editing";
+ }
+
+ /*
+ * register component
+ */
+ this._component = Vue.component('article-editor', {
+ template: '#article-editor-template',
+ props: props,
+ data: data,
+ computed: computed,
+ watch: watch,
+ methods: methods,
+ created: created,
+ });
+
+ this._initialized = 1;
+};
+
+/*
+ * utilities
+ */
+articleEditor.instances = [];
+
+articleEditor.ArticleEditor = class ArticleEditor {
+ constructor(targetElement) {
+ if (!targetElement) {
+ console.error('error in articleEditor.run(): no element given');
+ return;
+ }
+ this.vm = new Vue({el: targetElement});
+ }
+};
+
+articleEditor.addTrigger = function addTrigger (selector, targetElement) {
+ if (!targetElement) {
+ console.error('error in articleEditor.addTrigger(): no element given');
+ return;
+ }
+ if (!selector) {
+ console.error('error in articleEditor.addTrigger(): no selector given');
+ return;
+ }
+
+ var els = document.querySelectorAll(selector);
+ for (var i = 0; i < els.length; i++) {
+ els[i].addEventListener('click', function (e) {
+ articleEditor.init();
+ articleEditor.instances.push(new articleEditor.ArticleEditor(targetElement));
+ });
+ }
+};
+
+
-%]
<article id="[% item.id %]" type="[% item.content_type %]" item-id="[% item.content_id %]"
- [% IF !x_template %]v-if="0"[% ELSE %]v-if="mode != 'editing' || enableAutoPreview"[% END %]>
+ [% IF template_mode %]v-if="enableAutoPreview || mode != 'editing'"[% ELSE %]v-if=0[% END %]>
<header>
<h1>
- [%- IF item.primary_topic.image.length || x_template -%]
+ [%- IF item.primary_topic.image.length || template_mode -%]
<img [% IF item.primary_topic.image %]src="[% Site.topic_icon_base_url %]/[% item.primary_topic.image %]"[% END %]
alt="[% item.primary_topic.textname %]" :src="primaryTopicIconURL" v-if="primaryTopicIconURL" />
[%- END -%]
by <span v-text="item.author"><a href="/~[% item.author %]/">[% item.author %]</a></span>
</span>
<span class="create-time" v-text="item.create_time">[% dtf.mysql_to_user(item.create_time, user.config.ui.offset_sec, user.config.ui.time_format) %]</span>
- [%- IF x_template || user.is_admin %]
+ [%- IF user.is_admin %]
<span class="score">
pop: [% item.popularity %]
epop: [% item.editorpop %]
</span>
[%- END %]
- [%- IF x_template || item.content_type == 'story' %]
+ [%- IF template_mode || item.content_type == 'story' %]
<span class="dept" v-if="item.content_type == 'story'">
<span class="dept-name" v-text="item.dept" v-if="item.dept">[% item.dept %]</span> 部門より
</span>
[%- END %]
</div>
- <div class="toolbar" v-if="mode == ''">
- [%- IF x_template || page.type == 'single' && item.content_type == 'journal' && item.uid == user.uid -%]
- <button type="button" class="btn btn-default btn-sm" id="activate-journal-editor"
- title="編集" area-label="編集" v-show="editable" v-on:click="startEdit(item)">
+ <div class="toolbar">
+ [%- IF page.type == 'single' && item.content_type == 'journal' && item.uid == user.uid -%]
+ <button type="button" class="btn btn-default btn-sm"
+ title="編集" aria-label="編集"
+ [%- IF template_mode %]
+ v-if="mode != 'editing'" @click="startEdit()"
+ [%- ELSE %]
+ id="activate-journal-editor"
+ [%- END %]
+ >
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
[%- END -%]
- [%- IF x_template || item.content_type == 'story' && user.is_admin || user.editor -%]
+ [%- IF item.content_type == 'story' && user.is_admin || user.editor -%]
<a href="/admin/story/edit?stoid=[% item. stoid %]">[編集]</a>
[%- END -%]
- [%- IF item.content_type == 'submission' && (user.author || x_template) -%]
+ [%- IF item.content_type == 'submission' && user.author -%]
[<a href="/admin/story/edit?subid=[% item.submission_id %]">[accept]</a>]
[%- END -%]
</div>
[%- IF hide_bodytext %]
- [%- IF item.intro_text || x_template -%]
+ [%- IF item.intro_text || template_mode -%]
<div class="body contents-text" v-html="item.intro_text">[% item.intro_text %]</div>
[%- END -%]
[%- ELSE %]
- [%- IF item.body_text || x_template -%]
+ [%- IF item.body_text || template_mode -%]
<div class="body contents-text" v-html="item.intro_text">[% item.intro_text %]</div>
<div class="body contents-text" v-html="item.body_text">[% item.body_text %]</div>
[%- ELSE %]
<div class="body contents-text" v-html="item.full_text">[% item.full_text %]</div>
[%- END -%]
- [%- IF item.media || x_template %]
+ [%- IF item.media || template_mode %]
<div class="body contents-text" v-html="item.media">[% item.media %]</div>
[%- END %]
[%- END %]
- [%- IF x_template -%]
- <div class="body contents-text" v-if="item.url"><p><a :href="item.url">情報元へのリンク</a></p></div>
- [%- ELSIF item.url -%]
- <div class="body contents-text"><p><a href="[% item.url %]">情報元へのリンク</a></p></div>
+ [%- IF item.url || template_mode -%]
+ <div class="body contents-text" v-if="item.url">
+ <p><a href="[% item.url %]" :href="item.url">情報元へのリンク</a></p>
+ </div>
[%- END -%]
<footer>
- [%- IF item.content_type == "feed" -%]
- [%- # show nothing -%]
- [%- ELSIF !hide_more_link-%]
- [%- IF item.body_text && item.body_text.length > 0; next_text = "の続き"; ELSE; next_text = ""; END; -%]
- <div class="link-to-story" v-if="mode == ''"><a href="[% url %]" :href="url">
+ [%- IF item.content_type != "feed" -%]
+ [%- IF !hide_more_link || template_mode-%]
+ [%- IF item.body_text && item.body_text.length > 0; next_text = "の続き"; ELSE; next_text = ""; END; -%]
+ <div class="link-to-story" v-if="mode == 'show_introtext'"><a href="[% url %]" :href="url">
[%- IF item.content_type == "poll" -%]
- [%- IF item.comment_count || x_template -%]
+ [%- IF item.comment_count || template_mode -%]
<span v-if='item.comment_count > 0'><span v-text="item.comment_count">[% item.comment_count %]</span>件のコメントを見る</span>
[%- END -%]
- [%- IF !item.comment_count || x_template -%]<span v-if="item.comment_count == 0">投票結果を見る</span>[%- END -%]
+ [%- IF !item.comment_count || template_mode -%]<span v-if="item.comment_count == 0">投票結果を見る</span>[%- END -%]
[%- ELSE -%]
- [%- IF item.comment_count || x_template -%]
+ [%- IF item.comment_count || template_mode -%]
<span v-if="item.comment_count > 0">[% content_type_text %][% next_text %]と<span v-text="item.comment_count">[% item.comment_count %]</span>件のコメントを読む</span>
[%- END -%]
- [%- IF !item.comment_count || x_template -%]
+ [%- IF !item.comment_count || template_mode -%]
<span v-if="item.comment_count == 0">[% content_type_text %][% next_text %]を読む</span>
[%- END -%]
[%- END -%]
</a></div>
- [%- ELSE -%]
- <div class="comment-counter">
+ [% END %]
+
+ [%- IF hide_more_link || template_mode -%]
+ <div class="comment-counter" v-if="mode == 'show_all'">
[%- IF item.discussion_id -%]
- <a href="#comments"><span class="counter" v-text="item.comment_count">[% item.comment_count %]</span>コメント</a>
+ <a href="#comments"><span class="counter" v-text="item.comment_count || 0">[% item.comment_count %]</span>コメント</a>
[%- END -%]
</div>
+ [%- END -%]
[%- END -%]
<div class="tag-bar">