diff --git a/client/client-app.js b/client/client-app.js
index 6c943378..9b08f91a 100644
--- a/client/client-app.js
+++ b/client/client-app.js
@@ -158,6 +158,7 @@ Vue.prototype.Velocity = Velocity
Vue.component('admin', () => import(/* webpackChunkName: "admin" */ './components/admin.vue'))
Vue.component('editor', () => import(/* webpackPrefetch: -100, webpackChunkName: "editor" */ './components/editor.vue'))
Vue.component('history', () => import(/* webpackChunkName: "history" */ './components/history.vue'))
+Vue.component('page-source', () => import(/* webpackChunkName: "source" */ './components/source.vue'))
Vue.component('login', () => import(/* webpackPrefetch: true, webpackChunkName: "login" */ './components/login.vue'))
Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue'))
Vue.component('page-selector', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/page-selector.vue'))
diff --git a/client/components/common/page-selector.vue b/client/components/common/page-selector.vue
index 42853b9c..6242e35f 100644
--- a/client/components/common/page-selector.vue
+++ b/client/components/common/page-selector.vue
@@ -31,17 +31,7 @@
v-flex(xs8)
v-toolbar(color='grey darken-2', dark, dense, flat)
.body-2 Pages
- v-divider.ml-4(vertical)
- v-text-field(
- prepend-inner-icon='search'
- label='Search...'
- hide-details
- solo
- background-color='grey darken-2'
- flat
- clearable
- )
- v-divider.mx-3(vertical)
+ v-spacer
v-btn(icon): v-icon forward
v-btn(icon): v-icon delete
v-list(dense)
diff --git a/client/components/history.vue b/client/components/history.vue
index aef4d001..950ab3bd 100644
--- a/client/components/history.vue
+++ b/client/components/history.vue
@@ -5,11 +5,11 @@
v-toolbar(color='primary', dark)
.subheading Viewing history of page #[strong /{{path}}]
v-spacer
- .caption.blue--text.text--lighten-3 ID {{id}}
+ .caption.blue--text.text--lighten-3 ID {{pageId}}
v-btn.ml-4(depressed, color='blue darken-1', @click='goLive') Return to Live Version
v-container(fluid, grid-list-xl)
v-layout(row, wrap)
- v-flex(xs5)
+ v-flex(xs4)
v-chip.ma-0.grey--text.text--darken-2(
label
small
@@ -20,86 +20,92 @@
dense
)
v-timeline-item(
+ v-for='ph in trail'
+ :key='ph.versionId'
+ :small='ph.actionType === `edit`'
fill-dot
- color='primary'
- icon='edit'
+ :color='trailColor(ph.actionType)'
+ :icon='trailIcon(ph.actionType)'
)
- v-card.grey.lighten-3.radius-7(flat)
- v-card-text
- v-layout(justify-space-between)
- v-flex(xs7)
- v-chip.ml-0.mr-3(
- label
- small
- color='primary'
- )
- span.white--text Viewing
- span Edited by John Doe
- v-flex(xs5, text-xs-right, align-center, d-flex)
- .caption Today at 12:34 PM
+ v-card.radius-7(flat, :class='trailBgColor(ph.actionType)')
+ v-toolbar(flat, :color='trailBgColor(ph.actionType)')
+ v-chip.ml-0.mr-3(
+ v-if='diffSource === ph.versionId'
+ label
+ small
+ color='pink'
+ )
+ .caption.white--text Source
+ v-chip.ml-0.mr-3(
+ v-if='diffTarget === ph.versionId'
+ label
+ small
+ color='pink'
+ )
+ .caption.white--text Target
+ .caption(v-if='ph.actionType === `edit`') Edited by {{ ph.authorName }}
+ .caption(v-else-if='ph.actionType === `move`') Moved from #[strong {{ph.valueBefore}}] to #[strong {{ph.valueAfter}}] by {{ ph.authorName }}
+ .caption(v-else-if='ph.actionType === `initial`') Created by {{ ph.authorName }}
+ .caption(v-else) Unknown Action by {{ ph.authorName }}
+ v-spacer
+ .caption {{ ph.createdAt | moment('calendar') }}
+ v-menu(offset-x, left)
+ v-btn(icon, slot='activator'): v-icon more_horiz
+ v-list(dense).history-promptmenu
+ v-list-tile(@click='setDiffTarget(ph.versionId)')
+ v-list-tile-avatar: v-icon call_made
+ v-list-tile-title Set as Differencing Target
+ v-divider
+ v-list-tile(@click='setDiffSource(ph.versionId)')
+ v-list-tile-avatar: v-icon call_received
+ v-list-tile-title Set as Differencing Source
+ v-divider
+ v-list-tile
+ v-list-tile-avatar: v-icon code
+ v-list-tile-title View Source
+ v-divider
+ v-list-tile
+ v-list-tile-avatar: v-icon cloud_download
+ v-list-tile-title Download Version
+ v-divider
+ v-list-tile
+ v-list-tile-avatar: v-icon restore
+ v-list-tile-title Restore
+ v-divider
+ v-list-tile
+ v-list-tile-avatar: v-icon call_split
+ v-list-tile-title Branch off from here
- v-timeline-item(
- fill-dot
- small
- color='primary'
- icon='edit'
- )
- v-card.grey.lighten-3.radius-7(flat)
- v-card-text
- v-layout(justify-space-between)
- v-flex(xs7)
- span Edited by Jane Doe
- v-flex(xs5, text-xs-right, align-center, d-flex)
- .caption Today at 12:27 PM
-
- v-timeline-item(
- fill-dot
- small
- color='purple'
- icon='forward'
- )
- v-card.purple.lighten-5.radius-7(flat)
- v-card-text
- v-layout(justify-space-between)
- v-flex(xs7)
- span Moved page from #[strong /test] to #[strong /home] by John Doe
- v-flex(xs5, text-xs-right, align-center, d-flex)
- .caption Yesterday at 10:45 AM
-
- v-timeline-item(
- fill-dot
- color='teal'
- icon='add'
- )
- v-card.teal.lighten-5.radius-7(flat)
- v-card-text
- v-layout(justify-space-between)
- v-flex(xs7): span Initial page creation by John Doe
- v-flex(xs5, text-xs-right, align-center, d-flex)
- .caption Last Tuesday at 7:56 PM
v-chip.ma-0.grey--text.text--darken-2(
label
small
color='grey lighten-2'
- ) End of history
+ ) End of history trail
- v-flex(xs7)
+ v-flex(xs8)
v-card.radius-7
v-card-text
v-card.grey.lighten-4.radius-7(flat)
v-card-text
.subheading Page Title
.caption Some page description
+ .mt-3(v-html='diffHTML')
nav-footer
+
+
diff --git a/client/graph/history/history-trail-query.gql b/client/graph/history/history-trail-query.gql
new file mode 100644
index 00000000..d7ff2d25
--- /dev/null
+++ b/client/graph/history/history-trail-query.gql
@@ -0,0 +1,13 @@
+query($id: Int!, $offset: Int) {
+ pages {
+ history(id:$id, offset:$offset) {
+ versionId
+ authorId
+ authorName
+ actionType
+ valueBefore
+ valueAfter
+ createdAt
+ }
+ }
+}
diff --git a/client/scss/app.scss b/client/scss/app.scss
index 5df054c6..faa049ad 100644
--- a/client/scss/app.scss
+++ b/client/scss/app.scss
@@ -5,6 +5,7 @@
@import "../libs/animate/animate";
@import '~vue2-animate/src/sass/vue2-animate';
+@import '~diff2html/dist/diff2html.min.css';
@import 'components/v-btn';
@import 'components/v-data-table';
diff --git a/client/scss/base/base.scss b/client/scss/base/base.scss
index bad02832..ba364a78 100644
--- a/client/scss/base/base.scss
+++ b/client/scss/base/base.scss
@@ -20,7 +20,7 @@ html {
}
-@for $i from 1 through 25 {
+@for $i from 0 through 25 {
.radius-#{$i} {
border-radius: #{$i}px;
}
diff --git a/client/themes/default/scss/app.scss b/client/themes/default/scss/app.scss
index 77ee021e..b77bf11e 100644
--- a/client/themes/default/scss/app.scss
+++ b/client/themes/default/scss/app.scss
@@ -95,6 +95,13 @@
text-align: justify;
}
+ hr {
+ margin: 1rem;
+ height: 1px;
+ border: none;
+ background-color: mc('grey', '400');
+ }
+
blockquote {
padding: 0 0 1rem 0;
border: 1px solid mc('blue', '500');
@@ -204,17 +211,18 @@
.task-list-item {
position: relative;
+ list-style-type: none;
&-checkbox[disabled] {
display: none;
& + label {
- padding-left: 1.4rem;
+ padding-left: 1.5rem;
}
& + label::before {
position: absolute;
- left: 1rem;
+ left: 0;
top: 2px;
content: ' ';
display: block;
@@ -233,6 +241,10 @@
content: '✓';
}
}
+
+ .contains-task-list {
+ padding: .5rem 0 0 1.5rem;
+ }
}
}
diff --git a/package.json b/package.json
index d7ac22cd..5d9ba49c 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
"cookie-parser": "1.4.3",
"cors": "2.8.5",
"dependency-graph": "0.7.2",
+ "diff": "3.5.0",
"diff2html": "2.5.0",
"dotize": "^0.2.0",
"execa": "1.0.0",
diff --git a/server/controllers/common.js b/server/controllers/common.js
index 835b81d1..bbde3529 100644
--- a/server/controllers/common.js
+++ b/server/controllers/common.js
@@ -46,11 +46,11 @@ router.get(['/p', '/p/*'], (req, res, next) => {
})
/**
- * View document
+ * History
*/
router.get(['/h', '/h/*'], async (req, res, next) => {
const pageArgs = pageHelper.parsePath(req.path)
- const page = await WIKI.models.pages.getPage({
+ const page = await WIKI.models.pages.getPageFromDb({
path: pageArgs.path,
locale: pageArgs.locale,
userId: req.user.id,
@@ -63,6 +63,24 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
}
})
+/**
+ * Source
+ */
+router.get(['/s', '/s/*'], async (req, res, next) => {
+ const pageArgs = pageHelper.parsePath(req.path)
+ const page = await WIKI.models.pages.getPageFromDb({
+ path: pageArgs.path,
+ locale: pageArgs.locale,
+ userId: req.user.id,
+ isPrivate: false
+ })
+ if (page) {
+ res.render('source', { page })
+ } else {
+ res.redirect(`/${pageArgs.path}`)
+ }
+})
+
/**
* View document
*/
diff --git a/server/graph/resolvers/page.js b/server/graph/resolvers/page.js
index e621bf04..3e560826 100644
--- a/server/graph/resolvers/page.js
+++ b/server/graph/resolvers/page.js
@@ -10,6 +10,12 @@ module.exports = {
async pages() { return {} }
},
PageQuery: {
+ async history(obj, args, context, info) {
+ return WIKI.models.pageHistory.getHistory({
+ pageId: args.id,
+ offset: args.offset || 0
+ })
+ },
async list(obj, args, context, info) {
return WIKI.models.pages.query().select(
'pages.*',
diff --git a/server/graph/schemas/page.graphql b/server/graph/schemas/page.graphql
index 9816209a..808a0286 100644
--- a/server/graph/schemas/page.graphql
+++ b/server/graph/schemas/page.graphql
@@ -15,6 +15,11 @@ extend type Mutation {
# -----------------------------------------------
type PageQuery {
+ history(
+ id: Int!
+ offset: Int
+ ): [PageHistory]
+
list(
filter: String
orderBy: String
@@ -92,3 +97,13 @@ type Page {
createdAt: Date!
updatedAt: Date!
}
+
+type PageHistory {
+ versionId: Int!
+ authorId: Int!
+ authorName: String!
+ actionType: String!
+ valueBefore: String
+ valueAfter: String
+ createdAt: Date!
+}
diff --git a/server/models/pageHistory.js b/server/models/pageHistory.js
index d9862fcf..aae8d379 100644
--- a/server/models/pageHistory.js
+++ b/server/models/pageHistory.js
@@ -1,4 +1,5 @@
const Model = require('objection').Model
+const _ = require('lodash')
/* global WIKI */
@@ -101,4 +102,53 @@ module.exports = class PageHistory extends Model {
title: opts.title
})
}
+
+ static async getHistory({ pageId, offset = 0 }) {
+ const history = await WIKI.models.pageHistory.query()
+ .column([
+ 'pageHistory.id',
+ 'pageHistory.path',
+ 'pageHistory.authorId',
+ 'pageHistory.createdAt',
+ {
+ authorName: 'author.name'
+ }
+ ])
+ .joinRelation('author')
+ .where({
+ 'pageHistory.pageId': pageId
+ })
+ .orderBy('pageHistory.createdAt', 'asc')
+ .offset(offset)
+ .limit(20)
+
+ let prevPh = null
+
+ return _.reduce(history, (res, ph) => {
+ let actionType = 'edit'
+ let valueBefore = null
+ let valueAfter = null
+
+ if (!prevPh && offset === 0) {
+ actionType = 'initial'
+ } else if (_.get(prevPh, 'path', '') !== ph.path) {
+ actionType = 'move'
+ valueBefore = _.get(prevPh, 'path', '')
+ valueAfter = ph.path
+ }
+
+ res.unshift({
+ versionId: ph.id,
+ authorId: ph.authorId,
+ authorName: ph.authorName,
+ actionType,
+ valueBefore,
+ valueAfter,
+ createdAt: ph.createdAt
+ })
+
+ prevPh = ph
+ return res
+ }, [])
+ }
}
diff --git a/server/models/pages.js b/server/models/pages.js
index 187bde44..9eb26774 100644
--- a/server/models/pages.js
+++ b/server/models/pages.js
@@ -197,7 +197,7 @@ module.exports = class Page extends Model {
}
static async getPageFromDb(opts) {
- const page = await WIKI.models.pages.query()
+ return WIKI.models.pages.query()
.column([
'pages.*',
{
@@ -227,7 +227,6 @@ module.exports = class Page extends Model {
}
})
.first()
- return page
}
static async savePageToCache(page) {
diff --git a/server/views/history.pug b/server/views/history.pug
index 80643849..2380f9f5 100644
--- a/server/views/history.pug
+++ b/server/views/history.pug
@@ -5,7 +5,8 @@ block head
block body
#root
history(
- id=page.id
+ :page-id=page.id
locale=page.localeCode
path=page.path
+ live-content=page.content
)
diff --git a/server/views/source.pug b/server/views/source.pug
new file mode 100644
index 00000000..55d56ffc
--- /dev/null
+++ b/server/views/source.pug
@@ -0,0 +1,11 @@
+extends master.pug
+
+block head
+
+block body
+ #root
+ page-source(
+ page-id=page.id
+ locale=page.localeCode
+ path=page.path
+ )= page.content
diff --git a/yarn.lock b/yarn.lock
index 621e5d46..38a85ed3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4934,7 +4934,7 @@ diff2html@2.5.0:
lodash "^4.17.11"
whatwg-fetch "^3.0.0"
-diff@^3.1.0, diff@^3.5.0:
+diff@3.5.0, diff@^3.1.0, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==