diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ff040b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +bower_components +npm-debug.log diff --git a/README.md b/README.md index 3d8ae99..8faa092 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angular-pagedown -A pagedown editor for AngularJS. View this [Plunker](http://plnkr.co/edit/2LZiw454g77k6aE3HTyd) for demo. +A pagedown editor for AngularJS. View this [Plunker](http://plnkr.co/edit/m93lSn?p=preview) for demo. ## Instructions @@ -35,34 +35,39 @@ This package comes with 2 directives: ## Editor ```html - + ``` Options: -#### content +#### ng-model 1. An expression. 1. *Expression*: Mandatory -1. Example: `` -1. Example: `` +1. Example: `` +1. Example: `` #### show-preview 1. Should a live preview be displayed. 1. *Boolean*: Default to true +#### show-button-bar + +1. Should the button bar be displayed. +1. *Boolean*: Default to true + #### help 1. An expression to invoke upon clicking the help (?) button. 1. *Expression*: Default to open http://daringfireball.net/projects/markdown/syntax in new window -1. Example: `` +1. Example: `` #### insert-image 1. An expression to invoke upon clicking the "Insert Image" button. 1. *Expression*: Default to `noop` -1. Example: `` +1. Example: `` 1. The parent scope function `promptImageUrl` must return either: - A string of image URL. - A promise resolved with a string of image URL. @@ -71,15 +76,15 @@ Options: 1. An expression. 1. *Expression*: Default to empty string -1. Example: `` +1. Example: `` #### editor-class 1. An expression to use as [ngClass](https://docs.angularjs.org/api/ng/directive/ngClass). 1. *Expression*: Default to `wmd-input` -1. Example: `` -1. Example: `` -1. Example: `` +1. Example: `` +1. Example: `` +1. Example: `` #### editor-rows @@ -104,5 +109,4 @@ Options: ``` ## TODO -1. Grunt setup to minify files. 1. Extend PageDown editor to allow override of hyper link insertion. diff --git a/angular-pagedown.js b/angular-pagedown.js index 9eb962e..582bcd5 100644 --- a/angular-pagedown.js +++ b/angular-pagedown.js @@ -1,171 +1,176 @@ -// Mardown Extra Options -var mdExtraOptions = { - extensions: "all", - table_class: 'table' -}; - -// adapted from http://stackoverflow.com/a/20957476/940030 -angular.module("ui.pagedown", []) - .directive("pagedownEditor", ['$compile', '$timeout', '$window', '$q', function ($compile, $timeout, $window, $q) { - var nextId = 0; - var converter = Markdown.getSanitizingConverter(); - Markdown.Extra.init(converter, mdExtraOptions); - - converter.hooks.chain("preBlockGamut", function (text, rbg) { - return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) { - return "
" + rbg(inner) + "
\n"; - }); - }); - - return { - restrict: "E", - require: 'ngModel', - scope: { - ngModel: "=", - placeholder: "@", - showPreview: "@", - help: "&", - insertImage: "&", - editorClass: "=?", - editorRows: "@", - previewClass: "=?", - previewContent: "=?" - }, - link: function (scope, element, attrs, ngModel) { - - scope.changed = function () { - ngModel.$setDirty(); - scope.$parent.$eval(attrs.ngChange); - }; - - - var editorUniqueId; - - if (attrs.id == null) { - editorUniqueId = nextId++; - } else { - editorUniqueId = attrs.id; - } - - // just hide the preview, we still need it for "onPreviewRefresh" hook - var previewHiddenStyle = scope.showPreview == "false" ? "display: none;" : ""; - - var placeholder = attrs.placeholder || ""; - var editorRows = attrs.editorRows || "10"; - - var newElement = $compile( - '
' + - '
' + - '
' + - '' + - '
' + - '
' + - '
' + - '
')(scope); - - // html() doesn't work - element.append(newElement); - - var help = angular.isFunction(scope.help) ? scope.help : function () { - // redirect to the guide by default - $window.open("http://daringfireball.net/projects/markdown/syntax", "_blank"); - }; - - var editor = new Markdown.Editor(converter, "-" + editorUniqueId, { - handler: help - }); - - - var editorElement = angular.element(document.getElementById("wmd-input-" + editorUniqueId)); - - editorElement.val(scope.ngModel); - - converter.hooks.chain("postConversion", function (text) { - ngModel.$setViewValue(editorElement.val()); - - // update - scope.previewContent = text; - return text; +(function() { + "use strict"; + // Mardown Extra Options + var mdExtraOptions = { + extensions: 'all', + table_class: 'table' + }; + + // adapted from http://stackoverflow.com/a/20957476/940030 + angular.module('ui.pagedown', []) + .directive('pagedownEditor', ['$compile', '$timeout', '$window', '$q', function ($compile, $timeout, $window, $q) { + var nextId = 0; + var converter = Markdown.getSanitizingConverter(); + Markdown.Extra.init(converter, mdExtraOptions); + + converter.hooks.chain('preBlockGamut', function (text, rbg) { + return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) { + return '
' + rbg(inner) + '
\n'; }); + }); - - // add watch for content - if (scope.showPreview != "false") { - scope.$watch('content', function () { - editor.refreshPreview(); + return { + restrict: 'E', + require: 'ngModel', + scope: { + ngModel: '=', + placeholder: '@', + showPreview: '@', + showButtonBar: '@', + help: '&', + insertImage: '&', + editorClass: '=?', + editorRows: '@', + previewClass: '=?', + previewContent: '=?' + }, + link: function (scope, element, attrs, ngModel) { + + scope.changed = function () { + ngModel.$setDirty(); + scope.$parent.$eval(attrs.ngChange); + }; + + + var editorUniqueId; + + if (attrs.id === null) { + editorUniqueId = nextId++; + } else { + editorUniqueId = attrs.id; + } + + // just hide the preview, we still need it for 'onPreviewRefresh' hook + var previewHiddenStyle = scope.showPreview == 'false' ? 'display: none;' : ''; + var buttonBarHiddenStyle = scope.showButtonBar == 'false' ? 'display: none;' : ''; + + var placeholder = attrs.placeholder || ''; + var editorRows = attrs.editorRows || '10'; + + var newElement = $compile( + '
' + + '
' + + '
' + + '' + + '
' + + '
' + + '
' + + '
')(scope); + + // html() doesn't work + element.append(newElement); + + var help = angular.isFunction(scope.help) ? scope.help : function () { + // redirect to the guide by default + $window.open('http://daringfireball.net/projects/markdown/syntax', '_blank'); + }; + + var editor = new Markdown.Editor(converter, '-' + editorUniqueId, { + handler: help }); - } - editor.hooks.chain("onPreviewRefresh", function () { - // wire up changes caused by user interaction with the pagedown controls - // and do within $apply - $timeout(function () { - scope.content = editorElement.val(); + + + var editorElement = angular.element(document.getElementById('wmd-input-' + editorUniqueId)); + + editorElement.val(scope.ngModel); + + converter.hooks.chain('postConversion', function (text) { + ngModel.$setViewValue(editorElement.val()); + + // update + scope.previewContent = text; + return text; }); - }); - - if (angular.isFunction(scope.insertImage)) { - editor.hooks.set("insertImageDialog", function (callback) { - // expect it to return a promise or a url string - var result = scope.insertImage(); - - // Note that you cannot call the callback directly from the hook; you have to wait for the current scope to be exited. - // https://code.google.com/p/pagedown/wiki/PageDown#insertImageDialog + + + // add watch for content + if (scope.showPreview != 'false') { + scope.$watch('content', function () { + editor.refreshPreview(); + }); + } + editor.hooks.chain('onPreviewRefresh', function () { + // wire up changes caused by user interaction with the pagedown controls + // and do within $apply $timeout(function () { - if (!result) { - // must be null to indicate failure - callback(null); - } else { - // safe way to handle either string or promise - $q.when(result).then( - function success(imgUrl) { - callback(imgUrl); - }, - function error(reason) { - callback(null); - } - ); - } + scope.content = editorElement.val(); }); - - return true; }); - } - - editor.run(); - } - } - }]) - .directive("pagedownViewer", ['$compile', '$sce', function ($compile, $sce) { - var converter = Markdown.getSanitizingConverter(); - Markdown.Extra.init(converter, mdExtraOptions); - - return { - restrict: "E", - scope: { - content: "=" - }, - link: function (scope, element, attrs) { - var run = function run() { - if (!scope.content) { - scope.sanitizedContent = ''; - return; + + if (angular.isFunction(scope.insertImage)) { + editor.hooks.set('insertImageDialog', function (callback) { + // expect it to return a promise or a url string + var result = scope.insertImage(); + + // Note that you cannot call the callback directly from the hook; you have to wait for the current scope to be exited. + // https://code.google.com/p/pagedown/wiki/PageDown#insertImageDialog + $timeout(function () { + if (!result) { + // must be null to indicate failure + callback(null); + } else { + // safe way to handle either string or promise + $q.when(result).then( + function success(imgUrl) { + callback(imgUrl); + }, + function error(reason) { + callback(null); + } + ); + } + }); + + return true; + }); } - - scope.sanitizedContent = $sce.trustAsHtml(converter.makeHtml(scope.content)); - }; - - scope.$watch("content", run); - - run(); - - var newElementHtml = "

"; - var newElement = $compile(newElementHtml)(scope); - - element.append(newElement); - } - } - }]); + + editor.run(); + } + }; + }]) + .directive('pagedownViewer', ['$compile', '$sce', function ($compile, $sce) { + var converter = Markdown.getSanitizingConverter(); + Markdown.Extra.init(converter, mdExtraOptions); + + return { + restrict: 'E', + scope: { + content: '=' + }, + link: function (scope, element, attrs) { + var run = function run() { + if (!scope.content) { + scope.sanitizedContent = ''; + return; + } + + scope.sanitizedContent = $sce.trustAsHtml(converter.makeHtml(scope.content)); + }; + + scope.$watch('content', run); + + run(); + + var newElementHtml = '

'; + var newElement = $compile(newElementHtml)(scope); + + element.append(newElement); + } + }; + }]); +})(); diff --git a/angular-pagedown.min.css b/angular-pagedown.min.css index 2fbdd30..2cee674 100644 --- a/angular-pagedown.min.css +++ b/angular-pagedown.min.css @@ -1 +1 @@ -.wmd-button,.wmd-spacer{position:absolute;list-style:none}.wmd-button,.wmd-button>span,.wmd-spacer{display:inline-block}blockquote{border-left:2px dotted #888;padding-left:5px;background:#d0f0ff}.wmd-panel{min-width:500px}.wmd-button-bar{width:100%;background-color:Silver}.wmd-input{height:300px;width:100%;background-color:#dcdcdc;border:1px solid #a9a9a9}.wmd-preview{background-color:#c0e0ff}.wmd-button-row{position:relative;padding:0;height:20px;margin:10px 5px 5px}.wmd-spacer{width:1px;height:20px;margin-left:14px;background-color:Silver}.wmd-button{width:20px;height:20px;padding-left:2px;padding-right:3px;cursor:pointer}.wmd-button>span{background-image:url(../pagedown/wmd-buttons.png);background-repeat:no-repeat;background-position:0 0;width:20px;height:20px}.wmd-spacer1{left:50px}.wmd-spacer2{left:175px}.wmd-spacer3{left:300px}.wmd-prompt-background{background-color:#000}.wmd-prompt-dialog{border:1px solid #999;background-color:#F5F5F5}.wmd-prompt-dialog>div{font-size:.8em;font-family:arial,helvetica,sans-serif}.wmd-prompt-dialog>form>input[type=text]{border:1px solid #999;color:#000}.wmd-prompt-dialog>form>input[type=button]{border:1px solid #888;font-family:trebuchet MS,helvetica,sans-serif;font-size:.8em;font-weight:700} \ No newline at end of file +blockquote{border-left:2px dotted #888;padding-left:5px;background:#d0f0ff}.wmd-panel{min-width:500px}.wmd-button-bar{width:100%;background-color:Silver}.wmd-input{height:300px;width:100%;background-color:#dcdcdc;border:1px solid #a9a9a9}.wmd-preview{background-color:#c0e0ff}.wmd-button-row{position:relative;padding:0;height:20px;margin:10px 5px 5px}.wmd-button,.wmd-spacer{position:absolute;list-style:none}.wmd-spacer{width:1px;height:20px;margin-left:14px;background-color:Silver;display:inline-block}.wmd-button,.wmd-button>span{width:20px;height:20px;display:inline-block}.wmd-button{padding-left:2px;padding-right:3px;cursor:pointer}.wmd-button>span{background-image:url(../pagedown/wmd-buttons.png);background-repeat:no-repeat;background-position:0 0}.wmd-spacer1{left:50px}.wmd-spacer2{left:175px}.wmd-spacer3{left:300px}.wmd-prompt-background{background-color:#000}.wmd-prompt-dialog{border:1px solid #999;background-color:#F5F5F5}.wmd-prompt-dialog>div{font-size:.8em;font-family:arial,helvetica,sans-serif}.wmd-prompt-dialog>form>input[type=text]{border:1px solid #999;color:#000}.wmd-prompt-dialog>form>input[type=button]{border:1px solid #888;font-family:trebuchet MS,helvetica,sans-serif;font-size:.8em;font-weight:700} \ No newline at end of file diff --git a/angular-pagedown.min.js b/angular-pagedown.min.js index c6687fd..54641ee 100644 --- a/angular-pagedown.min.js +++ b/angular-pagedown.min.js @@ -1 +1 @@ -var mdExtraOptions={extensions:"all",table_class:"table"};angular.module("ui.pagedown",[]).directive("pagedownEditor",["$compile","$timeout","$window","$q",function(n,e,t,i){var o=0,r=Markdown.getSanitizingConverter();return Markdown.Extra.init(r,mdExtraOptions),r.hooks.chain("preBlockGamut",function(n,e){return n.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm,function(n,t){return"
"+e(t)+"
\n"})}),{restrict:"E",scope:{content:"=",placeholder:"@",showPreview:"@",help:"&",insertImage:"&",editorClass:"=?",editorRows:"@",previewClass:"=?",previewContent:"=?"},link:function(a,s,l){var d;d=null==l.id?o++:l.id;var c="false"==a.showPreview?"display: none;":"",u=l.placeholder||"",w=l.editorRows||"10",p=n('
")(a);s.append(p);var v=angular.isFunction(a.help)?a.help:function(){t.open("http://daringfireball.net/projects/markdown/syntax","_blank")},m=new Markdown.Editor(r,"-"+d,{handler:v});r.hooks.chain("postConversion",function(n){return a.previewContent=n,n});var h=angular.element(document.getElementById("wmd-input-"+d));"false"!=a.showPreview&&a.$watch("content",function(){m.refreshPreview()}),m.hooks.chain("onPreviewRefresh",function(){e(function(){a.content=h.val()})}),angular.isFunction(a.insertImage)&&m.hooks.set("insertImageDialog",function(n){var t=a.insertImage();return e(function(){t?i.when(t).then(function(e){n(e)},function(){n(null)}):n(null)}),!0}),m.run()}}}]).directive("pagedownViewer",["$compile","$sce",function(n,e){var t=Markdown.getSanitizingConverter();return Markdown.Extra.init(t,mdExtraOptions),{restrict:"E",scope:{content:"="},link:function(i,o){var r=function(){return i.content?void(i.sanitizedContent=e.trustAsHtml(t.makeHtml(i.content))):void(i.sanitizedContent="")};i.$watch("content",r),r();var a="

",s=n(a)(i);o.append(s)}}}]); \ No newline at end of file +!function(){"use strict";var n={extensions:"all",table_class:"table"};angular.module("ui.pagedown",[]).directive("pagedownEditor",["$compile","$timeout","$window","$q",function(e,t,i,o){var a=0,r=Markdown.getSanitizingConverter();return Markdown.Extra.init(r,n),r.hooks.chain("preBlockGamut",function(n,e){return n.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm,function(n,t){return"
"+e(t)+"
\n"})}),{restrict:"E",require:"ngModel",scope:{ngModel:"=",placeholder:"@",showPreview:"@",showButtonBar:"@",help:"&",insertImage:"&",editorClass:"=?",editorRows:"@",previewClass:"=?",previewContent:"=?"},link:function(n,l,s,d){n.changed=function(){d.$setDirty(),n.$parent.$eval(s.ngChange)};var c;c=null===s.id?a++:s.id;var u="false"==n.showPreview?"display: none;":"",w="false"==n.showButtonBar?"display: none;":"",p=s.placeholder||"",v=s.editorRows||"10",g=e('
")(n);l.append(g);var h=angular.isFunction(n.help)?n.help:function(){i.open("http://daringfireball.net/projects/markdown/syntax","_blank")},m=new Markdown.Editor(r,"-"+c,{handler:h}),f=angular.element(document.getElementById("wmd-input-"+c));f.val(n.ngModel),r.hooks.chain("postConversion",function(e){return d.$setViewValue(f.val()),n.previewContent=e,e}),"false"!=n.showPreview&&n.$watch("content",function(){m.refreshPreview()}),m.hooks.chain("onPreviewRefresh",function(){t(function(){n.content=f.val()})}),angular.isFunction(n.insertImage)&&m.hooks.set("insertImageDialog",function(e){var i=n.insertImage();return t(function(){i?o.when(i).then(function(n){e(n)},function(n){e(null)}):e(null)}),!0}),m.run()}}}]).directive("pagedownViewer",["$compile","$sce",function(e,t){var i=Markdown.getSanitizingConverter();return Markdown.Extra.init(i,n),{restrict:"E",scope:{content:"="},link:function(n,o,a){var r=function(){return n.content?void(n.sanitizedContent=t.trustAsHtml(i.makeHtml(n.content))):void(n.sanitizedContent="")};n.$watch("content",r),r();var l='

',s=e(l)(n);o.append(s)}}}])}(); \ No newline at end of file diff --git a/bower.json b/bower.json index 794b5d6..d4f55e5 100644 --- a/bower.json +++ b/bower.json @@ -1,12 +1,12 @@ { "name": "angular-pagedown", - "version": "0.4.4", + "version": "0.4.5", "main": [ "angular-pagedown.css", "angular-pagedown.js" ], "dependencies": { - "angular": "~1.2", + "angular": "~1.4", "pagedown": "^1.1.0" } } diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..eea2a79 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,34 @@ + + + + + + angular-pagedown demo + + + + + + + + + + + + + +
+ + +
+
+
+

This is an independent viewer

+ +
+ + \ No newline at end of file diff --git a/demo/script.js b/demo/script.js new file mode 100644 index 0000000..c906711 --- /dev/null +++ b/demo/script.js @@ -0,0 +1,28 @@ +angular.module("myApp", ["ui.pagedown"]) + +.controller("myController", function($scope, $window, $q) { + + $scope.data = { + content: "Wee *wang* **wang**!", + placeholder: "Enter something here.." + }; + + $scope.showSomeHelp = function showSomeHelp() { + // this is what the default will do + $window.open("http://daringfireball.net/projects/markdown/syntax", "_blank"); + }; + + $scope.insertImage = function insertImage() { + var deferred = $q.defer(); + + // or you can return a string straightaway + deferred.resolve("http://www.discoposse.com/wp-content/uploads/2014/08/test-all-the-things.jpg"); + + return deferred.promise; + }; + + $scope.shout = function shout() { + // test update the content programatically + $scope.data.content += '\n\nTell Laura I love her'; + }; +}); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..971e274 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,47 @@ +var gulp = require('gulp'); + +var jshint = require('gulp-jshint'); +var uglify = require('gulp-uglify'); +var rename = require('gulp-rename'); +var minifyCss = require('gulp-minify-css'); +var browserSync = require('browser-sync').create(); + +gulp.task('lint', function() { + return gulp.src('angular-pagedown.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')); +}); + +gulp.task('scripts', function() { + return gulp.src('angular-pagedown.js') + .pipe(rename('angular-pagedown.min.js')) + .pipe(uglify()) + .pipe(gulp.dest('./')); +}); + +gulp.task('styles', function() { + return gulp.src('angular-pagedown.css') + .pipe(rename('angular-pagedown.min.css')) + .pipe(minifyCss({compatibility: 'ie8'})) + .pipe(gulp.dest('./')); +}); + +gulp.task('browser-sync', function() { + browserSync.init({ + server: { + baseDir: ["./", "demo"], + routes: { + "/bower_components": "bower_components" + } + } + }); +}); + +gulp.task('watch', function() { + gulp.watch('angular-pagedown.js', ['lint', 'scripts']); + gulp.watch('angular-pagedown.css', ['styles']); + gulp.watch(['demo/*.html', 'angular-pagedown.min.js', 'angular-pagedown.min.css']).on("change", browserSync.reload); +}); + +gulp.task('default', ['lint', 'scripts', 'styles', 'watch']); +gulp.task('serve', ['lint', 'scripts', 'styles', 'browser-sync', 'watch']); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5eae5e4 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "angular-pagedown", + "private": true, + "devDependencies": { + "browser-sync": "^2.10.0", + "gulp": "^3.9.0", + "gulp-jshint": "^2.0.0", + "gulp-minify-css": "^1.2.1", + "gulp-rename": "^1.2.2", + "gulp-uglify": "^1.5.1", + "jshint": "^2.8.0" + } +}