├── LICENSE.txt ├── dist ├── assets │ ├── css │ │ └── tenon.css │ ├── img │ │ ├── gears.svg │ │ └── tenon.png │ └── js │ │ ├── .eslintrc │ │ ├── highlight.js │ │ └── tenon.js └── views │ └── partials │ └── browser │ └── panel-tenon.nunj ├── docs └── fractal-tenon.gif ├── index.js └── package.json /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2015 Joschi Kuphal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /dist/assets/css/tenon.css: -------------------------------------------------------------------------------- 1 | .tenon { 2 | width: 100%; 3 | } 4 | 5 | .tenon-controls button { 6 | color: #535363; 7 | background: #fff url(../img/tenon.png) .5rem center no-repeat; 8 | background-size: 1.5rem; 9 | border: 1px solid rgba(83, 83, 99, 0.25); 10 | padding: .5rem .75rem .5rem 2.75rem; 11 | } 12 | 13 | .tenon-controls button:hover { 14 | border-color: rgba(83, 83, 99, .5); 15 | } 16 | 17 | .tenon-controls button[disabled] { 18 | background-image: url(../img/gears.svg); 19 | color: rgba(83, 83, 99, .5); 20 | } 21 | 22 | .tenon-history { 23 | padding: 0 .5rem; 24 | } 25 | 26 | .tenon-results { 27 | width: 100%; 28 | } 29 | 30 | .tenon-results:empty { 31 | display: none; 32 | } 33 | 34 | .tenon-results ol { 35 | list-style-type: decimal; 36 | list-style-position: inside; 37 | } 38 | 39 | .tenon-results .tenon-success, 40 | .tenon-results li { 41 | border-top: 1px solid rgba(83, 83, 99, 0.25); 42 | margin-top: .75rem; 43 | padding: 1rem 0 .25rem; 44 | } 45 | 46 | .tenon-results h3 { 47 | display: inline-block; 48 | } 49 | 50 | .tenon-results code { 51 | display: block; 52 | margin: .5rem 0; 53 | } 54 | 55 | .tenon-results pre { 56 | background-color: #fff; 57 | border: 1px solid rgba(83, 83, 99, 0.25); 58 | padding: .5rem; 59 | } 60 | 61 | .tenon-priority { 62 | display: inline-block; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /dist/assets/img/gears.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/img/tenon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tollwerk/fractal-tenon/73f5aad9636998f6df2ff0d67f703a57c4a01ceb/dist/assets/img/tenon.png -------------------------------------------------------------------------------- /dist/assets/js/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 5, 5 | "sourceType": "script", 6 | "ecmaFeatures": { 7 | "globalReturn": false 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /dist/assets/js/highlight.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""+t(e)+">"}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/,r:0,c:[{cN:"attr",b:e,r:0},{b:/=\s*/,r:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"?",e:"/?>",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}}); -------------------------------------------------------------------------------- /dist/assets/js/tenon.js: -------------------------------------------------------------------------------- 1 | /* global hljs */ 2 | /* eslint-disable no-useless-escape */ 3 | 4 | (function (d) { 5 | var currentScript = "currentScript", 6 | scripts = d.getElementsByTagName('script'); // Live NodeList collection 7 | 8 | // If browser needs currentScript polyfill, add get currentScript() to the document object 9 | if (!(currentScript in d)) { 10 | Object.defineProperty(d, currentScript, { 11 | get: function () { 12 | 13 | // IE 6-10 supports script readyState 14 | // IE 10+ support stack trace 15 | try { 16 | throw new Error(); 17 | } catch (err) { 18 | 19 | // Find the second match for the "at" string to get file src url from stack. 20 | // Specifically works with the format of stack traces in IE. 21 | var i, res = ((/.*at [^\(]*\((.*):.+:.+\)$/ig).exec(err.stack) || [false])[1]; 22 | 23 | // For all scripts on the page, if src matches or if ready state is interactive, return the script tag 24 | for (i in scripts) { 25 | if (scripts[i].src == res || scripts[i].readyState == "interactive") { 26 | return scripts[i]; 27 | } 28 | } 29 | 30 | // If no match, return null 31 | return null; 32 | } 33 | } 34 | }); 35 | } 36 | 37 | /** 38 | * Tenon constructor 39 | * 40 | * @param {Element} currentScript Current script 41 | * @constructor 42 | */ 43 | function TenonAPIClient(currentScript) { 44 | var path = currentScript.getAttribute('src', 2).split('/'); 45 | this.apiUrl = 'https://tenon.io/api/'; 46 | this.apiKey = path[2]; 47 | this.publicUrl = atob(path[3]); 48 | } 49 | 50 | /** 51 | * Test the current component against Tenon 52 | * 53 | * @param {String} relUrl Relative component URL 54 | */ 55 | TenonAPIClient.prototype.test = function (relUrl) { 56 | this.button = d.querySelector('.tenon-controls button'); 57 | this.history = d.querySelector('.tenon-history'); 58 | this.results = d.querySelector('.tenon-results'); 59 | 60 | this.button.disabled = true; 61 | this.clearElement(this.results); 62 | 63 | var data = new FormData(); 64 | data.append('key', this.apiKey); 65 | data.append('url', this.publicUrl + relUrl); 66 | data.append('viewPortWidth', d.querySelector('.Preview-iframe').clientWidth); 67 | data.append('fragment', '1'); 68 | data.append('store', '1'); 69 | 70 | // API call 71 | var that = this; 72 | fetch(this.apiUrl, { method: 'post', body: data }) 73 | .then(function (response) { 74 | return response.json(); 75 | }) 76 | .then(this.showResult.bind(this)) 77 | .catch(function () { 78 | that.button.disabled = false; 79 | that.results.appendChild(that.createElement('p', { 'class': 'tenon-error' }, 'An error occurred. Please view the results on tenon.io')); 80 | }); 81 | }; 82 | 83 | /** 84 | * Show the tenon test results 85 | * 86 | * @param {Object} data Test results 87 | */ 88 | TenonAPIClient.prototype.showResult = function (data) { 89 | this.button.disabled = false; 90 | 91 | // Create the history link 92 | if (data.resultUrl) { 93 | this.clearElement(this.history).appendChild(this.createElement('a', { 94 | href: data.resultUrl, 95 | target: '_blank', 96 | }, 'View results on tenon.io')); 97 | } 98 | 99 | // Create the result list 100 | var results; 101 | var issues = this.filterIssues(data.resultSet); 102 | if (issues.length) { 103 | results = this.createElement('ol', { class: 'tenon-issues' }); 104 | for (var i = 0; i < issues.length; ++i) { 105 | this.addIssue(results, issues[i]); 106 | } 107 | } else { 108 | results = this.createElement('p', { class: 'tenon-success' }, 'Congratulations! There were no issues found with this component.'); 109 | } 110 | this.results.appendChild(results); 111 | }; 112 | 113 | /** 114 | * Filter issues 115 | * 116 | * @param {Array} issues Issues 117 | * @return {Array} Filtered issus 118 | */ 119 | TenonAPIClient.prototype.filterIssues = function (issues) { 120 | return issues.filter(function (issue) { 121 | return issue.bpID ? ([ 122 | 28 // Page level heading 123 | ].indexOf(issue.bpID) < 0) : true; 124 | }); 125 | }; 126 | 127 | /** 128 | * Add an issue to the result list 129 | * 130 | * @param {Element} results Result list 131 | * @param {Object} data Issue 132 | */ 133 | TenonAPIClient.prototype.addIssue = function (results, data) { 134 | var issue = this.createElement('li'); 135 | 136 | // Add title 137 | issue.appendChild(this.createElement('h3', null, data.errorTitle)); 138 | issue.appendChild(this.createElement('div', { class: 'tenon-priority Tree-title' }, 'Priority ' + data.priority + '%')); 139 | 140 | // Add code snippet 141 | var code = this.createElement('code'); 142 | var pre = code.appendChild(this.createElement('pre', null)); 143 | pre.innerHTML = data.errorSnippet; 144 | hljs.highlightBlock(pre); 145 | issue.appendChild(code); 146 | 147 | // Add error description 148 | var descr = this.createElement('p'); 149 | descr.innerHTML = data.errorDescription; 150 | issue.appendChild(descr); 151 | 152 | results.appendChild(issue); 153 | }; 154 | 155 | /** 156 | * Create and return an HTML element 157 | * 158 | * @param {String} element Element name 159 | * @param {Object} attributes Attributes 160 | * @param {String} content Text content 161 | * @return {Element} Element 162 | */ 163 | TenonAPIClient.prototype.createElement = function (element, attributes, content) { 164 | var el = d.createElement(element); 165 | var attrs = attributes || {}; 166 | for (var a in attrs) { 167 | el.setAttribute(a, attrs[a]); 168 | } 169 | el.textContent = content || ''; 170 | return el; 171 | }; 172 | 173 | /** 174 | * Clear an element 175 | * 176 | * @param {Element} element Element 177 | * @return {Element} 178 | */ 179 | TenonAPIClient.prototype.clearElement = function (element) { 180 | while (element.firstChild) { 181 | element.removeChild(element.firstChild); 182 | } 183 | return element; 184 | }; 185 | 186 | window.Tenon = new TenonAPIClient(d.currentScript); 187 | }(document)); 188 | -------------------------------------------------------------------------------- /dist/views/partials/browser/panel-tenon.nunj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test with Tenon 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/fractal-tenon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tollwerk/fractal-tenon/73f5aad9636998f6df2ff0d67f703a57c4a01ceb/docs/fractal-tenon.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require, import/no-dynamic-require, no-console */ 2 | const path = require('path'); 3 | const btoa = require('btoa'); 4 | 5 | function setupTenon(theme, opts) { 6 | const config = opts || {}; 7 | 8 | // Test for the theme 9 | if ((typeof theme !== 'object') || (typeof theme.options !== 'function')) { 10 | throw new Error('Please provide a valid Fractal theme as first argument'); 11 | } 12 | 13 | // Test for the Tenon API key 14 | if (!('apiKey' in config)) { 15 | throw new Error('Please provide a Tenon API key'); 16 | } 17 | 18 | // Test for the public URL 19 | if (!('publicUrl' in config)) { 20 | throw new Error('Please provide the public URL of your Fractal instance'); 21 | } 22 | 23 | // const scriptMount = `/tenon/${config.apiKey}/${encodeURIComponent(config.publicUrl)}`; 24 | const scriptMount = `/tenon/${config.apiKey}/${btoa(config.publicUrl)}`; 25 | theme.addLoadPath(path.resolve(__dirname, 'dist', 'views')); 26 | theme.addStatic(path.resolve(__dirname, 'dist', 'assets'), scriptMount); 27 | 28 | const options = theme.options(); 29 | options.scripts = [].concat(...(options.scripts || ['default'])).concat([ 30 | `/tenon/${config.apiKey}/${btoa(config.publicUrl)}/js/tenon.js`, 31 | `/tenon/${config.apiKey}/${btoa(config.publicUrl)}/js/highlight.js`, 32 | ]); 33 | options.styles = [].concat(...(options.styles || ['default'])).concat(`/tenon/${config.apiKey}/${btoa(config.publicUrl)}/css/tenon.css`); 34 | if (options.panels.indexOf('tenon') < 0) { 35 | options.panels.push('tenon'); 36 | } 37 | theme.options(options); 38 | } 39 | 40 | module.exports = setupTenon; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fractal-tenon", 3 | "version": "1.0.0", 4 | "description": "Fractal component library tool (http://fractal.build) plugin for accessibility testing against Tenon (http://tenon.io)", 5 | "homepage": "https://github.com/tollwerk/fractal-tenon#readme", 6 | "main": "index.js", 7 | "scripts": { 8 | "lint": "eslint index.js dist/assets/js/tenon.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/tollwerk/fractal-tenon.git" 13 | }, 14 | "keywords": [ 15 | "fractal", 16 | "tenon", 17 | "a11y", 18 | "accessibility", 19 | "component", 20 | "test", 21 | "testing" 22 | ], 23 | "author": "Joschi Kuphal (https://jkphl.is)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/tollwerk/fractal-tenon/issues" 27 | }, 28 | "devDependencies": { 29 | "eslint": "^6.1.0", 30 | "eslint-config-airbnb": "^18.0.1", 31 | "eslint-plugin-import": "^2.18.2", 32 | "eslint-plugin-jsx-a11y": "6.2.3", 33 | "eslint-plugin-react": "^7.14.3" 34 | }, 35 | "dependencies": { 36 | "btoa": "^1.2.1" 37 | }, 38 | "files": [ 39 | "dist", 40 | "docs" 41 | ] 42 | } 43 | --------------------------------------------------------------------------------