// ==UserScript==
// @name Google Classroom | Interface modification
// @name:en Google Classroom | Interface modification
// @name:ru Google Classroom | Модификация интерфейса
// @name:uk Google Classroom | Модифікація інтерфейсу
// @name:bg Google Classroom | Модификация на интерфейса
// @description Modification of the interface for the Google Classroom.
// @description:en Modification of the interface for the Google Classroom.
// @description:ru Модификация интерфейса для Google Classroom.
// @description:uk Модифікація інтерфейсу для Google Classroom.
// @description:bg Модификация на интерфейса за Google Classroom.
// @iconURL https://ssl.gstatic.com/classroom/favicon.png
// @version 1.4
// @match https://classroom.google.com/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @noframes
// @namespace https://stomaks.me
// @supportURL https://stomaks.me?feedback
// @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=stomaks@gmail.com&item_name=Greasy+Fork+donation
// @author Maxim Stoyanov (stomaks)
// @developer Maxim Stoyanov (stomaks)
// @license MIT
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible safari
// ==/UserScript==
(function() {
'use strict';
// Интеграция иконок
$(`head`).append(`
<link rel="preload" as="font" href="//stomaks.app/fonts/MaterialIcons/MaterialIcons1.woff2" type="font/woff2" crossorigin="anonymous">
<link rel="preload" as="font" href="//stomaks.app/fonts/MaterialIcons/MaterialIcons2.woff2" type="font/woff2" crossorigin="anonymous">
<link href="//stomaks.app/styles/icons.min.css" rel="stylesheet">`);
// Интеграция стилей
{
$(`head`).append(`
<style>
.tdS5P {
z-index: 900;
}
ol.FpfvHe > li .ClSQxf > [expanded] {
border-radius: 100px;
}
ol.FpfvHe > li .ClSQxf > [expanded] > i {
transition:
opacity 250ms 0ms cubic-bezier(.4, 0, .2, 1),
transform 250ms 0ms cubic-bezier(.4, 0, .2, 1);
padding: 8px;
border-radius: 100px;
}
ol.FpfvHe > li[expanded="false"] .ClSQxf > [expanded] > i {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
ol.FpfvHe > li[expanded]:not([expanded="true"]) > .zq2w8b {
display: none;
}
.xUYklb {
font-size: 20px;
}
.SRX5Hd {
position: fixed;
bottom: 45px;
right: 45px;
height: auto;
width: auto;
z-index: 900;
}
.SRX5Hd .aS18D.p0oLxb {
min-width: inherit;
}
.SRX5Hd .GcVcmc .RdyDwe {
display: none;
}
.SRX5Hd .aS18D.p0oLxb .Fxmcue.cd29Sd {
padding: 20px;
text-align: center;
}
.SRX5Hd .aS18D.p0oLxb .Ce1Y1c {
margin: 0;
}
.Kb1iQ {
margin: 0 0 0 auto;
}
.JPdR6b.e5Emjc.hVNH5c.bzD7fd.qjTEB {
position: fixed !important;
max-height: initial !important;
top: initial !important;
left: initial !important;
right: 25px !important;
bottom: 35px !important;
}
.JPdR6b.e5Emjc.hVNH5c.bzD7fd.qjTEB > .XvhY1d {
max-height: initial !important;
}
.JPdR6b.e5Emjc.hVNH5c.bzD7fd.qjTEB #stomaks_classroom_automation [icon="open_in_new"] {
opacity: .25;
font-size: 18px;
margin: auto;
position: absolute;
right: 10px;
top: 8px;
z-index: 1;
}
main {
padding-bottom: 75px;
}
.ClSQxf {
display: flex;
flex-direction: row;
}
/* # alt */
body > [name="alt"] {
position: absolute;
top: 50%;
left: 50%;
border-radius: 2px;
padding: 5px 10px;
max-width: 500px;
background: rgba(97, 97, 97, 0.9);
-webkit-transition: opacity 250ms 250ms cubic-bezier(.4, 0, .2, 1),
transform 250ms 250ms cubic-bezier(.4, 0, .2, 1),
top 250ms 0ms cubic-bezier(.4, 0, .2, 1),
left 250ms 0ms cubic-bezier(.4, 0, .2, 1);
transition: opacity 250ms 250ms cubic-bezier(.4, 0, .2, 1),
transform 250ms 250ms cubic-bezier(.4, 0, .2, 1),
top 250ms 0ms cubic-bezier(.4, 0, .2, 1),
left 250ms 0ms cubic-bezier(.4, 0, .2, 1);
-webkit-transform: scale3d(0, 0, 0);
transform: scale3d(0, 0, 0);
-webkit-transform-origin: top left;
transform-origin: top left;
opacity: 0;
pointer-events: none;
z-index: 970;
}
body > [name="alt"],
body > [name="alt"] > * {
color: #fff;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 10px;
line-height: initial;
letter-spacing: .5px;
}
body > [name="alt"][state="show"] {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
opacity: 1;
}
</style>`);
}
// Интеграция кнопки споилера
$(`body`).append(`<div name="alt"></div>`);
//+----------------------------------------------------------------------------------------------+
// Установка подсказок
function tick () {
// Скрыть темы
{
Object.keys(localStorage).forEach(function ( key, i ) {
const value = window.localStorage.getItem(key);
$(`ol.FpfvHe > li[data-dom-id='${key}']`).attr("expanded", value);
$(`ol.FpfvHe > li[data-dom-id='${key}'] .ClSQxf > [expanded]`).attr("expanded", value);
});
}
// Для основного меню
{
const el = $(`div[role="menu"].OX4Vcb a[aria-label]`);
el.each(function () {
const el_text = $(this).attr("aria-label");
$(this).attr("alt", el_text);
});
}
// Установка подсказок - Для меню
{
const el = $(`aside.GP1o5c ul > li`);
el.each(function () {
const el_text = $(this).find(`div.YVvGBb`).text();
$(this).attr("alt", el_text);
});
}
// Установка подсказок - Для тем
{
const el = $(`ol.FpfvHe > li > div[data-topic-id]`);
el.each(function () {
const el_text = $(this).find(`> div > a`).text();
$(this).attr("alt", el_text);
});
}
// Установка подсказок - Для елементов темы
{
const el = $(`ol.Xzp3fc > li > div`);
el.each(function () {
const el_text = $(this).find(`.kByKEb > span`).text();
$(this).attr("alt", el_text);
});
}
// Кнопка +
{
const el = $(`.SRX5Hd`);
const el_text = el.find(`.GcVcmc .RdyDwe`).text();
el.attr("alt", el_text);
}
// Установка кнопок для споилера
{
const el = $(`.ClSQxf`);
if ( !el.find(`[expanded]`).length ) {
el.prepend(`<div class="wwnMtb" expanded="true"><i icon="expand_less"></i></div>`);
}
}
// Добавить опцию создания
{
const el = $(`.JPdR6b.e5Emjc.hVNH5c.bzD7fd.qjTEB > div > div`);
if ( !el.find(`#stomaks_classroom_automation`).length ) {
el.append(`
<div role="separator" class="kCtYwe"></div>
<span jsslot id="stomaks_classroom_automation" tabindex="-1" class="z80M1 FeRvI" aria-label="Автоматизация" role="menuitem">
<div class="aBBjbd MbhUzd" jsname="ksKsZd"></div>
<div class="PCdOIb Ce1Y1c" aria-hidden="true">
<i icon="-cogs" class="mxmXhf NMm5M hhikbc"></i>
</div>
<div class="uyYuVb oJeWuf">
<div class="jO7h3c">Автоматизация</div>
</div>
<div>
<i icon="open_in_new"></i>
</div>
</span>`);
}
}
setTimeout(tick, 1000);
};
setTimeout(tick, 100);
//+----------------------------------------------------------------------------------------------+
//+----------------------------------------------------------------------------------------------+
/** Метод-утилита "getCoordinates" - Получает координаты курсора или элемента.
*
* @param {string|jQuery} Путь к элементу, ссылка на элемент или объект с настройками.
*
* @param {object} callback Данные для подписанных функций или функция обратного вызова.
*
* @return {object|null|function} Объект, или выполняет функцию обратного вызова.
*/
function getCoordinates ( data, callback = null, event = window.event ) {
let result = {};
try {
result.data = {};
if ( data == null ) {
result.data = {
X: event.clientX || null,
Y: event.clientY || null,
x: event.pageX || null,
y: event.pageY || null
};
} else {
switch ( typeof data ) {
case "string":
data = $(data);
case "object":
if ( data instanceof jQuery && data.is(":visible") ) {
result.data = {
X: data.position().left || null,
Y: data.position().top || null,
x: data.offset().left || null,
y: data.offset().top || null
};
}
break;
default: break;
}
}
// Без обратного вызова
if ( !callback ) {
return result.data;
}
// Функция обратного вызова
if ( typeof callback === "function" ) {
return callback( result );
}
} catch ( error ) {
result.error = error;
result.data = null;
}
return result.data;
};
//+----------------------------------------------------------------------------------------------+
//+----------------------------------------------------------------------------------------------+
/** Метод-действие "showAlt" - Отображает подсказку.
*
* @param {string|jQuery} Путь к элементу, или ссылка на элемент, или объект с настройками.
*
* @param {object} callback Данные для подписанных функций или функция обратного вызова.
*
* @return {object|null|function} Объект, или выполняет функцию обратного вызова.
*/
function showAlt ( data, callback = null, event = window.event ) {
let result = {};
result.data = {};
try {
function _ ( x, y ) {
let float = [];
let temp = x / $(`body`).width() * 100;
if ( temp <= 10 ) {
float.push("left");
} else if ( temp > 10 && temp < 90 ) {
float.push("center");
} else {
float.push("right");
}
temp = y / $(`body`).height() * 100;
if ( temp <= 10 ) {
float.push("top");
} else if ( temp > 10 && temp < 90 ) {
float.push("center");
} else {
float.push("bottom");
}
return float;
}
// Контейнер
switch ( typeof data ) {
case "string":
if ( data.length > 0 ) {
result.data.container = $(data);
break;
}
case "object":
if ( data instanceof jQuery ) {
result.data.container = data;
break;
}
default:
throw new TypeError(`Входящие данные не определены или имеют неверный тип данных.`);
}
// Подсказка
result.data.alt = result.data.container.attr("alt");
// Направление подсказки
result.data.float = [];
{
let temp = result.data.container.attr("alt-float");
if ( typeof temp === "string" ) {
temp = temp.split(" ");
result.data.float = [temp[0], temp[1]];
}
}
// Определение соответствия текста в alt и в элементе
function isAlt ( el, alt ) {
if ( el.children().length > 0 ) {
// Видимые в элементы
el = el.children(`:visible:not(.content)`).filter(function() {
return !($(this).css(`opacity`) === "0" || $(this).css(`visibility`) === "hidden");
});
}
// Текст
result.data.text = el.text().replace(/^\s+|\s+$/g, ``);
return (typeof alt === "string" && alt.length ); // && alt !== result.data.text
}
if ( isAlt(result.data.container, result.data.alt) ) {
$(`body > div[name="alt"]`)
.attr("state", "show")
.html( result.data.alt );
// Получить координаты контейнера
result.data.coordinates = getCoordinates( data );
// Валидация направления подсказки
{
let isX = false;
if ( result.data.float[0] === "left" || result.data.float[0] === "center" || result.data.float[0] === "right" ) {
isX = true;
}
let isY = false;
if ( result.data.float[1] === "top" || result.data.float[1] === "center" || result.data.float[1] === "bottom" ) {
isY = true;
}
if ( !isX || !isY || result.data.float.length !== 2 ) {
// Получить положение контейнера
let temp = _(result.data.coordinates.x, result.data.coordinates.y);
if ( !isX ) {
result.data.float = [temp[0], result.data.float[1]];
}
if ( !isY ) {
result.data.float = [result.data.float[0], temp[1]];
}
}
}
let app_width = $(`body`).outerWidth() || $(`body`).width();
let app_height = $(`body`).outerHeight() || $(`body`).height();
let alt_width = $(`body > div[name="alt"]`).outerWidth() || $(`body > div[name="alt"]`).width();
let alt_height = $(`body > div[name="alt"]`).outerHeight() || $(`body > div[name="alt"]`).height();
let container_width = result.data.container.outerWidth() || result.data.container.width();
let container_height = result.data.container.outerHeight() || result.data.container.height();
switch ( result.data.float.join(" ") ) {
case "left top": // ↘
result.data.coordinates.y += container_height + 10;
break;
case "left center": // →
result.data.coordinates.x += container_width + 20;
result.data.coordinates.y += (container_height - alt_height ) / 2;
break;
case "left bottom": // ↗
result.data.coordinates.x += container_width + 20;
result.data.coordinates.y += (container_height - alt_height ) / 2;
break;
case "center top": // ↓
result.data.coordinates.x += ((container_width - alt_width) / 2);
result.data.coordinates.y += container_height + 10;
break;
case "center center": // •
result.data.coordinates.x += ((container_width - alt_width) / 2);
result.data.coordinates.y += container_height + 10;
break;
case "center bottom": // ↑
result.data.coordinates.x += ((container_width - alt_width) / 2);
result.data.coordinates.y -= alt_height + 10;
break;
case "right top": // ↙
result.data.coordinates.x += container_width - alt_width;
result.data.coordinates.y += container_height + 10;
break;
case "right center": // ←
result.data.coordinates.x -= alt_width + 25;
result.data.coordinates.y += (container_height - alt_height ) / 2;
break;
case "right bottom": // ↖
result.data.coordinates.x -= alt_width + 25;
result.data.coordinates.y += (container_height - alt_height ) / 2;
break;
}
if ( result.data.coordinates.x < 20 ) {
result.data.coordinates.x = 20;
}
if ( result.data.coordinates.x + 40 >= app_width ) {
result.data.coordinates.x = app_width - (alt_width + 20);
}
if ( result.data.coordinates.y < 20 ) {
result.data.coordinates.y = 20;
}
if ( result.data.coordinates.y + 40 >= app_height ) {
result.data.coordinates.y = app_height - (alt_height + 20);
}
$(`body > div[name="alt"]`)
.css({
"left": result.data.coordinates.x,
"top": result.data.coordinates.y,
"-webkit-transform-origin": result.data.float.join(" "),
"transform-origin": result.data.float.join(" ")
});
} else {
$(`body > div[name="alt"]`)
.attr("state", "hide");
}
// Без обратного вызова
if ( !callback ) {
return result.data;
}
// Функция обратного вызова
if ( typeof callback === "function" ) {
return callback( result );
}
} catch ( error ) {
result.error = error;
result.data = null;
}
return result.data;
};
//+----------------------------------------------------------------------------------------------+
$(`html > body`)
// Показать подсказку
.on("mouseover focus", "*", function ( event = window.event ) {
// Контейнер
let el_container = $(this).find(event.target).closest(`[alt]`);
showAlt( el_container );
})
// [Свернуть|Развернуть споилер]
.on("mouseup", ".ClSQxf > [expanded]", function ( event = window.event ) {
const el = $(this);
const el_container = el.closest(`li`);
let expanded = el.attr("expanded");
const id = el_container.attr("data-dom-id");
if ( expanded == "true" ) {
expanded = "false";
} else {
expanded = "true";
}
el.attr("expanded", expanded);
el_container.attr("expanded", expanded);
localStorage.setItem(id, expanded);
console.log( id, expanded );
})
// Показать подсказку
.on("mouseover focus", "#stomaks_classroom_automation", function ( event = window.event ) {
const el = $(this);
el.addClass("FwR7Pc");
})
// Кнопка "Автоматизация"
.on("mouseover focus", "#stomaks_classroom_automation", function ( event = window.event ) {
const el = $(this);
el.addClass("FwR7Pc");
})
.on("mouseleave focusout", "#stomaks_classroom_automation", function ( event = window.event ) {
const el = $(this);
el.removeClass("FwR7Pc");
})
.on("mouseup", "#stomaks_classroom_automation", function ( event = window.event ) {
window.open("https://g-apps-script.com/blog/google-klass", "_blank");
})
;
})();