Browse Source

Editable list page (portal)

Sotaro KARASAWA 10 years ago
parent
commit
65da0513f6

+ 1 - 0
lib/form/revision.js

@@ -4,6 +4,7 @@ var form = require('express-form')
   , field = form.field;
 
 module.exports = form(
+  field('pageForm.path').required(),
   field('pageForm.body').required().custom(function(value) { return value.replace(/\r/g, '\n'); }),
   field('pageForm.currentRevision'),
   field('pageForm.grant').toInt().required()

+ 0 - 1
lib/models/page.js

@@ -234,7 +234,6 @@ module.exports = function(crowi) {
       /^\/\-\/.*/,
       /^\/_r\/.*/,
       /.+\/edit$/,
-      /\/$/,
       /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments).+/,
     ];
 

+ 1 - 1
lib/routes/index.js

@@ -85,7 +85,7 @@ module.exports = function(crowi, app) {
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);
 
-  app.post('/*/edit'                 , form.revision              , loginRequired(crowi, app) , page.pageEdit);
+  app.post('/_/edit'                 , form.revision             , loginRequired(crowi, app) , page.pageEdit);
   app.get('/*/$'                     , loginRequired(crowi, app) , page.pageListShow);
   app.get('/*'                       , loginRequired(crowi, app) , page.pageShow);
   //app.get('/*/edit'                , routes.edit);

+ 5 - 3
lib/routes/page.js

