瀏覽代碼

Added move document feature

NGPixel 8 年之前
父節點
當前提交
492bb9efa6

+ 1 - 1
README.md

@@ -23,8 +23,8 @@
 - [x] Create Entry
 - [x] Create Entry
 - [x] Edit Entry
 - [x] Edit Entry
 - [x] Git Management
 - [x] Git Management
-- [ ] History Management
 - [ ] Markdown Editor
 - [ ] Markdown Editor
+- [x] Move Entry
 - [x] Navigation
 - [x] Navigation
 - [x] Parsing / Tree / Metadata
 - [x] Parsing / Tree / Metadata
 - [x] Search
 - [x] Search

File diff suppressed because it is too large
+ 0 - 0
assets/css/app.css


File diff suppressed because it is too large
+ 0 - 0
assets/js/app.js


+ 29 - 0
client/js/modals/create.js

@@ -0,0 +1,29 @@
+
+//-> Create New Document
+
+let suggestedCreatePath = currentBasePath + '/new-page';
+
+$('.btn-create-prompt').on('click', (ev) => {
+	$('#txt-create-prompt').val(suggestedCreatePath);
+	$('#modal-create-prompt').toggleClass('is-active');
+	setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length);
+	$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden');
+});
+
+$('#txt-create-prompt').on('keypress', (ev) => {
+	if(ev.which === 13) {
+		$('.btn-create-go').trigger('click');
+	}
+});
+
+$('.btn-create-go').on('click', (ev) => {
+
+	let newDocPath = makeSafePath($('#txt-create-prompt').val());
+	if(_.isEmpty(newDocPath)) {
+		$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden');
+	} else {
+		$('#txt-create-prompt').parent().addClass('is-loading');
+		window.location.assign('/create/' + newDocPath);
+	}
+
+});

+ 49 - 0
client/js/modals/move.js

@@ -0,0 +1,49 @@
+
+//-> Move Existing Document
+
+if(currentBasePath !== '') {
+	$('.btn-move-prompt').removeClass('is-hidden');
+}
+
+let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1;
+
+$('.btn-move-prompt').on('click', (ev) => {
+	$('#txt-move-prompt').val(currentBasePath);
+	$('#modal-move-prompt').toggleClass('is-active');
+	setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length);
+	$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden');
+});
+
+$('#txt-move-prompt').on('keypress', (ev) => {
+	if(ev.which === 13) {
+		$('.btn-move-go').trigger('click');
+	}
+});
+
+$('.btn-move-go').on('click', (ev) => {
+
+	let newDocPath = makeSafePath($('#txt-move-prompt').val());
+	if(_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
+		$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden');
+	} else {
+		$('#txt-move-prompt').parent().addClass('is-loading');
+
+		$.ajax(window.location.href, {
+			data: {
+				move: newDocPath
+			},
+			dataType: 'json',
+			method: 'PUT'
+		}).then((rData, rStatus, rXHR) => {
+			if(rData.ok) {
+				window.location.assign('/' + newDocPath);
+			} else {
+				alerts.pushError('Something went wrong', rData.error);
+			}
+		}, (rXHR, rStatus, err) => {
+			alerts.pushError('Something went wrong', 'Save operation failed.');
+		});
+
+	}
+
+});

+ 5 - 0
client/js/pages/source.js

@@ -7,4 +7,9 @@ if($('#page-type-source').length) {
   scEditor.setReadOnly(true);
   scEditor.setReadOnly(true);
   scEditor.renderer.updateFull();
   scEditor.renderer.updateFull();
 
 
+  let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : '';
+
+  //=include ../modals/create.js
+	//=include ../modals/move.js
+
 }
 }

+ 3 - 28
client/js/pages/view.js

