「MediaWiki:Forum.js」の版間の差分
編集の要約なし |
編集の要約なし |
||
| (同じ利用者による、間の12版が非表示) | |||
| 39行目: | 39行目: | ||
'.mw-editsection', // 節編集リンク | '.mw-editsection', // 節編集リンク | ||
'#t-permalink', // 念のため固定リンクも非表示 | '#t-permalink', // 念のため固定リンクも非表示 | ||
'#ca-history', // 「履歴を表示」タブ | |||
'#t-upload', // サイドバーのアップロードリンク | |||
].join(',') + '{display:none!important;}'; | ].join(',') + '{display:none!important;}'; | ||
document.head.appendChild(editBlockStyle); | document.head.appendChild(editBlockStyle); | ||
| 55行目: | 57行目: | ||
'シャチ', 'ベルーガ', 'マッコウクジラ', 'ザトウクジラ', 'ジンベエザメ', 'ホオジロザメ', 'ハンマーヘッドシャーク', | 'シャチ', 'ベルーガ', 'マッコウクジラ', 'ザトウクジラ', 'ジンベエザメ', 'ホオジロザメ', 'ハンマーヘッドシャーク', | ||
'エイ', 'マンボウ', 'タツノオトシゴ', 'ウツボ', 'クリオネ', 'クラゲ', 'サンゴ', 'イソギンチャク', | 'エイ', 'マンボウ', 'タツノオトシゴ', 'ウツボ', 'クリオネ', 'クラゲ', 'サンゴ', 'イソギンチャク', | ||
'ワシ', 'トンビ', 'ハト', 'インコ', 'オウム', 'ブンチョウ', 'カナリア', | 'ワシ', 'トンビ', 'ハト', 'インコ', 'オウム', 'ブンチョウ', 'カナリア', | ||
'ダチョウ', 'エミュー', 'ヒクイドリ', 'キウィ', 'フラミンゴ', 'クジャク', 'ハクチョウ', | 'ダチョウ', 'エミュー', 'ヒクイドリ', 'キウィ', 'フラミンゴ', 'クジャク', 'ハクチョウ', | ||
'カワセミ', 'ハヤブサ', 'ミミズク', 'キツツキ', 'カモメ', 'ペリカン', | 'カワセミ', 'ハヤブサ', 'ミミズク', 'キツツキ', 'カモメ', 'ペリカン', | ||
| 68行目: | 70行目: | ||
'ツチノコ', 'ネッシー', 'イエティ', 'ビッグフット', 'チュパカブラ', 'モンゴリアンデスワーム', | 'ツチノコ', 'ネッシー', 'イエティ', 'ビッグフット', 'チュパカブラ', 'モンゴリアンデスワーム', | ||
'カブトムシ', 'クワガタ', 'チョウ', 'トンボ', 'セミ', 'カマキリ', 'ハチ', 'アリ', | 'カブトムシ', 'クワガタ', 'チョウ', 'トンボ', 'セミ', 'カマキリ', 'ハチ', 'アリ', | ||
'クモ', 'サソリ', 'ムカデ', 'ダンゴムシ' | 'クモ', 'サソリ', 'ムカデ', 'ダンゴムシ', 'ペンギン', 'イルカ', 'コアラ', 'パンダ', 'レッサーパンダ', | ||
'カピバラ', 'ナマケモノ', 'オポッサム', 'ミツバチ', 'テントウムシ', 'ホタル', 'カブトガニ', 'ダイオウグソクムシ', | |||
'シーラカンス', 'オウムガイ', 'アノマロカリス', 'ウミサソリ', 'トリロバイト', 'ディメトロドン', 'エダフォサウルス', | |||
'プレシオサウルス', 'モササウルス', 'ティラノサウルス', 'トリケラトプス', 'ステゴサウルス', 'ブラキオサウルス', | |||
'アンキロサウルス', 'ディプロドクス', 'ヴェロキラプトル', 'ギガノトサウルス', 'カルノタウルス', 'メガロサウルス', | |||
'スピノサウルス', 'イグアノドン', 'パラサウロロフス', 'エドモントサウルス', 'ヒプシロフォドン', 'マイアサウラ', | |||
'プロトケラトプス', 'オヴィラプトル', 'オルニトミムス', 'シノサウルス', 'サウロロフス', 'サウルハドン', 'ガリミムス', | |||
'アルバートサウルス', 'ダコタラプトル', 'アクロカントサウルス', 'シェノサウルス', 'エラスモサウルス', 'イクチオサウルス', | |||
'オルニトレステス', 'テリジノサウルス', 'アマルガサウルス', 'シンラプトル' | |||
]; | ]; | ||
| 181行目: | 191行目: | ||
</details>` : ''; | </details>` : ''; | ||
// ================================================= | // ================================================= | ||
// ========== 書式ツールバーHTML ========== | |||
const toolbarHtml = `<div id="f-toolbar" style="margin-bottom:.4em;display:flex;gap:.3em;flex-wrap:wrap;align-items:center;padding:.3em;background:#f8f8f8;border:1px solid #ccc;border-radius:3px;"> | |||
<span style="font-size:.85em;color:#555;margin-left:.3em;">文字色:</span> | |||
${['#cc0000', '#e07000', '#007700', '#0055cc', '#7700aa', '#555555'].map(c => | |||
`<span class="f-tb-color" data-color="${c}" title="${c}" style="display:inline-block;width:1.2em;height:1.2em;background:${c};border:2px solid #aaa;border-radius:2px;cursor:pointer;vertical-align:middle;"></span>` | |||
).join('')} | |||
</div>`; | |||
// ========================================= | |||
const msg = Object.assign({ | const msg = Object.assign({ | ||
loading: '<p>読み込み中...</p>', | loading: '<p>読み込み中...</p>', | ||
postform: `<div id="f-form"><h2>投稿${fedit ? `の編集 (<a href="#post-${fedit}")>#${fedit}</a>)` : ''}</h2>${anonSettingsHtml}<div class="mw-ui-checkbox" style="margin-bottom:.3em;${fedit ? 'display:none;' : ''}"><input type="checkbox" class="mw-ui-checkbox" id="f-reply-cb"><label for="f-reply-cb" style="user-select:none;">返信する</label></div><input type="number" id="f-reply" class="mw-ui-input" style="widht:50%;margin-bottom:.5em;"><textarea accesskey="," id="wpTextbox1" cols="80" rows="25" class="mw-editfont-monospace"></textarea><input type="button" id="f-post" value="投稿" class="mw-ui-button mw-ui-progressive" style="margin-top:.5em;"><input type="button" id="f-preview" value="プレビュー" class="mw-ui-button" style="margin: .5em 0 0 .5em;"><fieldset hidden><legend>プレビュー</legend><div id="f-preview-content"></div></fieldset><style>.mw-ui-checkbox:has(#f-reply-cb:checked)+input{display:block;}#f-reply{display:none;}</style></div>`, | postform: `<div id="f-form"><h2>投稿${fedit ? `の編集 (<a href="#post-${fedit}")>#${fedit}</a>)` : ''}</h2>${anonSettingsHtml}<div class="mw-ui-checkbox" style="margin-bottom:.3em;${fedit ? 'display:none;' : ''}"><input type="checkbox" class="mw-ui-checkbox" id="f-reply-cb"><label for="f-reply-cb" style="user-select:none;">返信する</label></div><input type="number" id="f-reply" class="mw-ui-input" style="widht:50%;margin-bottom:.5em;">${toolbarHtml}<textarea accesskey="," id="wpTextbox1" cols="80" rows="25" class="mw-editfont-monospace"></textarea><input type="button" id="f-post" value="投稿" class="mw-ui-button mw-ui-progressive" style="margin-top:.5em;"><input type="button" id="f-preview" value="プレビュー" class="mw-ui-button" style="margin: .5em 0 0 .5em;"><fieldset hidden><legend>プレビュー</legend><div id="f-preview-content"></div></fieldset><style>.mw-ui-checkbox:has(#f-reply-cb:checked)+input{display:block;}#f-reply{display:none;}</style></div>`, | ||
postsummary: 'post', | postsummary: 'post', | ||
replysummary: 'reply to', | replysummary: 'reply to', | ||
editsummary: 'edit', | editsummary: 'edit', | ||
deletesummary: 'delete post', | deletesummary: 'delete post', | ||
toppage_css: "<style>.f-sticky>td:first-child>a::before{content:'';background-image:url(https://upload.wikimedia.org/wikipedia/commons/a/a5/OOjs_UI_icon_pushPin.svg);width:.8em;height:.8em;margin-right:.2em;display:inline-block;background-size:.8em}#f-loadmore{display:block;margin-left:auto;margin-right:auto;}</style>", | deletethreadsummary: 'スレッドの削除', | ||
toppage_css: "<style>.f-sticky>td:first-child>a::before{content:'';background-image:url(https://upload.wikimedia.org/wikipedia/commons/a/a5/OOjs_UI_icon_pushPin.svg);width:.8em;height:.8em;margin-right:.2em;display:inline-block;background-size:.8em}#f-loadmore{display:block;margin-left:auto;margin-right:auto;}#f-create-btn-mobile{display:none;margin:1em 0;text-align:right;}@media (max-width:768px){.mw-indicators{display:block!important;}#f-create-btn-mobile{display:block;}}#f-notice{background:#fffacd;border:1px solid #daa520;border-radius:4px;padding:1em;margin:1em 0;font-size:.95em;line-height:1.6;}</style>", | |||
toppage_notice: mw.forum.notice ? `<div id="f-notice">${mw.forum.notice}</div>` : '', | |||
load_more: '<input type="button" value="もっと読み込む" class="mw-ui-button" id="f-loadmore">', | load_more: '<input type="button" value="もっと読み込む" class="mw-ui-button" id="f-loadmore">', | ||
create: '<input type="button" class="mw-ui-button mw-ui-progressive" value="スレッドを作成">', | create: '<input type="button" class="mw-ui-button mw-ui-progressive" value="スレッドを作成">', | ||
createform: | createform: `<div id="f-form"><label>スレッド名: <input type="text" id="f-threadname" class="mw-ui-input" style="margin-bottom:.5em;"></label>${toolbarHtml}<textarea accesskey="," id="wpTextbox1" cols="80" rows="25" class="mw-editfont-monospace"></textarea><input type="button" value="スレッドを作成" id="f-create" class="mw-ui-button mw-ui-progressive" style="margin-top:.5em;"><input type="button" id="f-preview" value="プレビュー" class="mw-ui-button" style="margin: .5em 0 0 .5em;"><fieldset hidden><legend>プレビュー</legend><div id="f-preview-content"></div></fieldset></div>`, | ||
createthreadsummary: 'スレッドの作成', | createthreadsummary: 'スレッドの作成', | ||
posterror: 'エラー: 投稿できませんでした', | posterror: 'エラー: 投稿できませんでした', | ||
| 245行目: | 266行目: | ||
const source = await (await fetch(mw.config.get('wgScript') + `?title=${mw.config.get('wgPageName')}&action=raw`)).text(); | const source = await (await fetch(mw.config.get('wgScript') + `?title=${mw.config.get('wgPageName')}&action=raw`)).text(); | ||
const newText = source.replace(new RegExp(`(\\{\\{post\\|(.*?)\\|${postId}\\|(.*?)\\|4=)((.|\n)*?)(}})`), '$1[Deleted]$6'); | const newText = source.replace(new RegExp(`(\\{\\{post\\|(.*?)\\|${postId}\\|(.*?)\\|4=)((.|\n)*?)(}})`), '$1[Deleted]$6'); | ||
fetch('/ | fetch('/wiki/forum-proxy.php', { | ||
method: 'POST', | method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | headers: { 'Content-Type': 'application/json' }, | ||
| 263行目: | 284行目: | ||
}); | }); | ||
}, | }, | ||
// ========== スレッド削除(管理者のみ) ========== | |||
deleteThread: async () => { | |||
const threadName = mw.config.get('wgPageName').replace(mw.forum.toppage + '/', ''); | |||
if (!confirm(`スレッド「${threadName}」を削除しますか?\nこの操作は取り消せません。`)) return; | |||
fetch('/wiki/forum-proxy.php', { | |||
method: 'POST', | |||
headers: { 'Content-Type': 'application/json' }, | |||
body: JSON.stringify({ | |||
action: 'delete', | |||
title: mw.config.get('wgPageName'), | |||
reason: msg.deletethreadsummary | |||
}) | |||
}).then(r => r.json()).then(data => { | |||
if (data.result === 'Success') { | |||
location.href = mw.config.get('wgArticlePath').replace('$1', mw.forum.toppage); | |||
} else { | |||
mw.notify(msg.deleteerror); | |||
} | |||
}).catch(() => mw.notify(msg.deleteerror)); | |||
}, | |||
// ================================================= | |||
// ========== 書式ツールバーの初期化 ========== | |||
initToolbar: () => { | |||
const toolbar = document.querySelector('#f-toolbar'); | |||
if (!toolbar) return; | |||
const ta = document.querySelector('#wpTextbox1'); | |||
// 選択範囲をマークアップで囲むヘルパー | |||
const wrapText = (start, end) => { | |||
const s = ta.selectionStart, e = ta.selectionEnd; | |||
const selected = ta.value.substring(s, e); | |||
ta.value = ta.value.substring(0, s) + start + selected + end + ta.value.substring(e); | |||
ta.selectionStart = s + start.length; | |||
ta.selectionEnd = s + start.length + selected.length; | |||
ta.focus(); | |||
}; | |||
// 太字・斜体ボタン | |||
toolbar.querySelectorAll('.f-tb-btn').forEach(btn => { | |||
btn.onclick = () => wrapText(btn.dataset.start, btn.dataset.end); | |||
}); | |||
// 文字色ボタン | |||
toolbar.querySelectorAll('.f-tb-color').forEach(btn => { | |||
btn.onclick = () => wrapText(`<span style="color:${btn.dataset.color}">`, '</span>'); | |||
}); | |||
}, | |||
// ============================================= | |||
// ========== 名前設定UIの初期化 ========== | // ========== 名前設定UIの初期化 ========== | ||
initAnonSettings: () => { | initAnonSettings: () => { | ||
| 318行目: | 389行目: | ||
mc.innerHTML = msg.createform; | mc.innerHTML = msg.createform; | ||
document.querySelector("#f-preview").onclick = func.preview; | document.querySelector("#f-preview").onclick = func.preview; | ||
func.initToolbar(); | |||
mw.loader.using('ext.wikiEditor'); | mw.loader.using('ext.wikiEditor'); | ||
document.querySelector('#f-create').onclick = function () { | document.querySelector('#f-create').onclick = function () { | ||
let content = | // ホームへ戻るリンクをページ先頭に追加 | ||
let content = `[[${mw.forum.toppage}|← ${mw.forum.toppage}に戻る]]\n`; | |||
if (mw.forum.zeroTemplate) { | if (mw.forum.zeroTemplate) { | ||
content += `{{post|System|0|{{subst:#timel:Y/m/d H:i:s}}|4=${mw.forum.zeroTemplate}}}\n`; | content += `{{post|System|0|{{subst:#timel:Y/m/d H:i:s}}|4=${mw.forum.zeroTemplate}}}\n`; | ||
| 327行目: | 400行目: | ||
const anonParam = isAnon ? '|anon=1' : ''; | const anonParam = isAnon ? '|anon=1' : ''; | ||
content += `{{post|${mw.forum.username}|1|{{subst:#timel:Y/m/d H:i:s}}|4=${document.querySelector('#wpTextbox1').value}${anonParam}}}`; | content += `{{post|${mw.forum.username}|1|{{subst:#timel:Y/m/d H:i:s}}|4=${document.querySelector('#wpTextbox1').value}${anonParam}}}`; | ||
fetch('/ | fetch('/wiki/forum-proxy.php', { | ||
method: 'POST', | method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | headers: { 'Content-Type': 'application/json' }, | ||
| 350行目: | 423行目: | ||
mc.innerHTML = msg.loading; | mc.innerHTML = msg.loading; | ||
func.getthreads().then((res) => { | func.getthreads().then((res) => { | ||
mc.innerHTML = res[0] + msg.toppage_css; | mc.innerHTML = msg.toppage_notice + res[0] + msg.toppage_css; | ||
// モバイル用の「スレッドを作成」ボタンを追加 | |||
const mobileBtn = document.createElement('div'); | |||
mobileBtn.id = 'f-create-btn-mobile'; | |||
mobileBtn.innerHTML = msg.create; | |||
mc.insertBefore(mobileBtn, mc.firstChild); | |||
mobileBtn.querySelector('input').onclick = function () { | |||
const url = new URL(window.location.href); | |||
url.searchParams.set('newthread', '1'); | |||
location.href = url; | |||
}; | |||
if (res[1]) { | if (res[1]) { | ||
document.querySelector('#mw-content-text').innerHTML += msg.load_more; | document.querySelector('#mw-content-text').innerHTML += msg.load_more; | ||
| 382行目: | 467行目: | ||
// 名前設定UIを初期化(匿名ユーザーのみ) | // 名前設定UIを初期化(匿名ユーザーのみ) | ||
func.initAnonSettings(); | func.initAnonSettings(); | ||
// 書式ツールバーを初期化 | |||
func.initToolbar(); | |||
// 管理者にスレッド削除ボタンを表示 | |||
if (isAdmin) { | |||
const delThreadBtn = document.createElement('input'); | |||
delThreadBtn.type = 'button'; | |||
delThreadBtn.value = 'スレッドを削除'; | |||
delThreadBtn.className = 'mw-ui-button mw-ui-destructive'; | |||
delThreadBtn.style.cssText = 'margin-left:.5em;'; | |||
delThreadBtn.onclick = func.deleteThread; | |||
const ind = document.querySelector('.mw-indicators'); | |||
if (ind) ind.appendChild(delThreadBtn); | |||
} | |||
document.querySelector('#f-post').onclick = (async () => { | document.querySelector('#f-post').onclick = (async () => { | ||
document.querySelector('#f-post').disabled = true; | document.querySelector('#f-post').disabled = true; | ||
const source = (await (await fetch(mw.config.get('wgScript') + `?title=${mw.config.get('wgPageName')}&action=raw`)).text()); | const source = (await (await fetch(mw.config.get('wgScript') + `?title=${mw.config.get('wgPageName')}&action=raw`)).text()); | ||
const lp = source.split('{{post|').length; | const lp = source.split('{{post|').length - 1; // 0スレッドを考慮して投稿数+1をIDにする | ||
let summary; | let summary; | ||
if (fedit) { | if (fedit) { | ||
| 410行目: | 510行目: | ||
postBody.appendtext = `\n{{post|${mw.forum.username}|${lp}|{{subst:#timel:Y/m/d H:i:s}}|4=${document.querySelector('#wpTextbox1').value}${document.querySelector('#f-reply-cb').checked ? '|re=' + document.querySelector('#f-reply').value : ''}${anonParam}}}`; | postBody.appendtext = `\n{{post|${mw.forum.username}|${lp}|{{subst:#timel:Y/m/d H:i:s}}|4=${document.querySelector('#wpTextbox1').value}${document.querySelector('#f-reply-cb').checked ? '|re=' + document.querySelector('#f-reply').value : ''}${anonParam}}}`; | ||
} | } | ||
fetch('/ | fetch('/wiki/forum-proxy.php', { | ||
method: 'POST', | method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | headers: { 'Content-Type': 'application/json' }, | ||
| 461行目: | 561行目: | ||
}); | }); | ||
} | } | ||
// ========== レスホバープレビュー ========== | |||
// >>>N のリンクにマウスを乗せると該当投稿をポップアップ表示する | |||
(() => { | |||
// ポップアップ用の浮動div(ページに1つだけ作成) | |||
const popup = document.createElement('div'); | |||
popup.id = 'f-hover-popup'; | |||
popup.style.cssText = [ | |||
'position:fixed', | |||
'z-index:9999', | |||
'max-width:480px', | |||
'min-width:200px', | |||
'background:#fff', | |||
'border:2px solid #BBBBBB', | |||
'border-radius:3px', | |||
'box-shadow:2px 4px 12px rgba(0,0,0,.2)', | |||
'pointer-events:none', // ポップアップ自体はマウスイベントを無視 | |||
'display:none', | |||
'font-size:.9em', | |||
'line-height:1.4', | |||
].join(';'); | |||
document.body.appendChild(popup); | |||
let hideTimer = null; | |||
const showPopup = (anchorEl, postId) => { | |||
const target = document.querySelector(`#post-${postId}`); | |||
if (!target) return; | |||
// 対象投稿をクローンしてポップアップに表示 | |||
const clone = target.cloneNode(true); | |||
clone.querySelectorAll('a[href*="fedit"], span[style*="#d33"]').forEach(el => el.remove()); | |||
popup.innerHTML = ''; | |||
popup.appendChild(clone); | |||
// ポップアップの位置をリンクの近くに配置(モバイル対応) | |||
const rect = anchorEl.getBoundingClientRect(); | |||
const viewportWidth = window.innerWidth; | |||
const viewportHeight = window.innerHeight; | |||
// ポップアップの最大幅を画面幅の90%に制限 | |||
const maxWidth = Math.min(480, viewportWidth * 0.9); | |||
popup.style.maxWidth = maxWidth + 'px'; | |||
// 一度表示して実際の高さを取得 | |||
popup.style.display = 'block'; | |||
popup.style.visibility = 'hidden'; | |||
const popupHeight = popup.offsetHeight; | |||
popup.style.visibility = 'visible'; | |||
// 縦位置:下に余裕があれば下、なければ上 | |||
const spaceBelow = viewportHeight - rect.bottom; | |||
const top = spaceBelow > popupHeight + 10 | |||
? rect.bottom + 4 | |||
: Math.max(4, rect.top - popupHeight - 4); | |||
// 横位置:画面からはみ出ないように調整 | |||
let left = rect.left; | |||
if (left + maxWidth > viewportWidth - 10) { | |||
left = viewportWidth - maxWidth - 10; | |||
} | |||
if (left < 10) { | |||
left = 10; | |||
} | |||
popup.style.left = left + 'px'; | |||
popup.style.top = top + 'px'; | |||
}; | |||
const hidePopup = () => { | |||
popup.style.display = 'none'; | |||
}; | |||
// >>>N リンクにホバーイベントを設定 | |||
document.querySelectorAll('.f-content a[href^="#post-"]').forEach(link => { | |||
const postId = link.getAttribute('href').replace('#post-', ''); | |||
link.addEventListener('mouseenter', () => { | |||
clearTimeout(hideTimer); | |||
showPopup(link, postId); | |||
}); | |||
link.addEventListener('mouseleave', () => { | |||
// 少し遅延させてチラつきを防ぐ | |||
hideTimer = setTimeout(hidePopup, 100); | |||
}); | |||
}); | |||
})(); | |||
// ========================================== | |||
} | } | ||
} // end startForum | } // end startForum | ||