Просмотр исходного кода

Merge pull request #838 from weseek/feat/enable-to-tag-page

Feat/enable to tag page
Yuki Takei 7 лет назад
Родитель
Сommit
26b94b70f3

+ 14 - 0
src/client/js/app.js

@@ -26,6 +26,7 @@ import CommentForm from './components/PageComment/CommentForm';
 import PageAttachment   from './components/PageAttachment';
 import PageStatusAlert  from './components/PageStatusAlert';
 import RevisionPath     from './components/Page/RevisionPath';
+import PageTagForm      from './components/PageTagForm';
 import RevisionUrl      from './components/Page/RevisionUrl';
 import BookmarkButton   from './components/BookmarkButton';
 import LikeButton       from './components/LikeButton';
@@ -64,6 +65,8 @@ let pagePath;
 let pageContent = '';
 let markdown = '';
 let slackChannels;
+let currentPageTags = '';
+let newPageTags = '';
 if (mainContent !== null) {
   pageId = mainContent.getAttribute('data-page-id') || null;
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
@@ -109,6 +112,14 @@ if (isEnabledPlugins) {
   crowiPlugin.installAll(crowi, crowiRenderer);
 }
 
+/**
+ * get new tags from page tag form
+ * @param {String} tags new tags [TODO] String -> Array
+ */
+const getNewPageTags = function(tags) {
+  newPageTags = tags;
+};
+
 /**
  * component store
  */
@@ -187,6 +198,7 @@ const saveWithShortcut = function(markdown) {
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
+  options.pageTags = newPageTags;
 
   if (editorMode === 'hackmd') {
     // set option to sync
@@ -224,6 +236,7 @@ const saveWithSubmitButton = function(submitOpts) {
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
+  options.pageTags = newPageTags;
 
   // set 'submitOpts.overwriteScopesOfDescendants' to options
   options.overwriteScopesOfDescendants = submitOpts ? !!submitOpts.overwriteScopesOfDescendants : false;
@@ -294,6 +307,7 @@ if (pageId) {
 if (pagePath) {
   componentMappings['page'] = <Page crowi={crowi} crowiRenderer={crowiRenderer} markdown={markdown} pagePath={pagePath} showHeadEditButton={true} onSaveWithShortcut={saveWithShortcut} />;
   componentMappings['revision-path'] = <RevisionPath pagePath={pagePath} crowi={crowi} />;
+  // componentMappings['page-tag'] = <PageTagForm pageTags={currentPageTags} submitTags={getNewPageTags} />; [pagetag]
   componentMappings['revision-url'] = <RevisionUrl pageId={pageId} pagePath={pagePath} />;
 }
 

+ 63 - 0
src/client/js/components/PageTagForm.jsx

@@ -0,0 +1,63 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+/**
+ *
+ * @author Yuki Takei <yuki@weseek.co.jp>
+ *
+ * @export
+ * @class PageTagForm
+ * @extends {React.Component}
+ */
+
+export default class PageTagForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      pageTags: this.props.pageTags,
+    };
+
+    this.updateState = this.updateState.bind(this);
+    this.handleSubmit = this.handleSubmit.bind(this);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.setState({
+      pageTags: nextProps.pageTags
+    });
+  }
+
+  handleSubmit() {
+    this.props.submitTags(this.state.pageTags);
+  }
+
+  updateState(value) {
+    this.setState({pageTags: value});
+  }
+
+  render() {
+    return (
+      <div className="input-group-sm mx-1">
+        <input className="form-control page-tag-form" type="text" value={this.state.pageTags} placeholder="tag name"
+          data-toggle="popover"
+          title="タグ"
+          data-content="タグ付けによりページをカテゴライズすることができます。"
+          data-trigger="focus"
+          data-placement="right"
+          onChange={e => this.updateState(e.target.value)}
+          onBlur={this.handleSubmit}
+          />
+      </div>
+    );
+  }
+}
+
+PageTagForm.propTypes = {
+  pageTags: PropTypes.string,
+  submitTags: PropTypes.func,
+};
+
+PageTagForm.defaultProps = {
+};

+ 19 - 9
src/client/styles/scss/_on-edit.scss

@@ -100,16 +100,26 @@ body.on-edit {
     .header-wrap {
       overflow-x: hidden;
     }
-    h1#revision-path {
-      @include variable-font-size(20px);
-      line-height: 1em;
-
-      // nowrap even if the path is too long
-      .d-flex {
-        flex-wrap: nowrap;
+    div.title-container {
+      margin-right: 0px;
+      h1#revision-path {
+        @include variable-font-size(20px);
+        line-height: 1em;
+
+        // nowrap even if the path is too long
+        .d-flex {
+          flex-wrap: nowrap;
+        }
+        .path-segment {
+          white-space: nowrap;
+        }
       }
-      .path-segment {
-        white-space: nowrap;
+    }
+    div#page-tag {
+      margin-right: auto;
+      display: inline;
+      .page-tag-form {
+        border-radius: 5px;
       }
     }
 

+ 5 - 0
src/client/styles/scss/_page.scss

@@ -13,6 +13,11 @@
       margin-right: auto;
     }
 
+    // hide unnecessary element
+    div#page-tag {
+      display: none;
+    }
+
     .btn-copy, .btn-copy-link, .btn-edit {
       @extend .text-muted;
       opacity: 0.3;

+ 2 - 0
src/server/models/index.js

@@ -3,11 +3,13 @@
 module.exports = {
   Page: require('./page'),
   PageGroupRelation: require('./page-group-relation'),
+  PageTagRelation: require('./page-tag-relation'),
   User: require('./user'),
   ExternalAccount: require('./external-account'),
   UserGroup: require('./user-group'),
   UserGroupRelation: require('./user-group-relation'),
   Revision: require('./revision'),
+  Tag: require('./tag'),
   Bookmark: require('./bookmark'),
   Comment: require('./comment'),
   Attachment: require('./attachment'),

+ 35 - 0
src/server/models/page-tag-relation.js

@@ -0,0 +1,35 @@
+const mongoose = require('mongoose');
+const mongoosePaginate = require('mongoose-paginate');
+const ObjectId = mongoose.Schema.Types.ObjectId;
+
+
+/*
+ * define schema
+ */
+const schema = new mongoose.Schema({
+  relatedPage: {
+    type: ObjectId,
+    ref: 'Page',
+    required: true
+  },
+  relatedTag: {
+    type: ObjectId,
+    ref: 'Tag',
+    required: true
+  },
+});
+schema.plugin(mongoosePaginate);
+
+/**
+ * PageTagRelation Class
+ *
+ * @class PageTagRelation
+ */
+class PageTagRelation {
+}
+
+module.exports = function() {
+  schema.loadClass(PageTagRelation);
+  const model = mongoose.model('PageTagRelation', schema);
+  return model;
+};

+ 68 - 0
src/server/models/page.js

@@ -292,6 +292,74 @@ module.exports = function(crowi) {
     return false;
   };
 
+  pageSchema.methods.updateTags = async function(newTagsName) {
+    // const page = this;
+    // const PageTagRelation = mongoose.model('PageTagRelation');
+    // const Tag = mongoose.model('Tag');
+
+    // const newTagNameList = [newTagsName]; // [TODO] listing requested Tags on client side
+
+    // // get tags relate this page
+    // const relations = await PageTagRelation.find({relatedPage: page._id}).populate('relatedTag').select('-_id relatedTag');
+    // console.log(relations);
+    // // relations.populate(...)
+    // // const relatedTagNameList = await Promise.all(relations.map(async function(relation) {
+    // //   const relatedTag =  await Tag.findOne({_id: relation.relatedTag});
+    // //   return relatedTag.name;
+    // // }));
+
+    // const relatedTags = ...;
+
+    // // creat unlinked tag list
+    // // TODO use filter and includes
+    // const unlinkedTagNameList = [];
+    // relatedTagNameList.map(function(relatedTagName) {
+    //   if (!newTagNameList.includes(relatedTagName)) {
+    //     return relatedTagName;
+    //   }
+    // });
+
+    // relatedTagTags.forEach(tag => {
+    //   // await つかえないかも
+    //   await tag.remove();
+    // })
+    // // unlinked page-tag-relations
+    // // unlinkedTagNameList.map(function(tagName) {
+    // //   Tag.findOne({name: tagName}, function(err, tag) {
+    // //     PageTagRelation.remove({relatedPage: page._id, relatedTag: tag._id}, function(err, relation) {
+    // //       if (err) {
+    // //         throw new Error(err);
+    // //       }
+    // //       debug('remove tag relation: ', tag.name);
+    // //     });
+    // //   });
+    // // });
+
+    // // creat set tag list
+    // const setTagNameList = [];
+    // newTagNameList.map(function(newTagName) {
+    //   if (!relatedTagNameList.includes(newTagName)) {
+    //     setTagNameList.push(newTagName);
+    //   }
+    // });
+    // // set tags
+    // setTagNameList.map((tagName) => {
+    //   Tag.findOne({name: tagName}, async function(err, tag) {
+    //     if (tag == null) {
+    //       tag = await Tag.create({name: tagName});
+    //     }
+    //     // make a relation
+    //     PageTagRelation.create({relatedPage: page._id, relatedTag: tag._id}, function(err, relation) {
+    //       if (err) {
+    //         throw new Error(err);
+    //       }
+    //       debug('tag linked this page: ', tag.name);
+    //     });
+    //   });
+    // });
+  };
+
+
   pageSchema.methods.isPortal = function() {
     return isPortalPath(this.path);
   };

+ 32 - 0
src/server/models/tag.js

@@ -0,0 +1,32 @@
+const mongoose = require('mongoose');
+const mongoosePaginate = require('mongoose-paginate');
+
+/*
+ * define schema
+ */
+const schema = new mongoose.Schema({
+  name: {
+    type: String,
+    required: true
+  },
+});
+schema.plugin(mongoosePaginate);
+
+/**
+ * Tag Class
+ *
+ * @class Tag
+ */
+class Tag {
+
+  async findOrCreate(tagName) {
+
+  }
+
+}
+
+module.exports = function() {
+  schema.loadClass(Tag);
+  const model = mongoose.model('Tag', schema);
+  return model;
+};

+ 4 - 0
src/server/routes/page.js

@@ -598,6 +598,7 @@ module.exports = function(crowi, app) {
     const overwriteScopesOfDescendants = req.body.overwriteScopesOfDescendants || null;
     const isSlackEnabled = !!req.body.isSlackEnabled;                     // cast to boolean
     const slackChannels = req.body.slackChannels || null;
+    const pageTags = req.body.pageTags || null;
     const isSyncRevisionToHackmd = !!req.body.isSyncRevisionToHackmd;     // cast to boolean
     const socketClientId = req.body.socketClientId || undefined;
 
@@ -656,6 +657,9 @@ module.exports = function(crowi, app) {
     if (isSlackEnabled && slackChannels != null) {
       await notifyToSlackByUser(page, req.user, slackChannels, 'update', previousRevision);
     }
+
+    // // update page tag
+    // await page.updateTags(pageTags); [pagetag]
   };
 
   /**

+ 1 - 0
src/server/views/layout-growi/widget/header.html

@@ -10,6 +10,7 @@
         <h1 class="title" id="revision-path"></h1>
         <div id="revision-url" class="url-line"></div>
       </div>
+      <!-- <div class="tag" id="page-tag"></div> [pagetag]-->
       {% if page %}
       {% include '../../widget/header-buttons.html' %}