├── .all-contributorsrc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── CHANGES.md ├── Gruntfile.js ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── bower.json ├── dist ├── css │ ├── medium-editor-insert-plugin-frontend.css │ ├── medium-editor-insert-plugin-frontend.min.css │ ├── medium-editor-insert-plugin.css │ └── medium-editor-insert-plugin.min.css └── js │ ├── medium-editor-insert-plugin.js │ └── medium-editor-insert-plugin.min.js ├── examples ├── absolute-container.html ├── css │ ├── demo.css │ └── normalize.css ├── delete.php ├── img │ ├── embed.png │ └── medium-editor.jpg ├── index.html ├── upload.php ├── uploads │ └── 01.jpg └── vendors │ └── UploadHandler.php ├── package-lock.json ├── package.json ├── spec ├── core.spec.js ├── embeds.spec.js ├── helpers │ └── util.js └── images.spec.js └── src ├── js ├── core.js ├── embeds.js ├── images.js ├── templates.js └── templates │ ├── core-buttons.hbs │ ├── core-caption.hbs │ ├── core-empty-line.hbs │ ├── embeds-toolbar.hbs │ ├── embeds-wrapper.hbs │ ├── images-fileupload.hbs │ ├── images-image.hbs │ ├── images-progressbar.hbs │ └── images-toolbar.hbs ├── sass ├── _core.scss ├── _embeds.scss ├── _images.scss ├── medium-editor-insert-plugin-frontend.scss └── medium-editor-insert-plugin.scss └── wrappers ├── end.js └── start.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "medium-editor-insert-plugin", 3 | "projectOwner": "orthes", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "orthes", 14 | "name": "Pavel Linkesch", 15 | "avatar_url": "https://avatars2.githubusercontent.com/u/312938?v=4", 16 | "profile": "http://linkesch.com", 17 | "contributions": [ 18 | "code", 19 | "doc", 20 | "maintenance", 21 | "review" 22 | ] 23 | }, 24 | { 25 | "login": "j0k3r", 26 | "name": "Jérémy Benoist", 27 | "avatar_url": "https://avatars2.githubusercontent.com/u/62333?v=4", 28 | "profile": "http://www.j0k3r.net", 29 | "contributions": [ 30 | "code", 31 | "maintenance", 32 | "review" 33 | ] 34 | }, 35 | { 36 | "login": "nleush", 37 | "name": "Nazar Leush", 38 | "avatar_url": "https://avatars3.githubusercontent.com/u/39333?v=4", 39 | "profile": "https://github.com/nleush", 40 | "contributions": [ 41 | "code" 42 | ] 43 | }, 44 | { 45 | "login": "ai", 46 | "name": "Andrey Sitnik", 47 | "avatar_url": "https://avatars1.githubusercontent.com/u/19343?v=4", 48 | "profile": "http://twitter.com/sitnikcode", 49 | "contributions": [ 50 | "code" 51 | ] 52 | }, 53 | { 54 | "login": "Jaza", 55 | "name": "Jeremy Epstein", 56 | "avatar_url": "https://avatars1.githubusercontent.com/u/79373?v=4", 57 | "profile": "http://greenash.net.au/", 58 | "contributions": [ 59 | "code" 60 | ] 61 | }, 62 | { 63 | "login": "vexus2", 64 | "name": "Hikaru Tooyama", 65 | "avatar_url": "https://avatars2.githubusercontent.com/u/1228229?v=4", 66 | "profile": "https://github.com/vexus2", 67 | "contributions": [ 68 | "code" 69 | ] 70 | }, 71 | { 72 | "login": "KELiON", 73 | "name": "Alexandr Subbotin", 74 | "avatar_url": "https://avatars2.githubusercontent.com/u/594298?v=4", 75 | "profile": "https://twitter.com/asubbotin", 76 | "contributions": [ 77 | "code" 78 | ] 79 | }, 80 | { 81 | "login": "patrinhani-ciandt", 82 | "name": "Vinicius Patrinhani", 83 | "avatar_url": "https://avatars3.githubusercontent.com/u/5272569?v=4", 84 | "profile": "https://github.com/patrinhani-ciandt", 85 | "contributions": [ 86 | "code" 87 | ] 88 | }, 89 | { 90 | "login": "fbessadok", 91 | "name": "Firas Bessadok", 92 | "avatar_url": "https://avatars2.githubusercontent.com/u/1790778?v=4", 93 | "profile": "http://firas.bessadok.com", 94 | "contributions": [ 95 | "code" 96 | ] 97 | }, 98 | { 99 | "login": "OmniaGM", 100 | "name": "Omnia G Helmi", 101 | "avatar_url": "https://avatars2.githubusercontent.com/u/1101183?v=4", 102 | "profile": "http://omniagm.github.io/", 103 | "contributions": [ 104 | "code" 105 | ] 106 | }, 107 | { 108 | "login": "daniel-huang", 109 | "name": "Daniel Huang", 110 | "avatar_url": "https://avatars2.githubusercontent.com/u/4083642?v=4", 111 | "profile": "https://github.com/daniel-huang", 112 | "contributions": [ 113 | "code" 114 | ] 115 | }, 116 | { 117 | "login": "bertzzie", 118 | "name": "Alex Xandra Albert Sim", 119 | "avatar_url": "https://avatars2.githubusercontent.com/u/610268?v=4", 120 | "profile": "https://bertzzie.com", 121 | "contributions": [ 122 | "code" 123 | ] 124 | }, 125 | { 126 | "login": "brenfrow", 127 | "name": "Brandon Renfrow", 128 | "avatar_url": "https://avatars3.githubusercontent.com/u/1891369?v=4", 129 | "profile": "https://github.com/brenfrow", 130 | "contributions": [ 131 | "code" 132 | ] 133 | }, 134 | { 135 | "login": "BurnHavoc", 136 | "name": "BurnHavoc", 137 | "avatar_url": "https://avatars3.githubusercontent.com/u/5192706?v=4", 138 | "profile": "https://github.com/BurnHavoc", 139 | "contributions": [ 140 | "code" 141 | ] 142 | }, 143 | { 144 | "login": "enzoz", 145 | "name": "Enzo", 146 | "avatar_url": "https://avatars2.githubusercontent.com/u/431361?v=4", 147 | "profile": "http://enzoz.me/", 148 | "contributions": [ 149 | "code" 150 | ] 151 | }, 152 | { 153 | "login": "jonathonsim", 154 | "name": "Jonathon Sim", 155 | "avatar_url": "https://avatars0.githubusercontent.com/u/883073?v=4", 156 | "profile": "http://idealstack.io", 157 | "contributions": [ 158 | "code" 159 | ] 160 | }, 161 | { 162 | "login": "KazuyaHara", 163 | "name": "Kazuya Hara", 164 | "avatar_url": "https://avatars1.githubusercontent.com/u/8471513?v=4", 165 | "profile": "https://kazuyahara.com", 166 | "contributions": [ 167 | "code" 168 | ] 169 | }, 170 | { 171 | "login": "miloshadzic", 172 | "name": "Miloš Hadžić", 173 | "avatar_url": "https://avatars2.githubusercontent.com/u/93555?v=4", 174 | "profile": "https://rightfold.io", 175 | "contributions": [ 176 | "code" 177 | ] 178 | }, 179 | { 180 | "login": "Siron", 181 | "name": "Siron", 182 | "avatar_url": "https://avatars3.githubusercontent.com/u/1642674?v=4", 183 | "profile": "https://github.com/Siron", 184 | "contributions": [ 185 | "code" 186 | ] 187 | }, 188 | { 189 | "login": "olleicua", 190 | "name": "Sam Auciello", 191 | "avatar_url": "https://avatars3.githubusercontent.com/u/1270102?v=4", 192 | "profile": "http://antha.site", 193 | "contributions": [ 194 | "code" 195 | ] 196 | }, 197 | { 198 | "login": "scashin133", 199 | "name": "Sean Cashin", 200 | "avatar_url": "https://avatars0.githubusercontent.com/u/28541?v=4", 201 | "profile": "https://github.com/scashin133", 202 | "contributions": [ 203 | "code" 204 | ] 205 | }, 206 | { 207 | "login": "jackyzhai", 208 | "name": "Yu Zhai", 209 | "avatar_url": "https://avatars0.githubusercontent.com/u/1086365?v=4", 210 | "profile": "https://github.com/jackyzhai", 211 | "contributions": [ 212 | "code" 213 | ] 214 | }, 215 | { 216 | "login": "acekat", 217 | "name": "acekat", 218 | "avatar_url": "https://avatars0.githubusercontent.com/u/1449414?v=4", 219 | "profile": "https://github.com/acekat", 220 | "contributions": [ 221 | "code" 222 | ] 223 | }, 224 | { 225 | "login": "linpekka", 226 | "name": "linpekka", 227 | "avatar_url": "https://avatars1.githubusercontent.com/u/18568266?v=4", 228 | "profile": "https://github.com/linpekka", 229 | "contributions": [ 230 | "code" 231 | ] 232 | }, 233 | { 234 | "login": "sainuio", 235 | "name": "sainu", 236 | "avatar_url": "https://avatars0.githubusercontent.com/u/12888685?v=4", 237 | "profile": "http://sa-inu.com/", 238 | "contributions": [ 239 | "code" 240 | ] 241 | }, 242 | { 243 | "login": "tasarsu", 244 | "name": "Murat Tasarsu", 245 | "avatar_url": "https://avatars3.githubusercontent.com/u/1642706?v=4", 246 | "profile": "http://medya-t.com", 247 | "contributions": [ 248 | "code" 249 | ] 250 | }, 251 | { 252 | "login": "anilmaurya", 253 | "name": "Anil Kumar Maurya", 254 | "avatar_url": "https://avatars1.githubusercontent.com/u/1912864?v=4", 255 | "profile": "http://anilmaurya.herokuapp.com", 256 | "contributions": [ 257 | "code" 258 | ] 259 | }, 260 | { 261 | "login": "artshevtsov", 262 | "name": "Artem Shevtsov", 263 | "avatar_url": "https://avatars3.githubusercontent.com/u/8297414?v=4", 264 | "profile": "https://github.com/artshevtsov", 265 | "contributions": [ 266 | "code" 267 | ] 268 | }, 269 | { 270 | "login": "bgaillard", 271 | "name": "Baptiste Gaillard", 272 | "avatar_url": "https://avatars0.githubusercontent.com/u/1327782?v=4", 273 | "profile": "https://github.com/bgaillard", 274 | "contributions": [ 275 | "code" 276 | ] 277 | }, 278 | { 279 | "login": "bernardwolff", 280 | "name": "Bernard Wolff", 281 | "avatar_url": "https://avatars3.githubusercontent.com/u/2744510?v=4", 282 | "profile": "https://github.com/bernardwolff", 283 | "contributions": [ 284 | "code" 285 | ] 286 | }, 287 | { 288 | "login": "GoGoCarl", 289 | "name": "Carl Scott", 290 | "avatar_url": "https://avatars0.githubusercontent.com/u/1719249?v=4", 291 | "profile": "http://gogocarl.blogspot.com", 292 | "contributions": [ 293 | "code" 294 | ] 295 | }, 296 | { 297 | "login": "flamerohr", 298 | "name": "Chris Joe", 299 | "avatar_url": "https://avatars0.githubusercontent.com/u/1064889?v=4", 300 | "profile": "https://github.com/flamerohr", 301 | "contributions": [ 302 | "code" 303 | ] 304 | }, 305 | { 306 | "login": "pvnr0082t", 307 | "name": "Daniel Wang", 308 | "avatar_url": "https://avatars1.githubusercontent.com/u/13829339?v=4", 309 | "profile": "https://github.com/pvnr0082t", 310 | "contributions": [ 311 | "code" 312 | ] 313 | }, 314 | { 315 | "login": "derrekbertrand", 316 | "name": "Derrek Bertrand", 317 | "avatar_url": "https://avatars0.githubusercontent.com/u/968252?v=4", 318 | "profile": "http://derrekbertrand.com/", 319 | "contributions": [ 320 | "code" 321 | ] 322 | }, 323 | { 324 | "login": "merqurio", 325 | "name": "Gabi Maeztu", 326 | "avatar_url": "https://avatars2.githubusercontent.com/u/1485056?v=4", 327 | "profile": "http://merqur.io", 328 | "contributions": [ 329 | "code" 330 | ] 331 | }, 332 | { 333 | "login": "inssein", 334 | "name": "Hussein Jafferjee", 335 | "avatar_url": "https://avatars1.githubusercontent.com/u/882228?v=4", 336 | "profile": "http://inssein.com", 337 | "contributions": [ 338 | "code" 339 | ] 340 | }, 341 | { 342 | "login": "indrasantosa", 343 | "name": "Indra Santosa", 344 | "avatar_url": "https://avatars0.githubusercontent.com/u/433501?v=4", 345 | "profile": "http://indrasantosa.com", 346 | "contributions": [ 347 | "code" 348 | ] 349 | }, 350 | { 351 | "login": "iparamonau", 352 | "name": "Ivan Paramonau", 353 | "avatar_url": "https://avatars0.githubusercontent.com/u/28841?v=4", 354 | "profile": "http://twitter.com/iparamonau", 355 | "contributions": [ 356 | "code" 357 | ] 358 | }, 359 | { 360 | "login": "jerinisready", 361 | "name": "JK", 362 | "avatar_url": "https://avatars3.githubusercontent.com/u/19725964?v=4", 363 | "profile": "http://jerinisready.wordpress.com", 364 | "contributions": [ 365 | "code" 366 | ] 367 | }, 368 | { 369 | "login": "jbellsey", 370 | "name": "Jeff Bellsey", 371 | "avatar_url": "https://avatars0.githubusercontent.com/u/1831399?v=4", 372 | "profile": "http://futureground.net", 373 | "contributions": [ 374 | "code" 375 | ] 376 | }, 377 | { 378 | "login": "oznek", 379 | "name": "Kenzo Okamura", 380 | "avatar_url": "https://avatars0.githubusercontent.com/u/55442?v=4", 381 | "profile": "http://blog.oznek.com.br", 382 | "contributions": [ 383 | "code" 384 | ] 385 | }, 386 | { 387 | "login": "mateid", 388 | "name": "Matei Dorobantu", 389 | "avatar_url": "https://avatars3.githubusercontent.com/u/366564?v=4", 390 | "profile": "http://matei.dorobantu.me", 391 | "contributions": [ 392 | "code" 393 | ] 394 | }, 395 | { 396 | "login": "maxkirchoff", 397 | "name": "Max Kirchoff", 398 | "avatar_url": "https://avatars1.githubusercontent.com/u/714146?v=4", 399 | "profile": "http://www.maxisnow.com", 400 | "contributions": [ 401 | "code" 402 | ] 403 | }, 404 | { 405 | "login": "MrEcl", 406 | "name": "MrEcl", 407 | "avatar_url": "https://avatars0.githubusercontent.com/u/8791672?v=4", 408 | "profile": "https://github.com/MrEcl", 409 | "contributions": [ 410 | "code" 411 | ] 412 | }, 413 | { 414 | "login": "RifRaf44", 415 | "name": "Raphaël Vercruyssen", 416 | "avatar_url": "https://avatars1.githubusercontent.com/u/2808250?v=4", 417 | "profile": "https://github.com/RifRaf44", 418 | "contributions": [ 419 | "code" 420 | ] 421 | }, 422 | { 423 | "login": "dazorni", 424 | "name": "Sebastian Zorn", 425 | "avatar_url": "https://avatars2.githubusercontent.com/u/1658380?v=4", 426 | "profile": "http://dazorni.de", 427 | "contributions": [ 428 | "code" 429 | ] 430 | }, 431 | { 432 | "login": "tsbalzhanov", 433 | "name": "tsbalzhanov", 434 | "avatar_url": "https://avatars0.githubusercontent.com/u/34696545?v=4", 435 | "profile": "https://github.com/tsbalzhanov", 436 | "contributions": [ 437 | "code" 438 | ] 439 | }, 440 | { 441 | "login": "bjrenfrow", 442 | "name": "bjrenfrow", 443 | "avatar_url": "https://avatars3.githubusercontent.com/u/3633090?v=4", 444 | "profile": "https://github.com/bjrenfrow", 445 | "contributions": [ 446 | "code" 447 | ] 448 | }, 449 | { 450 | "login": "orhanveli", 451 | "name": "orhan", 452 | "avatar_url": "https://avatars1.githubusercontent.com/u/525103?v=4", 453 | "profile": "https://github.com/orhanveli", 454 | "contributions": [ 455 | "code" 456 | ] 457 | }, 458 | { 459 | "login": "swolfod", 460 | "name": "swolfod", 461 | "avatar_url": "https://avatars3.githubusercontent.com/u/6654724?v=4", 462 | "profile": "https://github.com/swolfod", 463 | "contributions": [ 464 | "code" 465 | ] 466 | }, 467 | { 468 | "login": "keligijus", 469 | "name": "Eligijus Krėpšta", 470 | "avatar_url": "https://avatars1.githubusercontent.com/u/6559757?v=4", 471 | "profile": "https://kodinu.lt", 472 | "contributions": [ 473 | "code" 474 | ] 475 | } 476 | ], 477 | "contributorsPerLine": 7 478 | } 479 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = false 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.json] 12 | indent_size = 2 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device (please complete the following information):** 27 | - Device: [e.g. Desktop] 28 | - OS: [e.g. macOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | 35 | **Link to a reproducible example** 36 | Link to the bug in action - your website, https://codepen.io/, http://jsfiddle.net/ or similar service. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | | Q | A | 2 | | ---------------- | ------------------------------------------------------- | 3 | | Bug fix? | yes/no | 4 | | New feature? | yes/no | 5 | | BC breaks? | yes/no | 6 | | Deprecations? | yes/no | 7 | | New tests added? | yes/not needed | 8 | | Fixed tickets | comma-separated list of tickets fixed by the PR, if any | 9 | | License | MIT | 10 | 11 | **Description** 12 | Description of the bug or feature. 13 | 14 | -- 15 | 16 | **Please, don't submit `/dist` files with your PR!** 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .sass-cache/ 3 | bower_components/ 4 | node_modules/ 5 | examples/uploads/* 6 | .idea/ 7 | server.js 8 | .tm_properties 9 | .project 10 | reports 11 | .grunt/ 12 | _SpecRunner.html 13 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "crockford", 3 | "disallowDanglingUnderscores": false 4 | } 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "MediumEditor": true 4 | }, 5 | 6 | "predef": { 7 | "jQuery": false, 8 | "document": false, 9 | "window": false, 10 | "module": false, 11 | "test": false, 12 | "asyncTest": false, 13 | "ok": false, 14 | "equal": false, 15 | "start": false, 16 | "sinon": false, 17 | "alert": false, 18 | "FB": false, 19 | "describe": false, 20 | "it": false, 21 | "expect": false, 22 | "beforeEach": false, 23 | "afterEach": false, 24 | "jasmine": false, 25 | "spyOn": false, 26 | "placeCaret": false, 27 | "require": false, 28 | "dataURItoBlob": false 29 | }, 30 | 31 | "jquery" : true, 32 | "browser" : true, 33 | 34 | "boss" : false, 35 | "curly": false, 36 | "debug": false, 37 | "devel": false, 38 | "eqeqeq": true, 39 | "evil": false, 40 | "forin": false, 41 | "immed": true, 42 | "laxbreak": false, 43 | "newcap": true, 44 | "noarg": true, 45 | "noempty": false, 46 | "nonew": false, 47 | "plusplus": false, 48 | "undef": true, 49 | "sub": true, 50 | "strict": false, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # faster builds on new travis setup not using sudo 2 | sudo: false 3 | 4 | language: node_js 5 | 6 | node_js: 7 | - "8.8.1" 8 | 9 | before_script: 10 | - npm install -g grunt-cli 11 | - npm install -g bower 12 | - bower install 13 | 14 | cache: 15 | directories: 16 | - node_modules 17 | - bower_components 18 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 2.5.1 / 2019-01-24 2 | 3 | - perf: use object url for image previews (#487) 4 | - chore: update dependencies (#488) 5 | 6 | # 2.5.0 / 2018-02-24 7 | 8 | - Use relative path for uploaded image, instead of absolute (#455) 9 | - DeleteScript can now be a callback (#428) 10 | - Fix embeds caption (#437) 11 | - Fix backspace not triggering delete event (#427) 12 | - Fix insert plugin stripping plain text (#413) 13 | 14 | # 2.4.1. / 2017-04-11 15 | 16 | - Ability to add upload failed callback to options #377 17 | 18 | # 2.4.0. / 2016-08-08 19 | 20 | - Add storeMeta option (#333) 21 | - Remove progressbar on serialize (#375) 22 | - Encode/decode data-embed-code attribute (#374) 23 | - Remove default upload url from the code (#373) 24 | - Fix editor being submitted by clicking on the plugin buttons (#366) 25 | - Fix button position (#359) 26 | 27 | # 2.3.3 / 2016-07-20 28 | 29 | - Fix buttons and toolbars positions when editor is initialized inside absolute positioned container (#356) 30 | - Use buttons instead of links to avoid medium-editor conflicts (#355) 31 | 32 | # 2.3.2 / 2016-04-26 33 | 34 | - Fix UMD (#320) 35 | 36 | # 2.3.1 / 2016-04-19 37 | 38 | - Fix UMD dependencies (#314) 39 | - Fix extending editor's functions (#310, #316) 40 | 41 | # 2.3.0 / 2016-04-12 42 | 43 | - Add auto embedding urls as soon as they are pasted 44 | - Add UMD wrapper and publish to NPM 45 | - Fix deleting images when backspace/delete is pressed 46 | - Re-enable facebook and twitter embedding without oEmbed 47 | - Fix cursor position after image upload 48 | 49 | # 2.2.4 / 2016-04-05 50 | 51 | - Change dependency versioning to enable minor releases of medium-editor 52 | 53 | # 2.2.3 / 2016-04-04 54 | 55 | - Fix performance when uploading images 56 | - Update editor's `updatePlaceholder` function 57 | - Fix initializing plugin when editor's placeholder is turned off 58 | 59 | # 2.2.2 / 2016-02-27 60 | 61 | - Fix serializing html with embed codes containing scripts 62 | 63 | # 2.2.1 / 2016-02-05 64 | 65 | - Fix when `uploadCompleted()` is called - wait until uploaded image replaces preview 66 | - Fix uploading high quality images 67 | - Fix bug when an image toolbar action effects all instances of the editor (when using multiple editors on the same page) 68 | 69 | # 2.2.0 / 2016-01-11 70 | 71 | - Add `fileDeleteOptions` option to images 72 | 73 | # 2.1.1 / 2015-11-23 74 | 75 | - Fix context for sorting function 76 | - Add support for editor's `elementsContainer` option 77 | 78 | # 2.1.0 / 2015-10-27 79 | 80 | - Add support for `editableInput` event 81 | - Add support for textarea 82 | - Add `deleteMethod` option 83 | 84 | # 2.0.1 / 2015-06-11 85 | 86 | - Fix `hideButtons` function 87 | 88 | # 2.0.0 / 2015-06-09 89 | 90 | - **This version works for MediumEditor `v4.12.0` and up, because in `v4.12.0` MediumEditor changed the way to retrieve extension** 91 | - Remove deprecated images options: `uploadScript` and `formData`. Use `fileUploadOptions` instead. 92 | - Add `video-vimeo` and `video-youtube` classes to embedded videos to allow css customization 93 | 94 | # 1.7.0 / 2015-05-11 95 | 96 | - Version bump to 1.7.0, because 1.6.2 added file type and size validation which should cause a minor version bump, not only a patch 97 | 98 | # 1.6.2 / 2015-05-11 99 | 100 | - Fix file type and file size validation 101 | - Add a rotate animation on button 102 | - Speed up Travis build 103 | 104 | # 1.6.1 / 2015-04-30 105 | 106 | - Fix initializing plugin without addons 107 | - Trigger the input event after swapping out the preview image for the real one 108 | - Change default formData to null 109 | 110 | # 1.6.0/ 2015-04-16 111 | 112 | - Add option `captions` (boolean) to images and embeds to enable/disable captions. Captions are enabled by default. 113 | - Expose option `fileUploadOptions` in images to enable file upload configuration 114 | - Use medium-editor like placeholders for embeds and captions (using `:after` CSS pseudo-element instead of actual DOM element) 115 | - Add support for right click on placeholders (both in embeds and captions) to enable pasting via context menu 116 | - Deprecate `uploadScript` and `formData` options in images. Use `fileUploadOptions` instead. 117 | 118 | # 1.5.2 / 2015-04-15 119 | 120 | - Remove contenteditable attr and overlay div from embeds on `serialize` 121 | - Remove contenteditable attr from figure and figcaption in images on `serialize` 122 | - Simplify button positioning 123 | 124 | # 1.5.1 / 2015-04-13 125 | 126 | - Don't add empty paragraph on image deletion, when empty paragraph already exists 127 | - Fix clicking on placeholder in FF 128 | - Hide editor's placeholder on image upload 129 | - Use https for instagram and vimeo 130 | - Ensure core is `enabled` when `selectEmbed` or `selectImage` are called 131 | 132 | # 1.5.0 / 2015-04-02 133 | 134 | - Update medium-editor to v4.1.1 135 | - Add support for editor's new `destroy` and `setup` methods and removed `activatePlaceholder` method 136 | - Add `formData` option to images (see [jQuery-File-Upload docs](https://github.com/blueimp/jQuery-File-Upload/wiki/Options#formdata)) 137 | - Add `uploadCompleted` callback to images 138 | - Trigger `input` event also when preview image is inserted to the DOM 139 | - Disable embed toolbars when `styles`, or `actions` options are empty 140 | - Make sure that after removing caption placeholder, caret is placed inside the caption 141 | - Hide buttons when toolbar actions are triggered 142 | - Don't auto remove grid style when removing images 143 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | require('time-grunt')(grunt); 3 | require('jit-grunt')(grunt, { 4 | usebanner: 'grunt-banner' 5 | }); 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | banner: '/*! \n * <%= pkg.name %> v<%= pkg.version %> - <%= pkg.description %>\n *\n * <%= pkg.homepage %>\n * \n * Copyright (c) 2014 <%= pkg.author.name %> (<%= pkg.author.url %>)\n * Released under the <%= pkg.license %> license\n */\n\n', 10 | 11 | concat: { 12 | dist: { 13 | options: { 14 | banner: '<%= banner %>' 15 | }, 16 | src: ['src/wrappers/start.js', 'src/js/templates.js', 'src/js/core.js', 'src/js/*.js', 'src/wrappers/end.js'], 17 | dest: 'dist/js/<%= pkg.name %>.js' 18 | } 19 | }, 20 | 21 | uglify: { 22 | dist: { 23 | options: { 24 | banner: '<%= banner %>' 25 | }, 26 | src: ['dist/js/<%= pkg.name %>.js'], 27 | dest: 'dist/js/<%= pkg.name %>.min.js' 28 | } 29 | }, 30 | 31 | jscs: { 32 | src: [ 33 | 'src/js/*.js', 34 | 'spec/**/*.js', 35 | '!src/js/templates.js' 36 | ], 37 | options: { 38 | config: '.jscsrc' 39 | } 40 | }, 41 | 42 | jshint: { 43 | options: { 44 | jshintrc: true 45 | }, 46 | files: ['src/js/*.js', '!src/js/templates.js', 'spec/*.js'] 47 | }, 48 | 49 | jasmine: { 50 | suite: { 51 | src: 'src/js/*.js', 52 | options: { 53 | specs: ['spec/*.spec.js'], 54 | helpers: 'spec/helpers/*.js', 55 | vendor: [ 56 | 'bower_components/jquery/dist/jquery.min.js', 57 | 'bower_components/medium-editor/dist/js/medium-editor.min.js', 58 | 'bower_components/handlebars/handlebars.runtime.min.js', 59 | 'bower_components/blueimp-file-upload/js/vendor/jquery.ui.widget.js', 60 | 'bower_components/blueimp-file-upload/js/jquery.iframe-transport.js', 61 | 'bower_components/blueimp-file-upload/js/jquery.fileupload.js', 62 | 'bower_components/jquery-sortable/source/js/jquery-sortable-min.js' 63 | ], 64 | template: require('grunt-template-jasmine-istanbul'), 65 | templateOptions: { 66 | coverage: 'reports/jasmine/coverage.json', 67 | report: [{ 68 | type: 'lcov', 69 | options: { 70 | dir: 'reports/jasmine/lcov' 71 | } 72 | }] 73 | }, 74 | summary: true 75 | } 76 | } 77 | }, 78 | 79 | sass: { 80 | options: { 81 | implementation: require('node-sass') 82 | }, 83 | dist: { 84 | files: [{ 85 | expand: true, 86 | cwd: 'src/sass/', 87 | src: ['*.scss'], 88 | dest: 'dist/css/', 89 | ext: '.css' 90 | }] 91 | } 92 | }, 93 | 94 | autoprefixer: { 95 | dist: { 96 | src: 'dist/css/*.css' 97 | } 98 | }, 99 | 100 | csso: { 101 | dist: { 102 | options: { 103 | banner: '<%= banner %>' 104 | }, 105 | expand: true, 106 | cwd: 'dist/css/', 107 | src: ['*.css', '!*.min.css'], 108 | dest: 'dist/css/', 109 | ext: '.min.css' 110 | } 111 | }, 112 | 113 | usebanner: { 114 | dist: { 115 | options: { 116 | banner: '<%= banner %>', 117 | linebreak: false 118 | }, 119 | files: [{ 120 | expand: true, 121 | cwd: 'dist/css/', 122 | src: ['*.css', '!*.min.css'], 123 | dest: 'dist/css/', 124 | ext: '.css' 125 | }] 126 | } 127 | }, 128 | 129 | watch: { 130 | styles: { 131 | files: 'src/sass/**/*.scss', 132 | tasks: ['css'], 133 | options: { 134 | debounceDelay: 250 135 | } 136 | }, 137 | templates: { 138 | files: 'src/js/templates/**/*.hbs', 139 | tasks: ['handlebars'], 140 | options: { 141 | debounceDelay: 250 142 | } 143 | } 144 | }, 145 | 146 | handlebars: { 147 | compile: { 148 | options: { 149 | namespace: 'MediumInsert.Templates' 150 | }, 151 | files: { 152 | 'src/js/templates.js': 'src/js/templates/*.hbs' 153 | } 154 | } 155 | } 156 | }); 157 | 158 | grunt.registerTask('test', ['jscs', 'jshint', 'jasmine']); 159 | grunt.registerTask('js', ['test', 'handlebars', 'concat', 'uglify']); 160 | grunt.registerTask('css', ['sass', 'autoprefixer', 'csso', 'usebanner']); 161 | grunt.registerTask('default', ['js', 'css']); 162 | }; 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Pavel Linkesch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | ## How to add contributors to README 4 | 5 | After every pull request merge, use [all-contributors bot](https://all-contributors.js.org) to update contributors table in README. Do that by commenting in the same pull request the following message: 6 | 7 | `@all-contributors please add @[name of the contributor] for [code|doc|or other type]` 8 | 9 | Types of contribution are described in [all-contributors' docs](https://all-contributors.js.org/docs/emoji-key). Most commonly used ones are: `code` and `doc`. 10 | 11 | The bot will automatically create a new pull request with changes to README. Merge that to master and you're done. 12 | 13 | ## How to release a new version 14 | 15 | 1. Update changelog: 16 | 17 | a. Run [`git changelog`](https://github.com/tj/git-extras) or 18 | b. Find the last release commit in log history. Look through all the commits or PR history and see all the stuff that has happened since the last release. Add a row describing each high-level change into CHANGES.md. 19 | 20 | 2. Depending upon the changes, decide if it is a major/minor/patch release ([semantic versioning](http://semver.org/)). 21 | 3. Update version number in package.json and bower.json. 22 | 4. Run `grunt` to build dist files. 23 | 5. Commit all your changes (including CHANGES.md) into your commit. Add the new release number into your commit message. And push it up to the remote master branch. 24 | 6. [Go here](https://github.com/orthes/medium-editor-insert-plugin/releases) and ‘Draft a new release’. Title the release as the new release number (ex: 5.11.0). Copy/paste the entries you made in CHANGES.md into the release summary. Make sure the release is against the master branch. 25 | 7. Once the release is created, go back to your git and run `npm publish`. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery insert plugin for MediumEditor 2 | 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/orthes/medium-editor-insert-plugin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.org/orthes/medium-editor-insert-plugin.svg?branch=master)](https://travis-ci.org/orthes/medium-editor-insert-plugin) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/1f8565ed2e554e4fa952ec4da6a2080b)](https://www.codacy.com/app/orthes_3082/medium-editor-insert-plugin?utm_source=github.com&utm_medium=referral&utm_content=orthes/medium-editor-insert-plugin&utm_campaign=Badge_Grade) [![CDNJS](https://img.shields.io/cdnjs/v/medium-editor-insert-plugin.svg)](https://cdnjs.com/libraries/medium-editor-insert-plugin) 4 | 5 | This plugin expands capabilities of [MediumEditor](https://github.com/yabwe/medium-editor) (a clone of medium.com WYSIWYG editor) and it enables users to insert into the editor various types of content (depending on available addons). 6 | 7 | Current available addons: 8 | 9 | - **images** 10 | - **embeds** (either through oEmbed proxy, such as [Iframely](https://iframely.com/), or pre-defined parsers such as - Youtube, Vimeo, Twitter, Facebook, Instagram) 11 | 12 | 13 | ## Demo 14 | 15 | [http://orthes.github.io/medium-editor-insert-plugin](http://orthes.github.io/medium-editor-insert-plugin) 16 | 17 | 18 | ## Download 19 | 20 | **Via npm:** 21 | 22 | `npm install medium-editor-insert-plugin --save` 23 | 24 | **Via bower:** 25 | 26 | `bower install medium-editor-insert-plugin --save` 27 | 28 | **Manual:** 29 | 30 | [Download the latest release](https://github.com/orthes/medium-editor-insert-plugin/releases) 31 | 32 | 33 | ## Quick Start 34 | 35 | The first step is to include the plugin with all its dependencies to your code: 36 | 37 | ```html 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 75 | 76 | ``` 77 | 78 | 79 | 80 | Initialize MediumEditor as you normally would: 81 | 82 | ```javascript 83 | var editor = new MediumEditor('.editable'); 84 | ``` 85 | 86 | Finally, you can initialize the insert plugin: 87 | 88 | ```javascript 89 | $(function () { 90 | $('.editable').mediumInsert({ 91 | editor: editor 92 | }); 93 | }); 94 | ``` 95 | 96 | ## [Documentation](https://github.com/orthes/medium-editor-insert-plugin/wiki) 97 | 98 | - [Getting Started](https://github.com/orthes/medium-editor-insert-plugin/wiki/v2.x-Getting-Started) 99 | - [Configuration](https://github.com/orthes/medium-editor-insert-plugin/wiki/v2.x-Configuration) 100 | - [Using with webpack](https://github.com/orthes/medium-editor-insert-plugin/wiki/v2.x-Using-with-webpack) 101 | - [Server response](https://github.com/orthes/medium-editor-insert-plugin/wiki/v2.x-Server-response) 102 | - [Custom addons](https://github.com/orthes/medium-editor-insert-plugin/wiki/v2.x-Custom-addons) 103 | - [Upgrading from v0.3](https://github.com/orthes/medium-editor-insert-plugin/wiki/v2.x-Upgrading-from-v0.3) 104 | - [Versioning](https://github.com/orthes/medium-editor-insert-plugin/wiki/Versioning) 105 | - [Development & Contributing](https://github.com/orthes/medium-editor-insert-plugin/wiki/Development-&-Contributing) 106 | - [License](https://github.com/orthes/medium-editor-insert-plugin/wiki/License) 107 | 108 | ## Contributors 109 | 110 | Thanks goes to these wonderful people ([emoji key](https://github.com/all-contributors/all-contributors#emoji-key)): 111 | 112 | 113 | 114 | | [Pavel Linkesch
Pavel Linkesch](http://linkesch.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=orthes "Code") [📖](https://github.com/orthes/medium-editor-insert-plugin/commits?author=orthes "Documentation") [🚧](#maintenance-orthes "Maintenance") [👀](#review-orthes "Reviewed Pull Requests") | [Jérémy Benoist
Jérémy Benoist](http://www.j0k3r.net)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=j0k3r "Code") [🚧](#maintenance-j0k3r "Maintenance") [👀](#review-j0k3r "Reviewed Pull Requests") | [Nazar Leush
Nazar Leush](https://github.com/nleush)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=nleush "Code") | [Andrey Sitnik
Andrey Sitnik](http://twitter.com/sitnikcode)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=ai "Code") | [Jeremy Epstein
Jeremy Epstein](http://greenash.net.au/)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=Jaza "Code") | [Hikaru Tooyama
Hikaru Tooyama](https://github.com/vexus2)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=vexus2 "Code") | [Alexandr Subbotin
Alexandr Subbotin](https://twitter.com/asubbotin)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=KELiON "Code") | 115 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | 116 | | [Vinicius Patrinhani
Vinicius Patrinhani](https://github.com/patrinhani-ciandt)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=patrinhani-ciandt "Code") | [Firas Bessadok
Firas Bessadok](http://firas.bessadok.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=fbessadok "Code") | [Omnia G Helmi
Omnia G Helmi](http://omniagm.github.io/)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=OmniaGM "Code") | [Daniel Huang
Daniel Huang](https://github.com/daniel-huang)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=daniel-huang "Code") | [Alex Xandra Albert Sim
Alex Xandra Albert Sim](https://bertzzie.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=bertzzie "Code") | [Brandon Renfrow
Brandon Renfrow](https://github.com/brenfrow)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=brenfrow "Code") | [BurnHavoc
BurnHavoc](https://github.com/BurnHavoc)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=BurnHavoc "Code") | 117 | | [Enzo
Enzo](http://enzoz.me/)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=enzoz "Code") | [Jonathon Sim
Jonathon Sim](http://idealstack.io)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=jonathonsim "Code") | [Kazuya Hara
Kazuya Hara](https://kazuyahara.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=KazuyaHara "Code") | [Miloš Hadžić
Miloš Hadžić](https://rightfold.io)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=miloshadzic "Code") | [Siron
Siron](https://github.com/Siron)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=Siron "Code") | [Sam Auciello
Sam Auciello](http://antha.site)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=olleicua "Code") | [Sean Cashin
Sean Cashin](https://github.com/scashin133)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=scashin133 "Code") | 118 | | [Yu Zhai
Yu Zhai](https://github.com/jackyzhai)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=jackyzhai "Code") | [acekat
acekat](https://github.com/acekat)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=acekat "Code") | [linpekka
linpekka](https://github.com/linpekka)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=linpekka "Code") | [sainu
sainu](http://sa-inu.com/)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=sainuio "Code") | [Murat Tasarsu
Murat Tasarsu](http://medya-t.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=tasarsu "Code") | [Anil Kumar Maurya
Anil Kumar Maurya](http://anilmaurya.herokuapp.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=anilmaurya "Code") | [Artem Shevtsov
Artem Shevtsov](https://github.com/artshevtsov)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=artshevtsov "Code") | 119 | | [Baptiste Gaillard
Baptiste Gaillard](https://github.com/bgaillard)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=bgaillard "Code") | [Bernard Wolff
Bernard Wolff](https://github.com/bernardwolff)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=bernardwolff "Code") | [Carl Scott
Carl Scott](http://gogocarl.blogspot.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=GoGoCarl "Code") | [Chris Joe
Chris Joe](https://github.com/flamerohr)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=flamerohr "Code") | [Daniel Wang
Daniel Wang](https://github.com/pvnr0082t)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=pvnr0082t "Code") | [Derrek Bertrand
Derrek Bertrand](http://derrekbertrand.com/)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=derrekbertrand "Code") | [Gabi Maeztu
Gabi Maeztu](http://merqur.io)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=merqurio "Code") | 120 | | [Hussein Jafferjee
Hussein Jafferjee](http://inssein.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=inssein "Code") | [Indra Santosa
Indra Santosa](http://indrasantosa.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=indrasantosa "Code") | [Ivan Paramonau
Ivan Paramonau](http://twitter.com/iparamonau)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=iparamonau "Code") | [JK
JK](http://jerinisready.wordpress.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=jerinisready "Code") | [Jeff Bellsey
Jeff Bellsey](http://futureground.net)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=jbellsey "Code") | [Kenzo Okamura
Kenzo Okamura](http://blog.oznek.com.br)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=oznek "Code") | [Matei Dorobantu
Matei Dorobantu](http://matei.dorobantu.me)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=mateid "Code") | 121 | | [Max Kirchoff
Max Kirchoff](http://www.maxisnow.com)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=maxkirchoff "Code") | [MrEcl
MrEcl](https://github.com/MrEcl)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=MrEcl "Code") | [Raphaël Vercruyssen
Raphaël Vercruyssen](https://github.com/RifRaf44)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=RifRaf44 "Code") | [Sebastian Zorn
Sebastian Zorn](http://dazorni.de)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=dazorni "Code") | [tsbalzhanov
tsbalzhanov](https://github.com/tsbalzhanov)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=tsbalzhanov "Code") | [bjrenfrow
bjrenfrow](https://github.com/bjrenfrow)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=bjrenfrow "Code") | [orhan
orhan](https://github.com/orhanveli)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=orhanveli "Code") | 122 | | [swolfod
swolfod](https://github.com/swolfod)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=swolfod "Code") | [Eligijus Krėpšta
Eligijus Krėpšta](https://kodinu.lt)
[💻](https://github.com/orthes/medium-editor-insert-plugin/commits?author=keligijus "Code") | 123 | 124 | 125 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 126 | 127 | ## License 128 | 129 | [MIT](https://github.com/orthes/medium-editor-insert-plugin/blob/master/LICENSE) 130 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medium-editor-insert-plugin", 3 | "version": "2.5.1", 4 | "description": "jQuery insert plugin for MediumEditor", 5 | "homepage": "http://linkesch.com/medium-editor-insert-plugin", 6 | "main": [ 7 | "dist/js/medium-editor-insert-plugin.js", 8 | "dist/css/medium-editor-insert-plugin.css" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | "Pavel linkesch (http://linkesch.com)", 13 | "Jeremy Benoist " 14 | ], 15 | "moduleType": ["amd", "globals", "node"], 16 | "keywords": [ 17 | "contenteditable", 18 | "editor", 19 | "medium", 20 | "wysiwyg", 21 | "rich-text", 22 | "images", 23 | "embeds", 24 | "videos" 25 | ], 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "spec", 31 | "examples", 32 | "Gruntfile.js", 33 | "package.json" 34 | ], 35 | "dependencies": { 36 | "jquery": ">=1.9.0", 37 | "medium-editor": "^5.15.0", 38 | "handlebars": "~4.0.12", 39 | "blueimp-file-upload": "~9.28.0", 40 | "jquery-sortable": "~0.9.12" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dist/css/medium-editor-insert-plugin-frontend.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * medium-editor-insert-plugin v2.5.1 - jQuery insert plugin for MediumEditor 3 | * 4 | * http://linkesch.com/medium-editor-insert-plugin 5 | * 6 | * Copyright (c) 2014 Pavel Linkesch (http://linkesch.com) 7 | * Released under the MIT license 8 | */ 9 | 10 | .medium-insert-images, .mediumInsert { 11 | text-align: center; } 12 | .medium-insert-images figure, .mediumInsert figure { 13 | margin: 0; 14 | display: block; } 15 | .medium-insert-images figure img, .mediumInsert figure img { 16 | max-width: 100%; 17 | margin-top: 1em; 18 | vertical-align: top; } 19 | .medium-insert-images figure:first-child img, .mediumInsert figure:first-child img { 20 | margin-top: 0; } 21 | .medium-insert-images.medium-insert-images-left, .medium-insert-images-left.mediumInsert, .mediumInsert.small { 22 | max-width: 33.33%; 23 | float: left; 24 | margin: 0 30px 20px 0; } 25 | .medium-insert-images.medium-insert-images-right, .medium-insert-images-right.mediumInsert { 26 | max-width: 33.33%; 27 | float: right; 28 | margin: 0 0 20px 30px; } 29 | .medium-insert-images.medium-insert-images-grid, .medium-insert-images-grid.mediumInsert { 30 | display: -ms-flexbox; 31 | display: flex; 32 | -ms-flex-wrap: wrap; 33 | flex-wrap: wrap; 34 | -ms-flex-align: start; 35 | align-items: flex-start; 36 | -ms-flex-pack: center; 37 | justify-content: center; 38 | margin: 0.5em -0.5em; } 39 | .medium-insert-images.medium-insert-images-grid figure, .medium-insert-images-grid.mediumInsert figure { 40 | width: 33.33%; 41 | display: inline-block; } 42 | .medium-insert-images.medium-insert-images-grid figure img, .medium-insert-images-grid.mediumInsert figure img { 43 | max-width: calc(100% - 1em); 44 | margin: 0.5em; } 45 | 46 | .medium-insert-embeds, .mediumInsert-embeds { 47 | text-align: center; 48 | margin: 1em 0; 49 | position: relative; } 50 | .medium-insert-embeds iframe, .mediumInsert-embeds iframe { 51 | margin: 0 auto !important; } 52 | .medium-insert-embeds div, .mediumInsert-embeds div { 53 | margin: 0 auto !important; } 54 | .medium-insert-embeds.medium-insert-embeds-left, .medium-insert-embeds-left.mediumInsert-embeds { 55 | width: 33.33%; 56 | float: left; 57 | margin: 0 30px 20px 0; } 58 | .medium-insert-embeds.medium-insert-embeds-right, .medium-insert-embeds-right.mediumInsert-embeds { 59 | width: 33.33%; 60 | float: right; 61 | margin: 0 0 20px 30px; } 62 | 63 | .medium-insert-images figure, .mediumInsert figure, .medium-insert-embeds figure, .mediumInsert-embeds figure { 64 | position: relative; } 65 | .medium-insert-images figure figcaption, .mediumInsert figure figcaption, .medium-insert-embeds figure figcaption, .mediumInsert-embeds figure figcaption { 66 | position: relative; 67 | z-index: 1; 68 | display: block; 69 | text-align: center; 70 | margin: 10px 0; 71 | color: #ccc; 72 | font-size: 0.8em; 73 | font-style: italic; 74 | outline: 0 solid transparent; } 75 | .medium-insert-images figure figcaption:focus, .mediumInsert figure figcaption:focus, .medium-insert-embeds figure figcaption:focus, .mediumInsert-embeds figure figcaption:focus { 76 | outline: 0 solid transparent; } 77 | -------------------------------------------------------------------------------- /dist/css/medium-editor-insert-plugin-frontend.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * medium-editor-insert-plugin v2.5.1 - jQuery insert plugin for MediumEditor 3 | * 4 | * http://linkesch.com/medium-editor-insert-plugin 5 | * 6 | * Copyright (c) 2014 Pavel Linkesch (http://linkesch.com) 7 | * Released under the MIT license 8 | */ 9 | 10 | .medium-insert-images,.mediumInsert{text-align:center}.medium-insert-images figure,.mediumInsert figure{margin:0;display:block}.medium-insert-images figure img,.mediumInsert figure img{max-width:100%;margin-top:1em;vertical-align:top}.medium-insert-images figure:first-child img,.mediumInsert figure:first-child img{margin-top:0}.medium-insert-images-left.mediumInsert,.medium-insert-images.medium-insert-images-left,.mediumInsert.small{max-width:33.33%;float:left;margin:0 30px 20px 0}.medium-insert-images-right.mediumInsert,.medium-insert-images.medium-insert-images-right{max-width:33.33%;float:right;margin:0 0 20px 30px}.medium-insert-images-grid.mediumInsert,.medium-insert-images.medium-insert-images-grid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center;margin:.5em -.5em}.medium-insert-images-grid.mediumInsert figure,.medium-insert-images.medium-insert-images-grid figure{width:33.33%;display:inline-block}.medium-insert-images-grid.mediumInsert figure img,.medium-insert-images.medium-insert-images-grid figure img{max-width:calc(100% - 1em);margin:.5em}.medium-insert-embeds,.mediumInsert-embeds{text-align:center;margin:1em 0;position:relative}.medium-insert-embeds div,.medium-insert-embeds iframe,.mediumInsert-embeds div,.mediumInsert-embeds iframe{margin:0 auto!important}.medium-insert-embeds-left.mediumInsert-embeds,.medium-insert-embeds.medium-insert-embeds-left{width:33.33%;float:left;margin:0 30px 20px 0}.medium-insert-embeds-right.mediumInsert-embeds,.medium-insert-embeds.medium-insert-embeds-right{width:33.33%;float:right;margin:0 0 20px 30px}.medium-insert-embeds figure,.medium-insert-images figure,.mediumInsert figure,.mediumInsert-embeds figure{position:relative}.medium-insert-embeds figure figcaption,.medium-insert-images figure figcaption,.mediumInsert figure figcaption,.mediumInsert-embeds figure figcaption{position:relative;z-index:1;display:block;text-align:center;margin:10px 0;color:#ccc;font-size:.8em;font-style:italic;outline:0 solid transparent}.medium-insert-embeds figure figcaption:focus,.medium-insert-images figure figcaption:focus,.mediumInsert figure figcaption:focus,.mediumInsert-embeds figure figcaption:focus{outline:0 solid transparent} -------------------------------------------------------------------------------- /dist/css/medium-editor-insert-plugin.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * medium-editor-insert-plugin v2.5.1 - jQuery insert plugin for MediumEditor 3 | * 4 | * http://linkesch.com/medium-editor-insert-plugin 5 | * 6 | * Copyright (c) 2014 Pavel Linkesch (http://linkesch.com) 7 | * Released under the MIT license 8 | */ 9 | 10 | .medium-insert-images, .mediumInsert { 11 | text-align: center; } 12 | .medium-insert-images figure, .mediumInsert figure { 13 | margin: 0; 14 | display: block; } 15 | .medium-insert-images figure img, .mediumInsert figure img { 16 | max-width: 100%; 17 | margin-top: 1em; 18 | vertical-align: top; } 19 | .medium-insert-images figure:first-child img, .mediumInsert figure:first-child img { 20 | margin-top: 0; } 21 | .medium-insert-images.medium-insert-images-left, .medium-insert-images-left.mediumInsert, .mediumInsert.small { 22 | max-width: 33.33%; 23 | float: left; 24 | margin: 0 30px 20px 0; } 25 | .medium-insert-images.medium-insert-images-right, .medium-insert-images-right.mediumInsert { 26 | max-width: 33.33%; 27 | float: right; 28 | margin: 0 0 20px 30px; } 29 | .medium-insert-images.medium-insert-images-grid, .medium-insert-images-grid.mediumInsert { 30 | display: -ms-flexbox; 31 | display: flex; 32 | -ms-flex-wrap: wrap; 33 | flex-wrap: wrap; 34 | -ms-flex-align: start; 35 | align-items: flex-start; 36 | -ms-flex-pack: center; 37 | justify-content: center; 38 | margin: 0.5em -0.5em; } 39 | .medium-insert-images.medium-insert-images-grid figure, .medium-insert-images-grid.mediumInsert figure { 40 | width: 33.33%; 41 | display: inline-block; } 42 | .medium-insert-images.medium-insert-images-grid figure img, .medium-insert-images-grid.mediumInsert figure img { 43 | max-width: calc(100% - 1em); 44 | margin: 0.5em; } 45 | 46 | .medium-insert-embeds, .mediumInsert-embeds { 47 | text-align: center; 48 | margin: 1em 0; 49 | position: relative; } 50 | .medium-insert-embeds iframe, .mediumInsert-embeds iframe { 51 | margin: 0 auto !important; } 52 | .medium-insert-embeds div, .mediumInsert-embeds div { 53 | margin: 0 auto !important; } 54 | .medium-insert-embeds.medium-insert-embeds-left, .medium-insert-embeds-left.mediumInsert-embeds { 55 | width: 33.33%; 56 | float: left; 57 | margin: 0 30px 20px 0; } 58 | .medium-insert-embeds.medium-insert-embeds-right, .medium-insert-embeds-right.mediumInsert-embeds { 59 | width: 33.33%; 60 | float: right; 61 | margin: 0 0 20px 30px; } 62 | 63 | .medium-insert-images figure, .mediumInsert figure, .medium-insert-embeds figure, .mediumInsert-embeds figure { 64 | position: relative; } 65 | .medium-insert-images figure figcaption, .mediumInsert figure figcaption, .medium-insert-embeds figure figcaption, .mediumInsert-embeds figure figcaption { 66 | position: relative; 67 | z-index: 1; 68 | display: block; 69 | text-align: center; 70 | margin: 10px 0; 71 | color: #ccc; 72 | font-size: 0.8em; 73 | font-style: italic; 74 | outline: 0 solid transparent; } 75 | .medium-insert-images figure figcaption:focus, .mediumInsert figure figcaption:focus, .medium-insert-embeds figure figcaption:focus, .mediumInsert-embeds figure figcaption:focus { 76 | outline: 0 solid transparent; } 77 | 78 | .medium-editor-insert-plugin { 79 | outline: 0 solid transparent; } 80 | .medium-editor-insert-plugin:focus { 81 | outline: 0 solid transparent; } 82 | .medium-editor-insert-plugin .clearfix:before, .medium-editor-insert-plugin:before, .medium-editor-insert-plugin .clearfix:after, .medium-editor-insert-plugin:after { 83 | content: " "; 84 | display: table; 85 | clear: both; } 86 | .medium-editor-insert-plugin p { 87 | margin: 1em 0; } 88 | .medium-editor-insert-plugin progress { 89 | display: block; 90 | margin: 1em auto; } 91 | .medium-editor-insert-plugin .hide { 92 | display: none; } 93 | .medium-editor-insert-plugin.medium-editor-placeholder:after { 94 | padding: 1em 0; } 95 | .medium-editor-insert-plugin .medium-insert-buttons { 96 | position: absolute; 97 | color: #ddd; 98 | font-size: 0.9em; } 99 | .medium-editor-insert-plugin .medium-insert-buttons button { 100 | display: block; 101 | cursor: pointer; 102 | color: #ddd; 103 | background: #fff; 104 | width: 32px; 105 | height: 32px; 106 | box-sizing: border-box; 107 | border-radius: 20px; 108 | border: 1px solid #ddd; 109 | line-height: 30px; 110 | text-align: center; 111 | padding: 0; } 112 | .medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-show { 113 | font-size: 25px; 114 | transform: rotate(0); 115 | transition: transform 100ms; } 116 | .medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-show span { 117 | display: block; 118 | margin-top: -4px; } 119 | .medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-show.medium-insert-buttons-rotate { 120 | transition: transform 250ms; 121 | transform: rotate(45deg); } 122 | .medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-addons { 123 | margin: 0; 124 | padding: 0; 125 | list-style: none; 126 | display: none; 127 | position: relative; 128 | z-index: 2; 129 | left: 55px; 130 | top: -32px; } 131 | .medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-addons li { 132 | display: inline-block; 133 | margin: 0 5px; } 134 | .medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-addons li .fa { 135 | font-size: 15px; } 136 | 137 | .medium-insert-caption-placeholder { 138 | position: relative; } 139 | .medium-insert-caption-placeholder:after { 140 | position: absolute; 141 | top: 0; 142 | left: 0; 143 | width: 100%; 144 | text-align: center; 145 | content: attr(data-placeholder); } 146 | 147 | .dragging { 148 | cursor: move; } 149 | 150 | .medium-insert-image-active { 151 | outline: 2px solid #000; } 152 | 153 | .medium-insert-images-toolbar { 154 | display: none; } 155 | 156 | .medium-insert-images, .mediumInsert { 157 | margin: 1em 0; } 158 | .medium-insert-images .dragged, .mediumInsert .dragged { 159 | position: absolute; 160 | top: 0; 161 | opacity: .5; 162 | z-index: 2000; } 163 | .medium-insert-images .placeholder, .mediumInsert .placeholder { 164 | position: relative; 165 | margin: 0; 166 | padding: 0; 167 | border: none; } 168 | .medium-insert-images .medium-insert-images-progress, .mediumInsert .medium-insert-images-progress { 169 | position: absolute; 170 | width: 100%; 171 | height: 100%; 172 | top: 0; 173 | right: 0; 174 | background: rgba(255, 255, 255, 0.4); } 175 | 176 | .medium-insert-embeds-input { 177 | position: relative; 178 | color: #ccc; 179 | z-index: 1; 180 | text-align: left; } 181 | 182 | .medium-insert-embeds-placeholder { 183 | position: relative; } 184 | .medium-insert-embeds-placeholder:after { 185 | position: absolute; 186 | top: 0; 187 | left: 0; 188 | content: attr(data-placeholder); 189 | color: #ccc; } 190 | 191 | .medium-insert-embeds-selected .medium-insert-embed { 192 | outline: 2px solid #000; } 193 | 194 | .medium-insert-embeds-toolbar { 195 | display: none; } 196 | 197 | .medium-insert-embeds .medium-insert-embeds-overlay, .mediumInsert-embeds .medium-insert-embeds-overlay { 198 | position: absolute; 199 | width: 100%; 200 | height: 100%; 201 | top: 0; 202 | left: 0; } 203 | -------------------------------------------------------------------------------- /dist/css/medium-editor-insert-plugin.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * medium-editor-insert-plugin v2.5.1 - jQuery insert plugin for MediumEditor 3 | * 4 | * http://linkesch.com/medium-editor-insert-plugin 5 | * 6 | * Copyright (c) 2014 Pavel Linkesch (http://linkesch.com) 7 | * Released under the MIT license 8 | */ 9 | 10 | .medium-insert-images,.mediumInsert{text-align:center}.medium-insert-images figure,.mediumInsert figure{margin:0;display:block}.medium-insert-images figure img,.mediumInsert figure img{max-width:100%;margin-top:1em;vertical-align:top}.medium-insert-images figure:first-child img,.mediumInsert figure:first-child img{margin-top:0}.medium-insert-images-left.mediumInsert,.medium-insert-images.medium-insert-images-left,.mediumInsert.small{max-width:33.33%;float:left;margin:0 30px 20px 0}.medium-insert-images-right.mediumInsert,.medium-insert-images.medium-insert-images-right{max-width:33.33%;float:right;margin:0 0 20px 30px}.medium-insert-images-grid.mediumInsert,.medium-insert-images.medium-insert-images-grid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center;margin:.5em -.5em}.medium-insert-images-grid.mediumInsert figure,.medium-insert-images.medium-insert-images-grid figure{width:33.33%;display:inline-block}.medium-insert-images-grid.mediumInsert figure img,.medium-insert-images.medium-insert-images-grid figure img{max-width:calc(100% - 1em);margin:.5em}.medium-insert-embeds,.mediumInsert-embeds{text-align:center;margin:1em 0;position:relative}.medium-insert-embeds div,.medium-insert-embeds iframe,.mediumInsert-embeds div,.mediumInsert-embeds iframe{margin:0 auto!important}.medium-insert-embeds-left.mediumInsert-embeds,.medium-insert-embeds.medium-insert-embeds-left{width:33.33%;float:left;margin:0 30px 20px 0}.medium-insert-embeds-right.mediumInsert-embeds,.medium-insert-embeds.medium-insert-embeds-right{width:33.33%;float:right;margin:0 0 20px 30px}.medium-insert-embeds figure,.medium-insert-images figure,.mediumInsert figure,.mediumInsert-embeds figure{position:relative}.medium-insert-embeds figure figcaption,.medium-insert-images figure figcaption,.mediumInsert figure figcaption,.mediumInsert-embeds figure figcaption{position:relative;z-index:1;display:block;text-align:center;margin:10px 0;color:#ccc;font-size:.8em;font-style:italic;outline:0 solid transparent}.medium-editor-insert-plugin:focus,.medium-insert-embeds figure figcaption:focus,.medium-insert-images figure figcaption:focus,.mediumInsert figure figcaption:focus,.mediumInsert-embeds figure figcaption:focus{outline:0 solid transparent}.medium-editor-insert-plugin{outline:0 solid transparent}.medium-editor-insert-plugin .clearfix:after,.medium-editor-insert-plugin .clearfix:before,.medium-editor-insert-plugin:after,.medium-editor-insert-plugin:before{content:" ";display:table;clear:both}.medium-editor-insert-plugin p{margin:1em 0}.medium-editor-insert-plugin progress{display:block;margin:1em auto}.medium-editor-insert-plugin .hide{display:none}.medium-editor-insert-plugin.medium-editor-placeholder:after{padding:1em 0}.medium-editor-insert-plugin .medium-insert-buttons{position:absolute;color:#ddd;font-size:.9em}.medium-editor-insert-plugin .medium-insert-buttons button{display:block;cursor:pointer;color:#ddd;background:#fff;width:32px;height:32px;box-sizing:border-box;border-radius:20px;border:1px solid #ddd;line-height:30px;text-align:center;padding:0}.medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-show{font-size:25px;transform:rotate(0);transition:transform 100ms}.medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-show span{display:block;margin-top:-4px}.medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-show.medium-insert-buttons-rotate{transition:transform 250ms;transform:rotate(45deg)}.medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-addons{margin:0;padding:0;list-style:none;display:none;position:relative;z-index:2;left:55px;top:-32px}.medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-addons li{display:inline-block;margin:0 5px}.medium-editor-insert-plugin .medium-insert-buttons .medium-insert-buttons-addons li .fa{font-size:15px}.medium-insert-caption-placeholder{position:relative}.medium-insert-caption-placeholder:after{position:absolute;top:0;left:0;width:100%;text-align:center;content:attr(data-placeholder)}.dragging{cursor:move}.medium-insert-image-active{outline:2px solid #000}.medium-insert-images-toolbar{display:none}.medium-insert-images,.mediumInsert{margin:1em 0}.medium-insert-images .dragged,.mediumInsert .dragged{position:absolute;top:0;opacity:.5;z-index:2000}.medium-insert-images .placeholder,.mediumInsert .placeholder{position:relative;margin:0;padding:0;border:0}.medium-insert-images .medium-insert-images-progress,.mediumInsert .medium-insert-images-progress{position:absolute;width:100%;height:100%;top:0;right:0;background:rgba(255,255,255,.4)}.medium-insert-embeds-input{position:relative;color:#ccc;z-index:1;text-align:left}.medium-insert-embeds-placeholder{position:relative}.medium-insert-embeds-placeholder:after{position:absolute;top:0;left:0;content:attr(data-placeholder);color:#ccc}.medium-insert-embeds-selected .medium-insert-embed{outline:2px solid #000}.medium-insert-embeds-toolbar{display:none}.medium-insert-embeds .medium-insert-embeds-overlay,.mediumInsert-embeds .medium-insert-embeds-overlay{position:absolute;width:100%;height:100%;top:0;left:0} -------------------------------------------------------------------------------- /examples/absolute-container.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | medium-editor-insert-plugin | example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 44 | 45 | 46 | Fork me on GitHub 47 | 48 |
49 |
50 |

jQuery insert plugin for MediumEditor
inside absolute container

51 | 52 |

My father’s family name being Pirrip, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.

53 | 54 |
55 |
56 | 57 |
So, I called myself Pip, and came to be called Pip.
58 |
59 |
60 | 61 |

I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.

62 | 63 |

Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.

64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /examples/css/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | font-size: 22px; 4 | line-height: 30px; 5 | } 6 | 7 | #container { 8 | max-width: 960px; 9 | margin: 30px auto; 10 | padding: 0 15px; 11 | } 12 | 13 | .editable { 14 | outline: none; 15 | min-height: 38px; 16 | margin: 0 0 20px 0; 17 | padding: 0 0 20px 0; 18 | } 19 | 20 | h2 { 21 | font-size: 32px; 22 | line-height: 42px; 23 | text-align: center; 24 | } 25 | 26 | h3 { 27 | font-size: 26px; 28 | line-height: 32px; 29 | } 30 | 31 | blockquote { 32 | display: block; 33 | margin: 0.5em 0 0.5em -25px; 34 | border-left: 5px solid #efefef; 35 | padding: 0.5em 20px; 36 | color: #999; 37 | } 38 | 39 | footer { 40 | border-top: 5px solid #efefef; 41 | padding-top: 30px; 42 | text-align: center; 43 | } 44 | 45 | footer a { 46 | color: #000; 47 | padding: 0 5px; 48 | } 49 | 50 | footer .fa { 51 | font-size: 2.5em; 52 | } -------------------------------------------------------------------------------- /examples/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address styling not present in IE 8/9. 48 | */ 49 | 50 | [hidden] { 51 | display: none; 52 | } 53 | 54 | /* ========================================================================== 55 | Base 56 | ========================================================================== */ 57 | 58 | /** 59 | * 1. Prevent system color scheme's background color being used in Firefox, IE, 60 | * and Opera. 61 | * 2. Prevent system color scheme's text color being used in Firefox, IE, and 62 | * Opera. 63 | * 3. Set default font family to sans-serif. 64 | * 4. Prevent iOS text size adjust after orientation change, without disabling 65 | * user zoom. 66 | */ 67 | 68 | html { 69 | background: #fff; /* 1 */ 70 | color: #000; /* 2 */ 71 | font-family: sans-serif; /* 3 */ 72 | -ms-text-size-adjust: 100%; /* 4 */ 73 | -webkit-text-size-adjust: 100%; /* 4 */ 74 | } 75 | 76 | /** 77 | * Remove default margin. 78 | */ 79 | 80 | body { 81 | margin: 0; 82 | } 83 | 84 | /* ========================================================================== 85 | Links 86 | ========================================================================== */ 87 | 88 | /** 89 | * Address `outline` inconsistency between Chrome and other browsers. 90 | */ 91 | 92 | a:focus { 93 | outline: thin dotted; 94 | } 95 | 96 | /** 97 | * Improve readability when focused and also mouse hovered in all browsers. 98 | */ 99 | 100 | a:active, 101 | a:hover { 102 | outline: 0; 103 | } 104 | 105 | /* ========================================================================== 106 | Typography 107 | ========================================================================== */ 108 | 109 | /** 110 | * Address variable `h1` font-size and margin within `section` and `article` 111 | * contexts in Firefox 4+, Safari 5, and Chrome. 112 | */ 113 | 114 | h1 { 115 | font-size: 2em; 116 | margin: 0.67em 0; 117 | } 118 | 119 | /** 120 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 121 | */ 122 | 123 | abbr[title] { 124 | border-bottom: 1px dotted; 125 | } 126 | 127 | /** 128 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 129 | */ 130 | 131 | b, 132 | strong { 133 | font-weight: bold; 134 | } 135 | 136 | /** 137 | * Address styling not present in Safari 5 and Chrome. 138 | */ 139 | 140 | dfn { 141 | font-style: italic; 142 | } 143 | 144 | /** 145 | * Address differences between Firefox and other browsers. 146 | */ 147 | 148 | hr { 149 | -moz-box-sizing: content-box; 150 | box-sizing: content-box; 151 | height: 0; 152 | } 153 | 154 | /** 155 | * Address styling not present in IE 8/9. 156 | */ 157 | 158 | mark { 159 | background: #ff0; 160 | color: #000; 161 | } 162 | 163 | /** 164 | * Correct font family set oddly in Safari 5 and Chrome. 165 | */ 166 | 167 | code, 168 | kbd, 169 | pre, 170 | samp { 171 | font-family: monospace, serif; 172 | font-size: 1em; 173 | } 174 | 175 | /** 176 | * Improve readability of pre-formatted text in all browsers. 177 | */ 178 | 179 | pre { 180 | white-space: pre-wrap; 181 | } 182 | 183 | /** 184 | * Set consistent quote types. 185 | */ 186 | 187 | q { 188 | quotes: "\201C" "\201D" "\2018" "\2019"; 189 | } 190 | 191 | /** 192 | * Address inconsistent and variable font size in all browsers. 193 | */ 194 | 195 | small { 196 | font-size: 80%; 197 | } 198 | 199 | /** 200 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 201 | */ 202 | 203 | sub, 204 | sup { 205 | font-size: 75%; 206 | line-height: 0; 207 | position: relative; 208 | vertical-align: baseline; 209 | } 210 | 211 | sup { 212 | top: -0.5em; 213 | } 214 | 215 | sub { 216 | bottom: -0.25em; 217 | } 218 | 219 | /* ========================================================================== 220 | Embedded content 221 | ========================================================================== */ 222 | 223 | /** 224 | * Remove border when inside `a` element in IE 8/9. 225 | */ 226 | 227 | img { 228 | border: 0; 229 | } 230 | 231 | /** 232 | * Correct overflow displayed oddly in IE 9. 233 | */ 234 | 235 | svg:not(:root) { 236 | overflow: hidden; 237 | } 238 | 239 | /* ========================================================================== 240 | Figures 241 | ========================================================================== */ 242 | 243 | /** 244 | * Address margin not present in IE 8/9 and Safari 5. 245 | */ 246 | 247 | figure { 248 | margin: 0; 249 | } 250 | 251 | /* ========================================================================== 252 | Forms 253 | ========================================================================== */ 254 | 255 | /** 256 | * Define consistent border, margin, and padding. 257 | */ 258 | 259 | fieldset { 260 | border: 1px solid #c0c0c0; 261 | margin: 0 2px; 262 | padding: 0.35em 0.625em 0.75em; 263 | } 264 | 265 | /** 266 | * 1. Correct `color` not being inherited in IE 8/9. 267 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 268 | */ 269 | 270 | legend { 271 | border: 0; /* 1 */ 272 | padding: 0; /* 2 */ 273 | } 274 | 275 | /** 276 | * 1. Correct font family not being inherited in all browsers. 277 | * 2. Correct font size not being inherited in all browsers. 278 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 279 | */ 280 | 281 | button, 282 | input, 283 | select, 284 | textarea { 285 | font-family: inherit; /* 1 */ 286 | font-size: 100%; /* 2 */ 287 | margin: 0; /* 3 */ 288 | } 289 | 290 | /** 291 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 292 | * the UA stylesheet. 293 | */ 294 | 295 | button, 296 | input { 297 | line-height: normal; 298 | } 299 | 300 | /** 301 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 302 | * All other form control elements do not inherit `text-transform` values. 303 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 304 | * Correct `select` style inheritance in Firefox 4+ and Opera. 305 | */ 306 | 307 | button, 308 | select { 309 | text-transform: none; 310 | } 311 | 312 | /** 313 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 314 | * and `video` controls. 315 | * 2. Correct inability to style clickable `input` types in iOS. 316 | * 3. Improve usability and consistency of cursor style between image-type 317 | * `input` and others. 318 | */ 319 | 320 | button, 321 | html input[type="button"], /* 1 */ 322 | input[type="reset"], 323 | input[type="submit"] { 324 | -webkit-appearance: button; /* 2 */ 325 | cursor: pointer; /* 3 */ 326 | } 327 | 328 | /** 329 | * Re-set default cursor for disabled elements. 330 | */ 331 | 332 | button[disabled], 333 | html input[disabled] { 334 | cursor: default; 335 | } 336 | 337 | /** 338 | * 1. Address box sizing set to `content-box` in IE 8/9. 339 | * 2. Remove excess padding in IE 8/9. 340 | */ 341 | 342 | input[type="checkbox"], 343 | input[type="radio"] { 344 | box-sizing: border-box; /* 1 */ 345 | padding: 0; /* 2 */ 346 | } 347 | 348 | /** 349 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 350 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 351 | * (include `-moz` to future-proof). 352 | */ 353 | 354 | input[type="search"] { 355 | -webkit-appearance: textfield; /* 1 */ 356 | -moz-box-sizing: content-box; 357 | -webkit-box-sizing: content-box; /* 2 */ 358 | box-sizing: content-box; 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari 5 and Chrome 363 | * on OS X. 364 | */ 365 | 366 | input[type="search"]::-webkit-search-cancel-button, 367 | input[type="search"]::-webkit-search-decoration { 368 | -webkit-appearance: none; 369 | } 370 | 371 | /** 372 | * Remove inner padding and border in Firefox 4+. 373 | */ 374 | 375 | button::-moz-focus-inner, 376 | input::-moz-focus-inner { 377 | border: 0; 378 | padding: 0; 379 | } 380 | 381 | /** 382 | * 1. Remove default vertical scrollbar in IE 8/9. 383 | * 2. Improve readability and alignment in all browsers. 384 | */ 385 | 386 | textarea { 387 | overflow: auto; /* 1 */ 388 | vertical-align: top; /* 2 */ 389 | } 390 | 391 | /* ========================================================================== 392 | Tables 393 | ========================================================================== */ 394 | 395 | /** 396 | * Remove most spacing between table cells. 397 | */ 398 | 399 | table { 400 | border-collapse: collapse; 401 | border-spacing: 0; 402 | } 403 | -------------------------------------------------------------------------------- /examples/delete.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | medium-editor-insert-plugin | example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 | Fork me on GitHub 34 | 35 |
36 |
37 |

jQuery insert plugin for MediumEditor

38 | 39 |

My father’s family name being Pirrip, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.

40 | 41 |
42 |
43 | 44 |
So, I called myself Pip, and came to be called Pip.
45 |
46 |
47 | 48 |

I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.

49 | 50 |

Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.

51 |
52 | 53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /examples/upload.php: -------------------------------------------------------------------------------- 1 | 'uploads/', 17 | 'upload_url' => 'uploads/', 18 | )); -------------------------------------------------------------------------------- /examples/uploads/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkesch/medium-editor-insert-plugin/d4e994c34fffa8e558af10b55e404cee37c282ec/examples/uploads/01.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medium-editor-insert-plugin", 3 | "version": "2.5.1", 4 | "description": "jQuery insert plugin for MediumEditor", 5 | "main": "dist/js/medium-editor-insert-plugin.js", 6 | "homepage": "http://linkesch.com/medium-editor-insert-plugin", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/orthes/medium-editor-insert-plugin.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/orthes/medium-editor-insert-plugin/issues" 14 | }, 15 | "author": { 16 | "name": "Pavel Linkesch", 17 | "email": "pavel.linkesch@gmail.com", 18 | "url": "http://linkesch.com" 19 | }, 20 | "contributors": [ 21 | { 22 | "name": "Jeremy Benoist", 23 | "email": "jeremy.benoist@gmail.com" 24 | } 25 | ], 26 | "keywords": [ 27 | "contenteditable", 28 | "editor", 29 | "medium", 30 | "wysiwyg", 31 | "rich-text", 32 | "images", 33 | "embeds", 34 | "videos" 35 | ], 36 | "dependencies": { 37 | "blueimp-file-upload": "^9.28.0", 38 | "handlebars": "^4.0.12", 39 | "jquery": ">=1.9.0", 40 | "jquery-sortable": "~0.9.12", 41 | "medium-editor": "^5.15.0" 42 | }, 43 | "devDependencies": { 44 | "all-contributors-cli": "^5.10.2", 45 | "grunt": "^1.0.3", 46 | "grunt-autoprefixer": "^3.0.1", 47 | "grunt-banner": "^0.6.0", 48 | "grunt-contrib-concat": "^1.0.1", 49 | "grunt-contrib-handlebars": "^1.0.0", 50 | "grunt-contrib-jasmine": "^1.2.0", 51 | "grunt-contrib-jshint": "^2.0.0", 52 | "grunt-contrib-uglify": "^4.0.0", 53 | "grunt-contrib-watch": "^1.1.0", 54 | "grunt-csso": "^2.2.4", 55 | "grunt-jscs": "^3.0.1", 56 | "grunt-sass": "^3.0.2", 57 | "grunt-template-jasmine-istanbul": "^0.5.0", 58 | "jit-grunt": "^0.10.0", 59 | "node-sass": "^4.11.0", 60 | "time-grunt": "^2.0.0" 61 | }, 62 | "scripts": { 63 | "test": "grunt test --verbose", 64 | "contributors:add": "all-contributors add", 65 | "contributors:init": "all-contributors init", 66 | "contributors:check": "all-contributors check", 67 | "contributors:generate": "all-contributors generate" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spec/core.spec.js: -------------------------------------------------------------------------------- 1 | describe('Core', function () { 2 | beforeEach(function () { 3 | $('body').append('
'); 4 | this.$el = $('.editable'); 5 | 6 | jasmine.clock().install(); 7 | }); 8 | 9 | afterEach(function () { 10 | $('#fixtures').remove(); 11 | 12 | jasmine.clock().uninstall(); 13 | }); 14 | 15 | it('initializes plugin', function () { 16 | this.$el.mediumInsert(); 17 | 18 | expect(this.$el.hasClass('medium-editor-insert-plugin')).toBe(true); 19 | }); 20 | 21 | it('initializes plugin on a textarea', function () { 22 | var textareaId; 23 | 24 | $('#fixtures').html('
'); 25 | this.$el = $('#textarea'); 26 | 27 | this.$el.mediumInsert(); 28 | textareaId = this.$el.attr('medium-editor-textarea-id'); 29 | 30 | expect(this.$el.hasClass('medium-editor-insert-plugin')).toBe(false); 31 | expect(this.$el.siblings('[medium-editor-textarea-id="' + textareaId + '"]').hasClass('medium-editor-insert-plugin')).toBe(true); 32 | }); 33 | 34 | it('initializes addons', function () { 35 | spyOn($.fn, 'mediumInsertImages'); 36 | 37 | this.$el.data('plugin_mediumInsertImages', { 38 | options: {} 39 | }); 40 | this.$el.mediumInsert(); 41 | 42 | expect($.fn.mediumInsertImages.calls.count()).toEqual(1); 43 | }); 44 | 45 | it('does nothing if there is no addon selected', function () { 46 | this.$el.mediumInsert({ 47 | addons: false 48 | }); 49 | 50 | expect(this.$el.html()).toBe(''); 51 | }); 52 | 53 | it('adds empty paragraph if there is no content', function () { 54 | this.$el.mediumInsert(); 55 | 56 | expect(this.$el.find('p').length).toEqual(1); 57 | }); 58 | 59 | it('wraps
into paragraph', function () { 60 | this.$el.html('
'); 61 | 62 | this.$el.mediumInsert(); 63 | 64 | expect(this.$el.find('p').length).toEqual(1); 65 | expect(this.$el.find('br').length).toEqual(1); 66 | }); 67 | 68 | it('wraps text content into paragraph', function () { 69 | this.$el.html('text'); 70 | 71 | this.$el.mediumInsert(); 72 | 73 | expect(this.$el.find('p').length).toEqual(1); 74 | }); 75 | 76 | it('adds empty paragraph at the end if the last element is an addon element', function () { 77 | this.$el.html('
'); 78 | 79 | this.$el.mediumInsert(); 80 | 81 | expect(this.$el.find('.medium-insert-images').next().is('p')).toBe(true); 82 | }); 83 | 84 | it('adds plugin\'s buttons to the $el', function () { 85 | this.$el.mediumInsert(); 86 | 87 | expect(this.$el.find('.medium-insert-buttons').length).toEqual(1); 88 | }); 89 | 90 | it('shows plugin\'s buttons after clicking on empty paragraph', function () { 91 | this.$el.html('

 

test

'); 92 | 93 | this.$el.mediumInsert(); 94 | 95 | // Place caret at the beginning of #paragraph 96 | placeCaret(document.getElementById('paragraph'), 0); 97 | 98 | this.$el.find('#paragraph').click(); 99 | jasmine.clock().tick(1); 100 | 101 | expect(this.$el.find('.medium-insert-buttons').css('display')).toBe('block'); 102 | expect(this.$el.find('#paragraph').hasClass('medium-insert-active')).toBe(true); 103 | expect(this.$el.find('#paragraph2').hasClass('medium-insert-active')).toBe(false); 104 | }); 105 | 106 | it('shows only addon button after clicking on addon paragraph', function () { 107 | this.$el.html('

 

test

'); 108 | 109 | this.$el.mediumInsert(); 110 | 111 | // Place caret at the beginning of #paragraph 112 | placeCaret(document.getElementById('paragraph'), 0); 113 | 114 | this.$el.find('#paragraph').click(); 115 | jasmine.clock().tick(101); 116 | 117 | expect(this.$el.find('.medium-insert-buttons').css('display')).toBe('block'); 118 | expect(this.$el.find('.medium-insert-buttons button[data-addon="embeds"]').parent().css('display')).toBe('none'); 119 | }); 120 | 121 | it('hides plugin\'s buttons after clicking on non-empty paragraph', function () { 122 | this.$el.html('

 

test

'); 123 | 124 | this.$el.mediumInsert(); 125 | 126 | // Place caret at the beginning of #paragraph 127 | placeCaret(document.getElementById('paragraph2'), 0); 128 | 129 | this.$el.find('#paragraph2').click(); 130 | 131 | expect(this.$el.find('.medium-insert-buttons').css('display')).toBe('none'); 132 | }); 133 | 134 | it('toggles addons buttons after clicking on show button', function () { 135 | this.$el.html('

 

test

'); 136 | 137 | this.$el.mediumInsert(); 138 | 139 | // Place caret at the beginning of #paragraph 140 | placeCaret(document.getElementById('paragraph'), 0); 141 | 142 | this.$el.find('#paragraph').click(); 143 | this.$el.find('.medium-insert-buttons-show').click(); 144 | 145 | expect(this.$el.find('.medium-insert-buttons-addons').css('display')).toBe('block'); 146 | expect(this.$el.find('.medium-insert-buttons-show').hasClass('medium-insert-buttons-rotate')).toBe(true); 147 | 148 | this.$el.find('.medium-insert-buttons-show').click(); 149 | 150 | expect(!this.$el.find('.medium-insert-buttons-show').hasClass('medium-insert-buttons-rotate')).toBe(true); 151 | }); 152 | 153 | it('calls addon\'s add function if addon\'s button is clicked', function () { 154 | var addonInstance; 155 | 156 | this.$el.html('

 

test

'); 157 | 158 | this.$el.mediumInsert({ 159 | addons: { 160 | embeds: false 161 | } 162 | }); 163 | addonInstance = this.$el.data('plugin_mediumInsertImages'); 164 | 165 | spyOn(addonInstance, 'add'); 166 | 167 | // Place caret at the beginning of #paragraph 168 | placeCaret(document.getElementById('paragraph'), 0); 169 | 170 | this.$el.find('#paragraph').click(); 171 | this.$el.find('.medium-insert-buttons-show').click(); 172 | this.$el.find('.medium-insert-action').click(); 173 | 174 | expect(addonInstance.add.calls.count()).toBe(1); 175 | }); 176 | 177 | it('removes also plugin buttons on serialization', function () { 178 | var editor = new MediumEditor('.editable'); 179 | 180 | this.$el.mediumInsert({ 181 | editor: editor 182 | }); 183 | 184 | expect(editor.serialize()['element-0'].value).toBe('


'); 185 | }); 186 | 187 | it('disables plugin on editor destroy', function () { 188 | var editor = new MediumEditor(this.$el.get(0)); 189 | 190 | this.$el.mediumInsert({ 191 | editor: editor 192 | }); 193 | 194 | editor.destroy(); 195 | 196 | expect(this.$el.data('plugin_mediumInsert').options.enabled).toBe(false); 197 | }); 198 | 199 | it('enables plugin on editor setup', function () { 200 | var editor = new MediumEditor(this.$el.get(0)); 201 | 202 | this.$el.mediumInsert({ 203 | editor: editor 204 | }); 205 | 206 | editor.setup(); 207 | 208 | expect(this.$el.data('plugin_mediumInsert').options.enabled).toBe(true); 209 | }); 210 | 211 | it('displays placeholder dispite of plugin buttons', function () { 212 | var editor = new MediumEditor(this.$el.get(0)); 213 | 214 | this.$el.mediumInsert({ 215 | editor: editor 216 | }); 217 | 218 | editor.getExtensionByName('placeholder').updatePlaceholder(this.$el.get(0)); 219 | 220 | expect(this.$el.hasClass('medium-editor-placeholder')).toBe(true); 221 | }); 222 | 223 | it('hides placeholder when there is a text', function () { 224 | var editor = new MediumEditor(this.$el.get(0)); 225 | 226 | this.$el.mediumInsert({ 227 | editor: editor 228 | }); 229 | 230 | this.$el.addClass('medium-editor-placeholder'); 231 | this.$el.prepend('

asd

'); 232 | 233 | editor.getExtensionByName('placeholder').updatePlaceholder(this.$el.get(0)); 234 | 235 | expect(this.$el.hasClass('medium-editor-placeholder')).toBe(false); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /spec/embeds.spec.js: -------------------------------------------------------------------------------- 1 | describe('Embeds addon', function () { 2 | beforeEach(function () { 3 | $('body').append('
'); 4 | this.$el = $('.editable'); 5 | this.editor = new MediumEditor(this.$el.get(0)); 6 | this.$el.mediumInsert({ 7 | editor: this.editor, 8 | addons: { 9 | embeds: { 10 | oembedProxy: false, 11 | parseOnPaste: true 12 | } 13 | } 14 | }); 15 | this.addon = this.$el.data('plugin_mediumInsertEmbeds'); 16 | 17 | jasmine.clock().install(); 18 | }); 19 | 20 | afterEach(function () { 21 | $('#fixtures').remove(); 22 | 23 | jasmine.clock().uninstall(); 24 | }); 25 | 26 | it('toggles placeholder', function () { 27 | var $p = this.$el.find('p').first(); 28 | 29 | this.$el.append('
 
'); 30 | 31 | placeCaret($p.get(0), 0); 32 | $p.click(); 33 | 34 | this.$el.find('.medium-insert-buttons-show').click(); 35 | this.$el.find('.medium-insert-action[data-addon="embeds"]').click(); 36 | 37 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(1); 38 | expect(this.$el.find('.medium-insert-embeds-input').is('div')).toBe(true); 39 | 40 | placeCaret(this.$el.find('#p2').get(0), 0); 41 | this.$el.click(); 42 | 43 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 44 | }); 45 | 46 | it('removes empty placeholder on backspace', function () { 47 | var $event = $.Event('keydown'); 48 | 49 | $event.which = 8; 50 | this.$el.prepend('

'); 51 | 52 | this.$el.trigger($event); 53 | 54 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 55 | }); 56 | 57 | it('supports selecting embed', function () { 58 | this.$el.prepend('
'); 59 | 60 | this.$el.find('.medium-insert-embeds-overlay').click(); 61 | jasmine.clock().tick(50); 62 | 63 | expect(this.$el.find('.medium-insert-embeds').hasClass('medium-insert-embeds-selected')).toBe(true); 64 | expect($('.medium-insert-embeds-toolbar').length).toEqual(1); 65 | expect($('.medium-insert-embeds-toolbar2').length).toEqual(1); 66 | expect(this.$el.find('figcaption').length).toEqual(1); 67 | }); 68 | 69 | it('supports disabling captions', function () { 70 | $('#fixtures').html('
'); 71 | this.$el = $('.editable'); 72 | this.$el.mediumInsert({ 73 | addons: { 74 | embeds: { 75 | captions: false 76 | } 77 | } 78 | }); 79 | 80 | this.$el.find('img').click(); 81 | jasmine.clock().tick(50); 82 | 83 | expect(this.$el.find('figcaption').length).toEqual(0); 84 | }); 85 | 86 | it('removes placeholder after clicking on caption', function () { 87 | this.$el.prepend('
' + 88 | '
' + 89 | '
' + 90 | '
'); 91 | 92 | this.$el.find('.medium-insert-caption-placeholder').click(); 93 | jasmine.clock().tick(50); 94 | 95 | expect(this.$el.find('figcaption').hasClass('medium-insert-caption-placeholder')).toBe(false); 96 | }); 97 | 98 | it('supports unselecting embed', function () { 99 | this.$el.prepend('
'); 100 | this.$el.click(); 101 | 102 | expect(this.$el.find('.medium-insert-embeds').hasClass('medium-insert-embeds-selected')).toBe(false); 103 | expect($('.medium-insert-embeds-toolbar').length).toEqual(0); 104 | expect($('.medium-insert-embeds-toolbar2').length).toEqual(0); 105 | expect(this.$el.find('figcaption').length).toEqual(0); 106 | }); 107 | 108 | it('removes embed on backspace', function () { 109 | var $event = $.Event('keydown'); 110 | 111 | $event.which = 8; 112 | 113 | this.$el.prepend('
'); 114 | this.$el.trigger($event); 115 | 116 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(0); 117 | expect($('.medium-insert-embeds-toolbar').length).toEqual(0); 118 | }); 119 | 120 | it('triggers input event after removing embed ', function (done) { 121 | var $event = $.Event('keydown'); 122 | 123 | this.editor.subscribe('editableInput', function () { 124 | expect(true).toBe(true); 125 | done(); 126 | }); 127 | 128 | $event.which = 8; 129 | 130 | this.$el.prepend('
'); 131 | 132 | this.$el.trigger($event); 133 | }); 134 | 135 | it('supports chaning embed style', function () { 136 | var $embed; 137 | 138 | this.$el.prepend('
'); 139 | $embed = this.$el.find('.medium-insert-embeds'); 140 | 141 | $embed.find('.medium-insert-embeds-overlay').click(); 142 | jasmine.clock().tick(50); 143 | 144 | $('.medium-insert-embeds-toolbar .medium-editor-action').first().click(); 145 | 146 | expect($embed.hasClass('medium-insert-embeds-wide')).toBe(true); 147 | expect($embed.hasClass('medium-insert-embeds-left')).toBe(false); 148 | }); 149 | 150 | it('triggers input event after changing embed style ', function (done) { 151 | this.$el.prepend('
'); 152 | 153 | this.editor.subscribe('editableInput', function () { 154 | expect(true).toBe(true); 155 | done(); 156 | }); 157 | 158 | this.$el.find('.medium-insert-embeds-overlay').click(); 159 | jasmine.clock().tick(50); 160 | 161 | $('.medium-insert-embeds-toolbar .medium-editor-action').first().click(); 162 | }); 163 | 164 | it('choosing embed style calls callback function', function (done) { 165 | this.addon.options.styles.wide.added = function () { 166 | expect(true).toBe(true); 167 | done(); 168 | }; 169 | 170 | this.$el.prepend('
'); 171 | 172 | // Place caret into first paragraph 173 | placeCaret(this.$el.find('p').get(0), 0); 174 | 175 | this.$el.find('.medium-insert-embeds-overlay').click(); 176 | jasmine.clock().tick(50); 177 | 178 | $('.medium-insert-embeds-toolbar .medium-editor-action').first().click(); 179 | }); 180 | 181 | it('calls callback function after clicking on embed action ', function (done) { 182 | this.addon.options.actions.remove.clicked = function () { 183 | expect(true).toBe(true); 184 | done(); 185 | }; 186 | 187 | this.$el.prepend('
'); 188 | 189 | // Place caret into first paragraph 190 | placeCaret(this.$el.find('p').get(0), 0); 191 | 192 | this.$el.find('.medium-insert-embeds-overlay').click(); 193 | jasmine.clock().tick(50); 194 | 195 | $('.medium-insert-embeds-toolbar2 .medium-editor-action').first().click(); 196 | }); 197 | 198 | it('maintains backwards compatibility', function () { 199 | $('#fixtures').html('
'); 200 | this.$el = $('.editable'); 201 | 202 | this.$el.mediumInsert(); 203 | 204 | expect(this.$el.find('.medium-insert-embeds .medium-insert-embed iframe').length).toEqual(1); 205 | }); 206 | 207 | it('supports embedding youtube', function () { 208 | var $event = $.Event('keydown'); 209 | 210 | $event.which = 13; 211 | $('#fixtures').html('

https://www.youtube.com/watch?v=BROWqjuTM0g

'); 212 | this.$el = $('.editable'); 213 | this.$el.mediumInsert({ 214 | addons: { 215 | embeds: { 216 | oembedProxy: false 217 | } 218 | } 219 | }); 220 | this.$el.trigger($event); 221 | 222 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 223 | expect(this.$el.find('.medium-insert-embeds iframe').length).toEqual(1); 224 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 225 | }); 226 | 227 | it('supports embedding youtube via paste', function () { 228 | var e = { 229 | originalEvent: { 230 | clipboardData: { 231 | getData: function () { 232 | return 'https://www.youtube.com/watch?v=BROWqjuTM0g'; 233 | } 234 | } 235 | } 236 | }; 237 | this.$el = $('.editable'); 238 | this.$el.append($('

https://www.youtube.com/watch?v=BROWqjuTM0g

')); 239 | this.addon.processPasted(e); 240 | 241 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 242 | expect(this.$el.find('.medium-insert-embeds iframe').length).toEqual(1); 243 | }); 244 | 245 | it('supports embedding vimeo', function () { 246 | var $event = $.Event('keydown'); 247 | 248 | $event.which = 13; 249 | $('#fixtures').html('

http://vimeo.com/2619976

'); 250 | this.$el = $('.editable'); 251 | this.$el.mediumInsert({ 252 | addons: { 253 | embeds: { 254 | oembedProxy: false 255 | } 256 | } 257 | }); 258 | this.$el.trigger($event); 259 | 260 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 261 | expect(this.$el.find('.medium-insert-embeds iframe').length).toEqual(1); 262 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 263 | }); 264 | 265 | it('supports embedding vimeo via paste', function () { 266 | var e = { 267 | originalEvent: { 268 | clipboardData: { 269 | getData: function () { 270 | return 'http://vimeo.com/2619976'; 271 | } 272 | } 273 | } 274 | }; 275 | this.$el = $('.editable'); 276 | this.$el.append($('

http://vimeo.com/2619976

')); 277 | this.addon.processPasted(e); 278 | 279 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 280 | expect(this.$el.find('.medium-insert-embeds iframe').length).toEqual(1); 281 | }); 282 | 283 | it('support embedding instagram', function () { 284 | var $event = $.Event('keydown'); 285 | 286 | $event.which = 13; 287 | $('#fixtures').html('

http://instagram.com/p/u7PiWCsGxj

'); 288 | this.$el = $('.editable'); 289 | this.$el.mediumInsert({ 290 | addons: { 291 | embeds: { 292 | oembedProxy: false 293 | } 294 | } 295 | }); 296 | this.$el.trigger($event); 297 | 298 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 299 | expect(this.$el.find('.medium-insert-embeds iframe').length).toEqual(1); 300 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 301 | }); 302 | 303 | it('support embedding twitter', function () { 304 | var $event = $.Event('keydown'); 305 | 306 | $event.which = 13; 307 | $('#fixtures').html('

https://twitter.com/medium_editor/status/694987296379125760

'); 308 | this.$el = $('.editable'); 309 | this.$el.mediumInsert({ 310 | addons: { 311 | embeds: { 312 | oembedProxy: false 313 | } 314 | } 315 | }); 316 | this.$el.trigger($event); 317 | 318 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 319 | expect(this.$el.find('.medium-insert-embeds .twitter-tweet').length).toEqual(1); 320 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 321 | }); 322 | 323 | it('support embedding facebook', function () { 324 | var $event = $.Event('keydown'); 325 | 326 | $event.which = 13; 327 | $('#fixtures').html('

https://www.facebook.com/cneistat/videos/vb.210351389002863/922328184471843/?type=2&theater

'); 328 | this.$el = $('.editable'); 329 | this.$el.mediumInsert({ 330 | addons: { 331 | embeds: { 332 | oembedProxy: false 333 | } 334 | } 335 | }); 336 | this.$el.trigger($event); 337 | 338 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 339 | expect(this.$el.find('.medium-insert-embeds .fb-post').length).toEqual(1); 340 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 341 | }); 342 | 343 | it('converts bad embed into text', function () { 344 | var $event = $.Event('keydown'); 345 | 346 | $event.which = 13; 347 | $('#fixtures').html('

test

'); 348 | this.$el = $('.editable'); 349 | this.$el.mediumInsert({ 350 | addons: { 351 | embeds: { 352 | oembedProxy: false 353 | } 354 | } 355 | }); 356 | this.$el.trigger($event); 357 | 358 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 359 | expect(this.$el.find('p:first').html()).toBe('test'); 360 | }); 361 | 362 | it('support disabling style toolbar', function () { 363 | this.addon.options.styles = false; 364 | 365 | this.$el.prepend('
'); 366 | 367 | this.$el.find('.medium-insert-embeds-overlay').click(); 368 | jasmine.clock().tick(50); 369 | 370 | expect($('.medium-insert-embeds-toolbar').length).toEqual(0); 371 | expect($('.medium-insert-embeds-toolbar2').length).toEqual(1); 372 | }); 373 | 374 | it('support disabling action toolbar', function () { 375 | this.addon.options.actions = false; 376 | 377 | this.$el.prepend('
'); 378 | 379 | this.$el.find('.medium-insert-embeds-overlay').click(); 380 | jasmine.clock().tick(50); 381 | 382 | expect($('.medium-insert-embeds-toolbar').length).toEqual(1); 383 | expect($('.medium-insert-embeds-toolbar2').length).toEqual(0); 384 | }); 385 | 386 | it('adds contentediable attr and overlay on initialization', function () { 387 | $('#fixtures').html('
'); 388 | this.$el = $('.editable'); 389 | 390 | this.$el.mediumInsert({ 391 | addons: { 392 | embeds: {} 393 | } 394 | }); 395 | 396 | expect(this.$el.find('.medium-insert-embeds').attr('contenteditable')).toBe('false'); 397 | expect(this.$el.find('.medium-insert-embeds .medium-insert-embeds-overlay').length).toEqual(1); 398 | }); 399 | 400 | it('removes also contenteditable attr and ovelay on editor\'s serialize', function () { 401 | var html = '
', 402 | editor, $serialized; 403 | 404 | $('#fixtures').html('
' + html + '
'); 405 | this.$el = $('.editable'); 406 | 407 | editor = new MediumEditor(this.$el.get(0)); 408 | 409 | this.$el.mediumInsert({ 410 | editor: editor, 411 | addons: { 412 | embeds: {} 413 | } 414 | }); 415 | 416 | $serialized = $(editor.serialize()['element-0'].value); 417 | expect($serialized.find('.medium-insert-embeds').attr('contenteditable')).toBeUndefined(); 418 | expect($serialized.find('.medium-insert-embeds-overlay').length).toEqual(0); 419 | }); 420 | 421 | it('uses data-embed-code as container html for javascript-based embeds', function () { 422 | var html = '
bad-value
', 423 | editor, $serialized; 424 | 425 | $('#fixtures').html('
' + html + '
'); 426 | this.$el = $('.editable'); 427 | 428 | editor = new MediumEditor(this.$el.get(0)); 429 | 430 | this.$el.mediumInsert({ 431 | editor: editor, 432 | addons: { 433 | embeds: {} 434 | } 435 | }); 436 | 437 | $serialized = $(editor.serialize()['element-0'].value); 438 | expect($serialized.find('[data-embed-code]').html()).toEqual('
good-value
'); 439 | }); 440 | 441 | it('does include meta when storeMeta is set', function () { 442 | var $event = $.Event('keydown'); 443 | 444 | $event.which = 13; 445 | $('#fixtures').html('

https://www.facebook.com/cneistat/videos/vb.210351389002863/922328184471843/?type=2&theater

'); 446 | this.$el = $('.editable'); 447 | this.$el.mediumInsert({ 448 | addons: { 449 | embeds: { 450 | oembedProxy: false, 451 | storeMeta: true 452 | } 453 | } 454 | }); 455 | this.$el.trigger($event); 456 | 457 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 458 | expect(this.$el.find('.medium-insert-embeds .fb-post').length).toEqual(1); 459 | expect(this.$el.find('.medium-insert-embeds-meta').length).toEqual(1); 460 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 461 | }); 462 | 463 | it('does not include meta when storeMeta is not set', function () { 464 | var $event = $.Event('keydown'); 465 | 466 | $event.which = 13; 467 | $('#fixtures').html('

https://www.facebook.com/cneistat/videos/vb.210351389002863/922328184471843/?type=2&theater

'); 468 | this.$el = $('.editable'); 469 | this.$el.mediumInsert({ 470 | addons: { 471 | embeds: { 472 | oembedProxy: false, 473 | storeMeta: false 474 | } 475 | } 476 | }); 477 | this.$el.trigger($event); 478 | 479 | expect(this.$el.find('.medium-insert-embeds').length).toEqual(1); 480 | expect(this.$el.find('.medium-insert-embeds .fb-post').length).toEqual(1); 481 | expect(this.$el.find('.medium-insert-embeds-meta').length).toEqual(0); 482 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 483 | }); 484 | 485 | it('does not include meta for bad embeds', function () { 486 | var $event = $.Event('keydown'); 487 | 488 | $event.which = 13; 489 | $('#fixtures').html('

test

'); 490 | this.$el = $('.editable'); 491 | 492 | this.$el.mediumInsert({ 493 | addons: { 494 | embeds: { 495 | oembedProxy: false, 496 | storeMeta: true 497 | } 498 | } 499 | }); 500 | this.$el.trigger($event); 501 | 502 | expect(this.$el.find('.medium-insert-embeds-input').length).toEqual(0); 503 | expect(this.$el.find('.medium-insert-embeds-meta').length).toEqual(0); 504 | expect(this.$el.find('p:first').html()).toBe('test'); 505 | }); 506 | 507 | 508 | }); 509 | -------------------------------------------------------------------------------- /spec/helpers/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Placing caret to element and selected position 3 | * 4 | * @param {Element} el 5 | * @param {integer} position 6 | * @return {void} 7 | */ 8 | 9 | function placeCaret(el, position) { 10 | var range, sel; 11 | 12 | range = document.createRange(); 13 | sel = window.getSelection(); 14 | range.setStart(el.childNodes[0], position); 15 | range.collapse(true); 16 | sel.removeAllRanges(); 17 | sel.addRange(range); 18 | } 19 | 20 | // https://gist.github.com/davoclavo/4424731 21 | function dataURItoBlob(dataURI) { 22 | // convert base64 to raw binary data held in a string 23 | var byteString = atob(dataURI.split(',')[1]), 24 | // separate out the mime component 25 | mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0], 26 | // write the bytes of the string to an ArrayBuffer 27 | arrayBuffer = new ArrayBuffer(byteString.length), 28 | _ia = new Uint8Array(arrayBuffer), 29 | i, dataView, blob; 30 | 31 | for (i = 0; i < byteString.length; i++) { 32 | _ia[i] = byteString.charCodeAt(i); 33 | } 34 | 35 | dataView = new DataView(arrayBuffer); 36 | blob = new Blob([dataView], { type: mimeString }); 37 | 38 | return blob; 39 | } 40 | -------------------------------------------------------------------------------- /spec/images.spec.js: -------------------------------------------------------------------------------- 1 | describe('Images addon', function () { 2 | beforeEach(function () { 3 | $('body').append('
'); 4 | this.$el = $('.editable'); 5 | this.editor = new MediumEditor(this.$el.get(0)); 6 | this.$el.mediumInsert({ 7 | editor: this.editor 8 | }); 9 | this.addon = this.$el.data('plugin_mediumInsertImages'); 10 | 11 | jasmine.clock().install(); 12 | }); 13 | 14 | afterEach(function () { 15 | $('#fixtures').remove(); 16 | 17 | jasmine.clock().uninstall(); 18 | }); 19 | 20 | it('creates preview image before upload', function (done) { 21 | var $p = this.$el.find('p'); 22 | 23 | placeCaret($p.get(0), 0); 24 | $p.click(); 25 | 26 | this.addon.uploadAdd(null, { 27 | autoUpload: true, 28 | files: [dataURItoBlob('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=')], 29 | submit: function () { 30 | expect(this.$el.find('.medium-insert-images').length).toEqual(1); 31 | expect(this.$el.find('.medium-insert-images img').length).toEqual(1); 32 | expect(this.$el.find('.medium-insert-images .medium-insert-images-progress').length).toEqual(1); 33 | expect(this.$el.find('.medium-insert-images img').attr('src').match(/^(data|blob):/)).toBeTruthy(); 34 | done(); 35 | }.bind(this), 36 | process: function () { 37 | return this; 38 | }, 39 | done: function (callback) { 40 | callback(); 41 | } 42 | }); 43 | }); 44 | 45 | it('replaces preview by uploaded image', function () { 46 | var stubbedImage = jasmine.createSpy('image'); 47 | spyOn(this.addon, 'getDOMImage').and.returnValue(stubbedImage); 48 | this.$el.prepend('
'); 49 | 50 | this.addon.uploadDone(null, { 51 | context: this.$el.find('figure'), 52 | result: { 53 | files: [ 54 | { url: 'test.jpg' } 55 | ] 56 | } 57 | }); 58 | stubbedImage.onload(); 59 | expect(this.$el.find('.medium-insert-images img').attr('src')).toEqual('test.jpg'); 60 | }); 61 | 62 | it('uploads without preview when it is set like this in options', function (done) { 63 | var $p = this.$el.find('p'); 64 | 65 | this.addon.options.preview = false; 66 | 67 | placeCaret($p.get(0), 0); 68 | $p.click(); 69 | 70 | this.addon.uploadAdd(null, { 71 | autoUpload: true, 72 | files: [new Blob([''], { type: 'image/jpeg' })], 73 | submit: function () { 74 | expect(this.$el.find('.medium-insert-images').length).toEqual(1); 75 | expect(this.$el.find('.medium-insert-images progress').length).toEqual(1); 76 | expect(this.$el.find('.medium-insert-images img').length).toEqual(0); 77 | 78 | this.addon.uploadDone(null, { 79 | result: { 80 | files: [ 81 | { url: 'test.jpg' } 82 | ] 83 | } 84 | }); 85 | 86 | expect(this.$el.find('.medium-insert-images img').attr('src')).toEqual('test.jpg'); 87 | done(); 88 | }.bind(this), 89 | process: function () { 90 | return this; 91 | }, 92 | done: function (callback) { 93 | callback(); 94 | } 95 | }); 96 | }); 97 | 98 | it('automatically adds grid when multiple images are in a set', function (done) { 99 | this.addon.options.preview = false; 100 | this.$el.prepend('
' + 101 | '
' + 102 | '
' + 103 | '
'); 104 | 105 | this.addon.uploadAdd(null, { 106 | autoUpload: true, 107 | files: [new Blob([''], { type: 'image/jpeg' })], 108 | submit: function () { 109 | this.addon.uploadDone(null, { 110 | result: { 111 | files: [ 112 | { url: 'test.jpg' } 113 | ] 114 | } 115 | }); 116 | 117 | expect(this.$el.find('.medium-insert-images').hasClass('medium-insert-images-grid')).toBe(true); 118 | done(); 119 | }.bind(this), 120 | process: function () { 121 | return this; 122 | }, 123 | done: function (callback) { 124 | callback(); 125 | } 126 | }); 127 | }); 128 | 129 | it('doesn\'t add grid when not enough images are in a set', function (done) { 130 | this.addon.options.preview = false; 131 | this.$el.prepend('
' + 132 | '
' + 133 | '
'); 134 | 135 | this.addon.uploadAdd(null, { 136 | autoUpload: true, 137 | files: [new Blob([''], { type: 'image/jpeg' })], 138 | submit: function () { 139 | this.addon.uploadDone(null, { 140 | result: { 141 | files: [ 142 | { url: 'test.jpg' } 143 | ] 144 | } 145 | }); 146 | 147 | expect(this.$el.find('.medium-insert-images').hasClass('medium-insert-images-grid')).toBe(false); 148 | done(); 149 | }.bind(this), 150 | process: function () { 151 | return this; 152 | }, 153 | done: function (callback) { 154 | callback(); 155 | } 156 | }); 157 | }); 158 | 159 | it('triggers input event on showImage', function (done) { 160 | this.editor.subscribe('editableInput', function () { 161 | expect(true).toBe(true); 162 | done(); 163 | }); 164 | 165 | this.addon.showImage(null, { 166 | submit: function () { } 167 | }); 168 | }); 169 | 170 | it('triggers input event twice on showImage for preview', function (done) { 171 | var inputTriggerCount = 0, 172 | stubbedImage = jasmine.createSpy('image'), 173 | context = this.$el.prepend('
' + 174 | '
' + 175 | '
'); 176 | 177 | spyOn(this.addon, 'getDOMImage').and.returnValue(stubbedImage); 178 | 179 | this.editor.subscribe('editableInput', function () { 180 | inputTriggerCount++; 181 | 182 | if (inputTriggerCount === 2) { 183 | expect(true).toBe(true); 184 | done(); 185 | } 186 | }); 187 | 188 | this.addon.showImage('http://image.co', { 189 | context: context 190 | }); 191 | stubbedImage.onload(); 192 | }); 193 | 194 | it('calls uploadCompleted when preview is enabled', function (done) { 195 | var stubbedImage = jasmine.createSpy('image'), 196 | context = this.$el.prepend('
' + 197 | '
' + 198 | '
'); 199 | 200 | spyOn(this.addon, 'getDOMImage').and.returnValue(stubbedImage); 201 | 202 | this.addon.options.uploadCompleted = function () { 203 | expect(true).toBe(true); 204 | done(); 205 | }; 206 | 207 | this.addon.showImage('http://image.co', { 208 | context: context 209 | }); 210 | stubbedImage.onload(); 211 | }); 212 | 213 | it('calls uploadCompleted when preview is disabled', function (done) { 214 | this.addon.options.preview = false; 215 | 216 | this.addon.options.uploadCompleted = function () { 217 | expect(true).toBe(true); 218 | done(); 219 | }; 220 | 221 | this.addon.showImage(null, { 222 | submit: function () { } 223 | }); 224 | }); 225 | 226 | it('supports selecting image', function () { 227 | this.$el.find('p') 228 | .addClass('medium-insert-images') 229 | .append('
'); 230 | 231 | this.$el.find('img').click(); 232 | jasmine.clock().tick(50); 233 | 234 | expect(this.$el.find('img').hasClass('medium-insert-image-active')).toBe(true); 235 | expect($('.medium-insert-images-toolbar').length).toEqual(1); 236 | expect($('.medium-insert-images-toolbar2').length).toEqual(1); 237 | expect(this.$el.find('figcaption').length).toEqual(1); 238 | }); 239 | 240 | it('supports disabling captions', function () { 241 | $('#fixture').html('
'); 242 | this.$el = $('.editable'); 243 | this.$el.mediumInsert({ 244 | addons: { 245 | images: { 246 | captions: false 247 | } 248 | } 249 | }); 250 | 251 | this.$el.find('img').click(); 252 | jasmine.clock().tick(50); 253 | 254 | expect(this.$el.find('figcaption').length).toEqual(0); 255 | }); 256 | 257 | it('removes placeholder after clicking on caption', function () { 258 | this.$el.find('p') 259 | .addClass('medium-insert-images') 260 | .append('
'); 261 | 262 | this.$el.find('.medium-insert-caption-placeholder').click(); 263 | jasmine.clock().tick(50); 264 | 265 | expect(this.$el.find('figcaption').hasClass('medium-insert-caption-placeholder')).toEqual(false); 266 | }); 267 | 268 | it('supports unselecting image', function () { 269 | this.$el.find('p') 270 | .addClass('medium-insert-images') 271 | .append('
'); 272 | 273 | this.$el.click(); 274 | 275 | expect(this.$el.find('img').hasClass('medium-insert-image-active')).toBe(false); 276 | expect($('.medium-insert-images-toolbar').length).toEqual(0); 277 | expect($('.medium-insert-images-toolbar2').length).toEqual(0); 278 | expect(this.$el.find('figcaption').length).toEqual(0); 279 | }); 280 | 281 | it('supports removing image', function () { 282 | var $event = $.Event('keydown'); 283 | 284 | $event.which = 8; 285 | 286 | this.$el.find('p') 287 | .addClass('medium-insert-images medium-insert-images-grid') 288 | .append('
' + 289 | '
' + 290 | '
' + 291 | '
'); 292 | 293 | this.$el.trigger($event); 294 | 295 | expect(this.$el.find('img').length).toEqual(3); 296 | expect(this.$el.find('.medium-insert-images').hasClass('medium-insert-images-grid')).toBe(true); 297 | 298 | this.$el.find('img').last().addClass('medium-insert-image-active'); 299 | this.$el.trigger($event); 300 | 301 | expect(this.$el.find('img').length).toEqual(2); 302 | 303 | this.$el.find('img').addClass('medium-insert-image-active'); 304 | this.$el.trigger($event); 305 | 306 | expect(this.$el.find('.medium-insert-images').length).toEqual(0); 307 | expect($('.medium-insert-images-toolbar').length).toEqual(0); 308 | }); 309 | 310 | it('fires deleteFile function even when images isn\'t selected but backspace is pressed in text', function () { 311 | var $p = this.$el.find('p'), 312 | $event = $.Event('keydown'); 313 | 314 | $event.which = 8; 315 | 316 | spyOn(jQuery, 'ajax'); 317 | 318 | $p.before('
'); 319 | $p.html('test'); 320 | 321 | placeCaret($p.get(0), 0); 322 | this.$el.trigger($event); 323 | 324 | expect(jQuery.ajax.calls.count()).toEqual(1); 325 | }); 326 | 327 | it('fires deleteFile function even when images isn\'t selected but delete is pressed in text', function () { 328 | var $p = this.$el.find('p'), 329 | $event = $.Event('keydown'); 330 | 331 | $event.which = 46; 332 | 333 | spyOn(jQuery, 'ajax'); 334 | 335 | $p.after('
'); 336 | $p.html('test'); 337 | 338 | placeCaret($p.get(0), $p.text().length); 339 | this.$el.trigger($event); 340 | 341 | expect(jQuery.ajax.calls.count()).toEqual(1); 342 | }); 343 | 344 | it('triggers input event after removing image', function (done) { 345 | var $event = $.Event('keydown'); 346 | 347 | this.editor.subscribe('editableInput', function () { 348 | expect(true).toBe(true); 349 | done(); 350 | }); 351 | 352 | $event.which = 8; 353 | 354 | this.$el.find('p') 355 | .addClass('medium-insert-images') 356 | .append('
' + 357 | '
'); 358 | 359 | this.$el.trigger($event); 360 | }); 361 | 362 | it('supports deleting file', function () { 363 | var $event = $.Event('keydown'); 364 | 365 | $event.which = 8; 366 | 367 | this.$el.find('p') 368 | .addClass('medium-insert-images') 369 | .append('
' + 370 | '
'); 371 | 372 | spyOn(jQuery, 'ajax'); 373 | this.$el.trigger($event); 374 | 375 | expect(jQuery.ajax.calls.count()).toEqual(1); 376 | }); 377 | 378 | it('support changing image style', function () { 379 | var $p = this.$el.find('p') 380 | .attr('class', 'medium-insert-images medium-insert-active medium-insert-images-left') 381 | .append('
'); 382 | 383 | $p.find('img').click(); 384 | jasmine.clock().tick(50); 385 | 386 | $('.medium-insert-images-toolbar .medium-editor-action').first().click(); 387 | 388 | expect($p.hasClass('medium-insert-images-wide')).toBe(true); 389 | expect($p.hasClass('medium-insert-images-left')).toBe(false); 390 | }); 391 | 392 | it('triggers input event after changing image style', function (done) { 393 | var $p = this.$el.find('p') 394 | .attr('class', 'medium-insert-images medium-insert-active medium-insert-images-left') 395 | .append('
'); 396 | 397 | this.editor.subscribe('editableInput', function () { 398 | expect(true).toBe(true); 399 | done(); 400 | }); 401 | 402 | $p.find('img').click(); 403 | jasmine.clock().tick(50); 404 | 405 | $('.medium-insert-images-toolbar .medium-editor-action').first().click(); 406 | }); 407 | 408 | it('calls callback function after changing image style ', function (done) { 409 | this.addon.options.styles.wide.added = function () { 410 | expect(true).toBe(true); 411 | done(); 412 | }; 413 | 414 | this.$el.find('p') 415 | .addClass('medium-insert-images') 416 | .append('
'); 417 | 418 | // Place caret into first paragraph 419 | placeCaret(this.$el.find('p').get(0), 0); 420 | 421 | this.$el.find('img').click(); 422 | jasmine.clock().tick(50); 423 | 424 | $('.medium-insert-images-toolbar .medium-editor-action').first().click(); 425 | }); 426 | 427 | it('calls callback function after clicking on image action ', function (done) { 428 | this.addon.options.actions.remove.clicked = function () { 429 | expect(true).toBe(true); 430 | done(); 431 | }; 432 | 433 | this.$el.find('p') 434 | .addClass('medium-insert-images') 435 | .append('
'); 436 | 437 | // Place caret into first paragraph 438 | placeCaret(this.$el.find('p').get(0), 0); 439 | 440 | this.$el.find('img').click(); 441 | jasmine.clock().tick(50); 442 | 443 | $('.medium-insert-images-toolbar2 .medium-editor-action').first().click(); 444 | }); 445 | 446 | it('validatates file type on upload', function (done) { 447 | spyOn(window, 'alert').and.callFake(function (text) { 448 | expect(text).toMatch(/^This file is not in a supported format/); 449 | done(); 450 | }); 451 | 452 | this.$el.find('p').click(); 453 | 454 | this.addon.uploadAdd(null, { 455 | files: [{ type: 'application/json' }] 456 | }); 457 | }); 458 | 459 | it('validates file size on upload', function (done) { 460 | this.addon.options.fileUploadOptions.maxFileSize = 1000; 461 | 462 | spyOn(window, 'alert').and.callFake(function (text) { 463 | expect(text).toMatch(/^This file is too big/); 464 | done(); 465 | }); 466 | 467 | this.$el.find('p').click(); 468 | 469 | this.addon.uploadAdd(null, { 470 | files: [{ type: 'image/jpeg', size: 1001 }] 471 | }); 472 | }); 473 | 474 | it('calls callback function after failing upload', function (done) { 475 | this.addon.options.fileUploadOptions.maxFileSize = 1000; 476 | this.addon.options.uploadFailed = function () { 477 | expect(true).toBe(true); 478 | done(); 479 | }; 480 | 481 | this.$el.find('p').click(); 482 | 483 | this.addon.uploadAdd(null, { 484 | files: [{ type: 'image/jpeg', size: 1001 }] 485 | }); 486 | }); 487 | 488 | it('show alert if callback is no function', function (done) { 489 | this.addon.options.fileUploadOptions.maxFileSize = 1000; 490 | this.addon.options.uploadFailed = "string"; 491 | 492 | spyOn(window, 'alert').and.callFake(function (text) { 493 | expect(text).not.toBe(null); 494 | done(); 495 | }); 496 | 497 | this.$el.find('p').click(); 498 | 499 | this.addon.uploadAdd(null, { 500 | files: [{ type: 'image/jpeg', size: 1001 }] 501 | }); 502 | }); 503 | }); 504 | -------------------------------------------------------------------------------- /src/js/core.js: -------------------------------------------------------------------------------- 1 | ;(function ($, window, document, undefined) { 2 | 3 | 'use strict'; 4 | 5 | /** Default values */ 6 | var pluginName = 'mediumInsert', 7 | defaults = { 8 | editor: null, 9 | enabled: true, 10 | addons: { 11 | images: true, // boolean or object containing configuration 12 | embeds: true 13 | } 14 | }; 15 | 16 | /** 17 | * Capitalize first character 18 | * 19 | * @param {string} str 20 | * @return {string} 21 | */ 22 | 23 | function ucfirst(str) { 24 | return str.charAt(0).toUpperCase() + str.slice(1); 25 | } 26 | 27 | /** 28 | * Core plugin's object 29 | * 30 | * Sets options, variables and calls init() function 31 | * 32 | * @constructor 33 | * @param {DOM} el - DOM element to init the plugin on 34 | * @param {object} options - Options to override defaults 35 | * @return {void} 36 | */ 37 | 38 | function Core(el, options) { 39 | var editor; 40 | 41 | this.el = el; 42 | this.$el = $(el); 43 | this.templates = window.MediumInsert.Templates; 44 | 45 | if (options) { 46 | // Fix #142 47 | // Avoid deep copying editor object, because since v2.3.0 it contains circular references which causes jQuery.extend to break 48 | // Instead copy editor object to this.options manually 49 | editor = options.editor; 50 | options.editor = null; 51 | } 52 | this.options = $.extend(true, {}, defaults, options); 53 | this.options.editor = editor; 54 | if (options) { 55 | options.editor = editor; // Restore original object definition 56 | } 57 | 58 | this._defaults = defaults; 59 | this._name = pluginName; 60 | 61 | // Extend editor's functions 62 | if (this.options && this.options.editor) { 63 | if (this.options.editor._serialize === undefined) { 64 | this.options.editor._serialize = this.options.editor.serialize; 65 | } 66 | if (this.options.editor._destroy === undefined) { 67 | this.options.editor._destroy = this.options.editor.destroy; 68 | } 69 | if (this.options.editor._setup === undefined) { 70 | this.options.editor._setup = this.options.editor.setup; 71 | } 72 | this.options.editor._hideInsertButtons = this.hideButtons; 73 | 74 | this.options.editor.serialize = this.editorSerialize; 75 | this.options.editor.destroy = this.editorDestroy; 76 | this.options.editor.setup = this.editorSetup; 77 | 78 | if (this.options.editor.getExtensionByName('placeholder') !== undefined) { 79 | this.options.editor.getExtensionByName('placeholder').updatePlaceholder = this.editorUpdatePlaceholder; 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * Initialization 86 | * 87 | * @return {void} 88 | */ 89 | 90 | Core.prototype.init = function () { 91 | this.$el.addClass('medium-editor-insert-plugin'); 92 | 93 | if (typeof this.options.addons !== 'object' || Object.keys(this.options.addons).length === 0) { 94 | this.disable(); 95 | } 96 | 97 | this.initAddons(); 98 | this.clean(); 99 | this.events(); 100 | }; 101 | 102 | /** 103 | * Event listeners 104 | * 105 | * @return {void} 106 | */ 107 | 108 | Core.prototype.events = function () { 109 | var that = this; 110 | 111 | this.$el 112 | .on('dragover drop', function (e) { 113 | e.preventDefault(); 114 | }) 115 | .on('keyup click', $.proxy(this, 'toggleButtons')) 116 | .on('selectstart mousedown', '.medium-insert, .medium-insert-buttons', $.proxy(this, 'disableSelection')) 117 | .on('click', '.medium-insert-buttons-show', $.proxy(this, 'toggleAddons')) 118 | .on('click', '.medium-insert-action', $.proxy(this, 'addonAction')) 119 | .on('paste', '.medium-insert-caption-placeholder', function (e) { 120 | $.proxy(that, 'removeCaptionPlaceholder')($(e.target)); 121 | }); 122 | 123 | $(window).on('resize', $.proxy(this, 'positionButtons', null)); 124 | }; 125 | 126 | /** 127 | * Return editor instance 128 | * 129 | * @return {object} MediumEditor 130 | */ 131 | 132 | Core.prototype.getEditor = function () { 133 | return this.options.editor; 134 | }; 135 | 136 | /** 137 | * Extend editor's serialize function 138 | * 139 | * @return {object} Serialized data 140 | */ 141 | 142 | Core.prototype.editorSerialize = function () { 143 | var data = this._serialize(); 144 | 145 | $.each(data, function (key) { 146 | var $data = $('
').html(data[key].value); 147 | 148 | $data.find('.medium-insert-buttons').remove(); 149 | $data.find('.medium-insert-active').removeClass('medium-insert-active'); 150 | 151 | // Restore original embed code from embed wrapper attribute value. 152 | $data.find('[data-embed-code]').each(function () { 153 | var $this = $(this), 154 | html = $('
').html($this.attr('data-embed-code')).text(); 155 | $this.html(html); 156 | }); 157 | 158 | data[key].value = $data.html(); 159 | }); 160 | 161 | return data; 162 | }; 163 | 164 | /** 165 | * Extend editor's destroy function to deactivate this plugin too 166 | * 167 | * @return {void} 168 | */ 169 | 170 | Core.prototype.editorDestroy = function () { 171 | $.each(this.elements, function (key, el) { 172 | if ($(el).data('plugin_' + pluginName) instanceof Core) { 173 | $(el).data('plugin_' + pluginName).disable(); 174 | } 175 | }); 176 | 177 | this._destroy(); 178 | }; 179 | 180 | /** 181 | * Extend editor's setup function to activate this plugin too 182 | * 183 | * @return {void} 184 | */ 185 | 186 | Core.prototype.editorSetup = function () { 187 | this._setup(); 188 | 189 | $.each(this.elements, function (key, el) { 190 | if ($(el).data('plugin_' + pluginName) instanceof Core) { 191 | $(el).data('plugin_' + pluginName).enable(); 192 | } 193 | }); 194 | }; 195 | 196 | /** 197 | * Extend editor's placeholder.updatePlaceholder function to show placeholder dispite of the plugin buttons 198 | * 199 | * @return {void} 200 | */ 201 | 202 | Core.prototype.editorUpdatePlaceholder = function (el, dontShow) { 203 | var contents = $(el).children() 204 | .not('.medium-insert-buttons').contents(); 205 | 206 | if (!dontShow && contents.length === 1 && contents[0].nodeName.toLowerCase() === 'br') { 207 | this.showPlaceholder(el); 208 | this.base._hideInsertButtons($(el)); 209 | } else { 210 | this.hidePlaceholder(el); 211 | } 212 | }; 213 | 214 | /** 215 | * Trigger editableInput on editor 216 | * 217 | * @return {void} 218 | */ 219 | 220 | Core.prototype.triggerInput = function () { 221 | if (this.getEditor()) { 222 | this.getEditor().trigger('editableInput', null, this.el); 223 | } 224 | }; 225 | 226 | /** 227 | * Deselects selected text 228 | * 229 | * @return {void} 230 | */ 231 | 232 | Core.prototype.deselect = function () { 233 | document.getSelection().removeAllRanges(); 234 | }; 235 | 236 | /** 237 | * Disables the plugin 238 | * 239 | * @return {void} 240 | */ 241 | 242 | Core.prototype.disable = function () { 243 | this.options.enabled = false; 244 | 245 | this.$el.find('.medium-insert-buttons').addClass('hide'); 246 | }; 247 | 248 | /** 249 | * Enables the plugin 250 | * 251 | * @return {void} 252 | */ 253 | 254 | Core.prototype.enable = function () { 255 | this.options.enabled = true; 256 | 257 | this.$el.find('.medium-insert-buttons').removeClass('hide'); 258 | }; 259 | 260 | /** 261 | * Disables selectstart mousedown events on plugin elements except images 262 | * 263 | * @return {void} 264 | */ 265 | 266 | Core.prototype.disableSelection = function (e) { 267 | var $el = $(e.target); 268 | 269 | if ($el.is('img') === false || $el.hasClass('medium-insert-buttons-show')) { 270 | e.preventDefault(); 271 | } 272 | }; 273 | 274 | /** 275 | * Initialize addons 276 | * 277 | * @return {void} 278 | */ 279 | 280 | Core.prototype.initAddons = function () { 281 | var that = this; 282 | 283 | if (!this.options.addons || this.options.addons.length === 0) { 284 | return; 285 | } 286 | 287 | $.each(this.options.addons, function (addon, options) { 288 | var addonName = pluginName + ucfirst(addon); 289 | 290 | if (options === false) { 291 | delete that.options.addons[addon]; 292 | return; 293 | } 294 | 295 | that.$el[addonName](options); 296 | that.options.addons[addon] = that.$el.data('plugin_' + addonName).options; 297 | }); 298 | }; 299 | 300 | /** 301 | * Cleans a content of the editor 302 | * 303 | * @return {void} 304 | */ 305 | 306 | Core.prototype.clean = function () { 307 | var that = this, 308 | $buttons, $lastEl, $text; 309 | 310 | if (this.options.enabled === false) { 311 | return; 312 | } 313 | 314 | if (this.$el.html().length === 0) { 315 | this.$el.html(this.templates['src/js/templates/core-empty-line.hbs']().trim()); 316 | } 317 | 318 | // Fix #29 319 | // Wrap content text in

to avoid Firefox problems 320 | $text = this.$el 321 | .contents() 322 | .filter(function () { 323 | return (this.nodeName === '#text' && $.trim($(this).text()) !== '') || this.nodeName.toLowerCase() === 'br'; 324 | }); 325 | 326 | $text.each(function () { 327 | $(this).wrap('

'); 328 | 329 | // Fix #145 330 | // Move caret at the end of the element that's being wrapped 331 | that.moveCaret($(this).parent(), $(this).text().length); 332 | }); 333 | 334 | this.addButtons(); 335 | 336 | $buttons = this.$el.find('.medium-insert-buttons'); 337 | $lastEl = $buttons.prev(); 338 | if ($lastEl.attr('class') && $lastEl.attr('class').match(/medium\-insert(?!\-active)/)) { 339 | $buttons.before(this.templates['src/js/templates/core-empty-line.hbs']().trim()); 340 | } 341 | }; 342 | 343 | /** 344 | * Returns HTML template of buttons 345 | * 346 | * @return {string} HTML template of buttons 347 | */ 348 | 349 | Core.prototype.getButtons = function () { 350 | if (this.options.enabled === false) { 351 | return; 352 | } 353 | 354 | return this.templates['src/js/templates/core-buttons.hbs']({ 355 | addons: this.options.addons 356 | }).trim(); 357 | }; 358 | 359 | /** 360 | * Appends buttons at the end of the $el 361 | * 362 | * @return {void} 363 | */ 364 | 365 | Core.prototype.addButtons = function () { 366 | if (this.$el.find('.medium-insert-buttons').length === 0) { 367 | this.$el.append(this.getButtons()); 368 | } 369 | }; 370 | 371 | /** 372 | * Move buttons to current active, empty paragraph and show them 373 | * 374 | * @return {void} 375 | */ 376 | 377 | Core.prototype.toggleButtons = function (e) { 378 | var $el = $(e.target), 379 | selection = window.getSelection(), 380 | that = this, 381 | range, $current, $p, activeAddon; 382 | 383 | if (this.options.enabled === false) { 384 | return; 385 | } 386 | 387 | if (!selection || selection.rangeCount === 0) { 388 | $current = $el; 389 | } else { 390 | range = selection.getRangeAt(0); 391 | $current = $(range.commonAncestorContainer); 392 | } 393 | 394 | // When user clicks on editor's placeholder in FF, $current el is editor itself, not the first paragraph as it should 395 | if ($current.hasClass('medium-editor-insert-plugin')) { 396 | $current = $current.find('p:first'); 397 | } 398 | 399 | $p = $current.is('p') ? $current : $current.closest('p'); 400 | 401 | this.clean(); 402 | 403 | if ($el.hasClass('medium-editor-placeholder') === false && $el.closest('.medium-insert-buttons').length === 0 && $current.closest('.medium-insert-buttons').length === 0) { 404 | 405 | this.$el.find('.medium-insert-active').removeClass('medium-insert-active'); 406 | 407 | $.each(this.options.addons, function (addon) { 408 | if ($el.closest('.medium-insert-' + addon).length) { 409 | $current = $el; 410 | } 411 | 412 | if ($current.closest('.medium-insert-' + addon).length) { 413 | $p = $current.closest('.medium-insert-' + addon); 414 | activeAddon = addon; 415 | return; 416 | } 417 | }); 418 | 419 | if ($p.length && (($p.text().trim() === '' && !activeAddon) || activeAddon === 'images')) { 420 | $p.addClass('medium-insert-active'); 421 | 422 | if (activeAddon === 'images') { 423 | this.$el.find('.medium-insert-buttons').attr('data-active-addon', activeAddon); 424 | } else { 425 | this.$el.find('.medium-insert-buttons').removeAttr('data-active-addon'); 426 | } 427 | 428 | // If buttons are displayed on addon paragraph, wait 100ms for possible captions to display 429 | setTimeout(function () { 430 | that.positionButtons(activeAddon); 431 | that.showButtons(activeAddon); 432 | }, activeAddon ? 100 : 0); 433 | } else { 434 | this.hideButtons(); 435 | } 436 | } 437 | }; 438 | 439 | /** 440 | * Show buttons 441 | * 442 | * @param {string} activeAddon - Name of active addon 443 | * @returns {void} 444 | */ 445 | 446 | Core.prototype.showButtons = function (activeAddon) { 447 | var $buttons = this.$el.find('.medium-insert-buttons'); 448 | 449 | $buttons.show(); 450 | $buttons.find('li').show(); 451 | 452 | if (activeAddon) { 453 | $buttons.find('li').hide(); 454 | $buttons.find('button[data-addon="' + activeAddon + '"]').parent().show(); 455 | } 456 | }; 457 | 458 | /** 459 | * Hides buttons 460 | * 461 | * @param {jQuery} $el - Editor element 462 | * @returns {void} 463 | */ 464 | 465 | Core.prototype.hideButtons = function ($el) { 466 | $el = $el || this.$el; 467 | 468 | $el.find('.medium-insert-buttons').hide(); 469 | $el.find('.medium-insert-buttons-addons').hide(); 470 | $el.find('.medium-insert-buttons-show').removeClass('medium-insert-buttons-rotate'); 471 | }; 472 | 473 | /** 474 | * Position buttons 475 | * 476 | * @param {string} activeAddon - Name of active addon 477 | * @return {void} 478 | */ 479 | 480 | Core.prototype.positionButtons = function (activeAddon) { 481 | var $buttons = this.$el.find('.medium-insert-buttons'), 482 | $p = this.$el.find('.medium-insert-active'), 483 | $lastCaption = $p.hasClass('medium-insert-images-grid') ? [] : $p.find('figure:last figcaption'), 484 | elementsContainer = this.getEditor() ? this.getEditor().options.elementsContainer : $('body').get(0), 485 | elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1, 486 | position = {}; 487 | 488 | if ($p.length) { 489 | position.left = $p.position().left; 490 | position.top = $p.position().top; 491 | 492 | if (activeAddon) { 493 | position.left += $p.width() - $buttons.find('.medium-insert-buttons-show').width() - 10; 494 | position.top += $p.height() - 20 + ($lastCaption.length ? -$lastCaption.height() - parseInt($lastCaption.css('margin-top'), 10) : 10); 495 | } else { 496 | position.left += -parseInt($buttons.find('.medium-insert-buttons-addons').css('left'), 10) - parseInt($buttons.find('.medium-insert-buttons-addons button:first').css('margin-left'), 10); 497 | position.top += parseInt($p.css('margin-top'), 10); 498 | } 499 | 500 | if (elementsContainerAbsolute) { 501 | position.top += elementsContainer.scrollTop; 502 | } 503 | 504 | if (this.$el.hasClass('medium-editor-placeholder') === false && position.left < 0) { 505 | position.left = $p.position().left; 506 | } 507 | 508 | $buttons.css(position); 509 | } 510 | }; 511 | 512 | /** 513 | * Toggles addons buttons 514 | * 515 | * @return {void} 516 | */ 517 | 518 | Core.prototype.toggleAddons = function () { 519 | if (this.$el.find('.medium-insert-buttons').attr('data-active-addon') === 'images') { 520 | this.$el.find('.medium-insert-buttons').find('button[data-addon="images"]').click(); 521 | return; 522 | } 523 | 524 | this.$el.find('.medium-insert-buttons-addons').fadeToggle(); 525 | this.$el.find('.medium-insert-buttons-show').toggleClass('medium-insert-buttons-rotate'); 526 | }; 527 | 528 | /** 529 | * Hide addons buttons 530 | * 531 | * @return {void} 532 | */ 533 | 534 | Core.prototype.hideAddons = function () { 535 | this.$el.find('.medium-insert-buttons-addons').hide(); 536 | this.$el.find('.medium-insert-buttons-show').removeClass('medium-insert-buttons-rotate'); 537 | }; 538 | 539 | /** 540 | * Call addon's action 541 | * 542 | * @param {Event} e 543 | * @return {void} 544 | */ 545 | 546 | Core.prototype.addonAction = function (e) { 547 | var $a = $(e.currentTarget), 548 | addon = $a.data('addon'), 549 | action = $a.data('action'); 550 | 551 | this.$el.data('plugin_' + pluginName + ucfirst(addon))[action](); 552 | }; 553 | 554 | /** 555 | * Move caret at the beginning of the empty paragraph 556 | * 557 | * @param {jQuery} $el Element where to place the caret 558 | * @param {integer} position Position where to move caret. Default: 0 559 | * 560 | * @return {void} 561 | */ 562 | 563 | Core.prototype.moveCaret = function ($el, position) { 564 | var range, sel, el, textEl; 565 | 566 | position = position || 0; 567 | range = document.createRange(); 568 | sel = window.getSelection(); 569 | el = $el.get(0); 570 | 571 | if (!el.childNodes.length) { 572 | textEl = document.createTextNode(' '); 573 | el.appendChild(textEl); 574 | } 575 | 576 | range.setStart(el.childNodes[0], position); 577 | range.collapse(true); 578 | sel.removeAllRanges(); 579 | sel.addRange(range); 580 | }; 581 | 582 | /** 583 | * Add caption 584 | * 585 | * @param {jQuery Element} $el 586 | * @param {string} placeholder 587 | * @return {void} 588 | */ 589 | 590 | Core.prototype.addCaption = function ($el, placeholder) { 591 | var $caption = $el.find('figcaption'); 592 | 593 | if ($caption.length === 0) { 594 | $el.append(this.templates['src/js/templates/core-caption.hbs']({ 595 | placeholder: placeholder 596 | })); 597 | } 598 | }; 599 | 600 | /** 601 | * Remove captions 602 | * 603 | * @param {jQuery Element} $ignore 604 | * @return {void} 605 | */ 606 | 607 | Core.prototype.removeCaptions = function ($ignore) { 608 | var $captions = this.$el.find('figcaption'); 609 | 610 | if ($ignore) { 611 | $captions = $captions.not($ignore); 612 | } 613 | 614 | $captions.each(function () { 615 | if ($(this).hasClass('medium-insert-caption-placeholder') || $(this).text().trim() === '') { 616 | $(this).remove(); 617 | } 618 | }); 619 | }; 620 | 621 | /** 622 | * Remove caption placeholder 623 | * 624 | * @param {jQuery Element} $el 625 | * @return {void} 626 | */ 627 | 628 | Core.prototype.removeCaptionPlaceholder = function ($el) { 629 | var $caption = $el.is('figcaption') ? $el : $el.find('figcaption'); 630 | 631 | if ($caption.length) { 632 | $caption 633 | .removeClass('medium-insert-caption-placeholder') 634 | .removeAttr('data-placeholder'); 635 | } 636 | }; 637 | 638 | /** Plugin initialization */ 639 | 640 | $.fn[pluginName] = function (options) { 641 | return this.each(function () { 642 | var that = this, 643 | textareaId; 644 | 645 | if ($(that).is('textarea')) { 646 | textareaId = $(that).attr('medium-editor-textarea-id'); 647 | that = $(that).siblings('[medium-editor-textarea-id="' + textareaId + '"]').get(0); 648 | } 649 | 650 | if (!$.data(that, 'plugin_' + pluginName)) { 651 | // Plugin initialization 652 | $.data(that, 'plugin_' + pluginName, new Core(that, options)); 653 | $.data(that, 'plugin_' + pluginName).init(); 654 | } else if (typeof options === 'string' && $.data(that, 'plugin_' + pluginName)[options]) { 655 | // Method call 656 | $.data(that, 'plugin_' + pluginName)[options](); 657 | } 658 | }); 659 | }; 660 | 661 | })(jQuery, window, document); 662 | -------------------------------------------------------------------------------- /src/js/embeds.js: -------------------------------------------------------------------------------- 1 | ; (function ($, window, document, undefined) { 2 | 3 | 'use strict'; 4 | 5 | /** Default values */ 6 | var pluginName = 'mediumInsert', 7 | addonName = 'Embeds', // first char is uppercase 8 | defaults = { 9 | label: '', 10 | placeholder: 'Paste a YouTube, Vimeo, Facebook, Twitter or Instagram link and press Enter', 11 | oembedProxy: 'http://medium.iframe.ly/api/oembed?iframe=1', 12 | captions: true, 13 | captionPlaceholder: 'Type caption (optional)', 14 | storeMeta: false, 15 | styles: { 16 | wide: { 17 | label: '' 18 | // added: function ($el) {}, 19 | // removed: function ($el) {} 20 | }, 21 | left: { 22 | label: '' 23 | // added: function ($el) {}, 24 | // removed: function ($el) {} 25 | }, 26 | right: { 27 | label: '' 28 | // added: function ($el) {}, 29 | // removed: function ($el) {} 30 | } 31 | }, 32 | actions: { 33 | remove: { 34 | label: '', 35 | clicked: function () { 36 | var $event = $.Event('keydown'); 37 | 38 | $event.which = 8; 39 | $(document).trigger($event); 40 | } 41 | } 42 | }, 43 | parseOnPaste: false 44 | }; 45 | 46 | /** 47 | * Embeds object 48 | * 49 | * Sets options, variables and calls init() function 50 | * 51 | * @constructor 52 | * @param {DOM} el - DOM element to init the plugin on 53 | * @param {object} options - Options to override defaults 54 | * @return {void} 55 | */ 56 | 57 | function Embeds(el, options) { 58 | this.el = el; 59 | this.$el = $(el); 60 | this.templates = window.MediumInsert.Templates; 61 | this.core = this.$el.data('plugin_' + pluginName); 62 | 63 | this.options = $.extend(true, {}, defaults, options); 64 | 65 | this._defaults = defaults; 66 | this._name = pluginName; 67 | 68 | // Extend editor's functions 69 | if (this.core.getEditor()) { 70 | this.core.getEditor()._serializePreEmbeds = this.core.getEditor().serialize; 71 | this.core.getEditor().serialize = this.editorSerialize; 72 | } 73 | 74 | this.init(); 75 | } 76 | 77 | /** 78 | * Initialization 79 | * 80 | * @return {void} 81 | */ 82 | 83 | Embeds.prototype.init = function () { 84 | var $embeds = this.$el.find('.medium-insert-embeds'); 85 | 86 | $embeds.attr('contenteditable', false); 87 | $embeds.each(function () { 88 | if ($(this).find('.medium-insert-embeds-overlay').length === 0) { 89 | $(this).append($('

').addClass('medium-insert-embeds-overlay')); 90 | } 91 | }); 92 | 93 | this.events(); 94 | this.backwardsCompatibility(); 95 | }; 96 | 97 | /** 98 | * Event listeners 99 | * 100 | * @return {void} 101 | */ 102 | 103 | Embeds.prototype.events = function () { 104 | $(document) 105 | .on('click', $.proxy(this, 'unselectEmbed')) 106 | .on('keydown', $.proxy(this, 'removeEmbed')) 107 | .on('click', '.medium-insert-embeds-toolbar .medium-editor-action', $.proxy(this, 'toolbarAction')) 108 | .on('click', '.medium-insert-embeds-toolbar2 .medium-editor-action', $.proxy(this, 'toolbar2Action')); 109 | 110 | this.$el 111 | .on('keyup click paste', $.proxy(this, 'togglePlaceholder')) 112 | .on('keydown', $.proxy(this, 'processLink')) 113 | .on('click', '.medium-insert-embeds-overlay', $.proxy(this, 'selectEmbed')) 114 | .on('contextmenu', '.medium-insert-embeds-placeholder', $.proxy(this, 'fixRightClickOnPlaceholder')); 115 | 116 | if (this.options.parseOnPaste) { 117 | this.$el 118 | .on('paste', $.proxy(this, 'processPasted')); 119 | } 120 | 121 | $(window) 122 | .on('resize', $.proxy(this, 'autoRepositionToolbars')); 123 | }; 124 | 125 | /** 126 | * Replace v0.* class names with new ones, wrap embedded content to new structure 127 | * 128 | * @return {void} 129 | */ 130 | 131 | Embeds.prototype.backwardsCompatibility = function () { 132 | var that = this; 133 | 134 | this.$el.find('.mediumInsert-embeds') 135 | .removeClass('mediumInsert-embeds') 136 | .addClass('medium-insert-embeds'); 137 | 138 | this.$el.find('.medium-insert-embeds').each(function () { 139 | if ($(this).find('.medium-insert-embed').length === 0) { 140 | $(this).after(that.templates['src/js/templates/embeds-wrapper.hbs']({ 141 | html: $(this).html() 142 | })); 143 | $(this).remove(); 144 | } 145 | }); 146 | }; 147 | 148 | /** 149 | * Extend editor's serialize function 150 | * 151 | * @return {object} Serialized data 152 | */ 153 | 154 | Embeds.prototype.editorSerialize = function () { 155 | var data = this._serializePreEmbeds(); 156 | 157 | $.each(data, function (key) { 158 | var $data = $('
').html(data[key].value), 159 | $embeds = $data.find('.medium-insert-embeds'); 160 | 161 | $embeds.removeAttr('contenteditable'); 162 | $embeds.find('figcaption').removeAttr('contenteditable'); 163 | $data.find('.medium-insert-embeds-overlay').remove(); 164 | 165 | data[key].value = $data.html(); 166 | }); 167 | 168 | return data; 169 | }; 170 | 171 | /** 172 | * Add embedded element 173 | * 174 | * @return {void} 175 | */ 176 | 177 | Embeds.prototype.add = function () { 178 | var $place = this.$el.find('.medium-insert-active'); 179 | 180 | // Fix #132 181 | // Make sure that the content of the paragraph is empty and
is wrapped in

to avoid Firefox problems 182 | $place.html(this.templates['src/js/templates/core-empty-line.hbs']().trim()); 183 | 184 | // Replace paragraph with div to prevent #124 issue with pasting in Chrome, 185 | // because medium editor wraps inserted content into paragraph and paragraphs can't be nested 186 | if ($place.is('p')) { 187 | $place.replaceWith('
' + $place.html() + '
'); 188 | $place = this.$el.find('.medium-insert-active'); 189 | this.core.moveCaret($place); 190 | } 191 | 192 | $place.addClass('medium-insert-embeds medium-insert-embeds-input medium-insert-embeds-active'); 193 | 194 | this.togglePlaceholder({ target: $place.get(0) }); 195 | 196 | $place.click(); 197 | this.core.hideButtons(); 198 | }; 199 | 200 | /** 201 | * Toggles placeholder 202 | * 203 | * @param {Event} e 204 | * @return {void} 205 | */ 206 | 207 | Embeds.prototype.togglePlaceholder = function (e) { 208 | var $place = $(e.target), 209 | selection = window.getSelection(), 210 | range, $current, text; 211 | 212 | if (!selection || selection.rangeCount === 0) { 213 | return; 214 | } 215 | 216 | range = selection.getRangeAt(0); 217 | $current = $(range.commonAncestorContainer); 218 | 219 | if ($current.hasClass('medium-insert-embeds-active')) { 220 | $place = $current; 221 | } else if ($current.closest('.medium-insert-embeds-active').length) { 222 | $place = $current.closest('.medium-insert-embeds-active'); 223 | } 224 | 225 | if ($place.hasClass('medium-insert-embeds-active')) { 226 | 227 | text = $place.text().trim(); 228 | 229 | if (text === '' && $place.hasClass('medium-insert-embeds-placeholder') === false) { 230 | $place 231 | .addClass('medium-insert-embeds-placeholder') 232 | .attr('data-placeholder', this.options.placeholder); 233 | } else if (text !== '' && $place.hasClass('medium-insert-embeds-placeholder')) { 234 | $place 235 | .removeClass('medium-insert-embeds-placeholder') 236 | .removeAttr('data-placeholder'); 237 | } 238 | 239 | } else { 240 | this.$el.find('.medium-insert-embeds-active').remove(); 241 | } 242 | }; 243 | 244 | /** 245 | * Right click on placeholder in Chrome selects whole line. Fix this by placing caret at the end of line 246 | * 247 | * @param {Event} e 248 | * @return {void} 249 | */ 250 | 251 | Embeds.prototype.fixRightClickOnPlaceholder = function (e) { 252 | this.core.moveCaret($(e.target)); 253 | }; 254 | 255 | /** 256 | * Process link 257 | * 258 | * @param {Event} e 259 | * @return {void} 260 | */ 261 | 262 | Embeds.prototype.processLink = function (e) { 263 | var $place = this.$el.find('.medium-insert-embeds-active'), 264 | url; 265 | 266 | if (!$place.length) { 267 | return; 268 | } 269 | 270 | url = $place.text().trim(); 271 | 272 | // Return empty placeholder on backspace, delete or enter 273 | if (url === '' && [8, 46, 13].indexOf(e.which) !== -1) { 274 | $place.remove(); 275 | return; 276 | } 277 | 278 | if (e.which === 13) { 279 | e.preventDefault(); 280 | e.stopPropagation(); 281 | 282 | if (this.options.oembedProxy) { 283 | this.oembed(url); 284 | } else { 285 | this.parseUrl(url); 286 | } 287 | } 288 | }; 289 | 290 | /** 291 | * Process Pasted 292 | * 293 | * @param {Event} e 294 | * @return {void} 295 | */ 296 | 297 | Embeds.prototype.processPasted = function (e) { 298 | var pastedUrl, linkRegEx; 299 | if ($(".medium-insert-embeds-active").length) { 300 | return; 301 | } 302 | 303 | pastedUrl = e.originalEvent.clipboardData.getData('text'); 304 | linkRegEx = new RegExp('^(http(s?):)?\/\/','i'); 305 | if (linkRegEx.test(pastedUrl)) { 306 | if (this.options.oembedProxy) { 307 | this.oembed(pastedUrl, true); 308 | } else { 309 | this.parseUrl(pastedUrl, true); 310 | } 311 | } 312 | }; 313 | 314 | /** 315 | * Get HTML via oEmbed proxy 316 | * 317 | * @param {string} url 318 | * @return {void} 319 | */ 320 | 321 | Embeds.prototype.oembed = function (url, pasted) { 322 | var that = this; 323 | 324 | $.support.cors = true; 325 | 326 | $.ajax({ 327 | crossDomain: true, 328 | cache: false, 329 | url: this.options.oembedProxy, 330 | dataType: 'json', 331 | data: { 332 | url: url 333 | }, 334 | success: function (data) { 335 | var html = data && data.html; 336 | 337 | if (that.options.storeMeta) { 338 | html += '
'; 339 | } 340 | 341 | if (data && !html && data.type === 'photo' && data.url) { 342 | html = ''; 343 | } 344 | 345 | if (!html) { 346 | // Prevent render empty embed. 347 | $.proxy(that, 'convertBadEmbed', url)(); 348 | return; 349 | } 350 | 351 | if (pasted) { 352 | $.proxy(that, 'embed', html, url)(); 353 | } else { 354 | $.proxy(that, 'embed', html)(); 355 | } 356 | }, 357 | error: function (jqXHR, textStatus, errorThrown) { 358 | var responseJSON = (function () { 359 | try { 360 | return JSON.parse(jqXHR.responseText); 361 | } catch (e) { } 362 | })(); 363 | 364 | if (typeof window.console !== 'undefined') { 365 | window.console.log((responseJSON && responseJSON.error) || jqXHR.status || errorThrown.message); 366 | } else { 367 | window.alert('Error requesting media from ' + that.options.oembedProxy + ' to insert: ' + errorThrown + ' (response status: ' + jqXHR.status + ')'); 368 | } 369 | 370 | $.proxy(that, 'convertBadEmbed', url)(); 371 | } 372 | }); 373 | }; 374 | 375 | /** 376 | * Get HTML using regexp 377 | * 378 | * @param {string} url 379 | * @param {bool} pasted 380 | * @return {void} 381 | */ 382 | 383 | Embeds.prototype.parseUrl = function (url, pasted) { 384 | var html; 385 | 386 | if (!(new RegExp(['youtube', 'youtu.be', 'vimeo', 'instagram', 'twitter', 'facebook'].join('|')).test(url))) { 387 | $.proxy(this, 'convertBadEmbed', url)(); 388 | return false; 389 | } 390 | 391 | html = url.replace(/\n?/g, '') 392 | .replace(/^((http(s)?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/(watch\?v=|v\/)?)([a-zA-Z0-9\-_]+)(.*)?$/, '
') 393 | .replace(/^https?:\/\/vimeo\.com(\/.+)?\/([0-9]+)$/, '
') 394 | .replace(/^https:\/\/twitter\.com\/(\w+)\/status\/(\d+)\/?$/, '') 395 | .replace(/^(https:\/\/www\.facebook\.com\/(.*))$/, '
') 396 | .replace(/^https?:\/\/instagram\.com\/p\/(.+)\/?$/, ''); 397 | 398 | if (this.options.storeMeta) { 399 | html += '
'; 400 | } 401 | 402 | if ((/<("[^"]*"|'[^']*'|[^'">])*>/).test(html) === false) { 403 | $.proxy(this, 'convertBadEmbed', url)(); 404 | return false; 405 | } 406 | 407 | if (pasted) { 408 | this.embed(html, url); 409 | } else { 410 | this.embed(html); 411 | } 412 | }; 413 | 414 | /** 415 | * Add html to page 416 | * 417 | * @param {string} html 418 | * @param {string} pastedUrl 419 | * @return {void} 420 | */ 421 | 422 | Embeds.prototype.embed = function (html, pastedUrl) { 423 | var $place = this.$el.find('.medium-insert-embeds-active'), 424 | $div; 425 | 426 | if (!html) { 427 | alert('Incorrect URL format specified'); 428 | return false; 429 | } else { 430 | if (html.indexOf('') > -1) { 431 | // Store embed code with