User:Jdlrobson/rl.js

mw.loader.implement( "ext.gadget.readinglist@", {   "main": "resources/ext.gadget.readinglist/gadget.js",    "files": {    "resources/ext.gadget.readinglist/gadget.js": function ( require, module, exports ) { /** * When re-generating this file use: * http://localhost:8888/w/load.php?modules=ext.gadget.readinglist&debug=true */ // const { showOverlay, createMembersOverlay, editListOverlay } = require( './overlays.js'); const { getCollectionsWithMembership, fromBase64, saveReadingList,    deleteCollection, getReadingListUrl,    addToList, toBase64 } = require( './api.js' ); const READING_LIST_URL = getReadingListUrl( mw.user.getName );

(function {    if ( mw.user.isAnon ) {        return;    }

const mwuibutton = ( text, className ) => { const editBtn = document.createElement('button'); editBtn.classList.add( 'mw-ui-button', className ); editBtn.textContent = text; return editBtn; };

function addImportFromTable { Array.from( document.querySelectorAll('.wikitable') ) .filter((node) => node.querySelectorAll('a[title]').length > 4) .forEach((table) => {           const titles = {                [window.location.host]: Array.from(table.querySelectorAll('a[title]')).map((link) => link.getAttribute('title'))            };            const link = document.createElement('a');            link.textContent = 'Import into list';            const row = document.createElement('tr');            const col = document.createElement('td');            col.setAttribute('colspan', table.querySelectorAll('thead').length);            table.appendChild(row);            const base64 = toBase64(`Imported from tablez on ${mw.config.get('wgTitle')}`, '', titles);            link.setAttribute('href', `${READING_LIST_URL}?limport=${base64}`);            col.appendChild(link);            table.appendChild(col);        } ); }

function addToUserMenu { mw.util.addPortletLink( 'p-personal', READING_LIST_URL, 'My reading lists', 'pt-readinglists', null, 'l', 'pt-watchlist' ); }

function addBookmarkNextToWatchAction { let link = mw.util.addPortletLink('p-views', '#', 'Bookmark', 'pt-bookmark'); if ( !link ) { link = mw.util.addPortletLink('page-actions', '#', 'Bookmark', 'pt-bookmark' ); }       if (!link ) { return; }       // Hack: no native support for vector icons. setTimeout( => {            link.classList.remove( 'vector-tab-noicon' );        }, 300 ); link.querySelector( 'a' ).classList.add(           'mw-ui-icon',            'mw-ui-button',            'mw-ui-quiet',            'mw-ui-icon-element',            'mw-ui-icon-bookmark',            'mw-ui-icon-small'        ) link.addEventListener( 'click', => {            const title = mw.config.get( 'wgPageName' );            showOverlay( getCollectionsWithMembership( mw.user.getName, title ).then(( collections ) => {                   return createMembersOverlay( title, collections );               } )            );        } );    }

const isSpecialReadingList = mw.config.get( 'wgTitle').indexOf('ReadingListz/') > -1 || (mw.config.get( 'wgTitle').indexOf('ReadingList/') > -1 && !location.host.match(/(mediawiki.org|meta.wikimedia.org)/) );

function editList { const pathSplit = window.location.pathname.split('/'); const id = parseInt(pathSplit[4],10); const h1 = document.querySelector('.readinglist-collection-summary h1'); const desc = document.querySelector('.readinglist-collection-description'); const title = h1 ? h1.textContent : ''; const description = desc ? desc.textContent : ''; showOverlay(           Promise.resolve( editListOverlay( id, title, description, null, => {                    window.location.pathname = `${READING_LIST_URL}/${id}?updated=${new Date}`;                } ) )       );    }

function createList { showOverlay(           Promise.resolve( editListOverlay( null, , , null, => {                    window.location.pathname = `${READING_LIST_URL}/?updated=${new Date}`;                } ) )       );    }

function deleteList { const pathSplit = window.location.pathname.split('/'); const id = parseInt(pathSplit[4],10); const ok = confirm('Are you sure you want to delete this list?'); if ( ok ) { deleteCollection(id).then( => {               mw.notify('List has been deleted.');                window.location.pathname = READING_LIST_URL;            }) }   }

// Makes Special:ReadingListz loook like Special:ReadingList function registerTemporaryReadingListPage { if ( isSpecialReadingList ) { const action = mwuibutton( '' ); const container = document.createElement('div') container.setAttribute('id','reading-list-container') $('#mw-content-text').html('').append(container) $('#firstHeading').text('Reading lists'); if ( document.querySelectorAll( '.mw-portlet-associated-pages a' ).length === 0 ) { const link = mw.util.addPortletLink('p-associated-pages', READING_LIST_URL, 'Your lists'); if ( link ) { link.classList.add( 'vector-tab-noicon' ); }           }

mw.loader.using( 'special.readinglist.scripts' ); const callback = (mutationList, observer) => { for (const mutation of mutationList) { if (mutation.type === 'childList') { action.removeEventListener('click', createList ); const pathSplit = window.location.pathname.split('/'); // create at username if ( pathSplit.length === 4 ) { action.removeEventListener( 'click', deleteList ); action.textContent = 'Create list'; action.addEventListener( 'click', createList ); const editBtn = document.querySelector( '.readinglist-collection-summary .rl-edit-btn' ); if ( editBtn ) { editBtn.parentNode.removeChild(editBtn); }                   } else { action.textContent = 'Delete list'; action.removeEventListener( 'click', createList ); action.addEventListener( 'click', deleteList );

const summaryArea = document.querySelector( '.readinglist-collection-summary' ); const existingEdit = summaryArea.querySelector('.rl-edit-btn'); if ( summaryArea && !existingEdit ) { const editBtn = mwuibutton( 'Edit list', 'rl-edit-btn' ); editBtn.addEventListener( 'click', editList ); summaryArea.appendChild(editBtn) }                   }

}               }            };             // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); const config = { attributes: true, childList: true, subtree: true }; observer.observe(document.querySelector('#reading-list-container'), config); mw.util.$content[0].appendChild( action ); }   }

function importFunctionality { const importValue = mw.util.getParamValue('limport') || mw.util.getParamValue('lexport'); if ( importValue ) { const btn = mwuibutton( 'Import this list!' ); mw.util.$content[0].appendChild( btn ); btn.addEventListener( 'click', => {                const list = fromBase64( importValue );                saveReadingList( null, list.name, list.description ).then(( id ) => { Promise.all(                       Object.keys(list.list).map((project) => addToList( id, list.list[project], project) )                   ).then( => {                        mw.notify('List successfully imported!');                        window.location.pathname = READING_LIST_URL;                    }); });           } );        }    }

// Can be removed when these are added to menus addToUserMenu; addBookmarkNextToWatchAction; // Experimental feature for testing import feature: remove whenever necessary. addImportFromTable; // @todo: Can removed when $wgReadingListsWebAuthenticatedPreviews is true everywhere. registerTemporaryReadingListPage; importFunctionality; }); },   "resources/ext.gadget.readinglist/icons.json": {    "cdxIconBookmark": "\u003Cpath d=\"M5 1a2 2 0 00-2 2v16l7-5 7 5V3a2 2 0 00-2-2z\"/\u003E",    "cdxIconBookmarkOutline": "\u003Cpath d=\"M5 1a2 2 0 00-2 2v16l7-5 7 5V3a2 2 0 00-2-2zm10 14.25-5-3.5-5 3.5V3h10z\"/\u003E" },    "resources/ext.gadget.readinglist/api.js": function ( require, module, exports ) { const api = new mw.Api;

const DEFAULT_READING_LIST_NAME = 'Saved'; const DEFAULT_READING_LIST_DESCRIPTION = 'Default list for your saved articles.';

// // Existing API functions // /** * * @param {number} id of list * @return {JQuery.Promise } */ function deleteCollection( id ) { return api.postWithToken( 'csrf', {			action: 'readinglists',			list: id,			command: 'delete'	} ); }

/** * @param {string} name * @param {string} description * @param {Object} list * @return {string} */ function toBase64( name, description, list ) { const str = JSON.stringify( { name, description, list } ); try { return window.btoa( dataStr ); } catch ( e ) { return window.btoa(			encodeURIComponent( str ).replace( /%([0-9A-F]{2})/g, function toSolidBytes( match, p1 ) { return String.fromCharCode( '0x' + p1 ); }			)		);	} }

/** * Adapted from https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings * * @param {string} data * @return {Object} */ function fromBase64( data ) { let plainData; try { plainData = window.atob( data ); } catch ( e ) { plainData = decodeURIComponent( window.atob( str ).split( '' ).map( ( c ) => { return '%' + ( '00' + c.charCodeAt( 0 ).toString( 16 ) ).slice( -2 ); } ).join( '' ) ); }

return JSON.parse( plainData ); }

/** * Sets up the reading list feature for new users who have never used it before. * * @return {jQuery.Promise } * @param {string|null} id if an existing collection * @param {string} name of list * @param {string} description of list * @return {JQuery.Promise } */ function saveReadingList( id, name, description ) { if ( id ) { return api.postWithToken( 'csrf', {			action: 'readinglists',			list: id,			name,			description,			command: 'update'		} ); } else { return api.postWithToken( 'csrf', {			action: 'readinglists',			name,			description,			command: 'create'		} ).then( ( result ) => result && result.create && result.create.id ); } }

/** * @param {string} ownerName person who owns the list * @param {number} [id] of the list * @param {string} [title] of the list * @return {string} */ const getReadingListUrl = ( ownerName, id, title ) => { const READING_LIST_HOST = location.host.indexOf( 'localhost' ) > -1 ? '' : 'https://meta.wikimedia.org'; let titlePath = 'ReadingLists'; if ( ownerName ) { titlePath += `/${ownerName}`; }	if ( id ) { titlePath += `/${id}`; }	const titleWithName = title ? `${titlePath}/${encodeURIComponent( title )}` : titlePath; try { return READING_LIST_HOST + (			new mw.Title( titleWithName, -1 )		).getUrl; } catch ( e ) { // Uncaught Error: Unable to parse title // e.g. Special:ReadingLists/1/ return READING_LIST_HOST + (			new mw.Title( titlePath, -1 )		).getUrl; } };

/** * Converts API response to WVUI compatible response. * * @param {ApiQueryResponseReadingListItem} collection from API response * @param {string} ownerName of collection * @return {Card} modified collection */ const readingListToCard = ( collection, ownerName ) => { const description = collection.default ? DEFAULT_READING_LIST_DESCRIPTION : collection.description; const name = collection.default ? DEFAULT_READING_LIST_NAME : collection.name; const url = getReadingListUrl( ownerName, collection.id, name ); return Object.assign( {}, collection, { ownerName, name, description, url } ); };

/** * * @param {string} ownerName (username) * @param {number[]} marked a list of collection IDs which have a certain title * @return {Promise} */ function getCollections( ownerName, marked ) { return new Promise( ( resolve, reject ) => {		api.get( { action: 'query', format: 'json', rldir: 'descending', rlsort: 'updated', meta: 'readinglists', formatversion: 2 } ).then( function ( /** @type {ApiQueryResponseReadingLists} */ data ) { resolve(				( data.query.readinglists || [] ).map( ( collection ) => readingListToCard( collection, ownerName, marked ) )			); }, function ( /** @type {string} */ err ) { // setup a reading list and try again. if ( err === 'readinglists-db-error-not-set-up' ) { setupCollections.then( => getCollections( ownerName, marked ) ) .then( ( /** @type {Card[]} */ collections ) => resolve( collections ) ); } else { reject( err ); }		} );	} ); }

/** * Sets up the reading list feature for new users who have never used it before. * * @return {jQuery.Promise } */ function setupCollections { return api.postWithToken( 'csrf', {		action: 'readinglists',		command: 'setup'	} ); }

// // New API functions //

/** * * @return {string} */ function getCurrentProjectName { // Use wgServer to avoid issues with ".m." domain const server = mw.config.get( 'wgServer' ); if ( server.indexOf( 'wikipedia.beta.wmflabs' ) > -1 ) { return server.replace( 'wikipedia.beta.wmflabs', 'wikipedia' ).replace( 'https://', '' ); }	return server.indexOf( '//' ) === 0 ? window.location.protocol + server : server; }

/** * Return the collections belonging to ownerName collections but with isMember key. * * @param {string} ownerName * @param {string} title to check for existence in those collections * @return {Function} */ function getCollectionsWithMembership( ownerName, title ) { // make sure it's an array return getCollections( ownerName ).then((cards) => {		return api.get( { action: 'query', format: 'json', meta: 'readinglists', rldir: 'descending', rlsort: 'updated', rlproject: getCurrentProjectName, rltitle: title, formatversion: 2 } ).then( function ( /** @type {ApiQueryResponseReadingLists} */data ) { const marked = data.query.readinglists.map( ( collection ) => collection.id ); return cards.map( ( card ) => {				return Object.assign( card, { marked: marked.indexOf( card.id ) > -1 } );			} );		} );	}); }

/** * @param {number} id * @param {string|array} titleOrTitles * @param {string} [projectName] * @return {JQuery.Promise } */ function addToList( id, titleOrTitles, projectName ) { let project = projectName || getCurrentProjectName; const title = typeof titleOrTitles === 'string' ? titleOrTitles : undefined; const batch = typeof titleOrTitles !== 'string' ? JSON.stringify(		titleOrTitles.map( ( title ) => ( {				title,				project			} ) )	) : undefined; project = batch ? undefined : project;

return api.postWithToken( 'csrf', {		action: 'readinglists',		list: id,		project,		title,		batch,		command: 'createentry'	} ).then( ( result ) => ( { id } ) ); }

/** * @param {number} id of list * @param {string} title of page * @return {JQuery.Promise} */ function findItemInList( id, title ) { return api.get( { action: 'query', format: 'json',		list: 'readinglistentries',		rlelists: id	} ).then( function ( data ) {		const items = data.query.readinglistentries.filter( ( /** @type {ApiQueryResponseReadingListEntryItem} */ item ) => item.title === title );		if ( items.length === 0 ) {			throw new Error( 'findItemInList doesn\'t know how to deal with pagination yet.' );		} else {			return items[ 0 ];		}	} ); }

// Note the remove from list function currently doesn't work. // See https://phabricator.wikimedia.org/T198990 /** * @param {number} id * @param {string} title * @return {JQuery.Promise } */ function removeFromList( id, title ) { return findItemInList( id, title ).then( function ( entry ) {		return api.postWithToken( 'csrf', { action: 'readinglists', entry: entry.id, command: 'deleteentry' } );	} ); }

module.exports = { getReadingListUrl, fromBase64, toBase64, saveReadingList,

// New: deleteCollection, removeFromList, addToList, getCollectionsWithMembership }; },   "resources/ext.gadget.readinglist/CollectionDialog.vue": function ( require, module, exports ) { const { CdxButton, CdxCard, CdxIcon } = require( '@wikimedia/codex' ); const { getReadingListUrl } = require( './api.js' ); const { cdxIconBookmark, cdxIconBookmarkOutline } = require( './icons.json' ); const CdxDialog = require( './Dialog.vue' );

// @vue/component module.exports = { name: 'CollectionDialog', components: { CdxDialog, CdxButton, CdxIcon, CdxCard },	props: { collections: { type: Array }	},	computed: { collectionsUrl: => getReadingListUrl( mw.user.getName ), collectionsWithThumb { return this.collections; },		markedIcon: => cdxIconBookmark, unmarkedIcon: => cdxIconBookmarkOutline },	data: function { const selected = {}; this.collections.forEach(( collection ) => {			selected[collection.id] = collection.marked;		}); return { selected };	},	methods: { createList: function { this.$emit( 'create' ); },		hide: function { this.$emit( 'hide' ); },		getName: function ( id ) { const collections = this.collections.filter( ( c ) => c.id === id ); if ( !collections.length ) { throw new Error( 'Unable to locate collection with id ' + id ); }			return collections[ 0 ].name; },		select: function ( id ) { this.$emit(				'select',				id,				this.selected[ id ],				this.getName( id ),				 => {					this.selected[ id ] = !this.selected[ id ];				}			); }	},	props: { collections: [] } };; module.exports.template = " \		 \			 \				 \					 \						 \					<\/template> \					 \						 \					<\/template> \					 \						<\/cdx-icon> \						<\/cdx-icon> \					<\/template> \				<\/cdx-card> \			<\/li> \		<\/ul> \		 \			Add to new list<\/cdx-button> \			Manage lists<\/a> \		<\/footer> \	<\/cdx-dialog>"; },   "resources/ext.gadget.readinglist/CollectionEditorDialog.vue": function ( require, module, exports ) { const { CdxTextInput, CdxCard, CdxButton } = require( '@wikimedia/codex' ); const CdxDialog = require( './Dialog.vue' );

// @vue/component module.exports = { name: 'CollectionDialog', components: { CdxDialog, CdxTextInput, CdxButton, CdxCard },	data { return { exists: !!this.initialTitle, title: this.initialTitle, description: this.initialDescription };	},	computed: { label { return this.exists ? 'Edit list' : 'Create list'; },		getDialogTitle { return !this.initialDescription ? 'Create reading list' : undefined; },		isSaveDisabled { return !this.title; },		suggestion { return { title: this.title, description: this.description };		}	},	props: { initialTitle: { type: String, default: '' },		initialDescription: { type: String, default: '' }	},	methods: { cancel { this.$emit( 'hide' ); },		save { this.$emit( 'save', this.title, this.description ); }	} };; module.exports.template = " \		 \			 \				<template #title=\"\"> \					 \				<\/template> \				<template #description=\"\"> \					 \				<\/template> \			<\/cdx-card> \			 Name<\/label> \			<cdx-text-input v-model=\"title\" placeholder=\"Name this list\" class=\"dialog-collection-editor-panel-input\"><\/cdx-text-input> \			 Description<\/label> \			<cdx-text-input v-model=\"description\" placeholder=\"Describe this list\" class=\"dialog-collection-input dialog-collection-editor-panel-input-description\"><\/cdx-text-input> \		<\/div> \		<template #footer=\"\"> \			<cdx-button :disabled=\"isSaveDisabled\" @click=\"save\"><\/cdx-button> \		<\/template> \	<\/cdx-dialog>"; },   "resources/ext.gadget.readinglist/Dialog.vue": function ( require, module, exports ) { const wvuiIconClose = 'M4.34 2.93l12.73 12.73-1.41 1.41L2.93 4.35z M17.07 4.34L4.34 17.07l-1.41-1.41L15.66 2.93z'; const { CdxButton, CdxIcon } = require( '@wikimedia/codex' );

module.exports = { name: 'CdxDialog', components: { CdxButton, CdxIcon },	computed: { rootClass { return { 'wvui-dialog': true, 'wvui-dialog-simple': this.simple, 'wvui-dialog-complex': !this.simple };		}	},	methods: { onContinue { this.$emit( 'continue' ) },		onCancel { this.$emit( 'cancel' ); }	},	props: { continueDisabled: { type: Boolean, default: false },		closeIcon: { type: String, default: wvuiIconClose },		continueMsg: { type: String, default: '' },		cancelMsg: { type: String, default: '' },		title: { type: String, default: 'Title of dialog' },		simple: { type: Boolean, default: true }	} };; module.exports.template = "<div :class=\"rootClass\"> \		<div class=\"wvui-dialog-shield\" @click=\"onCancel\"><\/div> \		<div class=\"wvui-dialog-container\" @click.stop=\"\"> \			<header class=\"wvui-dialog-container-heading\"> \				 <\/h2> \				<cdx-icon v-if=\"cancelMsg &amp;&amp; !simple\" class=\"wvui-dialog-container-heading-cancel\" :icon=\"closeIcon\" @click=\"onCancel\"><\/cdx-icon> \			<\/header> \			<div class=\"wvui-dialog-container-content\"> \				 <\/slot> \			<\/div> \			<nav class=\"wvui-dialog-container-footer\"> \				<slot name=\"footer\"><\/slot> \			<\/nav> \		<\/div> \	<\/div>"; },   "resources/ext.gadget.readinglist/overlays.js": function ( require, module, exports ) { const CollectionDialog = require( './CollectionDialog.vue' ); const CollectionEditorDialog = require( './CollectionEditorDialog.vue' ); const Vue = require( 'vue' ).default || require( 'vue' ); const { removeFromList, addToList, saveReadingList } = require( './api.js' );

function registerOverlayArea { if ( document.querySelectorAll('.readinglist-overlay-area').length ) { return; }	const node = document.createElement('div'); node.classList.add('readinglist-overlay-area'); document.body.appendChild(node); }

function hideOverlay( promise ) { registerOverlayArea; const container = document.querySelector( '.readinglist-overlay-area' ); container.innerHTML = ''; }

function showOverlay( promise ) { registerOverlayArea;

return promise.then((app) => {		app.mount( '.readinglist-overlay-area' );	}); }

function editListOverlay( existingID, name, description, title, onSaveFn ) { const onSave = onSaveFn || ( => {} ); return Vue.createMwApp( CollectionEditorDialog, {		initialTitle: name,		initialDescription: description,		onSave: ( name, description ) => {			hideOverlay;			saveReadingList( existingID, name, description ).then((id) => { mw.notify( existingID ? 'List edited!' : 'List created!'); if ( title ) { addToList( id, title ).then( => {						mw.notify('Added title to list!');						onSave;					}); } else { onSave; }			}, => {				mw.notify('Error creating list'); })		},		onHide: => {			hideOverlay;		}	} ); }

/** * * @return {Object} */ function createMembersOverlay( title, collections ) { return Vue.createMwApp( CollectionDialog, {		collections,		onSelect: ( id, isSelected, name, callback ) => {			if ( isSelected ) {				removeFromList( id, title ).then( => { mw.notify( isSelected ? `Removed page from ${name}` : `Added page to ${name}.` ); callback; } );			} else {				addToList( id, title ).then( => { mw.notify( isSelected ? `Removed page from ${name}` : `Added page to ${name}.` ); callback; } );			}		},		onCreate: => {			showOverlay( Promise.resolve( editListOverlay( null, , , title ) ) );		},		onHide:  => {			hideOverlay;		}	} ); }

module.exports = { showOverlay, hideOverlay, editListOverlay, createMembersOverlay }; // } } }, {   "css": [ ".mw-ui-icon-vector-gadget-pt-readinglists:before {\n background-image: linear-gradient(transparent, transparent), url(\"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2220%22 height=%2220%22 viewBox=%220 0 20 20%22%3E%3Ctitle%3E bullet list %3C/title%3E%3Cg fill=%22%23000%22%3E%3Cpath d=%22M7 15h12v2H7zm0-6h12v2H7zm0-6h12v2H7z%22/%3E%3Ccircle cx=%223%22 cy=%224%22 r=%222%22/%3E%3Ccircle cx=%223%22 cy=%2210%22 r=%222%22/%3E%3Ccircle cx=%223%22 cy=%2216%22 r=%222%22/%3E%3C/g%3E%3C/svg%3E\");\n}\n.mw-ui-icon-bookmark:before {\n  background-image: linear-gradient(transparent, transparent), url(\"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2220%22 height=%2220%22 viewBox=%220 0 20 20%22%3E%3Ctitle%3E bookmark outlined %3C/title%3E%3Cg fill=%22%23000%22%3E%3Cpath d=%22M5 1a2 2 0 0 0-2 2v16l7-5 7 5V3a2 2 0 0 0-2-2zm10 14.25-5-3.5-5 3.5V3h10z%22/%3E%3C/g%3E%3C/svg%3E\");\n}\n.readinglist-overlay-area {\n  z-index: 9999;\n}\n\n.dialog-collection h2 {\n  font-size: 1.2em;\n  border: 0;\n  font-weight: bold;\n  padding: 0;\n  margin: 0.5em 0 0;\n}\n.dialog-collection footer {\n  text-align: center;\n  position: absolute;\n  bottom: 10px;\n  left: 0;\n  right: 0;\n}\n.dialog-collection footer a {\n  color: #333;\n}\n.dialog-collection ul {\n  height: calc(100% - 100px);\n  overflow: scroll;\n}\n.dialog-collection li {\n  display: flex;\n  align-items: center;\n}\n.dialog-collection li .cdx-card__text,\n.dialog-collection li .cdx-card {\n  flex-grow: 1;\n}\n.dialog-collection li .cdx-card__text__supporting-text {\n  justify-content: end;\n  display: flex;\n}\n\n.dialog-collection-editor-panel {\n  min-width: 300px;\n}\n.dialog-collection-editor-panel-preview {\n  border-top: 1px solid rgba(0, 0, 0, 0.2);\n  border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n}\n.dialog-collection-editor-panel label {\n  margin-top: 2em;\n  font-weight: bold;\n  margin-bottom: 0.5em;\n  display: block;\n}\n.dialog-collection-editor-panel-input {\n  margin-bottom: 20px;\n  display: block;\n}\n.dialog-collection-editor-panel-input-description {\n  margin-bottom: 20px;\n}\n\n.wvui-dialog {\n  z-index: 1;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  box-sizing: border-box;\n}\n.wvui-dialog-shield,\n.wvui-dialog {\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n}\n.wvui-dialog-shield {\n  opacity: 0.5;\n  background: #ccc;\n}\n.wvui-dialog-container \u003E * {\n  margin: 0;\n}\n.wvui-dialog-container-button + .wvui-dialog-container-button {\n  margin-top: 0.5em;\n}\n.wvui-dialog-container-heading {\n  display: flex;\n}\n.wvui-dialog-container-heading h2 {\n  flex-grow: 1;\n  margin: 0;\n}\n.wvui-dialog-container {\n  position: absolute;\n  background: white;\n  margin: auto;\n}\n.wvui-dialog-container-heading-continue {\n  display: none;\n}\n.wvui-dialog-simple {\n  align-items: center;\n}\n.wvui-dialog-simple .wvui-dialog-container-heading {\n  margin: 0 0 1.25em;\n}\n.wvui-dialog-simple .wvui-dialog-container {\n  padding: 1.5em;\n  position: absolute;\n  background: white;\n  margin: auto;\n  max-width: 400px;\n}\n.wvui-dialog-complex .wvui-dialog-container {\n  height: 100%;\n  max-height: 500px;\n}\n.wvui-dialog-complex .wvui-dialog-container-content {\n  overflow: scroll;\n  height: 100%;\n}\n@media all and (max-width: 400px) {\n  .wvui-dialog-complex {\n    align-items: flex-start;\n  }\n  .wvui-dialog-complex .wvui-dialog-container {\n    width: 100%;\n    padding-bottom: 40px;\n    box-sizing: border-box;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-heading-cancel {\n    order: 1;\n    padding: 16px;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-heading h2 {\n    order: 2;\n    margin-left: 16px;\n    padding: 16px 0;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-heading-continue {\n    display: block;\n    order: 3;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-content {\n    padding: 16px;\n    box-sizing: border-box;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-footer {\n    display: none;\n  }\n}\n@media all and (min-width: 400px) {\n  .wvui-dialog-complex .wvui-dialog-container {\n    width: 500px;\n    max-width: 500px;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-heading {\n    margin: 1.25em 1.25em 1em;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-content {\n    margin: 0 1.25em;\n  }\n  .wvui-dialog-complex .wvui-dialog-container-footer {\n    border-top: 1px solid gray;\n    padding: 1.25em;\n    text-align: right;\n  }\n}\n.wvui-dialog-container-footer {\n  margin-top: 1em;\n}" ] } );