diff --git a/i18n/ante/en.json b/i18n/ante/en.json index 719b266a..807244c9 100644 --- a/i18n/ante/en.json +++ b/i18n/ante/en.json @@ -5,7 +5,7 @@ "deputy.ante.edit": "Modify content attribution notices for this page", "deputy.ante.add": "Add a notice", "deputy.ante.mergeAll": "Merge all notices", - "deputy.ante.mergeAll.confirm": "You are about to merge $1 {{PLURAL:$1|notice|notices}}. Continue?", + "deputy.ante.mergeAll.confirm": "You are about to merge $1 {{PLURAL:$1|notice|notices}} which support rows. Continue?", "deputy.ante.reset": "Reset all changes", "deputy.ante.reset.confirm": "This will reset all changes. Proceed?", "deputy.ante.delete": "Delete all notices", diff --git a/src/modules/ante/models/CTEParsoidDocument.ts b/src/modules/ante/models/CTEParsoidDocument.ts index f90e178e..79525d01 100644 --- a/src/modules/ante/models/CTEParsoidDocument.ts +++ b/src/modules/ante/models/CTEParsoidDocument.ts @@ -9,6 +9,8 @@ import TemplateInsertEvent from '../events/TemplateInsertEvent'; import { CTEParsoidTransclusionTemplateNode } from './CTEParsoidTransclusionTemplateNode'; import TemplateFactory from './TemplateFactory'; import moveToStart from '../../../util/moveToStart'; +import RowedAttributionNotice from './RowedAttributionNotice'; +import organize from '../../../util/organize'; /** * An object containing an {@link HTMLIFrameElement} along with helper functions @@ -111,6 +113,15 @@ export default class CTEParsoidDocument extends ParsoidDocument { return notices; } + findRowedNoticesByHref(): Record[]> { + return organize( + this.findNotices().filter( + v => v instanceof RowedAttributionNotice + ) as RowedAttributionNotice[], + (v) => v.node.getTarget().href + ); + } + /** * Finds this document's {{copied}} notices. * diff --git a/src/modules/ante/models/TemplateMerger.ts b/src/modules/ante/models/TemplateMerger.ts index 11ec042e..6129557c 100644 --- a/src/modules/ante/models/TemplateMerger.ts +++ b/src/modules/ante/models/TemplateMerger.ts @@ -1,4 +1,5 @@ -import type CopiedTemplate from './templates/CopiedTemplate'; +import RowedAttributionNotice from './RowedAttributionNotice'; +import { AttributionNoticeRow } from './AttributionNoticeRow'; /** * Merges templates together. Its own class to avoid circular dependencies. @@ -13,11 +14,14 @@ export default class TemplateMerger { * @param pivot The template to merge into. If not supplied, the first template * in the list will be used. */ - static copied( templateList: CopiedTemplate[], pivot?: CopiedTemplate ) { + static merge>( templateList: T[], pivot?: T ) { pivot = pivot ?? templateList[ 0 ]; while ( templateList.length > 0 ) { const template = templateList[ 0 ]; if ( template !== pivot ) { + if ( template.node.getTarget().href !== pivot.node.getTarget().href ) { + throw new Error("Attempted to merge incompatible templates."); + } pivot.merge( template, { delete: true } ); } // Pop the pivot template out of the list. diff --git a/src/modules/ante/ui/CopiedTemplateEditorDialog.tsx b/src/modules/ante/ui/CopiedTemplateEditorDialog.tsx index 6344c2ba..978f905e 100644 --- a/src/modules/ante/ui/CopiedTemplateEditorDialog.tsx +++ b/src/modules/ante/ui/CopiedTemplateEditorDialog.tsx @@ -235,27 +235,28 @@ function initCopiedTemplateEditorDialog() { invisibleLabel: true, label: mw.msg( 'deputy.ante.mergeAll' ), title: mw.msg( 'deputy.ante.mergeAll' ), - disabled: true } ); - // TODO: Repair mergeButton this.mergeButton.on( 'click', () => { - const notices = this.parsoid.findNoticeType( 'copied' ); - if ( notices.length > 1 ) { - return OO.ui.confirm( + const notices = this.parsoid.findRowedNoticesByHref(); + const noticeCount = Object.values(notices) + .filter( v => v.length > 1 ) + .reduce( (p, n) => p + n.length, 0 ); + return noticeCount ? + OO.ui.confirm( mw.message( 'deputy.ante.mergeAll.confirm', - `${notices.length}` + `${noticeCount}` ).text() ).done( ( confirmed: boolean ) => { if ( !confirmed ) { return; } - TemplateMerger.copied( notices ); - } ); - } else { - return OO.ui.alert( 'There are no templates to merge.' ); - } + for ( const noticeSet of Object.values(notices)) { + TemplateMerger.merge(noticeSet); + } + } ) : + OO.ui.alert( 'There are no templates to merge.' ); } ); const resetButton = new OO.ui.ButtonWidget( { @@ -322,16 +323,16 @@ function initCopiedTemplateEditorDialog() { } ); this.layout.on( 'remove', () => { - const notices = this.parsoid.findNotices(); - // TODO: Repair mergeButton - // this.mergeButton.setDisabled( notices.length < 2 ); - deleteButton.setDisabled( notices.length === 0 ); + this.mergeButton.setDisabled( + !Object.values(this.parsoid.findRowedNoticesByHref()).some( v => v.length > 1 ) + ); + deleteButton.setDisabled( this.parsoid.findNotices().length === 0 ); } ); this.parsoid.addEventListener( 'templateInsert', () => { - const notices = this.parsoid.findNotices(); - // TODO: Repair mergeButton - // this.mergeButton.setDisabled( notices.length < 2 ); - deleteButton.setDisabled( notices.length === 0 ); + this.mergeButton.setDisabled( + !Object.values(this.parsoid.findRowedNoticesByHref()).some( v => v.length > 1 ) + ); + deleteButton.setDisabled( this.parsoid.findNotices().length === 0 ); } ); this.$overlay.append( @@ -438,7 +439,8 @@ function initCopiedTemplateEditorDialog() { // Recheck state of merge button this.mergeButton.setDisabled( - ( this.parsoid.findNoticeType( 'copied' ).length ?? 0 ) < 2 + !Object.values(this.parsoid.findRowedNoticesByHref()) + .some( v => v.length > 1 ) ); process.next( () => { diff --git a/src/modules/ante/ui/RowPageShared.tsx b/src/modules/ante/ui/RowPageShared.tsx index 5b539325..c45c12cc 100644 --- a/src/modules/ante/ui/RowPageShared.tsx +++ b/src/modules/ante/ui/RowPageShared.tsx @@ -77,7 +77,7 @@ export function renderMergePanel( ).done( ( confirmed: boolean ) => { if ( confirmed ) { // Recursively merge all templates - TemplateMerger.copied( + TemplateMerger.merge( notices as any, parentTemplate as any ); diff --git a/src/util/organize.ts b/src/util/organize.ts new file mode 100644 index 00000000..51bdef5b --- /dev/null +++ b/src/util/organize.ts @@ -0,0 +1,16 @@ +export default function organize( + objects: T[], + keyer: ( pivot: T ) => string +): Record { + const finalObj: Record = {}; + + for (const obj of objects) { + const key = keyer( obj ); + if (!finalObj[key]) { + finalObj[key] = []; + } + finalObj[key].push(obj); + } + + return finalObj; +}