├── src
├── js
│ ├── index.js
│ ├── utilities
│ │ ├── identity.js
│ │ ├── regexp.js
│ │ ├── string-replace.js
│ │ ├── ajax.js
│ │ ├── dom.js
│ │ └── extend.js
│ ├── defaults
│ │ └── defaults.js
│ ├── keyboard.js
│ ├── controller.js
│ ├── view.js
│ └── mentions.js
└── scss
│ └── style.scss
├── .gitignore
├── docs
├── scripts
│ ├── linenumber.js
│ └── prettify
│ │ ├── lang-css.js
│ │ └── Apache-License-2.0.txt
├── index.js.html
├── styles
│ ├── prettify-jsdoc.css
│ ├── prettify-tomorrow.css
│ └── jsdoc-default.css
├── format.js.html
├── index.html
├── module-search.html
├── identity.js.html
├── string-replace.js.html
├── ajax.js.html
├── ajax.html
├── identity.html
├── extend.html
├── module-mentions.html
├── extend.js.html
├── defaults.html
├── module-controller-Controller.html
├── search.js.html
├── defaults.js.html
├── keyboard.js.html
├── defaults-ajaxDefaults.html
├── KEYS.html
├── keydown.html
├── controller.js.html
├── module-controller.html
├── view.js.html
├── module-controller-AJAXController.html
└── mentions.js.html
├── dist
├── style.css
├── quill-mentions.css.map
├── quill-mentions.css
└── quill-mentions.min.js
├── package.json
├── lp
├── utilities.js
├── style.css
└── quill.base.css
├── LICENSE
├── gruntfile.js
├── README.md
└── index.html
/src/js/index.js:
--------------------------------------------------------------------------------
1 | global.QuillMentions = require("./mentions");
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | favicon.ico
2 | .sass-cache
3 | node_modules
4 | plugin
--------------------------------------------------------------------------------
/src/js/utilities/identity.js:
--------------------------------------------------------------------------------
1 | /** @module utilities/identity */
2 |
3 | module.exports = identity;
4 |
5 | /** @function identity */
6 | function identity(d) {
7 | return d;
8 | }
--------------------------------------------------------------------------------
/src/js/utilities/regexp.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | escapeRegExp: function escapeRegExp(str) {
3 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
4 | }
5 | };
--------------------------------------------------------------------------------
/src/js/utilities/string-replace.js:
--------------------------------------------------------------------------------
1 | var escapeRegExp = require("./regexp").escapeRegExp;
2 |
3 | module.exports = {
4 | all: replaceAll,
5 | };
6 |
7 | /**
8 | * @param {stirng} [options] - RegExp options (like "i").
9 | **/
10 | function replaceAll(string, toReplace, replaceWith, options) {
11 | options = options || "";
12 | var reOpts = "g" + options,
13 | re = new RegExp(escapeRegExp(toReplace), reOpts);
14 |
15 | return string.replace(re, replaceWith);
16 | }
--------------------------------------------------------------------------------
/docs/scripts/linenumber.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var counter = 0;
3 | var numbered;
4 | var source = document.getElementsByClassName('prettyprint source');
5 |
6 | if (source && source[0]) {
7 | source = source[0].getElementsByTagName('code')[0];
8 |
9 | numbered = source.innerHTML.split('\n');
10 | numbered = numbered.map(function(item) {
11 | counter++;
12 | return ' ' + item;
13 | });
14 |
15 | source.innerHTML = numbered.join('\n');
16 | }
17 | })();
18 |
--------------------------------------------------------------------------------
/dist/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica', 'Arial', san-serif;
3 | font-size: 13px;
4 | padding: 25px;
5 | }
6 | #content-container {
7 | margin: auto;
8 | width: 960px;
9 | }
10 | #formatting-container {
11 | background-color: #f5f5f5;
12 | border-bottom: 1px solid #ccc;
13 | padding: 5px 12px;
14 | }
15 | #formatting-container .ql-active,
16 | #formatting-container button:hover {
17 | color: #008000;
18 | font-weight: bold;
19 | }
20 | #editor-container {
21 | height: 600px;
22 | }
23 | #editor-wrapper {
24 | border: 1px solid #aaa;
25 | box-shadow: 0 0 2px 2px #ddd;
26 | }
27 |
--------------------------------------------------------------------------------
/dist/quill-mentions.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "mappings": "AASA,YAAkB;EAChB,UAAU,EAAE,qCACkB;EAC9B,OAAO,EAAE,IAAI;;AAEb,eAAG;EACD,eAAe,EAAE,IAAI;EACrB,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;;AAEV,kBAAG;EACD,UAAU,EAAE,IAAI;EAChB,aAAa,EAAE,eAAe;EAC9B,MAAM,EAAE,OAAO;EACf,OAAO,EAAE,QAAQ;EAlBrB,iBAAiB,EAAE,eAAc;EACjC,SAAS,EAAE,eAAc;;AAqBrB,uEACQ;EACN,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,KAAK;;AAGd,6BAAa;EACX,aAAa,EAAE,IAAI;;AAIzB,6BAAmB;EACjB,OAAO,EAAE,KAAK;;;AAIlB,YAAa;EACX,MAAM,EAAE,cAAc;EACtB,SAAS,EAAE,IAAI;EACf,OAAO,EAAE,CAAC;;;AAKZ,oBAAqB;EACnB,UAAU,EAAE,IAAI;EAChB,UAAU,EAAE,4EAGkB;EAC9B,OAAO,EAAE,gBAAgB;EACzB,cAAc,EAAE,IAAI;EArDpB,iBAAiB,EAAE,eAAc;EACjC,SAAS,EAAE,eAAc;;AAwDzB,sBAAE;EACA,WAAW,EAAE,iBAAwB;EACrC,YAAY,EAAE,GAAG",
4 | "sources": ["../src/scss/style.scss"],
5 | "names": [],
6 | "file": "quill-mentions.css"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quill-mentions",
3 | "version": "0.2.4",
4 | "description": "a module for integrating at-style mentions into quill.js",
5 | "keywords": [
6 | "quill",
7 | "mentions",
8 | "supergoodcodeiswear",
9 | "bugsgalorenojkbutseriously"
10 | ],
11 | "main": "src/js/mentions.js",
12 | "directories": {
13 | "doc": "docs"
14 | },
15 | "scripts": {
16 | "test": "echo \"Error: no test specified\" && exit 1"
17 | },
18 | "author": "boots",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "grunt": "~0.4.5",
22 | "grunt-browserify": "~3.3.0",
23 | "grunt-contrib-sass": "~0.9.2",
24 | "grunt-contrib-uglify": "~0.7.0",
25 | "matchdep": "~0.3.0",
26 | "grunt-jsdoc": "~0.5.8",
27 | "grunt-contrib-watch": "~0.6.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/docs/scripts/prettify/lang-css.js:
--------------------------------------------------------------------------------
1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
3 |
--------------------------------------------------------------------------------
/src/js/utilities/ajax.js:
--------------------------------------------------------------------------------
1 | /** @module utilities/ajax */
2 | module.exports = {
3 |
4 | // from stackoverflow
5 | // https://stackoverflow.com/questions/9838812/how-can-i-open-a-json-file-in-javascript-without-jquery
6 | /**
7 | * @function loadJSON
8 | */
9 | loadJSON: function loadJSON(path, success, error) {
10 | var xhr = new XMLHttpRequest();
11 | xhr.onreadystatechange = function()
12 | {
13 | if (xhr.readyState === XMLHttpRequest.DONE) {
14 | if (xhr.status === 200) {
15 | if (success)
16 | success(JSON.parse(xhr.responseText));
17 | } else {
18 | if (error)
19 | error(xhr);
20 | }
21 | }
22 | };
23 | xhr.open("GET", path, true);
24 | xhr.send();
25 | return xhr;
26 | },
27 | };
--------------------------------------------------------------------------------
/lp/utilities.js:
--------------------------------------------------------------------------------
1 |
2 | function loadJSON(path, success, error) {
3 | var xhr = new XMLHttpRequest();
4 | xhr.onreadystatechange = function()
5 | {
6 | if (xhr.readyState === XMLHttpRequest.DONE) {
7 | if (xhr.status === 200) {
8 | if (success)
9 | success(JSON.parse(xhr.responseText));
10 | } else {
11 | if (error)
12 | error(xhr);
13 | }
14 | }
15 | };
16 | xhr.open("GET", path, true);
17 | xhr.send();
18 | return xhr;
19 | }
20 |
21 | function addClass(node, className) {
22 | if (!node) return;
23 | if (!node.className) node.className = className;
24 | else if (node.className.indexOf(className) === -1) {
25 | node.className += " "+className;
26 | }
27 | }
28 | function removeClass(node, className) {
29 | if (!node) return;
30 | while (node.className.indexOf(className) !== -1) {
31 | node.className = node.className.replace(className, "");
32 | }
33 | }
--------------------------------------------------------------------------------
/src/js/utilities/dom.js:
--------------------------------------------------------------------------------
1 | module.exports.addClass = addClass;
2 | module.exports.getOlderSiblingsInclusive = getOlderSiblingsInclusive;
3 | module.exports.hasClass = hasClass;
4 | module.exports.removeClass = removeClass;
5 |
6 | function addClass(node, className) {
7 | if (!hasClass(node, className)) {
8 | node.className += " "+className;
9 | }
10 | }
11 |
12 | function getOlderSiblingsInclusive(node) {
13 | var result = [node];
14 | if (!node) return [];
15 | while (node.previousSibling) {
16 | result.push(node.previousSibling);
17 | node = node.previousSibling;
18 | }
19 | return result;
20 | }
21 |
22 | function hasClass(node, className) {
23 | if (!node) return console.log("Called hasClass on an empty node");
24 | return node.className.indexOf(className) !== -1;
25 | }
26 |
27 | function removeClass(node, className) {
28 | if (!hasClass(node, className)) return;
29 | while (hasClass(node, className)) {
30 | node.className = node.className.replace(className, "");
31 | }
32 | }
--------------------------------------------------------------------------------
/lp/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | color: #222;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | h1 {
8 | font-family: sans-serif;
9 | font-size: 42px;
10 | line-height: 56px;
11 | text-align: center;
12 | margin: 40px 0;
13 | }
14 |
15 | h1 small {
16 | display: block;
17 | color: #666;
18 | font-size: 24px;
19 | line-height: 32px;
20 | margin: 0 auto;
21 | max-width: 600px;
22 | padding: 0 40px;
23 | }
24 |
25 | .quill-wrapper {
26 | border-bottom: solid 5px #ddd;
27 | border-left: solid 2px #ddd;
28 | border-right: solid 2px #ddd;
29 | border-top: solid 2px #ddd;
30 | box-sizing: border-box;
31 | margin: 0 auto;
32 | max-width: 500px;
33 | }
34 | .quill-wrapper.focus {
35 | border-color: #2ba6cb;
36 | }
37 |
38 | .toolbar,
39 | .quill-wrapper .ql-container {
40 | margin: 0 auto;
41 | max-width: 500px;
42 |
43 | }
44 |
45 | .quill-wrapper .toolbar {
46 | padding: 14px 0 14px 14px;
47 | }
48 |
49 | .quill-wrapper .ql-container {
50 | border-top: solid 1px #ddd;
51 | box-sizing: border-box;
52 | height: 300px;
53 | max-width: 500px;
54 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Brett Beutell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/js/utilities/extend.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Extend module
3 | * @module utilities/extend
4 | */
5 | module.exports = extend;
6 |
7 | /**
8 | * Shallow-copies an arbitrary number of objects' properties into the first argument. Applies "last-in-wins" policy to conflicting property names.
9 | * @function extend
10 | * @param {...Object} o
11 | */
12 | function extend(o) {
13 | var args = [].slice.call(arguments, 0),
14 | result = args[0];
15 |
16 | for (var i=1; i < args.length; i++) {
17 | result = extendHelper(result, args[i]);
18 | }
19 |
20 | return result;
21 | }
22 |
23 | /**
24 | * Shallow-copies one object into another.
25 | * @function extendHelper
26 | * @param {Object} destination - Object into which `source` properties will be copied.
27 | * @param {Object} source - Object whose properties will be copied into `destination`.
28 | */
29 | function extendHelper(destination, source) {
30 | // thanks be to angus kroll
31 | // https://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/
32 | for (var k in source) {
33 | if (source.hasOwnProperty(k)) {
34 | destination[k] = source[k];
35 | }
36 | }
37 | return destination;
38 | }
--------------------------------------------------------------------------------
/dist/quill-mentions.css:
--------------------------------------------------------------------------------
1 | .ql-mentions {
2 | box-shadow: -1px -1px 2px #ddd, 1px 1px 2px #ddd;
3 | display: none;
4 | }
5 | .ql-mentions ul {
6 | list-style-type: none;
7 | margin: 0;
8 | padding: 0;
9 | }
10 | .ql-mentions ul li {
11 | background: #fff;
12 | border-bottom: solid thin #ddd;
13 | cursor: pointer;
14 | padding: 8px 12px;
15 | -webkit-transform: translateZ(1px);
16 | transform: translateZ(1px);
17 | }
18 | .ql-mentions ul li.ql-mention-choice-selected, .ql-mentions ul li:hover {
19 | background: #888;
20 | color: white;
21 | }
22 | .ql-mentions ul li:last-child {
23 | border-bottom: none;
24 | }
25 | .ql-is-mentioning.ql-mentions {
26 | display: table;
27 | }
28 |
29 | .ql-mentions {
30 | border: solid 1px #ddd;
31 | font-size: 13px;
32 | padding: 0;
33 | }
34 |
35 | .ql-mention-no-match {
36 | background: #fff;
37 | box-shadow: -1px -1px 1px #bbb, 1px 1px 1px #bbb, -1px 1px 1px #bbb, 1px -1px 1px #bbb;
38 | padding: 8px 12px 8px 8px;
39 | pointer-events: none;
40 | -webkit-transform: translateZ(1px);
41 | transform: translateZ(1px);
42 | }
43 | .ql-mention-no-match i {
44 | border-left: solid 2px #DF928E;
45 | padding-left: 6px;
46 | }
47 |
48 | /*# sourceMappingURL=quill-mentions.css.map */
49 |
--------------------------------------------------------------------------------
/src/scss/style.scss:
--------------------------------------------------------------------------------
1 | // mentions styles
2 | $faux-link: #2ba6cb;
3 | $warning-color: #DF928E;
4 |
5 | @mixin translate-z($n) {
6 | -webkit-transform: translateZ($n); // hack... makes the overlay _actually_ overlay text inside the editor. tried using z-index but no dice.
7 | transform: translateZ($n);
8 | }
9 |
10 | %ql-mentions-base {
11 | box-shadow: -1px -1px 2px #ddd,
12 | 1px 1px 2px #ddd;
13 | display: none;
14 |
15 | ul {
16 | list-style-type: none;
17 | margin: 0;
18 | padding: 0;
19 |
20 | li {
21 | background: #fff;
22 | border-bottom: solid thin #ddd;
23 | cursor: pointer;
24 | padding: 8px 12px;
25 |
26 | @include translate-z(1px);
27 |
28 | &.ql-mention-choice-selected,
29 | &:hover {
30 | background: #888;
31 | color: white;
32 | }
33 |
34 | &:last-child {
35 | border-bottom: none;
36 | }
37 | }
38 | }
39 | &.ql-is-mentioning {
40 | display: table;
41 | }
42 | }
43 |
44 | .ql-mentions {
45 | border: solid 1px #ddd;
46 | font-size: 13px;
47 | padding: 0;
48 |
49 | @extend %ql-mentions-base;
50 | }
51 |
52 | .ql-mention-no-match {
53 | background: #fff;
54 | box-shadow: -1px -1px 1px #bbb,
55 | 1px 1px 1px #bbb,
56 | -1px 1px 1px #bbb,
57 | 1px -1px 1px #bbb;
58 | padding: 8px 12px 8px 8px;
59 | pointer-events: none;
60 |
61 | @include translate-z(1px);
62 |
63 | i {
64 | border-left: solid 2px $warning-color;
65 | padding-left: 6px;
66 | }
67 | }
68 |
69 | [class^="ql-mention-item"] {
70 | // apply custom styles to the way the item looks
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | "use strict";
3 | require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);
4 |
5 | grunt.initConfig({
6 | pkg: grunt.file.readJSON('package.json'),
7 | watch: {
8 | js: {
9 | files: ['src/js/**/*.js'],
10 | tasks: ['buildjs'],
11 | options: {
12 | interrupt: true,
13 | },
14 | },
15 | css: {
16 | files: ['src/scss/**/*.scss'],
17 | tasks: ['sass'],
18 | options: {
19 | interrupt: true,
20 | },
21 | },
22 | },
23 | browserify: {
24 | dist: {
25 | files: {
26 | 'dist/quill-mentions.js': ['src/js/**/*.js'],
27 | },
28 | },
29 | },
30 | uglify: {
31 | build: {
32 | files: {
33 | 'dist/quill-mentions.min.js': ['dist/quill-mentions.js'],
34 | }
35 | }
36 | },
37 | sass: {
38 | dist: {
39 | options: {
40 | style: 'expanded'
41 | },
42 | files: {
43 | 'dist/quill-mentions.css': 'src/scss/style.scss',
44 | }
45 | }
46 | },
47 | jsdoc: {
48 | dist: {
49 | src: ['src/js/**/*.js'],
50 | options: {
51 | destination: 'docs'
52 | }
53 | },
54 | },
55 | });
56 |
57 | grunt.registerTask('default', ['browserify', 'uglify', 'sass', 'jsdoc']);
58 | grunt.registerTask('buildjs', ['browserify', 'uglify']); // don't build sass or docs bc they're sloowwwww
59 | };
--------------------------------------------------------------------------------
/docs/index.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: index.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: index.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | /** @global */
29 | global.QuillMentions = require("./mentions");
30 | if (window.Quill) {
31 | Quill.registerModule('mentions', QuillMentions);
32 | }
33 | else {
34 | throw new Error("Quill is not defined in the global scope.");
35 | }
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Modules Classes Namespaces
47 |
48 |
49 |
50 |
51 |
52 | Documentation generated by JSDoc 3.2.2 on Sat May 23 2015 04:18:16 GMT-0700 (PDT)
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/docs/styles/prettify-jsdoc.css:
--------------------------------------------------------------------------------
1 | /* JSDoc prettify.js theme */
2 |
3 | /* plain text */
4 | .pln {
5 | color: #000000;
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | /* string content */
11 | .str {
12 | color: #006400;
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
17 | /* a keyword */
18 | .kwd {
19 | color: #000000;
20 | font-weight: bold;
21 | font-style: normal;
22 | }
23 |
24 | /* a comment */
25 | .com {
26 | font-weight: normal;
27 | font-style: italic;
28 | }
29 |
30 | /* a type name */
31 | .typ {
32 | color: #000000;
33 | font-weight: normal;
34 | font-style: normal;
35 | }
36 |
37 | /* a literal value */
38 | .lit {
39 | color: #006400;
40 | font-weight: normal;
41 | font-style: normal;
42 | }
43 |
44 | /* punctuation */
45 | .pun {
46 | color: #000000;
47 | font-weight: bold;
48 | font-style: normal;
49 | }
50 |
51 | /* lisp open bracket */
52 | .opn {
53 | color: #000000;
54 | font-weight: bold;
55 | font-style: normal;
56 | }
57 |
58 | /* lisp close bracket */
59 | .clo {
60 | color: #000000;
61 | font-weight: bold;
62 | font-style: normal;
63 | }
64 |
65 | /* a markup tag name */
66 | .tag {
67 | color: #006400;
68 | font-weight: normal;
69 | font-style: normal;
70 | }
71 |
72 | /* a markup attribute name */
73 | .atn {
74 | color: #006400;
75 | font-weight: normal;
76 | font-style: normal;
77 | }
78 |
79 | /* a markup attribute value */
80 | .atv {
81 | color: #006400;
82 | font-weight: normal;
83 | font-style: normal;
84 | }
85 |
86 | /* a declaration */
87 | .dec {
88 | color: #000000;
89 | font-weight: bold;
90 | font-style: normal;
91 | }
92 |
93 | /* a variable name */
94 | .var {
95 | color: #000000;
96 | font-weight: normal;
97 | font-style: normal;
98 | }
99 |
100 | /* a function name */
101 | .fun {
102 | color: #000000;
103 | font-weight: bold;
104 | font-style: normal;
105 | }
106 |
107 | /* Specify class=linenums on a pre to get line numbering */
108 | ol.linenums {
109 | margin-top: 0;
110 | margin-bottom: 0;
111 | }
112 |
--------------------------------------------------------------------------------
/docs/format.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: format.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: format.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | module.exports = addFormat;
29 |
30 | function addFormat(QuillMentions) {
31 | /**
32 | * @method
33 | */
34 | QuillMentions.prototype.addFormat = function(className) {
35 | this.quill.addFormat('mention', { tag: 'SPAN', "class": "ql-", });
36 | };
37 | }
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Modules Classes Namespaces Global
48 |
49 |
50 |
51 |
52 |
53 | Documentation generated by JSDoc 3.2.2 on Wed May 27 2015 01:38:34 GMT-0700 (PDT)
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Index
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Index
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Modules Classes Namespaces Global
52 |
53 |
54 |
55 |
56 |
57 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/styles/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Menlo, Monaco, Consolas, monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/docs/module-search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: search
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: search
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | search
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Search module
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Source:
66 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | Modules Classes Namespaces
111 |
112 |
113 |
114 |
115 |
116 | Documentation generated by JSDoc 3.2.2 on Sat May 23 2015 04:19:02 GMT-0700 (PDT)
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/docs/identity.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: utilities/identity.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: utilities/identity.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | /** @module utilities/identity */
29 |
30 | module.exports = identity;
31 |
32 | /** @function identity */
33 | function identity(d) {
34 | return d;
35 | }
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Modules Classes Namespaces Global
46 |
47 |
48 |
49 |
50 |
51 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/js/defaults/defaults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module defaults/defaults
3 | */
4 |
5 | var extend = require("../utilities/extend"),
6 | identity = require("../utilities/identity");
7 |
8 | /**
9 | * @namespace
10 | * @prop {object} ajax - The default ajax configuration.
11 | * @prop {number} choiceMax - The maximum number of possible matches to display.
12 | * @prop {object[]} choices - A static array of possible choices. Ignored if `ajax` is truthy.
13 | * @prop {string} choiceTemplate - A string used as a template for possible choices.
14 | * @prop {string} containerClassName - The class attached to the mentions view container.
15 | * @prop {function} format - Function used by a Controller instance to munge data into expected form.
16 | * @prop {boolean} hotkeys - If false, disables navigating the popover with the keyboard.
17 | * @prop {boolean} includeTrigger - Whether to prepend triggerSymbol to the inserted mention.
18 | * @prop {number} marginTop - Amount of margin to place on top of the popover. (Controls space, in px, between the line and the popover)
19 | * @prop {RegExp} matcher - The regular expression used to trigger Controller#search
20 | * @prop {string} mentionClass - Prefixed with `ql-` for now because of how quill handles custom formats. The class given to inserted mention.
21 | * @prop {string} noMatchMessage - A message to display
22 | * @prop {string} noMatchTemplate - A template in which to display error message
23 | * @prop {string} template - A template for the popover, into which possible choices are inserted.
24 | * @prop {string} triggerSymbol - Symbol that triggers the mentioning state.
25 | */
26 | var defaults = {
27 | ajax: false,
28 | choiceMax: 6,
29 | choices: [],
30 | choiceTemplate: "{{value}} ",
31 | containerClassName: "ql-mentions",
32 | format: identity,
33 | hotkeys: true,
34 | includeTrigger: false,
35 | marginTop: 10,
36 | matcher: /@\w+$/i,
37 | mentionClass: "mention-item",
38 | noMatchMessage: "Ruh Roh Raggy!",
39 | noMatchTemplate: "{{message}}
",
40 | template: '',
41 | triggerSymbol: "@",
42 | };
43 |
44 | /**
45 | * @namespace
46 | * @prop {function} format - Mapped onto the array of possible matches returned by call to `path`. Should yield the expected interface for data, which is an object with `name` and `data` properties.
47 | * @prop {string} path - The path to endpoint we should query for possible matches.
48 | * @prop {string} queryParameter - The name of the query paramater in the url sent to `path`.
49 | */
50 | var ajaxDefaults = {
51 | format: identity,
52 | path: null,
53 | queryParameter: "q",
54 | };
55 |
56 | /**
57 | * Returns a configuration object for QuillMentions constructor.
58 | * @name defaultFactory
59 | */
60 | function defaultFactory(options) {
61 | var result = extend({}, defaults, options);
62 | if (options.ajax) {
63 | result.ajax = extend({}, ajaxDefaults, options.ajax);
64 | }
65 | return result;
66 | }
67 | module.exports = defaultFactory;
--------------------------------------------------------------------------------
/docs/string-replace.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: utilities/string-replace.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: utilities/string-replace.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | var escapeRegExp = require("./regexp").escapeRegExp;
29 |
30 | module.exports = {
31 | all: replaceAll,
32 | };
33 |
34 | /**
35 | * @param {stirng} [options] - RegExp options (like "i").
36 | **/
37 | function replaceAll(string, toReplace, replaceWith, options) {
38 | options = options || "";
39 | var reOpts = "g" + options,
40 | re = new RegExp(escapeRegExp(toReplace), reOpts);
41 |
42 | return string.replace(re, replaceWith);
43 | }
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Modules Classes Namespaces Global
54 |
55 |
56 |
57 |
58 |
59 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/js/keyboard.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | var DOM = require("./utilities/dom"),
4 | addClass = DOM.addClass,
5 | removeClass = DOM.removeClass;
6 |
7 | var SELECTED_CLASS = "ql-mention-choice-selected";
8 |
9 | /**
10 | * Dispatches keyboard events to handlers
11 | * @namespace
12 | * @prop {function}
13 | */
14 | /**
15 | * @namespace
16 | * @prop {Number} 13 - Handler for the enter key.
17 | * @prop {Number} 27 - Handler for the escape key.
18 | * @prop {Number} 38 - Handler for the up arrow key.
19 | * @prop {Number} 40 - Handler for the down arrow key.
20 | */
21 | var keydown = {
22 | 27: keydownEscape,
23 | 38: keydownUpKey,
24 | 40: keydownDownKey,
25 | };
26 |
27 | var keyup = {
28 | 13: keyupEnter,
29 | };
30 |
31 | /**
32 | * @method
33 | * @this {QuillMentions}
34 | */
35 | function keydownDownKey() {
36 | if (this.view.isHidden()) return;
37 | _moveSelection.call(this, 1);
38 | }
39 |
40 | /**
41 | * @method
42 | * @this {QuillMentions}
43 | */
44 | function keydownUpKey() {
45 | if (this.view.isHidden()) return;
46 | _moveSelection.call(this, -1);
47 | }
48 |
49 | /**
50 | * @method
51 | * @this {QuillMentions}
52 | */
53 | function keyupEnter() {
54 | var nodes,
55 | currIndex = this.selectedChoiceIndex,
56 | currNode;
57 |
58 | if (currIndex === -1) return;
59 | if (!this.view.hasMatches()) return;
60 |
61 | this.quill.setSelection(this._cachedRange);
62 | nodes = this.view.getMatches();
63 | currNode = nodes[currIndex];
64 | this.addMention(currNode);
65 | this.selectedChoiceIndex = -1;
66 | }
67 |
68 | /**
69 | * @method
70 | * @this {QuillMentions}
71 | */
72 | function keydownEscape() {
73 | this.view.hide();
74 | this.selectedChoiceIndex = -1;
75 | this.quill.focus();
76 | }
77 |
78 | /**
79 | * Moves the selected list item up or down. (+steps means down, -steps means up) PUT THIS IN THE VIEW
80 | * @method
81 | * @private
82 | * @this {QuillMentions}
83 | */
84 | function _moveSelection(steps) {
85 | var nodes,
86 | currIndex = this.selectedChoiceIndex,
87 | currNode,
88 | nextIndex,
89 | nextNode;
90 |
91 | nodes = this.view.container.querySelectorAll("li");
92 |
93 | if (nodes.length === 0) {
94 | this.selectedChoiceIndex = -1;
95 | return;
96 | }
97 | if (currIndex !== -1) {
98 | currNode = nodes[currIndex];
99 | removeClass(currNode, SELECTED_CLASS);
100 | }
101 |
102 | nextIndex = _normalizeIndex(currIndex + steps, nodes.length);
103 | nextNode = nodes[nextIndex];
104 |
105 | if (nextNode) {
106 | addClass(nextNode, SELECTED_CLASS);
107 | this.selectedChoiceIndex = nextIndex;
108 | }
109 | else {
110 | console.log("Indexing error on node returned by querySelectorAll");
111 | }
112 |
113 | }
114 |
115 | function _normalizeIndex(i, modulo) {
116 | if (modulo <= 0) throw new Error("TF are you doing? _normalizeIndex needs a nonnegative, nonzero modulo.");
117 | while (i < 0) {
118 | i += modulo;
119 | }
120 | return i % modulo;
121 | }
122 |
123 | module.exports = {keyup: keyup, keydown: keydown};
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :construction: quill-mentions
2 | _Under construction_
3 |
4 | An at-style mentions module for quilljs.
5 |
6 | ## Get it.
7 | You can get `quill-mentions` through npm
8 | ```bash
9 | $ npm install quill-modules
10 | ```
11 | Or you can just take the files from the `dist` folder. That works too.
12 |
13 | ## Use it.
14 | `quill-mentions` exposes a single global variable, `QuillMentions`.
15 |
16 | To include mentions in your Quill project, simply add the stylesheet and all the Javascripts to your page.
17 |
18 | Pass the global `QuillMentions` contsructor to Quill's [registerModule](http://quilljs.com/docs/api/#quillregistermodule) function, and add `mentions` to your module config when you instantiate your editor(s).
19 |
20 | ```html
21 |
22 | ...
23 |
24 | ...
25 |
26 |
27 | ...
28 |
29 |
30 |
38 |
39 |
40 | ```
41 |
42 | ## Docs
43 |
44 | The docs are not as exhaustive as they should be, but they live (nonetheless) in the `docs` folder.
45 |
46 | :warning: If you build the docs, be warned that your build will fail if the _full path_ to your clone/fork of the repo includes any folders with an underscore. See the issue and fix [here](https://github.com/brettimus/quill-mentions/issues/1).
47 |
48 |
49 | ## Style Dependencies...
50 | **Not Yet Written**
51 |
52 |
53 | # v-3 goals
54 | * ~~inject choices (as array)~~
55 | * ~~parse contents~~
56 | * ~~use `@` to summon popover with possible choices matched to text~~
57 | * ~~vertically align popover to position of calling `@`~~
58 |
59 | # v-2 goals
60 | * ~~Customizable no-match-found messages~~ TODO - figre out how to configure so there's _no message_. Was running into issues trying this out.
61 | * ~~Keyboard events for up and down arrows~~ Also for escape and enter!
62 | * ~~Insert data with mention into markup (this might require deviating from custom quill format because custom formats are too nascent :confused:)~~ Currently, the `data-mention` attribute from a matching `li` is appended to the class of a mention `span`. This is hacky, but it avoids having to manually insert HTML...
63 | * Horizontally align the popover
64 |
65 | # V-1 goals
66 | * ~~Refactor with MVC~~
67 | * Break out defaultFactory (the main default object has toooo much :shit: on it.)
68 | * **Horizontally align the popover**
69 | * Refactor styles to not rely on dom elements
70 |
71 | # V-0 goals
72 | * More flexible templates, allow custom `value` accessor functions
73 | * Allow config to turn of quill custom format
74 | * Customizable hotkeys
75 |
76 | # TODO
77 | * Determine horizontal rendering of mentions container.
78 | * Find alternative to current use of `transform: translateZ` on the popover list items...
79 | * ~~Write more robust regex for parsing names (separate for work)~~
80 | * ~~Hide view after insert~~
81 | * ~~Add keyboard events for up and down arrows~~
82 | * ~~Don't allow 'womp womp' message to be clicked~~
83 | * ~~Render ql-mentions container in a more logical position~~
84 |
--------------------------------------------------------------------------------
/docs/ajax.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: utilities/ajax.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: utilities/ajax.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | /** @module utilities/ajax */
29 | module.exports = {
30 |
31 | // from stackoverflow
32 | // https://stackoverflow.com/questions/9838812/how-can-i-open-a-json-file-in-javascript-without-jquery
33 | /**
34 | * @function loadJSON
35 | */
36 | loadJSON: function loadJSON(path, success, error) {
37 | var xhr = new XMLHttpRequest();
38 | xhr.onreadystatechange = function()
39 | {
40 | if (xhr.readyState === XMLHttpRequest.DONE) {
41 | if (xhr.status === 200) {
42 | if (success)
43 | success(JSON.parse(xhr.responseText));
44 | } else {
45 | if (error)
46 | error(xhr);
47 | }
48 | }
49 | };
50 | xhr.open("GET", path, true);
51 | xhr.send();
52 | return xhr;
53 | },
54 | };
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | Modules Classes Namespaces Global
65 |
66 |
67 |
68 |
69 |
70 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/docs/ajax.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: utilities/ajax
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: utilities/ajax
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | utilities/ajax
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Source:
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Modules Classes Namespaces Global
109 |
110 |
111 |
112 |
113 |
114 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/docs/identity.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: utilities/identity
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: utilities/identity
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | utilities/identity
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Source:
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Modules Classes Namespaces Global
109 |
110 |
111 |
112 |
113 |
114 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/docs/extend.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: utilities/extend
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: utilities/extend
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | utilities/extend
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Extend module
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Source:
66 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | Modules Classes Namespaces Global
111 |
112 |
113 |
114 |
115 |
116 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/docs/module-mentions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: mentions
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: mentions
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | mentions
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Source:
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Classes
89 |
90 |
91 | QuillMentions
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | Modules Classes Namespaces Global
116 |
117 |
118 |
119 |
120 |
121 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/docs/extend.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: utilities/extend.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: utilities/extend.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | /**
29 | * Extend module
30 | * @module utilities/extend
31 | */
32 | module.exports = extend;
33 |
34 | /**
35 | * Shallow-copies an arbitrary number of objects' properties into the first argument. Applies "last-in-wins" policy to conflicting property names.
36 | * @function extend
37 | * @param {...Object} o
38 | */
39 | function extend(o) {
40 | var args = [].slice.call(arguments, 0),
41 | result = args[0];
42 |
43 | for (var i=1; i < args.length; i++) {
44 | result = extendHelper(result, args[i]);
45 | }
46 |
47 | return result;
48 | }
49 |
50 | /**
51 | * Shallow-copies one object into another.
52 | * @function extendHelper
53 | * @param {Object} destination - Object into which `source` properties will be copied.
54 | * @param {Object} source - Object whose properties will be copied into `destination`.
55 | */
56 | function extendHelper(destination, source) {
57 | // thanks be to angus kroll
58 | // https://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/
59 | for (var k in source) {
60 | if (source.hasOwnProperty(k)) {
61 | destination[k] = source[k];
62 | }
63 | }
64 | return destination;
65 | }
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Modules Classes Namespaces Global
76 |
77 |
78 |
79 |
80 |
81 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/defaults.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: defaults/defaults
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: defaults/defaults
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | defaults/defaults
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Source:
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Namespaces
91 |
92 |
93 | ajaxDefaults
94 |
95 |
96 | defaults
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | Modules Classes Namespaces Global
119 |
120 |
121 |
122 |
123 |
124 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/js/controller.js:
--------------------------------------------------------------------------------
1 | // TODO - factor out data munging into separate object
2 |
3 | /** @module controller */
4 |
5 | var loadJSON = require("./utilities/ajax").loadJSON,
6 | escapeRegExp = require("./utilities/regexp").escapeRegExp;
7 |
8 | module.exports = {
9 | Controller: Controller,
10 | AJAXController: AJAXController,
11 | };
12 |
13 | /**
14 | * @callback searchCallback
15 | * @param {Object[]} data - An array of objects that represent possible matches to data. The data are mapped over a formatter to provide a consistent interface.
16 | */
17 |
18 |
19 | function Controller(formatter, view, options) {
20 | this.format = formatter;
21 | this.view = view;
22 | this.database = this.munge(options.data);
23 | this.max = options.max;
24 | }
25 |
26 |
27 | /**
28 | * @interface
29 | * @param {function} formatter - Munges data
30 | * @param {View} view
31 | * @param {object} options
32 | * @prop {function} format - Munges data
33 | * @prop {View} view
34 | * @prop {number} max - Maximum number of matches to pass to the View.
35 | */
36 | function AbstractController(formatter, view, options) {
37 | this.format = formatter;
38 | this.view = view;
39 | this.max = options.max;
40 | }
41 |
42 | /**
43 | * @abstract
44 | */
45 | AbstractController.prototype.search = function search() {
46 | throw new Error("NYI");
47 | };
48 |
49 | /**
50 | * Transforms data to conform to config.
51 | * @method
52 | * @param {string} qry
53 | * @param {searchCallback} callback
54 | */
55 | AbstractController.prototype.munge = function(data) {
56 | return data.map(this.format);
57 | };
58 |
59 |
60 | /**
61 | * @constructor
62 | * @prop {object[]} database - All possible choices for a given mention.
63 | */
64 | function Controller(formatter, view, options) {
65 | AbstractController.call(this, formatter, view, options);
66 | this.database = this.munge(options.data);
67 | }
68 | Controller.prototype = Object.create(AbstractController.prototype);
69 |
70 | Controller.prototype.search = function search(qry, callback) {
71 | var qryRE = new RegExp(escapeRegExp(qry), "i"),
72 | data;
73 |
74 | data = this.database.filter(function(d) {
75 | return qryRE.test(d.value);
76 | }).sort(function(d1, d2) {
77 | return d1.value.indexOf(qry) - d2.value.indexOf(qry);
78 | });
79 |
80 | this.view.render(data.slice(0, this.max));
81 | if (callback) callback();
82 | };
83 |
84 |
85 | /**
86 | * @constructor
87 | * @augments Controller
88 | * @prop {string} path - The path from which to request data.
89 | * @prop {string} queryParameter - The name of the paramter in the request to Controller~path
90 | * @prop {Object} _latestCall - Cached ajax call. Aborted if a new search is made.
91 | */
92 | function AJAXController(formatter, view, options) {
93 | AbstractController.call(this, formatter, view, options);
94 | this.path = options.path;
95 | this.queryParameter = options.queryParameter;
96 | this._latestCall = null;
97 | }
98 | AJAXController.prototype = Object.create(AbstractController.prototype);
99 |
100 | /**
101 | * @method
102 | * @param {String} qry
103 | * @param {searchCallback} callback
104 | */
105 | AJAXController.prototype.search = function search(qry, callback) {
106 |
107 | if (this._latestCall) this._latestCall.abort(); // caches ajax calls so we can cancel them as the input is updated
108 | var qryString = this.path +
109 | "?" + this.queryParameter +
110 | "=" + encodeURIComponent(qry);
111 |
112 | this._latestCall = loadJSON(qryString, success.bind(this), ajaxError);
113 |
114 | function success(data) {
115 | this._callback(data);
116 | if (callback) callback();
117 | }
118 | };
119 |
120 | /**
121 | * Munges the callback data
122 | * @method
123 | * @private
124 | * @param {array} data
125 | */
126 | AJAXController.prototype._callback = function(data) {
127 | data = this.munge(data).slice(0, this.max);
128 | this.view.render(data);
129 | };
130 |
131 | function ajaxError(error) {
132 | console.log("Loading json errored! Likely due to aborted request, but there's the error: ", error);
133 | }
--------------------------------------------------------------------------------
/lp/quill.base.css:
--------------------------------------------------------------------------------
1 | /*! Quill Editor v0.19.12
2 | * https://quilljs.com/
3 | * Copyright (c) 2014, Jason Chen
4 | * Copyright (c) 2013, salesforce.com
5 | */
6 | .ql-image-tooltip {
7 | padding: 10px;
8 | width: 300px;
9 | }
10 | .ql-image-tooltip:after {
11 | clear: both;
12 | content: "";
13 | display: table;
14 | }
15 | .ql-image-tooltip a {
16 | border: 1px solid #000;
17 | box-sizing: border-box;
18 | display: inline-block;
19 | float: left;
20 | padding: 5px;
21 | text-align: center;
22 | width: 50%;
23 | }
24 | .ql-image-tooltip img {
25 | bottom: 0;
26 | left: 0;
27 | margin: auto;
28 | max-height: 100%;
29 | max-width: 100%;
30 | position: absolute;
31 | right: 0;
32 | top: 0;
33 | }
34 | .ql-image-tooltip .input {
35 | box-sizing: border-box;
36 | width: 100%;
37 | }
38 | .ql-image-tooltip .preview {
39 | margin: 10px 0px;
40 | position: relative;
41 | border: 1px dashed #000;
42 | height: 200px;
43 | }
44 | .ql-image-tooltip .preview span {
45 | display: inline-block;
46 | position: absolute;
47 | text-align: center;
48 | top: 40%;
49 | width: 100%;
50 | }
51 | .ql-link-tooltip {
52 | padding: 5px 10px;
53 | }
54 | .ql-link-tooltip input.input {
55 | width: 170px;
56 | }
57 | .ql-link-tooltip input.input,
58 | .ql-link-tooltip a.done {
59 | display: none;
60 | }
61 | .ql-link-tooltip a.change {
62 | margin-right: 4px;
63 | }
64 | .ql-link-tooltip.editing input.input,
65 | .ql-link-tooltip.editing a.done {
66 | display: inline-block;
67 | }
68 | .ql-link-tooltip.editing a.url,
69 | .ql-link-tooltip.editing a.change,
70 | .ql-link-tooltip.editing a.remove {
71 | display: none;
72 | }
73 | .ql-multi-cursor {
74 | position: absolute;
75 | left: 0;
76 | top: 0;
77 | z-index: 1000;
78 | }
79 | .ql-multi-cursor .cursor {
80 | margin-left: -1px;
81 | position: absolute;
82 | }
83 | .ql-multi-cursor .cursor-flag {
84 | bottom: 100%;
85 | position: absolute;
86 | white-space: nowrap;
87 | }
88 | .ql-multi-cursor .cursor-name {
89 | display: inline-block;
90 | color: #fff;
91 | padding: 2px 8px;
92 | }
93 | .ql-multi-cursor .cursor-caret {
94 | height: 100%;
95 | position: absolute;
96 | width: 2px;
97 | }
98 | .ql-multi-cursor .cursor.hidden .cursor-flag {
99 | display: none;
100 | }
101 | .ql-multi-cursor .cursor.top .cursor-flag {
102 | bottom: auto;
103 | top: 100%;
104 | }
105 | .ql-multi-cursor .cursor.right .cursor-flag {
106 | right: -2px;
107 | }
108 | .ql-paste-manager {
109 | left: -100000px;
110 | position: absolute;
111 | top: 50%;
112 | }
113 | .ql-toolbar {
114 | box-sizing: border-box;
115 | }
116 | .ql-tooltip {
117 | background-color: #fff;
118 | border: 1px solid #000;
119 | box-sizing: border-box;
120 | position: absolute;
121 | top: 0px;
122 | white-space: nowrap;
123 | z-index: 2000;
124 | }
125 | .ql-tooltip a {
126 | cursor: pointer;
127 | text-decoration: none;
128 | }
129 | .ql-container {
130 | box-sizing: border-box;
131 | cursor: text;
132 | font-family: Helvetica, 'Arial', sans-serif;
133 | font-size: 13px;
134 | height: 100%;
135 | line-height: 1.42;
136 | margin: 0px;
137 | overflow-x: hidden;
138 | overflow-y: auto;
139 | padding: 12px 15px;
140 | position: relative;
141 | }
142 | .ql-editor {
143 | box-sizing: border-box;
144 | min-height: 100%;
145 | outline: none;
146 | tab-size: 4;
147 | white-space: pre-wrap;
148 | }
149 | .ql-editor div {
150 | margin: 0;
151 | padding: 0;
152 | }
153 | .ql-editor a {
154 | text-decoration: underline;
155 | }
156 | .ql-editor b {
157 | font-weight: bold;
158 | }
159 | .ql-editor i {
160 | font-style: italic;
161 | }
162 | .ql-editor s {
163 | text-decoration: line-through;
164 | }
165 | .ql-editor u {
166 | text-decoration: underline;
167 | }
168 | .ql-editor a,
169 | .ql-editor b,
170 | .ql-editor i,
171 | .ql-editor s,
172 | .ql-editor u,
173 | .ql-editor span {
174 | background-color: inherit;
175 | }
176 | .ql-editor img {
177 | max-width: 100%;
178 | }
179 | .ql-editor blockquote,
180 | .ql-editor ol,
181 | .ql-editor ul {
182 | margin: 0 0 0 2em;
183 | padding: 0;
184 | }
185 | .ql-editor ol {
186 | list-style-type: decimal;
187 | }
188 | .ql-editor ul {
189 | list-style-type: disc;
190 | }
191 | .ql-editor.ql-ie-9 br,
192 | .ql-editor.ql-ie-10 br {
193 | display: none;
194 | }
195 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Mentions
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Quill Mentions
22 |
23 | Try this totally badass example with emoji
24 | :metal:
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
hello there! 😅
50 |
51 |
enter a colon and at least two letters to search for emoji
52 |
53 |
54 |
55 |
56 |
57 |
58 |
108 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/docs/module-controller-Controller.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Class: Controller
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Class: Controller
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | controller ~
31 |
32 | Controller
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | new Controller()
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Properties:
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Name
71 |
72 |
73 | Type
74 |
75 |
76 |
77 |
78 |
79 | Description
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | database
89 |
90 |
91 |
92 |
93 |
94 | object[]
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | All possible choices for a given mention.
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Source:
131 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 | Modules Classes Namespaces Global
188 |
189 |
190 |
191 |
192 |
193 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
194 |
195 |
196 |
197 |
198 |
199 |
--------------------------------------------------------------------------------
/docs/search.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: search.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: search.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | // TODO - rename to "model"
29 | var loadJSON = require("./utilities/ajax").loadJSON;
30 | /**
31 | * @callback searchCallback
32 | * @param {Object[]} data - An array of objects that represent possible matches to data. The data are mapped over a formatter to provide a consistent interface.
33 | */
34 |
35 | function search(qry, callback) {
36 | var searcher = this.options.ajax ? this.ajaxSearch : this.staticSearch;
37 | searcher.call(this, qry, callback);
38 | }
39 |
40 | module.exports = function addSearch(QuillMentions) {
41 | /**
42 | * Dispatches search for possible matches to a query.
43 | * @method
44 | * @param {string} qry
45 | * @param {searchCallback} callback - Callback that handles the possible matches
46 | */
47 | QuillMentions.prototype.search = search;
48 |
49 | /**
50 | * @method
51 | * @param {string} qry
52 | * @param {searchCallback} callback - Callback that handles possible matches
53 | */
54 | QuillMentions.prototype.staticSearch = function staticSearch(qry, callback) {
55 | console.log("Static Search Query", qry);
56 | var data = this.options.choices.filter(staticFilter(qry).bind(this));
57 | if (!callback) noCallbackError("staticSearch");
58 | callback(data);
59 | };
60 |
61 | /**
62 | * @method
63 | * @param {string} qry
64 | * @param {searchCallback} callback - Callback that handles possible matches
65 | */
66 | QuillMentions.prototype.ajaxSearch = function ajaxSearch(qry, callback) {
67 | // TODO - remember last ajax request, and if it's still pending, cancel it.
68 | // ... to that end, just use promises.
69 |
70 | if (ajaxSearch.latest) ajaxSearch.latest.abort();
71 |
72 | var path = this.options.ajax.path,
73 | formatData = this.options.ajax.format,
74 | queryParameter = this.options.ajax.queryParameter,
75 | qryString = path + "?" + queryParameter + "=" + encodeURIComponent(qry);
76 |
77 | ajaxSearch.latest = loadJSON(qryString, ajaxSuccess(callback, formatData), ajaxError);
78 | };
79 | };
80 |
81 | function staticFilter(qry) {
82 | return function(choice) {
83 | var formatter = this.options.format;
84 | return choice.name.toLowerCase().indexOf(qry.toLowerCase()) !== -1;
85 | };
86 | }
87 |
88 | function ajaxSuccess(callback, formatter) {
89 | return function(data) {
90 | if (callback) callback(data.map(formatter));
91 | else noCallbackError("ajaxSearch");
92 | };
93 | }
94 |
95 | function ajaxError(error) {
96 | console.log("Loading json errored...", error);
97 | }
98 |
99 | function noCallbackError(functionName) {
100 | console.log("Warning!", functionName, "was not provided a callback. Don't be a ding-dong.");
101 | }
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | Modules Classes Namespaces Global
112 |
113 |
114 |
115 |
116 |
117 | Documentation generated by JSDoc 3.2.2 on Wed May 27 2015 01:38:34 GMT-0700 (PDT)
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/docs/styles/jsdoc-default.css:
--------------------------------------------------------------------------------
1 | html
2 | {
3 | overflow: auto;
4 | background-color: #fff;
5 | }
6 |
7 | body
8 | {
9 | font: 14px "DejaVu Sans Condensed", "Liberation Sans", "Nimbus Sans L", Tahoma, Geneva, "Helvetica Neue", Helvetica, Arial, sans serif;
10 | line-height: 130%;
11 | color: #000;
12 | background-color: #fff;
13 | }
14 |
15 | a {
16 | color: #444;
17 | }
18 |
19 | a:visited {
20 | color: #444;
21 | }
22 |
23 | a:active {
24 | color: #444;
25 | }
26 |
27 | header
28 | {
29 | display: block;
30 | padding: 6px 4px;
31 | }
32 |
33 | .class-description {
34 | font-style: italic;
35 | font-family: Palatino, 'Palatino Linotype', serif;
36 | font-size: 130%;
37 | line-height: 140%;
38 | margin-bottom: 1em;
39 | margin-top: 1em;
40 | }
41 |
42 | #main {
43 | float: left;
44 | width: 100%;
45 | }
46 |
47 | section
48 | {
49 | display: block;
50 |
51 | background-color: #fff;
52 | padding: 12px 24px;
53 | border-bottom: 1px solid #ccc;
54 | margin-right: 240px;
55 | }
56 |
57 | .variation {
58 | display: none;
59 | }
60 |
61 | .optional:after {
62 | content: "opt";
63 | font-size: 60%;
64 | color: #aaa;
65 | font-style: italic;
66 | font-weight: lighter;
67 | }
68 |
69 | nav
70 | {
71 | display: block;
72 | float: left;
73 | margin-left: -230px;
74 | margin-top: 28px;
75 | width: 220px;
76 | border-left: 1px solid #ccc;
77 | padding-left: 9px;
78 | }
79 |
80 | nav ul {
81 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
82 | font-size: 100%;
83 | line-height: 17px;
84 | padding:0;
85 | margin:0;
86 | list-style-type:none;
87 | }
88 |
89 | nav h2 a, nav h2 a:visited {
90 | color: #A35A00;
91 | text-decoration: none;
92 | }
93 |
94 | nav h3 {
95 | margin-top: 12px;
96 | }
97 |
98 | nav li {
99 | margin-top: 6px;
100 | }
101 |
102 | nav a {
103 | color: #5C5954;
104 | }
105 |
106 | nav a:visited {
107 | color: #5C5954;
108 | }
109 |
110 | nav a:active {
111 | color: #5C5954;
112 | }
113 |
114 | footer {
115 | display: block;
116 | padding: 6px;
117 | margin-top: 12px;
118 | font-style: italic;
119 | font-size: 90%;
120 | }
121 |
122 | h1
123 | {
124 | font-size: 200%;
125 | font-weight: bold;
126 | letter-spacing: -0.01em;
127 | margin: 6px 0 9px 0;
128 | }
129 |
130 | h2
131 | {
132 | font-size: 170%;
133 | font-weight: bold;
134 | letter-spacing: -0.01em;
135 | margin: 6px 0 3px 0;
136 | }
137 |
138 | h3
139 | {
140 | font-size: 150%;
141 | font-weight: bold;
142 | letter-spacing: -0.01em;
143 | margin-top: 16px;
144 | margin: 6px 0 3px 0;
145 | }
146 |
147 | h4
148 | {
149 | font-size: 130%;
150 | font-weight: bold;
151 | letter-spacing: -0.01em;
152 | margin-top: 16px;
153 | margin: 18px 0 3px 0;
154 | color: #A35A00;
155 | }
156 |
157 | h5, .container-overview .subsection-title
158 | {
159 | font-size: 120%;
160 | font-weight: bold;
161 | letter-spacing: -0.01em;
162 | margin: 8px 0 3px -16px;
163 | }
164 |
165 | h6
166 | {
167 | font-size: 100%;
168 | letter-spacing: -0.01em;
169 | margin: 6px 0 3px 0;
170 | font-style: italic;
171 | }
172 |
173 | .ancestors { color: #999; }
174 | .ancestors a
175 | {
176 | color: #999 !important;
177 | text-decoration: none;
178 | }
179 |
180 | .important
181 | {
182 | font-weight: bold;
183 | color: #950B02;
184 | }
185 |
186 | .yes-def {
187 | text-indent: -1000px;
188 | }
189 |
190 | .type-signature {
191 | color: #aaa;
192 | }
193 |
194 | .name, .signature {
195 | font-family: Consolas, "Lucida Console", Monaco, monospace;
196 | }
197 |
198 | .details { margin-top: 14px; border-left: 2px solid #DDD; }
199 | .details dt { width:100px; float:left; padding-left: 10px; padding-top: 6px; }
200 | .details dd { margin-left: 50px; }
201 | .details ul { margin: 0; }
202 | .details ul { list-style-type: none; }
203 | .details li { margin-left: 30px; padding-top: 6px; }
204 | .details pre.prettyprint { margin: 0 }
205 | .details .object-value { padding-top: 0; }
206 |
207 | .description {
208 | margin-bottom: 1em;
209 | margin-left: -16px;
210 | margin-top: 1em;
211 | }
212 |
213 | .code-caption
214 | {
215 | font-style: italic;
216 | font-family: Palatino, 'Palatino Linotype', serif;
217 | font-size: 107%;
218 | margin: 0;
219 | }
220 |
221 | .prettyprint
222 | {
223 | border: 1px solid #ddd;
224 | width: 80%;
225 | overflow: auto;
226 | }
227 |
228 | .prettyprint.source {
229 | width: inherit;
230 | }
231 |
232 | .prettyprint code
233 | {
234 | font-family: Consolas, 'Lucida Console', Monaco, monospace;
235 | font-size: 100%;
236 | line-height: 18px;
237 | display: block;
238 | padding: 4px 12px;
239 | margin: 0;
240 | background-color: #fff;
241 | color: #000;
242 | border-left: 3px #ddd solid;
243 | }
244 |
245 | .prettyprint code span.line
246 | {
247 | display: inline-block;
248 | }
249 |
250 | .params, .props
251 | {
252 | border-spacing: 0;
253 | border: 0;
254 | border-collapse: collapse;
255 | }
256 |
257 | .params .name, .props .name, .name code {
258 | color: #A35A00;
259 | font-family: Consolas, 'Lucida Console', Monaco, monospace;
260 | font-size: 100%;
261 | }
262 |
263 | .params td, .params th, .props td, .props th
264 | {
265 | border: 1px solid #ddd;
266 | margin: 0px;
267 | text-align: left;
268 | vertical-align: top;
269 | padding: 4px 6px;
270 | display: table-cell;
271 | }
272 |
273 | .params thead tr, .props thead tr
274 | {
275 | background-color: #ddd;
276 | font-weight: bold;
277 | }
278 |
279 | .params .params thead tr, .props .props thead tr
280 | {
281 | background-color: #fff;
282 | font-weight: bold;
283 | }
284 |
285 | .params th, .props th { border-right: 1px solid #aaa; }
286 | .params thead .last, .props thead .last { border-right: 1px solid #ddd; }
287 |
288 | .disabled {
289 | color: #454545;
290 | }
291 |
--------------------------------------------------------------------------------
/docs/defaults.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: defaults/defaults.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: defaults/defaults.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | /**
29 | * @module defaults/defaults
30 | */
31 |
32 | var extend = require("../utilities/extend"),
33 | identity = require("../utilities/identity");
34 |
35 | /**
36 | * @namespace
37 | * @prop {object} ajax - The default ajax configuration.
38 | * @prop {number} choiceMax - The maximum number of possible matches to display.
39 | * @prop {object[]} choices - A static array of possible choices. Ignored if `ajax` is truthy.
40 | * @prop {string} choiceTemplate - A string used as a template for possible choices.
41 | * @prop {string} containerClassName - The class attached to the mentions view container.
42 | * @prop {function} format - Function used by a Controller instance to munge data into expected form.
43 | * @prop {boolean} hotkeys - If false, disables navigating the popover with the keyboard.
44 | * @prop {boolean} includeTrigger - Whether to prepend triggerSymbol to the inserted mention.
45 | * @prop {number} marginTop - Amount of margin to place on top of the popover. (Controls space, in px, between the line and the popover)
46 | * @prop {RegExp} matcher - The regular expression used to trigger Controller#search
47 | * @prop {string} mentionClass - Prefixed with `ql-` for now because of how quill handles custom formats. The class given to inserted mention.
48 | * @prop {string} noMatchMessage - A message to display
49 | * @prop {string} noMatchTemplate - A template in which to display error message
50 | * @prop {string} template - A template for the popover, into which possible choices are inserted.
51 | * @prop {string} triggerSymbol - Symbol that triggers the mentioning state.
52 | */
53 | var defaults = {
54 | ajax: false,
55 | choiceMax: 6,
56 | choices: [],
57 | choiceTemplate: "<li data-display=\"{{value}}\" data-mention=\"{{data}}\">{{value}}</li>",
58 | containerClassName: "ql-mentions",
59 | format: identity,
60 | hotkeys: true,
61 | includeTrigger: false,
62 | marginTop: 10,
63 | matcher: /@\w+$/i,
64 | mentionClass: "mention-item",
65 | noMatchMessage: "Ruh Roh Raggy!",
66 | noMatchTemplate: "<div class='ql-mention-no-match'><i>{{message}}</i></div>",
67 | template: '<ul>{{choices}}</ul>',
68 | triggerSymbol: "@",
69 | };
70 |
71 | /**
72 | * @namespace
73 | * @prop {function} format - Mapped onto the array of possible matches returned by call to `path`. Should yield the expected interface for data, which is an object with `name` and `data` properties.
74 | * @prop {string} path - The path to endpoint we should query for possible matches.
75 | * @prop {string} queryParameter - The name of the query paramater in the url sent to `path`.
76 | */
77 | var ajaxDefaults = {
78 | format: identity,
79 | path: null,
80 | queryParameter: "q",
81 | };
82 |
83 | /**
84 | * Returns a configuration object for QuillMentions constructor.
85 | * @name defaultFactory
86 | */
87 | function defaultFactory(options) {
88 | var result = extend({}, defaults, options);
89 | if (options.ajax) {
90 | result.ajax = extend({}, ajaxDefaults, options.ajax);
91 | }
92 | return result;
93 | }
94 | module.exports = defaultFactory;
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Modules Classes Namespaces Global
105 |
106 |
107 |
108 |
109 |
110 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/docs/keyboard.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: keyboard.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: keyboard.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | var DOM = require("./utilities/dom"),
31 | addClass = DOM.addClass,
32 | removeClass = DOM.removeClass;
33 |
34 | var SELECTED_CLASS = "ql-mention-choice-selected";
35 |
36 | /**
37 | * Dispatches keyboard events to handlers
38 | * @namespace
39 | * @prop {function}
40 | */
41 | /**
42 | * @namespace
43 | * @prop {Number} 13 - Handler for the enter key.
44 | * @prop {Number} 27 - Handler for the escape key.
45 | * @prop {Number} 38 - Handler for the up arrow key.
46 | * @prop {Number} 40 - Handler for the down arrow key.
47 | */
48 | var keydown = {
49 | 27: keydownEscape,
50 | 38: keydownUpKey,
51 | 40: keydownDownKey,
52 | };
53 |
54 | var keyup = {
55 | 13: keyupEnter,
56 | };
57 |
58 | /**
59 | * @method
60 | * @this {QuillMentions}
61 | */
62 | function keydownDownKey() {
63 | if (this.view.isHidden()) return;
64 | _moveSelection.call(this, 1);
65 | }
66 |
67 | /**
68 | * @method
69 | * @this {QuillMentions}
70 | */
71 | function keydownUpKey() {
72 | if (this.view.isHidden()) return;
73 | _moveSelection.call(this, -1);
74 | }
75 |
76 | /**
77 | * @method
78 | * @this {QuillMentions}
79 | */
80 | function keyupEnter() {
81 | var nodes,
82 | currIndex = this.selectedChoiceIndex,
83 | currNode;
84 |
85 | if (currIndex === -1) return;
86 | if (!this.view.hasMatches()) return;
87 |
88 | this.quill.setSelection(this._cachedRange);
89 | nodes = this.view.getMatches();
90 | currNode = nodes[currIndex];
91 | this.addMention(currNode);
92 | this.selectedChoiceIndex = -1;
93 | }
94 |
95 | /**
96 | * @method
97 | * @this {QuillMentions}
98 | */
99 | function keydownEscape() {
100 | this.view.hide();
101 | this.selectedChoiceIndex = -1;
102 | this.quill.focus();
103 | }
104 |
105 | /**
106 | * Moves the selected list item up or down. (+steps means down, -steps means up) PUT THIS IN THE VIEW
107 | * @method
108 | * @private
109 | * @this {QuillMentions}
110 | */
111 | function _moveSelection(steps) {
112 | var nodes,
113 | currIndex = this.selectedChoiceIndex,
114 | currNode,
115 | nextIndex,
116 | nextNode;
117 |
118 | nodes = this.view.container.querySelectorAll("li");
119 |
120 | if (nodes.length === 0) {
121 | this.selectedChoiceIndex = -1;
122 | return;
123 | }
124 | if (currIndex !== -1) {
125 | currNode = nodes[currIndex];
126 | removeClass(currNode, SELECTED_CLASS);
127 | }
128 |
129 | nextIndex = _normalizeIndex(currIndex + steps, nodes.length);
130 | nextNode = nodes[nextIndex];
131 |
132 | if (nextNode) {
133 | addClass(nextNode, SELECTED_CLASS);
134 | this.selectedChoiceIndex = nextIndex;
135 | }
136 | else {
137 | console.log("Indexing error on node returned by querySelectorAll");
138 | }
139 |
140 | }
141 |
142 | function _normalizeIndex(i, modulo) {
143 | if (modulo <= 0) throw new Error("TF are you doing? _normalizeIndex needs a nonnegative, nonzero modulo.");
144 | while (i < 0) {
145 | i += modulo;
146 | }
147 | return i % modulo;
148 | }
149 |
150 | module.exports = {keyup: keyup, keydown: keydown};
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | Modules Classes Namespaces Global
161 |
162 |
163 |
164 |
165 |
166 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/docs/defaults-ajaxDefaults.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Namespace: ajaxDefaults
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Namespace: ajaxDefaults
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Properties:
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Name
57 |
58 |
59 | Type
60 |
61 |
62 |
63 |
64 |
65 | Description
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | format
75 |
76 |
77 |
78 |
79 |
80 | function
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Mapped onto the array of possible matches returned by call to `path`. Should yield the expected interface for data, which is an object with `name` and `data` properties.
91 |
92 |
93 |
94 |
95 |
96 |
97 | path
98 |
99 |
100 |
101 |
102 |
103 | string
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | The path to endpoint we should query for possible matches.
114 |
115 |
116 |
117 |
118 |
119 |
120 | queryParameter
121 |
122 |
123 |
124 |
125 |
126 | string
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | The name of the query paramater in the url sent to `path`.
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | Source:
163 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | Modules Classes Namespaces Global
208 |
209 |
210 |
211 |
212 |
213 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
214 |
215 |
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/docs/KEYS.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Namespace: KEYS
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Namespace: KEYS
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | KEYS
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Properties:
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Name
55 |
56 |
57 | Type
58 |
59 |
60 |
61 |
62 |
63 | Description
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 13
73 |
74 |
75 |
76 |
77 |
78 | Number
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Handler for the enter key.
89 |
90 |
91 |
92 |
93 |
94 |
95 | 27
96 |
97 |
98 |
99 |
100 |
101 | Number
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | Handler for the escape key.
112 |
113 |
114 |
115 |
116 |
117 |
118 | 38
119 |
120 |
121 |
122 |
123 |
124 | Number
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | Handler for the up arrow key.
135 |
136 |
137 |
138 |
139 |
140 |
141 | 40
142 |
143 |
144 |
145 |
146 |
147 | Number
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | Handler for the down arrow key.
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | Source:
184 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 | Modules Classes Namespaces Global
229 |
230 |
231 |
232 |
233 |
234 | Documentation generated by JSDoc 3.2.2 on Thu May 28 2015 21:03:55 GMT-0700 (PDT)
235 |
236 |
237 |
238 |
239 |
240 |
--------------------------------------------------------------------------------
/docs/keydown.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Namespace: keydown
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Namespace: keydown
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | keydown
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Properties:
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Name
55 |
56 |
57 | Type
58 |
59 |
60 |
61 |
62 |
63 | Description
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 13
73 |
74 |
75 |
76 |
77 |
78 | Number
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Handler for the enter key.
89 |
90 |
91 |
92 |
93 |
94 |
95 | 27
96 |
97 |
98 |
99 |
100 |
101 | Number
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | Handler for the escape key.
112 |
113 |
114 |
115 |
116 |
117 |
118 | 38
119 |
120 |
121 |
122 |
123 |
124 | Number
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | Handler for the up arrow key.
135 |
136 |
137 |
138 |
139 |
140 |
141 | 40
142 |
143 |
144 |
145 |
146 |
147 | Number
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | Handler for the down arrow key.
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | Source:
184 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 | Modules Classes Namespaces Global
229 |
230 |
231 |
232 |
233 |
234 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
235 |
236 |
237 |
238 |
239 |
240 |
--------------------------------------------------------------------------------
/docs/controller.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: controller.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: controller.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | // TODO - factor out data munging into separate object
29 |
30 | /** @module controller */
31 |
32 | var loadJSON = require("./utilities/ajax").loadJSON,
33 | escapeRegExp = require("./utilities/regexp").escapeRegExp;
34 |
35 | module.exports = {
36 | Controller: Controller,
37 | AJAXController: AJAXController,
38 | };
39 |
40 | /**
41 | * @callback searchCallback
42 | * @param {Object[]} data - An array of objects that represent possible matches to data. The data are mapped over a formatter to provide a consistent interface.
43 | */
44 |
45 |
46 | function Controller(formatter, view, options) {
47 | this.format = formatter;
48 | this.view = view;
49 | this.database = this.munge(options.data);
50 | this.max = options.max;
51 | }
52 |
53 |
54 | /**
55 | * @interface
56 | * @param {function} formatter - Munges data
57 | * @param {View} view
58 | * @param {object} options
59 | * @prop {function} format - Munges data
60 | * @prop {View} view
61 | * @prop {number} max - Maximum number of matches to pass to the View.
62 | */
63 | function AbstractController(formatter, view, options) {
64 | this.format = formatter;
65 | this.view = view;
66 | this.max = options.max;
67 | }
68 |
69 | /**
70 | * @abstract
71 | */
72 | AbstractController.prototype.search = function search() {
73 | throw new Error("NYI");
74 | };
75 |
76 | /**
77 | * Transforms data to conform to config.
78 | * @method
79 | * @param {string} qry
80 | * @param {searchCallback} callback
81 | */
82 | AbstractController.prototype.munge = function(data) {
83 | return data.map(this.format);
84 | };
85 |
86 |
87 | /**
88 | * @constructor
89 | * @prop {object[]} database - All possible choices for a given mention.
90 | */
91 | function Controller(formatter, view, options) {
92 | AbstractController.call(this, formatter, view, options);
93 | this.database = this.munge(options.data);
94 | }
95 | Controller.prototype = Object.create(AbstractController.prototype);
96 |
97 | Controller.prototype.search = function search(qry, callback) {
98 | var qryRE = new RegExp(escapeRegExp(qry), "i"),
99 | data;
100 |
101 | data = this.database.filter(function(d) {
102 | return qryRE.test(d.value);
103 | }).sort(function(d1, d2) {
104 | return d1.value.indexOf(qry) - d2.value.indexOf(qry);
105 | });
106 |
107 | this.view.render(data.slice(0, this.max));
108 | if (callback) callback();
109 | };
110 |
111 |
112 | /**
113 | * @constructor
114 | * @augments Controller
115 | * @prop {string} path - The path from which to request data.
116 | * @prop {string} queryParameter - The name of the paramter in the request to Controller~path
117 | * @prop {Object} _latestCall - Cached ajax call. Aborted if a new search is made.
118 | */
119 | function AJAXController(formatter, view, options) {
120 | AbstractController.call(this, formatter, view, options);
121 | this.path = options.path;
122 | this.queryParameter = options.queryParameter;
123 | this._latestCall = null;
124 | }
125 | AJAXController.prototype = Object.create(AbstractController.prototype);
126 |
127 | /**
128 | * @method
129 | * @param {String} qry
130 | * @param {searchCallback} callback
131 | */
132 | AJAXController.prototype.search = function search(qry, callback) {
133 |
134 | if (this._latestCall) this._latestCall.abort(); // caches ajax calls so we can cancel them as the input is updated
135 | var qryString = this.path +
136 | "?" + this.queryParameter +
137 | "=" + encodeURIComponent(qry);
138 |
139 | this._latestCall = loadJSON(qryString, success.bind(this), ajaxError);
140 |
141 | function success(data) {
142 | this._callback(data);
143 | if (callback) callback();
144 | }
145 | };
146 |
147 | /**
148 | * Munges the callback data
149 | * @method
150 | * @private
151 | * @param {array} data
152 | */
153 | AJAXController.prototype._callback = function(data) {
154 | data = this.munge(data).slice(0, this.max);
155 | this.view.render(data);
156 | };
157 |
158 | function ajaxError(error) {
159 | console.log("Loading json errored! Likely due to aborted request, but there's the error: ", error);
160 | }
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | Modules Classes Namespaces Global
171 |
172 |
173 |
174 |
175 |
176 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
177 |
178 |
179 |
180 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/src/js/view.js:
--------------------------------------------------------------------------------
1 | var DOM = require("./utilities/dom"),
2 | extend = require("./utilities/extend"),
3 | replaceAll = require("./utilities/string-replace").all,
4 | escapeRegExp = require("./utilities/regexp").escapeRegExp;
5 |
6 | module.exports = View;
7 |
8 |
9 | /**
10 | * @constructor
11 | * @param {HTMLElement} container
12 | * @param {Object} templates - a set of templates into which we render munged data
13 | * @param {Object} options
14 | */
15 | function View(container, templates, options) {
16 | this.container = container;
17 | this.templates = extend({}, templates);
18 | this.marginTop = options.marginTop;
19 | this.errMessage = options.errMessage;
20 | }
21 |
22 | /**
23 | * Creates view from data and calls View~_renderSuccess. If there are no data, calls View~_renderError.
24 | * @method
25 | * @param {array} data
26 | */
27 | View.prototype.render = function(data) {
28 | var templates = this.templates,
29 | items,
30 | err,
31 | toRender;
32 | if (!data || !data.length) {
33 | err = templates.error.replace("{{message}}", this.errMessage);
34 | this.container.innerHTML = err;
35 | }
36 | else {
37 | items = data.map(this._renderLI, this).join("");
38 | this.container.innerHTML = templates.list.replace("{{choices}}", items);
39 | }
40 | return this;
41 | };
42 |
43 | /**
44 | * Renders listItem template with a datum as the context
45 | * @method
46 | * @private
47 | * @param {object} datum - A piece of data
48 | */
49 | View.prototype._renderLI = function(datum) {
50 | var template = this.templates.listItem;
51 | return this._renderWithContext(template, datum);
52 | };
53 |
54 | /**
55 | * Renders a template given the context of an object
56 | * @method
57 | * @private
58 | * @param {string} template
59 | * @param {object} o - Context for a template string.
60 | */
61 | View.prototype._renderWithContext = function(template, o) {
62 | var prop,
63 | result = template;
64 |
65 | for (prop in o) {
66 | if (o.hasOwnProperty(prop)) {
67 | result = replaceAll(result, "{{"+prop+"}}", o[prop]);
68 | }
69 | }
70 |
71 | return result;
72 | };
73 |
74 | /**
75 | * Makes the popover disappear
76 | * @method
77 | * @param {Quill} quill
78 | * @param {Object} range
79 | */
80 | View.prototype.hide = function hide(quill, range) {
81 | DOM.removeClass(this.container, "ql-is-mentioning");
82 | this.container.style.marginTop = "0";
83 | if (range) quill.setSelection(range);
84 | return this;
85 | };
86 |
87 | /**
88 | * @method
89 | * @returns {HTMLElement[]}
90 | */
91 | View.prototype.getMatches = function getMatches() {
92 | return this.container.querySelectorAll("li");
93 | };
94 |
95 | /**
96 | * @method
97 | * @returns {HTMLElement[]}
98 | */
99 | View.prototype.hasMatches = function hasMatches() {
100 | return this.getMatches().length > 0;
101 | };
102 |
103 | /**
104 | * Returns whether the popover is in view. I had bad feels about this method but it's coming in hand re: keyboard events right now.
105 | * @method
106 | * @returns {boolean}
107 | */
108 | View.prototype.isHidden = function isHidden() {
109 | return !DOM.hasClass(this.container, "ql-is-mentioning");
110 | };
111 |
112 | /**
113 | * Adds an active class to the mentions popover and sits it beneath the cursor.
114 | * [TODO - add active class to config]
115 | * @method
116 | * @param {Quill} quill
117 | */
118 | View.prototype.show = function show(quill) {
119 |
120 | this.container.style.marginTop = this._getTopMargin(quill);
121 | this.container.style.marginLeft = this._getLeftMargin(quill);
122 | DOM.addClass(this.container, "ql-is-mentioning"); // TODO - config active class
123 | this.container.focus(); // Does this even do anything? It would if we were using form elements instead of LIs prob
124 |
125 | return this;
126 | };
127 |
128 | /**
129 | * Return an array of dom nodes corresponding to all lines at or before the line corresponding to the current range.
130 | * @method
131 | * @private
132 | * @param {Range} range
133 | * @return {Node[]}
134 | */
135 | View.prototype._findOffsetLines = function(quill) {
136 | var node = this._findMentionContainerNode(quill);
137 | return DOM.getOlderSiblingsInclusive(node);
138 | };
139 |
140 | /**
141 | * Return the DOM node that encloses the line on which current mention is being typed.
142 | * @method
143 | * @private
144 | * @param {Range} range
145 | * @return {Node|null}
146 | */
147 | View.prototype._findMentionContainerNode = function _findMentionContainerNode(quill) {
148 | var range = quill.getSelection(),
149 | leafAndOffset,
150 | leaf,
151 | offset,
152 | node;
153 |
154 |
155 | leafAndOffset = quill.editor.doc.findLeafAt(range.start, true);
156 | leaf = leafAndOffset[0];
157 | offset = leafAndOffset[1]; // how many chars in front of current range
158 | if (leaf) node = leaf.node;
159 | while (node) {
160 | if (node.tagName === "DIV") break;
161 | node = node.parentNode;
162 | }
163 | if (!node) return null;
164 | return node;
165 | };
166 |
167 | /**
168 | * Return the (hopefully inline-positioned) DOM node that encloses the mention itself.
169 | * @method
170 | * @private
171 | * @param {Range} range
172 | * @return {Node|null}
173 | */
174 | View.prototype._findMentionNode = function _findMentionNode(quill) {
175 | var range = quill.getSelection(),
176 | leafAndOffset,
177 | leaf,
178 | offset,
179 | node;
180 |
181 |
182 | leafAndOffset = quill.editor.doc.findLeafAt(range.start, true);
183 | leaf = leafAndOffset[0];
184 | offset = leafAndOffset[1]; // how many chars in front of current range
185 | if (leaf) node = leaf.node;
186 | while (node) {
187 | if (node.tagName === "SPAN") break;
188 | node = node.parentNode;
189 | }
190 | if (!node) return null;
191 | return node;
192 | };
193 |
194 | View.prototype._getLeftMargin = function(quill) {
195 | var mentionNode = this._findMentionNode(quill),
196 | mentionParent = this._findMentionContainerNode(quill);
197 |
198 | var mentionRect = mentionNode.getBoundingClientRect(),
199 | parentRect = mentionParent.getBoundingClientRect(),
200 | editorRect = quill.container.getBoundingClientRect();
201 |
202 | var marginLeft = mentionRect.left - parentRect.left;
203 |
204 | var overflow = marginLeft + mentionRect.width - editorRect.width;
205 |
206 | if (overflow > 0) {
207 | marginLeft -= (overflow);
208 | }
209 |
210 | return marginLeft + "px";
211 | };
212 |
213 | /**
214 | * @method
215 | * @private
216 | */
217 | View.prototype._getTopMargin = function(quill) {
218 | var qlEditor = quill.editor.root,
219 | qlLines,
220 | marginTop = this.marginTop,
221 | negMargin = -marginTop,
222 | range;
223 |
224 | qlLines = this._findOffsetLines(quill);
225 |
226 | negMargin += this._nodeHeight(qlEditor);
227 | negMargin -= qlLines.reduce(function(total, line) {
228 | return total + this._nodeHeight(line);
229 | }.bind(this), 0);
230 |
231 | return "-" + negMargin + "px";
232 | };
233 |
234 | /**
235 | * @method
236 | * @private
237 | */
238 | View.prototype._nodeHeight = function(node) {
239 | return node.getBoundingClientRect().height;
240 | };
241 |
242 |
243 | // TODO - write QuillEditor View
244 | function QuillEditorView() {
245 | throw new Error("NYI");
246 | }
--------------------------------------------------------------------------------
/docs/module-controller.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Module: controller
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Module: controller
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | controller
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Source:
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Classes
89 |
90 |
91 | AJAXController
92 |
93 |
94 | Controller
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Methods
105 |
106 |
107 |
108 |
109 | <inner> AbstractController(formatter, view, options)
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Parameters:
123 |
124 |
125 |
126 |
127 |
128 |
129 | Name
130 |
131 |
132 | Type
133 |
134 |
135 |
136 |
137 |
138 | Description
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | formatter
148 |
149 |
150 |
151 |
152 |
153 | function
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | Munges data
164 |
165 |
166 |
167 |
168 |
169 |
170 | view
171 |
172 |
173 |
174 |
175 |
176 | View
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | options
194 |
195 |
196 |
197 |
198 |
199 | object
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | Properties:
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 | Name
230 |
231 |
232 | Type
233 |
234 |
235 |
236 |
237 |
238 | Description
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | format
248 |
249 |
250 |
251 |
252 |
253 | function
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 | Munges data
264 |
265 |
266 |
267 |
268 |
269 |
270 | view
271 |
272 |
273 |
274 |
275 |
276 | View
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 | max
294 |
295 |
296 |
297 |
298 |
299 | number
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 | Maximum number of matches to pass to the View.
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 | Source:
336 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 | Modules Classes Namespaces Global
379 |
380 |
381 |
382 |
383 |
384 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
385 |
386 |
387 |
388 |
389 |
390 |
--------------------------------------------------------------------------------
/src/js/mentions.js:
--------------------------------------------------------------------------------
1 | /** @module mentions */
2 |
3 | var AJAXController = require("./controller").AJAXController,
4 | Controller = require("./controller").Controller,
5 | View = require("./view");
6 |
7 | var extend = require("./utilities/extend"),
8 | defaultFactory = require("./defaults/defaults"), // keep in defaults so we can write specific defaults for each object
9 | KEYUP = require("./keyboard").keyup,
10 | KEYDOWN = require("./keyboard").keydown;
11 |
12 | module.exports = QuillMentions;
13 |
14 | /**
15 | * @constructor
16 | * @param {Object} quill - An instance of `Quill`.
17 | * @param {Object} [options] - User configuration passed to the mentions module. It's mixed in with defaults.
18 | * @prop {Quill} quill
19 | * @prop {HTMLElement} container - Container for the popover (a.k.a. the View)
20 | * @prop {RegExp} matcher - Used to scan contents of editor for mentions.
21 | */
22 | function QuillMentions(quill, options) {
23 |
24 | this.quill = quill;
25 | var modOptions = defaultFactory(options),
26 | container = this.quill.addContainer(modOptions.containerClassName);
27 |
28 | this.triggerSymbol = modOptions.triggerSymbol;
29 | this.includeTrigger = modOptions.includeTrigger;
30 | this.matcher = modOptions.matcher;
31 | this.mentionClass = modOptions.mentionClass;
32 | this.currentMention = null;
33 |
34 | this.selectedChoiceIndex = -1;
35 |
36 | this.setView(container, modOptions)
37 | .setController(modOptions)
38 | .listenTextChange(quill)
39 | .listenSelectionChange(quill)
40 | .listenClick(container)
41 | .addFormat();
42 |
43 | if (modOptions.hotkeys) {
44 | this.listenHotKeys(quill);
45 | }
46 |
47 | this._cachedRange = null;
48 | this.charSinceMention = 0;
49 | }
50 |
51 | /**
52 | * Sets QuillMentions.view to a View object
53 | * @method
54 | * @private
55 | * @param {HTMLElement} container
56 | * @param {Object} options - Configuration for the view
57 | */
58 | QuillMentions.prototype.setView = function(container, options) {
59 | var templates = {},
60 | errMessage = options.noMatchMessage,
61 | marginTop = options.marginTop;
62 | templates.list = options.template;
63 | templates.listItem = options.choiceTemplate;
64 | templates.error = options.noMatchTemplate;
65 | this.view = new View(container, templates, {errMessage: errMessage, marginTop: marginTop });
66 | return this;
67 | };
68 |
69 | /**
70 | * Sets QuillMentions.controller to a Controller or AJAXController object (depending on options).
71 | * @method
72 | * @private
73 | * @param {Object} options - Configuration for the controller.
74 | */
75 | QuillMentions.prototype.setController = function(options) {
76 | if (!this.view) throw new Error("Must set view before controller.");
77 |
78 | var formatter,
79 | config = {
80 | max: options.choiceMax,
81 | };
82 | if (!options.ajax) {
83 | formatter = options.format;
84 | config.data = options.choices;
85 | this.controller = new Controller(formatter, this.view, config);
86 | } else {
87 | formatter = options.ajax.format;
88 | extend(config, options.ajax);
89 | this.controller = new AJAXController(formatter, this.view, config);
90 | }
91 | return this;
92 | };
93 |
94 | /**
95 | * Sets a listener for text-change events on the given Quill instance
96 | * @method
97 | * @param {Quill} quill - An instance of Quill
98 | */
99 | QuillMentions.prototype.listenTextChange = function listenTextChange(quill) {
100 | var eventName = this.quill.constructor.events.TEXT_CHANGE;
101 | quill.on(eventName, textChangeHandler.bind(this));
102 | return this;
103 |
104 | function textChangeHandler(delta, source) {
105 | if (source === "api") return;
106 | var mention = this.findMention(),
107 | query,
108 | _this;
109 |
110 | if (mention) {
111 | _this = this;
112 |
113 | this.charSinceMention = 0;
114 | this._cachedRange = quill.getSelection();
115 | this.currentMention = mention;
116 | this._addTemporaryMentionSpan();
117 |
118 | query = mention[0].replace(this.triggerSymbol, "");
119 |
120 | this.controller.search(query, function() {
121 | _this.view.show(_this.quill);
122 | });
123 | }
124 | else {
125 | this.charSinceMention++;
126 | this.view.hide();
127 | }
128 | }
129 | };
130 |
131 |
132 | /**
133 | * Sets a listener for selection-change events on the given Quill instance
134 | * @method
135 | * @param {Quill} quill - An instance of Quill
136 | */
137 | QuillMentions.prototype.listenSelectionChange = function(quill) {
138 | var eventName = quill.constructor.events.SELECTION_CHANGE;
139 | quill.on(eventName, selectionChangeHandler.bind(this));
140 | return this;
141 |
142 | function selectionChangeHandler(range) {
143 | if (!range) this.view.hide();
144 | }
145 | };
146 |
147 | /**
148 | * Sets a listener for keyboard events on the given container.
149 | * Events are dispatched through the KEYS object.
150 | * @method
151 | * @param {Quill} quill - An instance of Quill
152 | */
153 | QuillMentions.prototype.listenHotKeys = function(quill) {
154 | quill.container
155 | .addEventListener('keydown',
156 | keydownHandler.bind(this)); // TIL keypress is intended for keys that normally produce a character
157 |
158 | quill.container
159 | .addEventListener('keyup',
160 | keyupHandler.bind(this));
161 |
162 | return this;
163 |
164 | function keydownHandler(event) {
165 | var code = event.keyCode || event.which;
166 | if (!this.view.isHidden()) { // need special logic for enter key :sob:
167 | if (KEYDOWN[code]) {
168 | KEYDOWN[code].call(this);
169 | event.stopPropagation();
170 | event.preventDefault();
171 | }
172 | }
173 | }
174 | function keyupHandler(event) {
175 | var code = event.keyCode || event.which;
176 | if (!this.view.isHidden() || this.charSinceMention === 1) { // this weird if condition solve an issue where hitting enter would hide the view and we wouldn't be able to insert the mention...
177 | if (KEYUP[code]) {
178 | KEYUP[code].call(this);
179 | }
180 | }
181 | }
182 | };
183 |
184 | /**
185 | * Listens for a click or touchend event on the View.
186 | * @method
187 | * @param {HTMLElement} elt
188 | */
189 | QuillMentions.prototype.listenClick = function(elt) {
190 |
191 | elt.addEventListener("click", addMention.bind(this));
192 | elt.addEventListener("touchend", addMention.bind(this));
193 | return this;
194 |
195 | /** Wraps the QuillMentions~addMention method */
196 | function addMention(event) {
197 | var target = event.target || event.srcElement;
198 | if (target.tagName.toLowerCase() === "li") {
199 | this.addMention(target);
200 | }
201 | event.stopPropagation();
202 | }
203 | };
204 |
205 | /**
206 | * Looks for a string in the editor (up to the cursor's current position) for a match to QuillMentions~matcher
207 | * @method
208 | * @return {Match}
209 | */
210 | QuillMentions.prototype.findMention = function findMention() {
211 | var range = this.quill.getSelection() || this._cachedRange;
212 | if (!range) return;
213 | var cursor = range.end,
214 | contents = this.quill.getText(0, cursor);
215 |
216 | return this.matcher.exec(contents);
217 | };
218 |
219 | /**
220 | * Needs to be refactored! QuillMention should be responsible for this action.
221 | * @method
222 | * @param {HTMLElement}
223 | */
224 | QuillMentions.prototype.addMention = function addMention(node) {
225 | var insertAt = this.currentMention.index,
226 | toInsert = (this.includeTrigger ? this.triggerSymbol : "") + node.dataset.display,
227 | toFocus = insertAt + toInsert.length + 1;
228 |
229 |
230 | this.quill.deleteText(insertAt, insertAt + this.currentMention[0].length);
231 | this.quill.insertText(insertAt, toInsert, "mention", this.mentionClass+"-"+node.dataset.mention);
232 | this.quill.insertText(insertAt + toInsert.length, " ");
233 | this.quill.setSelection(toFocus, toFocus);
234 |
235 | this.view.hide();
236 | };
237 |
238 | QuillMentions.prototype._addTemporaryMentionSpan = function(range) {
239 | var insertAt = this.currentMention.index,
240 | toInsert = this.currentMention[0];
241 |
242 | this.quill.deleteText(insertAt, insertAt + this.currentMention[0].length);
243 | this.quill.insertText(insertAt, toInsert, "mention", "is-typing-mention");
244 | };
245 |
246 |
247 | /**
248 | * Refactor.
249 | * @method
250 | */
251 | QuillMentions.prototype.hasSelection = function() {
252 | return this.selectedChoiceIndex !== -1;
253 | };
254 |
255 | /**
256 | * Waiting on new custom formats in Quill to beef this up.
257 | * @method
258 | * @private
259 | */
260 | QuillMentions.prototype.addFormat = function(className) {
261 | this.quill.addFormat('mention', { tag: 'SPAN', "class": "ql-", }); // a mention is a span with the class prefix "ql-". the naming is an artifact of the current custom formats implementation
262 | return this;
263 | };
264 |
265 |
--------------------------------------------------------------------------------
/docs/view.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: view.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: view.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | var DOM = require("./utilities/dom"),
29 | extend = require("./utilities/extend"),
30 | replaceAll = require("./utilities/string-replace").all,
31 | escapeRegExp = require("./utilities/regexp").escapeRegExp;
32 |
33 | module.exports = View;
34 |
35 |
36 | /**
37 | * @constructor
38 | * @param {HTMLElement} container
39 | * @param {Object} templates - a set of templates into which we render munged data
40 | * @param {Object} options
41 | */
42 | function View(container, templates, options) {
43 | this.container = container;
44 | this.templates = extend({}, templates);
45 | this.marginTop = options.marginTop;
46 | this.errMessage = options.errMessage;
47 | }
48 |
49 | /**
50 | * Creates view from data and calls View~_renderSuccess. If there are no data, calls View~_renderError.
51 | * @method
52 | * @param {array} data
53 | */
54 | View.prototype.render = function(data) {
55 | var templates = this.templates,
56 | items,
57 | err,
58 | toRender;
59 | if (!data || !data.length) {
60 | err = templates.error.replace("{{message}}", this.errMessage);
61 | this.container.innerHTML = err;
62 | }
63 | else {
64 | items = data.map(this._renderLI, this).join("");
65 | this.container.innerHTML = templates.list.replace("{{choices}}", items);
66 | }
67 | return this;
68 | };
69 |
70 | /**
71 | * Renders listItem template with a datum as the context
72 | * @method
73 | * @private
74 | * @param {object} datum - A piece of data
75 | */
76 | View.prototype._renderLI = function(datum) {
77 | var template = this.templates.listItem;
78 | return this._renderWithContext(template, datum);
79 | };
80 |
81 | /**
82 | * Renders a template given the context of an object
83 | * @method
84 | * @private
85 | * @param {string} template
86 | * @param {object} o - Context for a template string.
87 | */
88 | View.prototype._renderWithContext = function(template, o) {
89 | var prop,
90 | result = template;
91 |
92 | for (prop in o) {
93 | if (o.hasOwnProperty(prop)) {
94 | result = replaceAll(result, "{{"+prop+"}}", o[prop]);
95 | }
96 | }
97 |
98 | return result;
99 | };
100 |
101 | /**
102 | * Makes the popover disappear
103 | * @method
104 | * @param {Quill} quill
105 | * @param {Object} range
106 | */
107 | View.prototype.hide = function hide(quill, range) {
108 | DOM.removeClass(this.container, "ql-is-mentioning");
109 | this.container.style.marginTop = "0";
110 | if (range) quill.setSelection(range);
111 | return this;
112 | };
113 |
114 | /**
115 | * @method
116 | * @returns {HTMLElement[]}
117 | */
118 | View.prototype.getMatches = function getMatches() {
119 | return this.container.querySelectorAll("li");
120 | };
121 |
122 | /**
123 | * @method
124 | * @returns {HTMLElement[]}
125 | */
126 | View.prototype.hasMatches = function hasMatches() {
127 | return this.getMatches().length > 0;
128 | };
129 |
130 | /**
131 | * Returns whether the popover is in view. I had bad feels about this method but it's coming in hand re: keyboard events right now.
132 | * @method
133 | * @returns {boolean}
134 | */
135 | View.prototype.isHidden = function isHidden() {
136 | return !DOM.hasClass(this.container, "ql-is-mentioning");
137 | };
138 |
139 | /**
140 | * Adds an active class to the mentions popover and sits it beneath the cursor.
141 | * [TODO - add active class to config]
142 | * @method
143 | * @param {Quill} quill
144 | */
145 | View.prototype.show = function show(quill) {
146 |
147 | this.container.style.marginTop = this._getTopMargin(quill);
148 | this.container.style.marginLeft = this._getLeftMargin(quill);
149 | DOM.addClass(this.container, "ql-is-mentioning"); // TODO - config active class
150 | this.container.focus(); // Does this even do anything? It would if we were using form elements instead of LIs prob
151 |
152 | return this;
153 | };
154 |
155 | /**
156 | * Return an array of dom nodes corresponding to all lines at or before the line corresponding to the current range.
157 | * @method
158 | * @private
159 | * @param {Range} range
160 | * @return {Node[]}
161 | */
162 | View.prototype._findOffsetLines = function(quill) {
163 | var node = this._findMentionContainerNode(quill);
164 | return DOM.getOlderSiblingsInclusive(node);
165 | };
166 |
167 | /**
168 | * Return the DOM node that encloses the line on which current mention is being typed.
169 | * @method
170 | * @private
171 | * @param {Range} range
172 | * @return {Node|null}
173 | */
174 | View.prototype._findMentionContainerNode = function _findMentionContainerNode(quill) {
175 | var range = quill.getSelection(),
176 | leafAndOffset,
177 | leaf,
178 | offset,
179 | node;
180 |
181 |
182 | leafAndOffset = quill.editor.doc.findLeafAt(range.start, true);
183 | leaf = leafAndOffset[0];
184 | offset = leafAndOffset[1]; // how many chars in front of current range
185 | if (leaf) node = leaf.node;
186 | while (node) {
187 | if (node.tagName === "DIV") break;
188 | node = node.parentNode;
189 | }
190 | if (!node) return null;
191 | return node;
192 | };
193 |
194 | /**
195 | * Return the (hopefully inline-positioned) DOM node that encloses the mention itself.
196 | * @method
197 | * @private
198 | * @param {Range} range
199 | * @return {Node|null}
200 | */
201 | View.prototype._findMentionNode = function _findMentionNode(quill) {
202 | var range = quill.getSelection(),
203 | leafAndOffset,
204 | leaf,
205 | offset,
206 | node;
207 |
208 |
209 | leafAndOffset = quill.editor.doc.findLeafAt(range.start, true);
210 | leaf = leafAndOffset[0];
211 | offset = leafAndOffset[1]; // how many chars in front of current range
212 | if (leaf) node = leaf.node;
213 | while (node) {
214 | if (node.tagName === "SPAN") break;
215 | node = node.parentNode;
216 | }
217 | if (!node) return null;
218 | return node;
219 | };
220 |
221 | View.prototype._getLeftMargin = function(quill) {
222 | var mentionNode = this._findMentionNode(quill),
223 | mentionParent = this._findMentionContainerNode(quill);
224 |
225 | var mentionRect = mentionNode.getBoundingClientRect(),
226 | parentRect = mentionParent.getBoundingClientRect(),
227 | editorRect = quill.container.getBoundingClientRect();
228 |
229 | var marginLeft = mentionRect.left - parentRect.left;
230 |
231 | var overflow = marginLeft + mentionRect.width - editorRect.width;
232 |
233 | if (overflow > 0) {
234 | marginLeft -= (overflow);
235 | }
236 |
237 | return marginLeft + "px";
238 | };
239 |
240 | /**
241 | * @method
242 | * @private
243 | */
244 | View.prototype._getTopMargin = function(quill) {
245 | var qlEditor = quill.editor.root,
246 | qlLines,
247 | marginTop = this.marginTop,
248 | negMargin = -marginTop,
249 | range;
250 |
251 | qlLines = this._findOffsetLines(quill);
252 |
253 | negMargin += this._nodeHeight(qlEditor);
254 | negMargin -= qlLines.reduce(function(total, line) {
255 | return total + this._nodeHeight(line);
256 | }.bind(this), 0);
257 |
258 | return "-" + negMargin + "px";
259 | };
260 |
261 | /**
262 | * @method
263 | * @private
264 | */
265 | View.prototype._nodeHeight = function(node) {
266 | return node.getBoundingClientRect().height;
267 | };
268 |
269 |
270 | // TODO - write QuillEditor View
271 | function QuillEditorView() {
272 | throw new Error("NYI");
273 | }
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 | Modules Classes Namespaces Global
284 |
285 |
286 |
287 |
288 |
289 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
290 |
291 |
292 |
293 |
294 |
295 |
296 |
--------------------------------------------------------------------------------
/dist/quill-mentions.min.js:
--------------------------------------------------------------------------------
1 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g{{value}}',containerClassName:"ql-mentions",format:f,hotkeys:!0,includeTrigger:!1,marginTop:10,matcher:/@\w+$/i,mentionClass:"mention-item",noMatchMessage:"Ruh Roh Raggy!",noMatchTemplate:"{{message}}
",template:"",triggerSymbol:"@"},h={format:f,path:null,queryParameter:"q"};b.exports=d},{"../utilities/extend":8,"../utilities/identity":9}],3:[function(a,b,c){(function(b){b.QuillMentions=a("./mentions")}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./mentions":5}],4:[function(a,b,c){function d(){this.view.isHidden()||h.call(this,1)}function e(){this.view.isHidden()||h.call(this,-1)}function f(){var a,b,c=this.selectedChoiceIndex;-1!==c&&this.view.hasMatches()&&(this.quill.setSelection(this._cachedRange),a=this.view.getMatches(),b=a[c],this.addMention(b),this.selectedChoiceIndex=-1)}function g(){this.view.hide(),this.selectedChoiceIndex=-1,this.quill.focus()}function h(a){var b,c,d,e,f=this.selectedChoiceIndex;return b=this.view.container.querySelectorAll("li"),0===b.length?void(this.selectedChoiceIndex=-1):(-1!==f&&(c=b[f],l(c,m)),d=i(f+a,b.length),e=b[d],void(e?(k(e,m),this.selectedChoiceIndex=d):console.log("Indexing error on node returned by querySelectorAll")))}function i(a,b){if(0>=b)throw new Error("TF are you doing? _normalizeIndex needs a nonnegative, nonzero modulo.");for(;0>a;)a+=b;return a%b}var j=a("./utilities/dom"),k=j.addClass,l=j.removeClass,m="ql-mention-choice-selected",n={27:g,38:e,40:d},o={13:f};b.exports={keyup:o,keydown:n}},{"./utilities/dom":7}],5:[function(a,b,c){function d(a,b){this.quill=a;var c=i(b),d=this.quill.addContainer(c.containerClassName);this.triggerSymbol=c.triggerSymbol,this.includeTrigger=c.includeTrigger,this.matcher=c.matcher,this.mentionClass=c.mentionClass,this.currentMention=null,this.selectedChoiceIndex=-1,this.setView(d,c).setController(c).listenTextChange(a).listenSelectionChange(a).listenClick(d).addFormat(),c.hotkeys&&this.listenHotKeys(a),this._cachedRange=null,this.charSinceMention=0}var e=a("./controller").AJAXController,f=a("./controller").Controller,g=a("./view"),h=a("./utilities/extend"),i=a("./defaults/defaults"),j=a("./keyboard").keyup,k=a("./keyboard").keydown;b.exports=d,d.prototype.setView=function(a,b){var c={},d=b.noMatchMessage,e=b.marginTop;return c.list=b.template,c.listItem=b.choiceTemplate,c.error=b.noMatchTemplate,this.view=new g(a,c,{errMessage:d,marginTop:e}),this},d.prototype.setController=function(a){if(!this.view)throw new Error("Must set view before controller.");var b,c={max:a.choiceMax};return a.ajax?(b=a.ajax.format,h(c,a.ajax),this.controller=new e(b,this.view,c)):(b=a.format,c.data=a.choices,this.controller=new f(b,this.view,c)),this},d.prototype.listenTextChange=function(a){function b(b,c){if("api"!==c){var d,e,f=this.findMention();f?(e=this,this.charSinceMention=0,this._cachedRange=a.getSelection(),this.currentMention=f,this._addTemporaryMentionSpan(),d=f[0].replace(this.triggerSymbol,""),this.controller.search(d,function(){e.view.show(e.quill)})):(this.charSinceMention++,this.view.hide())}}var c=this.quill.constructor.events.TEXT_CHANGE;return a.on(c,b.bind(this)),this},d.prototype.listenSelectionChange=function(a){function b(a){a||this.view.hide()}var c=a.constructor.events.SELECTION_CHANGE;return a.on(c,b.bind(this)),this},d.prototype.listenHotKeys=function(a){function b(a){var b=a.keyCode||a.which;this.view.isHidden()||k[b]&&(k[b].call(this),a.stopPropagation(),a.preventDefault())}function c(a){var b=a.keyCode||a.which;this.view.isHidden()&&1!==this.charSinceMention||j[b]&&j[b].call(this)}return a.container.addEventListener("keydown",b.bind(this)),a.container.addEventListener("keyup",c.bind(this)),this},d.prototype.listenClick=function(a){function b(a){var b=a.target||a.srcElement;"li"===b.tagName.toLowerCase()&&this.addMention(b),a.stopPropagation()}return a.addEventListener("click",b.bind(this)),a.addEventListener("touchend",b.bind(this)),this},d.prototype.findMention=function(){var a=this.quill.getSelection()||this._cachedRange;if(a){var b=a.end,c=this.quill.getText(0,b);return this.matcher.exec(c)}},d.prototype.addMention=function(a){var b=this.currentMention.index,c=(this.includeTrigger?this.triggerSymbol:"")+a.dataset.display,d=b+c.length+1;this.quill.deleteText(b,b+this.currentMention[0].length),this.quill.insertText(b,c,"mention",this.mentionClass+"-"+a.dataset.mention),this.quill.insertText(b+c.length," "),this.quill.setSelection(d,d),this.view.hide()},d.prototype._addTemporaryMentionSpan=function(a){var b=this.currentMention.index,c=this.currentMention[0];this.quill.deleteText(b,b+this.currentMention[0].length),this.quill.insertText(b,c,"mention","is-typing-mention")},d.prototype.hasSelection=function(){return-1!==this.selectedChoiceIndex},d.prototype.addFormat=function(a){return this.quill.addFormat("mention",{tag:"SPAN","class":"ql-"}),this}},{"./controller":1,"./defaults/defaults":2,"./keyboard":4,"./utilities/extend":8,"./view":12}],6:[function(a,b,c){b.exports={loadJSON:function(a,b,c){var d=new XMLHttpRequest;return d.onreadystatechange=function(){d.readyState===XMLHttpRequest.DONE&&(200===d.status?b&&b(JSON.parse(d.responseText)):c&&c(d))},d.open("GET",a,!0),d.send(),d}}},{}],7:[function(a,b,c){function d(a,b){f(a,b)||(a.className+=" "+b)}function e(a){var b=[a];if(!a)return[];for(;a.previousSibling;)b.push(a.previousSibling),a=a.previousSibling;return b}function f(a,b){return a?-1!==a.className.indexOf(b):console.log("Called hasClass on an empty node")}function g(a,b){if(f(a,b))for(;f(a,b);)a.className=a.className.replace(b,"")}b.exports.addClass=d,b.exports.getOlderSiblingsInclusive=e,b.exports.hasClass=f,b.exports.removeClass=g},{}],8:[function(a,b,c){function d(a){for(var b=[].slice.call(arguments,0),c=b[0],d=1;d0},d.prototype.isHidden=function(){return!e.hasClass(this.container,"ql-is-mentioning")},d.prototype.show=function(a){return this.container.style.marginTop=this._getTopMargin(a),this.container.style.marginLeft=this._getLeftMargin(a),e.addClass(this.container,"ql-is-mentioning"),this.container.focus(),this},d.prototype._findOffsetLines=function(a){var b=this._findMentionContainerNode(a);return e.getOlderSiblingsInclusive(b)},d.prototype._findMentionContainerNode=function(a){var b,c,d,e,f=a.getSelection();for(b=a.editor.doc.findLeafAt(f.start,!0),c=b[0],d=b[1],c&&(e=c.node);e&&"DIV"!==e.tagName;)e=e.parentNode;return e?e:null},d.prototype._findMentionNode=function(a){var b,c,d,e,f=a.getSelection();for(b=a.editor.doc.findLeafAt(f.start,!0),c=b[0],d=b[1],c&&(e=c.node);e&&"SPAN"!==e.tagName;)e=e.parentNode;return e?e:null},d.prototype._getLeftMargin=function(a){var b=this._findMentionNode(a),c=this._findMentionContainerNode(a),d=b.getBoundingClientRect(),e=c.getBoundingClientRect(),f=a.container.getBoundingClientRect(),g=d.left-e.left,h=g+d.width-f.width;return h>0&&(g-=h),g+"px"},d.prototype._getTopMargin=function(a){var b,c=a.editor.root,d=this.marginTop,e=-d;return b=this._findOffsetLines(a),e+=this._nodeHeight(c),e-=b.reduce(function(a,b){return a+this._nodeHeight(b)}.bind(this),0),"-"+e+"px"},d.prototype._nodeHeight=function(a){return a.getBoundingClientRect().height}},{"./utilities/dom":7,"./utilities/extend":8,"./utilities/regexp":10,"./utilities/string-replace":11}]},{},[1,2,3,4,5,6,7,8,9,10,11,12]);
--------------------------------------------------------------------------------
/docs/module-controller-AJAXController.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Class: AJAXController
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Class: AJAXController
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | controller ~
31 |
32 | AJAXController
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | new AJAXController()
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Properties:
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Name
71 |
72 |
73 | Type
74 |
75 |
76 |
77 |
78 |
79 | Description
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | path
89 |
90 |
91 |
92 |
93 |
94 | string
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | The path from which to request data.
105 |
106 |
107 |
108 |
109 |
110 |
111 | queryParameter
112 |
113 |
114 |
115 |
116 |
117 | string
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | The name of the paramter in the request to Controller~path
128 |
129 |
130 |
131 |
132 |
133 |
134 | _latestCall
135 |
136 |
137 |
138 |
139 |
140 | Object
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | Cached ajax call. Aborted if a new search is made.
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | Source:
177 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | Extends
208 |
209 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | Methods
226 |
227 |
228 |
229 |
230 | <private> _callback(data)
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 | Munges the callback data
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | Parameters:
248 |
249 |
250 |
251 |
252 |
253 |
254 | Name
255 |
256 |
257 | Type
258 |
259 |
260 |
261 |
262 |
263 | Description
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | data
273 |
274 |
275 |
276 |
277 |
278 | array
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 | Source:
318 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 | search(qry, callback)
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 | Parameters:
361 |
362 |
363 |
364 |
365 |
366 |
367 | Name
368 |
369 |
370 | Type
371 |
372 |
373 |
374 |
375 |
376 | Description
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 | qry
386 |
387 |
388 |
389 |
390 |
391 | String
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 | callback
409 |
410 |
411 |
412 |
413 |
414 | searchCallback
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 | Source:
454 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 | Modules Classes Namespaces Global
497 |
498 |
499 |
500 |
501 |
502 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
503 |
504 |
505 |
506 |
507 |
508 |
--------------------------------------------------------------------------------
/docs/scripts/prettify/Apache-License-2.0.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/docs/mentions.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: mentions.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: mentions.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | /** @module mentions */
29 |
30 | var AJAXController = require("./controller").AJAXController,
31 | Controller = require("./controller").Controller,
32 | View = require("./view");
33 |
34 | var extend = require("./utilities/extend"),
35 | defaultFactory = require("./defaults/defaults"), // keep in defaults so we can write specific defaults for each object
36 | KEYUP = require("./keyboard").keyup,
37 | KEYDOWN = require("./keyboard").keydown;
38 |
39 | module.exports = QuillMentions;
40 |
41 | /**
42 | * @constructor
43 | * @param {Object} quill - An instance of `Quill`.
44 | * @param {Object} [options] - User configuration passed to the mentions module. It's mixed in with defaults.
45 | * @prop {Quill} quill
46 | * @prop {HTMLElement} container - Container for the popover (a.k.a. the View)
47 | * @prop {RegExp} matcher - Used to scan contents of editor for mentions.
48 | */
49 | function QuillMentions(quill, options) {
50 |
51 | this.quill = quill;
52 | var modOptions = defaultFactory(options),
53 | container = this.quill.addContainer(modOptions.containerClassName);
54 |
55 | this.triggerSymbol = modOptions.triggerSymbol;
56 | this.includeTrigger = modOptions.includeTrigger;
57 | this.matcher = modOptions.matcher;
58 | this.mentionClass = modOptions.mentionClass;
59 | this.currentMention = null;
60 |
61 | this.selectedChoiceIndex = -1;
62 |
63 | this.setView(container, modOptions)
64 | .setController(modOptions)
65 | .listenTextChange(quill)
66 | .listenSelectionChange(quill)
67 | .listenClick(container)
68 | .addFormat();
69 |
70 | if (modOptions.hotkeys) {
71 | this.listenHotKeys(quill);
72 | }
73 |
74 | this._cachedRange = null;
75 | this.charSinceMention = 0;
76 | }
77 |
78 | /**
79 | * Sets QuillMentions.view to a View object
80 | * @method
81 | * @private
82 | * @param {HTMLElement} container
83 | * @param {Object} options - Configuration for the view
84 | */
85 | QuillMentions.prototype.setView = function(container, options) {
86 | var templates = {},
87 | errMessage = options.noMatchMessage,
88 | marginTop = options.marginTop;
89 | templates.list = options.template;
90 | templates.listItem = options.choiceTemplate;
91 | templates.error = options.noMatchTemplate;
92 | this.view = new View(container, templates, {errMessage: errMessage, marginTop: marginTop });
93 | return this;
94 | };
95 |
96 | /**
97 | * Sets QuillMentions.controller to a Controller or AJAXController object (depending on options).
98 | * @method
99 | * @private
100 | * @param {Object} options - Configuration for the controller.
101 | */
102 | QuillMentions.prototype.setController = function(options) {
103 | if (!this.view) throw new Error("Must set view before controller.");
104 |
105 | var formatter,
106 | config = {
107 | max: options.choiceMax,
108 | };
109 | if (!options.ajax) {
110 | formatter = options.format;
111 | config.data = options.choices;
112 | this.controller = new Controller(formatter, this.view, config);
113 | } else {
114 | formatter = options.ajax.format;
115 | extend(config, options.ajax);
116 | this.controller = new AJAXController(formatter, this.view, config);
117 | }
118 | return this;
119 | };
120 |
121 | /**
122 | * Sets a listener for text-change events on the given Quill instance
123 | * @method
124 | * @param {Quill} quill - An instance of Quill
125 | */
126 | QuillMentions.prototype.listenTextChange = function listenTextChange(quill) {
127 | var eventName = this.quill.constructor.events.TEXT_CHANGE;
128 | quill.on(eventName, textChangeHandler.bind(this));
129 | return this;
130 |
131 | function textChangeHandler(delta, source) {
132 | if (source === "api") return;
133 | var mention = this.findMention(),
134 | query,
135 | _this;
136 |
137 | if (mention) {
138 | _this = this;
139 |
140 | this.charSinceMention = 0;
141 | this._cachedRange = quill.getSelection();
142 | this.currentMention = mention;
143 | this._addTemporaryMentionSpan();
144 |
145 | query = mention[0].replace(this.triggerSymbol, "");
146 |
147 | this.controller.search(query, function() {
148 | _this.view.show(_this.quill);
149 | });
150 | }
151 | else {
152 | this.charSinceMention++;
153 | this.view.hide();
154 | }
155 | }
156 | };
157 |
158 |
159 | /**
160 | * Sets a listener for selection-change events on the given Quill instance
161 | * @method
162 | * @param {Quill} quill - An instance of Quill
163 | */
164 | QuillMentions.prototype.listenSelectionChange = function(quill) {
165 | var eventName = quill.constructor.events.SELECTION_CHANGE;
166 | quill.on(eventName, selectionChangeHandler.bind(this));
167 | return this;
168 |
169 | function selectionChangeHandler(range) {
170 | if (!range) this.view.hide();
171 | }
172 | };
173 |
174 | /**
175 | * Sets a listener for keyboard events on the given container.
176 | * Events are dispatched through the KEYS object.
177 | * @method
178 | * @param {Quill} quill - An instance of Quill
179 | */
180 | QuillMentions.prototype.listenHotKeys = function(quill) {
181 | quill.container
182 | .addEventListener('keydown',
183 | keydownHandler.bind(this)); // TIL keypress is intended for keys that normally produce a character
184 |
185 | quill.container
186 | .addEventListener('keyup',
187 | keyupHandler.bind(this));
188 |
189 | return this;
190 |
191 | function keydownHandler(event) {
192 | var code = event.keyCode || event.which;
193 | if (!this.view.isHidden()) { // need special logic for enter key :sob:
194 | if (KEYDOWN[code]) {
195 | KEYDOWN[code].call(this);
196 | event.stopPropagation();
197 | event.preventDefault();
198 | }
199 | }
200 | }
201 | function keyupHandler(event) {
202 | var code = event.keyCode || event.which;
203 | if (!this.view.isHidden() || this.charSinceMention === 1) { // this weird if condition solve an issue where hitting enter would hide the view and we wouldn't be able to insert the mention...
204 | if (KEYUP[code]) {
205 | KEYUP[code].call(this);
206 | }
207 | }
208 | }
209 | };
210 |
211 | /**
212 | * Listens for a click or touchend event on the View.
213 | * @method
214 | * @param {HTMLElement} elt
215 | */
216 | QuillMentions.prototype.listenClick = function(elt) {
217 |
218 | elt.addEventListener("click", addMention.bind(this));
219 | elt.addEventListener("touchend", addMention.bind(this));
220 | return this;
221 |
222 | /** Wraps the QuillMentions~addMention method */
223 | function addMention(event) {
224 | var target = event.target || event.srcElement;
225 | if (target.tagName.toLowerCase() === "li") {
226 | this.addMention(target);
227 | }
228 | event.stopPropagation();
229 | }
230 | };
231 |
232 | /**
233 | * Looks for a string in the editor (up to the cursor's current position) for a match to QuillMentions~matcher
234 | * @method
235 | * @return {Match}
236 | */
237 | QuillMentions.prototype.findMention = function findMention() {
238 | var range = this.quill.getSelection() || this._cachedRange;
239 | if (!range) return;
240 | var cursor = range.end,
241 | contents = this.quill.getText(0, cursor);
242 |
243 | return this.matcher.exec(contents);
244 | };
245 |
246 | /**
247 | * Needs to be refactored! QuillMention should be responsible for this action.
248 | * @method
249 | * @param {HTMLElement}
250 | */
251 | QuillMentions.prototype.addMention = function addMention(node) {
252 | var insertAt = this.currentMention.index,
253 | toInsert = (this.includeTrigger ? this.triggerSymbol : "") + node.dataset.display,
254 | toFocus = insertAt + toInsert.length + 1;
255 |
256 |
257 | this.quill.deleteText(insertAt, insertAt + this.currentMention[0].length);
258 | this.quill.insertText(insertAt, toInsert, "mention", this.mentionClass+"-"+node.dataset.mention);
259 | this.quill.insertText(insertAt + toInsert.length, " ");
260 | this.quill.setSelection(toFocus, toFocus);
261 |
262 | this.view.hide();
263 | };
264 |
265 | QuillMentions.prototype._addTemporaryMentionSpan = function(range) {
266 | var insertAt = this.currentMention.index,
267 | toInsert = this.currentMention[0];
268 |
269 | this.quill.deleteText(insertAt, insertAt + this.currentMention[0].length);
270 | this.quill.insertText(insertAt, toInsert, "mention", "is-typing-mention");
271 | };
272 |
273 |
274 | /**
275 | * Refactor.
276 | * @method
277 | */
278 | QuillMentions.prototype.hasSelection = function() {
279 | return this.selectedChoiceIndex !== -1;
280 | };
281 |
282 | /**
283 | * Waiting on new custom formats in Quill to beef this up.
284 | * @method
285 | * @private
286 | */
287 | QuillMentions.prototype.addFormat = function(className) {
288 | this.quill.addFormat('mention', { tag: 'SPAN', "class": "ql-", }); // a mention is a span with the class prefix "ql-". the naming is an artifact of the current custom formats implementation
289 | return this;
290 | };
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 | Modules Classes Namespaces Global
303 |
304 |
305 |
306 |
307 |
308 | Documentation generated by JSDoc 3.2.2 on Wed Jun 10 2015 15:59:13 GMT-0700 (PDT)
309 |
310 |
311 |
312 |
313 |
314 |
315 |
--------------------------------------------------------------------------------