diff --git a/.editorconfig b/.editorconfig index dfd0c109..99d117c5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,9 @@ root = true +[src/**/*.css] +end_of_line = lf +indent_style = tab + [{src, scripts, types}/**.{js,ts}] end_of_line = lf insert_final_newline = true diff --git a/package-lock.json b/package-lock.json index f2925a0f..7e391324 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.3", "license": "Apache-2.0", "dependencies": { - "@chlodalejandro/parsoid": "^2.0.0-62b5249", + "@chlodalejandro/parsoid": "^2.0.0-1631047", "@rollup/plugin-json": "^4.1.0", "broadcastchannel-polyfill": "^1.0.1", "idb": "^7.0.1", @@ -636,9 +636,9 @@ "dev": true }, "node_modules/@chlodalejandro/parsoid": { - "version": "2.0.0-62b5249", - "resolved": "https://registry.npmjs.org/@chlodalejandro/parsoid/-/parsoid-2.0.0-62b5249.tgz", - "integrity": "sha512-6sl4bnx1gqkBu4XurhgWQajczPx9NQHerDlxc3xTGpNNvzEYJnnnWWlWg76HaCD6kupO0JUW7dPaLZNZ0ulr+A==" + "version": "2.0.0-1631047", + "resolved": "https://registry.npmjs.org/@chlodalejandro/parsoid/-/parsoid-2.0.0-1631047.tgz", + "integrity": "sha512-WiYJQfk+4x/EypLLW8Px3cOB0+Bdqr0p4acC0G/tagNPYiT+7hgdLZvvho2dX/zuhJMpHXYMEeRtDcSvGH+9gw==" }, "node_modules/@es-joy/jsdoccomment": { "version": "0.31.0", @@ -8931,9 +8931,9 @@ "dev": true }, "@chlodalejandro/parsoid": { - "version": "2.0.0-62b5249", - "resolved": "https://registry.npmjs.org/@chlodalejandro/parsoid/-/parsoid-2.0.0-62b5249.tgz", - "integrity": "sha512-6sl4bnx1gqkBu4XurhgWQajczPx9NQHerDlxc3xTGpNNvzEYJnnnWWlWg76HaCD6kupO0JUW7dPaLZNZ0ulr+A==" + "version": "2.0.0-1631047", + "resolved": "https://registry.npmjs.org/@chlodalejandro/parsoid/-/parsoid-2.0.0-1631047.tgz", + "integrity": "sha512-WiYJQfk+4x/EypLLW8Px3cOB0+Bdqr0p4acC0G/tagNPYiT+7hgdLZvvho2dX/zuhJMpHXYMEeRtDcSvGH+9gw==" }, "@es-joy/jsdoccomment": { "version": "0.31.0", diff --git a/package.json b/package.json index c21da9e9..1b7abe27 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "cover 95% and supports fetch, not IE 11" ], "dependencies": { - "@chlodalejandro/parsoid": "^2.0.0-62b5249", + "@chlodalejandro/parsoid": "^2.0.0-1631047", "@rollup/plugin-json": "^4.1.0", "broadcastchannel-polyfill": "^1.0.1", "idb": "^7.0.1", diff --git a/src/css/deputy.css b/src/css/deputy.css index 7a85b54f..76ffd028 100644 --- a/src/css/deputy.css +++ b/src/css/deputy.css @@ -1,355 +1,364 @@ -/* -=============================================================================== - - GLOBAL DEPUTY CLASSES - -=============================================================================== -*/ -* > .deputy.dp-heading { - position: absolute; - opacity: 0; - pointer-events: none; -} - -*:hover > .deputy.dp-heading:not(.dp-heading--active) { - opacity: 1; - pointer-events: all; -} - -.dp-loadingDots-1, .dp-loadingDots-2, .dp-loadingDots-3 { - display: inline-block; - margin: 0.1em 0.6em 0.1em 0.1em; - width: 0.8em; - height: 0.8em; - background-color: rgba(0, 0, 0, 50%); - animation: dp-loadingDots linear 3s infinite; - border-radius: 50%; -} - -@keyframes dp-loadingDots { - 0% { background-color: rgba(0, 0, 0, 10%); } - 16% { background-color: rgba(0, 0, 0, 40%); } - 32% { background-color: rgba(0, 0, 0, 10%); } - 100% { background-color: rgba(0, 0, 0, 10%); } -} - -.dp-loadingDots-1 { - animation-delay: -1s; -} - -.dp-loadingDots-2 { - animation-delay: -0.5s; -} - -#mw-content-text.dp-reloading { - opacity: 0.2; - pointer-events: none; -} - -/* -=============================================================================== - - DEPUTY REVIEW DIALOG (DeputyReviewDialog) - -=============================================================================== -*/ -.dp-review-progress { - flex: 1; - width: 60%; - min-width: 300px; -} - -/* -=============================================================================== - - DEPUTY ENTRY POINTS (DeputyCCISessionStartLink, etc.) - -=============================================================================== -*/ -.deputy.dp-sessionStarter { - font-size: small; - font-weight: normal; - margin-left: 0.25em; - vertical-align: baseline; - line-height: 1em; -} - -.deputy.dp-sessionStarter::before { - content: '\200B'; -} - -.mw-content-ltr .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type, -.mw-content-rtl .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) { - margin-right: 0.25em; - color: #54595d; -} - -.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type, -.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) { - margin-left: 0.25em; - color:#54595d -} - -.dp-cs-session-continue { - margin-top: 8px; -} - -.dp-cs-section-add { - position: absolute; - top: 0; - /* -1.6em derived from MediaWiki list margins. */ - left: -1.6em; - width: calc(100% + 1.6em); - height: 100%; - - background-color: rgba(255, 255, 255, 75%); - - display: flex; - justify-content: center; - align-items: center; -} - -.dp-cs-section-add .dp-cs-section-addButton { - opacity: 0; - transition: opacity 0.2s ease-in-out; -} - -.dp-cs-section-add:hover .dp-cs-section-addButton { - opacity: 1; -} - -/* -=============================================================================== - - DEPUTY CONTRIBUTION SURVEY SECTION - -=============================================================================== -*/ -.dp-cs-session-notice { - margin-top: 8px; - position: sticky; - top: 8px; - z-index: 50; -} - -.skin-vector-2022.vector-sticky-header-visible .dp-cs-session-notice { - top: calc(3.125rem + 8px); -} - -.dp-cs-section-footer { - position: relative; - padding: 8px; -} - -.dp-cs-section-progress { - margin-top: 8px; - max-height: 0; - transition: max-height 0.2s ease-in-out; - - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; -} - -.dp-cs-section-progress.active { - max-height: 50px; -} - -.dp-cs-section-progress .oo-ui-progressBarWidget { - flex: 1 -} - -.dp-cs-section-closingCommentsField { - margin-top: 8px; -} - -.dp-cs-row { - margin-bottom: 8px; -} - -.dp-cs-row .dp--loadingDots { - display: flex; - align-items: center; - justify-content: center; - padding: 0.4em; -} - -.dp-cs-row-status { - max-width: 5.4em; -} - -.dp-cs-row-status .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label { - width: 0; - opacity: 0; -} - -.dp-cs-row-status .dp-cs-row-status--unknown:not(.oo-ui-optionWidget-selected) { - display: none; -} - -.dp-cs-row-head > * { - vertical-align: middle; -} - -body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child), -body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child) { - margin-right: 16px; -} -body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child), -body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child) { - margin-left: 16px; -} - -.dp-cs-row-links { - margin-right: 0 !important; -} - -.dp-cs-row-links > :not(:last-child) { - margin-right: 8px !important; -} - -.dp-cs-row-title { - font-weight: bold; - font-size: 1.2em; - vertical-align: middle; -} - -.dp-cs-row-details { - color: #4a5054; - font-weight: bold; -} - -.dp-cs-row-toggle .oo-ui-iconElement-icon { - background-size: 1em; -} - -.dp-cs-row-toggle .oo-ui-buttonElement-button { - border-radius: 50%; -} - -.dp-cs-row .history-user, -.dp-cs-row .mw-changeslist-date { - margin-left: 0.4em; - margin-right: 0.2em; -} - -.dp-cs-row-content { - padding: 16px; - background-color: rgba(0, 0, 0, 4%); - margin: 4px 0; -} - -.dp-cs-row-content.dp-cs-row-content-empty { - display: none !important; -} - -.dp-cs-row-unfinishedWarning { - margin-bottom: 8px; -} - -.dp-cs-row-closeComments { - font-family: monospace, monospace; - font-size: small; -} - -.dp-cs-row-closeComments:not(:last-child) { - margin-bottom: 8px; -} - -.dp-cs-row-finished .oo-ui-fieldLayout:first-child { - margin-top: 0; -} - -.dp-cs-row-finished .oo-ui-fieldLayout { - margin-top: 8px; -} - -.dp-cs-row-revisions .mw-tag-markers .mw-tag-marker:not(:first-child), -.dp-cs-row-detail:not(:first-child) { - margin-left: 0.2em; -} - -/* -=============================================================================== - - DEPUTY PAGE TOOLBAR - -=============================================================================== -*/ -.dp-pageToolbar { - position: fixed; - bottom: 8px; - left: 8px; - - padding: 8px; - background-color: #fff; - border: 1px solid gray; - font-size: 0.9rem; - - display: flex; - align-items: center; -} - -@media only screen and (max-width: 768px) { - .dp-pageToolbar { - flex-wrap: wrap; - bottom: 0; - left: 0; - border-left: 0; - border-bottom: 0; - border-right: 0; - width: 100%; - } -} - -.dp-pt-section { - display: inline-block; - white-space: nowrap; -} - -.dp-pt-section + .dp-pt-section { - /* TODO: Recheck RTL compatibility */ - margin-left: 16px; - padding-left: 16px; - border-left: 1px solid gray; -} - -.dp-pt-section:last-child { - /* TODO: Recheck RTL compatibility */ - margin-right: 8px; -} - -.dp-pt-section-label { - font-weight: bold; - font-size: 0.6rem; - color: #4a5054; - text-transform: uppercase; -} - -.dp-pt-section-content .oo-ui-buttonElement:last-child { - margin-right: 0; -} - -.dp-pt-caseInfo { - font-weight: bold; - font-size: 1.3rem; - pointer-events: none; -} - -.dp-pt-missingRevision { - white-space: normal; -} - -.dp-pageToolbar .dp-cs-row-status { - width: 5.4em; -} - -.dp-pt-menu .oo-ui-menuSelectWidget { - min-width: 300px; -} - -.dp-pt-menu .oo-ui-menuOptionWidget { - padding-top: 8px; - padding-bottom: 8px; -} +/* +=============================================================================== + + GLOBAL DEPUTY CLASSES + +=============================================================================== +*/ +* > .deputy.dp-heading { + position: absolute; + opacity: 0; + pointer-events: none; +} + +*:hover > .deputy.dp-heading:not(.dp-heading--active) { + opacity: 1; + pointer-events: all; +} + +.dp-loadingDots-1, .dp-loadingDots-2, .dp-loadingDots-3 { + display: inline-block; + margin: 0.1em 0.6em 0.1em 0.1em; + width: 0.8em; + height: 0.8em; + background-color: rgba(0, 0, 0, 50%); + animation: dp-loadingDots linear 3s infinite; + border-radius: 50%; +} + +@keyframes dp-loadingDots { + 0% { + background-color: rgba(0, 0, 0, 10%); + } + 16% { + background-color: rgba(0, 0, 0, 40%); + } + 32% { + background-color: rgba(0, 0, 0, 10%); + } + 100% { + background-color: rgba(0, 0, 0, 10%); + } +} + +.dp-loadingDots-1 { + animation-delay: -1s; +} + +.dp-loadingDots-2 { + animation-delay: -0.5s; +} + +#mw-content-text.dp-reloading { + opacity: 0.2; + pointer-events: none; +} + +/* +=============================================================================== + + DEPUTY REVIEW DIALOG (DeputyReviewDialog) + +=============================================================================== +*/ +.dp-review-progress { + flex: 1; + width: 60%; + min-width: 300px; +} + +/* +=============================================================================== + + DEPUTY ENTRY POINTS (DeputyCCISessionStartLink, etc.) + +=============================================================================== +*/ +.deputy.dp-sessionStarter { + font-size: small; + font-weight: normal; + margin-left: 0.25em; + vertical-align: baseline; + line-height: 1em; +} + +.deputy.dp-sessionStarter::before { + content: '\200B'; +} + +.mw-content-ltr .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type, +.mw-content-rtl .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) { + margin-right: 0.25em; + color: #54595d; +} + +.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type, +.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) { + margin-left: 0.25em; + color: #54595d +} + +.dp-cs-session-continue { + margin-top: 8px; +} + +.dp-cs-section-add { + position: absolute; + top: 0; + /* -1.6em derived from MediaWiki list margins. */ + left: -1.6em; + width: calc(100% + 1.6em); + height: 100%; + + background-color: rgba(255, 255, 255, 75%); + + display: flex; + justify-content: center; + align-items: center; +} + +.dp-cs-section-add .dp-cs-section-addButton { + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.dp-cs-section-add:hover .dp-cs-section-addButton { + opacity: 1; +} + +/* +=============================================================================== + + DEPUTY CONTRIBUTION SURVEY SECTION + +=============================================================================== +*/ +.dp-cs-session-notice { + margin-top: 8px; + position: sticky; + top: 8px; + z-index: 50; +} + +.skin-vector-2022.vector-sticky-header-visible .dp-cs-session-notice { + top: calc(3.125rem + 8px); +} + +.dp-cs-section-footer { + position: relative; + padding: 8px; +} + +.dp-cs-section-progress { + margin-top: 8px; + max-height: 0; + transition: max-height 0.2s ease-in-out; + + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.dp-cs-section-progress.active { + max-height: 50px; +} + +.dp-cs-section-progress .oo-ui-progressBarWidget { + flex: 1 +} + +.dp-cs-section-closingCommentsField { + margin-top: 8px; +} + +.dp-cs-row { + margin-bottom: 8px; +} + +.dp-cs-row .dp--loadingDots { + display: flex; + align-items: center; + justify-content: center; + padding: 0.4em; +} + +.dp-cs-row-status { + max-width: 5.4em; +} + +.dp-cs-row-status .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label { + width: 0; + opacity: 0; +} + +.dp-cs-row-status .dp-cs-row-status--unknown:not(.oo-ui-optionWidget-selected) { + display: none; +} + +.dp-cs-row-head > * { + vertical-align: middle; +} + +body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child), +body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child) { + margin-right: 16px; +} + +body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child), +body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child) { + margin-left: 16px; +} + +.dp-cs-row-links { + margin-right: 0 !important; +} + +.dp-cs-row-links > :not(:last-child) { + margin-right: 8px !important; +} + +.dp-cs-row-title { + font-weight: bold; + font-size: 1.2em; + vertical-align: middle; +} + +.dp-cs-row-details { + color: #4a5054; + font-weight: bold; +} + +.dp-cs-row-toggle .oo-ui-iconElement-icon { + background-size: 1em; +} + +.dp-cs-row-toggle .oo-ui-buttonElement-button { + border-radius: 50%; +} + +.dp-cs-row .history-user, +.dp-cs-row .mw-changeslist-date { + margin-left: 0.4em; + margin-right: 0.2em; +} + +.dp-cs-row-content { + padding: 16px; + background-color: rgba(0, 0, 0, 4%); + margin: 4px 0; +} + +.dp-cs-row-content.dp-cs-row-content-empty { + display: none !important; +} + +.dp-cs-row-unfinishedWarning { + margin-bottom: 8px; +} + +.dp-cs-row-closeComments { + font-family: monospace, monospace; + font-size: small; +} + +.dp-cs-row-closeComments:not(:last-child) { + margin-bottom: 8px; +} + +.dp-cs-row-finished .oo-ui-fieldLayout:first-child { + margin-top: 0; +} + +.dp-cs-row-finished .oo-ui-fieldLayout { + margin-top: 8px; +} + +.dp-cs-row-revisions .mw-tag-markers .mw-tag-marker:not(:first-child), +.dp-cs-row-detail:not(:first-child) { + margin-left: 0.2em; +} + +/* +=============================================================================== + + DEPUTY PAGE TOOLBAR + +=============================================================================== +*/ +.dp-pageToolbar { + position: fixed; + bottom: 8px; + left: 8px; + + padding: 8px; + background-color: #fff; + border: 1px solid gray; + font-size: 0.9rem; + + display: flex; + align-items: center; +} + +@media only screen and (max-width: 768px) { + .dp-pageToolbar { + flex-wrap: wrap; + bottom: 0; + left: 0; + border-left: 0; + border-bottom: 0; + border-right: 0; + width: 100%; + } +} + +.dp-pt-section { + display: inline-block; + white-space: nowrap; +} + +.dp-pt-section + .dp-pt-section { + /* TODO: Recheck RTL compatibility */ + margin-left: 16px; + padding-left: 16px; + border-left: 1px solid gray; +} + +.dp-pt-section:last-child { + /* TODO: Recheck RTL compatibility */ + margin-right: 8px; +} + +.dp-pt-section-label { + font-weight: bold; + font-size: 0.6rem; + color: #4a5054; + text-transform: uppercase; +} + +.dp-pt-section-content .oo-ui-buttonElement:last-child { + margin-right: 0; +} + +.dp-pt-caseInfo { + font-weight: bold; + font-size: 1.3rem; + pointer-events: none; +} + +.dp-pt-missingRevision { + white-space: normal; +} + +.dp-pageToolbar .dp-cs-row-status { + width: 5.4em; +} + +.dp-pt-menu .oo-ui-menuSelectWidget { + min-width: 300px; +} + +.dp-pt-menu .oo-ui-menuOptionWidget { + padding-top: 8px; + padding-bottom: 8px; +} diff --git a/src/modules/cte/CopiedTemplateEditor.ts b/src/modules/cte/CopiedTemplateEditor.ts index a5474e8f..3892c025 100644 --- a/src/modules/cte/CopiedTemplateEditor.ts +++ b/src/modules/cte/CopiedTemplateEditor.ts @@ -51,6 +51,10 @@ export default class CopiedTemplateEditor { * Pencil icon buttons on {{copied}} templates that open CTE. */ startButtons: any[] = []; + /** + * The CopiedTemplateEditorDialog. The face of the operation. + */ + dialog: any; /** * @return The responsible window manager for this class. @@ -83,7 +87,7 @@ export default class CopiedTemplateEditor { if ( // Button not yet appended document.getElementById( 'pt-cte' ) == null && - // Not ephemeral namespace + // Not virtual namespace mw.config.get( 'wgNamespaceNumber' ) >= 0 ) { mw.util.addPortletLink( @@ -163,19 +167,21 @@ export default class CopiedTemplateEditor { ], () => { mw.util.addCSS( cteStyles ); - // The following classes are used here: - // * deputy - // * copied-template-editor - const dialog = CopiedTemplateEditorDialog( { - main: this, - classes: [ - // Attach "deputy" class if Deputy. - this.deputy ? 'deputy' : null, - 'copied-template-editor' - ].filter( ( v ) => !!v ) - } ); - this.windowManager.addWindows( [ dialog ] ); - this.windowManager.openWindow( dialog ); + if ( !this.dialog ) { + // The following classes are used here: + // * deputy + // * copied-template-editor + this.dialog = CopiedTemplateEditorDialog( { + main: this, + classes: [ + // Attach "deputy" class if Deputy. + this.deputy ? 'deputy' : null, + 'copied-template-editor' + ].filter( ( v ) => !!v ) + } ); + this.windowManager.addWindows( [ this.dialog ] ); + } + this.windowManager.openWindow( this.dialog ); } ); } diff --git a/src/modules/cte/css/copied-template-editor.css b/src/modules/cte/css/copied-template-editor.css index b4e510dc..9d353158 100644 --- a/src/modules/cte/css/copied-template-editor.css +++ b/src/modules/cte/css/copied-template-editor.css @@ -1,66 +1,101 @@ .cte-preview .copiednotice { - margin-left: 0; - margin-right: 0; + margin-left: 0; + margin-right: 0; } + .cte-merge-panel { - padding: 16px; - z-index: 20; - border: 1px solid lightgray; - margin-bottom: 8px; -} -.cte-temop { - margin: 8px; -} -.cte-temop > div { - width: 50%; - display: inline-block; -} + padding: 16px; + z-index: 20; + border: 1px solid lightgray; + margin-bottom: 8px; +} + +.copied-template-editor .oo-ui-bookletLayout-outlinePanel { + bottom: 32px; +} + +.cte-actionPanel { + height: 32px; + width: 100%; + position: absolute; + bottom: 0; + z-index: 1; + background-color: white; + border-top: 1px solid #c8ccd1; +} + +.cte-actionPanel > .oo-ui-buttonElement { + display: inline-block; + margin: 0 !important; +} + +.cte-templateOptions { + margin: 8px; +} + +.cte-templateOptions > div { + width: 50%; + display: inline-block; +} + .cte-fieldset { - border: 1px solid gray; - background-color: #ddf7ff; - padding: 16px; - min-width: 200px; + border: 1px solid gray; + background-color: #ddf7ff; + padding: 16px; + min-width: 200px; } + .cte-fieldset-date { - float: left; - margin-top: 10px !important; + float: left; + margin-top: 10px !important; } + .cte-fieldset-advswitch { - float: right; + float: right; } + .cte-fieldset-advswitch .oo-ui-fieldLayout-field, .cte-fieldset-date .oo-ui-fieldLayout-field { - display: inline-block !important; + display: inline-block !important; } + .cte-fieldset-advswitch .oo-ui-fieldLayout-header { - display: inline-block !important; - margin-right: 16px; + display: inline-block !important; + margin-right: 16px; } + .cte-fieldset-date .oo-ui-iconElement-icon { - left: 0.5em; - width: 1em; - height: 1em; - top: 0.4em; + left: 0.5em; + width: 1em; + height: 1em; + top: 0.4em; } + .cte-fieldset-date .mw-widgets-datetime-dateTimeInputWidget-editField { - min-width: 2.5ch !important; + min-width: 2.5ch !important; } + .cte-fieldset-date :not(.mw-widgets-datetime-dateTimeInputWidget-empty) > .mw-widgets-datetime-dateTimeInputWidget-handle { - padding-right: 0; + padding-right: 0; } + .cte-page-template, .cte-fieldset-date.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header { - padding-bottom: 0 !important; + padding-bottom: 0 !important; } + .cte-page-row { - padding-top: 0 !important; + padding-top: 0 !important; } + .copied-template-editor .oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header { - position: relative; + position: relative; } + .oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header { - padding-bottom: 6px !important; + padding-bottom: 6px !important; } + .oo-ui-windowManager-modal > .oo-ui-window.oo-ui-dialog.oo-ui-messageDialog { - z-index: 200; + z-index: 200; } diff --git a/src/modules/cte/models/AttributionNotice.ts b/src/modules/cte/models/AttributionNotice.ts index 5aaa2e8c..9f826ae0 100644 --- a/src/modules/cte/models/AttributionNotice.ts +++ b/src/modules/cte/models/AttributionNotice.ts @@ -5,8 +5,6 @@ import { MediaWikiData, TemplateData, TemplateDataModifier } from './MediaWikiDa * The AttributionNotice abstract class serves as the blueprint for other * subclasses that are instances of AttributionNotices (e.g {@link CopiedTemplate}). * It provides the basic functionality for the processing of attribution notices. - * - * */ export default abstract class AttributionNotice extends EventTarget { diff --git a/src/modules/cte/models/CTEParsoidDocument.ts b/src/modules/cte/models/CTEParsoidDocument.ts index ced7781d..c9ef5fc3 100644 --- a/src/modules/cte/models/CTEParsoidDocument.ts +++ b/src/modules/cte/models/CTEParsoidDocument.ts @@ -105,6 +105,15 @@ export default class CTEParsoidDocument extends ParsoidDocument { } ); } + /** + * @inheritDoc + */ + reset() { + super.reset(); + this.originalNoticeCount = undefined; + this.copiedNotices = undefined; + } + /** * Finds this document's {{copied}} notices. */ diff --git a/src/modules/cte/models/CopiedTemplate.ts b/src/modules/cte/models/CopiedTemplate.ts index 40344f98..429f36f5 100644 --- a/src/modules/cte/models/CopiedTemplate.ts +++ b/src/modules/cte/models/CopiedTemplate.ts @@ -10,6 +10,27 @@ import AttributionNotice from './AttributionNotice'; */ export default class CopiedTemplate extends AttributionNotice { + /** + * Merge an array of CopiedTemplates into one big CopiedTemplate. Other templates + * will be destroyed. + * + * @param templateList The list of templates to merge + * @param pivot The template to merge into. If not supplied, the first template + * in the list will be used. + */ + static mergeTemplates( templateList: CopiedTemplate[], pivot?: CopiedTemplate ) { + pivot = pivot ?? templateList[ 0 ]; + while ( templateList.length > 1 ) { + const template = templateList[ 0 ]; + if ( template === pivot ) { + // Pop the pivot template out of the list. + templateList.shift(); + continue; + } + pivot.merge( template, { delete: true } ); + } + } + // TEMPLATE OPTIONS /** diff --git a/src/modules/cte/ui/CopiedTemplateEditorDialog.tsx b/src/modules/cte/ui/CopiedTemplateEditorDialog.tsx index 9b348199..0e13351f 100644 --- a/src/modules/cte/ui/CopiedTemplateEditorDialog.tsx +++ b/src/modules/cte/ui/CopiedTemplateEditorDialog.tsx @@ -1,8 +1,8 @@ import '../../../types'; -import CopiedTemplatesEmptyPage from './CopiedTemplatesEmptyPage'; +import CopiedTemplatesEmptyPage from './pages/CopiedTemplatesEmptyPage'; import CTEParsoidDocument, { TemplateInsertEvent } from '../models/CTEParsoidDocument'; -import CopiedTemplatePage from './CopiedTemplatePage'; -import CopiedTemplateRowPage from './CopiedTemplateRowPage'; +import CopiedTemplatePage from './pages/CopiedTemplatePage'; +import CopiedTemplateRowPage from './pages/CopiedTemplateRowPage'; import errorToOO from '../../../util/errorToOO'; import { blockExit, unblockExit } from '../../../util/blockExit'; import unwrapWidget from '../../../util/unwrapWidget'; @@ -12,6 +12,7 @@ import { OOUIBookletLayout } from '../../../types'; import type CopiedTemplateEditor from '../CopiedTemplateEditor'; import getObjectValues from '../../../util/getObjectValues'; import last from '../../../util/last'; +import { h } from 'tsx-dom'; interface CopiedTemplateEditorDialogData { main: CopiedTemplateEditor; @@ -48,36 +49,6 @@ function initCopiedTemplateEditorDialog() { title: mw.message( 'deputy.close' ).text(), invisibleLabel: true, action: 'close' - }, - { - action: 'add', - icon: 'add', - label: mw.message( 'deputy.cte.add' ).text(), - title: mw.message( 'deputy.cte.add' ).text(), - invisibleLabel: true - }, - { - action: 'merge', - icon: 'tableMergeCells', - label: mw.message( 'deputy.cte.merge' ).text(), - title: mw.message( 'deputy.cte.merge' ).text(), - invisibleLabel: true - }, - { - action: 'reset', - icon: 'reload', - label: mw.message( 'deputy.cte.reset' ).text(), - title: mw.message( 'deputy.cte.reset' ).text(), - invisibleLabel: true, - flags: [ 'destructive' ] - }, - { - action: 'delete', - icon: 'trash', - label: mw.message( 'deputy.cte.delete' ).text(), - title: mw.message( 'deputy.cte.delete' ).text(), - invisibleLabel: true, - flags: [ 'destructive' ] } ] }; @@ -123,6 +94,8 @@ function initCopiedTemplateEditorDialog() { this.layout.on( 'remove', () => { if ( Object.keys( this.layout.pages ).length === 0 ) { + // If no pages left, append the "no notices" page. + this.layout.addPages( [ CopiedTemplatesEmptyPage( { parent: this, parsoid: this.parsoid @@ -162,15 +135,133 @@ function initCopiedTemplateEditorDialog() { } ); + this.renderMenuActions(); this.$body.append( this.layout.$element ); } + /** + * Renders the collection of actions at the top of the page menu. Also + * appends the panel to the layout. + */ + renderMenuActions() { + const addButton = new OO.ui.ButtonWidget( { + icon: 'add', + framed: false, + invisibleLabel: true, + label: mw.message( 'deputy.cte.add' ).text(), + title: mw.message( 'deputy.cte.add' ).text(), + flags: [ 'progressive' ] + } ); + addButton.on( 'click', () => { + // TODO: Add support for adding different template types. + this.addTemplate(); + } ); + + const mergeButton = new OO.ui.ButtonWidget( { + icon: 'tableMergeCells', + framed: false, + invisibleLabel: true, + label: mw.message( 'deputy.cte.merge' ).text(), + title: mw.message( 'deputy.cte.merge' ).text(), + disabled: ( this.parsoid.copiedNotices?.length ?? 0 ) < 2 + } ); + mergeButton.on( 'click', () => { + const notices = this.parsoid.copiedNotices.length; + if ( notices > 1 ) { + return OO.ui.confirm( + mw.message( 'deputy.cte.merge.confirm', `${notices}` ).text() + ).done( ( confirmed: boolean ) => { + if ( !confirmed ) { + return; + } + + CopiedTemplate.mergeTemplates( this.parsoid.copiedNotices ); + } ); + } else { + return OO.ui.alert( 'There are no templates to merge.' ); + } + } ); + + const resetButton = new OO.ui.ButtonWidget( { + icon: 'reload', + framed: false, + invisibleLabel: true, + label: mw.message( 'deputy.cte.reset' ).text(), + title: mw.message( 'deputy.cte.reset' ).text() + } ); + resetButton.on( 'click', () => { + return OO.ui.confirm( + mw.message( 'deputy.cte.reset.confirm' ).text() + ).done( ( confirmed: boolean ) => { + if ( confirmed ) { + this.parsoid.reload().then( () => { + this.layout.clearPages(); + this.rebuildPages(); + } ); + } + } ); + } ); + + const deleteButton = new OO.ui.ButtonWidget( { + icon: 'trash', + framed: false, + invisibleLabel: true, + label: mw.message( 'deputy.cte.delete' ).text(), + title: mw.message( 'deputy.cte.delete' ).text(), + flags: [ 'destructive' ] + } ); + deleteButton.on( 'click', () => { + // Original copied notice count. + const notices = this.parsoid.copiedNotices.length; + const rows = this.parsoid.copiedNotices + .reduce( ( p: number, n: CopiedTemplate ) => p + n.rows.length, 0 ); + return OO.ui.confirm( + mw.message( + 'deputy.cte.delete.confirm', + `${notices}`, + `${rows}` + ).text() + ).done( ( confirmed: boolean ) => { + if ( confirmed ) { + while ( this.parsoid.copiedNotices.length > 0 ) { + this.parsoid.copiedNotices[ 0 ].destroy(); + } + } + } ); + } ); + + this.layout.on( 'remove', () => { + if ( this.parsoid.copiedNotices ) { + mergeButton.setDisabled( this.parsoid.copiedNotices.length < 2 ); + deleteButton.setDisabled( this.parsoid.copiedNotices.length === 0 ); + } + } ); + this.parsoid.addEventListener( 'templateInsert', () => { + if ( this.parsoid.copiedNotices ) { + mergeButton.setDisabled( this.parsoid.copiedNotices.length < 2 ); + deleteButton.setDisabled( this.parsoid.copiedNotices.length === 0 ); + } + } ); + + const actionPanel =
+ { unwrapWidget( addButton ) } + { unwrapWidget( mergeButton ) } + { unwrapWidget( resetButton ) } + { unwrapWidget( deleteButton ) } +
; + + const targetPanel = unwrapWidget( this.layout ).querySelector( + '.oo-ui-menuLayout .oo-ui-menuLayout-menu' + ); + targetPanel.insertAdjacentElement( 'afterbegin', actionPanel ); + } + /** * Rebuilds the pages of this dialog. */ rebuildPages(): void { const pages = []; - for ( const template of this.parsoid.copiedNotices ) { + for ( const template of ( this.parsoid.copiedNotices ?? [] ) ) { console.log( template ); if ( template.rows === undefined ) { // Likely deleted. Skip. @@ -219,34 +310,25 @@ function initCopiedTemplateEditorDialog() { getSetupProcess( data: any ) { const process = super.getSetupProcess( data ); - if ( this.parsoid.getDocument() != null ) { - // Reset the frame. - process.first( () => { - return OO.ui.alert( - mw.message( 'deputy.cte.dirty' ).text() - ).done( () => { - this.parsoid.reset(); - } ); - } ); - } - - // Load the talk page - process.next( () => { - return this.parsoid.loadPage( + if ( this.parsoid.getDocument() == null ) { + // Load the talk page + process.next( this.parsoid.loadPage( new mw.Title( mw.config.get( 'wgPageName' ) ) .getTalkPage() .getPrefixedText() - ).catch( errorToOO as any ); - } ); + ).catch( errorToOO as any ).then( () => true ) ); + } // Rebuild the list of pages process.next( () => { - return this.rebuildPages(); + this.rebuildPages(); + return true; } ); // Block exits process.next( () => { blockExit( 'cte' ); + return true; } ); return process; @@ -281,132 +363,71 @@ function initCopiedTemplateEditorDialog() { */ getActionProcess( action: string ) { const process = super.getActionProcess( action ); - switch ( action ) { - case 'save': - // Quick and dirty validity check. - if ( - unwrapWidget( this.layout ) - .querySelector( '.oo-ui-flaggedElement-invalid' ) != null - ) { - return new OO.ui.Process( () => { - OO.ui.alert( mw.message( 'deputy.cte.invalid' ).text() ); - } ); - } + if ( action === 'save' ) { + // Quick and dirty validity check. + if ( + unwrapWidget( this.layout ) + .querySelector( '.oo-ui-flaggedElement-invalid' ) != null + ) { + return new OO.ui.Process( () => { + OO.ui.alert( mw.message( 'deputy.cte.invalid' ).text() ); + } ); + } - // Saves the page. - process.next( async () => { - return new mw.Api().postWithEditToken( { - action: 'edit', - format: 'json', - formatversion: '2', - utf8: 'true', - title: this.parsoid.getPage(), - text: await this.parsoid.toWikitext(), - // TODO: l10n - summary: decorateEditSummary( `${ - this.parsoid.originalNoticeCount > 0 ? - 'Modifying' : 'Adding' - } content attribution notices` ) - } ).catch( errorToOO ); - }, this ); - - // Page redirect - process.next( () => { - unblockExit( 'cte' ); - if ( - mw.config.get( 'wgPageName' ) === this.parsoid.getPage() - ) { - // If on the talk page, reload the page. - window.location.reload(); - } else { - // If on another page, open the talk page. - window.location.href = - mw.config.get( 'wgArticlePath' ).replace( - /\$1/g, - encodeURIComponent( this.parsoid.getPage() ) - ); - } - }, this ); - break; - case 'reset': - process.next( () => { - return OO.ui.confirm( - mw.message( 'deputy.cte.reset.confirm' ).text() - ).done( ( confirmed: boolean ) => { - if ( confirmed ) { - this.parsoid.reload().then( () => { - this.layout.clearPages(); - this.rebuildPages(); - } ); - } - } ); - }, this ); - break; - case 'merge': - process.next( () => { - const notices = this.parsoid.copiedNotices.length; - if ( notices > 1 ) { - return OO.ui.confirm( - mw.message( 'deputy.cte.merge.confirm', `${notices}` ).text() - ).done( ( confirmed: boolean ) => { - if ( !confirmed ) { - return; - } - - const pivot = this.parsoid.copiedNotices[ 0 ]; - while ( this.parsoid.copiedNotices.length > 1 ) { - let template = this.parsoid.copiedNotices[ 0 ]; - if ( template === pivot ) { - template = this.parsoid.copiedNotices[ 1 ]; - } - pivot.merge( template, { delete: true } ); - } - } ); - } else { - return OO.ui.alert( 'There are no templates to merge.' ); - } - }, this ); - break; - case 'delete': - process.next( () => { - // Original copied notice count. - const notices = this.parsoid.copiedNotices.length; - const rows = this.parsoid.copiedNotices - .reduce( ( p: number, n: CopiedTemplate ) => p + n.rows.length, 0 ); - return OO.ui.confirm( - mw.message( - 'deputy.cte.delete.confirm', - `${notices}`, - `${rows}` - ).text() - ).done( ( confirmed: boolean ) => { - if ( confirmed ) { - while ( this.parsoid.copiedNotices.length > 0 ) { - this.parsoid.copiedNotices[ 0 ].destroy(); - } - } - } ); - }, this ); - break; - case 'add': - process.next( () => { - this.addTemplate(); - }, this ); - break; - } + // Saves the page. + process.next( async () => { + return new mw.Api().postWithEditToken( { + action: 'edit', + format: 'json', + formatversion: '2', + utf8: 'true', + title: this.parsoid.getPage(), + text: await this.parsoid.toWikitext(), + // TODO: l10n + summary: decorateEditSummary( `${ + this.parsoid.originalNoticeCount > 0 ? + 'Modifying' : 'Adding' + } content attribution notices` ) + } ).catch( errorToOO ); + }, this ); - if ( action === 'save' || action === 'close' ) { + // Page redirect process.next( () => { - this.close( { action: action } ); - // Already unblocked if "save", but this cuts down on code footprint. unblockExit( 'cte' ); - this.parsoid.reset(); - this.parsoid.destroy(); - - this.main.toggleButtons( true ); + if ( + mw.config.get( 'wgPageName' ) === this.parsoid.getPage() + ) { + // If on the talk page, reload the page. + window.location.reload(); + } else { + // If on another page, open the talk page. + window.location.href = + mw.config.get( 'wgArticlePath' ).replace( + /\$1/g, + encodeURIComponent( this.parsoid.getPage() ) + ); + } }, this ); } + process.next( () => { + this.close( { action: action } ); + }, this ); + + return process; + } + + /** + * Gets the teardown process. Called when the dialog is closing. + */ + getTeardownProcess() { + const process = super.getTeardownProcess(); + process.next( () => { + // Already unblocked if "save", but this cuts down on code footprint. + unblockExit( 'cte' ); + + this.main.toggleButtons( true ); + } ); return process; } diff --git a/src/modules/cte/ui/CopiedTemplatePage.tsx b/src/modules/cte/ui/pages/CopiedTemplatePage.tsx similarity index 92% rename from src/modules/cte/ui/CopiedTemplatePage.tsx rename to src/modules/cte/ui/pages/CopiedTemplatePage.tsx index cfebc20a..c8c58a24 100644 --- a/src/modules/cte/ui/CopiedTemplatePage.tsx +++ b/src/modules/cte/ui/pages/CopiedTemplatePage.tsx @@ -1,14 +1,14 @@ import { h } from 'tsx-dom'; -import '../../../types'; -import CopiedTemplate from '../models/CopiedTemplate'; +import '../../../../types'; +import CopiedTemplate from '../../models/CopiedTemplate'; import CopiedTemplateRowPage from './CopiedTemplateRowPage'; -import unwrapWidget from '../../../util/unwrapWidget'; -import CopiedTemplateRow from '../models/CopiedTemplateRow'; -import CTEParsoidDocument from '../models/CTEParsoidDocument'; -import RowChangeEvent from '../models/RowChangeEvent'; -import CopiedTemplateEditorDialog from './CopiedTemplateEditorDialog'; -import { OOUIBookletLayout } from '../../../types'; -import removeElement from '../../../util/removeElement'; +import unwrapWidget from '../../../../util/unwrapWidget'; +import CopiedTemplateRow from '../../models/CopiedTemplateRow'; +import CTEParsoidDocument from '../../models/CTEParsoidDocument'; +import RowChangeEvent from '../../models/RowChangeEvent'; +import CopiedTemplateEditorDialog from '../CopiedTemplateEditorDialog'; +import { OOUIBookletLayout } from '../../../../types'; +import removeElement from '../../../../util/removeElement'; export interface CopiedTemplatePageData { /** @@ -265,13 +265,10 @@ function initCopiedTemplatePage() { ).done( ( confirmed: boolean ) => { if ( confirmed ) { // Recursively merge all templates - while ( this.document.copiedNotices.length > 1 ) { - let template = this.document.copiedNotices[ 0 ]; - if ( template === this.copiedTemplate ) { - template = this.document.copiedNotices[ 1 ]; - } - this.copiedTemplate.merge( template, { delete: true } ); - } + CopiedTemplate.mergeTemplates( + this.document.copiedNotices, + this.copiedTemplate + ); mergeTarget.setValue( null ); mergePanel.toggle( false ); } @@ -321,6 +318,7 @@ function initCopiedTemplatePage() { await this.copiedTemplate.generatePreview().then( ( data ) => { this.previewPanel.innerHTML = data; + // Remove DiscussionTools empty talk page notice const emptyStateNotice = this.previewPanel.querySelector( '.ext-discussiontools-emptystate' ); @@ -328,6 +326,13 @@ function initCopiedTemplatePage() { removeElement( emptyStateNotice ); } + // Make all anchor links open in a new tab (prevents exit navigation) + this.previewPanel.querySelectorAll( 'a' ) + .forEach( ( el: HTMLElement ) => { + el.setAttribute( 'target', '_blank' ); + el.setAttribute( 'rel', 'noopener' ); + } ); + // Infuse collapsibles ( $( this.previewPanel ).find( '.collapsible' ) as any ) .makeCollapsible(); @@ -386,7 +391,7 @@ function initCopiedTemplatePage() { this.copiedTemplate.save(); } ); - return
+ return
{ unwrapWidget( this.fields.collapse ) }
{ unwrapWidget( this.fields.small ) }
; diff --git a/src/modules/cte/ui/CopiedTemplateRowPage.tsx b/src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx similarity index 98% rename from src/modules/cte/ui/CopiedTemplateRowPage.tsx rename to src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx index 50dcb402..18aa6d6e 100644 --- a/src/modules/cte/ui/CopiedTemplateRowPage.tsx +++ b/src/modules/cte/ui/pages/CopiedTemplateRowPage.tsx @@ -1,12 +1,12 @@ /* eslint-disable camelcase */ import { h } from 'tsx-dom'; -import '../../../types'; -import CopiedTemplateRow, { CopiedTemplateRowParameter } from '../models/CopiedTemplateRow'; -import RowChangeEvent from '../models/RowChangeEvent'; -import unwrapWidget from '../../../util/unwrapWidget'; -import copyToClipboard from '../../../util/copyToClipboard'; -import getObjectValues from '../../../util/getObjectValues'; -import CopiedTemplateEditorDialog from './CopiedTemplateEditorDialog'; +import '../../../../types'; +import CopiedTemplateRow, { CopiedTemplateRowParameter } from '../../models/CopiedTemplateRow'; +import RowChangeEvent from '../../models/RowChangeEvent'; +import unwrapWidget from '../../../../util/unwrapWidget'; +import copyToClipboard from '../../../../util/copyToClipboard'; +import getObjectValues from '../../../../util/getObjectValues'; +import CopiedTemplateEditorDialog from '../CopiedTemplateEditorDialog'; export interface CopiedTemplateRowPageData { /** diff --git a/src/modules/cte/ui/CopiedTemplatesEmptyPage.tsx b/src/modules/cte/ui/pages/CopiedTemplatesEmptyPage.tsx similarity index 86% rename from src/modules/cte/ui/CopiedTemplatesEmptyPage.tsx rename to src/modules/cte/ui/pages/CopiedTemplatesEmptyPage.tsx index 3cde9a09..a59ac181 100644 --- a/src/modules/cte/ui/CopiedTemplatesEmptyPage.tsx +++ b/src/modules/cte/ui/pages/CopiedTemplatesEmptyPage.tsx @@ -1,5 +1,5 @@ -import '../../../types'; -import CTEParsoidDocument from '../models/CTEParsoidDocument'; +import '../../../../types'; +import CTEParsoidDocument from '../../models/CTEParsoidDocument'; import { h } from 'tsx-dom'; export interface CopiedTemplatesEmptyPageData { @@ -37,12 +37,7 @@ function initCopiedTemplatesEmptyPage() { * @param config Configuration to be passed to the element. */ constructor( config: CopiedTemplatesEmptyPageData ) { - const finalConfig = { - label: 'No templates', - icon: 'puzzle', - level: 0 - }; - super( 'cte-no-templates', finalConfig ); + super( 'cte-no-templates', {} ); this.parent = config.parent; this.parsoid = config.parsoid; @@ -90,12 +85,7 @@ function initCopiedTemplatesEmptyPage() { /** @member any */ if ( this.outlineItem !== undefined ) { /** @member any */ - this.outlineItem - .setMovable( true ) - .setRemovable( true ) - .setIcon( this.icon ) - .setLevel( this.level ) - .setLabel( this.label ); + this.outlineItem.toggle( false ); } } diff --git a/src/wiki/DiffPage.ts b/src/wiki/DiffPage.ts index 21b99de6..6e002199 100644 --- a/src/wiki/DiffPage.ts +++ b/src/wiki/DiffPage.ts @@ -22,7 +22,7 @@ export default class DiffPage { * @see https://w.wiki/5Roy */ static async loadNewDiff( diff: number, options: DiffPageLoadOptions = {} ): Promise { - const diffURL = getRevisionDiffURL( + const diffUrl = getRevisionDiffURL( diff, options.oldid ?? null, true @@ -31,7 +31,7 @@ export default class DiffPage { const contentText = document.querySelector( '#mw-content-text' ); contentText.classList.add( 'dp-reloading' ); - const diffDoc = await fetch( diffURL ) + const diffDoc = await fetch( diffUrl ) .then( ( r ) => r.blob(), () => { mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-windows' @@ -77,7 +77,7 @@ export default class DiffPage { $( document.querySelector( 'body > table.diff' ) ) ); - history.pushState( {}, null, diffURL ); + history.pushState( {}, null, diffUrl ); } }