@@ -1,34 +1,9 @@
 
 
 if($('#page-type-view').length) {
 if($('#page-type-view').length) {
 
 
-	let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') + '/' : '';
-	let suggestedCreatePath = currentBasePath + 'new-page';
+	let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : '';
 
 
-	//-> Create New Document
-
-	$('.btn-create-prompt').on('click', (ev) => {
-		$('#txt-create-prompt').val(suggestedCreatePath);
-		$('#modal-create-prompt').toggleClass('is-active');
-		setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length, suggestedCreatePath.length);
-		$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden');
-	});
-
-	$('#txt-create-prompt').on('keypress', (ev) => {
-		if(ev.which === 13) {
-			$('.btn-create-go').trigger('click');
-		}
-	});
-
-	$('.btn-create-go').on('click', (ev) => {
-
-		let newDocPath = makeSafePath($('#txt-create-prompt').val());
-		if(_.isEmpty(newDocPath)) {
-			$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden');
-		} else {
-			$('#txt-create-prompt').parent().addClass('is-loading');
-			window.location.assign('/create/' + newDocPath);
-		}
-
-	});
+	//=include ../modals/create.js
+	//=include ../modals/move.js
 
 
 }
 }

+ 4 - 0
client/scss/layout/_content.scss

@@ -127,6 +127,10 @@ p code {
 		background-color: $red;
 		background-color: $red;
 	}
 	}
 
 
+	&.is-info {
+		background-color: $purple;
+	}
+
 }
 }
 
 
 .card-header-title {
 .card-header-title {

+ 29 - 0
controllers/pages.js

@@ -174,4 +174,33 @@ router.get('/*', (req, res, next) => {
 
 
 });
 });
 
 
+/**
+ * Move document
+ */
+router.put('/*', (req, res, next) => {
+
+	let safePath = entries.parsePath(req.path);
+
+	if(_.isEmpty(req.body.move)) {
+		return res.json({
+			ok: false,
+			error: 'Invalid document action call.'
+		});
+	}
+
+	let safeNewPath = entries.parsePath(req.body.move);
+
+	entries.move(safePath, safeNewPath).then(() => {
+		res.json({
+			ok: true
+		});
+	}).catch((err) => {
+		res.json({
+			ok: false,
+			error: err.message
+		});
+	});
+
+});
+
 module.exports = router;
 module.exports = router;

+ 17 - 0
models/entries.js

@@ -376,7 +376,24 @@ module.exports = {
 
 
 	},
 	},
 
 
+	/**
+	 * Move a document
+	 *
+	 * @param      {String}   entryPath     The current entry path
+	 * @param      {String}   newEntryPath  The new entry path
+	 * @return     {Promise}  Promise of the operation
+	 */
+	move(entryPath, newEntryPath) {
+
+		let self = this;
 
 
+		return git.moveDocument(entryPath, newEntryPath).then(() => {
+			return git.commitDocument(newEntryPath).then(() => {
+				return self.updateCache(newEntryPath);
+			});
+		});
+
+	},
 
 
 	/**
 	/**
 	 * Generate a starter page content based on the entry path
 	 * Generate a starter page content based on the entry path

+ 24 - 0
models/git.js

@@ -200,6 +200,30 @@ module.exports = {
 			});
 			});
 		});
 		});
 
 
+	},
+
+	/**
+	 * Move a document.
+	 *
+	 * @param      {String}            entryPath     The current entry path
+	 * @param      {String}            newEntryPath  The new entry path
+	 * @return     {Promise<Boolean>}  Resolve on success
+	 */
+	moveDocument(entryPath, newEntryPath) {
+
+		let self = this;
+		let gitFilePath = entryPath + '.md';
+		let gitNewFilePath = newEntryPath + '.md';
+
+		return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
+			let out = cProc.stdout.toString();
+			if(_.includes(out, 'fatal')) {
+				let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')));
+				throw new Error(errorMsg);
+			}
+			return true;
+		})
+
 	}
 	}
 
 
 };
 };

+ 0 - 13
views/common/header.pug

@@ -19,19 +19,6 @@
 		.nav-right.nav-menu
 		.nav-right.nav-menu
 			block rootNavRight
 			block rootNavRight
 				i.nav-item#notifload
 				i.nav-item#notifload
