From e9e6801cef62afdc2eea466e8f864fdfb0259b4c Mon Sep 17 00:00:00 2001
From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Sun, 28 Apr 2024 20:56:31 +0900
Subject: [PATCH 1/4] =?UTF-8?q?enhance(frontend):=20=E3=83=8E=E3=83=BC?=
=?UTF-8?q?=E3=83=88=E3=82=92=E3=81=9F=E3=81=9F=E3=82=80=E5=9F=BA=E6=BA=96?=
=?UTF-8?q?=E3=82=92=E4=BB=AE=E6=83=B3=E8=A1=8C=E6=95=B0=E3=81=8B=E3=82=89?=
=?UTF-8?q?=E7=AE=97=E5=AE=9A=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/src/components/MkNote.vue | 2 +-
.../src/components/MkSubNoteContent.vue | 10 +-
packages/frontend/src/scripts/collapsed.ts | 206 +++++++++++++++++-
3 files changed, 202 insertions(+), 16 deletions(-)
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 22b1691a86..f8764f3dea 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -260,7 +260,7 @@ const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
-const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
+const isLong = shouldCollapsed(appearNote.value, parsed.value, urls.value ?? []);
const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 9a07826f1a..3b47c831c2 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
({{ i18n.ts.private }})
({{ i18n.ts.deletedNote }})
-
+
RN: ...
@@ -30,8 +30,9 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend/src/scripts/collapsed.ts
index 237bd37c7a..e26b9863d1 100644
--- a/packages/frontend/src/scripts/collapsed.ts
+++ b/packages/frontend/src/scripts/collapsed.ts
@@ -3,19 +3,201 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
+import { safeParseFloat } from './safe-parse.js';
-export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
- const collapsed = note.cw == null && note.text != null && (
- (note.text.includes('$[x2')) ||
- (note.text.includes('$[x3')) ||
- (note.text.includes('$[x4')) ||
- (note.text.includes('$[scale')) ||
- (note.text.split('\n').length > 9) ||
- (note.text.length > 500) ||
- (note.files.length >= 5) ||
- (urls.length >= 4)
- );
+export function shouldCollapsed(note: Misskey.entities.Note, ast?: mfm.MfmNode[] | null, urls?: string[]): boolean {
+ if (note.cw != null) return false;
+ if (note.text == null) return false;
+ if (ast == null) return false;
+ if (note.files && note.files.length >= 5) return true;
+ if (urls && urls.length >= 4) return true;
- return collapsed;
+ // しきい値(X方向の文字数は半角換算)
+ const limitX = 55;
+ const limitY = 13.5;
+
+ let forceCollapsed = false;
+
+ // まずは、文字数を考慮せずに高さを計算
+ function getHeightForEachLine(nodes: mfm.MfmNode[], depth = 1): [number, number][] {
+ // [文字カウント, 高さ]
+ const lineHeights: [number, number][] = [];
+
+ // インライン要素の高さを追加
+ function addHeightsInline(lines: [number, number][]) {
+ // linesのはじめの要素と、lineHeightsの最後の要素を比較。それ以外はそのまま追加
+ if (lines.length === 0) return;
+
+ if (lineHeights.length === 0) {
+ lineHeights.push(...lines);
+ } else {
+ // 入力側の最初の行
+ const [firstLineCharCount, firstLineHeight] = lines.shift()!;
+
+ // 記憶側の最後の行
+ const [lastLineCharCount, lastLineHeight] = lineHeights.pop()!;
+
+ if (lastLineCharCount <= 0 && firstLineCharCount > 0) {
+ lineHeights.push([firstLineCharCount, firstLineHeight], ...lines);
+ } else {
+ lineHeights.push([firstLineCharCount + lastLineCharCount, Math.max(firstLineHeight, lastLineHeight)], ...lines);
+ }
+ }
+ }
+
+ // 半角は1、全角は2として文字数をカウント
+ function getCharCount(str: string): number {
+ return str.split('').reduce((count, char) => count + Math.min(new Blob([char]).size, 2), 0);
+ }
+
+ // 糖衣 文字列の高さ
+ function createTextHeight(text: string): [number, number][] {
+ return text.split('\n').map(l => [getCharCount(l), 1]);
+ }
+
+ // 糖衣 文字の大きさ変換
+ function transformSize(lineHeight: [number, number], size: number): [number, number] {
+ return [lineHeight[0] * size, lineHeight[1] * size];
+ }
+
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+
+ switch (node.type) {
+ case 'text': {
+ addHeightsInline(createTextHeight(node.props.text));
+ break;
+ }
+
+ case 'url': {
+ addHeightsInline(createTextHeight(node.props.url));
+ break;
+ }
+
+ case 'mention': {
+ addHeightsInline(createTextHeight(node.props.acct));
+ break;
+ }
+
+ case 'hashtag': {
+ addHeightsInline(createTextHeight('#' + node.props.hashtag));
+ break;
+ }
+
+ case 'inlineCode': {
+ addHeightsInline(createTextHeight(node.props.code));
+ break;
+ }
+
+ case 'mathInline': {
+ addHeightsInline(createTextHeight(node.props.formula));
+ break;
+ }
+
+ case 'small': {
+ addHeightsInline(getHeightForEachLine(node.children).map(h => [h[0], h[1] * 0.8]));
+ break;
+ }
+
+ case 'blockCode': {
+ // TODO: コードブロックは折り返ししないのでlimitXを考慮しないようにしたい
+ lineHeights.push(...createTextHeight(node.props.code), [0, 0]);
+ break;
+ }
+
+ case 'mathBlock': {
+ lineHeights.push(...createTextHeight(node.props.formula), [0, 0]);
+ break;
+ }
+
+ case 'search': {
+ lineHeights.push([1, 2], [0, 0]);
+ break;
+ }
+
+ case 'plain': {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1));
+ break;
+ }
+
+ case 'fn': {
+ switch (node.props.name) {
+ case 'tada': {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1).map(l => transformSize(l, 1.5)));
+ break;
+ }
+
+ case 'x2': {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1).map(l => transformSize(l, 2)));
+ break;
+ }
+
+ case 'x3': {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1).map(l => transformSize(l, 3)));
+ break;
+ }
+
+ case 'x4': {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1).map(l => transformSize(l, 4)));
+ break;
+ }
+
+ case 'scale': {
+ if ((safeParseFloat(node.props.args.x) ?? 1) > 1 || (safeParseFloat(node.props.args.y) ?? 1) > 1) {
+ forceCollapsed = true;
+ }
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1));
+ break;
+ }
+
+ default: {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1));
+ break;
+ }
+ }
+ break;
+ }
+
+ case 'center':
+ case 'quote': {
+ lineHeights.push(...getHeightForEachLine(node.children, depth + 1), [0, 0]);
+ break;
+ }
+
+ case 'unicodeEmoji':
+ case 'emojiCode': {
+ addHeightsInline([[2, 1]]);
+ break;
+ }
+
+ case 'bold':
+ case 'italic':
+ case 'strike':
+ case 'link': {
+ addHeightsInline(getHeightForEachLine(node.children, depth + 1));
+ break;
+ }
+ }
+ }
+
+ return lineHeights.filter(h => h[1] > 0);
+ }
+
+ function getHeight(nodes: mfm.MfmNode[]): number {
+ const heights = getHeightForEachLine(nodes);
+
+ // 横幅のリミットからはみ出た分、高さを追加
+ const vHeight = heights.reduce((a, b) => {
+ return a + b[1] + Math.max(Math.ceil(b[0] / limitX) - 1, 0) * b[1];
+ }, 0);
+
+ return vHeight;
+ }
+
+ const virtualHeight = getHeight(ast);
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ return forceCollapsed || virtualHeight > limitY;
}
From 0d95e97d319732cc961989ce9b4e4bebed2ac200 Mon Sep 17 00:00:00 2001
From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Sun, 28 Apr 2024 20:59:45 +0900
Subject: [PATCH 2/4] Update Changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68015596bd..1f3cae2073 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@
- Enhance: フォローするかどうかの確認ダイアログを出せるように
- Enhance: Playを手動でリロードできるように
- Enhance: 通報のコメント内のリンクをクリックした際、ウィンドウで開くように
+- Enhance: ノートをたたむ基準を変更
- Chore: AiScriptを0.18.0にバージョンアップ
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
- Fix: 周年の実績が閏年を考慮しない問題を修正
From cf48dbf6c89ad6165c770a95b17e2af90ffa5bd8 Mon Sep 17 00:00:00 2001
From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:02:00 +0900
Subject: [PATCH 3/4] add reference
---
packages/frontend/src/scripts/collapsed.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend/src/scripts/collapsed.ts
index e26b9863d1..bfb70867e8 100644
--- a/packages/frontend/src/scripts/collapsed.ts
+++ b/packages/frontend/src/scripts/collapsed.ts
@@ -48,6 +48,7 @@ export function shouldCollapsed(note: Misskey.entities.Note, ast?: mfm.MfmNode[]
}
// 半角は1、全角は2として文字数をカウント
+ // https://zenn.dev/terrierscript/articles/2020-09-19-multibyte-count
function getCharCount(str: string): number {
return str.split('').reduce((count, char) => count + Math.min(new Blob([char]).size, 2), 0);
}
From b0fe6ea04897fcadff59d081a0c087c2cacea26d Mon Sep 17 00:00:00 2001
From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:05:16 +0900
Subject: [PATCH 4/4] =?UTF-8?q?ast=E3=81=8C=E6=8F=90=E4=BE=9B=E3=81=95?=
=?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?=
=?UTF-8?q?=E3=81=AF=E3=83=91=E3=83=BC=E3=82=B9=E3=81=99=E3=82=8B=E3=82=88?=
=?UTF-8?q?=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/src/scripts/collapsed.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend/src/scripts/collapsed.ts
index bfb70867e8..ad83b648cf 100644
--- a/packages/frontend/src/scripts/collapsed.ts
+++ b/packages/frontend/src/scripts/collapsed.ts
@@ -10,10 +10,12 @@ import { safeParseFloat } from './safe-parse.js';
export function shouldCollapsed(note: Misskey.entities.Note, ast?: mfm.MfmNode[] | null, urls?: string[]): boolean {
if (note.cw != null) return false;
if (note.text == null) return false;
- if (ast == null) return false;
if (note.files && note.files.length >= 5) return true;
if (urls && urls.length >= 4) return true;
+ // ASTが提供されていない場合はパースしちゃう
+ const _ast = ast ?? mfm.parse(note.text);
+
// しきい値(X方向の文字数は半角換算)
const limitX = 55;
const limitY = 13.5;
@@ -197,7 +199,7 @@ export function shouldCollapsed(note: Misskey.entities.Note, ast?: mfm.MfmNode[]
return vHeight;
}
- const virtualHeight = getHeight(ast);
+ const virtualHeight = getHeight(_ast);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return forceCollapsed || virtualHeight > limitY;