NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Duolingo Mods // @description Includes Lesson Review, Easy Accents, and other miscellaneous mods. // @match *://www.duolingo.com/* // @author HodofHod // @namespace HodofHod // @version 0.3.2 // ==/UserScript== /* Copyright (c) 2013-2014 HodofHod (https://github.com/HodofHod) Licensed under the MIT License (MIT) Full text of the license is available at https://raw2.github.com/HodofHod/Userscripts/master/LICENSE */ function inject(f) { var script = document.createElement('script'); script.type = 'text/javascript'; script.textContent = '(' + f.toString() + ')()'; script.setAttribute("name", "duo-mods"); document.head.appendChild(script); } inject(main); function main(){ //modified from developer.mozilla.org/en-US/docs/Web/API/document.cookie function getCookie(sKey) { return unescape(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null; } function setCookie(sKey, sValue) { document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; domain=duolingo.com; path=/"; } var scripts = { 'Lesson Review' : {id: 'lesson_review', url: 'duolingo-lesson-review', desc: 'Adds the ability to go back and review missed problems'}, 'Easy Accents': {id: 'easy_accents', url: 'duo-easy-accents', desc: 'Type accented characters easily in lessons, using the Alt key'} },mods = { 'Discussion Search': {id: 'discussion_search', func: discussionSearch, desc: 'Expandable search box on the discussions page'}, 'Notification Links': {id: 'notification_links', func: notificationLinks, desc: 'Clicking on a notification will take you to its event page'}, 'Open in New Tab': {id: 'ctrl_new_tab', func: ctrlNewTab, desc: 'Holding ctrl when clicking a link opens a new tab. Like it\'s supposed to'}, 'Comment Links': {id: 'comment_links', func: commentLinks, desc: 'Turns the timestamps underneath comments into direct links to themselves'} }; function addMods(){ function addScript(URL, name) { var script = document.createElement("script"); script.setAttribute("src", URL); script.setAttribute("name", name); document.head.appendChild(script); } var base = ['//rawgithub.com/HodofHod/Userscripts/master/', '.user.js']; $.each(scripts, function(title, val){ var esc_name = val.id; if (getCookie(esc_name) !== 'false' && !$('script[name='+esc_name+']')[0]){ addScript(base.join(val.url), esc_name); } }); $.each(mods, function(title, val){ var esc_name = val.id; if (getCookie(esc_name) !== 'false'){ $('*').off('.' + esc_name); val.func(val.id); } }); } addMods(); $(document).on('mouseover', '#app.settings', function(){ if (!$('#mod-settings').length){ console.log('modded'); $('.radio-buttons:last').parents('li').after('<hr><li id="mod-settings" style="margin:-25px 0 20px 0;"/><hr>'); var template = $('<li style="float:left; margin-top:20px;">\ <label class="label"></label>\ <input class="border" type="checkbox" style="float:left; margin:4px 20px 0 30px;">\ </li>'); $.each($.extend({}, scripts, mods), function(title, val){ var esc_name = val.id;//[0], $setting = template.clone().find('label').text(title).attr('title', val.desc)//[2]) .end().find('input').attr('id', esc_name).end();//this line is really unnecessary. cookie = getCookie(esc_name), enabled = (cookie === "true" || !cookie); $('#mod-settings').append($setting); $setting.find('input').prop('checked', enabled); $setting.find('input').on('change', function(e){ var checked = $(this).prop('checked'); setCookie(esc_name, checked), addMods(); if (!checked){ $('script[name='+esc_name+']').remove(); $('*').off('.' + esc_name); duo.js_version = '';//forces a page reload when navigating away. } }); }); } }); function discussionSearch(nspace){ $(document).on('input.'+nspace, 'input[name=search]', function () { var $textarea = $(this), $searchtools = $('.comment-rankings>.search-topics, #ask-question'); if (!this.expanded && this.value.length >= 8){ $(this).prop('expanded', true); $('.nav-tabs').css({'clear': 'both'}); $('.comment-rankings>h1').css({'position': 'absolute'}); $searchtools.animate({ 'margin-top': '50px' }, 250, function () { //The textarea has a transition css property already //So it'll animate on its own. $textarea.css({'width': '415px', 'margin-bottom': '30px'}); }); } else if (this.expanded && this.value.length < 8) { $(this).css({'width': '', 'margin-bottom': ''}); $searchtools.delay(250).animate({ 'margin-top': '0px', }, 300); $(this).prop('expanded', false); } }); } function notificationLinks(nspace){ $(document).on('mouseover.'+nspace, '#popover-notifications:not(.event-linked)', function(){ $(this).addClass('event-linked'); var link = $('<a class="event_link">').css({ position: 'absolute', 'z-index': '1', width: '100%', height: '100%', top: 0, left: 0 }), ids = $.map(duo.user.get('notification_events'), function(el){ return el.id; }); $('.list-notifications>li').css('position', 'relative').each(function(i){ $(this).find('a').css({'z-index': '9', 'position': 'relative'}).end() .prepend(link.clone().attr('href', '/event/'+ids[i])); }); $('.event_link').hover(function () { $(this).css('background-color', 'rgba(0, 0, 0, 0.009)'); }, function () { $(this).css('background-color', ''); }); }); } function ctrlNewTab(nspace){ $('body').on('click.' + nspace, 'a:not([data-bypass])', function (e){ if (e.ctrlKey){ e.stopImmediatePropagation(); $(this).attr('target', '_blank').one('mouseout', function(){ $(this).removeAttr('target'); }); } }); } function commentLinks(nspace){ commentTimes(); $(document).on('mouseover', '.discussion-comments-list:not(.dlinked)', function (){ $(this).addClass('dlinked'); base_url = document.location.pathname.replace(/\$.+$/, ''); $('li[id*=comment-] .body').each(function(){ var $timestamp = $(this).next('.footer').contents().filter(function(){ return this.nodeType === 3; }), direct = '$comment_id=' + this.id.replace(/^body-(\d+)$/, '$1'), $link = $('<a class="direct-comment-link">') .attr('href', base_url+direct) .text($timestamp.text()); $timestamp.replaceWith($link); }); }); } function commentTimes(){ var comViewRender = duo.CommentView.prototype.render, listViewRender = duo.CommentListView.prototype.render, CV, LV; duo.CommentView.prototype.render = function(){ CV = this.model; return comViewRender.apply(this, arguments); }; duo.CommentListView.prototype.render = function(){ LV = this; return listViewRender.apply(this, arguments); }; $(document).on('mouseover', '.list-discussion-item-footer:not(.timeadded), .discussion-main-detail:not(.timeadded)', function(){ $(this).addClass('timeadded'); //id is only for list items and comment threads, not top comment posts. var id = this.parentNode.id.replace('comment-',''), post = (!$(this).is('footer') ? CV : $(LV.collection.models).filter(function(){ return this.id==id; })[0]), $timestamp = $(this).contents().filter(function(){return this.nodeType==3;}).eq(0); $timestamp.replaceWith( $('<a>').attr('title', gettime(post)) .text($timestamp.text()) ); }); $(document).on('mouseover', '.discussion-comments-list.dlinked:not(.timeadded)', function(){ $(this).addClass('timeadded'); var comments = CV.get('comments').models; $.each(comments, function(){ $('[id$=comment-'+this.id + ']').find('.direct-comment-link') .attr('title', gettime(this)); }); }); function gettime(a){return a.get('datetime_string').replace(/T([\d:]+)Z/,' at $1 GMT')} } }