Skip to content

Commit

Permalink
edit conflict management, fix save lockup
Browse files Browse the repository at this point in the history
  • Loading branch information
ChlodAlejandro committed Nov 1, 2022
1 parent f4c2585 commit d321ad5
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 13 deletions.
2 changes: 2 additions & 0 deletions i18n/core/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"deputy.session.section.saved": "Section saved",
"deputy.session.section.failed": "Failed to save section",
"deputy.session.section.missingSection": "The target section is missing from the case page.",
"deputy.session.section.conflict.title": "Edit conflict",
"deputy.session.section.conflict.help": "Someone else edited the page before you. Deputy will restart to load the new case content. Your changes will be preserved.",

"deputy.session.row.status": "Current page status",
"deputy.session.row.status.unfinished": "Unfinished",
Expand Down
9 changes: 8 additions & 1 deletion src/models/ContributionSurveySection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,26 @@ export default class ContributionSurveySection {
* The original wikitext of this section
*/
readonly originalWikitext: string;
/**
* The revision ID of the wikitext attached to this section.
*/
readonly revid: number;

/**
* @param casePage The case page of this section
* @param name The name of this section (based on the heading)
* @param closed Whether this section has been closed (wrapped in collapse templates)
* @param closingComments Closing comments for this section
* @param wikitext The original wikitext of this section
* @param revid The revision ID of the wikitext attached to this section.
*/
constructor(
casePage: DeputyCasePage,
name: string,
closed: boolean,
closingComments: string,
wikitext: string
wikitext: string,
revid: number
) {
this.casePage = casePage;
this.name = name;
Expand All @@ -53,6 +59,7 @@ export default class ContributionSurveySection {

this.originalWikitext = wikitext;
this.originallyClosed = closed;
this.revid = revid;
}

}
11 changes: 11 additions & 0 deletions src/session/DeputyRootSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export default class DeputyRootSession {
// User is editing, don't load interface.
return;
}

if ( await window.deputy.session.checkForActiveSessionTabs() ) {
// User is on another tab, don't load interface.
mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-windows' ], () => {
Expand Down Expand Up @@ -581,4 +582,14 @@ export default class DeputyRootSession {
}
}
}

/**
* Restarts the section. This rebuilds *everything* from the ground up, which may
* be required when there's an edit conflict.
*/
async restartSession() {
const casePage = this.casePage;
await this.closeSession();
await window.deputy.session.DeputyRootSession.continueSession( casePage );
}
}
37 changes: 31 additions & 6 deletions src/ui/root/DeputyContributionSurveySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export default class DeputyContributionSurveySection implements DeputyUIElement
headingName: string;
sectionElements: HTMLElement[];
originalList: HTMLElement;
/**
* Revision ID of the actively-used wikitext. Used for detecting edit conflicts.
*/
revid: number;

// UI elements (no OOUI types, fall back to `any`)
container: HTMLElement;
Expand Down Expand Up @@ -241,18 +245,20 @@ export default class DeputyContributionSurveySection implements DeputyUIElement
*
* @param wikitext Internal use only. Used to skip section loading using existing wikitext.
*/
async getSection( wikitext?: string ): Promise<ContributionSurveySection> {
async getSection( wikitext?: string & { revid: number } ): Promise<ContributionSurveySection> {
const collapsible = this.sectionElements.find(
( v: HTMLElement ) => v.querySelector( '.mw-collapsible' )
)?.querySelector( '.mw-collapsible' ) ?? null;

const sectionWikitext = await this.casePage.wikitext.getSectionWikitext( this.headingName );
return this._section ?? (
this._section = new ContributionSurveySection(
this.casePage,
this.headingName,
collapsible != null,
collapsible?.querySelector<HTMLElement>( 'th > div' ).innerText,
wikitext ?? await this.casePage.wikitext.getSectionWikitext( this.headingName )
wikitext ?? sectionWikitext,
sectionWikitext.revid
)
);
}
Expand Down Expand Up @@ -287,7 +293,9 @@ export default class DeputyContributionSurveySection implements DeputyUIElement
}
}

