Youtube Player Speed Slider

Add Speed Slider to Youtube Player Settings

当前为 2016-12-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Player Speed Slider
  3. // @namespace youtube_player_speed_slider
  4. // @version 0.2.1
  5. // @description Add Speed Slider to Youtube Player Settings
  6. // @author Łukasz
  7. // @match https://*.youtube.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. var yts = {};
  12.  
  13. // yts.env = "dev";
  14. yts.env = "prod";
  15.  
  16. yts.option = {
  17. timeoutBuild: 500,
  18. timeoutRemove: 1000,
  19. lastClick: 0
  20. };
  21.  
  22. yts.elements = {
  23. menu : null,
  24. speedMenu: null,
  25. slider: null,
  26. sliderLabel: null,
  27. annot: null
  28. };
  29.  
  30. yts.event = {
  31. speedRemove: false,
  32. playerObserve: false,
  33. addEnd: true
  34. };
  35.  
  36. yts.modules = {
  37. 'before':[
  38. 'click',
  39. 'setting',
  40. 'i18n',
  41. 'ad'
  42. ],
  43. 'after':[
  44. 'slider',
  45. 'menu',
  46. 'annot',
  47. 'player',
  48. 'appmenu'
  49. ],
  50. 'spfdone':[
  51. 'appmenu',
  52. 'player',
  53. 'annot'
  54. ],
  55. 'afterAd':[
  56. ''
  57. ]
  58. };
  59.  
  60. /*************************************
  61. * TRANSLATION *
  62. ************************************/
  63. yts.i18n = {};
  64.  
  65. yts.i18n.dic = {
  66. pl: {
  67. 'Speed': 'Szybkość',
  68. 'Settings': 'Ustawienia',
  69. 'Language': 'Język',
  70. 'Switch off advertisement': 'Wyłączaj reklamy',
  71. 'Hide annotation': 'Ukryj adnotacje',
  72. 'Remember speed': 'Pamiętaj szybkość'
  73. },
  74. de: {
  75. 'Speed' : 'Geschwindigkeit',
  76. 'Settings': 'Einstellungen',
  77. 'Language': 'Sprache',
  78. 'Switch off advertisement': 'Abschalten Werbung',
  79. 'Hide annotation': 'Ausblenden Anmerkungen',
  80. 'Remember speed': 'Erinnern Geschwindigkeit'
  81. },
  82. fr:{
  83. 'Speed':'Vitesse',
  84. 'Settings':'Paramètres',
  85. 'Language': 'La langue',
  86. 'Switch off advertisement': 'Désactiver la publicité',
  87. 'Hide annotation': 'Cacher les annotations',
  88. 'Remember speed': 'Rappelez-vous la vitesse'
  89. },
  90. en:{}
  91. };
  92.  
  93. yts.i18n.opt = {
  94. lang: 'en',
  95. default: 'en',
  96. map:{
  97. pl: 'pl',
  98. pl_pl: 'pl',
  99. en: 'en',
  100. en_gb: 'en',
  101. de: 'de',
  102. fr: 'fr'
  103. }
  104. };
  105.  
  106. yts.i18n.init = function () {
  107. yts.i18n.opt.lang = yts.i18n.getLang();
  108. yts.setting.change('lang', function (val) {
  109. yts.i18n.opt.lang = yts.i18n.getLang();
  110. });
  111. };
  112.  
  113. yts.i18n.getLang = function () {
  114. var lang = yts.i18n.filter(yts.setting.get('lang'));
  115. if(lang !== '' && yts.i18n.isAllow(lang)){
  116. return yts.i18n.map(lang);
  117. }
  118.  
  119. lang = yts.i18n.filter(yts.$.get('html').getAttribute('lang'));
  120. if(lang !== '' && yts.i18n.isAllow(lang)){
  121. return yts.i18n.map(lang);
  122. }
  123.  
  124. return yts.i18n.opt.default;
  125. };
  126.  
  127. yts.i18n.isAllow = function (lang) {
  128. return yts.i18n.opt.map.hasOwnProperty(lang);
  129. };
  130.  
  131. yts.i18n.t = function (pattern) {
  132. if(yts.i18n.dic.hasOwnProperty(yts.i18n.opt.lang) &&
  133. yts.i18n.dic[yts.i18n.opt.lang].hasOwnProperty(pattern)){
  134. return yts.i18n.dic[yts.i18n.opt.lang][pattern];
  135. }
  136. else {
  137. return pattern;
  138. }
  139. };
  140.  
  141. yts.i18n.filter = function (lang) {
  142. return lang ? lang.replace('-', '_').toLowerCase() : '';
  143. };
  144.  
  145. yts.i18n.map = function (lang) {
  146. return yts.i18n.opt.map[lang]
  147. };
  148.  
  149. yts.i18n.all = function () {
  150. var ret=[];
  151. for(var p in yts.i18n.dic) if(yts.i18n.dic.hasOwnProperty(p)) ret.push(p);
  152. return ret;
  153. };
  154.  
  155. /*************************************
  156. * INIT *
  157. ************************************/
  158. yts.init = function () {
  159. yts.log('Init App');
  160. yts.modules.before.forEach(function (module) {
  161. yts.run(module);
  162. });
  163. yts.$.event(document, "spfdone", function () {
  164. yts.modules.spfdone.forEach(function (module) {
  165. yts.run(module);
  166. });
  167. });
  168.  
  169. yts.option.lastClick = (new Date()).getTime();
  170. yts.menu.reopen();
  171. yts.buildApp(0);
  172. };
  173.  
  174. yts.run = function (module) {
  175. try{
  176. yts.log('Init ' + module + ' module');
  177. yts[module].init();
  178. }
  179. catch (e){
  180. yts.log('Error in ' + module + ' module');
  181. }
  182. };
  183.  
  184. yts.buildApp = function (num) {
  185. num++;
  186. yts.elements.menu = yts.$.get('.ytp-panel-menu');
  187. if (yts.elements.menu !== null) {
  188. yts.log('Init Build Menu after ' + num + ' attempt(s)');
  189. setTimeout(yts.menu.removeDefaultSpeed, yts.option.timeoutRemove);
  190. yts.modules.after.forEach(function (module) {
  191. yts.run(module);
  192. });
  193. }
  194. else {
  195. yts.log('Settimeout buildApp attempt: ' + num);
  196. setTimeout(yts.buildApp, yts.option.timeoutBuild, num);
  197. }
  198. };
  199.  
  200.  
  201. /*************************************
  202. * MENU *
  203. ************************************/
  204. yts.menu = {};
  205.  
  206. yts.menu.init = function () {
  207.  
  208. var speedMenu = yts.$.new('div', {'className': 'ytp-menuitem', id:'yts-menu'});
  209. var right = yts.$.new('div', {'className': 'ytp-menuitem-content'});
  210.  
  211. speedMenu.appendChild(yts.elements.sliderLabel);
  212. speedMenu.appendChild(right.appendChild(yts.elements.slider));
  213. yts.elements.menu.appendChild(speedMenu);
  214. yts.menu.event();
  215. };
  216.  
  217. yts.menu.event = function () {
  218. yts.elements.menu.addEventListener('click', yts.menu.click);
  219. };
  220.  
  221. yts.menu.removeDefaultSpeed = function(){
  222. var switchers = yts.$.getOpt(".ytp-menuitem", {role:'menuitemcheckbox'});
  223. var toRemove = null;
  224.  
  225. if(!yts.player.hasClass('ad-interrupting') &&
  226. switchers &&
  227. switchers.length &&
  228. !yts.event.speedRemove
  229. ){
  230. toRemove = switchers[switchers.length-1].nextElementSibling;
  231. if(toRemove && toRemove.id !== 'yts-menu'){
  232.  
  233. yts.log('Remove default speed menu item');
  234. yts.$.style(toRemove, 'display', 'none');
  235. yts.event.speedRemove = true;
  236. }
  237. }
  238. };
  239.  
  240. yts.menu.reopen = function () {
  241. var settings_button = yts.$.get(".ytp-settings-button");
  242. settings_button && settings_button.click();
  243. settings_button && settings_button.click();
  244. };
  245.  
  246. yts.menu.click = function () {
  247. yts.option.lastClick = (new Date()).getTime();
  248. };
  249.  
  250. /*************************************
  251. * SLIDER *
  252. ************************************/
  253. yts.slider = {};
  254.  
  255. yts.slider.init = function () {
  256. var speed = yts.setting.get('speed') || 1;
  257. speed = yts.setting.get('rem') ? speed : 1;
  258. yts.elements.sliderLabel = yts.$.new('div', {'className': 'ytp-menuitem-label'});
  259. yts.elements.slider = yts.$.new('input', {
  260. 'className': '',
  261. 'type': 'range',
  262. 'min': 0.5,
  263. 'max': 4,
  264. 'step': 0.1,
  265. 'value': speed,
  266. style:{
  267. 'width': '90%',
  268. 'margin':'0 5%'
  269. }
  270. });
  271. yts.$.event(yts.elements.slider, 'change', yts.slider.change);
  272. yts.$.event(yts.elements.slider, 'input', yts.slider.move);
  273.  
  274. yts.slider.updateLabel(speed);
  275. };
  276.  
  277. yts.slider.move = function (event) {
  278. yts.slider.updateLabel(event.target.value);
  279. };
  280.  
  281. yts.slider.change = function (event) {
  282. yts.player.duration(event.target.value);
  283. };
  284.  
  285. yts.slider.updateLabel = function (val) {
  286. yts.elements.sliderLabel.innerHTML = yts.i18n.t('Speed') + ': ' + parseFloat(val).toFixed(1);
  287. };
  288.  
  289.  
  290. /*************************************
  291. * PLAYER *
  292. ************************************/
  293. yts.player = {};
  294.  
  295. yts.player.init = function(){
  296. yts.elements.player = yts.$.get('.html5-main-video');
  297. yts.player.observe();
  298. if(yts.setting.get('speed') && yts.setting.get('rem')){
  299. yts.player.duration(yts.setting.get('speed'));
  300. yts.slider.updateLabel(yts.setting.get('speed'));
  301. }
  302.  
  303. };
  304.  
  305. yts.player.hasClass = function (cls) {
  306. yts.player.init();
  307. return yts.elements.player && yts.elements.player.classList.contains(cls)
  308. };
  309.  
  310. yts.player.duration = function(value){
  311. yts.setting.set('speed', value);
  312. yts.elements.player.playbackRate = value;
  313. };
  314.  
  315. yts.player.observe = function () {
  316. if(!yts.event.playerObserve){
  317. yts.observer.init(yts.elements.player.parentNode.parentNode, function (mutation) {
  318. if(/html5-video-player/.test(mutation.target.className) && !yts.event.speedRemove) {
  319. yts.menu.removeDefaultSpeed();
  320. }
  321. });
  322. yts.event.playerObserve = true;
  323. }
  324. };
  325.  
  326. /*************************************
  327. * ANNOTATION *
  328. ************************************/
  329. yts.annot = {};
  330. yts.annot.init = function(){
  331. yts.elements.annot = yts.$.get('.video-annotations');
  332. if(yts.setting.get('annot')){
  333. yts.annot.switch("off");
  334. }
  335. yts.setting.change('annot', function (val) {
  336. if(val){
  337. yts.annot.switch("off");
  338. }
  339. else{
  340. yts.annot.switch("on");
  341. }
  342. });
  343. };
  344.  
  345. yts.annot.change = function (mutation) {
  346. if(mutation.type == "attributes" && mutation.target.getAttribute('role')=="menuitemcheckbox"){
  347. yts.annot.switch("off");
  348. }
  349. };
  350.  
  351. yts.annot.switch = function(type){
  352. if(yts.elements.annot && type == 'off'){
  353. yts.$.style(yts.elements.annot, 'display', 'none', 'important');
  354. }
  355. else if(yts.elements.annot && type == 'on'){
  356. yts.$.style(yts.elements.annot, 'display', 'block');
  357. }
  358. };
  359.  
  360. /*************************************
  361. * COOKIE *
  362. ************************************/
  363. yts.cookie ={};
  364.  
  365. yts.cookie.set = function (name, value, option) {
  366. var d = new Date();
  367. option = option || {};
  368. option.expires = option.expires || 366;
  369. d.setTime(d.getTime() + (option.expires*24*60*60*1000));
  370. option.expires = d.toUTCString();
  371. option.path = option.path || '/';
  372.  
  373. var cookie = name + "=" + value + "; ";
  374. for(var prop in option){
  375. cookie += prop + "=" + option[prop] +"; ";
  376. }
  377. document.cookie = cookie;
  378. };
  379.  
  380. yts.cookie.get = function (name) {
  381. name += "=";
  382. var ca = document.cookie.split(';');
  383. for(var i = 0; i <ca.length; i++) {
  384. var c = ca[i];
  385. while (c.charAt(0)==' ') {
  386. c = c.substring(1);
  387. }
  388. if (c.indexOf(name) == 0) {
  389. return c.substring(name.length,c.length);
  390. }
  391. }
  392. return null;
  393. };
  394.  
  395. /************************************
  396. * DOM *
  397. ************************************/
  398. yts.$ = {};
  399.  
  400. yts.$.event = function (obj, event, callback) {
  401. obj.addEventListener(event, callback);
  402. };
  403.  
  404. yts.$.new = function (tag, option) {
  405. var element = document.createElement(tag);
  406. for (var param in option) {
  407. if(param == 'data' || param == 'style'|| param == 'attr'){
  408. for(var data in option[param]){
  409. yts.$[param](element, data, option[param][data]);
  410. }
  411. }
  412. else{
  413. element[param] = option[param];
  414. }
  415. }
  416. return element;
  417. };
  418.  
  419. yts.$.get = function (tselector, all) {
  420. all = all || false;
  421. var type = tselector.substring(0, 1);
  422. var selector = tselector.substring(1);
  423. var elements;
  424. if (type == "#") {
  425. return document.getElementById(selector);
  426. }
  427. else if (type == ".") {
  428. elements = document.getElementsByClassName(selector);
  429. }
  430. else{
  431. elements = document.querySelectorAll(tselector);
  432. }
  433.  
  434. if (all) {
  435. return elements;
  436. }
  437. else {
  438. return elements.length ? elements[0] : null;
  439. }
  440. };
  441.  
  442. yts.$.data = function (elem, key, val) {
  443. key = key.replace(/-(\w)/gi, function(x){return x.charAt(1).toUpperCase()});
  444. if(typeof val !== 'undefined'){
  445. elem.dataset[key] = val;
  446. }
  447. return elem.dataset[key];
  448.  
  449. };
  450.  
  451. yts.$.style = function (elem, key, val, priority) {
  452. priority = priority || '';
  453. if(typeof val !== 'undefined'){
  454. elem.style.setProperty(key, val, priority);
  455. }
  456. return elem.style.getPropertyValue(key);
  457.  
  458. };
  459.  
  460. yts.$.attr = function (elem, key, val) {
  461. if(typeof val !== 'undefined'){
  462. elem.setAttribute(key, val);
  463. }
  464. return elem.getAttribute(key);
  465.  
  466. };
  467.  
  468. yts.$.getOpt = function(selector, option){
  469. var el = yts.$.get(selector, true);
  470. var pass = [];
  471. var correct;
  472. for(var i =0; i< el.length; i++){
  473. correct = true;
  474. for(var prop in option){
  475. if(!yts.$.has(el[i], prop, option[prop])){
  476. correct = false;
  477. break;
  478. }
  479. }
  480. if(correct){
  481. pass.push(el[i]);
  482. }
  483. }
  484. return pass;
  485. };
  486.  
  487. yts.$.has = function (elem, key, val) {
  488. if(elem.hasAttribute(key)){
  489. var attr = elem.getAttribute(key);
  490. if(val !== null){
  491. return attr == val;
  492. }
  493. return true;
  494. }
  495. return false;
  496. };
  497.  
  498.  
  499. /*************************************
  500. * OBSERVER *
  501. ************************************/
  502. yts.observer= {};
  503.  
  504. yts.observer.obj = null;
  505.  
  506. yts.observer.get = function () {
  507. return window.MutationObserver || window.WebKitMutationObserver;
  508. };
  509.  
  510. yts.observer.init = function (element, callback) {
  511. var MutationObserver = yts.observer.get();
  512. if( MutationObserver ){
  513. var obs = new MutationObserver(function(mutations, observer){
  514. callback(mutations[0]);
  515. });
  516.  
  517. obs.observe( element, {
  518. childList: true,
  519. subtree: true,
  520. attributes:true,
  521. characterData:true,
  522. attributeOldValue:true,
  523. characterDataOldValue:true
  524. });
  525. }
  526. };
  527.  
  528.  
  529. /*************************************
  530. * SETTINGS *
  531. ************************************/
  532. yts.setting = {};
  533. yts.setting.item = {
  534. rem:{
  535. label: 'Remember speed',
  536. type: 'switcher'
  537. },
  538. annot:{
  539. label: 'Hide annotation',
  540. type: 'switcher'
  541. },
  542. ad:{
  543. label: 'Switch off advertisement',
  544. type: 'switcher'
  545. },
  546. lang:{
  547. label: 'Language',
  548. type: 'radio',
  549. items: yts.i18n.all()
  550. }
  551. };
  552. yts.setting.settings = null;
  553. yts.setting.event = {};
  554.  
  555. yts.setting.init = function(){
  556. var setting = localStorage.getItem('yts');
  557. yts.setting.settings = JSON.parse(setting === null ? '{}' : setting);
  558. };
  559.  
  560. yts.setting.set = function(key, val){
  561. yts.setting.settings[key] = val;
  562.  
  563. if(yts.setting.event.hasOwnProperty(key)){
  564. yts.setting.event[key](val);
  565. }
  566. localStorage.setItem('yts', JSON.stringify(yts.setting.settings));
  567. };
  568.  
  569. yts.setting.get = function(kay){
  570. return yts.setting.settings.hasOwnProperty(kay) ?
  571. yts.setting.settings[kay] : null;
  572. };
  573.  
  574. yts.setting.change = function(key, callback){
  575. yts.setting.event[key] = callback;
  576. };
  577.  
  578. /*************************************
  579. * CLICK *
  580. ************************************/
  581. yts.click = {};
  582. yts.click.fun = [];
  583. yts.click.init = function(){
  584. yts.$.event(yts.$.get('body'), 'click', function(event){
  585. for(var i =0; i<yts.click.fun.length; i++){
  586. yts.click.fun[i](event);
  587. }
  588. })
  589. };
  590.  
  591. yts.click.add = function(fun){
  592. yts.click.fun.push(fun);
  593. };
  594.  
  595. /*************************************
  596. * LOGGER *
  597. ************************************/
  598.  
  599. yts.log = function (text, type) {
  600. if(yts.env == 'prod') return 1;
  601. type = type || 'l';
  602. var app = "[YT SPEED SLIDER] ";
  603. if(type == "l") console.log(app, text);
  604. else if(type == "e") console.error(app, text);
  605. else if(type == "i") console.info(app, text);
  606. else if(type == "w") console.warn(app, text);
  607. };
  608.  
  609. /*************************************
  610. * ADVERTISEMENT *
  611. ************************************/
  612. yts.ad = {};
  613. yts.ad.init = function(){
  614. if(yts.setting.get('ad')){
  615. yts.ad.switch('off');
  616. }
  617. yts.setting.change('ad', function (val) {
  618. if(val){
  619. yts.ad.switch('off');
  620. }
  621. else{
  622. yts.ad.switch('on');
  623. }
  624. });
  625. };
  626.  
  627. yts.ad.switch = function (type) {
  628. var ad = yts.$.get(".video-ads");
  629. if(type == "off"){
  630. yts.cookie.set("VISITOR_INFO1_LIVE", "oKckVSqvaGw", {domain: "youtube.com"});
  631. ad && yts.$.style(ad, 'display', 'none', 'important');
  632. }
  633. else {
  634. yts.cookie.set("VISITOR_INFO1_LIVE", "", {domain: "youtube.com", expires: -1});
  635. ad && yts.$.style(ad, 'display', 'block');
  636. }
  637. };
  638.  
  639.  
  640. /*************************************
  641. * APP MENU *
  642. ************************************/
  643. yts.appmenu = {};
  644.  
  645. yts.appmenu.init = function(){
  646. yts.appmenu.build();
  647. };
  648.  
  649. yts.appmenu.build = function(){
  650.  
  651. yts.appmenu.panel();
  652. var menu = yts.$.get("#watch8-secondary-actions");
  653. if(menu){
  654. menu.appendChild(yts.appmenu.button());
  655. }
  656. };
  657.  
  658. yts.appmenu.button = function(){
  659. var button = yts.$.new('button', {
  660. 'className':'yt-uix-button yt-uix-button-opacity action-panel-trigger yt-uix-button-toggled',
  661. 'title' : yts.i18n.t('Settings'),
  662. 'type':'button',
  663. 'innerHTML':'<span class="yt-uix-button-content">Yts</span>',
  664. 'data': {
  665. 'tooltip-text':yts.i18n.t('Settings'),
  666. 'button-toggle':'true',
  667. 'trigger-for':'action-panel-yts'
  668. }
  669. });
  670.  
  671. return button;
  672. };
  673.  
  674. yts.appmenu.panel = function(){
  675. var panel = yts.$.new('div', {
  676. 'id':'action-panel-yts',
  677. 'className':'action-panel-content',
  678. data: {'panel-loaded':'true'},
  679. style: {'display':'none'}
  680. });
  681.  
  682. var list = yts.$.new('ul', {'className':'yt-uix-kbd-nav yt-uix-kbd-nav-list'});
  683. yts.appmenu.addSettingItems(list);
  684. panel.appendChild(list);
  685. var panels = yts.$.get('#watch-action-panels');
  686. if(panels){
  687. panels.appendChild(panel);
  688. }
  689. };
  690.  
  691. yts.appmenu.addSettingItems = function (list) {
  692. for(var item in yts.setting.item){
  693. var li = yts.$.new('li', {
  694. 'className':'ytp-menuitem',
  695. 'role':"menuitemcheckbox",
  696. attr: {
  697. 'aria-checked': yts.setting.get(item) ? "true" : "false",
  698. }
  699. });
  700. var type = yts.setting.item[item].type;
  701. if(type === 'switcher'){
  702. li = yts.appmenu.switcherItem(li, item);
  703. }
  704. if(type === 'radio'){
  705. li = yts.appmenu.radioItem(li, item);
  706. }
  707.  
  708. list.appendChild(li);
  709. }
  710. };
  711.  
  712.  
  713. yts.appmenu.switcherItem = function(li, item){
  714. var switcher = yts.$.new('div', {
  715. 'className':'ytp-menuitem-toggle-checkbox',
  716. style: {
  717. 'background-color':'#d2d2d2',
  718. 'margin-left': '10px'
  719. },
  720. data: {
  721. 'name': item
  722. },
  723. 'onclick' : function (event) {
  724. var el = event.target;
  725. var parent = el.parentNode;
  726. var stat = parent.getAttribute('aria-checked');
  727. if(stat == 'false'){
  728. yts.$.attr(parent, 'aria-checked' , 'true');
  729. yts.setting.set(yts.$.data(el, 'name'), true);
  730. }
  731. else{
  732. yts.$.attr(parent, 'aria-checked' , 'false');
  733. yts.setting.set(yts.$.data(el, 'name'), false);
  734. }
  735. }
  736. });
  737. var span = yts.$.new('span', {'innerHTML':yts.i18n.t(yts.setting.item[item].label)});
  738.  
  739.  
  740. li.appendChild(switcher);
  741. li.appendChild(span);
  742. return li;
  743. };
  744.  
  745. yts.appmenu.radioItem = function(li, item){
  746. var list = yts.setting.item[item].items;
  747. var checked = yts.setting.get(item);
  748. li.appendChild(yts.$.new('div',{'innerHTML':yts.i18n.t(yts.setting.item[item].label), style:{'margin-bottom':'5px'}}));
  749.  
  750. for(var i =0; i< list.length; i++){
  751. var label = yts.$.new('label',{style:{'margin-right':'5px'}});
  752. var spanInput = yts.$.new('span', {
  753. 'className': 'yt-uix-form-input-radio-container'
  754. });
  755. var input = yts.$.new('input', {
  756. 'type': 'radio',
  757. 'name': 'yts-' + item,
  758. 'checked': list[i] == checked,
  759. data: {
  760. 'name': item,
  761. 'val': list[i]
  762. },
  763. 'onchange': function (event) {
  764. var el = event.target;
  765. yts.setting.set(yts.$.data(el, 'name'), yts.$.data(el, 'val'));
  766. }
  767. });
  768. var spanCheck = yts.$.new('span', {'className': 'yt-uix-form-input-radio-element'});
  769. var spanLabel = yts.$.new('span', {'innerHTML': list[i]});
  770.  
  771. spanInput.appendChild(input);
  772. spanInput.appendChild(spanCheck);
  773.  
  774. label.appendChild(spanInput);
  775. label.appendChild(spanLabel);
  776.  
  777. li.appendChild(label);
  778.  
  779. }
  780. return li;
  781. };
  782.  
  783.  
  784. yts.init();