@@ -48,6 +48,8 @@ module.exports = function(crowi, app) {
   // routing
   actions.pageListShow = function(req, res) {
     var path = getPathFromRequest(req);
+    path = path + (path == '/' ? '' : '/');
+
     // index page
     var options = {
       offset: req.query.offset || 0,
@@ -56,7 +58,7 @@ module.exports = function(crowi, app) {
 
     var renderVars = {
       page: null,
-      path: path + (path == '/' ? '' : '/'),
+      path: path,
       pages: [],
       pager: generatePager(options)
     };
@@ -65,7 +67,7 @@ module.exports = function(crowi, app) {
     .then(function(portalPage) {
       renderVars.page = portalPage;
 
-      return Page.findListByStartWith(path, req.user, options);
+      return Page.findListByStartWith(path.substr(0, path.length -1), req.user, options);
     }).then(function(pageList) {
 
       renderVars.pages = pageList;
@@ -139,12 +141,12 @@ module.exports = function(crowi, app) {
   };
 
   actions.pageEdit = function(req, res) {
-    var path = getPathFromRequest(req);
 
     var pageForm = req.body.pageForm;
     var body = pageForm.body;
     var currentRevision = pageForm.currentRevision;
     var grant = pageForm.grant;
+    var path = pageForm.path;
 
     var redirectPath = encodeURI(path);
 

+ 9 - 9
lib/util/middlewares.js

@@ -32,20 +32,20 @@ exports.swigFunctions = function(crowi, app) {
 
 exports.swigFilters = function(app, swig) {
   return function(req, res, next) {
-
     swig.setFilter('path2name', function(string) {
-      var name = string;
-      if (string.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) { // /.../hoge/YYYY/MM/DD 形式のページ
-        return string.replace(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/, '$1');
+      var name = string.replace(/(\/)$/, '');
+
+      if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) { // /.../hoge/YYYY/MM/DD 形式のページ
+        return name.replace(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/, '$1');
       }
-      if (string.match(/.+\/([^/]+\/\d{4}\/\d{2})$/)) { // /.../hoge/YYYY/MM 形式のページ
-        return string.replace(/.+\/([^/]+\/\d{4}\/\d{2})$/, '$1');
+      if (name.match(/.+\/([^/]+\/\d{4}\/\d{2})$/)) { // /.../hoge/YYYY/MM 形式のページ
+        return name.replace(/.+\/([^/]+\/\d{4}\/\d{2})$/, '$1');
       }
-      if (string.match(/.+\/([^/]+\/\d{4})$/)) { // /.../hoge/YYYY 形式のページ
-        return string.replace(/.+\/([^/]+\/\d{4})$/, '$1');
+      if (name.match(/.+\/([^/]+\/\d{4})$/)) { // /.../hoge/YYYY 形式のページ
+        return name.replace(/.+\/([^/]+\/\d{4})$/, '$1');
       }
 
-      return string.replace(/.+\/(.+)?$/, '$1'); // ページの末尾を拾う
+      return name.replace(/.+\/(.+)?$/, '$1'); // ページの末尾を拾う
     });
 
     swig.setFilter('datetz', function(input, format) {

+ 3 - 2
lib/views/_form.html

@@ -9,9 +9,10 @@
 </div>
 {% endif %}
 <div id="form-box" class="row">
-  <form action="{{ path }}/edit" id="page-form" method="post" class="col-md-6 {% if isUploadable() %}uploadable{% endif %}">
-    <textarea name="pageForm[body]" class="form-control form-body-height" id="form-body">{% if pageForm.body %}{{ pageForm.body }}{% elseif not revision.body %}# {{ path|path2name }}{% else %}{{ revision.body }}{% endif %}</textarea>
+  <form action="/_/edit" id="page-form" method="post" class="col-md-6 {% if isUploadable() %}uploadable{% endif %}">
+    <textarea name="pageForm[body]" class="form-control" id="form-body">{% if pageForm.body %}{{ pageForm.body }}{% elseif not revision.body %}# {{ path|path2name }}{% else %}{{ revision.body }}{% endif %}</textarea>
 
+    <input type="hidden" name="pageForm[path]" value="{{ path }}">
     <input type="hidden" name="pageForm[currentRevision]" value="{{ pageForm.currentRevision|default(revision._id.toString()) }}">
     <div class="form-submit-group form-group form-inline">
       {#<button class="btn btn-default">

+ 1 - 1
lib/views/layout/2column.html

@@ -124,7 +124,7 @@
   <div id="footer-container" class="footer">
     <footer class="">
     <p>
-    <a href="" data-target="#helpModal" data-toggle="modal"><i class="fa fa-question-circle"> ヘルプ</i></a>
+    <a href="" data-target="#help-modal" data-toggle="modal"><i class="fa fa-question-circle"> ヘルプ</i></a>
     &copy; {{ now|date('Y') }} {{ config.crowi['app:title'] }} <img src="/logo/100x11_g.png" alt="powered by Crowi"> </p>
     </footer>
   </div>

+ 1 - 1
lib/views/modal/widget_help.html

@@ -1,4 +1,4 @@
-<div class="modal fade" id="helpModal">
+<div class="modal fade" id="help-modal">
   <div class="modal-dialog">
     <div class="modal-content">
 

+ 52 - 0
lib/views/modal/widget_what_is_portal.html

@@ -0,0 +1,52 @@
+<div class="modal fade" id="help-portal">
+  <div class="modal-dialog">
+    <div class="modal-content">
+
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+        <h4 class="modal-title">What is Portal?</h4>
+      </div>
+
+      <div class="modal-body">
+        <h4>基本的な機能</h4>
+        <br>
+        <ul>
+          <li>表示される画面には、「一覧ページ」と「ページ」の2種類があります</li>
+          <li>スラッシュ <code>/</code> で終わるページは、その階層の一覧ページとなります。</li>
+          <li>ページでの変更はすべて記録されています。サイドバーには、変更の履歴が一覧となっていて、クリックするとそのページの過去の状態を見ることができます。</li>
+        </ul>
+        <br>
+
+        <h4>編集のコツ</h4>
+        <br>
+        <p>
+        文章の <strong>構造</strong> を意識しましょう。本を書くように、内容と文脈を整理してセクション・サブセクション...と構造的に書くと、わかりやすく他人に伝わりやすいページがになります。
+        </p>
+        <br>
+
+        <h4>記法</h4>
+        <br>
+        <div class="wiki">
+        <pre># セクション</pre>
+        <h1>セクション</h1>
+        </div>
+        <hr>
+
+        <div class="wiki">
+        <pre>## サブセクション</pre>
+        <h2>サブセクション</h2>
+        </div>
+        <hr>
+
+        <div class="wiki">
+        <pre>### サブサブセクション</pre>
+        <h3>サブサブセクション</h3>
+        </div>
+        <hr>
+
+      </div>
+
+    </div><!-- /.modal-content -->
+  </div><!-- /.modal-dialog -->
+</div><!-- /.modal -->
+

+ 2 - 2
lib/views/page.html

@@ -29,7 +29,7 @@
 {% block content_main_before %}
 {% endblock %}
 
-<div id="content-main" class="content-main {% if not page %}on-edit{% endif %}"
+<div id="content-main" class="content-main {% if not page or req.body.pageForm %}on-edit{% endif %}"
   data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
@@ -38,7 +38,7 @@
 
   {% if not page %}
   <ul class="nav nav-tabs hidden-print">
-    <li><a>ページ作成: {{ path|path2name }}</a></li>
+    <li><a>Create: {{ path }}</a></li>
     <li class="dropdown pull-right">
       <a href="/"><i class="fa fa-times"></i> キャンセル</a>
     </li>

+ 138 - 42
lib/views/page_list.html

@@ -1,65 +1,143 @@
 {% extends 'layout/2column.html' %}
 
+{% block html_title %}{{ path|path2name }} · {{ path }}{% endblock %}
+
 {% block content_head %}
+
+{% block content_head_before %}
+{% endblock %}
+
   <header>
-  <h1 class="title" id="revision-path">{{ path }}</h1>
+  <h1 class="title" id="revision-path">
+    {{ path }}
+  </h1>
   </header>
 {% endblock %}
 
+{% block content_head_after %}
+{% endblock %}
+
 {% block content_main %}
-<div class="content-main">
+
+{% block content_main_before %}
+{% endblock %}
+
+<div class="page-list content-main {% if req.body.pageForm %}on-edit{% endif %}"
+  data-page-portal="{% if page %}{{ page.isPortal() }}1{% else %}0{% endif %}"
+  data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
+  data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
+  data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
+  data-page-revision-created="{% if revision %}{{ revision.createdAt|datetz('U') }}{% endif %}"
+  >
+
+<div class="portal">
+{% if page %}
+  <div class="">
+    {{ page.revision.body }}
+  </div>
+{% else %}
+  <div class="portal-form-header">
+    Create Portal: <strong>{{ path }}</strong>
+    <p class="pull-right ">
+      <a href="#" id="portal-form-close"><i class="fa fa-times"></i></a>
+    </p>
+  </div>
+  <div class="portal-form">
+    <div class="edit-form">
+      {% include '_form.html' %}
+    </div>
+  </div>
+{% endif %}
+</div>
 
 <ul class="nav nav-tabs">
     <li class="active"><a href="#view-list" data-toggle="tab">リスト表示</a></li>
     <li><a href="#view-timeline" data-toggle="tab">タイムライン表示</a></li>
 </ul>
 
-<h2>ページ一覧</h2>
-  <div class="tab-content">
-    {# list view #}
-    <div class="active page-list wiki tab-pane fade in" id="view-list">
-      {% for page in pages %}
-        <a class="page-list-link" href="{{ page.path }}">{{ page.path }}</a>
-
-        <span class="page-list-meta">
-          {% if page.commentCount > 0 %}
-            <i class="fa fa-comment"></i>{{ page.commentCount }}
-          {% endif  %}
-
-          {% if !page.isPublic() %}
-            <i class="fa fa-lock"></i>
-          {% endif %}
-        </span>
-
-        <br />
-      {% endfor %}
-
-        <ul class="pagination">
-          {% if pager.prev != null %}
-            <li class="prev"><a href="{{ path }}?offset={{ pager.prev }}&limit={{ pager.limit }}"><i class="fa fa-arrow-left"></i> Prev</a></li>
-          {% endif %}
-          {# この条件は無いな.. #}
-          {% if pages.length > 0 %}
-            <li class="next"><a href="{{ path }}?offset={{ pager.next }}&limit={{ pager.limit }}">Next <i class="fa fa-arrow-right"></i></a></li>
-          {% endif %}
-        </ul>
-    </div>
+<div class="tab-content">
+  {% if pages.length == 0 %}
+  There are no pages under <strong>{{ path }}</strong>.
+
+  <h3>Next Actions</h3>
+
+  <ul>
+    <li>Create portal page?
+      <ul>
+        <li>Great! To create the portal of <strong>{{ path }}</strong>, click "Create Portal" button.</li>
+      </ul>
+    </li>
+    <li>Create the under page directly?
+      <ul>
+        <li>Nice. To create the page under <strong>{{ path }}</strong> directly, type the page name on your browser.</li>
+      </ul>
+    </li>
+  </ul>
+  {% endif  %}
+
+  {# list view #}
+  <div class="active tab-pane fade page-list-container in" id="view-list">
+    <ul class="page-list-ul">
+    {% for page in pages %}
+    <li class="page-list-li">
+      <img src="{{ page.revision.author|picture }}" class="picture picture-rounded">
+
+      <a class="page-list-link" href="{{ page.path }}"
+        data-path="{{ page.path }}"
+        data-short-path="{{ page.path|path2name }}">{{ page.path }}</a>
+
+      <span class="page-list-meta">
+        {% if page.isPortal() %}
+          <span class="label label-info">PORTAL</span>
+        {% endif  %}
+
+
+        {% if page.commentCount > 0 %}
+          <i class="fa fa-comment"></i>{{ page.commentCount }}
+        {% endif  %}
+
+        {% if !page.isPublic() %}
+          <i class="fa fa-lock"></i>
+        {% endif %}
+      </span>
+    </li>
+    {% endfor %}
+    </ul>
+
+      <ul class="pagination">
+        {% if pager.prev != null %}
+          <li class="prev"><a href="{{ path }}?offset={{ pager.prev }}&limit={{ pager.limit }}"><i class="fa fa-arrow-left"></i> Prev</a></li>
+        {% endif %}
+        {# この条件は無いな.. #}
+        {% if pages.length > 0 %}
+          <li class="next"><a href="{{ path }}?offset={{ pager.next }}&limit={{ pager.limit }}">Next <i class="fa fa-arrow-right"></i></a></li>
+        {% endif %}
+      </ul>
+  </div>
 
-    {# timeline view #}
-    <div class="tab-pane" id="view-timeline">
-      {% for page in pages %}
-      <div class="timeline-body" id="id-{{ page.id }}">
-        <h3 class="revision-path"><a href="{{ page.path }}">{{ page.path }}</a></h3>
-        <div class="revision-body wiki"></div>
-        <script type="text/template">{{ page.revision.body }}</script>
-      </div>
-      <hr>
-      {% endfor %}
+  {# timeline view #}
+  <div class="tab-pane" id="view-timeline">
+    {% for page in pages %}
+    <div class="timeline-body" id="id-{{ page.id }}">
+      <h3 class="revision-path"><a href="{{ page.path }}">{{ page.path }}</a></h3>
+      <div class="revision-body wiki"></div>
+      <script type="text/template">{{ page.revision.body }}</script>
     </div>
+    <hr>
+    {% endfor %}
   </div>
+</div>
 
   <script type="text/javascript">
     $(function(){
+        $('#view-list .page-list-link').each(function() {
+          var $link = $(this);
+          var text = $link.text();
+          var path = $link.data('path');
+          var shortPath = $link.data('short-path');
+
+          $link.html(path.replace(shortPath, '<strong>' + shortPath + '</strong>'));
+        });
         $('#view-timeline .timeline-body').each(function()
         {
           var id = $(this).attr('id');
@@ -75,6 +153,8 @@
 
 </div> {# /.content-main #}
 
+{% block content_main_after %}
+{% endblock %}
 
 {% endblock %}
 
@@ -85,3 +165,19 @@
 </footer>
 {% endblock %}
 
+
+{% block side_header %}
+
+{% if not page %}
+<div class="portal-side">
+  <div class="portal-form-button">
+    <button class="btn btn-primary" id="create-portal-button">Create Portal</button>
+    <p class="help-block"><a href="#" data-target="#help-portal" data-toggle="modal"><i class="fa fa-question-circle"></i> What is Portal?</a></p>
+  </div>
+
+</div>
+{% endif %}
+
+{% endblock %} {# side_header #}
+
+{% include 'modal/widget_what_is_portal.html' %}

+ 136 - 4
resource/css/_form.scss

@@ -1,16 +1,148 @@
+.crowi.main-container .main .content-main.on-edit { // {{{ Edit Form of Page
+  position: fixed;
+  z-index: 1060;
+  background: #fff;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+
+  .nav {
+    margin-top: 8px;
+    height: 40px;
+  }
+
+  .portal-form,
+  .tab-content {
+    top: 48px;
+    bottom: 58px;
+    padding: 0 12px;
+    position: absolute;
+    z-index: 1061;
+    left: 0;
+    right: 0;
+    margin-top: 4px;
+
+    .alert-info {
+      display: none;
+    }
+
+    .edit-form { // {{{
+      height: 100%;
+      .row {
+        height: 100%;
+        .col-md-6 {
+          height: 100%;
+        }
+        form {
+          padding: 0;
+          border-right: solid 1px #ccc;
+          &::after {
+            position: absolute;
+            top: 0;
+            right: 15px;
+            font-size: 10px;
+            font-weight: 700;
+            color: #959595;
+            text-transform: uppercase;
+            letter-spacing: 1px;
+            content: "Input Content ...";
+          }
+        }
+        textarea {
+          height: 100%;
+          padding-top: 18px;
+          border: none;
+          box-shadow: none;
+
+          &.dragover {
+            border: dashed 6px #ccc;
+            padding: 12px 6px 0px;
+          }
+        }
+        .preview-body {
+          height: 100%;
+          padding-top: 18px;
+          overflow: scroll;
+
+          &::after {
+            position: absolute;
+            top: 0;
+            right: 15px;
+            font-size: 10px;
+            font-weight: 700;
+            color: #959595;
+            text-transform: uppercase;
+            letter-spacing: 1px;
+            content: "Preview";
+          }
+        }
+      }
+    }
+
+  } // }}}
+
+  .form-group.form-submit-group {
+
+    position: fixed;
+    z-index: 1054;
+    bottom: 0;
+    width: 100%;
+    left: 0;
+    padding: 8px;
+    height: 50px;
+    background: rgba(255,255,255,.8);
+    border-top: solid 1px #ccc;
+    margin-bottom: 0;
+  }
+} // }}}
+
+.crowi.main-container .main .page-list.content-main { // {{{ Edit Form of Page List
+  .portal-form-header,
+  .portal-form {
+    display: none;
+  }
+}
+.crowi.main-container .main .page-list.content-main.on-edit { // {{{ Edit Form of Page List
+  .nav {
+    display: none;
+  }
+  .tab-content {
+    display: none;
+  }
+  .portal-form-header,
+  .portal-form {
+    display: block;
+  }
+
+  .portal-form-header {
+    height: 48px;
+    padding: 14px;
+    border-bottom: solid 1px #ccc;
+  }
+} // }}}
 
 textarea {
   font-family: menlo, monaco, consolas, monospace;
   line-height: 1.1em;
 }
 
-textarea.form-body-height {
-  height: 300px;
-}
-
 input::-webkit-input-placeholder {
   color: #ccc;
 }
 input:-moz-placeholder {
   color: #ccc;
 }
+
+@media (max-width: $screen-sm-max) { // {{{ less than tablet size
+
+  .content-main.on-edit {
+    .form-group.form-submit-group {
+      select.form-control {
+        display: inline-block;
+        width: auto;
+      }
+    }
+  }
+
+} // }}}

+ 1 - 441
resource/css/_layout.scss

@@ -83,191 +83,6 @@
       }
     } // }}}
 
-
-    aside.sidebar { // {{{
-      z-index: 1030;
-      position: fixed;
-      padding: 65px 0 0 0;
-      margin-bottom: $crowiFooterHeight;
-      color: #333;
-      height: 100%;
-      right: 0;
-      top: 0;
-      overflow: auto;
-      border-left: solid 1px #ccc;
-      background: $crowiAsideBackground;
-
-      transition: .3s ease;
-      -webkit-transition: .3s ease;
-
-
-      .page-meta {
-        padding: 15px 15px 5px 15px;
-        color: #666;
-        font-size: .9em;
-        line-height: 1.4em;
-        border-bottom: solid 1px #ccc;
-
-        .creator-picture {
-          text-align: center;
-          img {
-            width: 48px;
-            height: 48px;
-            box-shadow: 0 0 2px #333;
-          }
-        }
-        .creator {
-          font-size: 1.3em;
-          font-weight: bold;
-        }
-        .created-at {
-        }
-
-        .like-box {
-          padding-bottom: 0;
-
-          .dl-horizontal {
-            margin-bottom: 0;
-
-            dt, dd {
-              border-top: solid 1px #ccc;
-              padding-top: 5px;
-              padding-bottom: 5px;
-            }
-            dt {
-              width: 80px;
-            }
-            dd {
-              margin-left: 90px;
-              text-align: right;
-            }
-          }
-        }
-
-        .liker-list, .contributor-list, .seen-user-list {
-          .picture-rounded {
-            box-shadow: 0 0 2px #666;
-          }
-        }
-        .liker-count, .contributor-count, .seen-user-count {
-          font-size: 1.2em;
-          font-weight: bold;
-          margin-bottom: 5px;
-        }
-        .contributor-list, .seen-user-list {
-        }
-      }
-
-
-      .side-content {
-        margin-bottom: $crowiFooterHeight + $crowiHeaderHeight;
-        color: #666;
-        padding: 15px;
-
-        h3 {
-          font-size: 1.1em;
-        }
-
-        a {
-          color: #ccc;
-          &:hover { color: #aaa;}
-        }
-
-        ul.fitted-list {
-          padding-left: 0;
-          li {
-            margin-bottom: 2px;
-
-            .input-group-addon {
-              padding: 5px 6px;
-            }
-          }
-        }
-      }
-    } // }}}
-
-    .main { // {{{
-      transition: .5s ease;
-      -webkit-transition: .5s ease;
-      background: #fff;
-
-      padding: 20px;
-      //margin-left: 10px;
-      //padding: 10px;
-      //
-      article {
-        background: #fff;
-      }
-
-      article header {
-        background: #fff;
-        width: 100%;
-
-        p.stopper {
-          display: none;
-        }
-
-        &.affix {
-          width: 100%;
-          top: 0;
-          left: 0;
-          padding: 5px 20px;
-          z-index: 1039;
-          background: rgba(255, 255, 255, .9);
-          box-shadow: 0 0px 2px #999;
-
-          h1 {
-            font-size: 1.8em;
-          }
-
-          p.stopper {
-            display: block;
-            position: absolute;
-            bottom: -30px;
-            right: 10px;
-            background: #fff;
-            padding: 7px 14px;
-            margin: 0;
-            border: solid 1px #ccc;
-            border-top: none;
-            border-radius: 0 0 5px 5px;
-            font-size: .8em;
-          }
-        }
-      }
-
-      &.col-md-12 article header.affix {
-        width: 100%;
-      }
-
-
-
-      article header { // not affixed
-        .bookmark-link {
-          float: right;
-          color: #e6b422;
-          font-size: 2em;
-          &.bookmarked {
-            //color: #fff;
-          }
-        }
-
-        h1 {
-          margin-top: 0;
-
-          a:last-child {
-            color: #D1E2E4;
-            opacity: .7;
-
-            &:hover {
-              color: inherit;
-            }
-          }
-        }
-      }
-
-    } // }}}
-
     .page-list {
       .page-list-link {
       }
@@ -277,254 +92,9 @@
       }
     }
 
-    .main.grant-restricted,
-    .main.grant-specified,
-    .main.grant-owner {
-      background: #333;
-      padding: 20px 10px;
-
-      .page-grant {
-        color: #ccc;
-      }
-
-      article {
-        border-radius: 5px;
-        padding: 20px;
-      }
-    }
-
-    .page-attachments {
-      p {
-        font-weight: bold;
-      }
-
-      ul {
-      }
-    }
-
-    .footer { // {{{
-      position: fixed;
-      width: 100%;
-      bottom: 0px;
-      height: 26px;
-      padding: 4px;
-      color: #444;
-      background: $crowiAsideBackground;
-      border-top-left-radius: 5px;
-      z-index: 1055;
-
-      a {
-        color: #666;
-      }
-    } // }}}
-  } // }}}
-
-  &.main-container.aside-hidden { // {{{
-    .layout-control {
-      right: 0;
-      i {
-        transform: rotate(180deg);
-      }
-    }
-
-    aside.sidebar { // {{{
-      right: -25%;
-    } // }}}
-
-    .main { // {{{
-      width: 100%;
-
-      article header.affix {
-        width: 100%;
-      }
-    } // }}}
   } // }}}
-
-  // override bootstrap modals
-  //.modal-backdrop {
-  //  z-index: 1052;
-  //}
-  //.modal {
-  //  z-index: 1055;
-  //}
 } // }}}
 
-.crowi.main-container .main {
-  .wiki-content {
-  }
-
-  .content-main {
-    .tab-content {
-      margin-top: 30px;
-
-      .revision-history {
-        h1 {
-          padding-bottom: 0.3em;
-          font-size: 2.3em;
-          font-weight: bold;
-          border-bottom: solid 1px #ccc;
-        }
-
-
-        .revision-history-list {
-          .revision-hisory-outer {
-            margin-top: 8px;
-
-            .picture {
-              float: left;
-              width: 32px;
-              height: 32px;
-            }
-
-            .revision-history-main {
-              margin-left: 40px;
-
-              .revision-history-author {
-              }
-              .revision-history-comment {
-              }
-              .revision-history-meta {
-              }
-            }
-          }
-
-          li {
-            position: relative;
-            list-style: none;
-
-            a {
-              color: #666;
-              padding: 3px 5px 3px 40px;
-              display: block;
-
-              &:hover {
-                background: darken($crowiAsideBackground, 10%);
-                text-decoration: none;
-                color: darken($link-color, 35%);
-              }
-            }
-
-          }
-        }
-
-      }
-    }
-  }
-
-  .content-main .timeline-body { // {{{ timeline
-    .revision-path {
-      margin-top: 1.6em;
-      margin-bottom: 0;
-      border: solid 2px #ddd;
-      border-bottom: none;
-      padding: 16px;
-      background: #ddd;
-    }
-    .revision-body {
-      font-size: 14px;
-      border: solid 2px #ddd;
-      padding: 16px;
-      background: #fdfdfd;
-    }
-  } // }}}
-
-  // on-edit
-  .content-main.on-edit {
-    position: fixed;
-    z-index: 1060;
-    background: #fff;
-    top: 0;
-    left: 0;
-    height: 100%;
-    width: 100%;
-
-    .nav {
-      margin-top: 8px;
-      height: 40px;
-    }
-
-    .tab-content {
-      .alert-info {
-        display: none;
-      }
-
-      top: 48px;
-      bottom: 58px;
-      padding: 0 12px;
-      position: absolute;
-      left: 0;
-      right: 0;
-      margin-top: 4px;
-
-      .edit-form {
-        height: 100%;
-        .row {
-          height: 100%;
-          .col-md-6 {
-            height: 100%;
-          }
-          form {
-            padding: 0;
-            border-right: solid 1px #ccc;
-            &::after {
-              position: absolute;
-              top: 0;
-              right: 15px;
-              font-size: 10px;
-              font-weight: 700;
-              color: #959595;
-              text-transform: uppercase;
-              letter-spacing: 1px;
-              content: "Input Content ...";
-            }
-          }
-          textarea {
-            height: 100%;
-            padding-top: 18px;
-            border: none;
-            box-shadow: none;
-
-            &.dragover {
-              border: dashed 6px #ccc;
-              padding: 12px 6px 0px;
-            }
-          }
-          .preview-body {
-            height: 100%;
-            padding-top: 18px;
-            overflow: scroll;
-
-            &::after {
-              position: absolute;
-              top: 0;
-              right: 15px;
-              font-size: 10px;
-              font-weight: 700;
-              color: #959595;
-              text-transform: uppercase;
-              letter-spacing: 1px;
-              content: "Preview";
-            }
-          }
-        }
-      }
-    }
-
-    .form-group.form-submit-group {
-
-      position: fixed;
-      z-index: 1054;
-      bottom: 0;
-      width: 100%;
-      left: 0;
-      padding: 8px;
-      height: 50px;
-      background: rgba(255,255,255,.8);
-      border-top: solid 1px #ccc;
-      margin-bottom: 0;
-    }
-  }
-}
 
 .crowi.single { // {{{
 } // }}}
@@ -612,17 +182,7 @@
 
 } // }}}
 
-@media (max-width: $screen-sm-max) { // {{{ less than tablet size
-
-  .content-main.on-edit {
-    .form-group.form-submit-group {
-      select.form-control {
-        display: inline-block;
-        width: auto;
-      }
-    }
-  }
-
+@media (max-width: $screen-sm-max) { // {{{
 } // }}}
 
 @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { // {{{ tablet size

+ 304 - 0
resource/css/_page.scss

@@ -0,0 +1,304 @@
+.crowi.main-container {
+
+  .main { // {{{ .main of layout related
+    transition: .5s ease;
+    -webkit-transition: .5s ease;
+    background: #fff;
+
+    padding: 20px;
+    article {
+      background: #fff;
+    }
+
+    article header {
+      background: #fff;
+      width: 100%;
+
+      p.stopper {
+        display: none;
+      }
+
+      &.affix {
+        width: 100%;
+        top: 0;
+        left: 0;
+        padding: 5px 20px;
+        z-index: 1039;
+        background: rgba(255, 255, 255, .9);
+        box-shadow: 0 0px 2px #999;
+
+        h1 {
+          font-size: 1.8em;
+        }
+
+        p.stopper {
+          display: block;
+          position: absolute;
+          bottom: -30px;
+          right: 10px;
+          background: #fff;
+          padding: 7px 14px;
+          margin: 0;
+          border: solid 1px #ccc;
+          border-top: none;
+          border-radius: 0 0 5px 5px;
+          font-size: .8em;
+        }
+      }
+
+    }
+
+    &.col-md-12 article header.affix {
+      width: 100%;
+    }
+
+    article header { // not affixed
+      .bookmark-link {
+        float: right;
+        color: #e6b422;
+        font-size: 2em;
+        &.bookmarked {
+          //color: #fff;
+        }
+      }
+
+      h1 {
+        margin-top: 0;
+
+        a:last-child {
+          color: #D1E2E4;
+          opacity: .7;
+
+          &:hover {
+            color: inherit;
+          }
+        }
+      }
+    }
+
+    .content-main {
+      .tab-content {
+        margin-top: 30px;
+      }
+    }
+  } // }}}
+
+  // {{{ grant related style
+  .main.grant-restricted,
+  .main.grant-specified,
+  .main.grant-owner {
+    background: #333;
+    padding: 20px 10px;
+
+    .page-grant {
+      color: #ccc;
+    }
+
+    article {
+      border-radius: 5px;
+      padding: 20px;
+    }
+  }
+  // }}}
+
+  .page-attachments { // {{{
+    p {
+      font-weight: bold;
+    }
+
+    ul {
+    }
+  } // }}}
+
+  aside.sidebar { // {{{
+    z-index: 1030;
+    position: fixed;
+    padding: 65px 0 0 0;
+    margin-bottom: $crowiFooterHeight;
+    color: #333;
+    height: 100%;
+    right: 0;
+    top: 0;
+    overflow: auto;
+    border-left: solid 1px #ccc;
+    background: $crowiAsideBackground;
+
+    transition: .3s ease;
+    -webkit-transition: .3s ease;
+
+
+    .page-meta {
+      padding: 15px 15px 5px 15px;
+      color: #666;
+      font-size: .9em;
+      line-height: 1.4em;
+      border-bottom: solid 1px #ccc;
+
+      .creator-picture {
+        text-align: center;
+        img {
+          width: 48px;
+          height: 48px;
+          box-shadow: 0 0 2px #333;
+        }
+      }
+      .creator {
+        font-size: 1.3em;
+        font-weight: bold;
+      }
+      .created-at {
+      }
+
+      .like-box {
+        padding-bottom: 0;
+
+        .dl-horizontal {
+          margin-bottom: 0;
+
+          dt, dd {
+            border-top: solid 1px #ccc;
+            padding-top: 5px;
+            padding-bottom: 5px;
+          }
+          dt {
+            width: 80px;
+          }
+          dd {
+            margin-left: 90px;
+            text-align: right;
+          }
+        }
+      }
+
+      .liker-list, .contributor-list, .seen-user-list {
+        .picture-rounded {
+          box-shadow: 0 0 2px #666;
+        }
+      }
+      .liker-count, .contributor-count, .seen-user-count {
+        font-size: 1.2em;
+        font-weight: bold;
+        margin-bottom: 5px;
+      }
+      .contributor-list, .seen-user-list {
+      }
+    }
+
+
+    .side-content {
+      margin-bottom: $crowiFooterHeight + $crowiHeaderHeight;
+      color: #666;
+      padding: 15px;
+
+      h3 {
+        font-size: 1.1em;
+      }
+
+      a {
+        color: #ccc;
+        &:hover { color: #aaa;}
+      }
+
+      ul.fitted-list {
+        padding-left: 0;
+        li {
+          margin-bottom: 2px;
+
+          .input-group-addon {
+            padding: 5px 6px;
+          }
+        }
+      }
+    }
+  } // }}}
+
+  .footer { // {{{
+    position: fixed;
+    width: 100%;
+    bottom: 0px;
+    height: 26px;
+    padding: 4px;
+    color: #444;
+    background: $crowiAsideBackground;
+    border-top-left-radius: 5px;
+    z-index: 1055;
+
+    a {
+      color: #666;
+    }
+  } // }}}
+
+  &.aside-hidden { // {{{
+    .layout-control {
+      right: 0;
+      i {
+        transform: rotate(180deg);
+      }
+    }
+
+    aside.sidebar { // {{{
+      right: -25%;
+    } // }}}
+
+    .main { // {{{
+      width: 100%;
+
+      article header.affix {
+        width: 100%;
+      }
+    } // }}}
+  } // }}}
+
+}
+.crowi.main-container .main .content-main .revision-history { // {{{
+  h1 {
+    padding-bottom: 0.3em;
+    font-size: 2.3em;
+    font-weight: bold;
+    border-bottom: solid 1px #ccc;
+  }
+
+
+  .revision-history-list {
+    .revision-hisory-outer {
+      margin-top: 8px;
+
+      .picture {
+        float: left;
+        width: 32px;
+        height: 32px;
+      }
+
+      .revision-history-main {
+        margin-left: 40px;
+
+        .revision-history-author {
+        }
+        .revision-history-comment {
+        }
+        .revision-history-meta {
+        }
+      }
+    }
+
+    li {
+      position: relative;
+      list-style: none;
+
+      a {
+        color: #666;
+        padding: 3px 5px 3px 40px;
+        display: block;
+
+        &:hover {
+          background: darken($crowiAsideBackground, 10%);
+          text-decoration: none;
+          color: darken($link-color, 35%);
+        }
+      }
+
+    }
+  }
+
+} // }}}
+

+ 48 - 0
resource/css/_page_list.scss

@@ -0,0 +1,48 @@
+
+.crowi.main-container .main .content-main .timeline-body { // {{{
+  .revision-path {
+    margin-top: 1.6em;
+    margin-bottom: 0;
+    border: solid 2px #ddd;
+    border-bottom: none;
+    padding: 16px;
+    background: #ddd;
+  }
+  .revision-body {
+    font-size: 14px;
+    border: solid 2px #ddd;
+    padding: 16px;
+    background: #fdfdfd;
+  }
+} // }}}
+
+.page-list {
+  .page-list-container {
+    line-height: 1.6em;
+    font-size: 15px;
+  }
+
+  .page-list-ul {
+    padding-left: 0;
+
+    .page-list-li {
+      list-style: none;
+      line-height: 1.8em;
+
+      .picture {
+        width: 16px;
+        height: 16px;
+        margin-right: 4px;
+      }
+
+      .page-list-link {
+        font-size: 1.1em;
+        color: #666;
+
+        strong {
+          color: #333;
+        }
+      }
+    }
+  }
+}

+ 11 - 0
resource/css/_portal.scss

@@ -0,0 +1,11 @@
+.portal {
+  .portal-form {
+
+  }
+} // .portal
+
+.portal-side {
+  .portal-form-button {
+    text-align: center;
+  }
+} // .portal-side

+ 3 - 0
resource/css/crowi.scss

@@ -8,11 +8,14 @@
 
 // crowi component
 @import 'layout';
+@import 'page';
+@import 'page_list';
 @import 'form';
 @import 'wiki';
 @import 'admin';
 @import 'comment';
 @import 'user';
+@import 'portal';
 
 
 ul {

+ 12 - 4
resource/js/crowi.js

@@ -22,10 +22,14 @@ Crowi.linkPath = function(revisionPath) {
   if (!$title.get(0)) {
     return;
   }
+  var realPath = $title.text().trim();
+  if (realPath.substr(-1, 1) == '/') {
+    realPath = realPath.substr(0, realPath.length - 1);
+  }
 
   var path = '';
   var pathHtml = '';
-  var splittedPath = $title.html().split(/\//);
+  var splittedPath = realPath.split(/\//);
   splittedPath.shift();
   splittedPath.forEach(function(sub) {
     path += '/';
@@ -269,6 +273,13 @@ $(function() {
     return false;
   });
 
+  $('#create-portal-button').on('click', function(e) {
+    $('.content-main').addClass('on-edit');
+  });
+  $('#portal-form-close').on('click', function(e) {
+    $('.content-main').removeClass('on-edit');
+  });
+
 
   if (pageId) {
 
@@ -404,17 +415,14 @@ $(function() {
 
     $bookmarkButton.click(function() {
       var bookmarked = $bookmarkButton.data('bookmarked');
-      console.log('isBookmarked', bookmarked);
       if (!bookmarked) {
         $.post('/_api/bookmarks.add', {page_id: pageId}, function(res) {
-          console.log(res);
           if (res.ok && res.bookmark) {
             MarkBookmarked();
           }
         });
       } else {
         $.post('/_api/bookmarks.remove', {page_id: pageId}, function(res) {
-          console.log(res);
           if (res.ok) {
             MarkUnBookmarked();
           }