Ver código fonte

Edit save + git commit + push sync

NGPixel 8 anos atrás
pai
commit
0f06ab6dc8
9 arquivos alterados com 169 adições e 33 exclusões
  1. 2 2
      README.md
  2. 0 0
      assets/js/app.js
  3. 1 1
      client/js/app.js
  4. 15 1
      client/js/pages/edit.js
  5. 33 1
      controllers/pages.js
  6. 79 15
      models/entries.js
  7. 35 9
      models/git.js
  8. 1 1
      views/pages/edit.pug
  9. 3 3
      views/pages/view.pug

+ 2 - 2
README.md

@@ -4,13 +4,13 @@
 
 # Requarks Wiki
 
+[![Release](https://img.shields.io/github/release/Requarks/wiki.svg?maxAge=86400)](https://github.com/Requarks/wiki/releases)
 [![License](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://github.com/requarks/wiki/blob/master/LICENSE)
 [![Build Status](https://travis-ci.org/Requarks/wiki.svg?branch=master)](https://travis-ci.org/Requarks/wiki)
 [![Codacy Badge](https://api.codacy.com/project/badge/Grade/1d0217a3153c4595bdedb322263e55c8)](https://www.codacy.com/app/Requarks/wiki)
-[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/df3886d694254a248a7585a90bc5faed)](https://www.codacy.com/app/requarks/wiki)
 [![Dependency Status](https://gemnasium.com/badges/github.com/Requarks/wiki.svg)](https://gemnasium.com/github.com/Requarks/wiki)
 [![Known Vulnerabilities](https://snyk.io/test/github/requarks/wiki/badge.svg)](https://snyk.io/test/github/requarks/wiki)
-[![Documentation](http://inch-ci.org/github/requarks/wiki.svg?branch=master)](https://requarks-wiki.readme.io/)
+[![Documentation](http://inch-ci.org/github/Requarks/wiki.svg?branch=master)](https://requarks-wiki.readme.io/)
 
 ##### A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown
 *Under development*

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
assets/js/app.js


+ 1 - 1
client/js/app.js

@@ -39,7 +39,7 @@ jQuery( document ).ready(function( $ ) {
 
 	if($('#mk-editor').length === 1) {
 
-		let mde = new SimpleMDE({
+		var mde = new SimpleMDE({
 			autofocus: true,
 			autoDownloadFontAwesome: false,
 			element: $("#mk-editor").get(0),

+ 15 - 1
client/js/pages/edit.js

@@ -11,7 +11,21 @@ if($('#page-type-edit').length) {
 
 	$('.btn-edit-save').on('click', (ev) => {
 
-		
+		$.ajax(window.location.href, {
+			data: {
+				markdown: mde.value()
+			},
+			dataType: 'json',
+			method: 'PUT'
+		}).then((rData, rStatus, rXHR) => {
+			if(rData.ok) {
+				window.location.assign('/' + $('#page-type-edit').data('entrypath'));
+			} else {
+				alerts.pushError('Something went wrong', rData.error);
+			}
+		}, (rXHR, rStatus, err) => {
+			alerts.pushError('Something went wrong', 'Save operation failed.');
+		});
 
 	});
 

+ 33 - 1
controllers/pages.js

@@ -4,6 +4,13 @@ var express = require('express');
 var router = express.Router();
 var _ = require('lodash');
 
+// ==========================================
+// EDIT MODE
+// ==========================================
+
+/**
+ * Edit document in Markdown
+ */
 router.get('/edit/*', (req, res, next) => {
 
 	let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
@@ -30,12 +37,37 @@ router.get('/edit/*', (req, res, next) => {
 
 });
 
+router.put('/edit/*', (req, res, next) => {
+
+	let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
+
+	entries.update(safePath, req.body.markdown).then(() => {
+		res.json({
+			ok: true
+		});
+	}).catch((err) => {
+		res.json({
+			ok: false,
+			error: err.message
+		});
+	});
+
+});
+
+// ==========================================
+// CREATE MODE
+// ==========================================
+
 router.get('/new/*', (req, res, next) => {
 	res.send('CREATE MODE');
 });
 
+// ==========================================
+// VIEW MODE
+// ==========================================
+
 /**
- * Home
+ * View document
  */
 router.get('/*', (req, res, next) => {
 

+ 79 - 15
models/entries.js

@@ -2,7 +2,7 @@
 
 var Promise = require('bluebird'),
 	path = require('path'),
-	fs = Promise.promisifyAll(require("fs")),
+	fs = Promise.promisifyAll(require("fs-extra")),
 	_ = require('lodash'),
 	farmhash = require('farmhash'),
 	BSONModule = require('bson'),
@@ -34,16 +34,16 @@ module.exports = {
 	},
 
 	/**
-	 * Fetch an entry from cache, otherwise the original
+	 * Fetch a document from cache, otherwise the original
 	 *
-	 * @param      {String}  entryPath  The entry path
-	 * @return     {Object}  Page Data
+	 * @param      {String}           entryPath  The entry path
+	 * @return     {Promise<Object>}  Page Data
 	 */
 	fetch(entryPath) {
 
 		let self = this;
 
-		let cpath = path.join(self._cachePath, farmhash.fingerprint32(entryPath) + '.bson');
+		let cpath = self.getCachePath(entryPath);
 
 		return fs.statAsync(cpath).then((st) => {
 			return st.isFile();
@@ -78,16 +78,16 @@ module.exports = {
 	/**
 	 * Fetches the original document entry
 	 *
-	 * @param      {String}  entryPath  The entry path
-	 * @param      {Object}  options    The options
-	 * @return     {Object}  Page data
+	 * @param      {String}           entryPath  The entry path
+	 * @param      {Object}           options    The options
+	 * @return     {Promise<Object>}  Page data
 	 */
 	fetchOriginal(entryPath, options) {
 
 		let self = this;
 
-		let fpath = path.join(self._repoPath, entryPath + '.md');
-		let cpath = path.join(self._cachePath, farmhash.fingerprint32(entryPath) + '.bson');
+		let fpath = self.getFullPath(entryPath);
+		let cpath = self.getCachePath(entryPath);
 
 		options = _.defaults(options, {
 			parseMarkdown: true,
@@ -174,8 +174,8 @@ module.exports = {
 	/**
 	 * Gets the parent information.
 	 *
-	 * @param      {String}        entryPath  The entry path
-	 * @return     {Object|False}  The parent information.
+	 * @param      {String}                 entryPath  The entry path
+	 * @return     {Promise<Object|False>}  The parent information.
 	 */
 	getParentInfo(entryPath) {
 
@@ -183,10 +183,10 @@ module.exports = {
 
 		if(_.includes(entryPath, '/')) {
 
-			let parentParts = _.split(entryPath, '/');
-			let parentPath = _.join(_.initial(parentParts),'/');
+			let parentParts = _.initial(_.split(entryPath, '/'));
+			let parentPath = _.join(parentParts,'/');
 			let parentFile = _.last(parentParts);
-			let fpath = path.join(self._repoPath, parentPath + '.md');
+			let fpath = self.getFullPath(parentPath);
 
 			return fs.statAsync(fpath).then((st) => {
 				if(st.isFile()) {
@@ -210,6 +210,70 @@ module.exports = {
 			return Promise.reject(new Error('Parent entry is root.'));
 		}
 
+	},
+
+	/**
+	 * Gets the full original path of a document.
+	 *
+	 * @param      {String}  entryPath  The entry path
+	 * @return     {String}  The full path.
+	 */
+	getFullPath(entryPath) {
+		return path.join(this._repoPath, entryPath + '.md');
+	},
+
+	/**
+	 * Gets the full cache path of a document.
+	 *
+	 * @param      {String}    entryPath  The entry path
+	 * @return     {String}  The full cache path.
+	 */
+	getCachePath(entryPath) {
+		return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.bson');
+	},
+
+	/**
+	 * Update an existing document
+	 *
+	 * @param      {String}            entryPath  The entry path
+	 * @param      {String}            contents   The markdown-formatted contents
+	 * @return     {Promise<Boolean>}  True on success, false on failure
+	 */
+	update(entryPath, contents) {
+
+		let self = this;
+		let fpath = self.getFullPath(entryPath);
+
+		return fs.statAsync(fpath).then((st) => {
+			if(st.isFile()) {
+				return self.makePersistent(entryPath, contents).then(() => {
+					return self.fetchOriginal(entryPath, {});
+				});
+			} else {
+				return Promise.reject(new Error('Entry does not exist!'));
+			}
+		}).catch((err) => {
+			return new Error('Entry does not exist!');
+		});
+
+	},
+
+	/**
+	 * Makes a document persistent to disk and git repository
+	 *
+	 * @param      {String}            entryPath  The entry path
+	 * @param      {String}            contents   The markdown-formatted contents
+	 * @return     {Promise<Boolean>}  True on success, false on failure
+	 */
+	makePersistent(entryPath, contents) {
+
+		let self = this;
+		let fpath = self.getFullPath(entryPath);
+
+		return fs.outputFileAsync(fpath, contents).then(() => {
+			return git.commitDocument(entryPath);
+		});
+
 	}
 
 };

+ 35 - 9
models/git.js

@@ -131,6 +131,11 @@ module.exports = {
 
 	},
 
+	/**
+	 * Sync with the remote repository
+	 *
+	 * @return     {Promise}  Resolve on sync success
+	 */
 	resync() {
 
 		let self = this;
@@ -149,23 +154,20 @@ module.exports = {
 
 			// Check for changes
 
-			return self._git.exec('status').then((cProc) => {
+			return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
 				let out = cProc.stdout.toString();
-				if(!_.includes(out, 'nothing to commit')) {
 
-					// Add, commit and push
+				if(_.includes(out, 'commit')) {
 
 					winston.info('[GIT] Performing push to remote repository...');
-					return self._git.add('-A').then(() => {
-				    return self._git.commit("Resync");
-				  }).then(() => {
-				    return self._git.push('origin', self._repo.branch);
-				  }).then(() => {
+					return self._git.push('origin', self._repo.branch).then(() => {
 						return winston.info('[GIT] Push completed.');
 					});
 
 				} else {
-					winston.info('[GIT] Repository is already up to date. Nothing to commit.');
+
+					winston.info('[GIT] Repository is already in sync.');
+
 				}
 
 				return true;
@@ -178,6 +180,30 @@ module.exports = {
 			throw err;
 		});
 
+	},
+
+	/**
+	 * Commits a document.
+	 *
+	 * @param      {String}   entryPath  The entry path
+	 * @return     {Promise}  Resolve on commit success
+	 */
+	commitDocument(entryPath) {
+
+		let self = this;
+		let gitFilePath = entryPath + '.md';
+		let commitMsg = '';
+
+		return self._git.exec('ls-files', gitFilePath).then((cProc) => {
+			let out = cProc.stdout.toString();
+			return _.includes(out, gitFilePath);
+		}).then((isTracked) => {
+			commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath;
+			return self._git.add(gitFilePath);
+		}).then(() => {
+			return self._git.commit(commitMsg);
+		});
+
 	}
 
 };

+ 1 - 1
views/pages/edit.pug

@@ -21,7 +21,7 @@ block rootNavRight
 
 block content
 
-	#page-type-edit
+	#page-type-edit(data-entrypath=pageData.meta.path)
 		section.section.is-small
 			textarea#mk-editor= pageData.markdown
 

+ 3 - 3
views/pages/view.pug

@@ -26,7 +26,7 @@ block rootNavRight
 
 block content
 
-	#page-type-view
+	#page-type-view(data-entrypath=pageData.meta.path)
 		section.section
 			.container.is-fluid
 				.columns
@@ -70,9 +70,9 @@ block content
 						p.card-header-title Create New Page
 					.card-content
 						.content
-							label.label Enter the full path:
+							label.label Enter the new document name:
 							p.control
-								input.input(type='text', placeholder='/path', value='/storage/new-page')
+								input.input(type='text', placeholder='page-name')
 					footer.card-footer
 						a.card-footer-item(onclick='$(".modal").removeClass("is-active");') Discard
 						a.card-footer-item.featured Create

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff