├── .bowerrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.coffee
├── LICENSE
├── README.md
├── angular-contenteditable-scenario.js
├── angular-contenteditable.js
├── bower.json
├── karma.coffee
├── package.json
├── test
├── e2e
│ └── scenarios.coffee
└── fixtures
│ ├── img
│ ├── gb.gif
│ ├── ru.gif
│ └── us.gif
│ ├── no-line-breaks.html
│ ├── select-non-editable.html
│ ├── simple.html
│ ├── states.json
│ ├── strip-br.html
│ ├── typeahead1.html
│ ├── typeahead2.html
│ └── typeahead3.html
└── util
└── post-commit
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 | .idea
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "es5": true,
5 | "esnext": true,
6 | "bitwise": true,
7 | "camelcase": true,
8 | "curly": true,
9 | "eqeqeq": true,
10 | "immed": true,
11 | "indent": 2,
12 | "latedef": true,
13 | "newcap": true,
14 | "noarg": true,
15 | "quotmark": "single",
16 | "regexp": true,
17 | "undef": true,
18 | "unused": true,
19 | "strict": true,
20 | "trailing": true,
21 | "smarttabs": true,
22 | "globals": {
23 | "angular": false
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - '0.10'
5 |
6 | notifications:
7 | email: false
8 |
--------------------------------------------------------------------------------
/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | # global module:false
2 |
3 | module.exports = (grunt) ->
4 |
5 | grunt.initConfig
6 | pkg: grunt.file.readJSON 'package.json'
7 | meta:
8 | test: 'test'
9 | karma:
10 | e2e: configFile: 'karma.coffee'
11 | e2e_ci:
12 | configFile: 'karma.coffee'
13 | singleRun: true
14 | browsers: ['PhantomJS']
15 | jshint:
16 | src: ['angular-contenteditable.js']
17 | options:
18 | asi: true
19 |
20 | require('matchdep').filterDev('grunt-*').forEach grunt.loadNpmTasks
21 |
22 | grunt.registerTask 'test', ['karma:e2e_ci']
23 | grunt.registerTask 'lint', ['jshint']
24 | grunt.registerTask 'default', ['lint' , 'test']
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Dmitri Akatov
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-contenteditable
2 | [](https://travis-ci.org/akatov/angular-contenteditable)
3 | [](https://gemnasium.com/akatov/angular-contenteditable)
4 | [](https://coderwall.com/akatov)
5 |
6 | An AngularJS directive to bind html tags with the `contenteditable` attribute to models.
7 |
8 | ## Install
9 |
10 | ```bash
11 | bower install angular-contenteditable
12 | ```
13 |
14 | ## Usage
15 |
16 | ```javascript
17 | angular.module('myapp', ['contenteditable'])
18 | .controller('Ctrl', ['$scope', function($scope) {
19 | $scope.model="interesting stuff"
20 | }])
21 | ```
22 |
23 | ```html
24 |
/g, '').replace(/
/g, '').replace(/<\/div>/g, '')
41 | if (html2 !== html) {
42 | rerender = true
43 | html = html2
44 | }
45 | }
46 | if (opts.stripTags) {
47 | rerender = true
48 | html = html.replace(/<\S[^><]*>/g, '')
49 | }
50 | ngModel.$setViewValue(html)
51 | if (rerender) {
52 | ngModel.$render()
53 | }
54 | if (html === '') {
55 | // the cursor disappears if the contents is empty
56 | // so we need to refocus
57 | $timeout(function(){
58 | element[0].blur()
59 | element[0].focus()
60 | })
61 | }
62 | })
63 | })
64 |
65 | // model -> view
66 | var oldRender = ngModel.$render
67 | ngModel.$render = function() {
68 | var el, el2, range, sel
69 | if (!!oldRender) {
70 | oldRender()
71 | }
72 | var html = ngModel.$viewValue || ''
73 | if (opts.stripTags) {
74 | html = html.replace(/<\S[^><]*>/g, '')
75 | }
76 |
77 | element.html(html)
78 | if (opts.moveCaretToEndOnChange) {
79 | el = element[0]
80 | range = document.createRange()
81 | sel = window.getSelection()
82 | if (el.childNodes.length > 0) {
83 | el2 = el.childNodes[el.childNodes.length - 1]
84 | range.setStartAfter(el2)
85 | } else {
86 | range.setStartAfter(el)
87 | }
88 | range.collapse(true)
89 | sel.removeAllRanges()
90 | sel.addRange(range)
91 | }
92 | }
93 | if (opts.selectNonEditable) {
94 | element.bind('click', function(e) {
95 | var range, sel, target
96 | target = e.toElement
97 | if (target !== this && angular.element(target).attr('contenteditable') === 'false') {
98 | range = document.createRange()
99 | sel = window.getSelection()
100 | range.setStartBefore(target)
101 | range.setEndAfter(target)
102 | sel.removeAllRanges()
103 | sel.addRange(range)
104 | }
105 | })
106 | }
107 | }
108 | }}]);
109 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-contenteditable",
3 | "version": "0.3.8",
4 | "main": "angular-contenteditable.js",
5 | "ignore": [
6 | ".*",
7 | "README.md",
8 | "Gruntfile.coffee",
9 | "package.json",
10 | "components",
11 | "node_modules",
12 | "src",
13 | "test"
14 | ],
15 | "devDependencies": {
16 | "angular": "*",
17 | "angular-mocks": "~1.0.5",
18 | "angular-scenario": "~1.0.5",
19 | "expect": "~0.2.0",
20 | "jquery": "~2.0.2",
21 | "bootstrap-css": "~2.3.2",
22 | "angular-bootstrap": "~0.4.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/karma.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (karma) ->
2 | toServe = for file in [
3 | 'bower_components/**/*.css'
4 | 'bower_components/*/*.js'
5 | 'test/fixtures/**/*'
6 | ]
7 | pattern: file
8 | watched: false
9 | included: false
10 | served: true
11 |
12 | karma.set
13 | frameworks: ['ng-scenario']
14 |
15 | preprocessors: '**/*.coffee': 'coffee'
16 |
17 | files: [
18 | 'test/e2e/**/*.coffee'
19 | 'angular-contenteditable.js'
20 | 'angular-contenteditable-scenario.js'
21 | ].concat toServe
22 |
23 | exclude: []
24 |
25 | reporters: ['progress']
26 |
27 | port: 9876
28 |
29 | runnerPort: 9100
30 |
31 | colors: true
32 |
33 | logLevel: karma.LOG_INFO
34 |
35 | autoWatch: true
36 |
37 | browsers: ['Chrome']
38 |
39 | captureTimeout: 60000
40 |
41 | singleRun: false
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-contenteditable",
3 | "version": "0.3.8",
4 | "description": "angular model for the 'contenteditable' html attribute",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/akatov/angular-contenteditable.git"
8 | },
9 | "main": "angular-contenteditable.js",
10 | "directories": {
11 | "test": "test"
12 | },
13 | "scripts": {
14 | "install": "bower install",
15 | "test": "grunt test"
16 | },
17 | "repository": "",
18 | "keywords": [
19 | "angular",
20 | "extension"
21 | ],
22 | "author": "Dmitri Akatov",
23 | "license": "BSD",
24 | "devDependencies": {
25 | "matchdep": "~0.3.0",
26 | "grunt": "~0.4.1",
27 | "grunt-cli": "~0.1.13",
28 | "grunt-contrib-jshint": "~0.7.1",
29 | "grunt-karma": "~0.6.2",
30 | "bower": "~1.3.5",
31 | "karma": "~0.10.2",
32 | "karma-ng-scenario": "0.1.0",
33 | "karma-coffee-preprocessor": "0.1.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/e2e/scenarios.coffee:
--------------------------------------------------------------------------------
1 | describe 'module contenteditable', ->
2 | describe 'directive contenteditable', ->
3 | describe 'simple application', ->
4 | beforeEach ->
5 | browser().navigateTo 'base/test/fixtures/simple.html'
6 |
7 | it 'should update the model from the view (simple text)', ->
8 | element('#input').enter('abc')
9 | expect(element('#input').html()).toBe 'abc'
10 | expect(element('#output').html()).toBe 'abc'
11 |
12 | it 'should update the model from the view (text with spans)', ->
13 | element('#input').html('abc
red')
14 | expect(element('#input span').html()).toBe 'red'
15 | expect(element('#output').html()).toBe 'abc <span style="color:red">red</span>'
16 |
17 | it 'should update the view from the model', ->
18 | input('model').enter('oops')
19 | expect(element('#input').html()).toBe 'oops'
20 | expect(element('#output').html()).toBe 'oops'
21 | input('model').enter('a
red b')
22 | expect(element('#input').html()).toBe 'a
red b'
23 | expect(element('#input span').html()).toBe 'red'
24 | expect(element('#output').html()).toBe 'a <span style="color:red">red</span> b'
25 |
--------------------------------------------------------------------------------
/test/fixtures/img/gb.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatov/angular-contenteditable/886324c6adc128fae290db5cc3425bb2adcc18fe/test/fixtures/img/gb.gif
--------------------------------------------------------------------------------
/test/fixtures/img/ru.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatov/angular-contenteditable/886324c6adc128fae290db5cc3425bb2adcc18fe/test/fixtures/img/ru.gif
--------------------------------------------------------------------------------
/test/fixtures/img/us.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatov/angular-contenteditable/886324c6adc128fae290db5cc3425bb2adcc18fe/test/fixtures/img/us.gif
--------------------------------------------------------------------------------
/test/fixtures/no-line-breaks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Simple
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
25 | something
26 |
27 |
28 |
29 |
30 |
{{ model }}
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/fixtures/select-non-editable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Simple
5 |
6 |
7 |
8 |
9 |
19 |
20 |
21 |
22 |
23 |
24 | something
25 |
26 |
27 |
28 |
29 |
{{ model }}
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/fixtures/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Simple
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
{{ model }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
Edit me - I don't affect anything
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/test/fixtures/states.json:
--------------------------------------------------------------------------------
1 | [ "Russia

"
2 | , "United States of America

"
3 | , "United Kingdom

"]
4 |
--------------------------------------------------------------------------------
/test/fixtures/strip-br.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Simple
5 |
6 |
7 |
8 |
9 |
19 |
20 |
21 |
22 |
23 |
24 | something
25 |
26 |
27 |
28 |
29 |
{{ model }}
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/fixtures/typeahead1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
Model: {{selected| json}}
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/fixtures/typeahead2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
42 |
43 |
44 |
45 |
Model: {{ selected | json }}
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/test/fixtures/typeahead3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
43 |
44 |
45 |
46 |
Model: {{ selected | json }}
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/util/post-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'tmpdir'
4 | require 'json'
5 |
6 | puts 'running post-commit hook'
7 |
8 | BOWER = JSON.parse File.read 'bower.json'
9 |
10 | def commit
11 | @commit ||= `git log | head -1 | cut -d' ' -f2`
12 | end
13 |
14 | # get version of library from bower.json
15 | def bower_version(library)
16 | BOWER["dependencies"][library] || BOWER["devDependencies"][library]
17 | end
18 |
19 | # return the base url for a library from bower / github
20 | def library_base_url(library)
21 | # TODO: figure out how to clean the cache so we can use `bower --offline`
22 | (@library_base_url ||= {})[library] ||=
23 | JSON.parse(`bower lookup #{library} --json`)["url"]
24 | .sub(/^git:\/\/github.com/, 'https://rawgithub.com')
25 | .sub(/\.git$/, "/#{bower_version(library).sub(/~/, '')}/")
26 | end
27 |
28 | # transform script ref to bower URL
29 | def script_url(src)
30 | if src =~ /\/bower_components\//
31 | parts = src.split('/bower_components/')[1].split('/')
32 | library_base_url(parts[0]) + parts.drop(1).join('/')
33 | else
34 | src.sub(/^\.\.\/\.\./,
35 | 'https://rawgithub.com/akatov/angular-contenteditable/master')
36 | end
37 | end
38 |
39 | # link href
40 | # script src
41 | def replace_script_and_link(contents)
42 | ["script src", "link href"].reduce(contents) do |c, tag|
43 | c.gsub /#{tag}="([^"]*)"/ do
44 | "#{tag}=\"#{script_url($1)}\""
45 | end
46 | end
47 | end
48 |
49 | def index_header
50 | <
52 |
53 | angular-contenteditable
54 |
55 |
56 | angular contenteditable
57 | examples
58 |
59 | EOF
60 | end
61 |
62 | def index_footer
63 | <
65 |
66 |