Skip to content

Commit

Permalink
[failing] abstractify attribution notices in CTEPD
Browse files Browse the repository at this point in the history
  • Loading branch information
ChlodAlejandro committed Jul 26, 2022
1 parent 95c6f97 commit d8ecb96
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 72 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"rollup-plugin-node-license": "^0.2.1",
"rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-string": "^3.0.0",
"rollup-pluginutils": "^2.8.2",
"serve-handler": "^6.1.3",
"shx": "^0.3.4",
"ts-jest": "^28.0.5",
Expand Down
41 changes: 36 additions & 5 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import typescript from 'rollup-plugin-typescript2';
import sourcemaps from 'rollup-plugin-sourcemaps';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { string } from 'rollup-plugin-string';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import license from 'rollup-plugin-node-license';
import { createFilter } from 'rollup-pluginutils';
import * as fs from 'fs';
import * as path from 'path';

const production = process.env.NODE_ENV === 'production';

// UTILS

/**
* Converts standard text to block comments.
*
Expand Down Expand Up @@ -43,13 +45,44 @@ function loadBanner( ...bannerPath ) {
return blockCommentIfy( fs.readFileSync( path.join( __dirname, ...bannerPath ) ) );
}

// TRANSFORMS

/**
* Loads CSS files as strings. Facilitates CSS loading through `mw.util`.
*
* @param {Object} options Options for the plugin.
* @param {boolean} options.minify Whether to perform a simple CSS minify.
* @return The plugin
*/
function cssString( options = { minify: true } ) {
const filter = createFilter( 'src/**/*.css' );

return {
name: 'CSS as string',
transform( code, id ) {
if ( filter( id ) ) {
return {
code: `export default ${JSON.stringify(
options.minify ? code.replace( /[\n\t]/g, '' ) : code
)};`,
map: { mappings: '' }
};
}
}
};
}

// OPTIONS