const sectionWikitext = ( await this.getSection() ).originalWikitext;
const section = await this.getSection();
const sectionWikitext = section.originalWikitext;
this.revid = section.revid;

const wikitextLines = sectionWikitext.split( '\n' );
this.rows = [];
Expand Down Expand Up @@ -385,10 +393,25 @@ export default class DeputyContributionSurveySection implements DeputyUIElement
pageid: this.casePage.pageId,
section: sectionId,
text: this.wikitext,
baserevid: this.revid,
summary: decorateEditSummary( this.editSummary )
} ).then( function ( data ) {
return data;
}, function ( code, data ) {
}, ( code, data ) => {
if ( code === 'editconflict' ) {
// Wipe cache.
this.casePage.wikitext.resetCachedWikitext();
OO.ui.alert(
mw.msg( 'deputy.session.section.conflict.help' ),
{
title: mw.msg( 'deputy.session.section.conflict.title' )
}
).then( () => {
window.deputy.session.rootSession.restartSession();
} );
return false;
}

mw.notify(
<span dangerouslySetInnerHTML={
data.errors[ 0 ].html
Expand Down Expand Up @@ -480,7 +503,7 @@ export default class DeputyContributionSurveySection implements DeputyUIElement

// Rebuild the entire section to HTML, and then reopen.
const {
element, wikitext
element, wikitext, revid
} = await getSectionHTML( this.casePage.title, sectionId );

removeElement( this.container );
Expand All @@ -503,9 +526,11 @@ export default class DeputyContributionSurveySection implements DeputyUIElement

if ( !this._section.closed ) {
this._section = null;
await this.getSection( wikitext );
await this.getSection( Object.assign( wikitext, { revid } ) );
await this.prepare();
oldHeading.insertAdjacentElement( 'afterend', this.render() );
// Run this asynchronously.
setTimeout( this.loadData.bind( this ), 0 );
}

removeElement( oldHeading );
Expand Down
21 changes: 18 additions & 3 deletions src/wiki/DeputyCasePageWikitext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,30 @@ export default class DeputyCasePageWikitext {
( this.content = await getPageContent( this.casePage.pageId ) );
}

/**
* Removes the cached wikitext for this page.
*/
resetCachedWikitext(): void {
this.content = undefined;
}

/**
* Gets the wikitext for a specific section. The section will be parsed using the
* wikitext cache if a section title was provided. Otherwise, it will attempt to
* grab the section using API:Query for an up-to-date version.
*
* @param section The section to edit
*/
async getSectionWikitext( section: string | number ): Promise<string> {
async getSectionWikitext( section: string | number ): Promise<string & { revid: number }> {
if ( typeof section === 'number' ) {
return getPageContent(
this.casePage.pageId,
{ rvsection: section }
).then( ( v ) => v.toString() );
).then( ( v ) => {
return Object.assign( v.toString(), {
revid: v.revid
} );
} );
} else {
const wikitext = await this.getWikitext();
const wikitextLines = wikitext.split( '\n' );
Expand All @@ -70,7 +81,11 @@ export default class DeputyCasePageWikitext {
}
}

return sectionLines.join( '\n' );
return Object.assign(
sectionLines.join( '\n' ), {
revid: wikitext.revid
}
);
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/wiki/util/getSectionHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export default async function (
page: mw.Title | string,
section: number | string,
extraOptions: Record<string, any> = {}
): Promise<{ element: HTMLDivElement, wikitext: string }> {
): Promise<{ element: HTMLDivElement, wikitext: string, revid: number }> {
if ( typeof section === 'string' ) {
section = await getSectionId( page, section );
}

return MwApi.action.get( {
action: 'parse',
prop: 'text|wikitext',
prop: 'text|wikitext|revid',
page: normalizeTitle( page ).getPrefixedText(),
section: section,
disablelimitreport: true,
Expand All @@ -32,7 +32,8 @@ export default async function (

return {
element: temp.children[ 0 ] as HTMLDivElement,
wikitext: data.parse.wikitext
wikitext: data.parse.wikitext,
revid: data.parse.revid
};
} );
}

0 comments on commit d321ad5

Please sign in to comment.