angular-ui-tinymce-rails 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/angular-ui-tinymce/rails/version.rb +1 -1
- data/node_modules/angular-ui-tinymce/CONTRIBUTING.md +45 -0
- data/node_modules/angular-ui-tinymce/LICENSE +21 -0
- data/node_modules/angular-ui-tinymce/README.md +150 -0
- data/node_modules/angular-ui-tinymce/bower.json +25 -0
- data/node_modules/angular-ui-tinymce/demo/demo.html +23 -0
- data/node_modules/angular-ui-tinymce/demo/demo.js +8 -0
- data/node_modules/angular-ui-tinymce/dist/tinymce.min.js +1 -0
- data/node_modules/angular-ui-tinymce/gruntFile.js +47 -0
- data/node_modules/angular-ui-tinymce/package.json +28 -0
- data/node_modules/angular-ui-tinymce/src/tinymce.js +235 -0
- data/node_modules/angular-ui-tinymce/test/karma.conf.js +18 -0
- data/node_modules/angular-ui-tinymce/test/tinymce.spec.js +184 -0
- metadata +13 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10c22b42174550252d4e0567c9d6f06a4409d861
|
4
|
+
data.tar.gz: 5524bd651b70166a379e701d2360f3f26ec3b1fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0c69c359a3caf9622ec26b84f8fb0eff75754624987ce6a01c793d9ebb39bd65aaad2865aa4b23e3c2a9d1ffed095ca992f9cc8907ff98de8dfd86740883b89
|
7
|
+
data.tar.gz: a11d9e4d8dc8f9105b42ad36c9ef539550540250475f150b49c5653d333f7c45e96fbd8cddc8cc51d156fd29f0880f7d2bcb21eea170b8a35f0e20736d709cc1
|
@@ -0,0 +1,45 @@
|
|
1
|
+
## Got a question or problem?
|
2
|
+
|
3
|
+
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](http://stackoverflow.com/questions/tagged/angular-ui-tinymce) where maintainers are looking at questions questions tagged with `angular-ui-tinymce`.
|
4
|
+
|
5
|
+
StackOverflow is a much better place to ask questions since:
|
6
|
+
* there are hundreds of people willing to help on StackOverflow
|
7
|
+
* questions and answers stay available for public viewing so your question / answer might help someone else
|
8
|
+
* SO voting system assures that the best answers are prominently visible.
|
9
|
+
|
10
|
+
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.
|
11
|
+
|
12
|
+
## You think you've found a bug?
|
13
|
+
|
14
|
+
Oh, we are ashamed and want to fix it asap! But before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a _minimal_ reproduce scenario using http://plnkr.co/. Having a live reproduce scenario gives us wealth of important information without going back & forth to you with additional questions like:
|
15
|
+
* version of AngularJS used
|
16
|
+
* version of this library that you are using
|
17
|
+
* 3rd-party libraries used, if any
|
18
|
+
* and most importantly - a use-case that fails
|
19
|
+
|
20
|
+
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem.
|
21
|
+
|
22
|
+
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
23
|
+
|
24
|
+
Unfortunately we are not able to investigate / fix bugs without a minimal reproduce scenario using http://plnkr.co/, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
|
25
|
+
|
26
|
+
|
27
|
+
## You want to contribute some code?
|
28
|
+
|
29
|
+
We are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules:
|
30
|
+
|
31
|
+
* Please open all pull requests against the `master` branch.
|
32
|
+
* Please assure that you are submitting quality code, specifically make sure that:
|
33
|
+
* You have accompanying tests and all the tests are passing. See testing below.
|
34
|
+
* Your PR doesn't break the build; check the Travis-CI build status after opening a PR and push corrective commits if anything goes wrong
|
35
|
+
* You are using 2 space indentation
|
36
|
+
* Your commits conform to the conventions established [here](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)
|
37
|
+
|
38
|
+
|
39
|
+
## Testing
|
40
|
+
To run tests:
|
41
|
+
```bash
|
42
|
+
$ npm install
|
43
|
+
$ bower install
|
44
|
+
$ grunt
|
45
|
+
```
|
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# UI Tinymce - [AngularJS](http://angularjs.org/) directive for [TinyMCE](http://tinymce.com).
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/angular-ui/ui-tinymce.png)](https://travis-ci.org/angular-ui/ui-tinymce)
|
4
|
+
[![Join the chat at https://gitter.im/angular-ui/ui-tinymce](https://badges.gitter.im/angular-ui/ui-tinymce.svg)](https://gitter.im/angular-ui/ui-tinymce?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
5
|
+
|
6
|
+
# Call for Maintainer
|
7
|
+
|
8
|
+
This library is looking for a maintainer. If you feel up to the task please open an issue indicating so.
|
9
|
+
|
10
|
+
# Requirements
|
11
|
+
|
12
|
+
- AngularJS 1.4.x or higher and it has been tested with Angular 1.4.8.
|
13
|
+
- TinyMCE 4
|
14
|
+
|
15
|
+
# Testing
|
16
|
+
|
17
|
+
We use karma and jshint to ensure the quality of the code. The easiest way to run these checks is to use grunt:
|
18
|
+
```
|
19
|
+
npm install -g grunt-cli
|
20
|
+
npm install
|
21
|
+
bower install
|
22
|
+
grunt
|
23
|
+
```
|
24
|
+
|
25
|
+
The karma task will try to open Chrome as a browser in which to run the tests. Make sure this is available or change the configuration in `test\test.config.js`
|
26
|
+
|
27
|
+
# Usage
|
28
|
+
|
29
|
+
We use [bower](http://twitter.github.com/bower/) for dependency management. Add
|
30
|
+
|
31
|
+
```
|
32
|
+
dependencies: {
|
33
|
+
"angular-ui-tinymce": "latest"
|
34
|
+
}
|
35
|
+
```
|
36
|
+
|
37
|
+
To your `bower.json` file. Then run
|
38
|
+
|
39
|
+
```
|
40
|
+
bower install
|
41
|
+
```
|
42
|
+
|
43
|
+
This will copy the ui-tinymce files into your `components` folder, along with its dependencies. Load the script files in your application:
|
44
|
+
|
45
|
+
```html
|
46
|
+
<script type="text/javascript" src="app/bower_components/tinymce/tinymce.js"></script>
|
47
|
+
<script type="text/javascript" src="app/bower_components/angular/angular.js"></script>
|
48
|
+
<script type="text/javascript" src="app/bower_components/angular-ui-tinymce/src/tinymce.js"></script>
|
49
|
+
```
|
50
|
+
|
51
|
+
Add the tinymce module as a dependency to your application module:
|
52
|
+
|
53
|
+
```javascript
|
54
|
+
var myAppModule = angular.module('MyApp', ['ui.tinymce'])
|
55
|
+
```
|
56
|
+
|
57
|
+
Apply the directive to your form elements:
|
58
|
+
|
59
|
+
```html
|
60
|
+
<form method="post">
|
61
|
+
<textarea ui-tinymce ng-model="tinymceModel"></textarea>
|
62
|
+
</form>
|
63
|
+
```
|
64
|
+
|
65
|
+
**Be sure not to set an `id` attribute**. This is because the directive needs to maintain selector knowledge in order to handle buggy behavior in TinyMCE when DOM manipulation is involved, such as in a reordering of HTML through ng-repeat or DOM destruction/recreation through ng-if.
|
66
|
+
|
67
|
+
When using other directives which do DOM manipulation involving elements with `ui-tinymce`, you may need to re-render the editor due to this buggy behavior with TinyMCE. For those situations, it is recommended to use the `$tinymce:refresh` event, which will handle re-rendering the editor to fix this problem.
|
68
|
+
|
69
|
+
## Working with ng-model
|
70
|
+
|
71
|
+
The ui-tinymce directive plays nicely with the ng-model directive such as ng-required.
|
72
|
+
|
73
|
+
If you add the ng-model directive to same the element as ui-tinymce then the text in the editor is automatically synchronized with the model value.
|
74
|
+
|
75
|
+
_The ui-tinymce directive stores the configuration options as specified in the [TinyMCE documentation](http://www.tinymce.com/wiki.php/Configuration) and expects the model value to be a html string or raw text, depending on whether `raw` is `true` (default value is `false`)._
|
76
|
+
|
77
|
+
**Note:** Make sure you using scopes correctly by following [this wiki page](https://github.com/angular/angular.js/wiki/Understanding-Scopes). If you are having issues with your model not updating, make sure you have a '.' in your model.
|
78
|
+
|
79
|
+
> This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.
|
80
|
+
|
81
|
+
## Options
|
82
|
+
|
83
|
+
The directive supports all of the standard TinyMCE initialization options as listed [here](http://www.tinymce.com/wiki.php/Configuration).
|
84
|
+
|
85
|
+
Use the [setup](https://www.tinymce.com/docs/configure/integration-and-setup/#setup) function to bind different events:
|
86
|
+
|
87
|
+
```javascript
|
88
|
+
scope.tinymceOptions = {
|
89
|
+
setup: function(editor) {
|
90
|
+
//Focus the editor on load
|
91
|
+
$timeout(function(){ editor.focus(); });
|
92
|
+
editor.on("init", function() {
|
93
|
+
...
|
94
|
+
});
|
95
|
+
editor.on("click", function() {
|
96
|
+
...
|
97
|
+
});
|
98
|
+
}
|
99
|
+
};
|
100
|
+
```
|
101
|
+
By default all TinyMCE content that is set to `ngModel` will be whitelisted by `$sce`.
|
102
|
+
|
103
|
+
In addition, it supports these additional optional options
|
104
|
+
|
105
|
+
- `format` Format to get content as, i.e. 'raw' for raw HTML, or 'text' for text only. Defaults to 'html'. Documentation [here](http://www.tinymce.com/wiki.php/api4:method.tinymce.Editor.getContent)
|
106
|
+
- `baseURL` This will set [baseURL property on the EditorManager](https://www.tinymce.com/docs/api/class/tinymce.editormanager/)
|
107
|
+
- `debounce` This will debounce the model update which helps with performance of editors with large text. Defaults to true.
|
108
|
+
|
109
|
+
This option is only supported when present on the `uiTinymceConfig` global injectable - this injectable needs to be an object.
|
110
|
+
|
111
|
+
- `baseUrl` Sets the base url used by tinymce asset loading
|
112
|
+
|
113
|
+
```javascript
|
114
|
+
myAppModule.controller('MyController', function($scope) {
|
115
|
+
$scope.tinymceOptions = {
|
116
|
+
onChange: function(e) {
|
117
|
+
// put logic here for keypress and cut/paste changes
|
118
|
+
},
|
119
|
+
inline: false,
|
120
|
+
plugins : 'advlist autolink link image lists charmap print preview',
|
121
|
+
skin: 'lightgray',
|
122
|
+
theme : 'modern'
|
123
|
+
};
|
124
|
+
});
|
125
|
+
```
|
126
|
+
```html
|
127
|
+
<form method="post">
|
128
|
+
<textarea ui-tinymce="tinymceOptions" ng-model="tinymceModel"></textarea>
|
129
|
+
</form>
|
130
|
+
```
|
131
|
+
|
132
|
+
## Testing your Application (Protractor)
|
133
|
+
|
134
|
+
If you are testing your application using Protractor and you wish to be able to automate the
|
135
|
+
contribution of rich text content as part of the tests, use the TinyMCE API method `insertContent`
|
136
|
+
in conjunction with the WebDriver's `executeScript` method, like this:
|
137
|
+
|
138
|
+
```javascript
|
139
|
+
browser.driver.executeScript("tinyMCE.activeEditor.insertContent('This is <em>RICH</em> content')");
|
140
|
+
```
|
141
|
+
|
142
|
+
Note that if you use the TinyMCE API method `setContent`, this will fail to update the Angular model
|
143
|
+
with the entered content, so use `insertContent` instead.
|
144
|
+
|
145
|
+
----
|
146
|
+
|
147
|
+
|
148
|
+
# Contributing to the project
|
149
|
+
|
150
|
+
We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
"name": "angular-ui-tinymce",
|
3
|
+
"version": "0.0.18",
|
4
|
+
"description": "This directive allows you to TinyMCE in your form.",
|
5
|
+
"author": "https://github.com/angular-ui/ui-tinymce/graphs/contributors",
|
6
|
+
"license": "MIT",
|
7
|
+
"homepage": "http://angular-ui.github.com",
|
8
|
+
"main": "./src/tinymce.js",
|
9
|
+
"ignore": [
|
10
|
+
"**/.*",
|
11
|
+
"node_modules",
|
12
|
+
"components",
|
13
|
+
"test*",
|
14
|
+
"demo*",
|
15
|
+
"gruntFile.js",
|
16
|
+
"package.json"
|
17
|
+
],
|
18
|
+
"dependencies": {
|
19
|
+
"angular": ">=1.4.0",
|
20
|
+
"tinymce": "~4.5.1"
|
21
|
+
},
|
22
|
+
"devDependencies": {
|
23
|
+
"angular-mocks": "~1.4.x"
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
3
|
+
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
4
|
+
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
5
|
+
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
6
|
+
<head>
|
7
|
+
<meta charset="utf-8">
|
8
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
9
|
+
<title>AngularUI - TinyMCE Demo</title>
|
10
|
+
<script type="text/javascript" src="../bower_components/tinymce/tinymce.min.js"></script>
|
11
|
+
<script type="text/javascript" src="../bower_components/angular/angular.js"></script>
|
12
|
+
<script type="text/javascript" src="../src/tinymce.js"></script>
|
13
|
+
<script type="text/javascript" src="demo.js"></script>
|
14
|
+
</head>
|
15
|
+
<body ng-app="ui.tinymce" ng-controller="DemoCtrl as demo">
|
16
|
+
<form method="post">
|
17
|
+
<textarea ui-tinymce
|
18
|
+
ng-model="demo.tinymce"
|
19
|
+
ng-change="demo.updateHtml()"></textarea>
|
20
|
+
</form>
|
21
|
+
<div ng-bind-html="demo.tinymceHtml"></div>
|
22
|
+
</body>
|
23
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
angular.module("ui.tinymce",[]).value("uiTinymceConfig",{}).directive("uiTinymce",["$rootScope","$compile","$timeout","$window","$sce","uiTinymceConfig","uiTinymceService",function(a,b,c,d,e,f,g){return f=f||{},f.baseUrl&&(tinymce.baseURL=f.baseUrl),{require:["ngModel","^?form"],priority:599,link:function(h,i,j,k){function l(a){a?(m(),o&&o.getBody().setAttribute("contenteditable",!1)):(m(),o&&!o.settings.readonly&&o.getDoc()&&o.getBody().setAttribute("contenteditable",!0))}function m(){o||(o=tinymce.get(j.id))}if(d.tinymce){var n,o,p=k[0],q=k[1]||null,r={debounce:!0},s=function(b){var c=b.getContent({format:r.format}).trim();c=e.trustAsHtml(c),p.$setViewValue(c),a.$$phase||h.$digest()},t=g.getUniqueId();j.$set("id",t),n={},angular.extend(n,h.$eval(j.uiTinymce));var u=function(a){var b;return function(d){c.cancel(b),b=c(function(){return function(a){a.isDirty()&&(a.save(),s(a))}(d)},a)}}(400),v={setup:function(b){b.on("init",function(){p.$render(),p.$setPristine(),p.$setUntouched(),q&&q.$setPristine()}),b.on("ExecCommand change NodeChange ObjectResized",function(){return r.debounce?void u(b):(b.save(),void s(b))}),b.on("blur",function(){i[0].blur(),p.$setTouched(),a.$$phase||h.$digest()}),b.on("remove",function(){i.remove()}),f.setup&&f.setup(b,{updateView:s}),n.setup&&n.setup(b,{updateView:s})},format:n.format||"html",selector:"#"+j.id};angular.extend(r,f,n,v),c(function(){r.baseURL&&(tinymce.baseURL=r.baseURL);var a=tinymce.init(r);a&&"function"==typeof a.then?a.then(function(){l(h.$eval(j.ngDisabled))}):l(h.$eval(j.ngDisabled))}),p.$formatters.unshift(function(a){return a?e.trustAsHtml(a):""}),p.$parsers.unshift(function(a){return a?e.getTrustedHtml(a):""}),p.$render=function(){m();var a=p.$viewValue?e.getTrustedHtml(p.$viewValue):"";o&&o.getDoc()&&(o.setContent(a),o.fire("change"))},j.$observe("disabled",l),h.$on("$tinymce:refresh",function(a,c){var d=j.id;if(angular.isUndefined(c)||c===d){var e=i.parent(),f=i.clone();f.removeAttr("id"),f.removeAttr("style"),f.removeAttr("aria-hidden"),tinymce.execCommand("mceRemoveEditor",!1,d),e.append(b(f)(h))}}),h.$on("$destroy",function(){m(),o&&(o.remove(),o=null)})}}}}]).service("uiTinymceService",[function(){var a=function(){var a="ui-tinymce",b=0,c=function(){return b++,a+"-"+b};return{getUniqueId:c}};return new a}]);
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module.exports = function (grunt) {
|
2
|
+
|
3
|
+
require('load-grunt-tasks')(grunt);
|
4
|
+
|
5
|
+
// Default task.
|
6
|
+
grunt.registerTask('default', ['jshint', 'karma', 'uglify']);
|
7
|
+
|
8
|
+
var karmaConfig = function(configFile, customOptions) {
|
9
|
+
var options = { configFile: configFile, singleRun: true };
|
10
|
+
var travisOptions = process.env.TRAVIS && { browsers: ['Firefox'], reporters: 'dots' };
|
11
|
+
return grunt.util._.extend(options, customOptions, travisOptions);
|
12
|
+
};
|
13
|
+
|
14
|
+
// Project configuration.
|
15
|
+
grunt.initConfig({
|
16
|
+
karma: {
|
17
|
+
unit: karmaConfig('test/karma.conf.js')
|
18
|
+
},
|
19
|
+
jshint:{
|
20
|
+
files:['src/**/*.js', 'test/**/*.js', 'demo/**/*.js'],
|
21
|
+
options:{
|
22
|
+
curly:true,
|
23
|
+
eqeqeq:true,
|
24
|
+
immed:true,
|
25
|
+
newcap:true,
|
26
|
+
noarg:true,
|
27
|
+
sub:true,
|
28
|
+
boss:true,
|
29
|
+
eqnull:true,
|
30
|
+
globals:{}
|
31
|
+
}
|
32
|
+
},
|
33
|
+
changelog: {
|
34
|
+
options: {
|
35
|
+
dest: 'CHANGELOG.md'
|
36
|
+
}
|
37
|
+
},
|
38
|
+
uglify: {
|
39
|
+
dist: {
|
40
|
+
files: {
|
41
|
+
'dist/tinymce.min.js': ['src/tinymce.js']
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
});
|
46
|
+
|
47
|
+
};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{
|
2
|
+
"name": "angular-ui-tinymce",
|
3
|
+
"version": "0.0.19",
|
4
|
+
"descriptin": "This directive allows you to add a tinymce to your form elements.",
|
5
|
+
"author": "https://github.com/angular-ui/ui-tinymce/graphs/contributors",
|
6
|
+
"license": "MIT",
|
7
|
+
"homepage": "http://angular-ui.github.com",
|
8
|
+
"main": "src/tinymce.js",
|
9
|
+
"dependencies": {},
|
10
|
+
"devDependencies": {
|
11
|
+
"grunt": "~1.0.1",
|
12
|
+
"grunt-contrib-jshint": "1.0.0",
|
13
|
+
"grunt-contrib-uglify": "~0.11.1",
|
14
|
+
"grunt-conventional-changelog": "~1.0.0",
|
15
|
+
"grunt-karma": "2.0.0",
|
16
|
+
"jasmine-core": "~2.3.4",
|
17
|
+
"karma": "0.13.22",
|
18
|
+
"karma-chrome-launcher": "~0.1.3",
|
19
|
+
"karma-firefox-launcher": "~0.1.3",
|
20
|
+
"karma-jasmine": "~0.3.5",
|
21
|
+
"load-grunt-tasks": "~0.2.0"
|
22
|
+
},
|
23
|
+
"scripts": {},
|
24
|
+
"repository": {
|
25
|
+
"type": "git",
|
26
|
+
"url": "git://github.com/angular-ui/ui-tinymce.git"
|
27
|
+
}
|
28
|
+
}
|
@@ -0,0 +1,235 @@
|
|
1
|
+
/**
|
2
|
+
* Binds a TinyMCE widget to <textarea> elements.
|
3
|
+
*/
|
4
|
+
angular.module('ui.tinymce', [])
|
5
|
+
.value('uiTinymceConfig', {})
|
6
|
+
.directive('uiTinymce', ['$rootScope', '$compile', '$timeout', '$window', '$sce', 'uiTinymceConfig', 'uiTinymceService', function($rootScope, $compile, $timeout, $window, $sce, uiTinymceConfig, uiTinymceService) {
|
7
|
+
uiTinymceConfig = uiTinymceConfig || {};
|
8
|
+
|
9
|
+
if (uiTinymceConfig.baseUrl) {
|
10
|
+
tinymce.baseURL = uiTinymceConfig.baseUrl;
|
11
|
+
}
|
12
|
+
|
13
|
+
return {
|
14
|
+
require: ['ngModel', '^?form'],
|
15
|
+
priority: 599,
|
16
|
+
link: function(scope, element, attrs, ctrls) {
|
17
|
+
if (!$window.tinymce) {
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
|
21
|
+
var ngModel = ctrls[0],
|
22
|
+
form = ctrls[1] || null;
|
23
|
+
|
24
|
+
var expression, options = {
|
25
|
+
debounce: true
|
26
|
+
}, tinyInstance,
|
27
|
+
updateView = function(editor) {
|
28
|
+
var content = editor.getContent({format: options.format}).trim();
|
29
|
+
content = $sce.trustAsHtml(content);
|
30
|
+
|
31
|
+
ngModel.$setViewValue(content);
|
32
|
+
if (!$rootScope.$$phase) {
|
33
|
+
scope.$digest();
|
34
|
+
}
|
35
|
+
};
|
36
|
+
|
37
|
+
function toggleDisable(disabled) {
|
38
|
+
if (disabled) {
|
39
|
+
ensureInstance();
|
40
|
+
|
41
|
+
if (tinyInstance) {
|
42
|
+
tinyInstance.getBody().setAttribute('contenteditable', false);
|
43
|
+
}
|
44
|
+
} else {
|
45
|
+
ensureInstance();
|
46
|
+
|
47
|
+
if (tinyInstance && !tinyInstance.settings.readonly && tinyInstance.getDoc()) {
|
48
|
+
tinyInstance.getBody().setAttribute('contenteditable', true);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// fetch a unique ID from the service
|
54
|
+
var uniqueId = uiTinymceService.getUniqueId();
|
55
|
+
attrs.$set('id', uniqueId);
|
56
|
+
|
57
|
+
expression = {};
|
58
|
+
|
59
|
+
angular.extend(expression, scope.$eval(attrs.uiTinymce));
|
60
|
+
|
61
|
+
//Debounce update and save action
|
62
|
+
var debouncedUpdate = (function(debouncedUpdateDelay) {
|
63
|
+
var debouncedUpdateTimer;
|
64
|
+
return function(ed) {
|
65
|
+
$timeout.cancel(debouncedUpdateTimer);
|
66
|
+
debouncedUpdateTimer = $timeout(function() {
|
67
|
+
return (function(ed) {
|
68
|
+
if (ed.isDirty()) {
|
69
|
+
ed.save();
|
70
|
+
updateView(ed);
|
71
|
+
}
|
72
|
+
})(ed);
|
73
|
+
}, debouncedUpdateDelay);
|
74
|
+
};
|
75
|
+
})(400);
|
76
|
+
|
77
|
+
var setupOptions = {
|
78
|
+
// Update model when calling setContent
|
79
|
+
// (such as from the source editor popup)
|
80
|
+
setup: function(ed) {
|
81
|
+
ed.on('init', function() {
|
82
|
+
ngModel.$render();
|
83
|
+
ngModel.$setPristine();
|
84
|
+
ngModel.$setUntouched();
|
85
|
+
if (form) {
|
86
|
+
form.$setPristine();
|
87
|
+
}
|
88
|
+
});
|
89
|
+
|
90
|
+
// Update model when:
|
91
|
+
// - a button has been clicked [ExecCommand]
|
92
|
+
// - the editor content has been modified [change]
|
93
|
+
// - the node has changed [NodeChange]
|
94
|
+
// - an object has been resized (table, image) [ObjectResized]
|
95
|
+
ed.on('ExecCommand change NodeChange ObjectResized', function() {
|
96
|
+
if (!options.debounce) {
|
97
|
+
ed.save();
|
98
|
+
updateView(ed);
|
99
|
+
return;
|
100
|
+
}
|
101
|
+
debouncedUpdate(ed);
|
102
|
+
});
|
103
|
+
|
104
|
+
ed.on('blur', function() {
|
105
|
+
element[0].blur();
|
106
|
+
ngModel.$setTouched();
|
107
|
+
if (!$rootScope.$$phase) {
|
108
|
+
scope.$digest();
|
109
|
+
}
|
110
|
+
});
|
111
|
+
|
112
|
+
ed.on('remove', function() {
|
113
|
+
element.remove();
|
114
|
+
});
|
115
|
+
|
116
|
+
if (uiTinymceConfig.setup) {
|
117
|
+
uiTinymceConfig.setup(ed, {
|
118
|
+
updateView: updateView
|
119
|
+
});
|
120
|
+
}
|
121
|
+
|
122
|
+
if (expression.setup) {
|
123
|
+
expression.setup(ed, {
|
124
|
+
updateView: updateView
|
125
|
+
});
|
126
|
+
}
|
127
|
+
},
|
128
|
+
format: expression.format || 'html',
|
129
|
+
selector: '#' + attrs.id
|
130
|
+
};
|
131
|
+
// extend options with initial uiTinymceConfig and
|
132
|
+
// options from directive attribute value
|
133
|
+
angular.extend(options, uiTinymceConfig, expression, setupOptions);
|
134
|
+
// Wrapped in $timeout due to $tinymce:refresh implementation, requires
|
135
|
+
// element to be present in DOM before instantiating editor when
|
136
|
+
// re-rendering directive
|
137
|
+
$timeout(function() {
|
138
|
+
if (options.baseURL){
|
139
|
+
tinymce.baseURL = options.baseURL;
|
140
|
+
}
|
141
|
+
var maybeInitPromise = tinymce.init(options);
|
142
|
+
if(maybeInitPromise && typeof maybeInitPromise.then === 'function') {
|
143
|
+
maybeInitPromise.then(function() {
|
144
|
+
toggleDisable(scope.$eval(attrs.ngDisabled));
|
145
|
+
});
|
146
|
+
} else {
|
147
|
+
toggleDisable(scope.$eval(attrs.ngDisabled));
|
148
|
+
}
|
149
|
+
});
|
150
|
+
|
151
|
+
ngModel.$formatters.unshift(function(modelValue) {
|
152
|
+
return modelValue ? $sce.trustAsHtml(modelValue) : '';
|
153
|
+
});
|
154
|
+
|
155
|
+
ngModel.$parsers.unshift(function(viewValue) {
|
156
|
+
return viewValue ? $sce.getTrustedHtml(viewValue) : '';
|
157
|
+
});
|
158
|
+
|
159
|
+
ngModel.$render = function() {
|
160
|
+
ensureInstance();
|
161
|
+
|
162
|
+
var viewValue = ngModel.$viewValue ?
|
163
|
+
$sce.getTrustedHtml(ngModel.$viewValue) : '';
|
164
|
+
|
165
|
+
// instance.getDoc() check is a guard against null value
|
166
|
+
// when destruction & recreation of instances happen
|
167
|
+
if (tinyInstance &&
|
168
|
+
tinyInstance.getDoc()
|
169
|
+
) {
|
170
|
+
tinyInstance.setContent(viewValue);
|
171
|
+
// Triggering change event due to TinyMCE not firing event &
|
172
|
+
// becoming out of sync for change callbacks
|
173
|
+
tinyInstance.fire('change');
|
174
|
+
}
|
175
|
+
};
|
176
|
+
|
177
|
+
attrs.$observe('disabled', toggleDisable);
|
178
|
+
|
179
|
+
// This block is because of TinyMCE not playing well with removal and
|
180
|
+
// recreation of instances, requiring instances to have different
|
181
|
+
// selectors in order to render new instances properly
|
182
|
+
var unbindEventListener = scope.$on('$tinymce:refresh', function(e, id) {
|
183
|
+
var eid = attrs.id;
|
184
|
+
if (angular.isUndefined(id) || id === eid) {
|
185
|
+
var parentElement = element.parent();
|
186
|
+
var clonedElement = element.clone();
|
187
|
+
clonedElement.removeAttr('id');
|
188
|
+
clonedElement.removeAttr('style');
|
189
|
+
clonedElement.removeAttr('aria-hidden');
|
190
|
+
tinymce.execCommand('mceRemoveEditor', false, eid);
|
191
|
+
parentElement.append($compile(clonedElement)(scope));
|
192
|
+
unbindEventListener();
|
193
|
+
}
|
194
|
+
});
|
195
|
+
|
196
|
+
scope.$on('$destroy', function() {
|
197
|
+
ensureInstance();
|
198
|
+
|
199
|
+
if (tinyInstance) {
|
200
|
+
tinyInstance.remove();
|
201
|
+
tinyInstance = null;
|
202
|
+
}
|
203
|
+
});
|
204
|
+
|
205
|
+
function ensureInstance() {
|
206
|
+
if (!tinyInstance) {
|
207
|
+
tinyInstance = tinymce.get(attrs.id);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
};
|
212
|
+
}])
|
213
|
+
.service('uiTinymceService', [
|
214
|
+
/**
|
215
|
+
* A service is used to create unique ID's, this prevents duplicate ID's if there are multiple editors on screen.
|
216
|
+
*/
|
217
|
+
function() {
|
218
|
+
var UITinymceService = function() {
|
219
|
+
var ID_ATTR = 'ui-tinymce';
|
220
|
+
// uniqueId keeps track of the latest assigned ID
|
221
|
+
var uniqueId = 0;
|
222
|
+
// getUniqueId returns a unique ID
|
223
|
+
var getUniqueId = function() {
|
224
|
+
uniqueId ++;
|
225
|
+
return ID_ATTR + '-' + uniqueId;
|
226
|
+
};
|
227
|
+
// return the function as a public method of the service
|
228
|
+
return {
|
229
|
+
getUniqueId: getUniqueId
|
230
|
+
};
|
231
|
+
};
|
232
|
+
// return a new instance of the service
|
233
|
+
return new UITinymceService();
|
234
|
+
}
|
235
|
+
]);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module.exports = function (config) {
|
2
|
+
config.set({
|
3
|
+
basePath: '..',
|
4
|
+
frameworks: ['jasmine'],
|
5
|
+
files: [
|
6
|
+
'bower_components/angular/angular.js',
|
7
|
+
'bower_components/angular-mocks/angular-mocks.js',
|
8
|
+
'bower_components/tinymce/tinymce.min.js',
|
9
|
+
'src/tinymce.js',
|
10
|
+
'test/*.spec.js',
|
11
|
+
{pattern: 'bower_components/tinymce/themes/**', included: false},
|
12
|
+
{pattern: 'bower_components/tinymce/skins/lightgray/**', included: false}
|
13
|
+
],
|
14
|
+
singleRun: false,
|
15
|
+
autoWatch: true,
|
16
|
+
browsers: [ 'Chrome' ],
|
17
|
+
});
|
18
|
+
};
|
@@ -0,0 +1,184 @@
|
|
1
|
+
/*global describe, beforeEach, module, inject, it, spyOn, expect, $, angular, afterEach, runs, waits */
|
2
|
+
describe('uiTinymce', function () {
|
3
|
+
'use strict';
|
4
|
+
|
5
|
+
var scope, $compile, $timeout, element, directiveElement, id, text = '<p>Hello</p>';
|
6
|
+
beforeEach(module('ui.tinymce'));
|
7
|
+
beforeEach(function() {
|
8
|
+
// throw some garbage in the tinymce cfg to be sure it's getting thru to the directive
|
9
|
+
angular.module('ui.tinymce').value('uiTinymceConfig', {tinymce: {bar: 'baz'}});
|
10
|
+
});
|
11
|
+
beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_) {
|
12
|
+
scope = _$rootScope_.$new();
|
13
|
+
$compile = _$compile_;
|
14
|
+
$timeout = _$timeout_;
|
15
|
+
}));
|
16
|
+
|
17
|
+
afterEach(function() {
|
18
|
+
angular.module('ui.tinymce').value('uiTinymceConfig', {});
|
19
|
+
tinymce.remove('textarea');
|
20
|
+
});
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Asynchronously runs the compilation.
|
24
|
+
*/
|
25
|
+
function compile() {
|
26
|
+
element = $compile('<form><textarea ui-tinymce="{foo: \'bar\', setup: setupFooBar }" data-ng-model="foo"></textarea></form>')(scope);
|
27
|
+
angular.element(document.getElementsByTagName('body')[0]).append(element);
|
28
|
+
scope.$apply();
|
29
|
+
$timeout.flush();
|
30
|
+
directiveElement = element.find('textarea');
|
31
|
+
id = directiveElement.attr('id');
|
32
|
+
}
|
33
|
+
|
34
|
+
it('should be pristine on load', function() {
|
35
|
+
compile();
|
36
|
+
expect(directiveElement.controller('form').$pristine).toBe(true);
|
37
|
+
expect(directiveElement.controller('ngModel').$pristine).toBe(true);
|
38
|
+
expect(directiveElement.controller('ngModel').$touched).toBe(false);
|
39
|
+
});
|
40
|
+
|
41
|
+
describe('compiling this directive', function() {
|
42
|
+
|
43
|
+
it('should include the passed options', function() {
|
44
|
+
spyOn(tinymce, 'init');
|
45
|
+
compile();
|
46
|
+
expect(tinymce.init).toHaveBeenCalled();
|
47
|
+
expect(tinymce.init.calls.mostRecent().args[0].foo).toBe('bar');
|
48
|
+
});
|
49
|
+
|
50
|
+
it('should include the default options', function() {
|
51
|
+
spyOn(tinymce, 'init');
|
52
|
+
compile();
|
53
|
+
expect(tinymce.init).toHaveBeenCalled();
|
54
|
+
expect(tinymce.init.calls.mostRecent().args[0].tinymce.bar).toBe('baz');
|
55
|
+
});
|
56
|
+
/*
|
57
|
+
it('should execute the passed `setup` option', function(done) {
|
58
|
+
scope.setupFooBar = jasmine.createSpy('setupFooBar');
|
59
|
+
compile();
|
60
|
+
|
61
|
+
//TODO: Get rid of timeout
|
62
|
+
setTimeout(function() {
|
63
|
+
expect(scope.setupFooBar).toHaveBeenCalled();
|
64
|
+
done();
|
65
|
+
}, 100);
|
66
|
+
}); */
|
67
|
+
});
|
68
|
+
|
69
|
+
it("should set touched on blur", function(){
|
70
|
+
compile();
|
71
|
+
expect(directiveElement.controller('ngModel').$touched).toBe(false);
|
72
|
+
|
73
|
+
element.find("textarea").triggerHandler("blur");
|
74
|
+
expect(directiveElement.controller('ngModel').$touched).toBe(true);
|
75
|
+
});
|
76
|
+
|
77
|
+
it('should remove tinymce instance on $scope destruction', function() {
|
78
|
+
compile();
|
79
|
+
expect(tinymce.get(element.attr('id'))).toBeDefined();
|
80
|
+
|
81
|
+
scope.$destroy();
|
82
|
+
|
83
|
+
expect(tinymce.get(element.attr('id'))).toBeNull();
|
84
|
+
});
|
85
|
+
|
86
|
+
// TODO: Figure out why such a large timeout is needed
|
87
|
+
describe('setting a value to the model', function() {
|
88
|
+
it('should update the editor', function(done) {
|
89
|
+
compile();
|
90
|
+
setTimeout(function() {
|
91
|
+
scope.foo = text;
|
92
|
+
scope.$apply();
|
93
|
+
|
94
|
+
try {
|
95
|
+
expect(tinymce.get(id).getContent()).toEqual(text);
|
96
|
+
} catch(e) {
|
97
|
+
expect(true).toBe(false);
|
98
|
+
done();
|
99
|
+
}
|
100
|
+
|
101
|
+
done();
|
102
|
+
}, 100);
|
103
|
+
});
|
104
|
+
it('shouldn\'t make the form dirty', function(done) {
|
105
|
+
compile();
|
106
|
+
setTimeout(function() {
|
107
|
+
scope.foo = text;
|
108
|
+
scope.$apply();
|
109
|
+
|
110
|
+
expect(directiveElement.controller('form').$dirty).toBe(false);
|
111
|
+
|
112
|
+
done();
|
113
|
+
});
|
114
|
+
});
|
115
|
+
// TODO: Fix test
|
116
|
+
/*xit('should handle undefined gracefully', function(done) {
|
117
|
+
compile();
|
118
|
+
setTimeout(function() {
|
119
|
+
scope.foo = undefined;
|
120
|
+
scope.$apply();
|
121
|
+
|
122
|
+
try {
|
123
|
+
expect(tinymce.get(id).getContent()).toEqual('');
|
124
|
+
} catch(e) {
|
125
|
+
expect(true).toBe(false);
|
126
|
+
done();
|
127
|
+
}
|
128
|
+
|
129
|
+
done();
|
130
|
+
}, 100);
|
131
|
+
});
|
132
|
+
xit('should handle null gracefully', function(done) {
|
133
|
+
compile();
|
134
|
+
setTimeout(function() {
|
135
|
+
scope.foo = null;
|
136
|
+
scope.$apply();
|
137
|
+
|
138
|
+
try {
|
139
|
+
expect(tinymce.get(id).getContent()).toEqual('');
|
140
|
+
} catch(e) {
|
141
|
+
expect(true).toBe(false);
|
142
|
+
done();
|
143
|
+
}
|
144
|
+
|
145
|
+
done();
|
146
|
+
}, 100);
|
147
|
+
});*/
|
148
|
+
});
|
149
|
+
/*describe('using the editor', function () {
|
150
|
+
it('should update the model', function (done) {
|
151
|
+
compile();
|
152
|
+
setTimeout(function () {
|
153
|
+
tinymce.get('foo').setContent(text);
|
154
|
+
|
155
|
+
expect(scope.foo).toEqual(text);
|
156
|
+
|
157
|
+
done();
|
158
|
+
});
|
159
|
+
});
|
160
|
+
});*/
|
161
|
+
|
162
|
+
it('should work with the ng-if directive', function () {
|
163
|
+
expect(function () {
|
164
|
+
$compile('<textarea ng-if="show" ui-tinymce data-ng-model="foo"></textarea>')(scope);
|
165
|
+
}).not.toThrow();
|
166
|
+
});
|
167
|
+
|
168
|
+
it('should create unique ID\'s when using multiple directives', function() {
|
169
|
+
|
170
|
+
element = $compile('<form><textarea ui-tinymce="{foo: \'bar\', setup: setupFooBar }" data-ng-model="foo_1"></textarea><textarea ui-tinymce="{foo: \'bar\', setup: setupFooBar }" data-ng-model="foo_2"></textarea><textarea ui-tinymce="{foo: \'bar\', setup: setupFooBar }" data-ng-model="foo_3"></textarea><textarea ui-tinymce="{foo: \'bar\', setup: setupFooBar }" data-ng-model="foo_4"></textarea></form>')(scope);
|
171
|
+
angular.element(document.getElementsByTagName('body')[0]).append(element);
|
172
|
+
scope.$apply();
|
173
|
+
$timeout.flush();
|
174
|
+
var directiveElements = element.find('textarea');
|
175
|
+
expect(directiveElements.length).toEqual(4);
|
176
|
+
var id1 = directiveElements[0].id;
|
177
|
+
var id2 = directiveElements[1].id;
|
178
|
+
var id3 = directiveElements[2].id;
|
179
|
+
var id4 = directiveElements[3].id;
|
180
|
+
expect(id1).not.toEqual(id2);
|
181
|
+
expect(id2).not.toEqual(id3);
|
182
|
+
expect(id3).not.toEqual(id4);
|
183
|
+
});
|
184
|
+
});
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angular-ui-tinymce-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julien Dargelos
|
@@ -53,6 +53,18 @@ files:
|
|
53
53
|
- lib/angular-ui-tinymce/rails/engine.rb
|
54
54
|
- lib/angular-ui-tinymce/rails/version.rb
|
55
55
|
- lib/tasks/angular/ui/tinymce/rails_tasks.rake
|
56
|
+
- node_modules/angular-ui-tinymce/CONTRIBUTING.md
|
57
|
+
- node_modules/angular-ui-tinymce/LICENSE
|
58
|
+
- node_modules/angular-ui-tinymce/README.md
|
59
|
+
- node_modules/angular-ui-tinymce/bower.json
|
60
|
+
- node_modules/angular-ui-tinymce/demo/demo.html
|
61
|
+
- node_modules/angular-ui-tinymce/demo/demo.js
|
62
|
+
- node_modules/angular-ui-tinymce/dist/tinymce.min.js
|
63
|
+
- node_modules/angular-ui-tinymce/gruntFile.js
|
64
|
+
- node_modules/angular-ui-tinymce/package.json
|
65
|
+
- node_modules/angular-ui-tinymce/src/tinymce.js
|
66
|
+
- node_modules/angular-ui-tinymce/test/karma.conf.js
|
67
|
+
- node_modules/angular-ui-tinymce/test/tinymce.spec.js
|
56
68
|
homepage: https://www.github.com/juliendargelos/angular-ui-tinymce-rails
|
57
69
|
licenses:
|
58
70
|
- MIT
|