mirror of
https://github.com/kokonect-link/cherrypick
synced 2024-12-11 13:18:54 +09:00
1 Feat, 1 Enhance
feat: 텍스트 장식(MFM, HTML, Markdown)을 보다 편하게 추가할 수 있음 enhance(frontend): HTML 태그 및 Markdown 태그가 자동 완성을 지원함
This commit is contained in:
parent
04e4347a5d
commit
4be7cf9d61
@ -36,6 +36,9 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
|
|||||||
- Feat: 아이콘 장식을 세부 조정할 수 있음 ([Secineralyr/misskey.dream@b3299181](https://github.com/Secineralyr/misskey.dream/commit/b329918194f1991c84633361d8a1319cf203641c), [Secineralyr/misskey.dream@1a9642bb](https://github.com/Secineralyr/misskey.dream/commit/1a9642bb9087a256522767e113c3bbfa87ec2e47))
|
- Feat: 아이콘 장식을 세부 조정할 수 있음 ([Secineralyr/misskey.dream@b3299181](https://github.com/Secineralyr/misskey.dream/commit/b329918194f1991c84633361d8a1319cf203641c), [Secineralyr/misskey.dream@1a9642bb](https://github.com/Secineralyr/misskey.dream/commit/1a9642bb9087a256522767e113c3bbfa87ec2e47))
|
||||||
- 위치, 크기, 불투명도를 추가로 조정할 수 있습니다.
|
- 위치, 크기, 불투명도를 추가로 조정할 수 있습니다.
|
||||||
- Feat: 노트를 클릭하여 자세히 볼 수 있음
|
- Feat: 노트를 클릭하여 자세히 볼 수 있음
|
||||||
|
- Feat: 텍스트 장식(MFM, HTML, Markdown)을 보다 편하게 추가할 수 있음
|
||||||
|
- 노트 작성 폼에서 텍스트 장식 버튼을 눌러 사용할 수 있음
|
||||||
|
- 텍스트를 선택한 상태에서도 적용 가능
|
||||||
- Revert: 사용자 통계 표시 기능 제거 ([MisskeyIO/misskey@114c7fe6](https://github.com/MisskeyIO/misskey/commit/114c7fe6b37dd6bddbcd9d92406f8b13bf688e8b))
|
- Revert: 사용자 통계 표시 기능 제거 ([MisskeyIO/misskey@114c7fe6](https://github.com/MisskeyIO/misskey/commit/114c7fe6b37dd6bddbcd9d92406f8b13bf688e8b))
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
@ -64,6 +67,8 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
|
|||||||
- 토큰을 생성할 때 토큰을 복사할 필요없이 '확인' 버튼을 누르면 자동으로 클립보드에 토큰이 복사됨
|
- 토큰을 생성할 때 토큰을 복사할 필요없이 '확인' 버튼을 누르면 자동으로 클립보드에 토큰이 복사됨
|
||||||
- 토큰을 삭제할 때 삭제 전 대화 상자가 표시됨
|
- 토큰을 삭제할 때 삭제 전 대화 상자가 표시됨
|
||||||
- Enhance: 링크 또는 내용을 복사할 때 토스트 알림 표시
|
- Enhance: 링크 또는 내용을 복사할 때 토스트 알림 표시
|
||||||
|
- Enhance: HTML 태그 및 Markdown 태그가 자동 완성을 지원함
|
||||||
|
- `<`를 입력하면 ``<b>, ~~, <i>, <small>, <center>, <plain>, `, ```, \(\), \(\\ \) `` 태그를 자동으로 입력할 수 있음
|
||||||
- Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음
|
- Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음
|
||||||
- Fix: 열람 주의로 설정된 노트의 리액션이 '더 보기'를 눌러야 표시됨
|
- Fix: 열람 주의로 설정된 노트의 리액션이 '더 보기'를 눌러야 표시됨
|
||||||
- Fix: 채널 이름이 긴 경우 게시 양식 표시가 깨지는 문제 (misskey-dev/misskey#12524)
|
- Fix: 채널 이름이 긴 경우 게시 양식 표시가 깨지는 문제 (misskey-dev/misskey#12524)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
_lang_: "English"
|
_lang_: "English"
|
||||||
|
showTextDecoration: "Show text decorations"
|
||||||
copiedLink: "The link has been copied!"
|
copiedLink: "The link has been copied!"
|
||||||
copiedContent: "Contents copied!"
|
copiedContent: "Contents copied!"
|
||||||
copied: "Copied!"
|
copied: "Copied!"
|
||||||
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
@ -3,6 +3,7 @@
|
|||||||
// Do not edit this file directly.
|
// Do not edit this file directly.
|
||||||
export interface Locale {
|
export interface Locale {
|
||||||
"_lang_": string;
|
"_lang_": string;
|
||||||
|
"showTextDecoration": string;
|
||||||
"copiedLink": string;
|
"copiedLink": string;
|
||||||
"copiedContent": string;
|
"copiedContent": string;
|
||||||
"copied": string;
|
"copied": string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
_lang_: "日本語"
|
_lang_: "日本語"
|
||||||
|
|
||||||
|
showTextDecoration: "文字装飾を追加"
|
||||||
copiedLink: "リンクをコピーしました!"
|
copiedLink: "リンクをコピーしました!"
|
||||||
copiedContent: "内容をコピーしました!"
|
copiedContent: "内容をコピーしました!"
|
||||||
copied: "コピーしました!"
|
copied: "コピーしました!"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
_lang_: "한국어"
|
_lang_: "한국어"
|
||||||
|
showTextDecoration: "텍스트 장식 표시"
|
||||||
copiedLink: "링크를 복사했어요!"
|
copiedLink: "링크를 복사했어요!"
|
||||||
copiedContent: "내용을 복사했어요!"
|
copiedContent: "내용을 복사했어요!"
|
||||||
copied: "복사했어요!"
|
copied: "복사했어요!"
|
||||||
|
@ -35,6 +35,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<span>{{ tag }}</span>
|
<span>{{ tag }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
<ol v-else-if="htmlTags.length > 0" ref="suggests" :class="$style.list">
|
||||||
|
<li v-for="tag in htmlTags" tabindex="-1" :class="$style.item" @click="complete(type, tag)" @keydown="onKeydown">
|
||||||
|
<span>{{ tag }}</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -50,7 +55,7 @@ import { emojilist, getEmojiName } from '@/scripts/emojilist.js';
|
|||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { customEmojis } from '@/custom-emojis.js';
|
import { customEmojis } from '@/custom-emojis.js';
|
||||||
import { MFM_TAGS } from '@/const.js';
|
import { MFM_TAGS, HTML_TAGS } from '@/const.js';
|
||||||
|
|
||||||
type EmojiDef = {
|
type EmojiDef = {
|
||||||
emoji: string;
|
emoji: string;
|
||||||
@ -151,6 +156,7 @@ const hashtags = ref<any[]>([]);
|
|||||||
const emojis = ref<(EmojiDef)[]>([]);
|
const emojis = ref<(EmojiDef)[]>([]);
|
||||||
const items = ref<Element[] | HTMLCollection>([]);
|
const items = ref<Element[] | HTMLCollection>([]);
|
||||||
const mfmTags = ref<string[]>([]);
|
const mfmTags = ref<string[]>([]);
|
||||||
|
const htmlTags = ref<string[]>([]);
|
||||||
const select = ref(-1);
|
const select = ref(-1);
|
||||||
const zIndex = os.claimZIndex('high');
|
const zIndex = os.claimZIndex('high');
|
||||||
|
|
||||||
@ -251,6 +257,13 @@ function exec() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q ?? ''));
|
mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q ?? ''));
|
||||||
|
} else if (props.type === 'htmlTag') {
|
||||||
|
if (!props.q || props.q === '') {
|
||||||
|
htmlTags.value = HTML_TAGS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlTags.value = HTML_TAGS.filter(tag => tag.startsWith(props.q ?? ''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<button v-tooltip="i18n.ts.disableRightClick" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: disableRightClick }]" @click="disableRightClick = !disableRightClick"><i class="ti ti-mouse-off"></i></button>
|
<button v-tooltip="i18n.ts.disableRightClick" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: disableRightClick }]" @click="disableRightClick = !disableRightClick"><i class="ti ti-mouse-off"></i></button>
|
||||||
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
|
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
|
||||||
<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
|
<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
|
||||||
|
<button v-tooltip="i18n.ts.showTextDecoration" :class="['_button', $style.footerButton]" @click="insertFunction"><i class="ti ti-palette"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.footerRight">
|
<div :class="$style.footerRight">
|
||||||
<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="$style.footerButton" @click="showPreviewMenu"><i class="ti ti-eye"></i></button>
|
<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="$style.footerButton" @click="showPreviewMenu"><i class="ti ti-eye"></i></button>
|
||||||
@ -133,6 +134,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
|
|||||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||||
import { vibrate } from '@/scripts/vibrate.js';
|
import { vibrate } from '@/scripts/vibrate.js';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
|
import { functionPicker } from '@/scripts/function-picker.js';
|
||||||
|
|
||||||
const modal = inject('modal');
|
const modal = inject('modal');
|
||||||
|
|
||||||
@ -929,6 +931,14 @@ async function insertEmoji(ev: MouseEvent) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function insertFunction(ev: MouseEvent) {
|
||||||
|
functionPicker(
|
||||||
|
ev.currentTarget ?? ev.target,
|
||||||
|
textareaEl.value,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function showActions(ev) {
|
function showActions(ev) {
|
||||||
os.popupMenu(postFormActions.map(action => ({
|
os.popupMenu(postFormActions.map(action => ({
|
||||||
text: action.title,
|
text: action.title,
|
||||||
@ -1331,9 +1341,17 @@ defineExpose({
|
|||||||
.footerLeft {
|
.footerLeft {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: column;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
|
||||||
grid-auto-rows: 40px;
|
grid-auto-rows: 40px;
|
||||||
|
overflow: scroll;
|
||||||
|
max-width: 80%;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
.scroll::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footerRight {
|
.footerRight {
|
||||||
|
@ -105,6 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<button v-tooltip="i18n.ts.disableRightClick" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: disableRightClick }]" @click="disableRightClick = !disableRightClick"><i class="ti ti-mouse-off"></i></button>
|
<button v-tooltip="i18n.ts.disableRightClick" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: disableRightClick }]" @click="disableRightClick = !disableRightClick"><i class="ti ti-mouse-off"></i></button>
|
||||||
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
|
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
|
||||||
<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
|
<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
|
||||||
|
<button v-tooltip="i18n.ts.showTextDecoration" :class="['_button', $style.footerButton]" @click="insertFunction"><i class="ti ti-palette"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.footerRight">
|
<div :class="$style.footerRight">
|
||||||
<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="$style.footerButton" @click="showPreviewMenu"><i class="ti ti-eye"></i></button>
|
<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="$style.footerButton" @click="showPreviewMenu"><i class="ti ti-eye"></i></button>
|
||||||
@ -150,6 +151,7 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
|
|||||||
import { vibrate } from '@/scripts/vibrate.js';
|
import { vibrate } from '@/scripts/vibrate.js';
|
||||||
import XSigninDialog from '@/components/MkSigninDialog.vue';
|
import XSigninDialog from '@/components/MkSigninDialog.vue';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
|
import { functionPicker } from '@/scripts/function-picker.js';
|
||||||
|
|
||||||
const modal = inject('modal');
|
const modal = inject('modal');
|
||||||
|
|
||||||
@ -923,6 +925,14 @@ async function insertEmoji(ev: MouseEvent) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function insertFunction(ev: MouseEvent) {
|
||||||
|
functionPicker(
|
||||||
|
ev.currentTarget ?? ev.target,
|
||||||
|
textareaEl.value,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function showActions(ev) {
|
function showActions(ev) {
|
||||||
os.popupMenu(postFormActions.map(action => ({
|
os.popupMenu(postFormActions.map(action => ({
|
||||||
text: action.title,
|
text: action.title,
|
||||||
@ -1380,9 +1390,17 @@ defineExpose({
|
|||||||
.footerLeft {
|
.footerLeft {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: column;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
|
||||||
grid-auto-rows: 40px;
|
grid-auto-rows: 40px;
|
||||||
|
overflow: scroll;
|
||||||
|
max-width: 80%;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
.scroll::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footerRight {
|
.footerRight {
|
||||||
|
@ -111,3 +111,4 @@ export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-foun
|
|||||||
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
|
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
|
||||||
|
|
||||||
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'font', 'blur', 'rainbow', 'sparkle', 'fade', 'rotate', 'ruby', 'unixtime'];
|
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'font', 'blur', 'rainbow', 'sparkle', 'fade', 'rotate', 'ruby', 'unixtime'];
|
||||||
|
export const HTML_TAGS = ['bold', 'strike', 'italic', 'small', 'center', 'plain', 'inlinecode', 'blockcode', 'mathinline', 'mathblock'];
|
||||||
|
@ -8,7 +8,7 @@ import getCaretCoordinates from 'textarea-caret';
|
|||||||
import { toASCII } from 'punycode/';
|
import { toASCII } from 'punycode/';
|
||||||
import { popup } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
|
|
||||||
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag';
|
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'htmlTag';
|
||||||
|
|
||||||
export class Autocomplete {
|
export class Autocomplete {
|
||||||
private suggestion: {
|
private suggestion: {
|
||||||
@ -49,7 +49,7 @@ export class Autocomplete {
|
|||||||
this.textarea = textarea;
|
this.textarea = textarea;
|
||||||
this.textRef = textRef;
|
this.textRef = textRef;
|
||||||
this.opening = false;
|
this.opening = false;
|
||||||
this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag'];
|
this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag', 'htmlTag'];
|
||||||
|
|
||||||
this.attach();
|
this.attach();
|
||||||
}
|
}
|
||||||
@ -80,12 +80,14 @@ export class Autocomplete {
|
|||||||
const hashtagIndex = text.lastIndexOf('#');
|
const hashtagIndex = text.lastIndexOf('#');
|
||||||
const emojiIndex = text.lastIndexOf(':');
|
const emojiIndex = text.lastIndexOf(':');
|
||||||
const mfmTagIndex = text.lastIndexOf('$');
|
const mfmTagIndex = text.lastIndexOf('$');
|
||||||
|
const htmlTagIndex = text.lastIndexOf('<');
|
||||||
|
|
||||||
const max = Math.max(
|
const max = Math.max(
|
||||||
mentionIndex,
|
mentionIndex,
|
||||||
hashtagIndex,
|
hashtagIndex,
|
||||||
emojiIndex,
|
emojiIndex,
|
||||||
mfmTagIndex);
|
mfmTagIndex,
|
||||||
|
htmlTagIndex);
|
||||||
|
|
||||||
if (max === -1) {
|
if (max === -1) {
|
||||||
this.close();
|
this.close();
|
||||||
@ -95,6 +97,7 @@ export class Autocomplete {
|
|||||||
const isMention = mentionIndex !== -1;
|
const isMention = mentionIndex !== -1;
|
||||||
const isHashtag = hashtagIndex !== -1;
|
const isHashtag = hashtagIndex !== -1;
|
||||||
const isMfmTag = mfmTagIndex !== -1;
|
const isMfmTag = mfmTagIndex !== -1;
|
||||||
|
const isHtmlTag = htmlTagIndex !== -1;
|
||||||
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
|
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
|
||||||
|
|
||||||
let opened = false;
|
let opened = false;
|
||||||
@ -134,6 +137,14 @@ export class Autocomplete {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isHtmlTag && !opened && this.onlyType.includes('htmlTag')) {
|
||||||
|
const htmlTag = text.substring(htmlTagIndex + 1);
|
||||||
|
if (!htmlTag.includes(' ')) {
|
||||||
|
this.open('htmlTag', htmlTag.replace('<', ''));
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
@ -280,6 +291,49 @@ export class Autocomplete {
|
|||||||
const pos = trimmedBefore.length + (value.length + 3);
|
const pos = trimmedBefore.length + (value.length + 3);
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
|
} else if (type === 'htmlTag') {
|
||||||
|
const source = this.text;
|
||||||
|
|
||||||
|
const before = source.substring(0, caret);
|
||||||
|
const trimmedBefore = before.substring(0, before.lastIndexOf('<'));
|
||||||
|
const after = source.substring(caret);
|
||||||
|
|
||||||
|
// 挿入
|
||||||
|
if (value === 'bold') this.text = `${trimmedBefore}<b>${after}</b>`;
|
||||||
|
if (value === 'strike') this.text = `${trimmedBefore}~~${after}~~`;
|
||||||
|
if (value === 'italic') this.text = `${trimmedBefore}<i>${after}</i>`;
|
||||||
|
if (value === 'small') this.text = `${trimmedBefore}<small>${after}</small>`;
|
||||||
|
if (value === 'center') this.text = `${trimmedBefore}<center>${after}</center>`;
|
||||||
|
if (value === 'plain') this.text = `${trimmedBefore}<plain>${after}</plain>`;
|
||||||
|
if (value === 'inlinecode') this.text = trimmedBefore + '`' + after + '`';
|
||||||
|
if (value === 'blockcode') this.text = trimmedBefore + '```' + '\n' + after + '\n' + '```';
|
||||||
|
if (value === 'mathinline') this.text = trimmedBefore + '\\(' + after + '\\)';
|
||||||
|
if (value === 'mathblock') this.text = trimmedBefore + '\\(' + after + '\\\\ \\)';
|
||||||
|
|
||||||
|
// キャレットを戻す
|
||||||
|
nextTick(() => {
|
||||||
|
this.textarea.focus();
|
||||||
|
const pos = trimmedBefore.length + (value.length + (
|
||||||
|
value.includes('bold')
|
||||||
|
? -1
|
||||||
|
: value.includes('strike')
|
||||||
|
? -4
|
||||||
|
: value.includes('italic')
|
||||||
|
? -3
|
||||||
|
: value.includes('small') || value.includes('center') || value.includes('plain')
|
||||||
|
? 2
|
||||||
|
: value.includes('inlinecode')
|
||||||
|
? -9
|
||||||
|
: value.includes('blockcode')
|
||||||
|
? -5
|
||||||
|
: value.includes('mathinline')
|
||||||
|
? -8
|
||||||
|
: value.includes('mathblock')
|
||||||
|
? -7
|
||||||
|
: 3
|
||||||
|
));
|
||||||
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
120
packages/frontend/src/scripts/function-picker.ts
Normal file
120
packages/frontend/src/scripts/function-picker.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { nextTick, Ref } from 'vue';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { MFM_TAGS, HTML_TAGS } from '@/const.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MFMの装飾のリストを表示する
|
||||||
|
*/
|
||||||
|
export function functionPicker(src: any, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
os.popupMenu([{
|
||||||
|
type: 'label',
|
||||||
|
}, ...getHTMLFunctionList(textArea, textRef)
|
||||||
|
, { type: 'divider' }
|
||||||
|
, ...getMFMFunctionList(textArea, textRef)], src);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHTMLFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) : object[] {
|
||||||
|
const ret: object[] = [];
|
||||||
|
HTML_TAGS.forEach(tag => {
|
||||||
|
ret.push({
|
||||||
|
text: tag,
|
||||||
|
icon: tag === 'bold' ? 'ti ti-bold' : tag === 'strike' ? 'ti ti-strikethrough' : tag === 'italic' ? 'ti ti-italic' : tag === 'small' ? 'ti ti-text-decrease' : tag === 'center' ? 'ti ti-align-center' : tag === 'plain' ? 'ti ti-clear-formatting' : tag === 'inlinecode' ? 'ti ti-code' : tag === 'blockcode' ? 'ti ti-script' : tag === 'mathinline' ? 'ti ti-math' : tag === 'mathblock' ? 'ti ti-math-function' : 'ti ti-icons',
|
||||||
|
action: () => add(textArea, textRef, tag),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMFMFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) : object[] {
|
||||||
|
const ret: object[] = [];
|
||||||
|
MFM_TAGS.forEach(tag => {
|
||||||
|
ret.push({
|
||||||
|
text: tag,
|
||||||
|
icon: 'ti ti-icons',
|
||||||
|
action: () => add(textArea, textRef, tag),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, type: string) {
|
||||||
|
const caretStart: number = textArea.selectionStart as number;
|
||||||
|
const caretEnd: number = textArea.selectionEnd as number;
|
||||||
|
|
||||||
|
MFM_TAGS.forEach(tag => {
|
||||||
|
if (type === tag) {
|
||||||
|
if (caretStart === caretEnd) {
|
||||||
|
// 単純にFunctionを追加
|
||||||
|
textRef.value = `${textRef.value.substring(0, caretStart)}$[${type} ]${textRef.value.substring(caretEnd)}`;
|
||||||
|
} else {
|
||||||
|
// 選択範囲を囲むようにFunctionを追加
|
||||||
|
textRef.value = `${textRef.value.substring(0, caretStart)}$[${type} ${textRef.value.substring(caretStart, caretEnd)}]${textRef.value.substring(caretEnd)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HTML_TAGS.forEach(tag => {
|
||||||
|
if (type === tag) {
|
||||||
|
if (caretStart === caretEnd) {
|
||||||
|
// 単純にFunctionを追加
|
||||||
|
if (tag === 'bold') textRef.value = `${textRef.value.substring(0, caretStart)}<b></b>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'strike') textRef.value = `${textRef.value.substring(0, caretStart)}~~~~${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'italic') textRef.value = `${textRef.value.substring(0, caretStart)}<i></i>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'small') textRef.value = `${textRef.value.substring(0, caretStart)}<small></small>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'center') textRef.value = `${textRef.value.substring(0, caretStart)}<center></center>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'plain') textRef.value = `${textRef.value.substring(0, caretStart)}<plain></plain>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'inlinecode') textRef.value = textRef.value.substring(0, caretStart) + '``' + textRef.value.substring(caretEnd);
|
||||||
|
if (tag === 'inlineblock') textRef.value = textRef.value.substring(0, caretStart) + '```' + '\n' + '\n' + '```' + textRef.value.substring(caretEnd);
|
||||||
|
if (tag === 'mathinline') textRef.value = textRef.value.substring(0, caretStart) + '\\(\\)' + textRef.value.substring(caretEnd);
|
||||||
|
if (tag === 'mathblock') textRef.value = textRef.value.substring(0, caretStart) + '\\(\\\\ \\)' + textRef.value.substring(caretEnd);
|
||||||
|
} else {
|
||||||
|
// 選択範囲を囲むようにFunctionを追加
|
||||||
|
if (tag === 'bold') textRef.value = `${textRef.value.substring(0, caretStart)}<b>${textRef.value.substring(caretStart, caretEnd)}</b>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'strike') textRef.value = `${textRef.value.substring(0, caretStart)}~~${textRef.value.substring(caretStart, caretEnd)}~~${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'italic') textRef.value = `${textRef.value.substring(0, caretStart)}<i>${textRef.value.substring(caretStart, caretEnd)}</i>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'small') textRef.value = `${textRef.value.substring(0, caretStart)}<small>${textRef.value.substring(caretStart, caretEnd)}</small>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'center') textRef.value = `${textRef.value.substring(0, caretStart)}<center>${textRef.value.substring(caretStart, caretEnd)}</center>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'plain') textRef.value = `${textRef.value.substring(0, caretStart)}<plain>${textRef.value.substring(caretStart, caretEnd)}</plain>${textRef.value.substring(caretEnd)}`;
|
||||||
|
if (tag === 'inlinecode') textRef.value = textRef.value.substring(0, caretStart) + '`' + textRef.value.substring(caretStart, caretEnd) + '`' + textRef.value.substring(caretEnd);
|
||||||
|
if (tag === 'inlineblock') textRef.value = textRef.value.substring(0, caretStart) + '```' + '\n' + textRef.value.substring(caretStart, caretEnd) + '\n' + '```' + textRef.value.substring(caretEnd);
|
||||||
|
if (tag === 'mathinline') textRef.value = textRef.value.substring(0, caretStart) + '\\(' + textRef.value.substring(caretStart, caretEnd) + '\\)' + textRef.value.substring(caretEnd);
|
||||||
|
if (tag === 'mathblock') textRef.value = textRef.value.substring(0, caretStart) + '\\(' + textRef.value.substring(caretStart, caretEnd) + '\\\\ \\)' + textRef.value.substring(caretEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const caretArray = (
|
||||||
|
type.includes('bold')
|
||||||
|
? -1
|
||||||
|
: type.includes('strike')
|
||||||
|
? -4
|
||||||
|
: type.includes('italic')
|
||||||
|
? -3
|
||||||
|
: type.includes('small') || type.includes('center') || type.includes('plain')
|
||||||
|
? 2
|
||||||
|
: type.includes('inlinecode')
|
||||||
|
? -9
|
||||||
|
: type.includes('inlineblock')
|
||||||
|
? -5
|
||||||
|
: type.includes('mathinline')
|
||||||
|
? -8
|
||||||
|
: type.includes('mathblock')
|
||||||
|
? -7
|
||||||
|
: 3
|
||||||
|
);
|
||||||
|
const nextCaretStart: number = caretStart + caretArray + type.length;
|
||||||
|
const nextCaretEnd: number = caretEnd + caretArray + type.length;
|
||||||
|
|
||||||
|
// キャレットを戻す
|
||||||
|
nextTick(() => {
|
||||||
|
textArea.focus();
|
||||||
|
textArea.setSelectionRange(nextCaretStart, nextCaretEnd);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user