コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

利用者:Syunsyunminmin/script/QuickEdit.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

/** Quick Edit * */

/*
 * Edit sections of a page without leaving the article
 * Original: [[en:w:User:BrandonXLF/QuickEdit]] by [[en:w:User:BrandonXLF]]
 *
 * ページ移動せずに節を編集できるようにする
 * 日本語化および改変: [[:w:ja:User:Syunsyunminmin]]
 */

(function() {
var mobile = mw.config.get('skin') === 'minerva',
	apiSingleton,
	titleRegexp = new RegExp(mw.config.get('wgArticlePath').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
		.replace(/\\\$1/, '([^?]+)') +
			'|[?&]title=([^&#]*)');

function api(func, params) {
	if (!apiSingleton) {
		apiSingleton = new mw.Api();
	}

	$.extend(params, {
		errorformat: 'html',
		errorlang: mw.config.get('wgUserLanguage'),
		errorsuselocal: true
	});

	return apiSingleton[func](params).fail(function(_, data) {
		mw.notify(apiSingleton.getErrorMessage(data), {
			type: 'error',
			tag: 'quickedit'
		});
	});
}

function getPageInfo(title, sectionID) {
	return api('get', {
		action: 'query',
		curtimestamp: 1,
		prop: 'revisions',
		indexpageids: 1,
		titles: title,
		rvprop: ['timestamp', 'content'],
		rvslots: 'main',
		rvsection: sectionID
	}).then(function(res) {
		var rev = res.query.pages[res.query.pageids[0]].revisions[0];

		return {
			start: res.curtimestamp,
			base: rev.timestamp,
			full: rev.slots.main['*']
		};
	});
}

function getPreviewCallback(editor) {
	editor.children('.preview').remove();

	new OO.ui.ProgressBarWidget().$element.css({
		maxWidth: '100%',
		borderRadius: '0',
		boxShadow: 'none',
		margin: '8px 0'
	}).addClass('preview')
		.appendTo(editor);

	return function(html) {
		editor.children('.preview').remove();

		$('<div>').html(html)
			.css({
				margin: '8px 0',
				border: '1px solid #a2a9b1',
				padding: '8px',
				overflowX: 'hidden'
			})
			.addClass('preview')
			.appendTo(editor);
	};
}

function showCompare(editor, title, from, to) {
	mw.loader.load('mediawiki.diff.styles');

	api('post', {
		'action': 'compare',
		'fromslots': 'main',
		'fromtext-main': from,
		'fromtitle': title,
		'frompst': 'true',
		'toslots': 'main',
		'totext-main': to,
		'totitle': title,
		'topst': 'true'
	}).then(function(r) {
		return r.compare['*']
			? $('<table>').addClass('diff')
				.append($('<colgroup>').append(
					$('<col>').addClass('diff-marker'),
					$('<col>').addClass('diff-content'),
					$('<col>').addClass('diff-marker'),
					$('<col>').addClass('diff-content')
				))
				.append(r.compare['*'])
			: '変更点なし。';
	})
		.then(getPreviewCallback(editor));
}

// Parts taken from EditPage::extractSectionTitle and Parser::stripSectionName
function getSectionSummary(text) {
	var match = text.match(/^(=+)(.+)\1\s*(\n|$)/);

	return !match ? '' : '/* ' + match[2].trim()
	// Strip internal link markup
		.replace(/\[\[:?([^[|]+)\|([^[]+)\]\]/g, '$2')
		.replace(/\[\[:?([^[]+)\|?\]\]/g, '$1')
	// Strip external link markup
		.replace(new RegExp('\\[(?:' + mw.config.get('wgUrlProtocols') + ')([^ ]+?) ([^\\[]+)\\]', 'ig'), '$2')
	// Remove wikitext quotes
		.replace(/(''|'''|''''')(?!')/g, '')
	// Strip HTML tags
		.replace(/<[^>]+?>/g, '') + ' */ ';
}

function showEditor(el) {
	var progress = new OO.ui.ProgressBarWidget(),

		/*
		 * https://www.mediawiki.org/wiki/Heading_HTML_changes
		 * Cannot use .closest() because DiscussionTools nests an h2 within a .mw-heading
		 */
		heading = el.parents(':header, .mw-heading').last(),
		matcher = heading.nextUntil.bind(heading),
		inserter = heading.after.bind(heading),
		targetEl = el.siblings('.quickedit-target').last(),
		titleMatch = targetEl.attr('href').match(titleRegexp),
		title = decodeURIComponent(titleMatch[1] || titleMatch[2]),
		sectionID = (/[?&]v?e?section=T?-?(\d*)/).exec(targetEl.attr('href'))[1];

	if (!heading.closest('.mw-parser-output').length) {
		var articleContent = $('#mw-content-text .mw-parser-output');

		matcher = function(selector) {
			var child = articleContent.children(selector).first();

			if (child.length) {
				return child.prevAll();
			}
			return articleContent.children();
		};
		inserter = articleContent.prepend.bind(articleContent);
	}

	inserter(progress.$element.css({
		maxWidth: '100%',
		borderRadius: '0',
		boxShadow: 'none'
	}));

	el.addClass('quickedit-loading');
	$('.quickedit-hide').removeClass('quickedit-hide');
	$('.quickedit-heading').removeClass('quickedit-heading');
	$('#quickedit-editor').remove();

	getPageInfo(title, sectionID).then(function(r) {
		var start = r.start,
			base = r.base,
			full = r.full,
			saving = false,
			expanded = false,
			remainderStart = full.match(/\n=+.+=+(?:\n|$)/),
			part = remainderStart ? full.substring(0, remainderStart.index) : full,
			remainder = remainderStart ? full.substring(remainderStart.index) : '',
			level = 0,
			editor;

		full.replace(/^(=+).+?(=+)(?:\n|$)/, function(m, a, b) {
			level = Math.min(a.length, b.length);
			return m;
		});

		var levelMatch = 'h1';
		for (var i = 2; i <= level; i++) {
			levelMatch += ',h' + i + ':has(*), .mw-heading' + i;
		}

		var partSection = matcher(':header:has(*), .mw-heading'),
			fullSection = matcher(levelMatch),
			textarea = new OO.ui.MultilineTextInputWidget({
				rows: 1,
				maxRows: 20,
				autosize: true,
				value: part
			}),
			summary = new OO.ui.TextInputWidget({
				value: getSectionSummary(part)
			}),
			minor = new OO.ui.CheckboxInputWidget(),
			save = new OO.ui.ButtonInputWidget({
				label: '保存',
				title: '変更を保存する',
				flags: ['primary', 'progressive']
			}),
			preview = new OO.ui.ButtonInputWidget({
				label: 'プレビュー',
				title: '新しいウィキテキストをプレビューする'
			}),
			compare = new OO.ui.ButtonInputWidget({
				label: '比較',
				title: '現在の版との違いを確認する'
			}),
			cancel = new OO.ui.ButtonInputWidget({
				useInputTag: true,
				label: 'キャンセル',
				title: '編集フォームを閉じて、変更を破棄する',
				flags: ['secondary', 'destructive']
			}),
			more = new OO.ui.ButtonInputWidget({
				label: '+',
				title: '節全体(サブセクションを含む)を編集する'
			}),
			buttons = new OO.ui.HorizontalLayout({
				items: [save, preview, compare, cancel]
			});

		if (part !== full) {
			buttons.addItems([more], 3);
		}

		partSection.addClass('quickedit-hide');
		heading.addClass('quickedit-heading');
		el.removeClass('quickedit-loading');
		progress.$element.remove();
		textarea.$input.css({
			borderRadius: '0'
		});

		summary.on('enter', function() {
			save.emit('click');
		});

		save.on('click', function() {
			if (saving) {
				return;
			}

			var fullText = textarea.getValue() + (expanded ? '' : remainder);
			saving = true;
			save.setLabel('保存中...');
			compare.setDisabled(true);
			preview.setDisabled(true);
			cancel.setDisabled(true);
			more.setDisabled(true);

			api('postWithEditToken', {
				action: 'edit',
				title: title,
				section: sectionID,
				summary: summary.getValue() + ' ([[:w:ja:User:Syunsyunminmin/script/QuickEdit.js|QuickEdit]])',
				text: fullText,
				minor: minor.isSelected() ? true : undefined,
				notminor: minor.isSelected() ? undefined : true,
				starttimestamp: start,
				basetimestamp: base
			}).then(function() {
				api('get', {
					action: 'parse',
					page: mw.config.get('wgPageName'),
					prop: ['text', 'categorieshtml']
				}).then(function(r) {
					var contentText = $('#mw-content-text'),
						catLinks = $('#catlinks');

					contentText.find('.mw-parser-output').replaceWith(r.parse.text['*']);
					mw.hook('wikipage.content').fire(contentText);

					catLinks.replaceWith(r.parse.categorieshtml['*']);
					mw.hook('wikipage.categories').fire(catLinks);

					mw.notify('変更は正常に保存されました。', {
						tag: 'quickedit'
					});

					saving = false;
				});
			}, function(code) {
				if (code === 'editconflict') {
					showEditConflict(editor, title, sectionID, fullText).then(function(r) {
						start = r.start;
						base = r.base;
						textarea = r.textarea;
						expanded = true;
					});
				}

				compare.setDisabled(false);
				preview.setDisabled(false);
				cancel.setDisabled(false);
				more.setDisabled(expanded);
				saving = false;
				save.setLabel('保存');
			});
		});

		preview.on('click', function() {
			api('post', {
				action: 'parse',
				title: title,
				prop: 'text',
				pst: 'true',
				disablelimitreport: 'true',
				disableeditsection: 'true',
				sectionpreview: 'true',
				disabletoc: 'true',
				text: textarea.getValue()
			}).then(function(r) {
				return r.parse.text['*'] + '<div style="clear:both;"></div>';
			})
				.then(getPreviewCallback(editor));
		});

		compare.on('click', function() {
			showCompare(editor, title, part + (expanded ? remainder : ''), textarea.getValue());
		});

		cancel.on('click', function() {
			editor.remove();
			heading.removeClass('quickedit-heading');
			fullSection.removeClass('quickedit-hide');
		});

		more.on('click', function() {
			expanded = true;
			textarea.setValue(textarea.getValue() + remainder);
			fullSection.addClass('quickedit-hide');
			more.setDisabled(true);
		});

		editor = $('<div id="quickedit-editor">').css({
			overflowX: 'hidden'
		})
			.append($('<div>').css({
				backgroundColor: '#eaecf0',
				borderBottom: '1px solid #a2a9b1',
				marginBottom: '8px'
			})
				.append(
					textarea.$element.css({
						width: '100%',
						maxWidth: '100%',
						fontFamily: 'monospace, monospace'
					}).addClass('quickedit-textarea'),
					$('<div>').css({
						border: '1px solid #a2a9b1',
						borderWidth: '0 1px'
					})
						.append(
							$('<div>').css({
								'padding': '8px 4px 8px 8px',
								'display': 'table-cell',
								'verticalAlign': 'middle',
								'white-space': 'nowrap'
							})
								.html('編集要約:'),
							summary.$element.css({
								width: '100%',
								maxWidth: '100%',
								padding: '8px 0px',
								display: 'table-cell',
								verticalAlign: 'middle'
							}),
							new OO.ui.FieldLayout(minor, {
								label: new OO.ui.HtmlSnippet('細部の編集?'),
								align: 'inline'
							}).$element.css({
								'padding': '8px 8px 8px 4px',
								'display': 'table-cell',
								'verticalAlign': 'middle',
								'white-space': 'nowrap'
							})
						),
					buttons.$element.css({
						border: '1px solid #a2a9b1',
						borderWidth: '0 1px',
						padding: '0px 8px 0'
					}),
					title !== mw.config.get('wgPageName')
						? $('<div>').css({
							'border': '1px solid #a2a9b1',
							'borderWidth': '0 1px',
							'padding': '0px 8px 8px',
							'white-space': 'nowrap'
						})
							.append(
								'編集中のページ: ',
								$('<a>').attr('href', mw.config.get('wgArticlePath').replace('$1', title))
									.css({
										fontWeight: 'bold'
									})
									.text(title.replace(/_/g, ' '))
							)
						: undefined
				));

		inserter(editor);
	}, function() {
		el.removeClass('quickedit-loading');
		progress.$element.remove();
	});
}

function showEditConflict(editor, title, sectionID, text) {
	return getPageInfo(title, sectionID).then(function(r) {
		var textarea = new OO.ui.MultilineTextInputWidget({
				rows: 1,
				maxRows: 20,
				autosize: true,
				value: r.full
			}),
			textarea2 = new OO.ui.MultilineTextInputWidget({
				rows: 1,
				maxRows: 20,
				autosize: true,
				value: text
			});

		function syncSize() {
			textarea.styleHeight = -1;
			textarea.adjustSize(true);

			textarea2.styleHeight = -1;
			textarea2.adjustSize(true);

			var height = Math.max(textarea.$input.height(), textarea2.$input.height());
			textarea.$input.height(height);
			textarea2.$input.height(height);
		}

		textarea.$input.css({
			borderRadius: '0'
		});
		editor.find('> :first-child > :first-child').remove();

		$('<table>').css({
			width: '100%',
			border: '1px solid #a2a9b1',
			borderBottom: 'none',
			borderSpacing: '0',
			margin: '0 !important'
		})
			.append(
				$('<tr>').append(
					$('<th>').css({
						width: '50%',
						paddingTop: '4px'
					})
						.text('現在の版 (保存予定)'),
					$('<th>').css({
						width: '50%',
						paddingTop: '4px'
					})
						.text('あなたの変更')
				),
				$('<tr>').append(
					$('<td>').css({
						width: '50%',
						padding: '4px 4px 0 8px'
					})
						.append(textarea.$element.css({
							width: '100%',
							maxWidth: '100%',
							fontFamily: 'monospace, monospace'
						})),
					$('<td>').css({
						width: '50%',
						padding: '4px 8px 0 4px'
					})
						.append(textarea2.$element.css({
							width: '100%',
							maxWidth: '100%',
							fontFamily: 'monospace, monospace'
						}))
				)
			)
			.prependTo(editor.find('> :first-child'));

		textarea.on('change', syncSize);
		textarea2.on('change', syncSize);
		syncSize();
		showCompare(editor, title, text, r.full);

		r.textarea = textarea;
		return r;
	});
}

function clickHandler(e) {
	var el = $(e.target);

	if (!el.hasClass('quickedit-editlink') || el.hasClass('quickedit-loading')) {
		return;
	}

	e.preventDefault();

	showEditor(el);
}

function addLinksToChildren(element) {
	element.find('#quickedit-editor, .quickedit-section').remove();
	element.find('.mw-editsection').each(function() {
		$('[href*="section="]', this).last()
			.after(
				mobile ? '' : '<span class="quickedit-section"> | </span>',
				$('<a>')
					.html(mobile ? '&nbsp;Q' : 'クイック編集')
					.addClass('quickedit-section quickedit-editlink')
					.attr('href', '#')
			)
			.addClass('quickedit-target');
	});
}

$.when(mw.loader.using('oojs-ui-core'), $.ready).then(function() {
	var body = $(document.body);

	body.on('click', clickHandler);
	addLinksToChildren(body);
	mw.hook('wikipage.content').add(addLinksToChildren);
});

mw.loader.addStyleTag('.skin-minerva .mw-editsection { white-space: nowrap; }' +
		'.skin-minerva .content .collapsible-heading .quickedit-section { visibility: hidden; }' +
		'.skin-minerva .content .collapsible-heading.open-block .quickedit-section { visibility: visible; }' +
		'.quickedit-hide { display: none !important; }' +
		'.quickedit-loading, .quickedit-heading { color: #777; }');
}());