mirror of
https://github.com/kokonect-link/cherrypick
synced 2024-11-23 22:56:53 +09:00
feat: 캡션 미설정 안내 표시 (1673beta/cherrypick#142)
- 노트를 게시하기 전에 첨부한 파일에 캡션이 없으면 경고를 표시합니다. - 이 변경으로 이미지 뷰어의 파일 이름 영역에는 더 이상 캡션이 아닌 실제 파일 이름이 표시됩니다.
This commit is contained in:
parent
1a5f3d963b
commit
7702ff29c1
@ -45,6 +45,9 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE
|
||||
- Feat: 답글 대상 노트의 반투명 옵션을 선택할 수 있음 (kokonect-link/cherrypick#495)
|
||||
- Feat: 사용자 페이지의 미디어 탭을 그리드 레이아웃으로 설정할 수 있음 (kokonect-link/cherrypick#494)
|
||||
- Feat: 검색 위젯 (1673beta/cherrypick#125)
|
||||
- Feat: 캡션 미설정 안내 표시 (1673beta/cherrypick#142)
|
||||
- 노트를 게시하기 전에 첨부한 파일에 캡션이 없으면 경고를 표시합니다.
|
||||
- 이 변경으로 이미지 뷰어의 파일 이름 영역에는 더 이상 캡션이 아닌 실제 파일 이름이 표시됩니다.
|
||||
|
||||
### Client
|
||||
- Enhance: CherryPick 업데이트 페이지를 제어판 목록에 추가함
|
||||
@ -55,7 +58,13 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE
|
||||
- Enhance: 설정 페이지 개선
|
||||
- 일반 설정에 있던 설정 중 디자인과 관련된 설정을 모양으로 옮겼습니다.
|
||||
- Enhance: 외부 사이트로 이동할 때 경고 표시 (MisskeyIO/misskey#558)
|
||||
- Enhance: 이미지의 확장자를 더욱 정확하게 표시함
|
||||
- APNG 형식의 이미지가 GIF로 표시되던 것을 APNG로 표시하도록 변경
|
||||
- Enhance: 이미지 뷰어가 파일 이름과 캡션을 동시에 표시하도록 변경
|
||||
- Enhance: 노트를 작성할 때 첨부한 파일에 캡션이 존재하는 경우 파일 목록에 아이콘을 표시함
|
||||
- Enhance: 미디어 숨기기 버튼의 디자인을 개선함
|
||||
- Fix: 환경설정 백업 시 일부 설정이 누락되어 백업될 수 있음
|
||||
- Fix: 이미지 자르기를 할 때 이미지 크기 전체를 표시하지 못할 수 있음
|
||||
|
||||
---
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
---
|
||||
_lang_: "English"
|
||||
showNoAltWarning: "Show caption unset warning"
|
||||
showNoAltWarningDescription: "Display a warning when no alternate text is set in the image"
|
||||
filesGridLayoutInUserPage: "Change the media tab to grid layout"
|
||||
filesGridLayoutInUserPageDescription: "When this function is turned on, the Media tab on your page is shown in album format.\nTurning it off will change to the original note timeline."
|
||||
showReplyTargetNoteInSemiTransparent: "Show reply target note in semi-transparent"
|
||||
@ -1479,8 +1481,8 @@ _cherrypick:
|
||||
reactableRemoteReaction: "Allow remote custom emoji reactions to react if there is an emoji with the same name on this server."
|
||||
showFollowingMessageInsteadOfButton: "Do not show the follow button in the notification field if you are already following someone"
|
||||
mobileHeaderChange: "Header design change in mobile environment"
|
||||
renameTheButtonInPostFormToNya: "Change the \"Note\" button on the note-posting form to \"Nyan!\""
|
||||
renameTheButtonInPostFormToNyaDescription: "Outside of the note-posting form, they are still as \"Note\"."
|
||||
renameTheButtonInPostFormToNya: "Change the \"Note\" button on the posting form to \"Nyan!\""
|
||||
renameTheButtonInPostFormToNyaDescription: "Outside of the posting form, they are still as \"Note\"."
|
||||
enableWidgetsArea: "Enable the widgets area"
|
||||
disableWidgetsArea: "Disable the widgets area"
|
||||
friendlyUiEnableNotificationsArea: "Enable the notifications area"
|
||||
@ -3016,3 +3018,6 @@ _externalNavigationWarning:
|
||||
title: "Navigate to an external site"
|
||||
description: "Leave {host} and go to an external site"
|
||||
trustThisDomain: "Trust this domain on this device in the future"
|
||||
_altWarning:
|
||||
noAltWarning: "No alternate text is configured in the file."
|
||||
noAltWarningDescription: "You can change this setting in \"Settings - Appearance\"."
|
||||
|
18
locales/index.d.ts
vendored
18
locales/index.d.ts
vendored
@ -13,6 +13,14 @@ export interface Locale extends ILocale {
|
||||
* 日本語
|
||||
*/
|
||||
"_lang_": string;
|
||||
/**
|
||||
* キャプション未設定案内を表示
|
||||
*/
|
||||
"showNoAltWarning": string;
|
||||
/**
|
||||
* 画像に代替テキストが設定されていない場合に警告を表示する
|
||||
*/
|
||||
"showNoAltWarningDescription": string;
|
||||
/**
|
||||
* メディアタブをグリッドレイアウトに変更
|
||||
*/
|
||||
@ -11794,6 +11802,16 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"trustThisDomain": string;
|
||||
};
|
||||
"_altWarning": {
|
||||
/**
|
||||
* ファイルに代替テキストが設定されていません。
|
||||
*/
|
||||
"noAltWarning": string;
|
||||
/**
|
||||
* この設定は「設定 - アピアランス」で変更できます。
|
||||
*/
|
||||
"noAltWarningDescription": string;
|
||||
};
|
||||
}
|
||||
declare const locales: {
|
||||
[lang: string]: Locale;
|
||||
|
@ -1,5 +1,7 @@
|
||||
_lang_: "日本語"
|
||||
|
||||
showNoAltWarning: "キャプション未設定案内を表示"
|
||||
showNoAltWarningDescription: "画像に代替テキストが設定されていない場合に警告を表示する"
|
||||
filesGridLayoutInUserPage: "メディアタブをグリッドレイアウトに変更"
|
||||
filesGridLayoutInUserPageDescription: "この設定をオンにすると、ユーザーページのメディアタブがアルバム形式で表示されます。\nオフにすると、元のノートのタイムラインに変更されます。"
|
||||
showReplyTargetNoteInSemiTransparent: "返信対象ノートを半透明に表示"
|
||||
@ -3140,3 +3142,7 @@ _externalNavigationWarning:
|
||||
title: "外部サイトに移動します"
|
||||
description: "{host}を離れて外部サイトに移動します"
|
||||
trustThisDomain: "このデバイスで今後このドメインを信頼する"
|
||||
|
||||
_altWarning:
|
||||
noAltWarning: "ファイルに代替テキストが設定されていません。"
|
||||
noAltWarningDescription: "この設定は「設定 - アピアランス」で変更できます。"
|
||||
|
@ -1,5 +1,7 @@
|
||||
---
|
||||
_lang_: "한국어"
|
||||
showNoAltWarning: "캡션 미설정 안내 표시"
|
||||
showNoAltWarningDescription: "이미지에 캡션이 설정되어 있지 않으면 경고를 표시해요"
|
||||
filesGridLayoutInUserPage: "미디어 탭을 그리드 레이아웃으로 변경"
|
||||
filesGridLayoutInUserPageDescription: "이 설정을 켜면 사용자 페이지의 미디어 탭이 앨범 형식으로 보여져요.\n끄면 원래의 노트 타임라인으로 변경돼요."
|
||||
showReplyTargetNoteInSemiTransparent: "답글 대상 노트를 반투명하게 표시"
|
||||
@ -3047,3 +3049,6 @@ _externalNavigationWarning:
|
||||
title: "외부 사이트로 이동할까요?"
|
||||
description: "{host}을(를) 떠나 외부 사이트로 이동하려고 해요"
|
||||
trustThisDomain: "이 장치에서 앞으로 이 도메인을 신뢰할게요"
|
||||
_altWarning:
|
||||
noAltWarning: "파일에 캡션이 설정되어 있지 않아요."
|
||||
noAltWarningDescription: "이 설정은 [설정 - 모양]에서 변경할 수 있어요."
|
||||
|
@ -113,6 +113,7 @@ const onImageLoad = () => {
|
||||
loading.value = false;
|
||||
|
||||
if (cropper) {
|
||||
cropper.getCropperCanvas();
|
||||
cropper.getCropperImage()!.$center('contain');
|
||||
cropper.getCropperSelection()!.$center();
|
||||
}
|
||||
@ -159,6 +160,7 @@ onMounted(() => {
|
||||
width: var(--vw);
|
||||
height: var(--vh);
|
||||
position: relative;
|
||||
object-fit: contain;
|
||||
|
||||
> .loading {
|
||||
position: absolute;
|
||||
@ -183,6 +185,7 @@ onMounted(() => {
|
||||
> ::v-deep(cropper-canvas) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
|
||||
> cropper-selection > cropper-handle[action="move"] {
|
||||
background: transparent;
|
||||
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
{ [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive },
|
||||
]"
|
||||
>
|
||||
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
|
||||
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.comment" :title="file.name" :cover="fit !== 'contain'" :showAltIndicator="showAltIndicator"/>
|
||||
<i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i>
|
||||
<i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i>
|
||||
<i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music" :class="$style.icon"></i>
|
||||
@ -34,6 +34,7 @@ const props = defineProps<{
|
||||
file: Misskey.entities.DriveFile;
|
||||
fit: 'cover' | 'contain';
|
||||
highlightWhenSensitive?: boolean;
|
||||
showAltIndicator?: boolean;
|
||||
}>();
|
||||
|
||||
const is = computed(() => {
|
||||
|
@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
>
|
||||
<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
|
||||
<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="[$style.img, { [$style.noDrag]: noDrag }]" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
|
||||
<i v-if="alt && showAltIndicator" :class="$style.altIndicator" class="ti ti-alt"></i>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</template>
|
||||
@ -82,6 +83,7 @@ const props = withDefaults(defineProps<{
|
||||
forceBlurhash?: boolean;
|
||||
onlyAvgColor?: boolean; // 軽量化のためにBlurhashを使わずに平均色だけを描画
|
||||
noDrag?: boolean;
|
||||
showAltIndicator?: boolean;
|
||||
}>(), {
|
||||
transition: null,
|
||||
src: null,
|
||||
@ -93,6 +95,7 @@ const props = withDefaults(defineProps<{
|
||||
forceBlurhash: false,
|
||||
onlyAvgColor: false,
|
||||
noDrag: false,
|
||||
showAltIndicator: false,
|
||||
});
|
||||
|
||||
const viewId = uuid();
|
||||
@ -269,4 +272,21 @@ onUnmounted(() => {
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
}
|
||||
|
||||
.altIndicator {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
top: 2.5px;
|
||||
right: 2px;
|
||||
background-color: var(--bg);
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
color: var(--accent);
|
||||
font-size: 1em;
|
||||
padding: 2px 4px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:forceBlurhash="hide"
|
||||
:cover="hide || cover"
|
||||
:alt="image.comment || image.name"
|
||||
:title="image.comment || image.name"
|
||||
:title="image.name"
|
||||
:width="image.properties.width"
|
||||
:height="image.properties.height"
|
||||
:style="hide ? 'filter: brightness(0.7);' : null"
|
||||
@ -44,7 +44,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
<template v-else-if="controls">
|
||||
<div :class="$style.indicators">
|
||||
<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
|
||||
<div v-if="['image/gif'].includes(image.type)" :class="$style.indicator">GIF</div>
|
||||
<div v-if="['image/apng'].includes(image.type)" :class="$style.indicator">APNG</div>
|
||||
<div v-if="image.comment" :class="$style.indicator">ALT</div>
|
||||
<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
|
||||
</div>
|
||||
@ -232,10 +233,10 @@ onUnmounted(() => {
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
background-color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
color: var(--accentLighten);
|
||||
font-size: 12px;
|
||||
opacity: .5;
|
||||
font-size: 18px;
|
||||
opacity: .7;
|
||||
padding: 5px 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
@ -153,8 +153,9 @@ onMounted(() => {
|
||||
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||
}
|
||||
itemData.msrc = file.thumbnailUrl ?? undefined;
|
||||
itemData.alt = file.comment ?? file.name;
|
||||
itemData.alt = file.comment ?? undefined;
|
||||
itemData.comment = file.comment ?? file.name;
|
||||
itemData.title = file.name;
|
||||
itemData.thumbCropped = true;
|
||||
|
||||
return itemData;
|
||||
@ -180,6 +181,25 @@ onMounted(() => {
|
||||
|
||||
pswp.on('change', () => {
|
||||
textBox.textContent = pswp.currSlide?.data.comment;
|
||||
|
||||
const altText = pswp.currSlide?.data.alt || null;
|
||||
textBox.textContent = altText;
|
||||
if (!altText) {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
lightbox?.pswp?.ui?.registerElement({
|
||||
name: 'fileName',
|
||||
className: 'pswp__file-name-container',
|
||||
appendTo: 'wrapper',
|
||||
onInit: (el, pswp) => {
|
||||
const textBox = document.createElement('p');
|
||||
textBox.className = 'pswp__file-name _acrylic';
|
||||
el.appendChild(textBox);
|
||||
pswp.on('change', () => {
|
||||
textBox.textContent = pswp.currSlide?.data.title;
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -339,12 +359,20 @@ defineExpose({
|
||||
align-items: center;
|
||||
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
bottom: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
width: 75%;
|
||||
max-width: 800px;
|
||||
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.pswp__file-name-container {
|
||||
@extend .pswp__alt-text-container;
|
||||
bottom: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.pswp__alt-text {
|
||||
@ -358,4 +386,9 @@ defineExpose({
|
||||
text-shadow: var(--bg) 0 0 10px, var(--bg) 0 0 3px, var(--bg) 0 0 3px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.pswp__file-name {
|
||||
@extend .pswp__alt-text;
|
||||
max-height: 16em;
|
||||
}
|
||||
</style>
|
||||
|
@ -186,7 +186,7 @@ const visibilityButton = shallowRef<HTMLElement>();
|
||||
const posting = ref(false);
|
||||
const posted = ref(false);
|
||||
const text = ref(props.initialText ?? '');
|
||||
const files = ref(props.initialFiles ?? []);
|
||||
const files = ref(props.initialFiles ?? ([] as Misskey.entities.DriveFile[]));
|
||||
const poll = ref<PollEditorModelValue | null>(null);
|
||||
const event = ref<{
|
||||
title: string;
|
||||
@ -785,6 +785,17 @@ async function post(ev?: MouseEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defaultStore.state.showNoAltTextWarning && files.value.some((f) => f.comment == null || f.comment.length === 0)) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts._altWarning.noAltWarning,
|
||||
caption: i18n.ts._altWarning.noAltWarningDescription,
|
||||
okText: i18n.ts.goBack,
|
||||
cancelText: i18n.ts.thisPostMayBeAnnoyingIgnore,
|
||||
});
|
||||
if (!canceled) return;
|
||||
}
|
||||
|
||||
if (ev) {
|
||||
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
|
||||
|
||||
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
|
||||
<template #item="{element}">
|
||||
<div :class="$style.file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
|
||||
<MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover"/>
|
||||
<MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover" :showAltIndicator="true"/>
|
||||
<div v-if="element.isSensitive" :class="$style.sensitive">
|
||||
<i class="ti ti-eye-exclamation" style="margin: auto;"></i>
|
||||
</div>
|
||||
|
@ -204,7 +204,7 @@ const showForm = ref(false);
|
||||
const posting = ref(false);
|
||||
const posted = ref(false);
|
||||
const text = ref(props.initialText ?? '');
|
||||
const files = ref(props.initialFiles ?? []);
|
||||
const files = ref(props.initialFiles ?? ([] as Misskey.entities.DriveFile[]));
|
||||
const poll = ref<PollEditorModelValue | null>(null);
|
||||
const event = ref<{
|
||||
title: string;
|
||||
@ -774,6 +774,17 @@ async function post(ev?: MouseEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defaultStore.state.showNoAltTextWarning && files.value.some((f) => f.comment == null || f.comment.length === 0)) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts._altWarning.noAltWarning,
|
||||
caption: i18n.ts._altWarning.noAltWarningDescription,
|
||||
okText: i18n.ts.goBack,
|
||||
cancelText: i18n.ts.thisPostMayBeAnnoyingIgnore,
|
||||
});
|
||||
if (!canceled) return;
|
||||
}
|
||||
|
||||
if (ev) {
|
||||
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
|
||||
|
||||
|
@ -180,6 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkSelect>
|
||||
<MkSwitch v-model="showFixedPostFormInReplies">{{ i18n.ts.showFixedPostFormInReplies }}<template #caption>{{ i18n.ts.showFixedPostFormInRepliesDescription }}</template> <span class="_beta">CherryPick</span></MkSwitch>
|
||||
<MkSwitch v-model="allMediaNoteCollapse">{{ i18n.ts.allMediaNoteCollapse }} <span class="_beta">CherryPick</span></MkSwitch>
|
||||
<MkSwitch v-model="showNoAltTextWarning">{{ i18n.ts.showNoAltWarning }}<template #caption>{{ i18n.ts.showNoAltWarningDescription }}</template> <span class="_beta">CherryPick</span></MkSwitch>
|
||||
<MkSwitch v-model="alwaysShowCw">{{ i18n.ts.alwaysShowCw }} <span class="_beta">CherryPick</span></MkSwitch>
|
||||
<MkSwitch v-model="showReplyTargetNoteInSemiTransparent">{{ i18n.ts.showReplyTargetNoteInSemiTransparent }} <span class="_beta">CherryPick</span></MkSwitch>
|
||||
</div>
|
||||
@ -330,6 +331,7 @@ const renoteQuoteButtonSeparation = computed(defaultStore.makeGetterSetter('reno
|
||||
const showFixedPostFormInReplies = computed(defaultStore.makeGetterSetter('showFixedPostFormInReplies'));
|
||||
const showingAnimatedImages = computed(defaultStore.makeGetterSetter('showingAnimatedImages'));
|
||||
const allMediaNoteCollapse = computed(defaultStore.makeGetterSetter('allMediaNoteCollapse'));
|
||||
const showNoAltTextWarning = computed(defaultStore.makeGetterSetter('showNoAltTextWarning'));
|
||||
const alwaysShowCw = computed(defaultStore.makeGetterSetter('alwaysShowCw'));
|
||||
const showReplyTargetNoteInSemiTransparent = computed(defaultStore.makeGetterSetter('showReplyTargetNoteInSemiTransparent'));
|
||||
const nsfwOpenBehavior = computed(defaultStore.makeGetterSetter('nsfwOpenBehavior'));
|
||||
|
@ -614,6 +614,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
showNoAltTextWarning: {
|
||||
where: 'device',
|
||||
default: true,
|
||||
},
|
||||
alwaysShowCw: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
|
Loading…
Reference in New Issue
Block a user