├── 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 |
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 |
--------------------------------------------------------------------------------