/**
* Get plugins for this Rollup instance.
*
* @return {Plugin[]}
*/
function getPlugins() {
return [
// Appends license information
license(),
// Inserts sourcemaps
!production && sourcemaps(),
// Makes Common.js imports possible
Expand All @@ -60,10 +93,8 @@ function getPlugins() {
typescript(),
// Allows JSON imports (i18n files)
json(),
// Allows file imports as standard strings (CSS files)
string( { include: 'src/**/*.css' } ),
// Appends license information
license()
// Transform CSS to standard JS strings
cssString()
].filter( ( v ) => !!v );
}

Expand Down
2 changes: 1 addition & 1 deletion src/DeputyLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default class DeputyLanguage {
// of translations), but the user has a 'wgUserLanguage' differing from
// English.
mw.notify(
// No languages to fall back on. Do not translate this string.
// No languages to fall back on. Do not translate this string.
'Deputy: Could not load requested language file.',
{ type: 'error' }
);
Expand Down
4 changes: 3 additions & 1 deletion src/modules/cte/CopiedTemplateEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import CopiedTemplate from './models/CopiedTemplate';
import cteStyles from './css/copied-template-editor.css';
import deputyCteEnglish from '../../../i18n/cte/en.json';
import DeputyLanguage from '../../DeputyLanguage';
import WikiAttributionNotices from './models/WikiAttributionNotices';

declare global {
interface Window {
Expand Down Expand Up @@ -168,8 +169,9 @@ export default class CopiedTemplateEditor {
'mediawiki.widgets',
'mediawiki.widgets.datetime',
'jquery.makeCollapsible'
], () => {
], async () => {
mw.util.addCSS( cteStyles );
await WikiAttributionNotices.init();

if ( !this.dialog ) {
// The following classes are used here:
Expand Down
107 changes: 49 additions & 58 deletions src/modules/cte/models/CTEParsoidDocument.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import ParsoidDocument from '@chlodalejandro/parsoid';
import last from '../../../util/last';
import AttributionNotice from './AttributionNotice';
import WikiAttributionNotices from './WikiAttributionNotices';
import CopiedTemplate from './CopiedTemplate';

/**
* An event dispatched when a template inside a `CopiedTemplateEditorDialog` is inserted.
*/
export class TemplateInsertEvent extends Event {

template: CopiedTemplate;
template: AttributionNotice;

/**
* @param template The template that was inserted
* @param eventInitDict
*/
constructor( template: CopiedTemplate, eventInitDict?: EventInit ) {
constructor( template: AttributionNotice, eventInitDict?: EventInit ) {
super( 'templateInsert', eventInitDict );
this.template = template;
}
Expand Down Expand Up @@ -64,25 +66,15 @@ export default class CTEParsoidDocument extends ParsoidDocument {
'<html><body><section data-mw-section-id="0"></section></body></html>';

/**
* Aliases of the {{copied}} template. This must be in lowercase and all
* spaces must be replaced with underscores.
* A map of all Parsoid HTML elements and their attribution notices. When notices are
* detected, they are added here. ParsoidTemplateTransclusionNode is not used here
* since they are regenerated every time `findTemplate` is called.
*/
static readonly copiedTemplateAliases = <const>[
'copied',
'copied_from',
'copywithin'
];

/**
* A list of {{copied}} notices in the document.
*
* @type {CopiedTemplate[]}
*/
copiedNotices: CopiedTemplate[];
notices: Map<HTMLElement, AttributionNotice> = new Map();
/**
* The original number of {{copied}} notices in the document.
*/
originalNoticeCount: number;
originalCount: number = null;

/**
* Creates a new CTE-specific ParsoidDocument. This extends from the existing
Expand All @@ -100,8 +92,8 @@ export default class CTEParsoidDocument extends ParsoidDocument {
return;
}

this.findCopiedNotices();
this.originalNoticeCount = this.copiedNotices.length;
const notices = this.findCopiedNotices();
this.originalCount = notices.length;
} );
}

Expand All @@ -110,52 +102,53 @@ export default class CTEParsoidDocument extends ParsoidDocument {
*/
reset() {
super.reset();
this.originalNoticeCount = undefined;
this.copiedNotices = undefined;
this.originalCount = undefined;
this.notices.clear();
}

/**
* Finds this document's {{copied}} notices.
* Finds all content attribution notices in the talk page. This includes {{copied}},
* {{merged to}}, {{merged from}}, etc.
*
* @return An array of AttributionNotice objects.
*/
findCopiedNotices() {
if ( !this.copiedNotices ) {
this.copiedNotices = [];
}

const newCopiedNotices: CopiedTemplate[] = [];
findNotices(): AttributionNotice[] {
this.buildIndex();
// Used instead of `this.notices.values()` to exclude nodes that are no longer on the DOM.
const notices: AttributionNotice[] = [];

for ( const templateElement of this.findTemplate(
new RegExp(
CTEParsoidDocument.copiedTemplateAliases.map(
( v ) => `(${mw.util.escapeRegExp( v )})`
).join( '|' ),
'gi'
)
) ) {
// This is a copied template.
const existing = this.copiedNotices.find(
( v ) => v.element === templateElement.originalElement
);
if ( existing ) {
// Record exists, reuse that same object (prevents memory leaks).
newCopiedNotices.push( existing );
} else {
// Not yet in the existing array, create a new object.
const notice = new CopiedTemplate(
CTEParsoidTransclusionTemplateNode.upgradeNode( templateElement, this )
);
newCopiedNotices.push(
notice
for (
const node of this.findTemplate( WikiAttributionNotices.templateAliasRegExp )
) {
if ( !this.notices.has( node.originalElement ) ) {
// Notice not yet cached, but this is an attribution notice.
// Now to determine what type.
const type = WikiAttributionNotices.getTemplateNoticeType(
node.getTarget().href
);
notice.addEventListener( 'destroy', () => {
const i = this.copiedNotices.indexOf( notice );
this.copiedNotices.splice( i, 1 );
} );

const noticeInstance = new (
WikiAttributionNotices.attributionNoticeClasses[ type ]
)( CTEParsoidTransclusionTemplateNode.upgradeNode( node, this ) );
this.notices.set( node.originalElement, noticeInstance );
}

notices.push( this.notices.get( node.originalElement ) );
}

this.copiedNotices = newCopiedNotices;
return notices;
}

/**
* Finds this document's {{copied}} notices.
*
* @return An array of all CopiedTemplate objects found
*/
findCopiedNotices(): CopiedTemplate[] {
return this.findNotices().filter(
( notice ) => notice instanceof
WikiAttributionNotices.attributionNoticeClasses.copied
) as CopiedTemplate[];
}

/**
Expand Down Expand Up @@ -237,9 +230,7 @@ export default class CTEParsoidDocument extends ParsoidDocument {
// Insert.
element.insertAdjacentElement( position, template );
this.findCopiedNotices();
const templateObject = this.copiedNotices.find(
( v ) => v.element === template
);
const templateObject = this.notices.get( template );
this.dispatchEvent( new TemplateInsertEvent( templateObject ) );
}
}
Loading

0 comments on commit d8ecb96

Please sign in to comment.