', {
23 | "class": "count",
24 | html: count+' of '+this.attr('data-maxlength')
25 | }));
26 |
27 | // Attach event
28 | this.bind('keydown keyup paste',function(ev){
29 | count = ($(this).chars()<$(this).attr('data-maxlength')) ? $(this).chars() : ''+$(this).chars()+' ';
30 | $(this).parent().find('.count').html(count+' of '+$(this).attr('data-maxlength'));
31 | });
32 |
33 | }
34 |
35 | };
36 |
37 | $.fn.chars = function() {
38 | count = (this.attr('contenteditable')!==undefined) ? this.text().length : this.val().length;
39 | return count;
40 | };
41 |
42 | $.fn.too_long = function() {
43 | return this.chars() > this.attr('data-maxlength');
44 | };
45 |
46 | })(jQuery);
--------------------------------------------------------------------------------
/examples/sir-trevor.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madebymany/sir-trevor-js/8b5807a94f6e4486ee00ed815f898c3eb72ef19d/examples/sir-trevor.gif
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src/');
2 |
3 | require('./src/sass/main.scss');
4 |
--------------------------------------------------------------------------------
/locales/de.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.de = {
2 | general: {
3 | delete: "Löschen?",
4 | drop: "__block__ hier ablegen",
5 | paste: "Oder Adresse hier einfügen",
6 | upload: "...oder Datei auswählen",
7 | close: "Schließen",
8 | position: "Position",
9 | wait: "Bitte warten...",
10 | yes: "Ja ",
11 | no: "Nein"
12 | },
13 | errors: {
14 | title: "Die folgenden Fehler sind aufgetreten:",
15 | validation_fail: "Block __type__ ist ungültig",
16 | block_empty: "__name__ darf nicht leer sein",
17 | type_missing: "Blöcke mit Typ __type__ sind hier nicht zulässig",
18 | required_type_empty: "Angeforderter Block-Typ __type__ ist leer",
19 | load_fail:
20 | "Es wurde ein Problem beim Laden des Dokumentinhalts festgestellt",
21 | link_empty: "This link appears to be empty",
22 | link_invalid: "The link is not valid"
23 | },
24 | formatters: {
25 | link: {
26 | prompt: "Link eintragen",
27 | new_tab: "Opens in a new tab",
28 | message:
29 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
30 | types: {
31 | email: "an email address",
32 | telephone: "a telephone number",
33 | url: "a link"
34 | }
35 | },
36 | superscript: {
37 | prompt: "Titelattribut (optional)"
38 | }
39 | },
40 | blocks: {
41 | text: {
42 | title: "Text"
43 | },
44 | list: {
45 | title: "Liste (unsortiert)"
46 | },
47 | quote: {
48 | title: "Zitat",
49 | credit_field: "Quelle"
50 | },
51 | image: {
52 | title: "Bild",
53 | upload_error: "Es wurde ein Problem beim Upload festgestellt"
54 | },
55 | video: {
56 | title: "Video",
57 | drop_title: "Video URL"
58 | },
59 | tweet: {
60 | title: "Tweet",
61 | drop_title: "Tweet URL",
62 | fetch_error: "Es wurde ein Problem beim Laden des Tweets festgestellt"
63 | },
64 | embedly: {
65 | title: "Embedly",
66 | fetch_error: "There was a problem fetching your embed",
67 | key_missing: "An Embedly API key must be present"
68 | },
69 | heading: {
70 | title: "Überschrift"
71 | }
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/locales/es.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.es = {
2 | general: {
3 | delete: "¿Eliminar?",
4 | drop: "Arrastrar __block__ ici",
5 | paste: "O copie el enlace aquí",
6 | upload: "...o seleccione un fichero",
7 | close: "cerrar",
8 | position: "Posición",
9 | wait: "Por favor, espere..."
10 | },
11 | errors: {
12 | title: "Aparecerá el siguiente error :",
13 | validation_fail: "Bloque __type__ inválido",
14 | block_empty: "__name__ no debe estar vacío",
15 | type_missing: "Debe tene un tipo de bloque __type__",
16 | required_type_empty: "Un bloque obligatorio de tipo __type__ está vacío",
17 | load_fail: "Hay un problema con la carga de datos",
18 | link_empty: "This link appears to be empty",
19 | link_invalid: "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | prompt: "Introduce un link",
24 | new_tab: "Opens in a new tab",
25 | message:
26 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
27 | types: {
28 | email: "an email address",
29 | telephone: "a telephone number",
30 | url: "a link"
31 | }
32 | },
33 | superscript: {
34 | prompt: "Atributo de título (opcional)"
35 | }
36 | },
37 | blocks: {
38 | text: {
39 | title: "Texto"
40 | },
41 | list: {
42 | title: "Lista"
43 | },
44 | quote: {
45 | title: "Cita",
46 | credit_field: "Autor"
47 | },
48 | image: {
49 | title: "Imagen",
50 | upload_error: "Hay un problema con la subida"
51 | },
52 | video: {
53 | title: "Vídeo",
54 | drop_title: "URL del Vídeo"
55 | },
56 | tweet: {
57 | title: "Tweet",
58 | drop_title: "Tweet URL",
59 | fetch_error: "Se produjo un problema al recuperar su tweet"
60 | },
61 | embedly: {
62 | title: "Embebido",
63 | fetch_error: "Se produjo un problema al recuperar su video",
64 | key_missing: "Debe tener asociada una clave API"
65 | },
66 | heading: {
67 | title: "Título"
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/locales/fi.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.fi = {
2 | general: {
3 | delete: "Poista?",
4 | drop: "Raahaa __block__ tähän",
5 | paste: "Tai lisää osoite tähän",
6 | upload: "...tai valitse tiedosto",
7 | close: "sulje",
8 | position: "Sijainti",
9 | wait: "Odota hetki..."
10 | },
11 | errors: {
12 | title: "Seuraavat virheet:",
13 | validation_fail: "__type__-lohko on epäkelpo",
14 | block_empty: "__name__ ei saa olla tyhjä",
15 | type_missing: "__type__ on pakollinen lohko",
16 | required_type_empty: "Pakollinen __type__-lohko on tyhjä",
17 | load_fail: "Sisällön lataamisessa on ongelma",
18 | link_empty: "This link appears to be empty",
19 | link_invalid: "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | prompt: "Kirjoita osoite",
24 | new_tab: "Opens in a new tab",
25 | message:
26 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
27 | types: {
28 | email: "an email address",
29 | telephone: "a telephone number",
30 | url: "a link"
31 | }
32 | },
33 | superscript: {
34 | prompt: "Otsikon määrite (valinnainen)"
35 | }
36 | },
37 | blocks: {
38 | text: {
39 | title: "Teksti"
40 | },
41 | list: {
42 | title: "Lista"
43 | },
44 | quote: {
45 | title: "Lainaus",
46 | credit_field: "Lähde"
47 | },
48 | image: {
49 | title: "Kuva",
50 | upload_error: "Tiedoston lataamisessa oli ongelma"
51 | },
52 | video: {
53 | title: "Video",
54 | drop_title: "Videon URL-osoite"
55 | },
56 | tweet: {
57 | title: "Tweet",
58 | drop_title: "Tweet URL-osoite",
59 | fetch_error: "Viestin hakemisessa oli ongelma"
60 | },
61 | embedly: {
62 | title: "Embedly",
63 | fetch_error: "Sisällön hakemisessa oli ongelma",
64 | key_missing: "Embedly vaatii API-avaimen"
65 | },
66 | heading: {
67 | title: "Otsikko"
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/locales/fr.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.fr = {
2 | general: {
3 | delete: "Suppression ?",
4 | drop: "Glissez __block__ ici",
5 | paste: "Ou copiez le lien ici",
6 | upload: "...ou choisissez un fichier",
7 | close: "fermer",
8 | position: "Position",
9 | wait: "Veuillez patienter...",
10 | yes: "Oui",
11 | no: "Non"
12 | },
13 | errors: {
14 | title: "Vous avez l'erreur suivante :",
15 | validation_fail: "Bloc __type__ est invalide",
16 | block_empty: "__name__ ne doit pas être vide",
17 | type_missing: "Vous devez avoir un bloc de type __type__",
18 | required_type_empty: "Un bloc requis de type __type__ est vide",
19 | load_fail: "Il y a un problème avec le chargement des données du document",
20 | link_empty: "This link appears to be empty",
21 | link_invalid: "The link is not valid"
22 | },
23 | formatters: {
24 | link: {
25 | prompt: "Entrez un lien",
26 | new_tab: "Opens in a new tab",
27 | message:
28 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
29 | types: {
30 | email: "an email address",
31 | telephone: "a telephone number",
32 | url: "a link"
33 | }
34 | },
35 | superscript: {
36 | prompt: "Attribut de titre (facultatif)"
37 | }
38 | },
39 | blocks: {
40 | text: {
41 | title: "Texte"
42 | },
43 | list: {
44 | title: "Liste"
45 | },
46 | quote: {
47 | title: "Citation",
48 | credit_field: "Auteur"
49 | },
50 | image: {
51 | title: "Image",
52 | upload_error: "Il y a un problème avec votre téléchargement"
53 | },
54 | video: {
55 | title: "Vidéo",
56 | drop_title: "URL de la Vidéo"
57 | },
58 | tweet: {
59 | title: "Tweet",
60 | drop_title: "URL de Tweet",
61 | fetch_error:
62 | "Un problème est survenu lors de la récupération de votre tweet"
63 | },
64 | embedly: {
65 | title: "Embedly",
66 | fetch_error:
67 | "Un problème est survenu lors de la récupération de votre embed",
68 | key_missing: "Une clé API pour Embedly doit être présente"
69 | },
70 | heading: {
71 | title: "Titre"
72 | }
73 | }
74 | };
75 |
--------------------------------------------------------------------------------
/locales/pt-BR.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.pt_BR = {
2 | general: {
3 | 'delete': 'Deletar?',
4 | 'drop': 'Arraste __block__ aqui',
5 | 'paste': 'Ou cole o link aqui',
6 | 'upload': '...ou escolha um arquivo',
7 | 'close': 'fechar',
8 | 'position': 'Posição',
9 | 'wait': 'Aguarde...'
10 | },
11 | errors: {
12 | 'title': "Você tem os seguintes erros:",
13 | 'validation_fail': "o bloco __type__ é inválido",
14 | 'block_empty': "__name__ não pode ser vazio",
15 | 'type_missing': "Você deve ter um bloco do tipo __type__",
16 | 'required_type_empty': "Um bloco obrigatório do tipo __type__ está vazio",
17 | 'load_fail': "Houve um problema ao carregar o conteúdo do documento",
18 | 'link_empty': "This link appears to be empty",
19 | 'link_invalid': "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | 'prompt': "Insira o link",
24 | 'new_tab': "Opens in a new tab",
25 | 'message': "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
26 | types: {
27 | 'email': 'an email address',
28 | 'telephone': 'a telephone number',
29 | 'url': 'a link'
30 | }
31 | },
32 | superscript: {
33 | prompt: "Atributo de título (opcional)"
34 | }
35 | },
36 | blocks: {
37 | text: {
38 | 'title': "Texto"
39 | },
40 | list: {
41 | 'title': "Lista"
42 | },
43 | quote: {
44 | 'title': "Citação",
45 | 'credit_field': "Crédito"
46 | },
47 | image: {
48 | 'title': "Imagem",
49 | 'upload_error': "Houve um problema com o seu upload"
50 | },
51 | video: {
52 | 'title': "Video",
53 | 'drop_title': "URL do Vídeo"
54 | },
55 | tweet: {
56 | 'title': "Tweet",
57 | 'drop_title': "URL do Tweet"
58 | 'fetch_error': "Houve um problema ao carregar seu tweet"
59 | },
60 | embedly: {
61 | 'title': "Embedly",
62 | 'fetch_error': "Houve um problema ao carregar seu embed",
63 | 'key_missing': "Uma API key da Embedly precisa estar presente"
64 | },
65 | heading: {
66 | 'title': "Título"
67 | }
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/locales/pt.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.pt = {
2 | general: {
3 | delete: "Elimina?",
4 | drop: "Arrastar __block__ aqui",
5 | paste: "Ou cola o URL aquí",
6 | upload: "...ou selecionar um fichero",
7 | close: "Fechar",
8 | position: "Posicionar",
9 | wait: "Por favor, espere..."
10 | },
11 | errors: {
12 | title: "Sugerio os seguientes erros :",
13 | validation_fail: "Bloque __type__ inválido",
14 | block_empty: "__name__ no debe estar vacío",
15 | type_missing: "Necessitas um bloque de __type__",
16 | required_type_empty: "Um bloque obligatorio de tipo __type__ está vazio",
17 | load_fail: "Sugerio um problema a cargar os dados do documento",
18 | link_empty: "This link appears to be empty",
19 | link_invalid: "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | prompt: "Introduz um link",
24 | new_tab: "Opens in a new tab",
25 | message:
26 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
27 | types: {
28 | email: "an email address",
29 | telephone: "a telephone number",
30 | url: "a link"
31 | }
32 | },
33 | superscript: {
34 | prompt: "Atributo de título (opcional)"
35 | }
36 | },
37 | blocks: {
38 | text: {
39 | title: "Texto"
40 | },
41 | list: {
42 | title: "Lista"
43 | },
44 | quote: {
45 | title: "Cita",
46 | credit_field: "Autor"
47 | },
48 | image: {
49 | title: "Imagem",
50 | upload_error: "Sugerio um problema com o upload da imagem"
51 | },
52 | video: {
53 | title: "Vídeo",
54 | drop_title: "URL do Vídeo"
55 | },
56 | tweet: {
57 | title: "Tweet",
58 | drop_title: "URL do Tweet",
59 | fetch_error: "Sugerio um problema na busca da sua tweet"
60 | },
61 | embedly: {
62 | title: "Embebido",
63 | fetch_error: "Sugerio um problema na busca do video",
64 | key_missing: "Necessitamos a chave do API Embedly"
65 | },
66 | heading: {
67 | title: "Título"
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/locales/ru.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.ru = {
2 | general: {
3 | delete: "Удалить?",
4 | drop: "Перетащите __block__ сюда",
5 | paste: "Или введите URL здесь",
6 | upload: "... или выберите файл",
7 | close: "Закрыть",
8 | position: "Позиция",
9 | wait: "Пожалуйста, подождите ..."
10 | },
11 | errors: {
12 | title: "У вас есть следующие ошибки:",
13 | validation_fail: "__type__ блок является недопустимым",
14 | block_empty: "__name__ не должно быть пустым",
15 | type_missing: "Вы должны иметь блок типа __type__",
16 | required_type_empty: "Нужный тип блока __type__ пуст",
17 | load_fail: "Есть проблема при загрузке содержимого документа",
18 | link_empty: "This link appears to be empty",
19 | link_invalid: "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | prompt: "Введите ссылку",
24 | new_tab: "Opens in a new tab",
25 | message:
26 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
27 | types: {
28 | email: "an email address",
29 | telephone: "a telephone number",
30 | url: "a link"
31 | }
32 | },
33 | superscript: {
34 | prompt: "Атрибут заголовка (необязательно)"
35 | }
36 | },
37 | blocks: {
38 | text: {
39 | title: "Текст"
40 | },
41 | list: {
42 | title: "Cписок"
43 | },
44 | quote: {
45 | title: "Цитата",
46 | credit_field: "Слова"
47 | },
48 | image: {
49 | title: "Изображение",
50 | upload_error: "Есть проблемы с загрузкой"
51 | },
52 | video: {
53 | title: "Видео",
54 | drop_title: "URL видео"
55 | },
56 | tweet: {
57 | title: "Твит",
58 | drop_title: "URL Твит",
59 | fetch_error: "Есть проблемы с загрузкой"
60 | },
61 | embedly: {
62 | title: "Вставка",
63 | fetch_error: "Есть проблемы с вставлением",
64 | key_missing: "API должен присутствовать"
65 | },
66 | heading: {
67 | title: "Заголовок"
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/locales/zh-cn.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.cn = {
2 | general: {
3 | 'delete': '删除',
4 | 'drop': '把目标__block__拖拽到这里',
5 | 'paste': '或者粘贴一个URL',
6 | 'upload': '...或者点击上传',
7 | 'close': '关闭',
8 | 'position': '位置',
9 | 'wait': '处理中,请稍候...'
10 | },
11 | errors: {
12 | 'title': "发生了以下的错误:",
13 | 'validation_fail': "__type__ 模块不是有效模块",
14 | 'block_empty': "__name__ 不能为空",
15 | 'type_missing': "你必须至少有一个 __type__ 类型的模块",
16 | 'required_type_empty': "一个必须不为空的模块 __type__ 目前为空",
17 | 'load_fail': "载入文档内容失败",
18 | 'link_empty': "This link appears to be empty",
19 | 'link_invalid': "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | 'prompt': 请填写要插入的链接",
24 | 'new_tab': "Opens in a new tab",
25 | 'message': "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
26 | types: {
27 | 'email': 'an email address',
28 | 'telephone': 'a telephone number',
29 | 'url': 'a link'
30 | }
31 | },
32 | superscript: {
33 | prompt: "标题属性(可选)"
34 | }
35 | },
36 | blocks: {
37 | text: {
38 | 'title': "文字"
39 | },
40 | list: {
41 | 'title': "列表"
42 | },
43 | quote: {
44 | 'title': "引用",
45 | 'credit_field': "出处"
46 | },
47 | image: {
48 | 'title': "图片",
49 | 'upload_error': "载入图片失败"
50 | },
51 | video: {
52 | 'title': "视频",
53 | 'drop_title': "影片网址"
54 | },
55 | tweet: {
56 | 'title': "Tweet",
57 | 'drop_title': "鸣叫网址",
58 | 'fetch_error': "获取tweet失败"
59 | },
60 | embedly: {
61 | 'title': "内嵌内容",
62 | 'fetch_error': "内嵌内容载入失败",
63 | 'key_missing': "嵌入内容的API没有合法的Key"
64 | },
65 | heading: {
66 | 'title': '标题'
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/locales/zh-tw.js:
--------------------------------------------------------------------------------
1 | SirTrevor.Locales.cn_TW = {
2 | general: {
3 | delete: "刪除",
4 | drop: "把目標__block__ 拖拉到這里",
5 | paste: "或者粘貼一個URL",
6 | upload: "...或者點擊上傳",
7 | close: "關閉",
8 | position: "位置",
9 | wait: "處理中,請稍候..."
10 | },
11 | errors: {
12 | title: "發生了以下的錯誤:",
13 | validation_fail: "__type__ 模塊不是有效模塊",
14 | block_empty: "__name__ 不能為空",
15 | type_missing: "你必須至少有一個 __type__ 類型的模塊",
16 | required_type_empty: "一個必須不為空的模塊 __type__ 目前為空",
17 | load_fail: "載入文檔內容失敗",
18 | link_empty: "This link appears to be empty",
19 | link_invalid: "The link is not valid"
20 | },
21 | formatters: {
22 | link: {
23 | prompt: "請填寫要插入的鏈結",
24 | new_tab: "Opens in a new tab",
25 | message:
26 | "The URL you entered appears to be __type__. Do you want to add the required “__prefix__” prefix?",
27 | types: {
28 | email: "an email address",
29 | telephone: "a telephone number",
30 | url: "a link"
31 | }
32 | },
33 | superscript: {
34 | prompt: "标题属性(可选)"
35 | }
36 | },
37 | blocks: {
38 | text: {
39 | title: "文字"
40 | },
41 | list: {
42 | title: "列表"
43 | },
44 | quote: {
45 | title: "引用",
46 | credit_field: "出處"
47 | },
48 | image: {
49 | title: "圖片",
50 | upload_error: "載入圖片失敗"
51 | },
52 | video: {
53 | title: "視頻",
54 | drop_title: "影片網址"
55 | },
56 | tweet: {
57 | title: "Tweet",
58 | drop_title: "鳴叫網址",
59 | fetch_error: "獲取tweet失敗"
60 | },
61 | embedly: {
62 | title: "內嵌內容",
63 | fetch_error: "內嵌內容失敗",
64 | key_missing: "嵌入內容的API沒有合法的Key"
65 | },
66 | heading: {
67 | title: "標題"
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/public/images/icons/src/Bin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/images/icons/src/Competition.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Default.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/images/icons/src/Embed.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Image.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/List.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Poll.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Text.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Tweet.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/Video.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/add-block.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/images/icons/src/back.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/binopen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/bottom.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/images/icons/src/bump.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/center-align.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/images/icons/src/cross.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/edit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/emphasis.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/images/icons/src/file.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-bold.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-heading.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-italic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-quote.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-quote2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-superscript.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | superscript
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/images/icons/src/fmt-unlink.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/images/icons/src/game.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/heading.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/iFrame.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/instagram.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/images/icons/src/left-align.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/images/icons/src/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/middle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/images/icons/src/minus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/move-up-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/next.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/images/icons/src/publish.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/quote.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/republish.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/right-align.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/images/icons/src/rotate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/images/icons/src/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/small-tick.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/table.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/images/icons/src/tick.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/top.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/images/icons/src/user.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/icons/src/view.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/spec/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/spec/e2e/drag_and_drop_helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function() {
4 |
5 | var SimulateDragDrop = function(elem, options) {
6 | this.options = options || {};
7 | this.options.dataTransfer = Object.assign({
8 | data: {},
9 | setData: function(type, val) {
10 | this.data[type] = val;
11 | },
12 | getData: function(type) {
13 | return this.data[type];
14 | },
15 | setDragImage: function(el, x, y) {}
16 | }, this.options.dataTransfer || {});
17 |
18 | this.simulateEvent(elem);
19 | };
20 |
21 | SimulateDragDrop.prototype.simulateEvent = function(elem) {
22 | var type, event;
23 |
24 | if (elem) {
25 | /*Simulating drag start*/
26 | type = 'dragstart';
27 | event = this.createEvent(type, this.options.dataTransfer);
28 | this.dispatchEvent(elem, type, event);
29 | } else {
30 | event = this.createEvent('custom', this.options.dataTransfer);
31 | }
32 |
33 | /*Simulating drop*/
34 | type = 'drop';
35 | var dropEvent = this.createEvent(type);
36 | this.dispatchEvent(this.options.dropTarget, type, dropEvent);
37 |
38 | if (elem) {
39 | /*Simulating drag end*/
40 | type = 'dragend';
41 | var dragEndEvent = this.createEvent(type);
42 | this.dispatchEvent(elem, type, dragEndEvent);
43 | }
44 | };
45 |
46 | SimulateDragDrop.prototype.createEvent = function(type) {
47 | var event = document.createEvent("CustomEvent");
48 | event.initCustomEvent(type, true, true, null);
49 | event.dataTransfer = this.options.dataTransfer;
50 | return event;
51 | };
52 |
53 | SimulateDragDrop.prototype.dispatchEvent = function(elem, type, event) {
54 | if (elem.dispatchEvent) {
55 | elem.dispatchEvent(event);
56 | } else if(elem.fireEvent) {
57 | elem.fireEvent("on"+type, event);
58 | }
59 | };
60 |
61 | window.simulateDragDrop = function(element, options) {
62 | new SimulateDragDrop(element, options); // jshint ignore:line
63 | };
64 |
65 | })();
--------------------------------------------------------------------------------
/spec/javascripts/helpers/shims.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | require('es5-shim');
4 | // require('es6-shim'); // should be bundled into SirTrevor for now
5 |
--------------------------------------------------------------------------------
/spec/javascripts/helpers/sir-trevor.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | global.SirTrevor = require('../../../');
4 |
5 | global.createBaseElement = function() {
6 | var form = document.createElement("form");
7 | var element = document.createElement("textarea");
8 | form.appendChild(element);
9 | return element;
10 | };
11 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block-manager/options.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("BlockManager::Options", function(){
4 |
5 | var manager, mediator;
6 |
7 | beforeEach(function(){
8 | mediator = Object.assign({}, SirTrevor.Events);
9 | });
10 |
11 | describe("setting required blocks", function(){
12 |
13 | beforeEach(function(){
14 | managerWithOptions({ required: ['Text'] });
15 | });
16 |
17 | it("has an array of required blocks", function(){
18 | expect(manager.required).not.toBe(false);
19 | });
20 |
21 | it("has the options passed in the array", function(){
22 | expect(manager.required).toContain('Text');
23 | });
24 |
25 | });
26 |
27 | describe("setting available block types", function(){
28 |
29 | beforeEach(function(){
30 | managerWithOptions({ blockTypes: ['Text'] });
31 | });
32 |
33 | it("sets the object to the specified option", function(){
34 | expect(manager.blockTypes).toEqual(["Text"]);
35 | });
36 |
37 | it("won't be the default set of blocks", function(){
38 | expect(manager.blockTypes).not.toBe(SirTrevor.Blocks);
39 | });
40 |
41 | });
42 |
43 | describe("setting the block type limits", function(){
44 |
45 | beforeEach(function(){
46 | managerWithOptions({ blockTypeLimits: { 'Text': 1 } });
47 | });
48 |
49 | it("sets the options to the specified value", function(){
50 | expect(manager.options.blockTypeLimits.Text).toBe(1);
51 | });
52 |
53 | });
54 |
55 | describe("setting the block limit", function(){
56 |
57 | beforeEach(function(){
58 | managerWithOptions({
59 | blockLimit: 1
60 | });
61 | });
62 |
63 | it("sets the limit to the specified option", function(){
64 | expect(manager.options.blockLimit).toBe(1);
65 | });
66 |
67 | });
68 |
69 | function managerWithOptions(options) {
70 | var element = global.createBaseElement();
71 | var editor = new SirTrevor.Editor(Object.assign({
72 | el: element,
73 | blockTypes: ["Text"]
74 | }, options));
75 | manager = editor.blockManager;
76 | }
77 |
78 | });
79 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block-manager/removing-blocks.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("BlockManager::Removing blocks", function(){
4 |
5 | var manager;
6 |
7 | beforeEach(function(){
8 | var element = global.createBaseElement();
9 | var editor = new SirTrevor.Editor({
10 | el: element,
11 | blockTypes: ["Text"]
12 | });
13 | manager = editor.blockManager;
14 | manager.createBlock('Text');
15 | });
16 |
17 | it("removes the block from the blocks array", function(){
18 | manager.removeBlock(manager.blocks[0].blockID);
19 | expect(manager.blocks.length).toBe(0);
20 | });
21 |
22 | it("decrements the block type count", function(){
23 | manager.removeBlock(manager.blocks[0].blockID);
24 | expect(manager.blockCounts.Text).toBe(0);
25 | });
26 |
27 | });
28 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block-manager/validations.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("BlockManager::Validations", function(){
4 |
5 | var manager;
6 |
7 | describe("required block types", function(){
8 |
9 | beforeEach(function(){
10 | var element = global.createBaseElement();
11 | var editor = new SirTrevor.Editor({
12 | el: element,
13 | blockTypes: ["Text"],
14 | defaultType: false,
15 | required: ['Text']
16 | });
17 | manager = editor.blockManager;
18 | spyOn(manager.mediator, 'trigger');
19 | });
20 |
21 | it("will emit an error if the block type is missing", function(){
22 | manager.validateBlockTypesExist(true);
23 | expect(manager.mediator.trigger).toHaveBeenCalledWith('errors:add',
24 | { text : i18n.t("errors:type_missing", { type: "Text" }) });
25 | });
26 |
27 | xit("will error if a required block is empty", function(){
28 | createBlock();
29 | manager.validateBlockTypesExist(true);
30 | expect(manager.mediator.trigger).toHaveBeenCalledWith('errors:add',
31 | { text : i18n.t("errors:required_type_empty", { type: "Text" }) });
32 | });
33 |
34 | it("won't error if a required block has text", function(){
35 | createBlock({ text: 'YOLO' });
36 | manager.validateBlockTypesExist(true);
37 | expect(manager.mediator.trigger).not.toHaveBeenCalledWith('errors:add',
38 | { text : i18n.t("errors:required_type_empty", { type: "Text" }) });
39 | });
40 |
41 | });
42 |
43 | function createBlock(data) {
44 | manager.createBlock('Text', data || {});
45 | return manager.blocks[manager.blocks.length - 1];
46 | }
47 |
48 | });
49 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/controllable.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Controllable Block", function(){
4 |
5 | var element, editor, block, testHandler;
6 |
7 | beforeEach(function(){
8 | element = global.createBaseElement();
9 | editor = new SirTrevor.Editor({
10 | el: element,
11 | blockTypes: ["Text"]
12 | });
13 |
14 | testHandler = jasmine.createSpy();
15 |
16 | SirTrevor.Blocks.ControllableBlock = SirTrevor.Block.extend({
17 | controllable: true,
18 | controls: {
19 | 'test': testHandler
20 | }
21 | });
22 |
23 | block = new SirTrevor.Blocks.ControllableBlock({}, editor.ID, editor.mediator);
24 | });
25 |
26 | afterEach(function(){
27 | delete SirTrevor.Blocks.ControllableBlock;
28 | });
29 |
30 | describe("render", function(){
31 |
32 | beforeEach(function(){
33 | spyOn(block, 'withMixin').and.callThrough();
34 | block.render();
35 | });
36 |
37 | it("gets the controllable mixin", function(){
38 | expect(block.withMixin)
39 | .toHaveBeenCalledWith(SirTrevor.BlockMixins.Controllable);
40 | });
41 |
42 | it("adds an element to control_ui", function(){
43 | expect(block.control_ui.querySelectorAll('.st-block-control-ui-btn').length)
44 | .toBe(1);
45 | });
46 |
47 | xit("runs the handler on click", function(){
48 | var event = new MouseEvent("click");
49 |
50 | block.control_ui.querySelector('.st-block-control-ui-btn').dispatchEvent(event);
51 | expect(testHandler)
52 | .toHaveBeenCalled();
53 | });
54 |
55 | });
56 |
57 | });
58 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/droppable.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Block:Droppable Block", function(){
4 |
5 | var element, editor, block;
6 |
7 | beforeEach(function(){
8 | element = global.createBaseElement();
9 | editor = new SirTrevor.Editor({ el: element });
10 |
11 | SirTrevor.Blocks.DroppableBlock = SirTrevor.Block.extend({
12 | droppable: true
13 | });
14 |
15 | block = new SirTrevor.Blocks.DroppableBlock({}, editor.ID, editor.mediator);
16 | });
17 |
18 | afterEach(function(){
19 | delete SirTrevor.Blocks.DroppableBlock;
20 | });
21 |
22 | describe("render", function(){
23 |
24 | beforeEach(function(){
25 | spyOn(block, 'withMixin').and.callThrough();
26 | block = block.render();
27 | });
28 |
29 | it("gets the droppable mixin", function(){
30 | expect(block.withMixin)
31 | .toHaveBeenCalledWith(SirTrevor.BlockMixins.Droppable);
32 | });
33 |
34 | it("adds a droppable class to inner", function(){
35 | expect(block.inner.classList.contains('st-block__inner--droppable'));
36 | });
37 |
38 | it("creates an inputs element", function(){
39 | expect(block.inputs)
40 | .not.toBe(undefined);
41 | });
42 |
43 | it("appends the html to the inputs element", function(){
44 | expect(block.inputs.querySelectorAll('.st-block__dropzone').length)
45 | .toBe(1);
46 | });
47 |
48 | });
49 |
50 | });
51 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/heading.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Blocks: Heading block", function() {
4 | var block;
5 |
6 | var getSerializedData = function(data) {
7 | var element = global.createBaseElement();
8 | var editor = new SirTrevor.Editor({ el: element });
9 | var options = editor.blockManager.blockOptions;
10 | block = new SirTrevor.Blocks.Heading(
11 | data,
12 | editor.id,
13 | editor.mediator,
14 | options,
15 | editor.options
16 | );
17 | block.render();
18 | return block.getBlockData();
19 | };
20 |
21 | it("doesn't allow block level elements", function() {
22 | getSerializedData({ text: "Test Heading" });
23 | expect(block._scribe.options.allowBlockElements).toBe(false);
24 | });
25 |
26 | it("doesn't wrap content in tags", function() {
27 | var data = getSerializedData({ text: "Test Heading" });
28 | expect(data.text).not.toContain("
");
29 | });
30 |
31 | it("doesn't save at the end of the text", function() {
32 | var data = getSerializedData({ text: "Test Heading" });
33 | expect(data.text).toEqual("Test Heading");
34 | });
35 |
36 | it("converts markdown inline styling to html", function() {
37 | var data = getSerializedData({ text: "**Test** _Heading_" });
38 | expect(data.text).toEqual("Test Heading ");
39 | });
40 |
41 | it("doesn't strip HTML style tags", function() {
42 | var blockData = { text: "Test Heading ", format: "html" };
43 | var data = getSerializedData(blockData);
44 | expect(data.text).toEqual("Test Heading ");
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/pastable.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Block:Pastable Block", function(){
4 |
5 | var element, editor, block;
6 |
7 | beforeEach(function(){
8 | element = global.createBaseElement();
9 | editor = new SirTrevor.Editor({ el: element });
10 |
11 | SirTrevor.Blocks.PastableBlock = SirTrevor.Block.extend({
12 | pastable: true
13 | });
14 |
15 | block = new SirTrevor.Blocks.PastableBlock({}, editor.ID, editor.mediator);
16 | });
17 |
18 | afterEach(function(){
19 | delete SirTrevor.Blocks.PastableBlock;
20 | });
21 |
22 | describe("render", function(){
23 |
24 | beforeEach(function(){
25 | spyOn(block, 'withMixin').and.callThrough();
26 | block = block.render();
27 | });
28 |
29 | it("gets the pastable mixin", function(){
30 | expect(block.withMixin)
31 | .toHaveBeenCalledWith(SirTrevor.BlockMixins.Pastable);
32 | });
33 |
34 | it("creates an inputs element", function(){
35 | expect(block.inputs).not.toBe(undefined);
36 | });
37 |
38 | it("appends the html to the inputs element", function(){
39 | expect(block.inputs.querySelectorAll('.st-paste-block').length)
40 | .toBe(1);
41 | });
42 |
43 | });
44 |
45 | });
46 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/store.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Block:Store", function(){
4 |
5 | var element, editor, block;
6 |
7 | beforeEach(function(){
8 | element = global.createBaseElement();
9 | editor = new SirTrevor.Editor({ el: element });
10 | });
11 |
12 | describe("create", function(){
13 |
14 | beforeEach(function(){
15 | block = new SirTrevor.Blocks.Text({ text: 'Test' }, editor.ID, editor.mediator);
16 | });
17 |
18 | it("creates the data in the block store", function(){
19 | expect(block.blockStorage).toBeDefined();
20 | });
21 |
22 | it("should have the data provided on intialization", function(){
23 | expect(block.blockStorage.data.text).toBe('Test');
24 | });
25 |
26 | });
27 |
28 | describe("_getData", function(){
29 |
30 | beforeEach(function(){
31 | block = new SirTrevor.Blocks.Text({ text: 'Test' }, editor.ID, editor.mediator);
32 | });
33 |
34 | it("should retrieve the data", function(){
35 | expect(block._getData().text).toBe("Test");
36 | });
37 |
38 | });
39 |
40 | describe("setData", function(){
41 |
42 | beforeEach(function(){
43 | block = new SirTrevor.Blocks.Text({ text: 'Test' }, editor.ID, editor.mediator);
44 | });
45 |
46 | it("should set the data on the block", function(){
47 | block.setData({ text: "Boom" });
48 | expect(block._getData().text).toBe("Boom");
49 | });
50 |
51 | it("should save the data, even if none is given", function(){
52 | block.setData({ text: '' });
53 | expect(block._getData().text).toBe('');
54 | });
55 |
56 | });
57 |
58 | describe("save", function(){
59 |
60 | beforeEach(function() {
61 | block = new SirTrevor.Blocks.Text({ text: 'Test' }, editor.ID, editor.mediator);
62 | spyOn(block, '_serializeData');
63 | });
64 |
65 | it("calls _serializeData on save", function(){
66 | block.save();
67 | expect(block._serializeData).toHaveBeenCalled();
68 | });
69 |
70 | });
71 |
72 | });
73 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/uploadable.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Block:Uploadable Block", function(){
4 |
5 | var element, editor, block;
6 |
7 | beforeEach(function(){
8 | element = global.createBaseElement();
9 | editor = new SirTrevor.Editor({ el: element });
10 |
11 | SirTrevor.Blocks.UploadableBlock = SirTrevor.Block.extend({
12 | uploadable: true
13 | });
14 |
15 | block = new SirTrevor.Blocks.UploadableBlock({}, editor.ID, editor.mediator);
16 | });
17 |
18 | afterEach(function(){
19 | delete SirTrevor.Blocks.UploadableBlock;
20 | });
21 |
22 | describe("render", function(){
23 |
24 | beforeEach(function(){
25 | spyOn(block, 'withMixin').and.callThrough();
26 |
27 | block = block.render();
28 | });
29 |
30 | it("gets the uploadable mixin", function(){
31 | expect(block.withMixin)
32 | .toHaveBeenCalledWith(SirTrevor.BlockMixins.Uploadable);
33 | });
34 |
35 | it("creates an inputs element", function(){
36 | expect(block.inputs)
37 | .not.toBe(undefined);
38 | });
39 |
40 | it("appends the html to the inputs element", function(){
41 | expect(block.inputs.querySelectorAll('.st-block__upload-container').length)
42 | .toBe(1);
43 | });
44 |
45 | });
46 |
47 | });
48 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/validation.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Block:Validation", function(){
4 | var element, editor, block;
5 |
6 | beforeEach(function(){
7 | element = global.createBaseElement();
8 | editor = new SirTrevor.Editor({ el: element });
9 | block = new SirTrevor.Blocks.Text({}, editor.ID, editor.mediator);
10 | });
11 |
12 | describe("valid", function(){
13 |
14 | beforeEach(function(){
15 | block.performValidations = function(){};
16 |
17 | spyOn(block, "performValidations");
18 | });
19 |
20 | it("will return true if there are no errors", function(){
21 | expect(block.valid()).toBe(true);
22 | });
23 |
24 | it("will return false if there are errors", function(){
25 | block.errors.push(1);
26 | expect(block.valid()).toBe(false);
27 | });
28 |
29 | it("will call the performValidations method on calling valid", function(){
30 | block.valid();
31 | expect(block.performValidations).toHaveBeenCalled();
32 | });
33 |
34 | });
35 |
36 | describe("performValidations", function(){
37 |
38 | beforeEach(function(){
39 | block.validations = ['testValidator'];
40 | block.testValidator = function(){};
41 | block.resetErrors = function(){};
42 | spyOn(block, "testValidator");
43 | spyOn(block, "resetErrors");
44 |
45 | block.performValidations();
46 | });
47 |
48 | it("will call any custom validators in the validations array", function(){
49 | expect(block.testValidator).toHaveBeenCalled();
50 | });
51 |
52 | it("will call resetErrors", function(){
53 | expect(block.resetErrors).toHaveBeenCalled();
54 | });
55 |
56 | });
57 |
58 | describe("validateField", function(){
59 |
60 | var field;
61 |
62 | beforeEach(function(){
63 | field = document.createElement("input");
64 | block.setError = function(field, reason){};
65 | spyOn(block, "setError");
66 | });
67 |
68 | it("will call setError if the field has no content", function(){
69 | block.validateField(field);
70 | expect(block.setError).toHaveBeenCalled();
71 | });
72 |
73 | it("will won't call setError if the field has content", function(){
74 | field.value = "Test";
75 | block.validateField(field);
76 | expect(block.setError).not.toHaveBeenCalled();
77 | });
78 |
79 | });
80 |
81 | });
82 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block/video.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var utils = require('../../../../src/utils');
4 |
5 | describe('Blocks: Video block', function() {
6 |
7 | var createBlock = function(type, data) {
8 | var element = global.createBaseElement();
9 | var editor = new SirTrevor.Editor({ el: element });
10 | var options = editor.blockManager.blockOptions;
11 | var Klass = SirTrevor.Blocks[utils.classify(type)];
12 | var block = new Klass(data, editor.id, editor.mediator, options);
13 | editor.blockManager.renderBlock(block);
14 |
15 | return block;
16 | };
17 |
18 | describe('initialize', function() {
19 | it('creates a video block', function() {
20 | var block = createBlock('video');
21 | expect(block).not.toBe(undefined);
22 | });
23 |
24 | it('test URL formats', function() {
25 |
26 | var urls = {
27 | youtube: [
28 | 'http://www.youtube.com/watch?v=-wtIMTCHWuI',
29 | 'http://www.youtube.com/v/-wtIMTCHWuI?version=3&autohide=1',
30 | 'http://youtu.be/-wtIMTCHWuI',
31 | 'https://www.youtube.com/watch?v=-wtIMTCHWuI&feature=youtu.be',
32 | 'https://youtu.be/-wtIMTCHWuI?t=35',
33 | 'http://www.youtube.com/v/PjDw3azfZWI&hl=en_US&start=1868'
34 | ],
35 | vimeo: [
36 | 'https://vimeo.com/11111111',
37 | 'http://vimeo.com/11111111',
38 | 'https://www.vimeo.com/11111111',
39 | 'http://www.vimeo.com/11111111',
40 | 'https://vimeo.com/channels/11111111',
41 | 'http://vimeo.com/channels/11111111',
42 | 'https://vimeo.com/groups/name/videos/11111111',
43 | 'http://vimeo.com/groups/name/videos/11111111',
44 | 'https://vimeo.com/album/2222222/video/11111111',
45 | 'http://vimeo.com/album/2222222/video/11111111',
46 | 'https://vimeo.com/11111111?param=test',
47 | 'http://vimeo.com/11111111?param=test'
48 | ]
49 | };
50 |
51 | var block = createBlock('video', urls[0]);
52 |
53 | for (var provider in block.providers) {
54 | if (typeof urls[provider] !== 'undefined') {
55 | var regex = block.providers[provider].regex;
56 | for (var i = 0; i < urls[provider].length; i++) {
57 | expect(urls[provider][i]).toMatch(regex);
58 | }
59 | }
60 | }
61 |
62 | });
63 |
64 | });
65 |
66 | });
67 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block_controls.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("BlockControls", function(){
4 |
5 | describe("creating a new instance", function(){
6 |
7 | var blockControls, editor;
8 |
9 | beforeEach(function(){
10 | var element = global.createBaseElement();
11 | editor = new SirTrevor.Editor({
12 | el: element
13 | });
14 | blockControls = editor.blockControls;
15 | });
16 |
17 | it("can be created", function(){
18 | expect(blockControls).toBeDefined();
19 | });
20 |
21 | it("creates an el", function(){
22 | expect(blockControls.el).toBeDefined();
23 | });
24 |
25 | it("sets the available types", function(){
26 |
27 | editor.blockManager.blockTypes.forEach(function(blockType){
28 |
29 | if (SirTrevor.Blocks[blockType].prototype.toolbarEnabled) {
30 | expect(
31 | blockControls.el.querySelector("[data-type=" + blockType.toLowerCase() + "]")
32 | ).not.toBe(null);
33 | } else {
34 | expect(
35 | blockControls.el.querySelector("[data-type=" + blockType.toLowerCase() + "]")
36 | ).toBe(null);
37 | }
38 | });
39 | });
40 |
41 | });
42 |
43 | });
44 |
--------------------------------------------------------------------------------
/spec/javascripts/units/block_positioner.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("BlockPositioner", function(){
4 |
5 | var positioner, positionerSelect, el, mediator;
6 |
7 | beforeEach(function() {
8 | el = document.createElement("div");
9 | mediator = Object.assign({}, SirTrevor.Events);
10 | positioner = new SirTrevor.BlockPositioner(el, mediator);
11 | positionerSelect = new SirTrevor.BlockPositionerSelect(mediator);
12 | positionerSelect.positioner = positioner;
13 | });
14 |
15 | describe("onSelectChange", function() {
16 | beforeEach(function() {
17 | spyOn(positioner.mediator, "trigger");
18 | });
19 |
20 | it("triggers a block:changePosition when the select value isn't 0", function() {
21 | positionerSelect.select = {value: 2};
22 | positionerSelect.onSelectChange();
23 |
24 | expect(positionerSelect.mediator.trigger)
25 | .toHaveBeenCalledWith("block:changePosition", el, 2, 'after');
26 | });
27 |
28 | it("triggers a block:changePosition with the type set as before when the value is 1", function() {
29 | positionerSelect.select = {value: 1};
30 | positionerSelect.onSelectChange();
31 |
32 | expect(positionerSelect.mediator.trigger)
33 | .toHaveBeenCalledWith("block:changePosition", el, 1, 'before');
34 | });
35 | });
36 |
37 | });
38 |
--------------------------------------------------------------------------------
/spec/javascripts/units/editor/options.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Editor:Editor with options", function(){
4 |
5 | var element, editor;
6 |
7 | beforeEach(function(){
8 | SirTrevor.config.instances = [];
9 | element = global.createBaseElement();
10 | });
11 |
12 | describe("setting the block limit", function(){
13 |
14 | beforeEach(function(){
15 | editor = new SirTrevor.Editor({
16 | el: element,
17 | blockLimit: 1
18 | });
19 | });
20 |
21 | it("sets the limit to the specified option", function(){
22 | expect(editor.options.blockLimit).toBe(1);
23 | });
24 |
25 | });
26 |
27 | describe("setting the defaultType", function(){
28 |
29 | beforeEach(function(){
30 | editor = new SirTrevor.Editor({
31 | el: element,
32 | defaultType: 'Text'
33 | });
34 | spyOn(editor.mediator, 'trigger');
35 | });
36 |
37 | it("is not false", function(){
38 | expect(editor.options.defaultType).not.toBe(false);
39 | });
40 |
41 | it("creates a default block of a type specified", function(){
42 | editor.createBlocks();
43 | expect(editor.mediator.trigger).toHaveBeenCalledWith(
44 | 'block:create', 'Text', {});
45 | });
46 |
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/spec/javascripts/units/editor/submission.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("Editor:Submission", function(){
4 |
5 | var element, editor;
6 |
7 | beforeEach(function(){
8 | SirTrevor.instances = [];
9 | element = global.createBaseElement();
10 | editor = new SirTrevor.Editor({
11 | el: element, defaultType: false
12 | });
13 | });
14 |
15 | it("calls reset and save on the store", function(){
16 | spyOn(editor.store, "reset");
17 | editor.onFormSubmit();
18 | expect(editor.store.reset).toHaveBeenCalled();
19 | });
20 |
21 | it("calls the validateBlocks method", function(){
22 | spyOn(editor, "validateBlocks");
23 | editor.onFormSubmit();
24 | expect(editor.validateBlocks).toHaveBeenCalled();
25 | });
26 |
27 | it("calls the validateBlockTypesExist method", function(){
28 | spyOn(editor.blockManager, "validateBlockTypesExist");
29 | editor.onFormSubmit();
30 | expect(editor.blockManager.validateBlockTypesExist).toHaveBeenCalled();
31 | });
32 |
33 | it("calls toString on the store", function(){
34 | spyOn(editor.store, "toString");
35 | editor.onFormSubmit();
36 | // Store gets called twice
37 | expect(editor.store.toString).toHaveBeenCalled();
38 | });
39 |
40 | });
41 |
--------------------------------------------------------------------------------
/spec/javascripts/units/format_bar.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("FormatBar", function(){
4 | });
5 |
--------------------------------------------------------------------------------
/spec/javascripts/units/sir-trevor.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("SirTrevor", function(){
4 |
5 | describe("setBlockOptions", function(){
6 |
7 | var block;
8 |
9 | beforeEach(function(){
10 | SirTrevor.Blocks.Test = SirTrevor.Block.extend({
11 | test: true
12 | });
13 |
14 | SirTrevor.setBlockOptions("Test", { test: false });
15 | });
16 |
17 | afterEach(function(){
18 | delete SirTrevor.Blocks.Test;
19 | });
20 |
21 | it("can set an option on a specific block", function(){
22 | expect(SirTrevor.Blocks.Test.prototype.test).toBe(false);
23 | });
24 |
25 | it("has the property set when we instantiate a block", function(){
26 | block = new SirTrevor.Blocks.Test();
27 | expect(block.test).toBe(false);
28 | });
29 |
30 | });
31 |
32 | describe("getInstance", function(){
33 |
34 | beforeEach(function(){
35 | SirTrevor.config.instances = [
36 | { ID: '123' },
37 | { ID: '456' }
38 | ];
39 | });
40 |
41 | it("retrieves the first instance if no params are given", function(){
42 | var instance = SirTrevor.getInstance();
43 | expect(instance.ID).toBe('123');
44 | });
45 |
46 | it("retrieves the instance by ID if a string is provided", function(){
47 | var instance = SirTrevor.getInstance('456');
48 | expect(instance.ID).toBe('456');
49 | });
50 |
51 | it("retrieves the instance by position if an integer is provided", function(){
52 | var instance = SirTrevor.getInstance(0);
53 | expect(instance.ID).toBe('123');
54 | });
55 |
56 | });
57 |
58 | });
59 |
--------------------------------------------------------------------------------
/spec/javascripts/units/submittable.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var utils = require('../../../src/packages/dom');
4 |
5 | describe("SirTrevor.Submittable", function() {
6 |
7 | var submittable;
8 | var formTemplate = "
";
9 |
10 | beforeEach(function() {
11 | submittable = new SirTrevor.Submittable(utils.createDocumentFragmentFromString(formTemplate));
12 | });
13 |
14 | describe("submitBtn", function() {
15 |
16 | it("should be an input btn", function() {
17 | expect(submittable.submitBtns[0].nodeName).toBe("INPUT");
18 | });
19 |
20 | });
21 |
22 | describe("submitBtnTitles", function() {
23 |
24 | it("has the initial title for the submitBtn", function() {
25 | expect(submittable.submitBtnTitles).toContain("Go!");
26 | });
27 |
28 | });
29 |
30 | describe("_disableSubmitButton", function() {
31 |
32 | beforeEach(function(){
33 | submittable._disableSubmitButton();
34 | });
35 |
36 | it("should set a disabled attribute", function() {
37 | expect(submittable.submitBtns[0].getAttribute('disabled')).toBe('disabled');
38 | });
39 |
40 | it("should set a disabled class", function() {
41 | expect(submittable.submitBtns[0].classList.contains('disabled')).toBe(true);
42 | });
43 |
44 | });
45 |
46 | describe("_enableSubmitButton", function() {
47 |
48 | beforeEach(function(){
49 | submittable._disableSubmitButton();
50 | submittable._enableSubmitButton();
51 | });
52 |
53 | it("shouldn't set a disabled attribute", function() {
54 | expect(submittable.submitBtns[0].getAttribute('disabled')).toBe(null);
55 | });
56 |
57 | it("shouldn't have a disabled class", function() {
58 | expect(submittable.submitBtns[0].classList.contains('disabled')).toBe(false);
59 | });
60 |
61 | });
62 |
63 | describe("setSubmitButton", function() {
64 |
65 | it("Adds the title provided", function() {
66 | submittable.setSubmitButton(null, "YOLO");
67 | expect(submittable.submitBtns[0].value).toBe('YOLO');
68 | });
69 |
70 | });
71 |
72 |
73 | describe("resetSubmitButton", function() {
74 |
75 | beforeEach(function() {
76 | submittable.setSubmitButton(null, "YOLO");
77 | submittable.resetSubmitButton();
78 | });
79 |
80 | it("should reset the title back to its previous state", function() {
81 | expect(submittable.submitBtns[0].value).toContain("Go!");
82 | });
83 |
84 | });
85 |
86 | });
87 |
--------------------------------------------------------------------------------
/src/block-addition-top.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | * SirTrevor Block Controls
5 | * --
6 | * Gives an interface for adding new Sir Trevor blocks.
7 | */
8 |
9 | const Events = require("./packages/events");
10 |
11 | module.exports.create = function(SirTrevor) {
12 |
13 | function createBlock(e) {
14 | // REFACTOR: mediator so that we can trigger events directly on instance?
15 | // REFACTOR: block create event expects data as second argument.
16 | /*jshint validthis:true */
17 | SirTrevor.mediator.trigger(
18 | "block:create", SirTrevor.options.defaultType || "Text", null, this.parentNode.parentNode.previousSibling, { autoFocus: true }
19 | );
20 | }
21 |
22 | function hide() {}
23 |
24 | // Public
25 | function destroy() {
26 | SirTrevor = null;
27 | }
28 |
29 | Events.delegate(
30 | SirTrevor.wrapper, ".st-block-addition-top__button", "click", createBlock
31 | );
32 |
33 | Events.delegate(
34 | SirTrevor.wrapper, ".st-block-addition-top__icon", "click", createBlock
35 | );
36 |
37 | return {destroy, hide};
38 | };
39 |
--------------------------------------------------------------------------------
/src/block-addition.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | * SirTrevor Block Controls
5 | * --
6 | * Gives an interface for adding new Sir Trevor blocks.
7 | */
8 |
9 | const dropEvents = require('./helpers/drop-events');
10 |
11 | const EventBus = require('./event-bus');
12 |
13 | const Dom = require('./packages/dom');
14 | const Events = require("./packages/events");
15 |
16 | const TOP_CONTROLS_TEMPLATE = require("./templates/top-controls");
17 |
18 | module.exports.create = function(SirTrevor) {
19 |
20 | function createBlock(e) {
21 | // REFACTOR: mediator so that we can trigger events directly on instance?
22 | // REFACTOR: block create event expects data as second argument.
23 | /*jshint validthis:true */
24 | SirTrevor.mediator.trigger(
25 | "block:create", SirTrevor.options.defaultType || "Text", null, this.parentNode.parentNode.id ? this.parentNode.parentNode : this.parentNode
26 | );
27 | }
28 |
29 | function hide() {}
30 |
31 | // Public
32 | function destroy() {
33 | SirTrevor = null;
34 | }
35 |
36 | SirTrevor.wrapper.insertAdjacentHTML("beforeend", TOP_CONTROLS_TEMPLATE());
37 |
38 | const topControls = SirTrevor.wrapper.querySelector('.st-top-controls');
39 |
40 | function onDrop(ev) {
41 | ev.preventDefault();
42 |
43 | var dropped_on = topControls,
44 | item_id = ev.dataTransfer.getData("text/plain"),
45 | block = document.querySelector('#' + item_id);
46 |
47 | if (!!item_id, !!block, dropped_on.id !== item_id) {
48 | Dom.insertAfter(block, dropped_on);
49 | }
50 | SirTrevor.mediator.trigger("block:rerender", item_id);
51 | EventBus.trigger("block:reorder:dropped", item_id);
52 | }
53 |
54 | dropEvents.dropArea(topControls);
55 | topControls.addEventListener('drop', onDrop);
56 |
57 | Events.delegate(
58 | SirTrevor.wrapper, ".st-block-addition", "click", createBlock
59 | );
60 |
61 | return {destroy, hide};
62 | };
63 |
--------------------------------------------------------------------------------
/src/block-deletion.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('./config');
4 |
5 | var BlockDeletion = function() {
6 | this._ensureElement();
7 | this._bindFunctions();
8 | };
9 |
10 | Object.assign(BlockDeletion.prototype, require('./function-bind'), require('./renderable'), {
11 |
12 | tagName: 'a',
13 | className: 'st-block-ui-btn__delete',
14 |
15 | attributes: {
16 | html: () => `
17 |
18 | `,
19 | 'data-icon': 'close'
20 | }
21 |
22 | });
23 |
24 | module.exports = BlockDeletion;
25 |
--------------------------------------------------------------------------------
/src/block-positioner-select.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Dom = require('./packages/dom');
4 |
5 | var template = [
6 | " ",
7 | " "
8 | ].join("\n");
9 |
10 | var BlockPositionerSelect = function(mediator) {
11 | this.mediator = mediator;
12 |
13 | this._ensureElement();
14 | this._bindFunctions();
15 |
16 | this.initialize();
17 | };
18 |
19 | Object.assign(BlockPositionerSelect.prototype, require('./function-bind'), require('./renderable'), {
20 |
21 | total_blocks: 0,
22 |
23 | bound: ['onBlockCountChange', 'onSelectChange'],
24 |
25 | className: 'st-block-positioner__inner',
26 |
27 | initialize: function(){
28 | this.el.insertAdjacentHTML("beforeend", template);
29 | this.select = this.el.querySelector('.st-block-positioner__select');
30 | this.positioner = null;
31 |
32 | this.select.addEventListener('change', this.onSelectChange);
33 | },
34 |
35 | onBlockCountChange: function(new_count) {
36 | if (new_count !== this.total_blocks) {
37 | this.total_blocks = new_count;
38 | this.renderPositionList();
39 | }
40 | },
41 |
42 | onSelectChange: function() {
43 | var val = this.select.value;
44 | if (val !== 0) {
45 | this.mediator.trigger(
46 | "block:changePosition", this.positioner.block, val,
47 | (val === 1 ? 'before' : 'after'));
48 | this.positioner.toggle();
49 | }
50 | },
51 |
52 | renderPositionList: function() {
53 | var inner = "" + i18n.t("general:position") + " ";
54 | for(var i = 1; i <= this.total_blocks; i++) {
55 | inner += ""+i+" ";
56 | }
57 | this.select.innerHTML = inner;
58 | },
59 |
60 | renderInBlock: function(positioner) {
61 | // hide old
62 | if (this.positioner && this.positioner !== positioner) {
63 | this.positioner.hide();
64 | }
65 |
66 | // add new
67 | this.positioner = positioner;
68 | this.select.value = 0;
69 | Dom.remove(this.el);
70 | positioner.el.appendChild(this.el);
71 | }
72 |
73 | });
74 |
75 | module.exports = BlockPositionerSelect;
76 |
--------------------------------------------------------------------------------
/src/block-positioner.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var BlockPositioner = function(block, mediator) {
4 | this.mediator = mediator;
5 | this.block = block;
6 |
7 | this._ensureElement();
8 | this._bindFunctions();
9 |
10 | this.initialize();
11 | };
12 |
13 | Object.assign(BlockPositioner.prototype, require('./function-bind'), require('./renderable'), {
14 |
15 | bound: ['toggle', 'show', 'hide'],
16 |
17 | className: 'st-block-positioner',
18 | visibleClass: 'active',
19 |
20 | initialize: function(){},
21 |
22 | toggle: function() {
23 | this.mediator.trigger('block-positioner-select:render', this);
24 | this.el.classList.toggle(this.visibleClass);
25 | },
26 |
27 | show: function(){
28 | this.el.classList.add(this.visibleClass);
29 | },
30 |
31 | hide: function(){
32 | this.el.classList.remove(this.visibleClass);
33 | }
34 |
35 | });
36 |
37 | module.exports = BlockPositioner;
38 |
--------------------------------------------------------------------------------
/src/block-store.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('./lodash');
4 | var utils = require('./utils');
5 |
6 | var EventBus = require('./event-bus');
7 |
8 | module.exports = {
9 |
10 | /**
11 | * Internal storage object for the block
12 | */
13 | blockStorage: {},
14 |
15 | /**
16 | * Initialize the store, including the block type
17 | */
18 | createStore: function(blockData) {
19 | this.blockStorage = {
20 | type: utils.underscored(this.type),
21 | data: blockData || {}
22 | };
23 | },
24 |
25 | /**
26 | * Serialize the block and save the data into the store
27 | */
28 | save: function() {
29 | var data = this._serializeData();
30 |
31 | if (!_.isEmpty(data)) {
32 | this.setData(data);
33 | }
34 | },
35 |
36 | getData: function() {
37 | this.save();
38 | return this.blockStorage;
39 | },
40 |
41 | getBlockData: function() {
42 | this.save();
43 | return this.blockStorage.data;
44 | },
45 |
46 | _getData: function() {
47 | return this.blockStorage.data;
48 | },
49 |
50 | /**
51 | * Set the block data.
52 | * This is used by the save() method.
53 | */
54 | setData: function(blockData) {
55 | utils.log("Setting data for block " + this.blockID);
56 | Object.assign(this.blockStorage.data, blockData || {});
57 | },
58 |
59 | setAndLoadData: function(blockData) {
60 | this.setData(blockData);
61 | this.beforeLoadingData();
62 | },
63 |
64 | _serializeData: function() {},
65 | loadData: function() {},
66 |
67 | beforeLoadingData: function() {
68 | utils.log("loadData for " + this.blockID);
69 | EventBus.trigger("editor/block/loadData");
70 | this.loadData(this._getData());
71 | },
72 |
73 | _loadData: function() {
74 | utils.log("_loadData is deprecated and will be removed in the future. Please use beforeLoadingData instead.");
75 | this.beforeLoadingData();
76 | },
77 |
78 | checkAndLoadData: function() {
79 | if (!_.isEmpty(this._getData())) {
80 | this.beforeLoadingData();
81 | }
82 | }
83 |
84 | };
85 |
--------------------------------------------------------------------------------
/src/block-validations.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('./lodash');
4 | var utils = require('./utils');
5 |
6 | var bestNameFromField = function(field) {
7 | var msg = field.getAttribute("data-st-name") || field.getAttribute("name");
8 |
9 | if (!msg) {
10 | msg = 'Field';
11 | }
12 |
13 | return utils.capitalize(msg);
14 | };
15 |
16 | module.exports = {
17 |
18 | errors: [],
19 |
20 | valid: function(){
21 | this.performValidations();
22 | return this.errors.length === 0;
23 | },
24 |
25 | // This method actually does the leg work
26 | // of running our validators and custom validators
27 | performValidations: function() {
28 | this.resetErrors();
29 |
30 | var required_fields = this.$('.st-required');
31 | Array.prototype.forEach.call(required_fields, function (f, i) {
32 | this.validateField(f);
33 | }.bind(this));
34 | this.validations.forEach(this.runValidator, this);
35 |
36 | this.el.classList.toggle('st-block--with-errors', this.errors.length > 0);
37 | },
38 |
39 | // Everything in here should be a function that returns true or false
40 | validations: [],
41 |
42 | validateField: function(field) {
43 |
44 | var content = field.getAttribute('contenteditable') ? field.textContent : field.value;
45 |
46 | if (content.length === 0) {
47 | this.setError(field, i18n.t("errors:block_empty",
48 | { name: bestNameFromField(field) }));
49 | }
50 | },
51 |
52 | runValidator: function(validator) {
53 | if (!_.isUndefined(this[validator])) {
54 | this[validator].call(this);
55 | }
56 | },
57 |
58 | setError: function(field, reason) {
59 | var msg = this.addMessage(reason, "st-msg--error");
60 | field.classList.add('st-error');
61 |
62 | this.errors.push({ field: field, reason: reason, msg: msg });
63 | },
64 |
65 | resetErrors: function() {
66 | this.errors.forEach(function(error){
67 | error.field.classList.remove('st-error');
68 | error.msg.remove();
69 | });
70 |
71 | this.messages.classList.remove("st-block__messages--is-visible");
72 | this.errors = [];
73 | }
74 |
75 | };
76 |
--------------------------------------------------------------------------------
/src/block_mixins/ajaxable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var utils = require('../utils');
4 |
5 | module.exports = {
6 |
7 | mixinName: "Ajaxable",
8 |
9 | ajaxable: true,
10 |
11 | initializeAjaxable: function(){
12 | this._queued = [];
13 | },
14 |
15 | addQueuedItem: function(name, deferred) {
16 | utils.log("Adding queued item for " + this.blockID + " called " + name);
17 |
18 | this._queued.push({ name: name, deferred: deferred });
19 | },
20 |
21 | removeQueuedItem: function(name) {
22 | utils.log("Removing queued item for " + this.blockID + " called " + name);
23 |
24 | this._queued = this._queued.filter(function(queued) {
25 | return queued.name !== name;
26 | });
27 | },
28 |
29 | hasItemsInQueue: function() {
30 | return this._queued.length > 0;
31 | },
32 |
33 | resolveAllInQueue: function() {
34 | this._queued.forEach(function(item){
35 | utils.log("Aborting queued request: " + item.name);
36 | item.deferred.cancel();
37 | }, this);
38 | }
39 |
40 | };
41 |
--------------------------------------------------------------------------------
/src/block_mixins/controllable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var utils = require('../utils');
4 | var config = require('../config');
5 | var Dom = require('../packages/dom');
6 | var Events = require('../packages/events');
7 |
8 | module.exports = {
9 |
10 | mixinName: "Controllable",
11 |
12 | initializeControllable: function() {
13 | utils.log("Adding controllable to block " + this.blockID);
14 | this.inner.classList.add('st-block__inner--controllable');
15 | this.control_ui = Dom.createElement('div', {'class': 'st-block__control-ui'});
16 | Object.keys(this.controls).forEach(
17 | function(cmd) {
18 | // Bind configured handler to current block context
19 | this.addUiControl(cmd, this.controls[cmd].bind(this));
20 | },
21 | this
22 | );
23 | this.inner.appendChild(this.control_ui);
24 | },
25 |
26 | getControlTemplate: function(cmd) {
27 | return Dom.createElement("a", {
28 | 'data-icon': cmd,
29 | 'class': 'st-icon st-block-control-ui-btn st-block-control-ui-btn--' + cmd,
30 | 'html': `
31 |
32 | `
33 | });
34 | },
35 |
36 | addUiControl: function(cmd, handler) {
37 | this.control_ui.appendChild(this.getControlTemplate(cmd));
38 | Events.delegate(this.control_ui, '.st-block-control-ui-btn--' + cmd, 'click', (e) => {
39 | this.selectUiControl(cmd);
40 | handler(e);
41 | });
42 | },
43 |
44 | selectUiControl: function(cmd) {
45 | var selectedClass = 'st-block-control-ui-btn--selected';
46 | Object.keys(this.controls).forEach(control => {
47 | this.getControlUiBtn(control).classList.remove(selectedClass);
48 | });
49 | this.getControlUiBtn(cmd).classList.add(selectedClass);
50 | },
51 |
52 | getControlUiBtn: function(cmd) {
53 | return this.control_ui.querySelector('.st-block-control-ui-btn--' + cmd);
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/src/block_mixins/fetchable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('../lodash');
4 | var Ajax = require('../packages/ajax');
5 |
6 | module.exports = {
7 |
8 | mixinName: "Fetchable",
9 |
10 | initializeFetchable: function(){
11 | this.withMixin(require('./ajaxable'));
12 | },
13 |
14 | fetch: function(url, options, success, failure){
15 | var uid = _.uniqueId(this.blockID + "_fetch"),
16 | xhr = Ajax.fetch(url, options);
17 |
18 | this.resetMessages();
19 | this.addQueuedItem(uid, xhr);
20 |
21 | function alwaysFunc(func, arg) {
22 | /*jshint validthis: true */
23 | func.call(this, arg);
24 | this.removeQueuedItem(uid);
25 | }
26 |
27 | if(!_.isUndefined(success)) {
28 | xhr.then(alwaysFunc.bind(this, success));
29 | }
30 |
31 | if(!_.isUndefined(failure)) {
32 | xhr.catch(alwaysFunc.bind(this, failure));
33 | }
34 |
35 | return xhr;
36 | }
37 |
38 | };
39 |
--------------------------------------------------------------------------------
/src/block_mixins/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | Ajaxable: require('./ajaxable.js'),
5 | Controllable: require('./controllable.js'),
6 | Droppable: require('./droppable.js'),
7 | Fetchable: require('./fetchable.js'),
8 | Pastable: require('./pastable.js'),
9 | Uploadable: require('./uploadable.js'),
10 | MultiEditable: require('./multi-editable.js'),
11 | Textable: require('./textable.js')
12 | };
13 |
--------------------------------------------------------------------------------
/src/block_mixins/pastable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('../lodash');
4 | var config = require('../config');
5 | var utils = require('../utils');
6 |
7 | module.exports = {
8 |
9 | mixinName: "Pastable",
10 | requireInputs: true,
11 |
12 | initializePastable: function() {
13 | utils.log("Adding pastable to block " + this.blockID);
14 |
15 | this.paste_options = Object.assign(
16 | {}, config.defaults.Block.paste_options, this.paste_options);
17 |
18 | this.inputs.insertAdjacentHTML("beforeend", _.template(this.paste_options.html, this));
19 |
20 | Array.prototype.forEach.call(this.$('.st-paste-block'), (el) => {
21 | el.addEventListener('click', function() {
22 | var event = document.createEvent('HTMLEvents');
23 | event.initEvent('select', true, false);
24 | this.dispatchEvent(event);
25 | });
26 | el.addEventListener('paste', this._handleContentPaste);
27 | el.addEventListener('submit', this._handleContentPaste);
28 | });
29 | }
30 |
31 | };
32 |
--------------------------------------------------------------------------------
/src/block_mixins/uploadable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('../lodash');
4 | var config = require('../config');
5 | var utils = require('../utils');
6 |
7 | var fileUploader = require('../extensions/file-uploader');
8 |
9 | module.exports = {
10 |
11 | mixinName: "Uploadable",
12 |
13 | uploadsCount: 0,
14 | requireInputs: true,
15 |
16 | initializeUploadable: function() {
17 | utils.log("Adding uploadable to block " + this.blockID);
18 | this.withMixin(require('./ajaxable'));
19 |
20 | this.upload_options = Object.assign({}, config.defaults.Block.upload_options, this.upload_options);
21 | this.inputs.insertAdjacentHTML("beforeend", _.template(this.upload_options.html, this));
22 |
23 | Array.prototype.forEach.call(this.inputs.querySelectorAll('button'), function(button) {
24 | button.addEventListener('click', function(ev){ ev.preventDefault(); });
25 | });
26 | Array.prototype.forEach.call(this.inputs.querySelectorAll('input'), function(input) {
27 | input.addEventListener('change', (function(ev) {
28 | this.onDrop(ev.currentTarget);
29 | }).bind(this));
30 | }.bind(this));
31 | },
32 |
33 | uploader: function(file, success, failure){
34 | return fileUploader(this, file, success, failure);
35 | }
36 |
37 | };
38 |
--------------------------------------------------------------------------------
/src/blocks/heading.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | Heading Block
5 | */
6 |
7 | var Block = require('../block');
8 | var stToHTML = require('../to-html');
9 |
10 | var ScribeTextBlockPlugin = require('./scribe-plugins/scribe-text-block-plugin');
11 | var ScribeQuotePlugin = require('./scribe-plugins/scribe-quote-plugin');
12 | var ScribeHeadingPlugin = require('./scribe-plugins/scribe-heading-plugin');
13 |
14 | module.exports = Block.extend({
15 |
16 | type: 'heading',
17 |
18 | editorHTML: ' ',
19 |
20 | configureScribe: function(scribe) {
21 | scribe.use(new ScribeHeadingPlugin(this));
22 | scribe.use(new ScribeTextBlockPlugin(this));
23 | scribe.use(new ScribeQuotePlugin(this));
24 |
25 | scribe.on('content-changed', this.toggleEmptyClass.bind(this));
26 | },
27 |
28 | mergeable: true,
29 | textable: true,
30 | toolbarEnabled: false,
31 |
32 | scribeOptions: {
33 | allowBlockElements: false,
34 | tags: {
35 | p: false
36 | }
37 | },
38 |
39 | icon_name: 'heading',
40 |
41 | loadData: function(data) {
42 | if (this.options.convertFromMarkdown && data.format !== "html") {
43 | this.setTextBlockHTML(stToHTML(data.text, this.type));
44 | } else {
45 | this.setTextBlockHTML(data.text);
46 | }
47 |
48 | const level = data.level || this.editorOptions.defaultHeadingLevel;
49 |
50 | this.setData({ level });
51 | this.el.dataset.level = level;
52 | },
53 |
54 | onBlockRender: function() {
55 | this.toggleEmptyClass();
56 | },
57 |
58 | toggleEmptyClass: function() {
59 | this.el.classList.toggle('st-block--empty', this._scribe.getTextContent().length === 0);
60 | },
61 |
62 | asClipboardHTML: function() {
63 | var data = this.getBlockData();
64 | return `${data.text} `;
65 | }
66 | });
67 |
--------------------------------------------------------------------------------
/src/blocks/image.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Dom = require('../packages/dom');
4 | var Block = require('../block');
5 |
6 | module.exports = Block.extend({
7 |
8 | type: "image",
9 |
10 | droppable: true,
11 | uploadable: true,
12 |
13 | icon_name: 'image',
14 |
15 | loadData: function(data){
16 | // Create our image tag
17 | this.editor.innerHTML = '';
18 | this.editor.appendChild(Dom.createElement('img', { src: data.file.url }));
19 | },
20 |
21 | onDrop: function(transferData){
22 | var file = transferData.files[0],
23 | urlAPI = (typeof URL !== "undefined") ? URL : (typeof webkitURL !== "undefined") ? webkitURL : null;
24 |
25 | // Handle one upload at a time
26 | if (/image/.test(file.type)) {
27 | this.loading();
28 | // Show this image on here
29 | Dom.hide(this.inputs);
30 | this.editor.innerHTML = '';
31 | this.editor.appendChild(Dom.createElement('img', { src: urlAPI.createObjectURL(file) }));
32 | Dom.show(this.editor);
33 |
34 | this.uploader(
35 | file,
36 | function(data) {
37 | this.setData(data);
38 | this.ready();
39 | },
40 | function(error) {
41 | this.addMessage(i18n.t('blocks:image:upload_error'));
42 | this.ready();
43 | }
44 | );
45 | }
46 | },
47 |
48 | asClipboardHTML: function() {
49 | var data = this.getBlockData();
50 | var url = data.file && data.file.url;
51 | if (!url) return;
52 | return ` `;
53 | }
54 | });
55 |
--------------------------------------------------------------------------------
/src/blocks/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | Text: require('./text'),
5 | Quote: require('./quote'),
6 | Image: require('./image'),
7 | Heading: require('./heading'),
8 | List: require('./list'),
9 | Tweet: require('./tweet'),
10 | Video: require('./video'),
11 | };
12 |
--------------------------------------------------------------------------------
/src/blocks/quote.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | Block Quote
5 | */
6 |
7 | var _ = require('../lodash');
8 |
9 | var Block = require('../block');
10 | var stToHTML = require('../to-html');
11 | var ScribeHeadingPlugin = require('./scribe-plugins/scribe-heading-plugin');
12 | var ScribeQuotePlugin = require('./scribe-plugins/scribe-quote-plugin');
13 |
14 | var template = _.template([
15 | ' ',
16 | ' <%= i18n.t("blocks:quote:credit_field") %> ',
17 | ' "',
18 | ' class="st-input-string js-cite-input" type="text" />'
19 | ].join("\n"));
20 |
21 | module.exports = Block.extend({
22 |
23 | type: "quote",
24 |
25 | icon_name: 'quote',
26 |
27 | mergeable: true,
28 | textable: true,
29 | toolbarEnabled: false,
30 |
31 | editorHTML: function() {
32 | return template(this);
33 | },
34 |
35 | configureScribe: function(scribe) {
36 | scribe.use(new ScribeHeadingPlugin(this));
37 | scribe.use(new ScribeQuotePlugin(this));
38 | },
39 |
40 | loadData: function(data){
41 | if (this.options.convertFromMarkdown && data.format !== "html") {
42 | this.setTextBlockHTML(stToHTML(data.text, this.type));
43 | } else {
44 | this.setTextBlockHTML(data.text);
45 | }
46 |
47 | if (data.cite) {
48 | this.$('.js-cite-input')[0].value = data.cite;
49 | }
50 | },
51 |
52 | asClipboardHTML: function() {
53 | var data = this.getBlockData();
54 |
55 | return `${data.text}- ${data.cite} `;
56 | }
57 | });
58 |
--------------------------------------------------------------------------------
/src/blocks/scribe-plugins/scribe-heading-plugin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var scribeHeadingPlugin = function(block) {
4 | return function(scribe) {
5 | let { defaultHeadingLevel, headingLevels } = block.editorOptions;
6 | headingLevels = headingLevels.sort();
7 | const minHeadingLevel = headingLevels[0];
8 | const maxHeadingLevel = headingLevels[headingLevels.length - 1];
9 |
10 | const headingCommand = new scribe.api.Command(`heading`);
11 | headingCommand.queryEnabled = () => {
12 | return block.inline_editable;
13 | };
14 | headingCommand.queryState = () => {
15 | if (block.type === 'heading') {
16 | return block.getBlockData().level || defaultHeadingLevel || minHeadingLevel;
17 | } else {
18 | return false;
19 | }
20 | };
21 |
22 | headingCommand.execute = function headingCommandExecute(value) {
23 | const nextIndex = headingLevels.indexOf(block.getBlockData().level) + 1;
24 | const level = headingLevels[nextIndex];
25 | const blockType = level ? 'Heading' : 'Text';
26 |
27 | var data = {
28 | format: 'html',
29 | level: level,
30 | text: block.getScribeInnerContent()
31 | };
32 |
33 | block.mediator.trigger("block:replace", block.el, blockType, data);
34 | };
35 |
36 | scribe.commands.heading = headingCommand;
37 | };
38 | };
39 |
40 | module.exports = scribeHeadingPlugin;
41 |
--------------------------------------------------------------------------------
/src/blocks/scribe-plugins/scribe-quote-plugin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var scribeQuotePlugin = function(block) {
4 | return function(scribe) {
5 |
6 | const quoteCommand = new scribe.api.Command('quote');
7 | quoteCommand.queryEnabled = () => {
8 | return block.inline_editable;
9 | };
10 | quoteCommand.queryState = () => {
11 | return block.type === 'quote';
12 | };
13 |
14 | const getBlockType = function() {
15 | return quoteCommand.queryState() ? 'Text' : 'Quote';
16 | };
17 |
18 | quoteCommand.execute = function quoteCommandExecute(value) {
19 | var data = {
20 | format: 'html',
21 | text: block.getScribeInnerContent()
22 | };
23 |
24 | block.mediator.trigger("block:replace", block.el, getBlockType(), data);
25 | };
26 |
27 | scribe.commands.quote = quoteCommand;
28 | };
29 | };
30 |
31 | module.exports = scribeQuotePlugin;
32 |
--------------------------------------------------------------------------------
/src/blocks/text.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | Text Block
5 | */
6 |
7 | var Block = require('../block');
8 | var stToHTML = require('../to-html');
9 |
10 | var ScribeTextBlockPlugin = require('./scribe-plugins/scribe-text-block-plugin');
11 | var ScribePastePlugin = require('./scribe-plugins/scribe-paste-plugin');
12 | var ScribeHeadingPlugin = require('./scribe-plugins/scribe-heading-plugin');
13 | var ScribeLinkPromptPlugin = require('./scribe-plugins/scribe-link-prompt-plugin');
14 | var ScribeQuotePlugin = require('./scribe-plugins/scribe-quote-plugin');
15 | var ScribeSuperscriptPromptPlugin = require('./scribe-plugins/scribe-superscript-prompt-plugin');
16 |
17 | module.exports = Block.extend({
18 |
19 | type: "text",
20 |
21 | editorHTML: '
',
22 |
23 | icon_name: 'text',
24 |
25 | mergeable: true,
26 | textable: true,
27 | toolbarEnabled: false,
28 |
29 | configureScribe: function(scribe) {
30 | scribe.use(new ScribeTextBlockPlugin(this));
31 | scribe.use(new ScribePastePlugin(this));
32 | scribe.use(new ScribeHeadingPlugin(this));
33 | scribe.use(new ScribeLinkPromptPlugin(this));
34 | scribe.use(new ScribeQuotePlugin(this));
35 | scribe.use(new ScribeSuperscriptPromptPlugin(this));
36 |
37 | scribe.on('content-changed', this.toggleEmptyClass.bind(this));
38 | },
39 |
40 | scribeOptions: {
41 | allowBlockElements: true,
42 | tags: {
43 | p: true
44 | }
45 | },
46 |
47 | loadData: function(data){
48 | if (this.options.convertFromMarkdown && data.format !== "html") {
49 | this.setTextBlockHTML(stToHTML(data.text, this.type));
50 | } else {
51 | this.setTextBlockHTML(data.text);
52 | }
53 | },
54 |
55 | onBlockRender: function() {
56 | this.toggleEmptyClass();
57 | },
58 |
59 | toggleEmptyClass: function() {
60 | this.el.classList.toggle('st-block--empty', this.isEmpty());
61 | },
62 |
63 | isEmpty: function() {
64 | return this._scribe.getTextContent() === '';
65 | },
66 |
67 | asClipboardHTML: function() {
68 | var data = this.getBlockData();
69 | return `${data.text}`;
70 | }
71 | });
72 |
--------------------------------------------------------------------------------
/src/error-handler.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('./lodash');
4 | var Dom = require('./packages/dom');
5 |
6 | var ErrorHandler = function(wrapper, mediator, container) {
7 | this.wrapper = wrapper;
8 | this.mediator = mediator;
9 | this.el = container;
10 |
11 | if (_.isUndefined(this.el)) {
12 | this._ensureElement();
13 | this.wrapper.insertBefore(this.el, this.wrapper.firstChild);
14 | }
15 |
16 | Dom.hide(this.el);
17 |
18 | this._bindFunctions();
19 | this._bindMediatedEvents();
20 |
21 | this.initialize();
22 | };
23 |
24 | Object.assign(ErrorHandler.prototype, require('./function-bind'), require('./mediated-events'), require('./renderable'), {
25 |
26 | errors: [],
27 | className: "st-errors",
28 | eventNamespace: 'errors',
29 |
30 | mediatedEvents: {
31 | 'reset': 'reset',
32 | 'add': 'addMessage',
33 | 'render': 'render'
34 | },
35 |
36 | initialize: function() {
37 | var list = document.createElement("ul");
38 | var p = document.createElement("p");
39 | p.innerHTML = i18n.t("errors:title");
40 |
41 | this.el.appendChild(p)
42 | .appendChild(list);
43 | this.list = list;
44 | },
45 |
46 | render: function() {
47 | if (this.errors.length === 0) { return false; }
48 | this.errors.forEach(this.createErrorItem, this);
49 | Dom.show(this.el);
50 | },
51 |
52 | createErrorItem: function(errorObj) {
53 | var error = document.createElement("li");
54 | error.classList.add("st-errors__msg");
55 | error.innerHTML = errorObj.text;
56 | this.list.appendChild(error);
57 | },
58 |
59 | addMessage: function(error) {
60 | this.errors.push(error);
61 | },
62 |
63 | reset: function() {
64 | if (this.errors.length === 0) { return false; }
65 | this.errors = [];
66 | this.list.innerHTML = '';
67 | Dom.hide(this.el);
68 | }
69 |
70 | });
71 |
72 | module.exports = ErrorHandler;
73 |
74 |
--------------------------------------------------------------------------------
/src/event-bus.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = Object.assign({}, require('./events'));
4 |
--------------------------------------------------------------------------------
/src/events.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = require('eventablejs');
4 |
--------------------------------------------------------------------------------
/src/extensions/editor-store.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | * Sir Trevor Editor Store
5 | * By default we store the complete data on the instances $el
6 | * We can easily extend this and store it on some server or something
7 | */
8 |
9 | var _ = require('../lodash');
10 | var utils = require('../utils');
11 |
12 |
13 | var EditorStore = function(data, mediator) {
14 | this.mediator = mediator;
15 | this.initialize(data ? data.trim() : '');
16 | };
17 |
18 | Object.assign(EditorStore.prototype, {
19 |
20 | initialize: function(data) {
21 | this.store = this._parseData(data) || { data: [] };
22 | },
23 |
24 | retrieve: function() {
25 | return this.store;
26 | },
27 |
28 | toString: function(space) {
29 | return JSON.stringify(this.store, undefined, space);
30 | },
31 |
32 | reset: function() {
33 | utils.log("Resetting the EditorStore");
34 | this.store = { data: [] };
35 | },
36 |
37 | addData: function(data) {
38 | this.store.data.push(data);
39 | return this.store;
40 | },
41 |
42 | _parseData: function(data) {
43 | var result;
44 |
45 | if (data.length === 0) { return result; }
46 |
47 | try {
48 | // Ensure the JSON string has a data element that's an array
49 | var jsonStr = JSON.parse(data);
50 | if (!_.isUndefined(jsonStr.data)) {
51 | result = jsonStr;
52 | }
53 | } catch(e) {
54 | this.mediator.trigger(
55 | 'errors:add',
56 | { text: i18n.t("errors:load_fail") });
57 |
58 | this.mediator.trigger('errors:render');
59 |
60 | console.log('Sorry there has been a problem with parsing the JSON');
61 | console.log(e);
62 | }
63 |
64 | return result;
65 | }
66 |
67 | });
68 |
69 | module.exports = EditorStore;
70 |
--------------------------------------------------------------------------------
/src/extensions/file-uploader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | * Sir Trevor Uploader
5 | * Generic Upload implementation that can be extended for blocks
6 | */
7 |
8 | var _ = require('../lodash');
9 | var config = require('../config');
10 | var utils = require('../utils');
11 | var Ajax = require('../packages/ajax');
12 |
13 | var EventBus = require('../event-bus');
14 |
15 | module.exports = function(block, file, success, error) {
16 | var uid = [block.blockID, (new Date()).getTime(), 'raw'].join('-');
17 | var data = new FormData();
18 | var attachmentName = block.attachmentName || config.defaults.attachmentName;
19 | var attachmentFile = block.attachmentFile || config.defaults.attachmentFile;
20 | var attachmentUid = block.attachmentUid || config.defaults.attachmentUid;
21 |
22 | data.append(attachmentName, file.name);
23 | data.append(attachmentFile, file);
24 | data.append(attachmentUid, uid);
25 |
26 | EventBus.trigger('onUploadStart', data);
27 |
28 | block.resetMessages();
29 |
30 | var callbackSuccess = function(data) {
31 | utils.log('Upload callback called');
32 | EventBus.trigger('onUploadStop', data);
33 |
34 | if (!_.isUndefined(success) && _.isFunction(success)) {
35 | success.apply(block, arguments, data);
36 | }
37 |
38 | block.removeQueuedItem(uid);
39 | };
40 |
41 | var callbackError = function(jqXHR, status, errorThrown) {
42 | utils.log('Upload callback error called');
43 | EventBus.trigger('onUploadStop', undefined, errorThrown, status, jqXHR);
44 |
45 | if (!_.isUndefined(error) && _.isFunction(error)) {
46 | error.call(block, status);
47 | }
48 |
49 | block.removeQueuedItem(uid);
50 | };
51 |
52 | var url = block.uploadUrl || config.defaults.uploadUrl;
53 |
54 | var xhr = Ajax.fetch(url, {
55 | body: data,
56 | method: 'POST',
57 | dataType: 'json'
58 | });
59 |
60 | block.addQueuedItem(uid, xhr);
61 |
62 | xhr.then(callbackSuccess)
63 | .catch(callbackError);
64 |
65 | return xhr;
66 | };
67 |
--------------------------------------------------------------------------------
/src/form-events.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('./config');
4 | var utils = require('./utils');
5 |
6 | var EventBus = require('./event-bus');
7 | var Submittable = require('./extensions/submittable');
8 |
9 | var formBound = false; // Flag to tell us once we've bound our submit event
10 |
11 | var FormEvents = {
12 | bindFormSubmit: function(form) {
13 | if (!formBound) {
14 | // XXX: should we have a formBound and submittable per-editor?
15 | // telling JSHint to ignore as it'll complain we shouldn't be creating
16 | // a new object, but otherwise `this` won't be set in the Submittable
17 | // initialiser. Bit weird.
18 | new Submittable(form); // jshint ignore:line
19 | form.addEventListener('submit', this.onFormSubmit);
20 | formBound = true;
21 | }
22 | },
23 |
24 | onBeforeSubmit: function(shouldValidate) {
25 | // Loop through all of our instances and do our form submits on them
26 | var errors = 0;
27 | config.instances.forEach(function(inst, i) {
28 | errors += inst.onFormSubmit(shouldValidate);
29 | });
30 | utils.log("Total errors: " + errors);
31 |
32 | return errors;
33 | },
34 |
35 | onFormSubmit: function(ev) {
36 | var errors = FormEvents.onBeforeSubmit();
37 |
38 | if(errors > 0) {
39 | EventBus.trigger("onError");
40 | ev.preventDefault();
41 | }
42 | },
43 | };
44 |
45 | module.exports = FormEvents;
46 |
--------------------------------------------------------------------------------
/src/function-bind.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* Generic function binding utility, used by lots of our classes */
4 |
5 | module.exports = {
6 | bound: [],
7 | _bindFunctions: function(){
8 | this.bound.forEach(function(f) {
9 | this[f] = this[f].bind(this);
10 | }, this);
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/helpers/drop-events.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function dragEnter(e) {
4 | e.preventDefault();
5 | e.stopPropagation();
6 | }
7 |
8 | function dragOver(e) {
9 | e.dataTransfer.dropEffect = "copy";
10 | e.currentTarget.classList.add('st-drag-over');
11 | e.preventDefault();
12 | e.stopPropagation();
13 | }
14 |
15 | function dragLeave(e) {
16 | e.currentTarget.classList.remove('st-drag-over');
17 | e.preventDefault();
18 | e.stopPropagation();
19 | }
20 |
21 | module.exports = {
22 |
23 | dropArea: function(el) {
24 | el.addEventListener("dragenter", dragEnter);
25 | el.addEventListener("dragover", dragOver);
26 | el.addEventListener("dragleave", dragLeave);
27 | return el;
28 | },
29 |
30 | noDropArea: function(el) {
31 | el.removeEventListener("dragenter");
32 | el.removeEventListener("dragover");
33 | el.removeEventListener("dragleave");
34 | return el;
35 | }
36 |
37 | };
--------------------------------------------------------------------------------
/src/helpers/extend.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*
4 | Backbone Inheritence
5 | --
6 | From: https://github.com/documentcloud/backbone/blob/master/backbone.js
7 | Backbone.js 0.9.2
8 | (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
9 | */
10 |
11 | module.exports = function(protoProps, staticProps) {
12 | var parent = this;
13 | var child;
14 |
15 | // The constructor function for the new subclass is either defined by you
16 | // (the "constructor" property in your `extend` definition), or defaulted
17 | // by us to simply call the parent's constructor.
18 | if (protoProps && protoProps.hasOwnProperty('constructor')) {
19 | child = protoProps.constructor;
20 | } else {
21 | child = function(){ return parent.apply(this, arguments); };
22 | }
23 |
24 | // Add static properties to the constructor function, if supplied.
25 | Object.assign(child, parent, staticProps);
26 |
27 | // Set the prototype chain to inherit from `parent`, without calling
28 | // `parent`'s constructor function.
29 | var Surrogate = function(){ this.constructor = child; };
30 | Surrogate.prototype = parent.prototype;
31 | child.prototype = new Surrogate; // jshint ignore:line
32 |
33 | // Add prototype properties (instance properties) to the subclass,
34 | // if supplied.
35 | if (protoProps) {
36 | Object.assign(child.prototype, protoProps);
37 | }
38 |
39 | // Set a convenience property in case the parent's prototype is needed
40 | // later.
41 | child.__super__ = parent.prototype;
42 |
43 | return child;
44 | };
45 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | require("./icons/sir-trevor-icons.svg");
4 |
5 | // ES6 shims
6 | require('object.assign').shim();
7 | require('array.prototype.find').shim();
8 | require('./vendor/array-includes'); // shims ES7 Array.prototype.includes
9 | require('es6-promise').polyfill();
10 |
11 | // Old IE support
12 | require('./vendor/custom-event')
13 | require('./vendor/ie-classlist-toggle');
14 | require('./vendor/dom-shims');
15 |
16 | var utils = require('./utils');
17 |
18 | var SirTrevor = {
19 |
20 | config: require('./config'),
21 |
22 | log: utils.log,
23 |
24 | Locales: require('./locales'),
25 |
26 | Events: require('./events'),
27 | EventBus: require('./event-bus'),
28 |
29 | EditorStore: require('./extensions/editor-store'),
30 | Submittable: require('./extensions/submittable'),
31 | FileUploader: require('./extensions/file-uploader'),
32 |
33 | BlockMixins: require('./block_mixins'),
34 | BlockPositioner: require('./block-positioner'),
35 | BlockPositionerSelect: require('./block-positioner-select'),
36 | BlockReorder: require('./block-reorder'),
37 | BlockDeletion: require('./block-deletion'),
38 | BlockValidations: require('./block-validations'),
39 | BlockStore: require('./block-store'),
40 | BlockManager: require('./block-manager'),
41 |
42 | SimpleBlock: require('./simple-block'),
43 | Block: require('./block'),
44 |
45 | Blocks: require('./blocks'),
46 |
47 | FormatBar: require('./format-bar'),
48 | Editor: require('./editor'),
49 |
50 | toMarkdown: require('./to-markdown'),
51 | toHTML: require('./to-html'),
52 |
53 | setDefaults: function(options) {
54 | Object.assign(SirTrevor.config.defaults, options || {});
55 | },
56 |
57 | getInstance: utils.getInstance,
58 |
59 | setBlockOptions: function(type, options) {
60 | var block = SirTrevor.Blocks[type];
61 |
62 | if (typeof block === "undefined") {
63 | return;
64 | }
65 |
66 | Object.assign(block.prototype, options || {});
67 | },
68 |
69 | runOnAllInstances: function(method) {
70 | if (SirTrevor.Editor.prototype.hasOwnProperty(method)) {
71 | var methodArgs = Array.prototype.slice.call(arguments, 1);
72 | Array.prototype.forEach.call(SirTrevor.config.instances, function(i) {
73 | i[method].apply(null, methodArgs);
74 | });
75 | } else {
76 | SirTrevor.log("method doesn't exist");
77 | }
78 | },
79 |
80 | };
81 |
82 | Object.assign(SirTrevor, require('./form-events'));
83 |
84 |
85 | module.exports = SirTrevor;
86 |
--------------------------------------------------------------------------------
/src/lodash.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.isEmpty = require('lodash.isempty');
4 | exports.isFunction = require('lodash.isfunction');
5 | exports.isObject = require('lodash.isobject');
6 | exports.isString = require('lodash.isstring');
7 | exports.isUndefined = require('lodash.isundefined');
8 | exports.result = require('lodash.result');
9 | exports.template = require('lodash.template');
10 | exports.uniqueId = require('lodash.uniqueid');
11 |
--------------------------------------------------------------------------------
/src/mediated-events.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | mediatedEvents: {},
5 | eventNamespace: null,
6 | _bindMediatedEvents: function() {
7 | Object.keys(this.mediatedEvents).forEach(function(eventName){
8 | var cb = this.mediatedEvents[eventName];
9 | eventName = this.eventNamespace ?
10 | this.eventNamespace + ':' + eventName :
11 | eventName;
12 | this.mediator.on(eventName, this[cb].bind(this));
13 | }, this);
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/packages/ajax.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | require('whatwg-fetch');
4 | var fetchJsonP = require('jsonp-promise');
5 | var cancellablePromise = require('./cancellable-promise');
6 | var config = require('../config');
7 |
8 | let Ajax = Object.create(null);
9 |
10 | Ajax.fetch = (url, options = {}) => {
11 |
12 | options = Object.assign({}, config.defaults.ajaxOptions, options);
13 |
14 | var promise;
15 | if (options.jsonp) {
16 | promise = fetchJsonP(url).promise;
17 | } else {
18 | promise = fetch(url, options).then( function(response) {
19 | if (options.dataType === 'json') {
20 | return response.json();
21 | }
22 | return response.text();
23 | });
24 | }
25 | return cancellablePromise(promise);
26 | };
27 |
28 | module.exports = Ajax;
--------------------------------------------------------------------------------
/src/packages/cancellable-promise.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var cancellablePromise = function(promise) {
4 | var resolve, reject;
5 |
6 | var proxyPromise = new Promise(function(res, rej) {
7 | resolve = res;
8 | reject = rej;
9 | });
10 |
11 | promise.then(
12 | function(value) {
13 | if(!proxyPromise.cancelled) {
14 | resolve(value);
15 | }
16 | },
17 | function(value) {
18 | if(!proxyPromise.cancelled) {
19 | reject(value);
20 | }
21 | }
22 | );
23 |
24 | proxyPromise.cancel = function() {
25 | this.cancelled = true;
26 | };
27 |
28 | return proxyPromise;
29 | };
30 |
31 | module.exports = cancellablePromise;
32 |
--------------------------------------------------------------------------------
/src/packages/events.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Dom = require("./dom");
4 |
5 | var fixEvent = function(e, target) {
6 | var obj = {};
7 |
8 | // Events don't work as normal objects, so need to copy properties directly.
9 | // List and matchers taken from jQuery.Event.fix.
10 | // For other properties refer to the originalEvent object.
11 |
12 | var props = {
13 | shared: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
14 | "metaKey relatedTarget shiftKey target timeStamp view which" ).split(" "),
15 | mouseEvent: ( "button buttons clientX clientY offsetX offsetY pageX pageY " +
16 | "screenX screenY toElement" ).split(" "),
17 | keyEvent: "char charCode key keyCode".split(" ")
18 | };
19 |
20 | var rkeyEvent = /^key/,
21 | rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/;
22 |
23 | var propsToCopy =
24 | rmouseEvent.test( e.type ) ? props.shared.concat(props.mouseEvent) :
25 | rkeyEvent.test( e.type ) ? props.shared.concat(props.keyEvent) :
26 | props.shared;
27 |
28 | var prop;
29 | for(var i = 0; i < propsToCopy.length; i++) {
30 | prop = propsToCopy[i];
31 | obj[prop] = e[prop];
32 | }
33 |
34 | obj.currentTarget = target;
35 | obj.originalEvent = e;
36 |
37 | obj.preventDefault = function() {
38 | if ( this.originalEvent ) {
39 | this.originalEvent.preventDefault();
40 | }
41 | };
42 |
43 | obj.stopPropagation = function() {
44 | if ( this.originalEvent ) {
45 | this.originalEvent.stopPropagation();
46 | }
47 | };
48 |
49 | return obj;
50 | };
51 |
52 | module.exports.delegate =
53 | function delegate(el, selector, event, fn, useCapture = false) {
54 | el.addEventListener(event, (e) => {
55 | var target = e.target;
56 | for (target; target && target !== el; target = target.parentNode) {
57 | if (Dom.matches(target, selector)) {
58 | fn.call(target, fixEvent(e, target));
59 | break;
60 | }
61 | }
62 | target = null;
63 | }, useCapture);
64 | };
65 |
--------------------------------------------------------------------------------
/src/packages/uuid.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // jshint ignore: start
4 | module.exports = function uuid(a,b){
5 | for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');
6 | return b;
7 | };
8 | // jshint ignore: end
9 |
--------------------------------------------------------------------------------
/src/renderable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('./lodash');
4 | var Dom = require('./packages/dom');
5 |
6 | module.exports = {
7 | tagName: 'div',
8 | className: 'sir-trevor__view',
9 | attributes: {},
10 |
11 | $: function(selector) {
12 | return this.el.querySelectorAll(selector);
13 | },
14 |
15 | render: function() {
16 | return this;
17 | },
18 |
19 | destroy: function() {
20 | if (!_.isUndefined(this.stopListening)) { this.stopListening(); }
21 | Dom.remove(this.el);
22 | },
23 |
24 | _ensureElement: function() {
25 | if (!this.el) {
26 | var attrs = Object.assign({}, _.result(this, 'attributes'));
27 | if (this.id) { attrs.id = this.id; }
28 | if (this.className) { attrs['class'] = this.className; }
29 |
30 | var el = Dom.createElement(this.tagName, attrs);
31 | this._setElement(el);
32 | } else {
33 | this._setElement(this.el);
34 | }
35 | },
36 |
37 | _setElement: function(element) {
38 | this.el = element;
39 | return this;
40 | }
41 | };
42 |
43 |
--------------------------------------------------------------------------------
/src/sass/_icons.scss:
--------------------------------------------------------------------------------
1 | .st-icon {
2 | cursor:pointer;
3 | width: 1em;
4 | height: 1em;
5 | fill: currentColor;
6 | }
--------------------------------------------------------------------------------
/src/sass/_variables.scss:
--------------------------------------------------------------------------------
1 | $text-block-font-size: 1.275em !default;
2 |
3 | $text-block-font-color: #282d31 !default;
4 | $accent-color: #17bb75 !default;
5 | $base-ui-color: #42474b !default;
6 | $light-ui-color: #ccc !default;
7 | $blocks-control-bg-color: #f7f7f7 !default;
8 | $blocks-control-border-color: #ddd !default;
9 | $error-color: #d70014 !default;
10 | $selection-color: #b1f2d6 !default;
11 |
12 | $inner-block-padding: 30px 30px 48px !default;
13 |
14 | $block-controls-height: 164px !default;
15 | $block-add-height: 40px !default;
16 | $block-controls-color: #9b9b9b !default;
17 |
18 | $border-size: 2px !default;
19 | $border-style: solid !default;
20 | $default-border: $border-size $border-style $accent-color !default;
21 |
--------------------------------------------------------------------------------
/src/sass/base.scss:
--------------------------------------------------------------------------------
1 | .st-outer {
2 | font-size: 16px;
3 | position: relative;
4 | background:#fff;
5 | }
6 |
7 | .st-icon {
8 | cursor:pointer;
9 | width: 100%;
10 | height: 100%;
11 | fill: currentColor;
12 | }
13 |
14 | .st-block__inner {
15 | ::-moz-selection {
16 | background: $selection-color;
17 | text-shadow: none;
18 | }
19 | ::selection {
20 | background: $selection-color;
21 | text-shadow: none;
22 | }
23 | }
24 |
25 | .st-spinner {
26 | position: absolute!important;
27 | left: 50%; top: 50%;
28 | }
29 |
--------------------------------------------------------------------------------
/src/sass/block-addition-top.scss:
--------------------------------------------------------------------------------
1 | .st-block-addition-top {
2 | transition: all $animation-speed 0.2s ease-in-out;
3 | text-align: left;
4 | outline:none;
5 | border:none;
6 | width:100%;
7 | background-color:transparent;
8 | padding:0;
9 | z-index: 2;
10 | position: relative;
11 | cursor: text;
12 | display: none;
13 |
14 | &::-moz-focus-inner {
15 | padding:0;
16 | margin:0;
17 | margin-left:-1px;
18 | }
19 |
20 | position: absolute;
21 | top: -2em;
22 | height: 30px;
23 | opacity: 0;
24 | display: block;
25 |
26 | &:before {
27 | transition: all $animation-speed 0.1s ease-in-out;
28 | background: $accent-color;
29 | position: absolute;
30 | height: 2px;
31 | top: 50%;
32 | left: 110px;
33 | right: 110px;
34 | content: "";
35 | display: block;
36 | transform: translateY(-50%) translateZ(0);
37 | }
38 |
39 | .st-block--empty & {
40 | display: none !important;
41 | }
42 |
43 | &:hover {
44 | opacity: 1;
45 | }
46 |
47 | .st-block--textable &,
48 | .st-block[data-type="list"] & {
49 | top: -1.5em;
50 | }
51 |
52 | .st-block:nth-child(3) & {
53 | display: none;
54 | }
55 |
56 | .st-block--empty + .st-block & {
57 | display: none;
58 | }
59 | }
60 |
61 | .st-block-addition-top__icon {
62 | transition: all $animation-speed 0.1s ease-in-out;
63 | border: 1px solid transparent;
64 | color: #444444;
65 | position:absolute;
66 | top: 50%;
67 | box-sizing:border-box;
68 | padding-left: 35px;
69 | display: inline-block;
70 | margin: 0 auto;
71 | cursor: pointer;
72 | opacity: 0.2;
73 | transform: translateY(-50%) translateZ(0);
74 |
75 | .st-icon {
76 | width: 41px;
77 | height: 41px;
78 | color: inherit;
79 | }
80 |
81 | .st-block-addition-top:hover &,
82 | .st-block--active & {
83 | color: $accent-color;
84 | opacity: 1;
85 | }
86 | }
87 |
88 | .st-block-addition-top__button {
89 | background: none;
90 | border:none;
91 | outline:none;
92 | position: absolute;
93 | top: 0;
94 | left: 76px;
95 | right: 0;
96 | bottom: 0;
97 | display: inline-block;
98 | }
99 |
--------------------------------------------------------------------------------
/src/sass/block-addition.scss:
--------------------------------------------------------------------------------
1 |
2 | $animation-speed: 0.2s;
3 |
4 | .st-top-controls {
5 | min-height: 1.750em;
6 | position: relative;
7 | z-index: 2;
8 | }
9 |
10 | .st-block-addition {
11 | text-align: center;
12 | outline:none;
13 | border:none;
14 | display:none;
15 | width:100%;
16 | background-color:transparent;
17 | padding:0;
18 | z-index: 2;
19 |
20 | &::-moz-focus-inner {
21 | padding:0;
22 | margin:0;
23 | margin-left:-1px;
24 | }
25 |
26 | .st-top-controls &,
27 | .st-block:last-child:not(.st-block--empty):not(.st-block--textable) &,
28 | .st-block[data-type="quote"]:last-child & {
29 | display: block;
30 | }
31 | }
32 |
33 | .st--hide-top-controls {
34 | .st-top-controls .st-block-addition {
35 | display: none;
36 | }
37 | }
38 |
39 | .st--block-limit-reached {
40 | .st-top-controls,
41 | .st-block-addition {
42 | display: none;
43 | }
44 | }
45 |
46 | .st-block-addition__button {
47 | transition: all $animation-speed 0.3s ease-in-out;
48 | border: 1px solid transparent;
49 | color: #444444;
50 | position:relative;
51 | box-sizing:border-box;
52 | padding:.2em;
53 | display: inline-block;
54 | margin: 0 auto;
55 | width: 100%;
56 | cursor: pointer;
57 | opacity: 0.2;
58 | cursor: pointer;
59 |
60 | transform:translateZ(0);
61 |
62 | .st-icon {
63 | height: 20px;
64 | width: 20px;
65 | color: inherit;
66 | }
67 |
68 | .st-block-addition:hover &,
69 | .st-block--active & {
70 | color: $accent-color;
71 | opacity: 1;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/sass/block-controls.scss:
--------------------------------------------------------------------------------
1 | // Block Controls
2 |
3 | .st-block-controls {
4 | position: absolute;
5 | left: 0;
6 | top: 50%;
7 | right: 0;
8 | font-size:0.8em;
9 | padding: 0px 110px;
10 | transform: translateY(-50%);
11 | z-index: 1;
12 | }
13 |
14 | .st-block-controls__buttons {
15 | text-align: center;
16 | }
17 |
18 | .st-block-controls__button {
19 | border:none;
20 | background:transparent;
21 | font-size: 12px;
22 | text-transform: uppercase;
23 | display: inline-block;
24 | cursor: pointer;
25 | margin: 0.5em;
26 | text-transform: uppercase;
27 | }
28 |
29 | .st-block-controls__button .st-icon {
30 | margin: 0 0 10px 0;
31 | display: block;
32 | width: 42px;
33 | height: 42px;
34 | }
35 |
36 | .st-block-controls__button:hover {
37 | color: $accent-color;
38 | }
39 |
--------------------------------------------------------------------------------
/src/sass/block-positioner.scss:
--------------------------------------------------------------------------------
1 | .st-block-positioner {
2 | @include ui-popup;
3 | right: 4em;
4 | top: 0;
5 | &:before {
6 | left: auto;
7 | right: -0.3em;
8 | }
9 | }
10 |
11 | .st-block-positioner__inner {
12 | background: #fff;
13 | position: relative;
14 | z-index: 2;
15 | padding: 0.3em 0.5em;
16 | }
17 |
18 | .st-block-positioner__select {
19 | display:block;
20 | }
21 |
22 | .st-block--with-errors, .st-block--delete-active {
23 | & > .st-block__inner {
24 |
25 | & > .st-block__ui {
26 | .st-block-positioner {
27 | border-color: $error-color;
28 | &:after {
29 | border-color: $error-color;
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/sass/block-replacer.scss:
--------------------------------------------------------------------------------
1 | .st-block-replacer {
2 | position: absolute;
3 | text-align: center;
4 | transform: translateY(-50%);
5 | outline:none;
6 | border:none;
7 | font-size:1.3em;
8 | display:none;
9 | background-color:transparent;
10 | padding:0;
11 | z-index: 2;
12 | top: 50%;
13 | left: 35px;
14 |
15 | &::-moz-focus-inner {
16 | padding:0;
17 | margin:0;
18 | margin-left:-1px;
19 | }
20 |
21 | .st-block--textable.st-block--empty & {
22 | display: block;
23 | }
24 |
25 | }
26 |
27 | .st-block-replacer__button {
28 | transition: all $animation-speed ease-in-out;
29 | color: $accent-color;
30 | position:relative;
31 | box-sizing:border-box;
32 | display: inline-block;
33 | margin: 0 auto;
34 | width: 41px;
35 | height: 41px;
36 | cursor: pointer;
37 |
38 | transform:translateZ(0);
39 |
40 | .st-block--controls-active & {
41 | transform: rotate(45deg);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/sass/errors.scss:
--------------------------------------------------------------------------------
1 | .st-errors {
2 | background-color: lighten($error-color, 52%);
3 | padding: 2em;
4 | color: $error-color;
5 | margin-bottom: 2em;
6 | }
7 |
8 | .st-errors p,
9 | .st-errors ul {
10 | margin: 0;
11 | }
12 |
13 | .st-errors ul {
14 | padding-left: 1em;
15 | }
16 |
17 | .st-errors p {
18 | margin-bottom: 0.5em;
19 | font-weight: 700;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/sass/format-bar.scss:
--------------------------------------------------------------------------------
1 | .st-format-bar {
2 | top: 0;
3 | position: absolute;
4 | margin: -4.5em 0 0 0;
5 | background: #252525;
6 | opacity: 0;
7 | visibility: hidden;
8 | z-index: 10;
9 | border-radius: 0.4em;
10 | padding: 0;
11 |
12 | transition: opacity 0.2s ease-in-out;
13 | }
14 |
15 | .st-format-bar:before {
16 | content: '';
17 | display: block;
18 | position: absolute;
19 | left: 50%;
20 | top: 3.3em;
21 | width: 0;
22 | height: 0;
23 | border-left: 0.875em solid transparent;
24 | border-right: 0.875em solid transparent;
25 | border-top: 0.875em solid #252525;
26 | margin-left: -0.875em;
27 | }
28 |
29 | .st-format-bar--is-ready {
30 | visibility: visible;
31 | opacity: 1;
32 | }
33 |
34 | .st-format-btn {
35 | background: transparent;
36 | border: 0;
37 | color: #fff;
38 | font-size: 1em;
39 | line-height: 1;
40 | padding: 0.5em 0.6em;
41 | vertical-align: middle;
42 | text-align: center;
43 |
44 | &:focus {
45 | outline: 0;
46 | }
47 | }
48 | .st-format-btn .st-icon {
49 | width: 35px;
50 | height: 35px;
51 | }
52 |
53 | .st-format-btn--Heading,
54 | .st-format-btn--Quote {
55 | border-left: 1px solid #3e4245;
56 | }
57 |
58 | .st-format-btn:hover,
59 | .st-format-btn--is-active {
60 | color:$accent-color;
61 | }
62 |
63 | .st-format-btn--Unlink {
64 | text-decoration: line-through;
65 | }
66 |
67 | .st-format-btn--Heading[data-state] {
68 | position: relative;
69 |
70 | &::after {
71 | content: attr(data-state);
72 | font-weight: 800;
73 | position: absolute;
74 | right: 12px;
75 | bottom: 17px;
76 | }
77 |
78 | &[data-state="false"]::after {
79 | content: "_";
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/src/sass/inputs.scss:
--------------------------------------------------------------------------------
1 | // Reset all our input styles
2 | .st-input-label {
3 | display: block;
4 | margin-bottom: 0.5em;
5 | font-size: 13px;
6 | text-transform: uppercase;
7 | }
8 |
9 | // Generic styles
10 | @mixin st-input {
11 | font-size: inherit;
12 | margin: 0;
13 | }
14 |
15 | .st-block input[type="text"],
16 | .st-block textarea {
17 | @include st-input;
18 | }
19 |
20 | // Focus / Active styles
21 | @mixin st-input-active {
22 | outline: none;
23 | border: none;
24 | }
25 |
26 | @mixin st-input-styled {
27 | color: $base-ui-color;
28 | border: 0.1em solid #d4d4d4;
29 | padding: 0.6em;
30 | }
31 |
32 | .st-block [contenteditable="true"],
33 | .st-block [contenteditable="true"]:active,
34 | .st-block [contenteditable="true"]:focus,
35 | .st-block input[type="text"],
36 | .st-block input[type="text"]:active,
37 | .st-block input[type="text"]:focus,
38 | .st-block textarea,
39 | .st-block textarea:hover,
40 | .st-block textarea:active {
41 | @include st-input-active;
42 | }
43 |
44 | .st-block input[type="text"],
45 | .st-block input[type="text"]:active,
46 | .st-block input[type="text"]:focus {
47 | @include st-input-styled;
48 | }
49 |
50 | // Generic styles
51 | @mixin st-btn-hover {
52 | background: $accent-color;
53 | color: #fff;
54 | }
55 |
56 | @mixin st-btn {
57 | border: 0;
58 | background: $base-ui-color;
59 | border-radius: 0.2em;
60 | padding: 0.35em 1em;
61 | font-size: 1.125em;
62 | cursor: pointer;
63 | color: #fff;
64 | position: relative;
65 | z-index: 10;
66 |
67 | &:hover {
68 | @include st-btn-hover;
69 | }
70 | }
71 |
72 | .st-block__upload-container:hover .st-upload-btn {
73 | background: $accent-color;
74 | color: #fff;
75 | }
76 |
--------------------------------------------------------------------------------
/src/sass/main.scss:
--------------------------------------------------------------------------------
1 | @import '_variables';
2 | @import '_icons';
3 |
4 |
5 | @import 'patterns/ui-popup';
6 | @import 'utils';
7 | @import 'base';
8 | @import 'inputs';
9 |
10 | @import 'errors';
11 |
12 | // Block Controls
13 | @import 'block-addition';
14 | @import 'block-addition-top';
15 | @import 'block-controls';
16 | @import 'block-replacer';
17 |
18 | // Blocks
19 | @import 'block';
20 | @import 'block-positioner';
21 | @import 'block-ui';
22 |
23 | // Format Bar
24 | @import 'format-bar';
25 |
26 | // Modal
27 | @import 'modal';
28 |
--------------------------------------------------------------------------------
/src/sass/patterns/ui-popup.scss:
--------------------------------------------------------------------------------
1 | @mixin ui-popup {
2 | border: $default-border;
3 | position: absolute;
4 | z-index: 2;
5 | background: #fff;
6 | display:none;
7 |
8 | &.active {
9 | display:block;
10 | }
11 |
12 | &:after {
13 | content: '';
14 | display: block;
15 | background-color:#fff;
16 | position:absolute;
17 | top:0;
18 | right:0;
19 | bottom:0;
20 | left:0;
21 | z-index: -1;
22 | }
23 |
24 | &:before {
25 | content: '';
26 | display: block;
27 | width: 0.4em;
28 | height: 0.4em;
29 | position: absolute;
30 | left: -0.3em;
31 | top: 50%;
32 | z-index: -1;
33 | border: $default-border;
34 | background: #fff;
35 | transform: translateY(-50%) rotate(45deg);
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/src/sass/utils.scss:
--------------------------------------------------------------------------------
1 | .st-utils__hidden {
2 | border: 0;
3 | clip: rect(0 0 0 0);
4 | height: 1px;
5 | margin: -1px;
6 | overflow: hidden;
7 | padding: 0;
8 | position: absolute;
9 | width: 1px;
10 | }
11 |
--------------------------------------------------------------------------------
/src/scribe-interface.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('./lodash');
4 | var Scribe = require('scribe-editor');
5 | var config = require('./config');
6 |
7 | var scribePluginFormatterPlainTextConvertNewLinesToHTML = require('scribe-plugin-formatter-plain-text-convert-new-lines-to-html');
8 | var scribePluginLinkPromptCommand = require('./blocks/scribe-plugins/scribe-link-prompt-plugin');
9 | var scribePluginSanitizer = require('scribe-plugin-sanitizer');
10 |
11 | var sanitizeDefaults = {
12 | p: true,
13 | a: {
14 | href: true,
15 | target: '_blank',
16 | rel: true
17 | },
18 | i: true,
19 | b: true,
20 | strong: true,
21 | em: true,
22 | sup: true
23 | };
24 |
25 | module.exports = {
26 |
27 | initScribeInstance: function(el, scribeOptions, configureScribe, editorOptions) {
28 |
29 | scribeOptions = scribeOptions || {};
30 |
31 | var scribeConfig = {debug: config.scribeDebug};
32 | var tags = sanitizeDefaults;
33 |
34 | if (_.isObject(scribeOptions)) {
35 | scribeConfig = Object.assign(scribeConfig, scribeOptions);
36 | }
37 |
38 | var scribe = new Scribe(el, scribeConfig);
39 |
40 | if (scribeOptions.hasOwnProperty("tags")) {
41 | tags = Object.assign(sanitizeDefaults, scribeOptions.tags);
42 | }
43 |
44 | scribe.use(scribePluginFormatterPlainTextConvertNewLinesToHTML());
45 | scribe.use(scribePluginLinkPromptCommand({ editorOptions }));
46 | scribe.use(scribePluginSanitizer({tags: tags}));
47 |
48 | if (_.isFunction(configureScribe)) {
49 | configureScribe.call(this, scribe);
50 | }
51 |
52 | return scribe;
53 | },
54 |
55 | execTextBlockCommand: function(scribeInstance, cmdName) {
56 | if (_.isUndefined(scribeInstance)) {
57 | throw "No Scribe instance found to query command";
58 | }
59 |
60 | var cmd = scribeInstance.getCommand(cmdName);
61 | scribeInstance.el.focus();
62 | return cmd.execute();
63 | },
64 |
65 | queryTextBlockCommandState: function(scribeInstance, cmdName) {
66 | if (_.isUndefined(scribeInstance)) {
67 | throw "No Scribe instance found to query command";
68 | }
69 |
70 | var cmd = scribeInstance.getCommand(cmdName),
71 | sel = new scribeInstance.api.Selection();
72 | return sel.range && cmd.queryState();
73 | },
74 | };
75 |
--------------------------------------------------------------------------------
/src/templates/block-addition-top.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('../config');
4 |
5 | module.exports = () => {
6 | return `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | `;
16 | };
17 |
--------------------------------------------------------------------------------
/src/templates/block-addition.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('../config');
4 |
5 | module.exports = () => {
6 | return `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | `;
15 | };
16 |
--------------------------------------------------------------------------------
/src/templates/block-control.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('../config');
4 |
5 | module.exports = (block) => {
6 | return `
7 |
8 |
9 |
10 |
11 | ${block.title()}
12 |
13 | `;
14 | };
15 |
--------------------------------------------------------------------------------
/src/templates/block-replacer.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('../config');
4 |
5 | module.exports = () => {
6 | return `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | `;
15 | };
16 |
--------------------------------------------------------------------------------
/src/templates/block.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const BLOCK_ADDITION_TOP_TEMPLATE = require("./block-addition-top");
4 | const BLOCK_ADDITION_TEMPLATE = require("./block-addition");
5 | const BLOCK_REPLACER_TEMPLATE = require("./block-replacer");
6 |
7 | module.exports = (editor_html) => {
8 | return `
9 |
10 | ${ editor_html }
11 |
12 | ${ BLOCK_REPLACER_TEMPLATE() }
13 | ${ BLOCK_ADDITION_TOP_TEMPLATE() }
14 | ${ BLOCK_ADDITION_TEMPLATE() }
15 | `;
16 | };
17 |
--------------------------------------------------------------------------------
/src/templates/delete.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = () => {
4 | return `
5 |
6 |
7 | ${i18n.t('general:delete')}
8 |
9 |
10 | ${i18n.t('general:yes')}
11 |
12 |
13 | ${i18n.t('general:no')}
14 |
15 |
16 | `;
17 | };
18 |
--------------------------------------------------------------------------------
/src/templates/format-button.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = require('../config');
4 |
5 | module.exports = function({name, text, cmd, iconName}) {
6 | return `
7 |
8 |
9 |
10 |
11 |
12 | `;
13 | };
14 |
--------------------------------------------------------------------------------
/src/templates/top-controls.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const BLOCK_ADDITION_TEMPLATE = require("./block-addition");
4 |
5 | module.exports = () => {
6 | return `
7 |
8 | ${BLOCK_ADDITION_TEMPLATE()}
9 |
10 | `;
11 | };
12 |
--------------------------------------------------------------------------------
/src/vendor/array-includes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // jshint freeze: false, maxcomplexity: 11
4 |
5 | if (!Array.prototype.includes) {
6 | Array.prototype.includes = function(searchElement /*, fromIndex*/ ) {
7 | var O = Object(this);
8 | var len = parseInt(O.length) || 0;
9 | if (len === 0) {
10 | return false;
11 | }
12 | var n = parseInt(arguments[1]) || 0;
13 | var k;
14 | if (n >= 0) {
15 | k = n;
16 | } else {
17 | k = len + n;
18 | if (k < 0) {k = 0;}
19 | }
20 | var currentElement;
21 | while (k < len) {
22 | currentElement = O[k];
23 | if (searchElement === currentElement ||
24 | (searchElement !== searchElement && currentElement !== currentElement)) {
25 | return true;
26 | }
27 | k++;
28 | }
29 | return false;
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/src/vendor/custom-event.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | if ( typeof window.CustomEvent === "function" ) return false;
5 |
6 | function CustomEvent ( event, params ) {
7 | params = params || { bubbles: false, cancelable: false, detail: undefined };
8 | var evt = document.createEvent( 'CustomEvent' );
9 | evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
10 | return evt;
11 | }
12 |
13 | CustomEvent.prototype = window.Event.prototype;
14 |
15 | window.CustomEvent = CustomEvent;
16 | }());
--------------------------------------------------------------------------------
/src/vendor/dom-shims.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | // The IE's "contains" method does not work when a text node is passed as argument.
5 | if (/Trident/.test(navigator.userAgent)) {
6 | Object.defineProperty(HTMLElement.prototype, 'contains', {
7 | writable: true,
8 | enumerable: false,
9 | configurable: true,
10 | value: function(node) {
11 | if (!node) return false;
12 | return this === node || !!(this.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY);
13 | }
14 | });
15 | }
16 |
17 | // IE does not implement `Document.prototype.contains`
18 | if (typeof Document.prototype.contains !== 'function') {
19 | Object.defineProperty(Document.prototype, 'contains', {
20 | writable: true,
21 | enumerable: false,
22 | configurable: true,
23 | value: function(el) {
24 | if (!el) return false;
25 | return this.documentElement.contains(el);
26 | }
27 | });
28 | }
29 |
30 | // IE does not implement `Range.prototype.intersectsNode`
31 | if (typeof Range.prototype.intersectsNode !== 'function') {
32 | Object.defineProperty(Range.prototype, 'intersectsNode', {
33 | writable: true,
34 | enumerable: false,
35 | configurable: true,
36 | value: function(node) {
37 | if (!node) {
38 | throw new TypeError("Failed to execute 'intersectsNode' on 'Range': 1 argument required, but only 0 present.");
39 | }
40 | if (this.startContainer.ownerDocument !== node.ownerDocument) return false;
41 | if (!node.parentNode) return true;
42 |
43 | var targetRange = document.createRange();
44 | targetRange.selectNode(node);
45 | var startEnd = this.compareBoundaryPoints(Range.START_TO_END, targetRange);
46 | var endStart = this.compareBoundaryPoints(Range.END_TO_START, targetRange);
47 |
48 | return startEnd === 1 && endStart === -1;
49 | }
50 | });
51 | }
52 | }());
--------------------------------------------------------------------------------
/src/vendor/ie-classlist-toggle.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | "use strict";
3 |
4 | var testElement = document.createElement("_");
5 |
6 | testElement.classList.add("c1", "c2");
7 |
8 | // Polyfill for IE 10/11 and Firefox <26, where classList.add and
9 | // classList.remove exist but support only one argument at a time.
10 | if (!testElement.classList.contains("c2")) {
11 | var createMethod = function(method) {
12 | var original = DOMTokenList.prototype[method];
13 |
14 | DOMTokenList.prototype[method] = function(token) {
15 | var i, len = arguments.length;
16 |
17 | for (i = 0; i < len; i++) {
18 | token = arguments[i];
19 | original.call(this, token);
20 | }
21 | };
22 | };
23 | createMethod('add');
24 | createMethod('remove');
25 | }
26 |
27 | testElement.classList.toggle("c3", false);
28 |
29 | // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
30 | // support the second argument.
31 | if (testElement.classList.contains("c3")) {
32 | var _toggle = DOMTokenList.prototype.toggle;
33 |
34 | DOMTokenList.prototype.toggle = function(token, force) {
35 | if (1 in arguments && !this.contains(token) === !force) {
36 | return force;
37 | } else {
38 | return _toggle.call(this, token);
39 | }
40 | };
41 |
42 | }
43 |
44 | testElement = null;
45 | }());
--------------------------------------------------------------------------------
/used_underscore_functions.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | egrep -Rho "\b_\.\w+" src | sort | uniq
6 |
--------------------------------------------------------------------------------
/website/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.5.1
2 |
--------------------------------------------------------------------------------
/website/Gemfile:
--------------------------------------------------------------------------------
1 | # If you have OpenSSL installed, we recommend updating
2 | # the following line to use "https"
3 | source 'http://rubygems.org'
4 |
5 | gem "middleman", "~>4.3.0"
6 | gem 'middleman-gh-pages'
7 |
8 | # Live-reloading plugin
9 | gem "middleman-livereload"
10 |
11 | gem "redcarpet"
12 |
13 | # For faster file watcher updates:
14 | # gem "wdm", "~> 0.1.0") # Windows
15 |
16 | # Cross-templating language block fix for Ruby 1.8
17 | platforms :mri_18 do
18 | gem "ruby18_source_location"
19 | end
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # Sir Trevor JS Website
2 |
3 | The website is created with Middleman and deployed to Github pages.
4 |
5 | To update the website you're going to need Ruby and bundler installed. You can read a full guide to [getting started with Middleman here](http://12devs.co.uk/articles/204/).
6 |
7 | The `build` folder points to an orphaned git branch that goes up to Github pages on the `gh-pages` branch.
8 |
9 | First off init the submodule (assuming you're in the website directory)
10 |
11 | cd ../ && git submodule init
12 |
13 | Next, make changes to the website **in the `source` directory** and commit these changes. Then, when you're ready to push run:
14 |
15 | bundle exec middleman build
16 |
17 | Change into the `website/build` directory push your changes:
18 |
19 | git push origin gh-pages
20 |
21 | Then, switch back to the website folder and commit your changes:
22 |
23 | cd ../ && git push origin master
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/website/Rakefile:
--------------------------------------------------------------------------------
1 | require 'middleman-gh-pages'
--------------------------------------------------------------------------------
/website/config.rb:
--------------------------------------------------------------------------------
1 | ###
2 | # Compass
3 | ###
4 |
5 | # Change Compass configuration
6 | # compass_config do |config|
7 | # config.output_style = :compact
8 | # end
9 |
10 | ###
11 | # Page options, layouts, aliases and proxies
12 | ###
13 |
14 | # Per-page layout changes:
15 | #
16 | # With no layout
17 | # page "/path/to/file.html", :layout => false
18 | #
19 | # With alternative layout
20 | # page "/path/to/file.html", :layout => :otherlayout
21 | #
22 | # A path which all have the same layout
23 | # with_layout :admin do
24 | # page "/admin/*"
25 | # end
26 |
27 | # Proxy pages (http://middlemanapp.com/dynamic-pages/)
28 | # proxy "/this-page-has-no-template.html", "/template-file.html", :locals => {
29 | # :which_fake_page => "Rendering a fake page with a local variable" }
30 |
31 | ###
32 | # Helpers
33 | ###
34 |
35 | # Automatic image dimensions on image_tag helper
36 | # activate :automatic_image_sizes
37 |
38 | # Reload the browser automatically whenever files change
39 | # activate :livereload
40 |
41 | # Methods defined in the helpers block are available in templates
42 | # helpers do
43 | # def some_helper
44 | # "Helping"
45 | # end
46 | # end
47 |
48 | set :css_dir, 'stylesheets'
49 | set :js_dir, 'javascripts'
50 | set :images_dir, 'images'
51 | set :relative_links, true
52 |
53 | activate :external_pipeline,
54 | name: :webpack,
55 | command: build? ?
56 | "./node_modules/webpack/bin/webpack.js --bail -p" :
57 | "./node_modules/webpack/bin/webpack.js --watch -d --progress --color",
58 | source: ".tmp/dist",
59 | latency: 1
60 |
61 | set :markdown_engine, :redcarpet
62 | set :markdown,
63 | :hard_wrap => true,
64 | :fenced_code_blocks => true,
65 | :smartypants => true,
66 | :layout_engine => :erb,
67 | :autolink => true
68 |
69 | # Build-specific configuration
70 | configure :build do
71 | activate :minify_css
72 | activate :minify_javascript
73 | #activate :asset_hash
74 | activate :relative_assets
75 |
76 | # Or use a different image path
77 | # set :http_path, "/Content/images/"
78 | end
79 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sir-trevor-js-docs",
3 | "version": "1.0.0",
4 | "description": "The website is created with Middleman and deployed to Github pages.",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "dev": "webpack --mode development",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "@webpack-cli/migrate": "^0.1.5",
14 | "mini-css-extract-plugin": "^0.5.0",
15 | "node-sass": "^4.12.0",
16 | "sass-loader": "^7.1.0",
17 | "sir-trevor": "^0.8.2",
18 | "webpack": "^4.28.4",
19 | "webpack-cli": "^3.2.1"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/website/source/blank.html.erb:
--------------------------------------------------------------------------------
1 | ---
2 | title: Blank | Sir Trevor JS | Made by Many
3 | layout: example
4 | ---
5 |
6 | <%= partial "partials/header" %>
7 |
8 |
13 |
14 | <%= partial "partials/footer" %>
--------------------------------------------------------------------------------
/website/source/docs.html.erb:
--------------------------------------------------------------------------------
1 | ---
2 | title: Docs | Sir Trevor JS | Made by Many
3 | ---
4 |
5 | <%= partial "partials/header" %>
6 |
7 |
8 | <%= partial "partials/docs/nav" %>
9 |
10 |
11 | <%= partial "partials/docs/1" %>
12 | <%= partial "partials/docs/2" %>
13 | <%= partial "partials/docs/3" %>
14 | <%= partial "partials/docs/4" %>
15 | <%= partial "partials/docs/5" %>
16 |
17 |
18 |
19 | <%= partial "partials/footer" %>
--------------------------------------------------------------------------------
/website/source/example.html.erb:
--------------------------------------------------------------------------------
1 | ---
2 | title: Example | Sir Trevor JS | Made by Many
3 | layout: example
4 | ---
5 |
6 | <%= partial "partials/header" %>
7 |
8 |
9 |
10 | {"data":[{"type":"text","data":{"text":"Hello, I'm Sir Trevor .
","format":"html"}},{"type":"text","data":{"text":"Create some new blocks and see what I can do .
","format":"html"}},{"type":"video","data":{"source":"youtube","remote_id":"hcFLFpmc4Pg"}}]}
11 |
12 |
13 |
14 | <%= partial "partials/footer" %>
--------------------------------------------------------------------------------
/website/source/images/itv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madebymany/sir-trevor-js/8b5807a94f6e4486ee00ed815f898c3eb72ef19d/website/source/images/itv.png
--------------------------------------------------------------------------------
/website/source/images/mxm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madebymany/sir-trevor-js/8b5807a94f6e4486ee00ed815f898c3eb72ef19d/website/source/images/mxm.png
--------------------------------------------------------------------------------
/website/source/images/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madebymany/sir-trevor-js/8b5807a94f6e4486ee00ed815f898c3eb72ef19d/website/source/images/screen.png
--------------------------------------------------------------------------------
/website/source/images/sir-trevor-icons.svg:
--------------------------------------------------------------------------------
1 | ../../node_modules/sir-trevor/build/sir-trevor-icons.svg
--------------------------------------------------------------------------------
/website/source/images/sir-trevor.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madebymany/sir-trevor-js/8b5807a94f6e4486ee00ed815f898c3eb72ef19d/website/source/images/sir-trevor.gif
--------------------------------------------------------------------------------
/website/source/javascripts/all.js:
--------------------------------------------------------------------------------
1 | require('../stylesheets/all.scss');
2 | require('jquery.typer');
3 |
4 | $(function(){
5 | var $typer = $('.typer-target');
6 |
7 | $.typer.options.highlightSpeed = 10;
8 | $.typer.options.typeSpeed = 75;
9 | $.typer.options.typeDelay = 75;
10 |
11 | $typer.typer();
12 | });
13 |
--------------------------------------------------------------------------------
/website/source/javascripts/example.js:
--------------------------------------------------------------------------------
1 | var SirTrevor = require('../../node_modules/sir-trevor');
2 | require('../stylesheets/example.scss');
3 |
4 | (function() {
5 |
6 | SirTrevor.setDefaults({
7 | iconUrl: document.body.getAttribute('icon-url')
8 | });
9 |
10 | new SirTrevor.Editor({
11 | el: document.querySelector(".js-sir-trevor-instance"),
12 | blockTypes: ["Text", "List", "Video", "Quote", "Iframe"],
13 | defaultType: ['Text']
14 | });
15 | })();
--------------------------------------------------------------------------------
/website/source/layouts/example.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | <%= current_page.data.title %>
12 | <%= stylesheet_link_tag "example" %>
13 |
14 | ">
15 |
16 | <%= yield %>
17 |
18 | <%= javascript_include_tag "example" %>
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/website/source/layouts/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | <%= current_page.data.title %>
12 | <%= stylesheet_link_tag "normalize", "all" %>
13 |
14 |
15 |
16 | <%= yield %>
17 |
18 |
19 | <%= javascript_include_tag "all" %>
20 |
21 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/website/source/partials/_footer.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/source/partials/_header.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/source/partials/docs/_2.html.markdown:
--------------------------------------------------------------------------------
1 |
2 | # Options
3 |
4 | The editor accepts the following options. Options are passed to the editor on initialisation.
5 |
6 | **`blockTypes`**
7 | Specify an array of block types to use with the editor.
8 | *Defaults to all block types*.
9 |
10 | ```js
11 | {
12 | blockTypes: ["Text", "Tweet", "Image"]
13 | }
14 | ```
15 |
16 | **`defaultType`**
17 | Specify a default block to start the editor with.
18 | *Defaults to no block*.
19 |
20 | ```js
21 | {
22 | defaultType: "Text"
23 | }
24 | ```
25 |
26 | **`blockLimit`**
27 | Set an overall total number of blocks that can be displayed.
28 | *Defaults to 0 (infinite)*.
29 |
30 | ```js
31 | {
32 | blockLimit: 1
33 | }
34 | ```
35 |
36 | **`blockTypeLimits`**
37 | Set a limit on the number of blocks that can be displayed by its type.
38 | *Defaults to {}*.
39 |
40 | ```js
41 | {
42 | blockTypeLimits: {
43 | "Text": 2,
44 | "Image": 1
45 | }
46 | }
47 | ```
48 |
49 | **`required`**
50 | Specify which block types are required for validatation.
51 | *Defaults to none*.
52 |
53 | ```js
54 | {
55 | required: ["Text", "Image"]
56 | }
57 | ```
58 |
59 | **`onEditorRender`**
60 | Call a function once the Editor has rendered.
61 | *Defaults to undefined*.
62 |
63 | ```js
64 | {
65 | onEditorRender: function() {
66 | alert('Do something');
67 | }
68 | }
69 | ```
70 |
71 | **`headingLevels`**
72 | Set the heading levels that can be set with the heading block
73 | *Defaults to [2]*.
74 |
75 | ```js
76 | {
77 | headingLevels: [1,2,3,4,5,6]
78 | }
79 | ```
80 |
81 |
82 | **`defaultHeadingLevel`**
83 | Set the default heading level when a heading block is added
84 | *Defaults to 2*.
85 |
86 | ```js
87 | {
88 | defaultHeadingLevel: 2
89 | }
90 | ```
91 |
92 |
93 | ## Block Options
94 |
95 | You can set specific options for blocks by using the `setBlockOptions` method.
96 |
97 | ```js
98 | SirTrevor.setBlockOptions('Tweet', {
99 | someValue: true
100 | });
101 | ```
102 |
103 |
104 | ## Global Options
105 |
106 | You can also set options *globally* for all Sir Trevor instances using the `setDefaults` method.
107 |
108 | ```js
109 | SirTrevor.setDefaults({
110 | required: ["Text"]
111 | });
112 | ```
113 |
--------------------------------------------------------------------------------
/website/source/partials/docs/_5.html.markdown:
--------------------------------------------------------------------------------
1 |
2 | # Text editing
3 |
4 |
5 | ## Scribe
6 |
7 | Sir Trevor uses [Scribe](https://github.com/guardian/scribe) for text editing. The document that is available can be found here https://github.com/guardian/scribe/wiki.
8 |
9 |
10 | ## Text Formatting
11 |
12 | The main reason for using Scribe is to provide text formatting and make content editable cross browser compatible.
13 |
14 | Formatting has been enabled using various plugins that can be found here:
15 | https://github.com/madebymany/sir-trevor-js/blob/master/src/scribe-interface.js
16 |
17 | To add your own formatting you'll want to [install a plugin](https://github.com/guardian/scribe/wiki/Plugins) or create your own.
18 |
19 |
20 | ## Create a Scribe plugin
21 |
22 | If you want to create your own plugins then follow the steps below:
23 |
24 | **Step 1.**
25 |
26 | Define your toolbar commands
27 | https://github.com/madebymany/sir-trevor-js/blob/master/src/config.js#L66-L112
28 |
29 | **Step 2.**
30 |
31 | Modify the linkPrompt plugin
32 | https://github.com/guardian/scribe-plugin-link-prompt-command
33 |
34 | The main file is
35 | https://github.com/guardian/scribe-plugin-link-prompt-command/blob/master/src/scribe-plugin-link-prompt-command.js
36 |
37 | You'll find this assigns `scribe.commands.linkPrompt` which is the `cmd` set in the config.
38 | So what you'll do is write your own plugin and then assign it to something like `scribe.commands.customLinkPrompt` then modify the `formatBar.commands` in your config override.
39 |
40 | **Step 3.**
41 |
42 | You'll need to find a way to call `scribe.use`for the plugin.
43 |
44 | If it needs to be on all blocks then you'll need to patch something in:
45 | https://github.com/madebymany/sir-trevor-js/blob/master/src/scribe-interface.js#L43
46 |
47 | Per block you can define `configureScribe`
48 | https://github.com/madebymany/sir-trevor-js/blob/master/src/blocks/text.js#L26
49 |
--------------------------------------------------------------------------------
/website/source/partials/docs/_nav.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/source/setting-up-image-uploading.html.erb:
--------------------------------------------------------------------------------
1 | ---
2 | title: How to set up image uploading | Docs | Sir Trevor JS | Made by Many
3 | ---
4 |
5 | <%= partial "partials/header" %>
6 |
7 |
8 | <%= partial "partials/docs/nav" %>
9 |
10 |
11 | <%= partial "partials/docs/uploads" %>
12 |
13 |
14 |
15 | <%= partial "partials/footer" %>
16 |
--------------------------------------------------------------------------------
/website/source/stylesheets/all.scss:
--------------------------------------------------------------------------------
1 | @import 'icons';
2 | @import 'modules/variables';
3 | @import 'modules/typography';
4 | @import 'modules/layout';
5 | @import 'modules/header';
6 | @import 'modules/docs';
7 | @import 'base';
8 | @import 'highlight';
--------------------------------------------------------------------------------
/website/source/stylesheets/example.scss:
--------------------------------------------------------------------------------
1 | @import "normalize";
2 |
3 | @import 'modules/variables';
4 | @import 'modules/typography';
5 | @import 'modules/layout';
6 | @import 'modules/header';
7 | @import 'base';
8 |
9 | @import "../../node_modules/sir-trevor/build/sir-trevor";
10 |
--------------------------------------------------------------------------------
/website/source/stylesheets/modules/_docs.scss:
--------------------------------------------------------------------------------
1 | code, pre {
2 | background-color: darken(#fff, 5%);
3 | color: lighten(#000, 20%);
4 | font-size: 16px;
5 | }
6 |
7 | pre {
8 | padding: .5em;
9 | border-left: 3px solid darken(#f2f2f2, 5%);
10 | margin-bottom: 20px;
11 | }
12 |
13 | .docs {
14 | overflow: hidden;
15 | }
16 |
17 | .docs__content {
18 | font-size: 18px;
19 | width: 75%;
20 | float: left;
21 | padding-bottom: 30px;
22 |
23 | h1, h2, h3 {
24 | color: lighten(#000, 20%);
25 | }
26 |
27 | h1 {
28 | padding-bottom: 10px;
29 | border-bottom: 1px solid #ccc;
30 | }
31 | }
32 |
33 | .docs__navigation {
34 | width: 20%;
35 | margin: 40px 5% 0 0;
36 | float: left;
37 |
38 | ul {
39 | margin: 0; padding: 0;
40 | list-style: none;
41 | }
42 |
43 | ul ul a {
44 | padding-left: 20px;
45 | font-weight: normal;
46 | }
47 |
48 | a {
49 | display: block;
50 | padding: .3em .5em;
51 | font-size: 18px;
52 | border-bottom: 1px solid #ccc;
53 | color: #333;
54 | text-decoration: none;
55 | font-weight: bold;
56 |
57 | &:hover,
58 | &:focus {
59 | background-color: #f2f2f2;
60 | }
61 | }
62 | }
63 |
64 | @include breakpoint($medium-break) {
65 | .docs__navigation {
66 | display: none;
67 | }
68 |
69 | .docs__content {
70 | float: none;
71 | width: auto;
72 |
73 | h1 { font-size: 1.5em; }
74 | }
75 | }
--------------------------------------------------------------------------------
/website/source/stylesheets/modules/_header.scss:
--------------------------------------------------------------------------------
1 | .site-logo {
2 | display: inline-block;
3 | margin: px-to-em(20px) 0;
4 | padding-left: 1.25em;
5 | font-size: px-to-em(28px);
6 | color: #fff;
7 | text-decoration: none;
8 | font-weight: 600;
9 | position: relative;
10 |
11 | &:before {
12 | color: $accent-color;
13 | position: absolute;
14 | left: 0; top: .15em;
15 | }
16 |
17 | &:hover {
18 | color: $accent-color;
19 | }
20 |
21 | @include breakpoint($small-break) {
22 | padding-left: 1.1em;
23 | }
24 | }
25 |
26 | .site-header__info-container {
27 | padding: px-to-em(20px) 0;
28 | position: relative;
29 | }
30 |
31 | .site-header__video {
32 | position: absolute;
33 | bottom: 0;
34 | right: 0;
35 |
36 | @include breakpoint(px-to-em(1400px)) {
37 | display: none;
38 | }
39 | }
40 |
41 | .site-nav {
42 | margin: 1.75em 0 0 0;
43 | float: right;
44 | }
45 |
46 | .site-nav__items {
47 | margin: 0; padding: 0;
48 | list-style: none;
49 | }
50 |
51 | .site-nav__item {
52 | margin-left: 1.5em;
53 | float: left;
54 |
55 | a {
56 | color: $accent-color;
57 | text-decoration: none;
58 | }
59 |
60 | @include breakpoint($small-break) {
61 | margin-left: .5em;
62 | }
63 | }
--------------------------------------------------------------------------------
/website/source/stylesheets/modules/_layout.scss:
--------------------------------------------------------------------------------
1 | .site-width {
2 | width: $large-break;
3 | margin: 0pt auto;
4 | overflow: hidden;
5 |
6 | @include breakpoint($large-break + 20) {
7 | margin: 0 5%;
8 | width: auto;
9 | }
10 | }
11 |
12 | .example-container {
13 | padding: 50px 0;
14 | }
--------------------------------------------------------------------------------
/website/source/stylesheets/modules/_typography.scss:
--------------------------------------------------------------------------------
1 | @import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600);
2 |
3 | html {
4 | font-family: 'Source Sans Pro', sans-serif;
5 | font-size: 100%;
6 | line-height: 1.3;
7 | -webkit-font-smoothing: antialiased;
8 | }
9 |
10 | body {
11 | font-size: $base-font-size;
12 | color: #707477;
13 |
14 | @include breakpoint($medium-break) {
15 | font-size: 16px;
16 | }
17 | }
--------------------------------------------------------------------------------
/website/source/stylesheets/modules/_variables.scss:
--------------------------------------------------------------------------------
1 | $base-font-size: 20px;
2 |
3 | @function px-to-em($px, $base: $base-font-size) {
4 | @return ($px / $base) * 1em;
5 | }
6 |
7 | $large-break: px-to-em(960px);
8 | $medium-break: px-to-em(760px);
9 | $small-break: px-to-em(480px);
10 |
11 | $accent-color: #34E0C2;
12 | $dark-grey: #42474B;
13 |
14 | @mixin breakpoint($point) {
15 | @media (max-width: $point) { @content; }
16 | }
17 |
18 | @function grid-calc($n, $g: 12) {
19 | @return ($n / $g) * 100%;
20 | }
21 |
22 | @for $i from 1 through 12 {
23 | %grid-#{$i}-of-12 {
24 | @extend %grid;
25 | width: grid-calc($i);
26 | }
27 | }
28 |
29 | %grid {
30 | float: left;
31 | }
32 |
33 | %grid-half {
34 | @extend %grid;
35 | width: 50%;
36 |
37 | @include breakpoint($medium-break) {
38 | float: none;
39 | width: auto;
40 | }
41 | }
42 |
43 | %grid-five {
44 | @extend %grid;
45 | width: grid-calc(3,15);
46 |
47 | @include breakpoint($medium-break) {
48 | width: grid-calc(4);
49 | }
50 | }
51 |
52 | %grid-one-third {
53 | @extend %grid;
54 | width: grid-calc(4);
55 |
56 | @include breakpoint($medium-break) {
57 | float: none;
58 | width: auto;
59 | }
60 | }
61 |
62 | %grid-two-thirds {
63 | @extend %grid;
64 | width: grid-calc(8);
65 |
66 | @include breakpoint($medium-break) {
67 | float: none;
68 | width: auto;
69 | }
70 | }
71 |
72 | p a,
73 | li a {
74 | color: $accent-color;
75 | }
--------------------------------------------------------------------------------
/website/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 |
3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
4 |
5 | module.exports = {
6 | entry: {
7 | all: "./source/javascripts/all.js",
8 | example: "./source/javascripts/example.js"
9 | },
10 |
11 | module: {
12 | rules: [
13 | {
14 | test: /\.js$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: "babel-loader"
18 | }
19 | },
20 | {
21 | test: /\.scss$/,
22 | use: [
23 | "style-loader",
24 | {
25 | loader: MiniCssExtractPlugin.loader,
26 | options: {
27 | publicPath: "../"
28 | }
29 | },
30 | "css-loader",
31 | "sass-loader"
32 | ]
33 | },
34 | {
35 | test: /\.(jpe?g|png|gif|mp3)$/i,
36 | use: [
37 | {
38 | loader: "file-loader",
39 | options: {
40 | name: "images/[name].[ext]"
41 | }
42 | }
43 | ]
44 | }
45 | ]
46 | },
47 |
48 | resolve: {
49 | modules: [__dirname + "/source/javascripts"]
50 | },
51 |
52 | output: {
53 | path: __dirname + "/.tmp/dist",
54 | filename: "javascripts/[name].js"
55 | },
56 |
57 | plugins: [
58 | new MiniCssExtractPlugin({
59 | // Options similar to the same options in webpackOptions.output
60 | // both options are optional
61 | filename: "stylesheets/[name].css",
62 | chunkFilename: "[id].css"
63 | })
64 | ]
65 | };
66 |
--------------------------------------------------------------------------------