diff --git a/CHANGELOG.md b/CHANGELOG.md index 23bd05ea8..eb06e52ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### General - Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed) - Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83) +- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正 ### Client - Enhance: 絵文字のオートコンプリート機能強化 #12364 @@ -24,6 +25,7 @@ - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 ### Server +- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように - Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303 - Fix: ロールタイムラインが保存されない問題を修正 - Fix: api.jsonの生成ロジックを改善 #12402 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5982e7171..9b5dfce63 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1856,7 +1856,7 @@ _ago: weeksAgo: "{n}週間前" monthsAgo: "{n}ヶ月前" yearsAgo: "{n}年前" - invalid: "ありません" + invalid: "日時の解析に失敗" _timeIn: seconds: "{n}秒後" diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index af602168d..e74c62e1a 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -250,6 +250,12 @@ export class MfmService { } } + function fnDefault(node: mfm.MfmFn) { + const el = doc.createElement('i'); + appendChildren(node.children, el); + return el; + } + const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => any } = { bold: (node) => { const el = doc.createElement('b'); @@ -276,17 +282,68 @@ export class MfmService { }, fn: (node) => { - if (node.props.name === 'unixtime') { - const text = node.children[0]!.type === 'text' ? node.children[0].props.text : ''; - const date = new Date(parseInt(text, 10) * 1000); - const el = doc.createElement('time'); - el.setAttribute('datetime', date.toISOString()); - el.textContent = date.toISOString(); - return el; - } else { - const el = doc.createElement('i'); - appendChildren(node.children, el); - return el; + switch (node.props.name) { + case 'unixtime': { + const text = node.children[0].type === 'text' ? node.children[0].props.text : ''; + try { + const date = new Date(parseInt(text, 10) * 1000); + const el = doc.createElement('time'); + el.setAttribute('datetime', date.toISOString()); + el.textContent = date.toISOString(); + return el; + } catch (err) { + return fnDefault(node); + } + } + + case 'ruby': { + if (node.children.length === 1) { + const child = node.children[0]; + const text = child.type === 'text' ? child.props.text : ''; + const rubyEl = doc.createElement('ruby'); + const rtEl = doc.createElement('rt'); + + // ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする + const rpStartEl = doc.createElement('rp'); + rpStartEl.appendChild(doc.createTextNode('(')); + const rpEndEl = doc.createElement('rp'); + rpEndEl.appendChild(doc.createTextNode(')')); + + rubyEl.appendChild(doc.createTextNode(text.split(' ')[0])); + rtEl.appendChild(doc.createTextNode(text.split(' ')[1])); + rubyEl.appendChild(rpStartEl); + rubyEl.appendChild(rtEl); + rubyEl.appendChild(rpEndEl); + return rubyEl; + } else { + const rt = node.children.at(-1); + + if (!rt) { + return fnDefault(node); + } + + const text = rt.type === 'text' ? rt.props.text : ''; + const rubyEl = doc.createElement('ruby'); + const rtEl = doc.createElement('rt'); + + // ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする + const rpStartEl = doc.createElement('rp'); + rpStartEl.appendChild(doc.createTextNode('(')); + const rpEndEl = doc.createElement('rp'); + rpEndEl.appendChild(doc.createTextNode(')')); + + appendChildren(node.children.slice(0, node.children.length - 1), rubyEl); + rtEl.appendChild(doc.createTextNode(text.trim())); + rubyEl.appendChild(rpStartEl); + rubyEl.appendChild(rtEl); + rubyEl.appendChild(rpEndEl); + return rubyEl; + } + } + + default: { + return fnDefault(node); + } } }, diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index f08d538fc..2eeab4d28 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -28,12 +28,25 @@ const props = withDefaults(defineProps<{ mode: 'relative', }); -const _time = props.time == null ? NaN : - typeof props.time === 'number' ? props.time : - (props.time instanceof Date ? props.time : new Date(props.time)).getTime(); +function getDateSafe(n: Date | string | number) { + try { + if (n instanceof Date) { + return n; + } + return new Date(n); + } catch (err) { + return { + getTime: () => NaN, + }; + } +} + +// eslint-disable-next-line vue/no-setup-props-destructure +const _time = props.time == null ? NaN : getDateSafe(props.time).getTime(); const invalid = Number.isNaN(_time); const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid; +// eslint-disable-next-line vue/no-setup-props-destructure let now = $ref((props.origin ?? new Date()).getTime()); const ago = $computed(() => (now - _time) / 1000/*ms*/);