您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds the average points row to the scrumpoker-online.org page.
// ==UserScript== // @name scrumpoker-online.org Average Points // @description Adds the average points row to the scrumpoker-online.org page. // @icon https://www.google.com/s2/favicons?sz=64&domain=scrumpoker-online.org // @author Antonio Terrero <[email protected]> // @namespace http://github.com/ihton // @license GPL-3.0-or-later // @copyright Copyright (C) 2024, Antonio Terrero <[email protected]> // @match https://*.scrumpoker-online.org/*/room/*/scrum-poker // @version 1.3 // @run-at document-end // ==/UserScript== (() => { /** -------------------------- * Utilities * -------------------------- */ const $ = (selector, ctx = document) => ctx.querySelector(selector); const $$ = (selector, ctx = document) => Array.from(ctx.querySelectorAll(selector)); const parsePoints = (spans) => spans .map((span) => parseFloat(span.textContent.trim())) .filter((n) => !isNaN(n)); const mean = (values) => { if (!values.length) return ''; const avg = values.reduce((a, b) => a + b, 0) / values.length; return Number.isInteger(avg) ? `${avg}` : avg.toFixed(1); }; const getNgContentAttr = (row) => { if (!row) return null; return [...row.attributes].find((a) => a.name.startsWith('_ngcontent-')) || null; }; const applyNgContentRecursively = (el, ngAttr) => { if (!ngAttr || !el) return; el.setAttribute(ngAttr.name, ngAttr.value); el.querySelectorAll('*').forEach((child) => child.setAttribute(ngAttr.name, ngAttr.value) ); }; const createCell = ({ html, className }) => { const td = document.createElement('td'); td.className = className; td.setAttribute('role', 'cell'); td.setAttribute('mat-cell', ''); if (html) td.innerHTML = html; return td; }; /** -------------------------- * Core logic * -------------------------- */ function updateMeanRow(table) { const spans = $$('tr:not(.mean-row) .flip-card-back span', table); const points = parsePoints(spans); const value = mean(points); const firstRow = $('tbody tr:not(.mean-row)', table); const ngAttr = getNgContentAttr(firstRow); const cardClass = $('.flip-card', table)?.className || 'flip-card'; let meanRow = $('tr.mean-row', table); if (!meanRow) { meanRow = document.createElement('tr'); meanRow.className = 'mat-mdc-row mdc-data-table__row cdk-row ng-star-inserted mean-row'; meanRow.setAttribute('role', 'row'); meanRow.setAttribute('mat-row', ''); const labelCell = createCell({ html: '<span style="font-weight: bold">Average</span>', className: 'mat-mdc-cell mdc-data-table__cell cdk-cell cdk-column-displayName mat-column-displayName ng-star-inserted', }); const valueCell = createCell({ className: 'mat-mdc-cell mdc-data-table__cell cdk-cell story-points points-column cdk-column-storyPoints mat-column-storyPoints ng-star-inserted', html: ` <div class="${cardClass}"> <div class="flip-card-inner"> <div class="flip-card-front"> <img src="assets/images/logo_trans.png" alt="SP" style="width:30px;height:30px;" class="ng-star-inserted"> </div> <div class="flip-card-back"> <span class="ng-star-inserted">${value}</span> </div> </div> </div>`, }); meanRow.append(labelCell, valueCell); $('tbody', table).appendChild(meanRow); } else { // Update the card class if it has changed const cardDiv = $('.flip-card', meanRow); cardDiv.className = cardClass; // Update the existing value const span = $('.flip-card-back span', meanRow); if (span) span.textContent = value; } // Apply Angular attribute to everything inside the new row if (ngAttr) applyNgContentRecursively(meanRow, ngAttr); } /** -------------------------- * Init with safe MutationObserver * -------------------------- */ function init() { const appRoot = $('app-root'); if (!appRoot) return; const rootObserver = new MutationObserver(() => { const table = $('table.mat-mdc-table'); if (!table) return; rootObserver.disconnect(); const tableObserver = new MutationObserver(() => { tableObserver.disconnect(); updateMeanRow(table); tableObserver.observe(table, { childList: true, subtree: true, attributes: true, characterData: true, }); }); // initial run updateMeanRow(table); // start observing table changes tableObserver.observe(table, { childList: true, subtree: true, attributes: true, characterData: true, }); }); rootObserver.observe(appRoot, { childList: true, subtree: true }); } init(); })();