1/** 2 * The JSON-LD playground is used to test out JavaScript Object Notation 3 * for Linked Data. 4 * 5 * @author Manu Sporny <[email protected]> 6 * @author Dave Longley <[email protected]> 7 * @author Nicholas Bollweg 8 * @author Markus Lanthaler 9 */ 10;(function($, CodeMirror, jsonld, Promise){ 11 "use strict"; 12 // assume nothing 13 var window = this, 14 console = window.console, 15 setTimeout = window.setTimeout, 16 document = window.document, 17 18 // create the playground instance if it doesn't already exist 19 playground = window.playground = {}, 20 21 // given this is needed, we probably need a `Document` class... 22 docs = function(){ 23 return { 24 markup: null, 25 frame: null, 26 context: null 27 }; 28 }; 29 30 // the codemirror editors 31 playground.editors = docs(); 32 33 // ... and outputs 34 playground.outputs = {}; 35 36 // default theme 37 playground.theme = "neat"; 38 39 // the last parsed version of same 40 playground.lastParsed = docs(); 41 42 // set the active tab to the expanded view 43 playground.activeTab = 'tab-expanded'; 44 45 // map of original to modifed contexts 46 playground.contextMap = { 47 // be careful when working with redirectors as as Chrome (not firefox) 48 // will drop Accept: application/ld+json after redirecting 49 }; 50 51 // map of currently active mapped contexts for user feedback use 52 playground.activeContextMap = {}; 53 54 // JSON schema for JSON-LD documents 55 playground.schema = null; 56 57 // copy context from the input 58 playground.copyContext = false; 59 60 // currently-active urls 61 playground.remoteUrl = docs(); 62 63 // whether a remote document should be used 64 playground.useRemote = docs(); 65 66 67 /** 68 * Get a query parameter by name. 69 * 70 * Code from: 71 * http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript/5158301#5158301 72 * 73 * @param name a query parameter name. 74 * 75 * @return the value of the parameter or null if it does not exist 76 */ 77 function getParameterByName(name) { 78 var match = new RegExp('[#?&]' + name + '=([^&]*)') 79 .exec(window.location.hash || window.location.search); 80 return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); 81 } 82 83 84 /** 85 * Consistent human-readable JSON formatting. 86 * 87 * @param the object or string to humanize. 88 * 89 * @return a string containing the humanized string. 90 */ 91 playground.humanize = function(value) { 92 switch($.type(value)) { 93 case 'string': 94 return value; 95 case 'error': 96 // TODO: limit output size 97 var str = value.toString(); 98 if('details' in value) { 99 // TODO: improve error handling 100 // special cases as of jsonld.js 8.0.0 101 if('event' in value.details) { 102 str += '\n' + JSON.stringify(value.details.event, null, 2); 103 } 104 // some error and event details can be verbose and need good handling 105 // all errors and events should have type codes that can be used 106 } 107 return str; 108 default: 109 return JSON.stringify(value, null, 2); 110 } 111 }; 112 113 114 /** 115 * Handle URL query parameters. 116 * 117 * Checks 'json-ld', 'context', and 'frame' parameters. If they look 118 * like JSON then interpret as JSON strings else interpret as URLs of remote 119 * resources. Note: URLs must be CORS enabled to load due to browser same 120 * origin policy issues. 121 * 122 * If 'startTab' is present, select that tab automatically. 123 */ 124 playground.processQueryParameters = function() { 125 // data from the query 126 var queryData = docs(); 127 128 /** 129 * Read a parameter as JSON or create an jQuery AJAX Deferred call 130 * to read the data. 131 * 132 * @param param a query parameter value. 133 * @param fieldName the field name to populate in queryData object. 134 * @param msgName the param name to use in UI messages. 135 * 136 * @return jQuery Deferred or null. 137 */ 138 function handleParameter(param, fieldName) { 139 // the ajax deferred or null 140 var rval = null; 141 142 // check 'json-ld' parameter 143 if(param !== null) { 144 if(param.length === 0 || param[0] === '{' || param[0] === '[') { 145 // param looks like JSON, try to parse it 146 try {
147 queryData[fieldName] = JSON.parse(param); 148 playground.setRemoteUrl(fieldName, null); 149 } 150 catch(e) { 151 queryData[fieldName] = param; 152 } 153 } 154 else { 155 rval = playground.setRemoteUrl(fieldName, param); 156 playground.toggleRemote(fieldName, true); 157 if(rval){ 158 rval.then(function(data){ 159 queryData[fieldName] = data; 160 }); 161 } 162 } 163 } 164 165 return rval; 166 } 167 168 function handleGist(gist){ 169 /** 170 * Turn the contents of a gist into the equivalent of the legacy URL 171 * scheme 172 * @param gist: a gist id 173 */ 174 var deferreds = [], 175 manifest = JSON.parse(gist.files["playground.jsonld"].content), 176 done = function(){ 177 $('#' + manifest["startTab"]).tab('show'); 178 playground.populateWithJSON(queryData); 179 }; 180 181 playground.copyContext = manifest["copyContext"]; 182 183 $.each(queryData, function(key, val){ 184 val = manifest[key]; 185 if(val){ 186 // is within this gist? 187 if(val[0] === "."){ 188 var file = gist.files[val.slice(2)]; 189 if(!file.truncated){ 190 queryData[key] = JSON.parse(file.content); 191 playground.setRemoteUrl(key, null); 192 }else{ 193 deferreds.push($.get(file.raw_url).done(function(data){ 194 queryData[fieldName] = JSON.parse(data); 195 })); 196 } 197 }else{ 198 playground.toggleRemote(key, true); 199 var rval = playground.setRemoteUrl(key, val); 200 if(rval){ 201 rval.then(function(data){ 202 queryData[fieldName] = data; 203 }); 204 deferreds.push(rval); 205 } 206 } 207 } 208 }); 209 210 return deferreds.length ? 211 $.when.apply($, deferreds).done(done) : 212 done(); 213 } 214 215 var gistId = new RegExp('^#/gist/(.*)').exec(window.location.hash); 216 217 if(gistId){ 218 playground.gist(gistId[1]).done(handleGist); 219 return; 220 } 221 222 // build deferreds 223 var jsonLdDeferred = handleParameter( 224 getParameterByName('json-ld'), 'markup', 'JSON-LD'); 225 226 var frameDeferred = handleParameter( 227 getParameterByName('frame'), 'frame', 'frame'); 228 229 var contextDeferred = handleParameter( 230 getParameterByName('context'), 'context', 'context'); 231 232 var paramDeferred = handleParameter( 233 getParameterByName('param'), 'param', 'param'); 234 235 var startTab = getParameterByName('startTab'); 236 if(startTab) { 237 $('#' + startTab).tab('show'); 238 } 239 240 playground.copyContext = getParameterByName('copyContext') === "true"; 241 242 // wait for ajax if needed 243 // failures handled in AJAX calls 244 $.when(jsonLdDeferred, frameDeferred, contextDeferred, paramDeferred) 245 .done(function() { 246 // Maintain backwards permalink compatability 247 if(queryData.param && !(queryData.frame || queryData.context)) { 248 queryData.frame = queryData.context = queryData.param; 249 } 250 // populate UI with data 251 playground.populateWithJSON(queryData); 252 }); 253 }; 254 255 256 /** 257 * Used to initialize the UI, call once on document load. 258 */ 259 playground.init = function() { 260 // options storage 261 playground.options = { 262 api: { 263 // TODO: add other API options 264 processingMode: '', 265 base: '', 266 baseUrl: '', 267 compactArrays: true, 268 compactToRelative: true, 269 rdfDirection: '', 270 safe: '' 271 } 272 }; 273 274 // enable bootstrap tabs 275 $('#input-tabs a,' + 276 '#context-tabs a,' + 277 '#frame-tabs a,' + 278 '#privatekey-rsa-tabs a,' + 279 '#privatekey-koblitz-tabs a').click(function (e) { 280 e.preventDefault(); 281 $(this).tab('show'); 282 }); 283 $('#output-tabs a').click(function (e) { 284 e.preventDefault(); 285 $(this).tab('show'); 286 }).on("show", playground.tabSelected); 287 288 // show keybaord shortcuts 289 $('.popover-info').popover({ 290 placement: "left", 291 html: true, 292 content: $(".popover-info-content").html() 293 }); 294 295 CodeMirror.commands.autocomplete = function(cm) { 296 CodeMirror.showHint(cm, CodeMirror.hint.jsonld, { 297 lastParsed: playground.lastParsed[cm.options._playground_key], 298 completeSingle: false, 299 schemata: function(){ 300 return playground.schema ? [playground.schema] : []; 301 } 302 }); 303 }; 304 305 CodeMirror.commands.at_autocomplete = function(cm) { 306 CodeMirror.showHint(cm, CodeMirror.hint.jsonld, { 307 isAt: true, 308 completeSingle: false, 309 lastParsed: playground.lastParsed[cm.options._playground_key], 310 schemata: function(){ 311 return playground.schema ? [playground.schema] : []; 312 } 313 }); 314 }; 315 316 $(".codemirror-input").each(function(){ playground.init.editor(this); }); 317 $(".codemirror-output").each(function(){ playground.init.output(this); }); 318 319 playground.makeResizer($("#markup-container"), playground.editors); 320 playground.makeResizer($("#output-container"), playground.outputs); 321 322 $("#copy-context").click(function(){ 323 playground.toggleCopyContext(); 324 }); 325 326 $(".editor-option").each(function(){ 327 var option = $(this), 328 key = option.data("editor"); 329 option.find("input").bind("input", function(){ 330 playground.setRemoteUrl(key, this.value); 331 }); 332 option.find("button").bind("click", function(){ 333 playground.toggleRemote(key); 334 }); 335 }); 336 337 // setup options
338 $("#options-api-processingMode-default").prop( 339 'checked', playground.options.api.processingMode === ''); 340 $("#options-api-processingMode-1-0").prop( 341 'checked', playground.options.api.processingMode === 'json-ld-1.0'); 342 $("#options-api-processingMode-1-1").prop( 343 'checked', playground.options.api.processingMode === 'json-ld-1.1'); 344 345 $("#options-api-base-default").prop( 346 'checked', playground.options.api.base === ''); 347 $("#options-api-base-custom").prop( 348 'checked', playground.options.api.base === 'custom'); 349 350 $("#options-api-base-url").value = playground.options.api.baseUrl; 351 352 $("#options-api-compactArrays").prop( 353 'checked', playground.options.api.compactArrays); 354 355 $("#options-api-compactToRelative").prop( 356 'checked', playground.options.api.compactToRelative); 357 358 $("#options-api-rdfDirection-default").prop( 359 'checked', playground.options.api.rdfDirection === ''); 360 $("#options-api-rdfDirection-i18n-datatype").prop( 361 'checked', playground.options.api.rdfDireciton === 'i18n-datatype'); 362 //$("#options-api-rdfDirection-compound-literal").prop( 363 // 'checked', playground.options.api.rdfDirection === 'compound-literal'); 364 365 $("#options-api-safe-default").prop( 366 'checked', playground.options.api.safe === ''); 367 $("#options-api-safe-false").prop( 368 'checked', playground.options.api.safe === false); 369 $("#options-api-safe-true").prop( 370 'checked', playground.options.api.safe === true); 371 372 // process on option changes 373 $("#options-api-processingMode-default, " + 374 "#options-api-processingMode-1-0, " + 375 "#options-api-processingMode-1-1").change(function(e) { 376 playground.options.api.processingMode = e.target.value; 377 playground.process(); 378 }); 379 380 $("#options-api-base-default, " + 381 "#options-api-base-custom").change(function(e) { 382 playground.options.api.base = e.target.value; 383 playground.process(); 384 }); 385 386 $("#options-api-base-url").on('keypress', function(e) { 387 var keyPressed = e.keyCode || e.which; 388 if(keyPressed === 13) { 389 e.preventDefault(); 390 return false; 391 } 392 }); 393 $("#options-api-base-url").on('input', function(e) { 394 playground.options.api.baseUrl = e.target.value; 395 playground.process(); 396 }); 397 398 $("#options-api-compactArrays").change(function(e) { 399 playground.options.api.compactArrays = e.target.checked; 400 playground.process(); 401 }); 402 403 $("#options-api-compactToRelative").change(function(e) { 404 playground.options.api.compactToRelative = e.target.checked; 405 playground.process(); 406 }); 407 408 $("#options-api-rdfDirection-default" ).change(function(e) { 409 playground.options.api.rdfDirection = ''; 410 playground.process(); 411 }); 412 $("#options-api-rdfDirection-i18n-datatype").change(function(e) { 413 playground.options.api.rdfDirection = 'i18n-datatype'; 414 playground.process(); 415 }); 416 //$("#options-api-rdfDirection-compound-literal").change(function(e) { 417 // playground.options.api.rdfDirection = 'compound-literal'; 418 // playground.process(); 419 //}); 420 421 $("#options-api-safe-default" ).change(function(e) { 422 playground.options.api.safe = ''; 423 playground.process(); 424 }); 425 $("#options-api-safe-false").change(function(e) { 426 playground.options.api.safe = false; 427 playground.process(); 428 }); 429 $("#options-api-safe-true").change(function(e) { 430 playground.options.api.safe = true; 431 playground.process(); 432 }); 433 434 $("[title]").tooltip(); 435 436 $(window).bind("hashchange", function(){ 437 if(window.location.href !== playground.permalink.url){ 438 playground.processQueryParameters(); 439 $("#permalink").popover("hide"); 440 } 441 }); 442 443 // load the schema 444 $.ajax({ 445 url: "/schemas/jsonld-schema.json", 446 dataType: "json" 447 }) 448 .done(function(schema){ 449 playground.schema = schema; 450 }) 451 .fail(function(){ 452 console.warn("Schema could not be loaded. Schema validation disabled."); 453 }); 454 455 if(window.location.search || window.location.hash) { 456 playground.processQueryParameters(); 457 } 458 $(".loading").fadeOut(function(){ 459 $(this).remove(); 460 $(".loaded").fadeIn(); 461 playground.editor.refresh(); 462 playground.editor.refresh(playground.outputs); 463 }); 464 }; 465 466 467 /** 468 * return a debounced copy of a function 469 * thanks to @cwarden 470 * https://github.com/cwarden/promising-debounce/blob/master/src/debounce.js 471 * 472 * @param a function 473 * @param a number of milliseconds 474 * 475 * @return the function, which will only be called every `delay` milliseconds, 476 * which will then, in turn, return a $.Deferred 477 */ 478 playground.debounce = function(fn, wait, immediate) { 479 var timer = null; 480 return function() { 481 var context = this; 482 var args = arguments; 483 var resolve; 484 var promise = new Promise(function(_resolve) { 485 resolve = _resolve; 486 }).then(function() {
487 return fn.apply(context, args); 488 }); 489 if(!!immediate && !timer) { 490 resolve(); 491 } 492 if(timer) { 493 clearTimeout(timer); 494 } 495 timer = setTimeout(function() { 496 timer = null; 497 if(!immediate) { 498 resolve(); 499 } 500 }, wait); 501 return promise; 502 }; 503 }; 504 505 506 /** 507 * Initialize a CodeMirror editor 508 * 509 * @param a `<textarea>` 510 * 511 * @return the CodeMirror editor 512 */ 513 playground.init.editor = function(node){ 514 var key = node.id; 515 var editor; 516 517 // don't use JSON-LD for PEM data 518 if(key === 'privatekey-rsa' || key === 'privatekey-koblitz' || 519 key === 'publickey-koblitz') { 520 editor = CodeMirror.fromTextArea(node, { 521 matchBrackets: true, 522 autoCloseBrackets: true, 523 lineWrapping: false, 524 mode: 'text', 525 theme: playground.theme, 526 _playground_key: key 527 }); 528 } else { 529 editor = CodeMirror.fromTextArea(node, { 530 matchBrackets: true, 531 autoCloseBrackets: true, 532 lineWrapping: true, 533 mode: "application/ld+json", 534 gutters: ["CodeMirror-lint-markers"], 535 theme: playground.theme, 536 lintWith: { 537 getAnnotations: CodeMirror.lint.jsonSchema, 538 async: true, 539 schemata: function(){ 540 return playground.schema ? [playground.schema] : []; 541 } 542 }, 543 extraKeys: { 544 "Ctrl-Space": "autocomplete" 545 }, 546 _playground_key: key 547 }); 548 } 549 550 playground.editors[key] = editor; 551 552 // set up 'process' areas to process JSON-LD after typing 553 editor.on("change", function(){ 554 if(playground.copyContext && key === "markup"){ 555 if(playground.toggleCopyContext.copy()){ 556 return; 557 } 558 } 559 playground.process(); 560 }); 561 562 // check on every keyup for `@`: doesn't get caught by (extra|custom)Keys 563 editor.on("keyup", function(editor, evt) { 564 // these are the keys that move the cursor 565 var noHint = [ 566 8, // backspace 567 9, // hey! where's my tab? 568 33, 34, 35, 46, // pg/home/end 569 37, 38, 39, 40, // arrows 570 12, // enter 571 27 //escape 572 ]; 573 574 if(noHint.indexOf(evt.keyCode) >= 0) { return; } 575 576 var cursor = editor.getCursor(), 577 token = editor.getTokenAt(cursor), 578 chr = token.string[cursor.ch - token.start - 1]; 579 if(chr === "@") { 580 CodeMirror.commands.at_autocomplete(editor, evt); 581 } 582 }); 583 584 return editor; 585 }; 586 587 588 /** 589 * Initialize a read-only CodeMirror viewer 590 * 591 * @param a `<textarea>` 592 *
593 * @return the CodeMirror editor 594 */ 595 playground.init.output = function(node) { 596 var key = node.id, 597 output = playground.outputs[key] = CodeMirror.fromTextArea(node, { 598 readOnly: true, 599 lineWrapping: true, 600 mode: ["canonized", "nquads"].indexOf(key) > -1 ? 601 "text/n-triples" : 602 "application/ld+json", 603 theme: playground.theme 604 }); 605 return output; 606 }; 607 608 609 /** 610 * Toggle whether the output context will be updated from the input 611 * 612 * @param the new value of the setting. If not provided, invert current 613 * 614 * @return the JSON that was actually set, or `undefined` if nothing was set 615 */ 616 playground.toggleCopyContext = function(val){ 617 var editor = playground.editors.context; 618 619 playground.copyContext = val = arguments.length ? 620 Boolean(val) : 621 !playground.copyContext; 622 623 playground.editor.setReadOnly(editor, val); 624 625 setTimeout(function(){ 626 $("#copy-context").toggleClass("toggle", val); 627 }, 1); 628 629 if(val){ 630 return playground.toggleCopyContext.copy(); 631 } 632 }; 633 634 635 /** 636 * Copy the context right now. 637 * 638 * @return the JSON that was set, or `undefined` if nothing was set 639 */ 640 playground.toggleCopyContext.copy = function(){ 641 var editor = playground.editors.context, 642 json = playground.humanize({ 643 "@context": playground.lastParsed.markup ? 644 playground.lastParsed.markup["@context"] : 645 {}}); 646 if(json !== editor.getValue()){ 647 playground.editors.context.setValue(json); 648 return json; 649 } 650 }; 651 652 653 /** 654 * Set the remote URL for an editor, then fetch (if enabled). 655 * 656 * @param the key for the editor 657 * @param the value 658 * 659 * @return jQuery deferred, or `undefined` 660 */ 661 playground.setRemoteUrl = function(key, val){ 662 var opt = $("[data-editor=" + key + "]"), 663 btn = opt.find("button"), 664 inp = opt.find("input"); 665 666 playground.remoteUrl[key] = val ? val : null; 667 668 if(inp.val() != val){ 669 inp.val(val); 670 } 671 672 // the button state is no longer valid 673 btn.removeClass("btn-danger btn-info"); 674 675 return playground.fetchRemote(key); 676 }; 677 678 679 /** 680 * Toggle (or set) whether a remote document will be used for an editor. 681 * 682 * @param the key for the editor 683 * @param the value: omit to toggle 684 * 685 * @return jQuery deferred, or `undefined` 686 */ 687 playground.toggleRemote = function(key, val){ 688 var btn = $("[data-editor=" + key + "] button"); 689 690 playground.useRemote[key] = val = arguments.length === 2 ? 691 Boolean(val) : 692 !playground.useRemote[key]; 693 694 playground.editor.setReadOnly(key, val); 695 696 // the button state is no longer valid 697 setTimeout(function(){ 698 btn.removeClass("btn-danger btn-info" + (!val ? " active" : "")); 699 }, 1); 700 return playground.fetchRemote(key); 701 }; 702 703 704 // Cache of fetched remote urls 705 playground.fetchRemoteCache = {}; 706 playground.fetchRemoteFails = {}; 707 708 /** 709 * Fetch a remote document and populate an editor. 710 * 711 * @param the key for the editor 712 * 713 * @return jQuery deferred, or `undefined` 714 */ 715 playground.fetchRemote = playground._fetchRemote = function(key){ 716 if(!playground.useRemote[key]){ return; } 717 718 var btn = $("[data-editor=" + key + "] button"), 719 debounced = playground.fetchRemote !== playground._fetchRemote, 720 url = playground.remoteUrl[key], 721 hit = playground.fetchRemoteCache[url], 722 fail = playground.fetchRemoteFails[url], 723 success = function(data){ 724 playground.fetchRemoteCache[url] = data; 725 btn.addClass("btn-info active"); 726 // setValue always triggers a .process() 727 playground.editors[key].setValue(playground.humanize(data)); 728 playground.fetchRemote = playground._fetchRemote; 729 }, 730 error = function(err) { 731 playground.fetchRemoteFails[url] = err; 732 btn.addClass("btn-danger active"); 733 $('#processing-errors') 734 .text('Error loading ' + key + ' URL: ' + playground.remoteUrl[key]) 735 .show(); 736 737 playground.fetchRemote = debounced ? 738 playground.fetchRemote : 739 playground.debounce(playground._fetchRemote, 500); 740 }; 741 742 return hit ? success(hit) : 743 fail ? error(fail) : 744 jsonld.documentLoader(url).then(function(remoteDoc) { 745 /* Note: Do injection of Link header @context; this could possibly 746 be done in a less obfuscated way (to the user) or use the expandContext 747 API option, or better integrate debouncing in the remote document 748 loader defined elsewhere. However, this approach was the least 749 intrusive to do now to restore Link header functionality and has the 750 advantage of allowing the document to be edited inline w/the 751 injected @context. */ 752 if(remoteDoc.contextUrl) { 753 // TODO: flash link header injection notice on UI 754 if(Array.isArray(remoteDoc.document)) { 755 remoteDoc.document = { 756 '@context': remoteDoc.contextUrl, 757 '@graph': remoteDoc.document 758 }; 759 } else if(typeof remoteDoc.document === 'object') { 760 // inject @context as first key 761 var obj = {'@context': remoteDoc.contextUrl}; 762 for(var key in remoteDoc.document) { 763 obj[key] = remoteDoc.document[key]; 764 } 765 remoteDoc.document = obj; 766 } 767 } 768 success(remoteDoc.document); 769 }).catch(error); 770 }; 771 772 773 /** 774 * Make one or more editor resizeable together. 775 * 776 * @param parent the dom element to which the button should be attached 777 * @param an object or list of CodeMirror instances to be resized together 778 * 779 * @return the resizer button DOM 780 */ 781 playground.makeResizer = function(parent, targets){ 782 targets = $.map(targets, function(val){ return val; }); 783 var start_y, 784 start_height, 785 handlers = {}, 786 btn = $("<button/>", {"class": "btn resizer"}) 787 .mousedown(handlers.mousedown = function(evt){ 788 start_y = evt.screenY; 789 start_height = targets[0].display.wrapper.clientHeight; 790 $(window) 791 .bind("mousemove.resizer", function(evt){ 792 targets.map(function(tgt){ 793 tgt.setSize(null, start_height - (start_y - evt.screenY)); 794 }); 795 }) 796 .bind("mouseup.resizer", function(){ 797 $(window).unbind(".resizer"); 798 btn.blur(); 799 }); 800 }) 801 .appendTo(parent); 802 return btn[0]; 803 }; 804 805 806 /** 807 * Namespace for editor functions, and utility for doing things against them 808 * 809 * @param a keyed object of editors, the name of an editor, an editor 810 * or a list of editors. or nothing, which assumes all of them. 811 * @param the action to peform, of the form `function(editor, key)` 812 * 813 * @return the result of the action 814 */ 815 playground.editor = function(editors, action){ 816 var key, 817 editor; 818 if($.type(editors) === "string"){ 819 key = editors; 820 editors = {}; 821 editors[key] = playground.editors[key]; 822 }else if(editors instanceof CodeMirror){ 823 key = editors.getTextArea().id; 824 editor = editors; 825 editors = {}; 826 editors[key] = editor; 827 }else if(!editors){ 828 editors = playground.editors; 829 } 830 return $.map(editors, action); 831 }; 832 833 834 /** 835 * Make a CodeMirror editor (temporarily) read-only. 836 * 837 * @param see `playground.editor` 838 * @param whether the CodeMirror editor should be editable 839 * 840 * @return the new value of the read-only setting 841 */ 842 playground.editor.setReadOnly = function(editors, value){ 843 value = Boolean(value); 844 playground.editor(editors, function(editor){ 845 editor.setOption("readOnly", value); 846 $(editor.getWrapperElement()).toggleClass("read-only", value); 847 }); 848 return value; 849 }; 850 851 852 /** 853 * Refresh one or more CodeMirror editors, such as after being revealed. 854 * 855 * @param see `playground.editor` 856 */ 857 playground.editor.refresh = function(editor){ 858 return playground.editor(editor, function(editor){ 859 editor.refresh(); 860 }); 861 }; 862 863 864 /** 865 * Callback for when tabs are selected in the UI. 866 * 867 * @param event the event that fired when the tab was selected. 868 * 869 * @return the process() promise 870 */ 871 playground.tabSelected = function(evt) { 872 var id = playground.activeTab = evt.target.id; 873 874 if(['tab-compacted', 'tab-flattened', 'tab-framed', 875 /*'tab-signed-rsa', 'tab-signed-koblitz'*/].indexOf(id) > -1) {
876 // these options require more UI inputs, so compress UI space 877 $('#markup-div').removeClass('span12').addClass('span6'); 878 $('#frame-div, #privatekey-rsa-div, #privatekey-koblitz-div, ' + 879 '#context-div').hide(); 880 if(id === 'tab-compacted' || id === 'tab-flattened') { 881 $('#param-type').html('JSON-LD Context'); 882 $('#context-div').show(); 883 } else if(id==='tab-framed') { 884 $('#param-type').html('JSON-LD Frame'); 885 $('#frame-div').show(); 886 } else if(id === 'tab-signed-rsa') { 887 $('#param-type').html('PEM-encoded Private Key'); 888 $('#privatekey-rsa-div').show(); 889 } else if(id === 'tab-signed-koblitz') { 890 $('#param-type').html('Base 58 Encoded Private Key'); 891 $('#privatekey-koblitz-div').show(); 892 } 893 } 894 else { 895 // else no input textarea required 896 $('#context-div, #frame-div, #privatekey-rsa-div, ' + 897 '#privatekey-koblitz-div').hide(); 898 $('#markup-div').removeClass('span6').addClass('span12'); 899 $('#param-type').html(''); 900 } 901 902 // refresh all the editors 903 playground.editor.refresh(); 904 playground.editor.refresh(playground.outputs); 905 906 // perform processing on the data provided in the input boxes 907 return playground.process(); 908 }; 909 910 911 /** 912 * Returns a Promise to perform the JSON-LD API action based on the active 913 * tab. 914 * 915 * @param input the JSON-LD object input or null no error. 916 * @param param the JSON-LD param to use. 917 * 918 * @return a promise to perform the action 919 */ 920 playground.performAction = function(input, param) { 921 // set options 922 var options = {}; 923 if(playground.options.api.processingMode !== '') { 924 options.processingMode = playground.options.api.processingMode; 925 } 926 if(playground.options.api.base === 'custom') { 927 options.base = playground.options.api.baseUrl || ''; 928 } else { 929 // default base IRI is based on remote url or set to null 930 options.base = 931 (playground.useRemote.markup && playground.remoteUrl.markup) || null; 932 } 933 options.compactArrays = playground.options.api.compactArrays; 934 options.compactToRelative = playground.options.api.compactToRelative; 935 if(playground.options.api.rdfDirection !== '') { 936 options.rdfDirection = playground.options.api.rdfDirection; 937 } 938 if(playground.options.api.safe !== '') { 939 options.safe = playground.options.api.safe; 940 } 941 942 var promise; 943 if(playground.activeTab === 'tab-compacted') { 944 promise = jsonld.compact(input, param, options); 945 } 946 else if(playground.activeTab === 'tab-expanded') { 947 promise = jsonld.expand(input, options); 948 } 949 else if(playground.activeTab === 'tab-flattened') { 950 promise = jsonld.flatten(input, param, options); 951 } 952 else if(playground.activeTab === 'tab-framed') { 953 promise = jsonld.frame(input, param, options); 954 } 955 else if(playground.activeTab === 'tab-nquads') { 956 options.format = 'application/n-quads'; 957 promise = jsonld.toRDF(input, options); 958 } 959 else if(playground.activeTab === 'tab-canonized') { 960 options.format = 'application/n-quads'; 961 promise = jsonld.canonize(input, options); 962 } 963 else if(playground.activeTab === 'tab-table') { 964 return jsonld.toRDF(input, options) 965 .then(_datasetToTable($('#table tbody'))); 966 } 967 else if(playground.activeTab === 'tab-visualized') { 968 // early return because this isn't an editor 969 return new Promise(function() { 970 $('#visualized').empty(); 971 d3.jsonldVis(input, '#visualized'); 972 }); 973 } 974 else if(playground.activeTab === 'tab-signed-rsa') { 975 options.format = 'application/ld+json'; 976 977 var jsigs = window.jsigs || window['jsonld-signatures']; 978 var pkey = playground.editors['privatekey-rsa'].getValue(); 979 980 var secCtx = jsigs.SECURITY_CONTEXT_URL; 981 var algorithm; 982 var newerJsigs = 'suites' in jsigs && 'RsaSignature2018' in jsigs.suites; 983 // FIXME: remove when jsonld.js updated 984 var v11Ctx = {'@version': 1.1}; 985 986 if(newerJsigs) { 987 algorithm = 'RsaSignature2018'; 988 } else { 989 algorithm = 'LinkedDataSignature2015'; 990 } 991 992 // add security context to input
993 if(!('@context' in input)) { 994 input['@context'] = secCtx; 995 } else if(Array.isArray(input['@context'])) { 996 if(newerJsigs) { 997 input['@context'].unshift(v11Ctx); 998 } 999 input['@context'].push(secCtx); 1000 } else { 1001 input['@context'] = 1002 [v11Ctx, input['@context'], secCtx]; 1003 } 1004 1005 promise = jsigs.promises.sign(input, { 1006 privateKeyPem: pkey, 1007 algorithm: algorithm, 1008 nonce: forge.util.bytesToHex(forge.random.getBytesSync(4)), 1009 domain: 'json-ld.org', 1010 creator: 'https://example.com/jdoe/keys/1' 1011 }); 1012 } 1013 else if(playground.activeTab === 'tab-signed-koblitz') { 1014 options.format = 'application/ld+json'; 1015 1016 var jsigs = window.jsigs || window['jsonld-signatures']; 1017 var privateKey = playground.editors['privatekey-koblitz'].getValue().trim(); 1018 1019 // add security context to input 1020 if(!('@context' in input)) { 1021 input['@context'] = 'https://w3id.org/security/v1'; 1022 } else if(Array.isArray(input['@context'])) { 1023 input['@context'].push('https://w3id.org/security/v1'); 1024 } else { 1025 input['@context'] = 1026 [input['@context'], 'https://w3id.org/security/v1']; 1027 } 1028 1029 var signed = null; 1030 var publicKeyFromPrivateKey; 1031 try { 1032 publicKeyFromPrivateKey = new bitcoreMessage.Bitcore.PrivateKey(privateKey).toPublicKey(); 1033 } catch (e) { 1034 promise = Promise.reject(e) 1035 } 1036 1037 promise = promise || jsigs.promises.sign(input, { 1038 privateKeyWif: privateKey, 1039 algorithm: 'EcdsaKoblitzSignature2016', 1040 creator: 'ecdsa-koblitz-pubkey:' + publicKeyFromPrivateKey 1041 }).then( function(_signed) { 1042 signed = _signed; 1043 var publicKey = playground.editors['publickey-koblitz'].getValue().trim(); 1044 var verificationKey = { 1045 '@context': jsigs.SECURITY_CONTEXT_URL, 1046 id: 'ecdsa-koblitz-pubkey:' + publicKey, 1047 type: 'CryptographicKey', 1048 publicKeyWif: publicKey 1049 }; 1050 var publicKeyBtcOwner = { 1051 '@context': jsigs.SECURITY_CONTEXT_URL, 1052 publicKey: [verificationKey] 1053 }; 1054 return jsigs.promises.verify(signed, {
1055 publicKey: verificationKey, 1056 publicKeyOwner: publicKeyBtcOwner 1057 }) 1058 }).then( function(verification) { 1059 if (verification) { 1060 $('#validation-message') 1061 .text('Signature validated successfully using this public key') 1062 .show(); 1063 } else { 1064 $('#validation-errors') 1065 .text('Signature was NOT validated using this public key') 1066 .show(); 1067 } 1068 return signed; 1069 }).catch( function(e) { 1070 console.error(e.stack) 1071 throw(e) 1072 }); 1073 } 1074 else if(playground.activeTab === 'tab-signatures') { 1075 promise = Promise.resolve(); 1076 } 1077 else { 1078 promise = Promise.reject(new Error('Invalid tab selection.')); 1079 } 1080 1081 return promise.then(function(result) { 1082 var outputTab = playground.activeTab.substr('tab-'.length); 1083 if(outputTab in playground.outputs) { 1084 result = playground.humanize(result); 1085 playground.outputs[outputTab].setValue(result); 1086 } 1087 }); 1088 }; 1089 1090 var _datasetToTable = function(tbody) { 1091 return function(dataset) { 1092 tbody.empty(); 1093 // simple sort, good enough for this purpose 1094 // FIXME: is this needed? 1095 dataset.sort(function(a, b) { 1096 if(a.graph.value !== b.graph.value) { 1097 return a.graph.value > b.graph.value; 1098 } 1099 if(a.subject.value !== b.subject.value) { 1100 return a.subject.value > b.subject.value; 1101 } 1102 if(a.predicate.value !== b.predicate.value) { 1103 return a.predicate.value > b.predicate.value; 1104 } 1105 return a.object.value > b.object.value; 1106 }); 1107 dataset.forEach(function(quad) { 1108 tbody.append(_tableRow(quad)); 1109 }); 1110 }; 1111 }; 1112 1113 var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; 1114 var RDF_LANGSTRING = RDF + 'langString'; 1115 var XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string'; 1116 1117 var _tableRow = function(quad) { 1118 var g = quad.graph; 1119 var s = quad.subject; 1120 var p = quad.predicate; 1121 var o = quad.object; 1122 1123 var row = $('<tr>'); 1124 1125 // subject (IRI) 1126 var td = $('<td>'); 1127 if(s.termType === 'NamedNode') { 1128 var a = $('<a>').attr('href', s.value).text(s.value); 1129 td.append(a); 1130 } else { 1131 td.text(s.value); 1132 } 1133 row.append(td); 1134 1135 // predicate (IRI) 1136 td = $('<td>'); 1137 if(p.termType === 'NamedNode') { 1138 var a = $('<a>').attr('href', p.value).text(p.value); 1139 td.append(a); 1140 } else { 1141 td.text(p.value); 1142 } 1143 row.append(td); 1144 1145 // object (IRI, bnode, or literal) 1146 if(o.termType === 'NamedNode') { 1147 var a = $('<a>').attr('href', o.value).text(o.value); 1148 row.append($('<td>').append(a)); 1149 row.append($('<td>')); 1150 row.append($('<td>')); 1151 } else if(o.termType === 'BlankNode') { 1152 row.append($('<td>').text(o.value)); 1153 row.append($('<td>')); 1154 row.append($('<td>')); 1155 } else { 1156 row.append($('<td>').text(o.value)); 1157 if(o.datatype.value === RDF_LANGSTRING) { 1158 if(o.language) { 1159 row.append($('<td>').text(o.language)); 1160 } else { 1161 row.append($('<td>')); 1162 } 1163 row.append($('<td>')); 1164 } else if(o.datatype.value !== XSD_STRING) { 1165 row.append($('<td>')); 1166 var a = $('<a>').attr('href', o.datatype.value).text(o.datatype.value); 1167 row.append($('<td>').append(a)); 1168 } else { 1169 row.append($('<td>')); 1170 row.append($('<td>')); 1171 } 1172 } 1173 /* 1174 td = $('<td>'); 1175 if(o.type === 'IRI') { 1176 var a = $('<a>').attr('href', o.value).text(o.value); 1177 td.append(a); 1178 } else if(o.termType === 'BlankNode') { 1179 td.text(o.value); 1180 } else { 1181 var t = o.value; 1182 if(o.datatype === RDF_LANGSTRING) { 1183 if(o.language) { 1184 t += '@' + o.language; 1185 } 1186 } else if(o.datatype !== XSD_STRING) { 1187 t += '^^<' + o.datatype + '>'; 1188 } 1189 td.text(t); 1190 } 1191 row.append(td); 1192 */ 1193 1194 // TODO: ad lang and datatype columns? 1195 1196 // graph (null, IRI, bnode) 1197 td = $('<td>'); 1198 if(g.value !== '') { 1199 if(g.value.indexOf('_:') !== 0) { 1200 var a = $('<a>').attr('href', g.value).text(g.value); 1201 td.append(a); 1202 } else { 1203 td.text(g.value); 1204 } 1205 } 1206 row.append(td); 1207 1208 return row; 1209 }; 1210 1211 /** 1212 * Process the JSON-LD markup that has been input and display the output 1213 * in the active tab. 1214 * 1215 * @return a promise to process 1216 */ 1217 playground.process = playground._process = function(){ 1218 $('#markup-errors').hide().empty(); 1219 $('#param-errors').hide().empty(); 1220 $('#validation-errors').hide().empty(); 1221 $('#validation-message').hide().empty(); 1222 $('#processing-errors').hide().empty(); 1223 $('#using-context-map').hide(); 1224 $('#using-context-map table tbody').empty(); 1225 playground.activeContextMap = {}; 1226 var errors = false;
1227 var markup = playground.editors.markup.getValue(); 1228 var input; 1229 1230 // nothing to process 1231 if(markup === '') { 1232 return; 1233 } 1234 1235 // check to see if the JSON-LD markup is valid JSON 1236 try { 1237 input = playground.lastParsed.markup = JSON.parse(markup); 1238 } 1239 catch(e) { 1240 $('#markup-errors') 1241 .text('JSON markup - ' + e) 1242 .show(); 1243 errors = true; 1244 } 1245 1246 // If we're using a param, check to see if it is valid JSON 1247 var needParam = false; 1248 var param = null; 1249 var jsonParam = null; 1250 var paramType = null; 1251 1252 if(playground.activeTab === 'tab-compacted' || 1253 playground.activeTab === 'tab-flattened') { 1254 jsonParam = playground.editors.context.getValue(); 1255 needParam = true; 1256 paramType = "context"; 1257 } 1258 else if(playground.activeTab === 'tab-framed') { 1259 jsonParam = playground.editors.frame.getValue(); 1260 needParam = true; 1261 paramType = "frame"; 1262 } 1263 1264 if(needParam) { 1265 try { 1266 playground.lastParsed[paramType] = param = JSON.parse(jsonParam); 1267 } 1268 catch(e) { 1269 $('#param-errors') 1270 .text($('#param-type').text() + ' - ' + e) 1271 .show(); 1272 errors = true; 1273 } 1274 } 1275 1276 var debounced = playground.process !== playground._process; 1277 1278 // no errors, perform the action and display the output 1279 return playground.performAction(input, param) 1280 .then( 1281 function(){ 1282 playground.permalink(); 1283 playground.process = playground._process; 1284 }, 1285 function(err){ 1286 // FIXME: add better error handling output 1287 // log to allow console inspection 1288 console.error('Processing Error:', {error: err}); 1289 $('#processing-errors') 1290 .append('Processing error:') 1291 .append( 1292 $('<span style="font-size: 0.7em; text-align=right">') 1293 .text(' (See console for details.)')) 1294 .append( 1295 $('<pre>').text(playground.humanize(err))) 1296 .show(); 1297 playground.permalink(err); 1298 playground.process = debounced ? 1299 playground.process : 1300 playground.debounce(playground._process, 500); 1301 return err; 1302 } 1303 ); 1304 }; 1305 1306 playground.gist = function(gistId, description){ 1307 var gistUrl = "https://api.github.com/gists"; 1308 1309 if(gistId){ 1310 var xhr = $.get(gistUrl + "/" + gistId); 1311 1312 playground.lastGist = null; 1313 1314 xhr.done(function(gist){ 1315 playground.lastGist = gist; 1316 }); 1317 1318 return xhr; 1319 }else{ 1320 var files = { 1321 "README.md": { 1322 content: 1323 "# " + description + "\n\n" + 1324 "> This gist was automatically created by the [JSON-LD" + 1325 " Playground](http://json-ld.org/playground). You can see it" + 1326 " loaded there by visiting:\n" + 1327 "```\nhttp://json-ld.org/playground#/gist/<gist id>\n```" 1328 } 1329 }, 1330 manifest = { 1331 "@context": { 1332 rdfs: "http://www.w3.org/2000/01/rdf-schema#", 1333 playground: "http://json-ld.org/playground#", 1334 1335 description: "rdfs:label", 1336 1337 startTab: "playground:startTab", 1338 copyContext: "playground:copyContext", 1339 1340 markup: {"@id": "playground:markup", "@type": "@id"}, 1341 context: {"@id": "playground:context", "@type": "@id"}, 1342 frame: {"@id": "playground:frame", "@type": "@id"} 1343 }, 1344 "description": description, 1345 "startTab": playground.activeTab, 1346 "copyContext": playground.copyContext 1347 }; 1348 1349 // check the editors for inputs/remotes 1350 $.each(playground.editors, function(key){ 1351 if(key === "context" && playground.copyContext){ return; } 1352 if(playground.useRemote[key]){ 1353 manifest[key] = playground.remoteUrl[key]; 1354 }else if(playground.lastParsed[key]){ 1355 manifest[key] = "./" + key + ".jsonld"; 1356 files[key + ".jsonld"] = { 1357 content: playground.humanize(playground.lastParsed[key]) 1358 }; 1359 } 1360 }); 1361 1362 files["playground.jsonld"] = { 1363 content: playground.humanize(manifest) 1364 }; 1365 1366 var gist = { 1367 description: "JSON-LD Playground: " + description, 1368 public: true, 1369 files: files 1370 }; 1371 1372 return $.ajax({ 1373 type: "POST", 1374 url: gistUrl, 1375 contentType: "application/json", 1376 dataType: "json", 1377 data: playground.humanize(gist) 1378 }); 1379 } 1380 }; 1381 1382 playground.gist.title = function(){ 1383 return $("<div/>").text("Gist"); 1384 }; 1385 1386 playground.gist.content = function(){ 1387 var content = $("<div/>").css({width: 320}); 1388 1389 1390 content.append($("<p/>").text("Try to load a gist into the playground.")); 1391 1392 var idWrap = $("<div/>", {"class": "input-append"}) 1393 .appendTo(content); 1394 1395 content.append($("<p/>").text( 1396 "Create a public forkable, commentable version of the current playground configuration." 1397 )); 1398 1399 var descWrap = $("<div/>", {"class": "input-append"}) 1400 .appendTo(content); 1401 1402 var gistId = $("<input/>", { 1403 "type": "text", 1404 "placeholder": "<gist id> or <user>/<gist id>" 1405 }) 1406 .appendTo(idWrap), 1407 1408 gistDesc = $("<input/>", { 1409 "type": "text", 1410 "placeholder": "New Gist description" 1411 }) 1412 .appendTo(descWrap); 1413 1414 $("<button/>", {"class": "btn btn-info"}) 1415 .css({width: 100}) 1416 .append( 1417 $("<i/>", {"class": "icon icon-cloud-download pull-left"}), 1418 $("<span/>").text("Load") 1419 ) 1420 .click(function(){ 1421 playground.gist(gistId.val()).done(function(gist){ 1422 gistDesc.val(gist.description);
1423 window.location.hash = "/gist/" + gist.id; 1424 }); 1425 }) 1426 .appendTo(idWrap); 1427 1428 $("<button/>", {"class": "btn btn-primary"}) 1429 .css({width: 100}) 1430 .append( 1431 $("<i/>", {"class": "icon icon-cloud-upload pull-left"}), 1432 $("<span/>").text("Create") 1433 ) 1434 .click(function(){ 1435 playground.gist(null, gistDesc.val()).done(function(gist){ 1436 gistDesc.val(null); 1437 gistId.val(gist.id); 1438 }); 1439 }) 1440 .appendTo(descWrap); 1441 1442 return content; 1443 }; 1444 1445 /** 1446 * Update the permalink button with a `#` link to the current playground 1447 * 1448 * @param the error object, string or object 1449 * 1450 * @return the current permalink URL 1451 */ 1452 playground.permalink = function(errors) { 1453 // generate a hash link for current data, starting with the tab 1454 var hash = "", 1455 params = { 1456 startTab: playground.activeTab, 1457 copyContext: playground.copyContext 1458 }, 1459 val, 1460 messages; 1461 1462 // check the editors for inputs/remotes 1463 $.each(playground.editors, function(key){ 1464 if(key === "context" && params.copyContext){ return; } 1465 val = playground.useRemote[key] ? playground.remoteUrl[key] : null; 1466 val = val ? val : JSON.stringify(playground.lastParsed[key]); 1467 if(val && val !== "null"){ 1468 params[key] = val; 1469 } 1470 }); 1471 1472 // encode and concat the hash components 1473 $.each(params, function(key, val){ 1474 if(!val){ return; } 1475 hash += (hash ? "&" : "#") + 1476 (key === "markup" ? "json-ld" : key) + "=" + encodeURIComponent(val); 1477 }); 1478 1479 playground.permalink.url = window.location.href.replace(/[#\?].*$/, "") + 1480 hash; 1481 1482 messages = { 1483 danger: errors === void 0 ? "" : 1484 "This link will show the current errors.", 1485 warning: playground.permalink.url.length < 2048 ? "" : 1486 "This link is longer than 2kb, and may not work." 1487 }; 1488 1489 $("#permalink") 1490 .popover("hide") 1491 .attr({href: playground.permalink.url}) 1492 .removeClass("hide") 1493 .toggleClass("btn-danger", messages.danger.length !== 0) 1494 .toggleClass("btn-warning", messages.warning.length !== 0) 1495 .find("span") 1496 .text("Permalink"); 1497 1498 return playground.permalink.url; 1499 }; 1500 1501 playground.permalink.title = function(){ 1502 var title = $("<span/>"); 1503 title.append( 1504 $("<span/>").text("Share this "), 1505 $("<a/>", { 1506 href: "http://tinyurl.com/create.php?url=" + 1507 playground.permalink.url.replace(/#/, "?"), 1508 target: "_blank", 1509 "class": "pull-right" 1510 }).text("Shorten")); 1511 return title[0]; 1512 }; 1513 1514 playground.permalink.content = function(){ 1515 var tip = $("<p/>"), 1516 inp = $("<input/>", { 1517 "class": "span2", 1518 autofocus: true 1519 }) 1520 .val(playground.permalink.url); 1521 tip.append(inp); 1522 1523 setTimeout(function(){ 1524 inp[0].select(); 1525 }); 1526 1527 return tip[0]; 1528 }; 1529 1530 1531 /** 1532 * Populate the UI with markup, frame, and context JSON. The data parameter 1533 * should have a 'markup' field and optional 'frame' and 'context' fields. 1534 * 1535 * @param data object with optional 'markup', 'frame' and 'context' fields. 1536 * 1537 * @return the process promise, or `undefined` if no data was found 1538 */ 1539 playground.populateWithJSON = function(data) { 1540 var hasData = false; 1541 1542 $.each(playground.editors, function(key, editor){ 1543 if(key in data && data[key] !== null){ 1544 hasData = true; 1545 editor.setValue(playground.humanize(data[key])); 1546 }else{ 1547 if(key !== 'privatekey-rsa' && key !== 'privatekey-koblitz' && 1548 key !== 'publickey-koblitz') { 1549 editor.setValue("{}"); 1550 } 1551 } 1552 }); 1553 1554 if(playground.copyContext){ 1555 playground.toggleCopyContext(true); 1556 } 1557 1558 if(hasData) { 1559 // perform processing on the data provided in the input boxes 1560 return playground.process(); 1561 } 1562 }; 1563 1564 1565 /** 1566 * Populate the UI with a named example. 1567 * 1568 * @param name the name of the example to pre-populate the input boxes. 1569 * 1570 * @return a promise to process the data, or `undefined` 1571 */ 1572 playground.populateWithExample = function(name) { 1573 var data = docs(); 1574 1575 if(name in playground.examples) {
1576 // fill the markup with the example 1577 data.markup = playground.examples[name]; 1578 1579 if(name in playground.frames) { 1580 // fill the frame with the example 1581 data.frame = playground.frames[name]; 1582 } 1583 1584 if(name in playground.contexts) { 1585 // fill the context with the example 1586 data.contexts = playground.contexts[name]; 1587 } 1588 else if('@context' in playground.examples[name]) { 1589 // use context from markup as default 1590 data.context = { '@context': playground.examples[name]['@context'] }; 1591 } 1592 } 1593 1594 // clean up any remote URLs 1595 $.each(playground.editors, function(key){ 1596 playground.toggleRemote(key, false); 1597 playground.setRemoteUrl(key, null); 1598 }); 1599 playground.toggleCopyContext(false); 1600 1601 // populate with the example 1602 return playground.populateWithJSON(data); 1603 }; 1604 1605 1606 // event handlers 1607 $(document).ready(function() { 1608 // Add custom document loader that uses a context URL map. 1609 var xhrDocumentLoader = jsonld.documentLoaders.xhr(); 1610 // FIXME: add UI to let users control and set context mapping 1611 jsonld.documentLoader = function(url) { 1612 if(url in playground.contextMap) { 1613 $('#using-context-map').show(); 1614 var modified = playground.contextMap[url]; 1615 if(!(modified in playground.activeContextMap)) { 1616 var row = $('<tr>') 1617 .append('<td>' + url + '</td>') 1618 .append('<td>' + modified + '</td>'); 1619 $('#using-context-map table tbody').append(row); 1620 playground.activeContextMap[url] = modified; 1621 } 1622 1623 if($('#use-context-map').prop('checked')) { 1624 url = modified; 1625 } 1626 } 1627 // rewrite URLs that we know have secure JSON-LD Contexts 1628 if(url === 'http://schema.org/' || url === 'http://schema.org') { 1629 url = 'https://schema.org/'; 1630 } 1631 1632 // if a non-HTTPS URL, use the proxy since we run in HTTPS only mode 1633 if(!url.startsWith('https://')) { 1634 url = [ 1635 location.protocol, 1636 '//', 1637 location.host, 1638 // NOTE: using hard-coded path so file can be shared with dev page 1639 //location.pathname, 1640 '/playground/', 1641 'proxy?url=', 1642 url 1643 ].join(''); 1644 } 1645 1646 return xhrDocumentLoader(url); 1647 }; 1648 1649 // set up buttons to load examples 1650 $('.button').each(function() { 1651 var button = $(this); 1652 button.click(function() { 1653 playground.populateWithExample(button.find('span').text()); 1654 }); 1655 }); 1656 1657 $('#use-context-map').change(function() { 1658 playground.process(); 1659 }); 1660 1661 $("#permalink").popover({ 1662 placement: "left", 1663 title: playground.permalink.title, 1664 content: playground.permalink.content, 1665 html: true 1666 }); 1667 1668 $("#gist").popover({ 1669 placement: "left", 1670 title: playground.gist.title, 1671 content: playground.gist.content, 1672 html: true 1673 }); 1674 }); 1675 return playground; 1676}).call(this, this.jQuery, this.CodeMirror, this.jsonld, this.Promise);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.