Zum Inhalt springen

Benutzer:Lustiger seth/list2table.js

aus Wikipedia, der freien Enzyklopädie
Dies ist die aktuelle Version dieser Seite, zuletzt bearbeitet am 29. Januar 2025 um 11:41 Uhr durch Lustiger seth (Diskussion | Beiträge) (fix: not yet ready for edit filter).
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
/*
task: replace lists with tables in watchlist/history/contribs and some other logs
      and add input fields for filtering per column

german docs: [[w:de:user:lustiger_seth/list2table]]

requirements: tested in firefox and with vector (2022) only and mainly in dewiki with english interface.

usage:
  mw.loader.load('//de.wikipedia.org/w/index.php?title=User:lustiger_seth/list2table.js&action=raw&ctype=text/javascript');

or via [[m:user:lustiger_seth/global.js]]
*/

(function (){
	'use strict';

	function add_filters_to_table(table){
		const filters = table.querySelectorAll('thead input');
		filters.forEach((input, index) => {
			input.addEventListener('input', function () {
				applyFilters(table, filters);
			});
		});
	}

	function applyFilters(table, filters){
		const rows = table.querySelectorAll('tbody tr');
		rows.forEach(row => {
			let display = '';
			filters.forEach((input, index) => {
				const filterValue = input.value;
				const cell = row.cells[index];
				if(cell){
					const match = filterValue.match(/^\/(.*)\/(i?)$/);
					if(match){
						const re = new RegExp(match[1], match[2]);
						if(!re.test(cell.textContent)){
							display = 'none';
						}
					}else if(!cell.textContent.includes(filterValue)){
						display = 'none';
					}
				}
			});
			row.style.display = display;
		});
	}

	function flatten_log_entries(elem){
		let log_entry = elem.querySelector('.mw-changeslist-log-entry');
		let moep = false;
		if(log_entry !== null){
			while(log_entry.querySelector('bdi')){
				// remove bdi elements, but keep content
				letChildrenEatParent(log_entry.querySelector('bdi'));
			}
			// group some children containing the comment in new parent
			const before_child = log_entry.querySelector('.mw-usertoollinks');
			const last_child = log_entry.querySelector('.comment');
			const newGroup = document.createElement('span');
			let currentChild = before_child.nextSibling;
			while(currentChild && currentChild !== last_child){
				const nextSibling = currentChild.nextSibling;
				newGroup.appendChild(currentChild);
				currentChild = nextSibling;
			}
			newGroup.appendChild(last_child);
			// move "diff" to front
			log_entry.insertBefore(newGroup, before_child.nextSibling);  
			log_entry.innerHTML = log_entry.innerHTML.replace(
				/\((<a [^>]+>diff<\/a>\s*\|\s*<a [^>]+>change visibility<\/a>)\)/,
				'<span class="mw-changeslist-links">$1</span>');
			// set page title as mw-title
			log_entry.innerHTML = log_entry.innerHTML.replace(
				/(.*changed visibility of a revision on page) (<a [^>]+>.*?<\/a>)\s*(:\s*content )/,
				'<span class="mw-title">$2</span>$1$3');
			// watchlist: log entries in deletion log lines
			letChildrenEatParent(elem.querySelector('.mw-changeslist-log-entry'));
		}
		// watchlist: deletion log lines
		letChildrenEatParent(elem.querySelector('.mw-changeslist-line-inner'));
	}

	function get_flagged_rev_class(elem){
		let flaggedrevs_class = '';
		elem.classList.forEach(c => {
			if(c.startsWith("flaggedrevs")){
				flaggedrevs_class = c;
			}
		});
		if(flaggedrevs_class === '' && elem.children[0].className.startsWith("flaggedrevs")){
			flaggedrevs_class = elem.children[0].className;
			letChildrenEatParent(elem.querySelector('.' + flaggedrevs_class));
		}
		return flaggedrevs_class;
	}

	function get_list_patterns(){
		const patterns = {
			'classes': {
				'plainlinks': {
					name: "abuselog",
					show_sections: false,
					conditions: {
						'date': {
							'category_re': new RegExp(/^(?:<span>|)20.*?:\s*</),
							're': /(20.*?):\s*</,
							'filter': true,
						},
						'user': {
							'category_re': new RegExp(/ class="[^"]*mw-userlink\b/),
							'filter': true,
						}, 
						'user tools': {
							'category_re': new RegExp(/class="mw-usertoollinks/),
							're': /(<span class="mw-usertoollinks">.*<\/span>)/,
							'post_process': function(str){
								str.replace(/\((.*)\)/, '$1')
								return post_process_user_tools(str);
							},
						},
						'rule': {
							'category_re': new RegExp(/^<a [^>]+>(?:global |)filter [0-9]+</),
							'post_process': function(str){
								return str.replace(/>global filter /, '>g')
									.replace(/>filter /, '>');
							},
						},
						'did': {
							'category_re': new RegExp(/^(?:<span>|), performing the action "[^"]+" on/),
							're': /performing the action "([^"]+)" on/,
							'filter': true,
						},
						'page': {
							'category_re': new RegExp(/^<a [^>]*\bhref="\/w\/index\.php\?title=(?:(?!&amp;diff=prev)[^"])+"[^>]+\btitle="[^"]+"[^>]*>[^>]+<\/a>/),
							'filter': true,
						},
						'action': {
							'category_re': new RegExp(/^(?:<span>|)\.\s*Actions taken: [^;]+;\s*Filter description:/),
							're': new RegExp(/Actions taken: ([^;]+);\s*Filter description:/),
							'filter': true,
						},
						'description': {
							'category_re': new RegExp(/^(?:<span>|)\.\s*Actions taken: [^;]+;\s*Filter description:\s*/),
							're': new RegExp(/Actions taken: [^;]+;\s*Filter description:\s*(.*?) \(/),
							'filter': true,
						},
						'tools': {
							'category_re': new RegExp(/^<a href="\/wiki\/[^"]+(?:Logbuch|examine\/log)\/[0-9]+" title="[^"]+">[^>]+<\/a>/),
							'post_process': function(str){
								return str.replace(/>details<\/a>/, '>d</a>/')
									.replace(/>examine</, '>e<');
							},
						},
					},
				},

				'mw-contributions-list': {
					name: "contribs",
					show_sections: false,
					conditions: {
						'vis': {
							'category_re': new RegExp(/ class="mw-revdelundel-link"/),
							're': /\((.*)\)/,
							'post_process': function(str){
								return str.replace(/>(?:change visibility|\+\/−)</, '>vis<');
							}
						},
						'time': {
							'category_re': new RegExp(/ class="[^"]*mw-changeslist-date/),
							'filter': true,
						},
						'rev': {
							'category_re': new RegExp(/ class="mw-changeslist-links"/),
							'post_process': function(str){
								return '<nobr>' + str.replace(/<\/span>\s*<span>/, '<\/span>/<span>')
									.replace(/Unterschied/, 'diff')
									.replace(/Versionen/, 'hist')
									+ '</nobr>';
							},
						},
						'bytes': {
							'category_re': new RegExp(/class="mw-plusminus-[a-z]+ mw-diff-bytes"/),
							'filter': {
								'width': '3em'
							},
						},
						'flag': {
							'category_re': new RegExp(/<abbr class="(?:minoredit|newpage)"|class="mw-uctop"/),
							'post_process': function(str){
								return str.replace(/>(?:current|aktuell)</, '>c<');
							},
						},
						'page': {
							'category_re': new RegExp(/ class="[^"]*mw-contributions-title"/),
							'post_process': function(str){
								return str.replace(/>Wikipedia:/, '>WP:')
									.replace(/>Wikipedia Diskussion:/, '>WD:')
									.replace(/>Benutzer(?:in|):/, '>user:')
									.replace(/>Benutzer(?:in|) Diskussion:/, '>user talk:')
									.replace(/>Diskussion:/, '>talk:')
									.replace(/>Hilfe:/, '>H:')
									.replace(/>Hilfe Diskussion:/, '>HD:')
									.replace(/>Portal:/, '>P:')
									.replace(/>Portal Diskussion:/, '>PD:');
							},
							'filter': true,
						}, 
						'comment': {
							'category_re': new RegExp(/class="comment\b/),
							'filter': true,
						},
						'edit tools': {
							'category_re': new RegExp(/class="mw-changeslist-links[^"]+">/),
							'post_process': function(str){ return post_process_edit_tools(str); },
						},
						'tags': {
							'category_re': new RegExp(/class="mw-tag-markers"/),
							'post_process': function(str){ return post_process_tags(str); },
						},
					},
					global_css: function(){
						const css_classes = [
							'.comment--without-parentheses',
							'.mw-changeslist-links',
							'.mw-diff-bytes',
							'.mw-uctop',
							'.mw-rollback-link',
							'.mw-tag-markers',
							'.mw-changeslist-links > span:not(:first-child)'
						];
						css_classes.forEach(c => {
							document.styleSheets[0].addRule(c + '::before','content: "";');
							document.styleSheets[0].addRule(c + '::after','content: "";');
						});
						return true;
					},
				},

				'special': {
					name: "watchlist",
					show_sections: true,
					conditions: {
						'rev': {
							'category_re': new RegExp(/ class="mw-changeslist-links"/),
							'post_process': function(str){
								if(str.includes('/delete"')){
									return str.replace(/>Deletion log</, '>log<')
										.replace(/<\/a>\s*<span/, '</a>/<span')
										.replace(/>change visibility</, '>vis<')
										.replace(/<\/a>\s*\|\s*<a/, '</a>/<a');
								}
								return '<nobr>' + str.replace(/<\/span><span>/, '<\/span>/<span>') + '</nobr>';
							},
						},
						'page': {
							'category_re': new RegExp(/ class="mw-title"/),
							'post_process': function(str){
								return str.replace(/>Wikipedia:/, '>WP:')
									.replace(/>Wikipedia Diskussion:/, '>WD:')
									.replace(/>Benutzer(?:in|):/, '>user:')
									.replace(/>Benutzer(?:in|) Diskussion:/, '>user talk:')
									.replace(/>Diskussion:/, '>talk:')
									.replace(/>Hilfe:/, '>H:')
									.replace(/>Hilfe Diskussion:/, '>HD:')
									.replace(/>Portal:/, '>P:')
									.replace(/>Portal Diskussion:/, '>PD:');
							},
							'filter': true,
						}, 
						'time': {
							'category_re': new RegExp(/ class="mw-changeslist-date mw-changeslist-time"/)
						}, 
						'bytes': {
							'category_re': new RegExp(/ class="mw-plusminus-[a-z]+ mw-diff-bytes"/)
						}, 
						'user': {
							'category_re': new RegExp(/ class="mw-userlink\b/),
							'filter': true,
						}, 
						'u.&nbsp;links': {
							'category_re': new RegExp(/class="mw-usertoollinks mw-changeslist-links"/),
							're': /(<span class="mw-usertoollinks mw-changeslist-links">.*<\/span>)/,
							'post_process': function(str){ return post_process_user_tools(str); },
						},
						'comment': {
							'category_re': new RegExp(/class="comment\b/),
							'post_process': function(str){
								return str.replace(/>\((.*)\)</, '>$1<');
							},
							'filter': true,
						},
						'tags': {
							'category_re': new RegExp(/class="mw-tag-markers"/),
							'post_process': function(str){ return post_process_tags(str); },
						},
						'edit tools': {
							'category_re': new RegExp(/class="mw-changeslist-links mw-pager-tools/),
							'post_process': function(str){ return post_process_edit_tools(str); },
						},
					},
					global_css: function(){
						const css_classes = [
							'.comment--without-parentheses',
							'.mw-changeslist-links',
							'.mw-diff-bytes',
							'.mw-uctop',
							'.mw-tag-markers',
							'.mw-changeslist-links > span:not(:first-child)'
						];
						css_classes.forEach(c => {
							document.styleSheets[0].addRule(c + '::before','content: "";');
							document.styleSheets[0].addRule(c + '::after','content: "";');
						});
						return true;
					},
				},

			},
			'ids': {
				'pagehistory': {
					name: "history",
					show_sections: false,
					conditions: {
						'cur/prev': {
							'category_re': new RegExp(/class="mw-history-histlinks mw-changeslist-links"/),
							'post_process': function(str){
								return '<nobr>' + str.replace(/<\/span><span>/, '</span>/<span>')
									.replace(/Aktuell/, 'jetzt')
									.replace(/Vorherige/, 'vorher') + '</nobr>';
							},
						},
						'sel': {
							'category_re': new RegExp(/^<input[^>]+(?:name="(?:oldid|diff)"|form="mw-history-revisionactions")/),
							'post_process': function(str){
								return '<nobr>' + str.replace(/ disabled=""/, '') + '</nobr>';
							},
						},
						'time': {
							'category_re': new RegExp(/ class="[^"]*mw-changeslist-date"/),
							'filter': true,
						},
						'user': {
							'category_re': new RegExp(/class="history-user"/),
							're': /(<a\s[^>]+><bdi>[^>]+<\/bdi>\s*<\/a>|<span [^>]+>\([^)]+\)<\/span>)/,
							'filter': true,
						},
						'user tools': {
							'category_re': new RegExp(/class="mw-usertoollinks mw-changeslist-links"/),
							're': /(<span class="mw-usertoollinks mw-changeslist-links">.*<\/span>)/,
							'post_process': function(str){ return post_process_user_tools(str); },
						},
						'flag': {
							'category_re': new RegExp(/<abbr class="(?:minoredit|newpage)"/)
						},
						'size': {
							'category_re': new RegExp(/class="history-size mw-diff-bytes"/),
							're': /data-mw-bytes="([0-9]+)"/,
							'filter': {
								'width': '3em'
							},
						},
						'diff': {
							'category_re': new RegExp(/class="mw-plusminus-[a-z]+ mw-diff-bytes"/),
							'filter': {
								'width': '3em'
							},
						},
						'comment': {
							'category_re': new RegExp(/class="comment\b/),
							'filter': true,
						},
						'edit tools': {
							'category_re': new RegExp(/class="mw-changeslist-links[^"]*">/),
							'post_process': function(str){ return post_process_edit_tools(str); },
						},
						'tags': {
							'category_re': new RegExp(/class="mw-tag-markers"/),
							'post_process': function(str){ return post_process_tags(str); },
						},
						'chk': {
							'category_re': new RegExp(/class="fr-hist-basic-/),
							'post_process': function(str){
								return str.replace(/\[(?:automatically checked|automatisch gesichtet)\]/, 'auto')
									.replace(/\[checked (by .*)\]/, '$1')
									.replace(/\[gesichtet (von .*)\]/, '$1');
							},
						},
					},
					global_css: function(){
						const css_classes = [
							'.comment--without-parentheses',
							'.mw-changeslist-links',
							'.mw-diff-bytes',
							'.mw-uctop',
							'.mw-tag-markers',
							'.mw-changeslist-links > span:not(:first-child)'
						];
						css_classes.forEach(c => {
							document.styleSheets[0].addRule(c + '::before','content: "";');
							document.styleSheets[0].addRule(c + '::after','content: "";');
						});
						return true;
					}
				}

			}
		};
		return patterns;
	}

	function letChildrenEatParent(parentElem){
		if(parentElem){
			let found = false;
			while(parentElem.firstChild){
				parentElem.parentNode.insertBefore(parentElem.firstChild, parentElem);
				found = true;
			}
			if(found){
				parentElem.parentNode.removeChild(parentElem);
			}
		}
	}

	function list_elem2row(elem, conditions, prev_row){
		let converted = false;
		let row = '';
		if(elem.tagName === 'LI'){
			const flaggedrevs_class = get_flagged_rev_class(elem);
			row += "<tr" + (flaggedrevs_class == '' ? '': ' class="' + flaggedrevs_class + '"') + ">";
			flatten_log_entries(elem);
			wrap_text_nodes_in_span(elem);
			let content = {};
			// fetch data from list entry and put to js-struct
			Array.from(elem.children).forEach(cell => {
				for(let [cell_header, conds] of Object.entries(conditions)){
					if(conds['category_re'].test(cell.outerHTML)){
						if(content[cell_header] === undefined){
							content[cell_header] = '';
						}
						if(conds['re'] !== undefined){
							const parts = conds['re'].exec(cell.outerHTML);
							content[cell_header] += parts.slice(1).join('');
							converted = true;
						}else{
							content[cell_header] += cell.outerHTML;
						}
					}
				}
			});
			// add cells content to html table row
			for(let [cell_header, conds] of Object.entries(conditions)){
				row += "<td>";
				if(content[cell_header] !== undefined){
					if(conds['post_process'] !== undefined){
						content[cell_header] = conds['post_process'](content[cell_header]);
					}
					//TODO: not sure about this one; might not be helpful in some cases
					//if(prev_row[cell_header] == content[cell_header] && content[cell_header].length > 5){
					//	row += '-"-';
					//}else{
						row += content[cell_header];
						prev_row[cell_header] = content[cell_header];
					//}
				}else{
					prev_row[cell_header] = '';
				}
				row += "</td>";
			}
			row += "</tr>";
		}
		return {row: row, converted: converted};
	}

	function list2table(list, pattern){
		if(list && list.children.length > 0){
			let table = '<table id="list2table" class="wikitable"><thead><tr>';
			for(let h in pattern.conditions){
				if(pattern.conditions[h].filter){
					const filter = pattern.conditions[h].filter;
					table += '<th><input type="text" style="width:'
						+ (filter['width'] ? filter['width'] : '100%')
						+ '; box-sizing: border-box;"></th>';
				}else{
					table += '<th><input type="hidden"></th>';
				}
			}
			table += "</tr><tr>";
			for(let h in pattern.conditions){
				table += "<th>" + h + "</th>";
			}
			let found_something = 0;
			table += "</tr></thead><tbody>";
			let prev_row = {};
			Array.from(list.children).forEach(elem => {
				const li_elems = (elem.tagName === 'UL' ? Array.from(elem.children) : [elem]);
				li_elems.forEach(li => {
					const row = list_elem2row(li, pattern.conditions, prev_row);
					if(row.converted){
						++found_something;
						table += row.row;
					}
				});
			});
			table += "</tbody></table>";
			if(found_something > 0){
				if(pattern.global_css !== undefined){
					pattern.global_css();
				}
				const mySpan = document.createElement("span");
				mySpan.innerHTML = table;
				list.parentNode.replaceChild(mySpan, list);
				add_filters_to_table(document.getElementById('list2table'));
			}
		}
	}

	function post_process_tags(str){
		return str.replace(/<a .*?>(?:Tags?|Markierung(?:en|))<\/a>:\s*/, '')
			.replace(/Advanced mobile edit/, 'adv. mob.')
			.replace(/(?:iOS app edit|Bearbeitung mit iOS-App)/, 'iOS')
			.replace(/(?:Manual revert|Manuelle Zurücksetzung)/, 'manual rev')
			.replace(/(?:Mobile app edit|Bearbeitung von einer mobilen Anwendung)/, 'mob. app')
			.replace(/(?:Mobile edit|Mobile Bearbeitung)/, 'mob. web')
			.replace(/(?:Mobile web edit|Mobile Web-Bearbeitung)/, 'mob.')
			.replace(/(?:Rollback|Zurücksetzung)/, 'rollback')
			.replace(/(?:Reverted|Zurückgesetzt)/, 'rev')
			.replace(/(?:2017 source edit|2017-Quelltext-Bearbeitung)/, '2017')
			.replace(/(?:Visual edit|Visuelle Bearbeitung)/, 'VE');
	}

	function post_process_user_tools(str){
		return str.replace(/>(?:talk|Diskussion)</, '>💬<')
			.replace(/>(?:contribs|Beiträge)</, '>📝<')
			.replace(/>(?:block|Sperren)</, '>🚫<')
			.replace(/<\/span>\s+<span>/g, '</span><span>');
	}

	function post_process_edit_tools(str){
		return str.replace(/<span class="mw-rollback-link">.*?<\/span>/, '')
			.replace(/>updated since your last visit</, '>updated<')
			.replace(/>(?:undo|rückgängig)</, '>👎<')
			.replace(/>(?:thank|danken)</, '>👍<');
	}

	function wrap_text_nodes_in_span(li_elem){
		li_elem.childNodes.forEach(node => {
			if (node.nodeType === Node.TEXT_NODE) {
				const span = document.createElement('span');
				span.textContent = node.textContent;
				node.parentNode.replaceChild(span, node);
			}
		});
	}
	if(mw.config.get('wgCanonicalSpecialPageName') !== 'AbuseFilter'){
		const patterns = get_list_patterns();
		const classes_of_lists = Object.keys(patterns.classes);
		if(!document.getElementById('pagehistory')){
			classes_of_lists.forEach(class_name => {
				const lists = document.getElementsByClassName(class_name);
				if(lists && lists.length > 0){
					if(patterns.classes[class_name].show_sections){
						Array.from(lists).forEach(list => {
							list2table(list, patterns.classes[class_name]);
						});
					}else{
						list2table(lists.item(0).parentNode, patterns.classes[class_name]);
					}
				}
			});
		}
		const ids_of_lists = Object.keys(patterns.ids);
		ids_of_lists.forEach(id => {
			const list = document.getElementById(id);
			if(list){
				list2table(list, patterns.ids[id]);
			}
		});
	}
})();