├── README.md ├── index.html └── say_restyle.js /README.md: -------------------------------------------------------------------------------- 1 | # Edit webpages with your voice 2 | 3 | Include `say_restyle.js` on a page and you'll be able to edit the page by issuing voice commands. 4 | 5 | Browsers that support the web speech API: Chrome, Safari, Opera. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | DEMO: Edit this webpage with your voice 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 56 | 57 | 58 | 59 | 60 |
61 | 62 |

Edit this webpage with your voice

63 | 64 |

65 | If you're using a browser that supports the Speech Recognition API 66 | (e.g. Chrome, Safari, Opera), you can edit this webpage by speaking your commands, and you can inspect page elements 67 | by having the information spoken to you. 68 |

69 | 70 |

71 | Tip: Open your developer console to view logs from the Speech Recognition API and the say_restyle.js script. 72 |

73 | 74 |
75 | 76 |
77 | 78 | 124 | 125 |
126 |

127 | 128 |

129 |

130 | 131 |

132 |

133 | 134 |

135 |

136 | 137 |

138 |

139 | 140 |

141 |
142 | 143 |
144 | 145 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /say_restyle.js: -------------------------------------------------------------------------------- 1 | 2 | //! annyang 3 | //! version : 1.6.0 4 | //! author : Tal Ater @TalAter 5 | //! license : MIT 6 | //! https://www.TalAter.com/annyang/ 7 | (function(a){"use strict";var b=this,c=b.SpeechRecognition||b.webkitSpeechRecognition||b.mozSpeechRecognition||b.msSpeechRecognition||b.oSpeechRecognition;if(!c)return b.annyang=null,a;var d,e,f=[],g={start:[],error:[],end:[],result:[],resultMatch:[],resultNoMatch:[],errorNetwork:[],errorPermissionBlocked:[],errorPermissionDenied:[]},h=0,i=!1,j="font-weight: bold; color: #00f;",k=!1,l=/\s*\((.*?)\)\s*/g,m=/(\(\?:[^)]+\))\?/g,n=/(\(\?)?:\w+/g,o=/\*\w+/g,p=/[\-{}\[\]+?.,\\\^$|#]/g,q=function(a){return a=a.replace(p,"\\$&").replace(l,"(?:$1)?").replace(n,function(a,b){return b?a:"([^\\s]+)"}).replace(o,"(.*?)").replace(m,"\\s*$1?\\s*"),new RegExp("^"+a+"$","i")},r=function(a){a.forEach(function(a){a.callback.apply(a.context)})},s=function(){t()||b.annyang.init({},!1)},t=function(){return d!==a};b.annyang={init:function(l,m){m=m===a?!0:!!m,d&&d.abort&&d.abort(),d=new c,d.maxAlternatives=5,d.continuous="http:"===b.location.protocol,d.lang="en-US",d.onstart=function(){r(g.start)},d.onerror=function(a){switch(r(g.error),a.error){case"network":r(g.errorNetwork);break;case"not-allowed":case"service-not-allowed":e=!1,r((new Date).getTime()-h<200?g.errorPermissionBlocked:g.errorPermissionDenied)}},d.onend=function(){if(r(g.end),e){var a=(new Date).getTime()-h;1e3>a?setTimeout(b.annyang.start,1e3-a):b.annyang.start()}},d.onresult=function(a){if(k)return i&&b.console.log("Speech heard, but annyang is paused"),!1;r(g.result);for(var c,d=a.results[a.resultIndex],e=0;eh;h++){var m=f[h].command.exec(c);if(m){var n=m.slice(1);return i&&(b.console.log("command matched: %c"+f[h].originalPhrase,j),n.length&&b.console.log("with parameters",n)),f[h].callback.apply(this,n),r(g.resultMatch),!0}}}return r(g.resultNoMatch),!1},m&&(f=[]),l.length&&this.addCommands(l)},start:function(c){k=!1,s(),c=c||{},e=c.autoRestart!==a?!!c.autoRestart:!0,c.continuous!==a&&(d.continuous=!!c.continuous),h=(new Date).getTime();try{d.start()}catch(f){i&&b.console.log(f.message)}},abort:function(){e=!1,t&&d.abort()},pause:function(){k=!0},resume:function(){b.annyang.start()},debug:function(a){i=arguments.length>0?!!a:!0},setLanguage:function(a){s(),d.lang=a},addCommands:function(a){var c,d;s();for(var e in a)if(a.hasOwnProperty(e)){if(c=b[a[e]]||a[e],"function"!=typeof c)continue;d=q(e),f.push({command:d,callback:c,originalPhrase:e})}i&&b.console.log("Commands successfully loaded: %c"+f.length,j)},removeCommands:function(b){return b===a?void(f=[]):(b=Array.isArray(b)?b:[b],void(f=f.filter(function(a){for(var c=0;c 1) { 349 | camel_words = element_words.map(function(word, index) { 350 | return (index == 0) ? word : word.charAt(0).toUpperCase() + word.slice(1); 351 | }) 352 | element = camel_words.join(""); 353 | } 354 | 355 | if (hint) { 356 | checks = checks.concat([ 357 | hint + '#' + element, 358 | hint + '[id^="' + element + '"]', 359 | hint + '.' + element, 360 | '#' + element + ' ' + hint, 361 | '*[id^="' + element + '"] ' + hint, 362 | '.' + element + ' ' + hint, 363 | 364 | hint + '#' + dashed_element, 365 | hint + '[id^="' + dashed_element + '"]', 366 | hint + '.' + dashed_element, 367 | '#' + dashed_element + ' ' + hint, 368 | '*[id^="' + dashed_element + '"] ' + hint, 369 | '.' + dashed_element + ' ' + hint, 370 | 371 | hint + '#' + underscored_element, 372 | hint + '[id^="' + underscored_element + '"]', 373 | hint + '.' + underscored_element, 374 | '#' + underscored_element + ' ' + hint, 375 | '*[id^="' + underscored_element + '"] ' + hint, 376 | '.' + underscored_element + ' ' + hint 377 | 378 | ]); 379 | if (hint == 'button') { 380 | checks = checks.concat([ 381 | 'input#' + element + '[type="submit"]', 382 | 'input[id^="' + element + '"][type="submit"]', 383 | 'input.' + element + '[type="submit"]', 384 | 385 | 'input#' + dashed_element + '[type="submit"]', 386 | 'input[id^="' + dashed_element + '"][type="submit"]', 387 | 'input.' + dashed_element + '[type="submit"]', 388 | 389 | 'input#' + underscored_element + '[type="submit"]', 390 | 'input[id^="' + underscored_element + '"][type="submit"]', 391 | 'input.' + underscored_element + '[type="submit"]', 392 | 393 | 'input[type="submit"][value="' + element + '"]', 394 | 'input[type="button"][value="' + element + '"]' 395 | 396 | ]); 397 | } 398 | } 399 | 400 | checks = checks.concat([ 401 | '#' + element, 402 | '*[id^="' + element + '"]', 403 | '.' + element, 404 | 405 | '#' + dashed_element, 406 | '*[id^="' + dashed_element + '"]', 407 | '.' + dashed_element, 408 | 409 | '#' + underscored_element, 410 | '*[id^="' + underscored_element + '"]', 411 | '.' + underscored_element 412 | ]); 413 | 414 | found = checks.filter(function(check) { 415 | return document.querySelectorAll(check).length; 416 | }); 417 | 418 | console.log('found: ', found); 419 | 420 | if (found && found[0]) { 421 | var el = document.querySelectorAll(found[0])[0]; 422 | return el; 423 | } 424 | 425 | } 426 | 427 | var setStyle = function(style, value) { 428 | console.log('setStyle - style: ', style, ' value: ', value); 429 | if (!selectedElement) { 430 | return; 431 | } 432 | style_words = style.split(" "); 433 | if (style_words.length > 1) { 434 | camel_words = style_words.map(function(word, index) { 435 | return (index == 0) ? word : word.charAt(0).toUpperCase() + word.slice(1); 436 | }) 437 | style = camel_words.join(""); 438 | } 439 | 440 | value = value.replace(" pixels", "px"); 441 | value = value.replace(" pixel", "px"); 442 | value = value.replace(" pickles", "px"); 443 | value = value.replace(" percent", "%"); 444 | value = value.replace(" per-cent", "%"); 445 | 446 | value = value.replace(/(\d+),? (\d+),? (\d+)/, "rgb($1, $2, $3)"); 447 | 448 | selectedElement.style[style] = value; 449 | 450 | changeLog.push({'element': selectedElement, 'style': style, 'value': value}); 451 | 452 | }; 453 | 454 | 455 | var shakeThingsUp = function() { 456 | var allElements = document.querySelectorAll('*'); 457 | allElements = Array.prototype.slice.call(allElements, 0); 458 | 459 | shakeEm = ['DIV', 'SPAN', 'A', 'IMG', 'H1', 'H2', 'H2', 'H4', 'H5', 'H6', 460 | 'B', 'STRONG', 'I', 'EM', 'INPUT', 'BUTTON']; 461 | 462 | shakeElements = allElements.filter(function(element) { 463 | return Math.random() > 0.4 && shakeEm.indexOf(element.tagName) > -1; 464 | }); 465 | 466 | shakeElements.forEach(function(element) { 467 | element.className += "shake shake-slow"; 468 | }); 469 | }; 470 | 471 | 472 | var selectLargestImage = function() { 473 | console.log('selectLargestImage'); 474 | var images = document.getElementsByTagName('img'); 475 | images = Array.prototype.slice.call(images, 0); 476 | console.log('images.length: ', images.length); 477 | var imageSizes = images.map(function(image) { 478 | var area = image.clientWidth * image.clientHeight; 479 | return {'image': image, 'area': area}; 480 | }); 481 | imageSizes.sort(function(a, b) { 482 | return b['area'] > a['area']; 483 | }); 484 | var newSelectedElement = imageSizes[0]['image']; 485 | console.log('newSelectedElement: ', newSelectedElement); 486 | 487 | if (selectedElement) { 488 | selectedElement.className = selectedElement.className.replace(' pulse', ''); 489 | } 490 | selectedElement = newSelectedElement; 491 | selectedElement.className += ' pulse'; 492 | 493 | }; 494 | 495 | 496 | var replaceText = function(text) { 497 | if (!selectedElement) { return false; } 498 | var capitalize = function(string, capital_letter, word_to_capitalize) { 499 | console.log('capitalize - capital_letter: ', capital_letter, ' word_to_capitalize: ', word_to_capitalize); 500 | if (capital_letter.toUpperCase() == word_to_capitalize.charAt(0).toUpperCase()) { 501 | return word_to_capitalize.charAt(0).toUpperCase() + word_to_capitalize.slice(1); 502 | } 503 | }; 504 | text = text.replace(/capital (\w) (\w+)/g, capitalize); 505 | selectedElement.innerHTML = text; 506 | changeLog.push({'element': selectedElement, 'operation': 'replace text', 'value': text}); 507 | } 508 | 509 | 510 | var hideElement = function() { 511 | if (!selectedElement) { return false; } 512 | selectedElement.previousDisplay = selectedElement.style.display; 513 | selectedElement.style.display = 'none'; 514 | changeLog.push({'element': selectedElement, 'style': 'display', 'value': 'none'}); 515 | }; 516 | 517 | 518 | var showElement = function() { 519 | if (!selectedElement) { return false; } 520 | selectedElement.style.display = selectedElement.previousDisplay; 521 | changeLog.push({'element': selectedElement, 'style': 'display', 'value': selectedElement.previousDisplay}); 522 | }; 523 | 524 | 525 | var speakStyle = function(style) { 526 | console.log('speakStyle - style: ', style); 527 | if (!selectedElement) { return false; } 528 | 529 | style_words = style.split(" "); 530 | if (style_words.length > 1) { 531 | camel_words = style_words.map(function(word, index) { 532 | return (index == 0) ? word : word.charAt(0).toUpperCase() + word.slice(1); 533 | }) 534 | style = camel_words.join(""); 535 | } 536 | 537 | var value = selectedElement.style[style]; 538 | if (!value) { 539 | computed = true; 540 | if (style == 'width') { 541 | computed = selectedElement.offsetWidth + 'px'; 542 | } 543 | else if (style == 'height') { 544 | computed = selectedElement.offsetHeight + 'px'; 545 | } 546 | computedStyles = window.getComputedStyle(selectedElement); 547 | dashed_style = style.replace(' ', '-'); 548 | computed = computedStyles[dashed_style]; 549 | if (computed.includes('px')) { 550 | computed = computed.replace('px',''); 551 | computed = Math.round(computed) + ' pixels'; 552 | } 553 | } 554 | 555 | if (value) { 556 | value = value.replace("px", " pixels"); 557 | value = value.replace("%", " percent"); 558 | var msg = new SpeechSynthesisUtterance('Its ' + style + ' is ' + value + '.'); 559 | } else if (computed) { 560 | computed = computed.replace("px", " pixels"); 561 | computed = computed.replace("%", " percent"); 562 | var msg = new SpeechSynthesisUtterance('Its ' + style + ' is not set, but its computed ' + style + ' is ' + computed + '.'); 563 | } else { 564 | var msg = new SpeechSynthesisUtterance('I could not determine its ' + style + '.'); 565 | } 566 | msg.onstart = function(e) { 567 | annyang.pause(); 568 | }; 569 | msg.onend = function(e) { 570 | annyang.resume(); 571 | }; 572 | window.speechSynthesis.speak(msg); 573 | }; 574 | 575 | 576 | var showChangeLog = function() { 577 | console.table(changeLog); 578 | }; 579 | 580 | 581 | 582 | if (annyang) { 583 | 584 | annyang.debug(true); 585 | 586 | // Let's define a command. 587 | var commands = { 588 | 'show (the) change log': showChangeLog, 589 | 'output (the) change log': showChangeLog, 590 | 'print (the) change log': showChangeLog, 591 | '(show me the) change log': showChangeLog, 592 | 'hide it': hideElement, 593 | 'show it': showElement, 594 | 'select the largest image': selectLargestImage, 595 | 'shake things up': shakeThingsUp, 596 | 'select the parent': selectParent, 597 | 'select the :ordinal child': selectChild, 598 | 'select its parent': selectParent, 599 | 'select its :ordinal child': selectChild, 600 | '(go to the) next element': selectNext, 601 | 'select (the) next element': selectNext, 602 | '(go to the) next sibling': selectNext, 603 | 'select (the) next sibling': selectNext, 604 | '(go to the) previous element': selectPrevious, 605 | 'select (the) previous element': selectPrevious, 606 | '(go to the) previous sibling': selectPrevious, 607 | 'select (the) previous sibling': selectPrevious, 608 | 'select the text *text': selectText, 609 | 'select the *element': selectElement, 610 | 'change (the) text to *value': replaceText, 611 | 'replace (the) text with *value': replaceText, 612 | 'change the *style to *value': setStyle, 613 | 'set the *style to *value': setStyle, 614 | 'change (its) *style to *value': setStyle, 615 | 'set (its) *style to *value': setStyle, 616 | 'tell me its *style': speakStyle, 617 | 'tell me the *style': speakStyle 618 | }; 619 | 620 | 621 | // Add our commands to annyang 622 | annyang.addCommands(commands); 623 | 624 | // Start listening. 625 | annyang.start(); 626 | } 627 | 628 | --------------------------------------------------------------------------------