feat: Added media right-click prevention function

This commit is contained in:
NoriDev 2022-09-07 17:09:00 +09:00
parent ed56415407
commit fc0d8238e3
15 changed files with 45 additions and 2 deletions

View file

@ -16,6 +16,7 @@
- 클라이언트: Enter 키를 눌러 보내기 옵션 추가
- 클라이언트: 서버와 연결이 끊어졌을 때 경고를 표시하지 않는 옵션 추가
- 클라이언트: (friendly) 모바일 환경에서 서버와 연결이 끊어졌을 때 표시되는 경고창의 UI 개선
- 클라이언트: 미디어 우클릭 방지 기능 추가
## 12.x.x-cp-2.x.x (unreleased)_legacy

View file

@ -1,5 +1,6 @@
---
_lang_: "English"
disableRightClick: "Prohibit right click"
useEnterToSend: "Press Enter to send"
useEnterToSendDescription: "When the option is enabled, you can use the Shift+Enter key for line break."
headlineMisskey: "A network connected by notes"

View file

@ -1,5 +1,6 @@
_lang_: "日本語"
disableRightClick: "右クリックを禁止"
useEnterToSend: "Enterキーを押して送信"
useEnterToSendDescription: "オプションを有効にすると、行替えはShiftEnterキーでできます。"
headlineMisskey: "ノートでつながるネットワーク"

View file

@ -1,5 +1,6 @@
---
_lang_: "한국어"
disableRightClick: "우클릭 방지"
useEnterToSend: "Enter 키를 눌러 보내기"
useEnterToSendDescription: "옵션을 활성화하면 줄 바꿈은 Shift + Enter 키로 할 수 있어요."
headlineMisskey: "노트로 연결되는 네트워크"

View file

@ -0,0 +1,13 @@
export class disableRightClick1629387925000 {
constructor() {
this.name = 'disableRightClick1629387925000';
}
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "disableRightClick" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "disableRIghtClick"`);
}
}

View file

@ -86,6 +86,11 @@ export class Note {
})
public localOnly: boolean;
@Column('boolean', {
default: false,
})
public disableRightClick: boolean;
@Column('smallint', {
default: 0,
})

View file

@ -234,6 +234,7 @@ export const NoteRepository = db.getRepository(Note).extend({
visibility: note.visibility,
localOnly: note.localOnly || undefined,
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
disableRightClick: note.disableRightClick || undefined,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
reactions: convertLegacyReactions(note.reactions),

View file

@ -52,6 +52,10 @@ export const packedNoteSchema = {
optional: true, nullable: true,
ref: 'Note',
},
disableRightClick: {
type: 'boolean',
optional: true, nullable: false,
},
isHidden: {
type: 'boolean',
optional: true, nullable: false,

View file

@ -253,6 +253,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
cw,
text,
localOnly: false,
disableRightClick: false,
visibility,
visibleUsers,
apMentions,

View file

@ -90,6 +90,7 @@ export const paramDef = {
text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
cw: { type: 'string', nullable: true, maxLength: 100 },
localOnly: { type: 'boolean', default: false },
disableRightClick: { type: 'boolean', default: false },
noExtractMentions: { type: 'boolean', default: false },
noExtractHashtags: { type: 'boolean', default: false },
noExtractEmojis: { type: 'boolean', default: false },
@ -261,6 +262,7 @@ export default define(meta, paramDef, async (ps, user) => {
renote,
cw: ps.cw,
localOnly: ps.localOnly,
disableRightClick: ps.disableRightClick,
visibility: ps.visibility,
visibleUsers,
channel,

View file

@ -114,6 +114,7 @@ type Option = {
files?: DriveFile[] | null;
poll?: IPoll | null;
localOnly?: boolean | null;
disableRightClick?: boolean | null;
cw?: string | null;
visibility?: string;
visibleUsers?: MinimumUser[] | null;
@ -146,6 +147,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.localOnly == null) data.localOnly = false;
if (data.disableRightClick == null) data.disableRightClick = false;
if (data.channel != null) data.visibility = 'public';
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
@ -520,6 +522,7 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O
emojis,
userId: user.id,
localOnly: data.localOnly!,
disableRightClick: data.disableRightClick!,
visibility: data.visibility as any,
visibleUserIds: data.visibility === 'specified'
? data.visibleUsers

View file

@ -67,7 +67,8 @@
</div>
</div>
<div v-if="appearNote.files.length > 0" class="files">
<XMediaList :media-list="appearNote.files"/>
<XMediaList v-if="appearNote.disableRightClick" :media-list="appearNote.files" @contextmenu.prevent/>
<XMediaList v-else :media-list="appearNote.files"/>
</div>
<XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" class="url-preview"/>

View file

@ -56,7 +56,8 @@
</div>
</div>
<div v-if="appearNote.files.length > 0" class="files">
<XMediaList :media-list="appearNote.files"/>
<XMediaList v-if="appearNote.disableRightClick" :media-list="appearNote.files" @contextmenu.prevent/>
<XMediaList v-else :media-list="appearNote.files"/>
</div>
<XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>

View file

@ -53,6 +53,7 @@
<button v-tooltip="i18n.ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button>
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button>
<button v-tooltip="i18n.ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button>
<button v-tooltip="$ts.disableRightClick" class="_button" :class="{ active: disableRightClick }" @click="disableRightClick = !disableRightClick"><i class="fas fa-mouse"></i></button>
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button>
</footer>
<datalist id="hashtags">
@ -149,6 +150,7 @@ let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(localStorage.getItem('hashtags') || '[]'));
let imeText = $ref('');
let disableRightClick = $ref(false);
const typing = throttle(3000, () => {
if (props.channel) {
@ -294,6 +296,7 @@ function watchForDraft() {
watch($$(text), () => saveDraft());
watch($$(useCw), () => saveDraft());
watch($$(cw), () => saveDraft());
watch($$(disableRightClick), () => saveDraft());
watch($$(poll), () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft());
@ -542,6 +545,7 @@ function saveDraft() {
text: text,
useCw: useCw,
cw: cw,
disableRightClick: disableRightClick,
visibility: visibility,
localOnly: localOnly,
files: files,
@ -572,6 +576,7 @@ async function post() {
localOnly: localOnly,
visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
disableRightClick: disableRightClick,
};
if (withHashtags && hashtags && hashtags.trim() !== '') {
@ -682,6 +687,7 @@ onMounted(() => {
text = draft.data.text;
useCw = draft.data.useCw;
cw = draft.data.cw;
disableRightClick = draft.data.disableRightClick;
visibility = draft.data.visibility;
localOnly = draft.data.localOnly;
files = (draft.data.files || []).filter(draftFile => draftFile);
@ -709,6 +715,7 @@ onMounted(() => {
visibility = init.visibility;
localOnly = init.localOnly;
quoteId = init.renote ? init.renote.id : null;
disableRightClick = init.disableRightClick != null;
}
nextTick(() => watchForDraft());

View file

@ -9,6 +9,7 @@
</div>
<details v-if="note.files.length > 0">
<summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary>
<XMediaList v-if="note.disableRightClick" :media-list="note.files" @contextmenu.prevent/>
<XMediaList :media-list="note.files"/>
</details>
<details v-if="note.poll">