diff --git a/i18n/cte/en.json b/i18n/cte/en.json index 898a3d3b..4326e39f 100644 --- a/i18n/cte/en.json +++ b/i18n/cte/en.json @@ -32,6 +32,8 @@ "deputy.cte.merge.all.confirm": "You are about to merge $1 'copied' {{PLURAL:$1|notice|notices}} into this notice. Continue?", "deputy.cte.merge.button": "Merge", + "deputy.cte.templateOptions": "Template options", + "deputy.cte.copied.label": "Copied $1", "deputy.cte.copied.remove": "Remove notice", @@ -39,6 +41,7 @@ "deputy.cte.copied.add": "Add entry", "deputy.cte.copied.entry.label": "Template entry", + "deputy.cte.copied.entry.short": "$1 to $2", "deputy.cte.copied.entry.remove": "Remove entry", "deputy.cte.copied.entry.copy": "Copy attribution edit summary", @@ -66,7 +69,7 @@ "deputy.cte.copied.diff.placeholder": "https://en.wikipedia.org/w/index.php?diff=123456", "deputy.cte.copied.diff.label": "Diff URL", "deputy.cte.copied.diff.help": "The URL of the diff. Using to_diff and to_oldid is preferred, although supplying this parameter will override both.", - "deputy.cte.copied.merge.label": "Merged", + "deputy.cte.copied.merge.label": "Merged?", "deputy.cte.copied.merge.help": "Whether the copying was done as a result of merging two pages.", "deputy.cte.copied.afd.placeholder": "AfD page (without Wikipedia:Articles for deletion/)", "deputy.cte.copied.afd.label": "AfD page", @@ -86,6 +89,7 @@ "deputy.cte.splitArticle.entry.label": "Template entry", "deputy.cte.splitArticle.entry.remove": "Remove entry", + "deputy.cte.splitArticle.entry.short": "$1 on $2", "deputy.cte.splitArticle.collapse": "Collapse", "deputy.cte.splitArticle.from": "From", @@ -101,5 +105,22 @@ "deputy.cte.splitArticle.date.help": "The date that the split occurred.", "deputy.cte.splitArticle.diff.placeholder": "https://en.wikipedia.org/w/index.php?diff=123456&oldid=123455", "deputy.cte.splitArticle.diff.label": "Diff URL", - "deputy.cte.splitArticle.diff.help": "The diff URL of the split." + "deputy.cte.splitArticle.diff.help": "The diff URL of the split.", + + "deputy.cte.mergedFrom.label": "Merged from $1", + "deputy.cte.mergedFrom.remove": "Remove notice", + + "deputy.cte.mergedFrom.article.placeholder": "Page A", + "deputy.cte.mergedFrom.article.label": "Original article", + "deputy.cte.mergedFrom.article.help": "This is the page where the merged content is or used to be.", + "deputy.cte.mergedFrom.date.label": "Date", + "deputy.cte.mergedFrom.date.help": "The date (in UTC) when the content was merged into this page.", + "deputy.cte.mergedFrom.talk.label": "Link to talk?", + "deputy.cte.mergedFrom.talk.help": "Whether to link to the original article's talk page or not.", + "deputy.cte.mergedFrom.target.placeholder": "Page B", + "deputy.cte.mergedFrom.target.label": "Merge target", + "deputy.cte.mergedFrom.target.help": "The page that the content was merged into. Used if the page that the content was merged into was the talk page.", + "deputy.cte.mergedFrom.afd.placeholder": "Wikipedia:Articles for deletion/Page A", + "deputy.cte.mergedFrom.afd.label": "AfD", + "deputy.cte.mergedFrom.afd.help": "The AfD discussion that led to the merge. If this merge was not the result of an AfD discussion, leave this blank." } diff --git a/src/modules/cte/CopiedTemplateEditor.ts b/src/modules/cte/CopiedTemplateEditor.ts index 55742749..5474e1b6 100644 --- a/src/modules/cte/CopiedTemplateEditor.ts +++ b/src/modules/cte/CopiedTemplateEditor.ts @@ -126,7 +126,10 @@ export default class CopiedTemplateEditor { // Find all {{copied}} templates and append our special button. // This runs on the actual document, not the Parsoid document. document.querySelectorAll( - [ 'copiednotice', 'box-split-article' ].map( + [ + 'copiednotice', 'box-split-article', 'box-merged-from', + 'box-merged-to', 'box-backwards-copy', 'box-translated-page' + ].map( ( v ) => `.${v} > tbody > tr` ).join( ', ' ) ) diff --git a/src/modules/cte/css/copied-template-editor.css b/src/modules/cte/css/copied-template-editor.css index c37d239f..ccd4798b 100644 --- a/src/modules/cte/css/copied-template-editor.css +++ b/src/modules/cte/css/copied-template-editor.css @@ -92,12 +92,13 @@ padding-right: 0; } -.cte-page-template, +.cte-page-row, +.cte-page-template:not(:last-child), .cte-fieldset-date.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header { padding-bottom: 0 !important; } -.cte-page-row { +.cte-page-template + .cte-page-row { padding-top: 0 !important; } diff --git a/src/modules/cte/models/RowedAttributionNotice.ts b/src/modules/cte/models/RowedAttributionNotice.ts index 44ebc980..be2bb7f8 100644 --- a/src/modules/cte/models/RowedAttributionNotice.ts +++ b/src/modules/cte/models/RowedAttributionNotice.ts @@ -56,16 +56,16 @@ export default abstract class RowedAttributionNotice< if ( this.node.hasParameter( key ) !== undefined ) { row[ key ] = this.node.getParameter( key ); } else if ( - suffix === '' && this.node.getParameter( `${key}1` ) !== undefined + suffix === '' && this.node.hasParameter( `${key}1` ) ) { // Non-numbered parameter not found but a numbered parameter with - // an index of 1 was. Fall back to that value. - row[ key ] = this.node.getParameter( `${key}1` ); + // an index of 1 was found. Fall back to that value. + row[ key ] = this.node.getParameter( `${key}1` ).trim(); } else if ( - suffix === 1 && this.node.getParameter( `${key}` ) + suffix === 1 && this.node.hasParameter( `${key}` ) ) { // This is i = 1, so fall back to a non-numbered parameter (if exists) - const unnumberedParamValue = this.node.getParameter( `${key}` ); + const unnumberedParamValue = this.node.getParameter( `${key}` ).trim(); if ( unnumberedParamValue !== undefined ) { row[ key ] = unnumberedParamValue; } diff --git a/src/modules/cte/models/templates/CopiedTemplate.ts b/src/modules/cte/models/templates/CopiedTemplate.ts index 3ff54402..02070477 100644 --- a/src/modules/cte/models/templates/CopiedTemplate.ts +++ b/src/modules/cte/models/templates/CopiedTemplate.ts @@ -6,6 +6,7 @@ import { AttributionNoticePageLayout } from '../../ui/pages/AttributionNoticePag import CopiedTemplatePage from '../../ui/pages/CopiedTemplatePage'; import { AttributionNoticePageGenerator } from '../../ui/AttributionNoticePageGenerator'; import RowedAttributionNotice from '../RowedAttributionNotice'; +import yesNo from '../../../../util/yesNo'; /** * Represents a single {{copied}} template in the Parsoid document. @@ -41,10 +42,10 @@ export default class CopiedTemplate */ parse() { if ( this.node.getParameter( 'collapse' ) ) { - this.collapsed = this.node.getParameter( 'collapse' ).trim().length > 0; + this.collapsed = yesNo( this.node.getParameter( 'collapse' ).trim() ); } if ( this.node.getParameter( 'small' ) ) { - this.small = this.node.getParameter( 'small' ).trim().length > 0; + this.small = yesNo( this.node.getParameter( 'small' ).trim() ); } // Extract {{copied}} rows. diff --git a/src/modules/cte/models/templates/MergedFromTemplate.ts b/src/modules/cte/models/templates/MergedFromTemplate.ts index 03d0c02e..ccf17e59 100644 --- a/src/modules/cte/models/templates/MergedFromTemplate.ts +++ b/src/modules/cte/models/templates/MergedFromTemplate.ts @@ -1,21 +1,11 @@ -import CopiedTemplateRow, { - copiedTemplateRowParameters, - RawCopiedTemplateRow -} from './CopiedTemplateRow'; +import CopiedTemplateRow from './CopiedTemplateRow'; import { AttributionNoticePageLayout } from '../../ui/pages/AttributionNoticePageLayout'; -import CopiedTemplatePage from '../../ui/pages/CopiedTemplatePage'; import { AttributionNoticePageGenerator } from '../../ui/AttributionNoticePageGenerator'; import RowedAttributionNotice from '../RowedAttributionNotice'; import MergedFromTemplatePage from '../../ui/pages/MergedFromTemplatePage'; +import yesNo from '../../../../util/yesNo'; -/** - * Represents a single {{merged-from}} template in the Parsoid document. - */ -export default class MergedFromTemplate - extends RowedAttributionNotice - implements AttributionNoticePageGenerator { - - // TEMPLATE OPTIONS +export interface RawMergedFromTemplate { /** * The article that content from the target page was originally from. */ @@ -25,9 +15,7 @@ export default class MergedFromTemplate */ date: string; /** - * If the merge discussion occurred on a talk page that is not the target - * page's talk page, this should be supplied with the page title of that talk - * page. + * Whether to link to the original article's talk page or not. */ talk?: string; /** @@ -41,6 +29,27 @@ export default class MergedFromTemplate * @example "Wikipedia:Articles for deletion/Wikipedia" */ afd?: string; +} +export type MergedFromTemplateParameter = keyof RawMergedFromTemplate; + +/** + * Represents a single {{merged-from}} template in the Parsoid document. + */ +export default class MergedFromTemplate + extends RowedAttributionNotice + implements AttributionNoticePageGenerator, RawMergedFromTemplate { + + // TEMPLATE OPTIONS + /** @inheritDoc */ + article: string; + /** @inheritDoc */ + date: string; + /** @inheritDoc */ + talk?: string; + /** @inheritDoc */ + target?: string; + /** @inheritDoc */ + afd?: string; /** * Parses parameters into class properties. This WILL destroy unknown @@ -73,13 +82,13 @@ export default class MergedFromTemplate this.node.setParameter( '1', this.article.trim() ); this.node.setParameter( '2', this.date.trim() ); this.node.setParameter( - 'talk', this.talk.trim().length > 0 ? this.talk.trim() : null + 'talk', yesNo( this.talk ) ? null : 'no' ); this.node.setParameter( - 'target', this.target.trim().length > 0 ? this.target.trim() : null + 'target', ( this.target ?? '' ).trim().length > 0 ? this.target.trim() : null ); this.node.setParameter( - 'afd', this.afd.trim().length > 0 ? this.afd.trim() : null + 'afd', ( this.afd ?? '' ).trim().length > 0 ? this.afd.trim() : null ); this.dispatchEvent( new Event( 'save' ) ); diff --git a/src/modules/cte/models/templates/SplitArticleTemplate.ts b/src/modules/cte/models/templates/SplitArticleTemplate.ts index b7588e00..d4304365 100644 --- a/src/modules/cte/models/templates/SplitArticleTemplate.ts +++ b/src/modules/cte/models/templates/SplitArticleTemplate.ts @@ -7,6 +7,7 @@ import SplitArticleTemplateRow, { } from './SplitArticleTemplateRow'; import RowedAttributionNotice from '../RowedAttributionNotice'; import SplitArticleTemplatePage from '../../ui/pages/SplitArticleTemplatePage'; +import yesNo from '../../../../util/yesNo'; /** * Represents a single {{split article}} template. @@ -26,7 +27,7 @@ export default class SplitArticleTemplate this.from = this.node.getParameter( 'from' ).trim(); } if ( this.node.hasParameter( 'collapse' ) ) { - this.collapse = this.node.getParameter( 'collapse' ).trim().length > 0; + this.collapse = yesNo( this.node.getParameter( 'collapse' ) ); } // Extract {{copied}} rows. @@ -94,10 +95,10 @@ export default class SplitArticleTemplate * @inheritDoc */ destroy(): void { - this.dispatchEvent( new Event( 'destroy' ) ); - this.accessTemplateData( () => undefined ); + this.node.destroy(); // Self-destruct Object.keys( this ).forEach( ( k ) => delete ( this as any )[ k ] ); + this.dispatchEvent( new Event( 'destroy' ) ); } /** diff --git a/src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx b/src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx index ddac1e5e..baeef05c 100644 --- a/src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx +++ b/src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx @@ -9,6 +9,7 @@ import copyToClipboard from '../../../../util/copyToClipboard'; import getObjectValues from '../../../../util/getObjectValues'; import CopiedTemplateEditorDialog from '../CopiedTemplateEditorDialog'; import { AttributionNoticePageLayout } from './AttributionNoticePageLayout'; +import yesNo from '../../../../util/yesNo'; export interface CopiedTemplateRowPageData { /** @@ -77,14 +78,13 @@ function initCopiedTemplateRowPage() { } const finalConfig = { - label: `${copiedTemplateRow.from || '???'} to ${copiedTemplateRow.to || '???'}`, classes: [ 'cte-page-row' ] }; super( copiedTemplateRow.id, finalConfig ); this.parent = parent; this.copiedTemplateRow = copiedTemplateRow; - this.label = finalConfig.label; + this.refreshLabel(); this.copiedTemplateRow.parent.addEventListener( 'destroy', () => { parent.rebuildPages(); @@ -96,6 +96,20 @@ function initCopiedTemplateRowPage() { this.$element.append( this.render().$element ); } + /** + * Refreshes the page's label + */ + refreshLabel(): void { + this.label = mw.message( + 'deputy.cte.copied.entry.short', + this.copiedTemplateRow.from || '???', + this.copiedTemplateRow.to || '???' + ).text(); + if ( this.outlineItem ) { + this.outlineItem.setLabel( this.label ); + } + } + /** * Renders this page. Returns a FieldsetLayout OOUI widget. * @@ -260,7 +274,7 @@ function initCopiedTemplateRowPage() { value: copiedTemplateRow.diff } ), merge: new OO.ui.CheckboxInputWidget( { - value: copiedTemplateRow.merge !== undefined + value: yesNo( copiedTemplateRow.merge ) } ), afd: new OO.ui.TextInputWidget( { placeholder: mw.message( 'deputy.cte.copied.afd.placeholder' ).text(), @@ -332,9 +346,9 @@ function initCopiedTemplateRowPage() { } ), merge: new OO.ui.FieldLayout( this.inputs.merge, { $overlay: this.parent.$overlay, - label: mw.message( 'deputy.cte.merge.label' ).text(), + label: mw.message( 'deputy.cte.copied.merge.label' ).text(), align: 'inline', - help: mw.message( 'deputy.cte.merge.help' ).text() + help: mw.message( 'deputy.cte.copied.merge.help' ).text() } ), afd: new OO.ui.FieldLayout( this.inputs.afd, { $overlay: this.parent.$overlay, @@ -426,16 +440,10 @@ function initCopiedTemplateRowPage() { ); } ); this.inputs.from.on( 'change', () => { - /** @member any */ - this.outlineItem.setLabel( - `${this.inputs.from.value || '???'} to ${this.inputs.to.value || '???'}` - ); + this.refreshLabel(); } ); this.inputs.to.on( 'change', () => { - /** @member any */ - this.outlineItem.setLabel( - `${this.inputs.from.value || '???'} to ${this.inputs.to.value || '???'}` - ); + this.refreshLabel(); } ); for ( const _field in this.inputs ) { @@ -448,6 +456,7 @@ function initCopiedTemplateRowPage() { // Attach the change listener input.on( 'change', ( value: string ) => { if ( input instanceof OO.ui.CheckboxInputWidget ) { + // Specific to `merge`. Watch out before adding more checkboxes. this.copiedTemplateRow[ field ] = value ? 'yes' : ''; } else if ( input instanceof mw.widgets.datetime.DateTimeInputWidget ) { this.copiedTemplateRow[ field ] = diff --git a/src/modules/cte/ui/pages/MergedFromTemplatePage.tsx b/src/modules/cte/ui/pages/MergedFromTemplatePage.tsx index 37703225..21b2a3b4 100644 --- a/src/modules/cte/ui/pages/MergedFromTemplatePage.tsx +++ b/src/modules/cte/ui/pages/MergedFromTemplatePage.tsx @@ -1,8 +1,15 @@ import '../../../../types'; -import MergedFromTemplate from '../../models/templates/MergedFromTemplate'; +import MergedFromTemplate, { + MergedFromTemplateParameter +} from '../../models/templates/MergedFromTemplate'; import CTEParsoidDocument from '../../models/CTEParsoidDocument'; import { AttributionNoticePageLayout } from './AttributionNoticePageLayout'; import { h } from 'tsx-dom'; +import unwrapWidget from '../../../../util/unwrapWidget'; +import { renderPreviewPanel } from '../RowPageShared'; +import getObjectValues from '../../../../util/getObjectValues'; +import nsId from '../../../../util/nsId'; +import yesNo from '../../../../util/yesNo'; export interface MergedFromTemplatePageData { /** @@ -34,7 +41,7 @@ function initMergedFromTemplatePage() { /** * @inheritDoc */ - parent: /* splitArticleTemplateEditorDialog */ any; + parent: /* CopiedTemplateEditorDialog */ any; /** * The CTEParsoidDocument that this page refers to. */ @@ -57,37 +64,200 @@ function initMergedFromTemplatePage() { throw new Error( 'Reference template (MergedFromTemplate) is required' ); } - const label = mw.message( - 'deputy.cte.mergedFrom.label', - config.mergedFromTemplate.name - ).text(); const finalConfig = { - label: label, classes: [ 'cte-page-template' ] }; - super( - `${ - mergedFromTemplate.element.getAttribute( 'about' ) - }-${ - mergedFromTemplate.i - }`, - finalConfig - ); + super( finalConfig ); - this.document = config.mergedFromTemplate.parsoid; - this.mergedFromTemplate = config.mergedFromTemplate; + this.document = mergedFromTemplate.parsoid; + this.mergedFromTemplate = mergedFromTemplate; this.parent = config.parent; - this.label = label; + this.refreshLabel(); mergedFromTemplate.addEventListener( 'destroy', () => { parent.rebuildPages(); } ); - this.$element.append(
- {this.mergedFromTemplate.article} || {this.mergedFromTemplate.date} || - {this.mergedFromTemplate.talk} || {this.mergedFromTemplate.target} || - {this.mergedFromTemplate.afd} -
); + this.$element.append( + this.renderButtons(), + this.renderHeader(), + renderPreviewPanel( + this.mergedFromTemplate + ), + this.renderTemplateOptions() + ); + } + + /** + * Refreshes the page's label + */ + refreshLabel(): void { + this.label = mw.message( + 'deputy.cte.mergedFrom.label', + this.mergedFromTemplate.article || '???' + ).text(); + if ( this.outlineItem ) { + this.outlineItem.setLabel( this.label ); + } + } + + /** + * Renders the set of buttons that appear at the top of the page. + * + * @return A
element. + */ + renderButtons(): JSX.Element { + const buttonSet =
; + + const deleteButton = new OO.ui.ButtonWidget( { + icon: 'trash', + title: mw.message( 'deputy.cte.mergedFrom.remove' ).text(), + framed: false, + flags: [ 'destructive' ] + } ); + deleteButton.on( 'click', () => { + this.mergedFromTemplate.destroy(); + } ); + + buttonSet.appendChild( unwrapWidget( deleteButton ) ); + + return buttonSet; + } + + /** + * @return The rendered header of this PageLayout. + */ + renderHeader(): JSX.Element { + return

{ this.label }

; + } + + /** + * @return The options for this template + */ + renderTemplateOptions(): JSX.Element { + const layout = new OO.ui.FieldsetLayout( { + icon: 'parameter', + label: mw.message( 'deputy.cte.templateOptions' ).text(), + classes: [ 'cte-fieldset' ] + } ); + + const rowDate = this.mergedFromTemplate.date; + const parsedDate = + ( rowDate == null || rowDate.trim().length === 0 ) ? + undefined : ( + !isNaN( new Date( rowDate.trim() + ' UTC' ).getTime() ) ? + ( new Date( rowDate.trim() + ' UTC' ) ) : ( + !isNaN( new Date( rowDate.trim() ).getTime() ) ? + new Date( rowDate.trim() ) : null + ) + ); + + const inputs = { + article: new mw.widgets.TitleInputWidget( { + $overlay: this.parent.$overlay, + required: true, + value: this.mergedFromTemplate.article || '', + placeholder: mw.message( 'deputy.cte.mergedFrom.article.placeholder' ).text() + } ), + date: new mw.widgets.datetime.DateTimeInputWidget( { + $overlay: this.parent.$overlay, + required: true, + calendar: null, + icon: 'calendar', + clearable: true, + value: parsedDate + } ), + target: new mw.widgets.TitleInputWidget( { + $overlay: this.parent.$overlay, + value: this.mergedFromTemplate.target || '', + placeholder: mw.message( 'deputy.cte.mergedFrom.target.placeholder' ).text() + } ), + afd: new mw.widgets.TitleInputWidget( { + $overlay: this.parent.$overlay, + value: this.mergedFromTemplate.afd || '', + placeholder: mw.message( 'deputy.cte.mergedFrom.afd.placeholder' ).text(), + validate: ( title: string ) => { + // TODO: l10n + return title.trim().length === 0 || title.startsWith( + new mw.Title( 'Articles for deletion/', nsId( 'wikipedia' ) ) + .toText() + ); + } + } ), + talk: new OO.ui.CheckboxInputWidget( { + $overlay: this.parent.$overlay, + selected: yesNo( this.mergedFromTemplate.target ) + } ) + }; + const fieldLayouts = { + article: new OO.ui.FieldLayout( inputs.article, { + $overlay: this.parent.$overlay, + align: 'top', + label: mw.message( 'deputy.cte.mergedFrom.article.label' ).text(), + help: mw.message( 'deputy.cte.mergedFrom.article.help' ).text() + } ), + date: new OO.ui.FieldLayout( inputs.date, { + $overlay: this.parent.$overlay, + align: 'left', + label: mw.message( 'deputy.cte.mergedFrom.date.label' ).text(), + help: mw.message( 'deputy.cte.mergedFrom.date.help' ).text() + } ), + target: new OO.ui.FieldLayout( inputs.target, { + $overlay: this.parent.$overlay, + align: 'left', + label: mw.message( 'deputy.cte.mergedFrom.target.label' ).text(), + help: mw.message( 'deputy.cte.mergedFrom.target.help' ).text() + } ), + afd: new OO.ui.FieldLayout( inputs.afd, { + $overlay: this.parent.$overlay, + align: 'left', + label: mw.message( 'deputy.cte.mergedFrom.afd.label' ).text(), + help: mw.message( 'deputy.cte.mergedFrom.afd.help' ).text() + } ), + talk: new OO.ui.FieldLayout( inputs.talk, { + $overlay: this.parent.$overlay, + align: 'inline', + label: mw.message( 'deputy.cte.mergedFrom.talk.label' ).text(), + help: mw.message( 'deputy.cte.mergedFrom.talk.help' ).text() + } ) + }; + + for ( const _field in inputs ) { + const field = _field as MergedFromTemplateParameter; + const input = inputs[ field ]; + + // Attach the change listener + input.on( 'change', ( value: string ) => { + if ( input instanceof OO.ui.CheckboxInputWidget ) { + this.mergedFromTemplate[ field ] = value ? 'yes' : 'no'; + } else if ( input instanceof mw.widgets.datetime.DateTimeInputWidget ) { + this.mergedFromTemplate[ field ] = + new Date( value ).toLocaleDateString( 'en-GB', { + year: 'numeric', month: 'long', day: 'numeric' + } ); + if ( value.length > 0 ) { + fieldLayouts[ field ].setWarnings( [] ); + } + } else { + this.mergedFromTemplate[ field ] = value; + } + + this.mergedFromTemplate.save(); + } ); + + if ( input instanceof OO.ui.TextInputWidget ) { + // Rechecks the validity of the field. + input.setValidityFlag(); + } + } + + inputs.article.on( 'change', () => { + this.refreshLabel(); + } ); + + layout.addItems( getObjectValues( fieldLayouts ) ); + + return unwrapWidget( layout ); } /** diff --git a/src/modules/cte/ui/pages/SplitArticleTemplateRowPage.tsx b/src/modules/cte/ui/pages/SplitArticleTemplateRowPage.tsx index e0f45ce2..df65908f 100644 --- a/src/modules/cte/ui/pages/SplitArticleTemplateRowPage.tsx +++ b/src/modules/cte/ui/pages/SplitArticleTemplateRowPage.tsx @@ -31,13 +31,11 @@ function initSplitArticleTemplateRowPage() { extends OO.ui.PageLayout implements AttributionNoticePageLayout, SplitArticleTemplateRowPageData { + label: string; + splitArticleTemplateRow: SplitArticleTemplateRow; parent: ReturnType; - // Elements - inputs: Record; - fieldLayouts: Record; - /** * @param config Configuration to be passed to the element. */ @@ -51,16 +49,13 @@ function initSplitArticleTemplateRowPage() { } const finalConfig = { - label: `${ - splitArticleTemplateRow.to || '???' - } on ${splitArticleTemplateRow.date || '???'}`, classes: [ 'cte-page-row' ] }; super( splitArticleTemplateRow.id, finalConfig ); this.parent = parent; this.splitArticleTemplateRow = splitArticleTemplateRow; - this.label = finalConfig.label; + this.refreshLabel(); this.splitArticleTemplateRow.parent.addEventListener( 'destroy', () => { parent.rebuildPages(); @@ -72,6 +67,20 @@ function initSplitArticleTemplateRowPage() { this.$element.append( this.render().$element ); } + /** + * Refreshes the page's label + */ + refreshLabel(): void { + this.label = mw.message( + 'deputy.cte.splitArticle.entry.short', + this.splitArticleTemplateRow.to || '???', + this.splitArticleTemplateRow.date || '???' + ).text(); + if ( this.outlineItem ) { + this.outlineItem.setLabel( this.label ); + } + } + /** * Renders this page. Returns a FieldsetLayout OOUI widget. * @@ -134,7 +143,7 @@ function initSplitArticleTemplateRowPage() { ) ); - this.inputs = { + const inputs = { to: new mw.widgets.TitleInputWidget( { $overlay: this.parent.$overlay, required: true, @@ -171,27 +180,27 @@ function initSplitArticleTemplateRowPage() { } } ) }; - this.fieldLayouts = { - to: new OO.ui.FieldLayout( this.inputs.to, { + const fieldLayouts = { + to: new OO.ui.FieldLayout( inputs.to, { $overlay: this.parent.$overlay, align: 'top', label: mw.message( 'deputy.cte.splitArticle.to.label' ).text(), help: mw.message( 'deputy.cte.splitArticle.to.help' ).text() } ), // eslint-disable-next-line camelcase - from_oldid: new OO.ui.FieldLayout( this.inputs.from_oldid, { + from_oldid: new OO.ui.FieldLayout( inputs.from_oldid, { $overlay: this.parent.$overlay, align: 'left', label: mw.message( 'deputy.cte.splitArticle.from_oldid.label' ).text(), help: mw.message( 'deputy.cte.splitArticle.from_oldid.help' ).text() } ), - date: new OO.ui.FieldLayout( this.inputs.date, { + date: new OO.ui.FieldLayout( inputs.date, { $overlay: this.parent.$overlay, align: 'left', label: mw.message( 'deputy.cte.splitArticle.date.label' ).text(), help: mw.message( 'deputy.cte.splitArticle.date.help' ).text() } ), - diff: new OO.ui.FieldLayout( this.inputs.diff, { + diff: new OO.ui.FieldLayout( inputs.diff, { $overlay: this.parent.$overlay, align: 'left', label: mw.message( 'deputy.cte.splitArticle.diff.label' ).text(), @@ -199,9 +208,9 @@ function initSplitArticleTemplateRowPage() { } ) }; - for ( const _field in this.inputs ) { + for ( const _field in inputs ) { const field = _field as SplitArticleTemplateRowParameter; - const input = this.inputs[ field ]; + const input = inputs[ field ]; // Attach the change listener input.on( 'change', ( value: string ) => { @@ -211,7 +220,7 @@ function initSplitArticleTemplateRowPage() { year: 'numeric', month: 'long', day: 'numeric' } ); if ( value.length > 0 ) { - this.fieldLayouts[ field ].setWarnings( [] ); + fieldLayouts[ field ].setWarnings( [] ); } } else { this.splitArticleTemplateRow[ field ] = value; @@ -225,22 +234,14 @@ function initSplitArticleTemplateRowPage() { } } - this.inputs.to.on( 'change', () => { - this.outlineItem.setLabel( - `${ - this.splitArticleTemplateRow.to || '???' - } on ${this.splitArticleTemplateRow.date || '???'}` - ); + inputs.to.on( 'change', () => { + this.refreshLabel(); } ); - this.inputs.date.on( 'change', () => { - this.outlineItem.setLabel( - `${ - this.splitArticleTemplateRow.to || '???' - } on ${this.splitArticleTemplateRow.date || '???'}` - ); + inputs.date.on( 'change', () => { + this.refreshLabel(); } ); - return getObjectValues( this.fieldLayouts ); + return getObjectValues( fieldLayouts ); } /** diff --git a/src/util/yesNo.ts b/src/util/yesNo.ts new file mode 100644 index 00000000..2cb3ab0a --- /dev/null +++ b/src/util/yesNo.ts @@ -0,0 +1,28 @@ +/** + * Performs {{yesno}}-based string interpretation. + * + * @param value The value to check + * @param pull Depends which direction to pull unspecified values. + * @return If `pull` is true, + * any value that isn't explicitly a negative value will return `true`. Otherwise, + * any value that isn't explicitly a positive value will return `false`. + */ +export default function ( value: string|number|boolean, pull = true ): boolean { + if ( pull ) { + return value !== false && + value !== 'no' && + value !== 'n' && + value !== 'false' && + value !== 'f' && + value !== 'off' && + +value !== 0; + } else { + return value !== true && + value !== 'yes' && + value !== 'y' && + value !== 't' && + value !== 'true' && + value !== 'on' && + value !== '1'; + } +}