-				a.nav-item(href='/history/' + pageData.meta.path)
-					| History
-				a.nav-item(href='/source/' + pageData.meta.path)
-					| Source
-				span.nav-item
-					a.button(href='/edit/' + pageData.meta.path)
-						span.icon
-							i.fa.fa-edit
-						span Edit
-					a.button.is-primary.btn-create-prompt
-						span.icon
-							i.fa.fa-plus
-						span Create
 
 
 	.box.searchresults.animated(v-show='searchactive', transition='slide', v-cloak, style={'display':'none'})
 	.box.searchresults.animated(v-show='searchactive', transition='slide', v-cloak, style={'display':'none'})
 		.menu
 		.menu

+ 18 - 0
views/modals/move.pug

@@ -0,0 +1,18 @@
+
+.modal#modal-move-prompt
+	.modal-background
+	.modal-container
+		.modal-content
+			.card.is-fullwidth
+				header.card-header.is-info
+					p.card-header-title Move document
+				.card-content
+					.content
+						label.label Enter the new document path:
+						p.control
+							input.input(type='text', placeholder='page-name')#txt-move-prompt
+							span.help.is-danger.is-hidden This document path is invalid or not allowed!
+						span Note that moving or renaming documents can lead to broken links. Make sure to edit any page that links to this document afterwards!
+				footer.card-footer
+					a.card-footer-item.btn-move-prompt Discard
+					a.card-footer-item.btn-move-go Move

+ 2 - 5
views/pages/edit.pug

@@ -5,10 +5,6 @@ block rootNavCenter
 
 
 block rootNavRight
 block rootNavRight
 	i.nav-item#notifload
 	i.nav-item#notifload
-	a.nav-item(href='/history/' + pageData.meta.path, target='_blank')
-		| History
-	a.nav-item(href='/source/' + pageData.meta.path, target='_blank')
-		| Source
 	span.nav-item
 	span.nav-item
 		a.button.is-warning.btn-edit-discard
 		a.button.is-warning.btn-edit-discard
 			span.icon
 			span.icon
@@ -24,4 +20,5 @@ block content
 	#page-type-edit(data-entrypath=pageData.meta.path)
 	#page-type-edit(data-entrypath=pageData.meta.path)
 		textarea#mk-editor= pageData.markdown
 		textarea#mk-editor= pageData.markdown
 
 
-	include ../modals/edit.pug
+	include ../modals/edit.pug
+	include ../modals/move.pug

+ 21 - 1
views/pages/source.pug

@@ -1,9 +1,29 @@
 extends ../layout.pug
 extends ../layout.pug
 
 
+block rootNavCenter
+	h2.nav-item= pageData.meta.title
+
+block rootNavRight
+	i.nav-item#notifload
+	a.nav-item.btn-move-prompt.is-hidden
+		| Move
+	a.nav-item(href='/' + pageData.meta.path)
+		| Normal View
+	span.nav-item
+		a.button(href='/edit/' + pageData.meta.path)
+			span.icon
+				i.fa.fa-edit
+			span Edit
+		a.button.is-primary.btn-create-prompt
+			span.icon
+				i.fa.fa-plus
+			span Create
+
 block content
 block content
 
 
 	#page-type-source(data-entrypath=pageData.meta.path)
 	#page-type-source(data-entrypath=pageData.meta.path)
 		.ace-container
 		.ace-container
 			#source-display= pageData.markdown
 			#source-display= pageData.markdown
 
 
-	include ../modals/create
+	include ../modals/create.pug
+	include ../modals/move.pug

+ 3 - 2
views/pages/view.pug

@@ -10,8 +10,8 @@ mixin tocMenu(ti)
 
 
 block rootNavRight
 block rootNavRight
 	i.nav-item#notifload
 	i.nav-item#notifload
-	a.nav-item(href='/history/' + pageData.meta.path)
-		| History
+	a.nav-item.btn-move-prompt.is-hidden
+		| Move
 	a.nav-item(href='/source/' + pageData.meta.path)
 	a.nav-item(href='/source/' + pageData.meta.path)
 		| Source
 		| Source
 	span.nav-item
 	span.nav-item
@@ -62,3 +62,4 @@ block content
 							!= pageData.html
 							!= pageData.html
 
 
 	include ../modals/create.pug
 	include ../modals/create.pug
+	include ../modals/move.pug

Some files were not shown because too many files changed in this diff