This commit is contained in:
parent
48246bd166
commit
539356023d
|
@ -146,7 +146,7 @@ import { getPluginHandlers } from '@/plugin.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { checkDragDataType, getDragData } from '@/drag-and-drop.js';
|
import { checkDragDataType, getDragData } from '@/drag-and-drop.js';
|
||||||
import { useUploader } from '@/composables/use-uploader.js';
|
import { FileUploader } from '@/composables/use-uploader.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ const targetChannel = shallowRef(props.channel);
|
||||||
const serverDraftId = ref<string | null>(null);
|
const serverDraftId = ref<string | null>(null);
|
||||||
const postFormActions = getPluginHandlers('post_form_action');
|
const postFormActions = getPluginHandlers('post_form_action');
|
||||||
|
|
||||||
const uploader = useUploader({
|
const uploader = new FileUploader({
|
||||||
multiple: true,
|
multiple: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -223,6 +223,10 @@ uploader.events.on('itemUploaded', ctx => {
|
||||||
uploader.removeItem(ctx.item);
|
uploader.removeItem(ctx.item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (props.initialLocalFiles) {
|
||||||
|
uploader.addFiles(props.initialLocalFiles);
|
||||||
|
}
|
||||||
|
|
||||||
const draftKey = computed((): string => {
|
const draftKey = computed((): string => {
|
||||||
let key = targetChannel.value ? `channel:${targetChannel.value.id}` : '';
|
let key = targetChannel.value ? `channel:${targetChannel.value.id}` : '';
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { useUploader } from '@/composables/use-uploader.js';
|
import { FileUploader } from '@/composables/use-uploader.js';
|
||||||
import MkUploaderItems from '@/components/MkUploaderItems.vue';
|
import MkUploaderItems from '@/components/MkUploaderItems.vue';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
@ -80,7 +80,7 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const dialog = useTemplateRef('dialog');
|
const dialog = useTemplateRef('dialog');
|
||||||
|
|
||||||
const uploader = useUploader({
|
const uploader = new FileUploader({
|
||||||
multiple: props.multiple,
|
multiple: props.multiple,
|
||||||
folderId: props.folderId,
|
folderId: props.folderId,
|
||||||
features: props.features,
|
features: props.features,
|
||||||
|
|
|
@ -99,31 +99,43 @@ function getCompressionSettings(level: 0 | 1 | 2 | 3) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUploader(options: {
|
export class FileUploader {
|
||||||
folderId?: string | null;
|
private $i: Misskey.entities.MeDetailed;
|
||||||
multiple?: boolean;
|
public events = new EventEmitter<{
|
||||||
features?: UploaderFeatures;
|
|
||||||
} = {}) {
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const events = new EventEmitter<{
|
|
||||||
'itemUploaded': (ctx: { item: UploaderItem; }) => void;
|
'itemUploaded': (ctx: { item: UploaderItem; }) => void;
|
||||||
}>();
|
}>();
|
||||||
|
private uploaderFeatures = computed<Required<UploaderFeatures>>(() => {
|
||||||
const uploaderFeatures = computed<Required<UploaderFeatures>>(() => {
|
|
||||||
return {
|
return {
|
||||||
imageEditing: options.features?.imageEditing ?? true,
|
imageEditing: this.options.features?.imageEditing ?? true,
|
||||||
watermark: options.features?.watermark ?? true,
|
watermark: this.options.features?.watermark ?? true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
public items = ref<UploaderItem[]>([]);
|
||||||
|
public uploading = computed(() => this.items.value.some(item => item.uploading));
|
||||||
|
public readyForUpload = computed(() => this.items.value.length > 0 && this.items.value.some(item => item.uploaded == null) && !this.items.value.some(item => item.uploading || item.preprocessing));
|
||||||
|
public allItemsUploaded = computed(() => this.items.value.every(item => item.uploaded != null));
|
||||||
|
|
||||||
const items = ref<UploaderItem[]>([]);
|
constructor(
|
||||||
|
public options: {
|
||||||
|
folderId?: string | null;
|
||||||
|
multiple?: boolean;
|
||||||
|
features?: UploaderFeatures;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
this.$i = ensureSignin();
|
||||||
|
|
||||||
function initializeFile(file: File) {
|
onUnmounted(() => {
|
||||||
|
for (const item of this.items.value) {
|
||||||
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeFile(file: File) {
|
||||||
const id = genId();
|
const id = genId();
|
||||||
const filename = file.name ?? 'untitled';
|
const filename = file.name ?? 'untitled';
|
||||||
const extension = filename.split('.').length > 1 ? '.' + filename.split('.').pop() : '';
|
const extension = filename.split('.').length > 1 ? '.' + filename.split('.').pop() : '';
|
||||||
items.value.push({
|
this.items.value.push({
|
||||||
id,
|
id,
|
||||||
name: prefer.s.keepOriginalFilename ? filename : id + extension,
|
name: prefer.s.keepOriginalFilename ? filename : id + extension,
|
||||||
progress: null,
|
progress: null,
|
||||||
|
@ -134,27 +146,27 @@ export function useUploader(options: {
|
||||||
uploaded: null,
|
uploaded: null,
|
||||||
uploadFailed: false,
|
uploadFailed: false,
|
||||||
compressionLevel: prefer.s.defaultImageCompressionLevel,
|
compressionLevel: prefer.s.defaultImageCompressionLevel,
|
||||||
watermarkPresetId: uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? prefer.s.defaultWatermarkPresetId : null,
|
watermarkPresetId: this.uploaderFeatures.value.watermark && this.$i.policies.watermarkAvailable ? prefer.s.defaultWatermarkPresetId : null,
|
||||||
file: markRaw(file),
|
file: markRaw(file),
|
||||||
});
|
});
|
||||||
const reactiveItem = items.value.at(-1)!;
|
const reactiveItem = this.items.value.at(-1)!;
|
||||||
preprocess(reactiveItem).then(() => {
|
this.preprocess(reactiveItem).then(() => {
|
||||||
triggerRef(items);
|
triggerRef(this.items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFiles(newFiles: File[]) {
|
public addFiles(newFiles: File[]) {
|
||||||
for (const file of newFiles) {
|
for (const file of newFiles) {
|
||||||
initializeFile(file);
|
this.initializeFile(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeItem(item: UploaderItem) {
|
public removeItem(item: UploaderItem) {
|
||||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
items.value.splice(items.value.indexOf(item), 1);
|
this.items.value.splice(this.items.value.indexOf(item), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMenu(item: UploaderItem): MenuItem[] {
|
public getMenu(item: UploaderItem): MenuItem[] {
|
||||||
const menu: MenuItem[] = [];
|
const menu: MenuItem[] = [];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -206,7 +218,7 @@ export function useUploader(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
uploaderFeatures.value.imageEditing &&
|
this.uploaderFeatures.value.imageEditing &&
|
||||||
IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) &&
|
IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) &&
|
||||||
!item.preprocessing &&
|
!item.preprocessing &&
|
||||||
!item.uploading &&
|
!item.uploading &&
|
||||||
|
@ -222,14 +234,14 @@ export function useUploader(options: {
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const cropped = await os.cropImageFile(item.file, { aspectRatio: null });
|
const cropped = await os.cropImageFile(item.file, { aspectRatio: null });
|
||||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
items.value.splice(items.value.indexOf(item), 1, {
|
this.items.value.splice(this.items.value.indexOf(item), 1, {
|
||||||
...item,
|
...item,
|
||||||
file: markRaw(cropped),
|
file: markRaw(cropped),
|
||||||
thumbnail: window.URL.createObjectURL(cropped),
|
thumbnail: window.URL.createObjectURL(cropped),
|
||||||
});
|
});
|
||||||
const reactiveItem = items.value.find(x => x.id === item.id)!;
|
const reactiveItem = this.items.value.find(x => x.id === item.id)!;
|
||||||
preprocess(reactiveItem).then(() => {
|
this.preprocess(reactiveItem).then(() => {
|
||||||
triggerRef(items);
|
triggerRef(this.items);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}, /*{
|
}, /*{
|
||||||
|
@ -247,14 +259,14 @@ export function useUploader(options: {
|
||||||
}, {
|
}, {
|
||||||
ok: (file) => {
|
ok: (file) => {
|
||||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
items.value.splice(items.value.indexOf(item), 1, {
|
this.items.value.splice(this.items.value.indexOf(item), 1, {
|
||||||
...item,
|
...item,
|
||||||
file: markRaw(file),
|
file: markRaw(file),
|
||||||
thumbnail: window.URL.createObjectURL(file),
|
thumbnail: window.URL.createObjectURL(file),
|
||||||
});
|
});
|
||||||
const reactiveItem = items.value.find(x => x.id === item.id)!;
|
const reactiveItem = this.items.value.find(x => x.id === item.id)!;
|
||||||
preprocess(reactiveItem).then(() => {
|
this.preprocess(reactiveItem).then(() => {
|
||||||
triggerRef(items);
|
triggerRef(this.items);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -265,19 +277,19 @@ export function useUploader(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
uploaderFeatures.value.watermark &&
|
this.uploaderFeatures.value.watermark &&
|
||||||
$i.policies.watermarkAvailable &&
|
this.$i.policies.watermarkAvailable &&
|
||||||
WATERMARK_SUPPORTED_TYPES.includes(item.file.type) &&
|
WATERMARK_SUPPORTED_TYPES.includes(item.file.type) &&
|
||||||
!item.preprocessing &&
|
!item.preprocessing &&
|
||||||
!item.uploading &&
|
!item.uploading &&
|
||||||
!item.uploaded
|
!item.uploaded
|
||||||
) {
|
) {
|
||||||
function changeWatermarkPreset(presetId: string | null) {
|
const changeWatermarkPreset = (presetId: string | null) => {
|
||||||
item.watermarkPresetId = presetId;
|
item.watermarkPresetId = presetId;
|
||||||
preprocess(item).then(() => {
|
this.preprocess(item).then(() => {
|
||||||
triggerRef(items);
|
triggerRef(this.items);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
menu.push({
|
menu.push({
|
||||||
icon: 'ti ti-copyright',
|
icon: 'ti ti-copyright',
|
||||||
|
@ -323,12 +335,12 @@ export function useUploader(options: {
|
||||||
!item.uploading &&
|
!item.uploading &&
|
||||||
!item.uploaded
|
!item.uploaded
|
||||||
) {
|
) {
|
||||||
function changeCompressionLevel(level: 0 | 1 | 2 | 3) {
|
const changeCompressionLevel = (level: 0 | 1 | 2 | 3) => {
|
||||||
item.compressionLevel = level;
|
item.compressionLevel = level;
|
||||||
preprocess(item).then(() => {
|
this.preprocess(item).then(() => {
|
||||||
triggerRef(items);
|
triggerRef(this.items);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
menu.push({
|
menu.push({
|
||||||
icon: 'ti ti-leaf',
|
icon: 'ti ti-leaf',
|
||||||
|
@ -381,14 +393,14 @@ export function useUploader(options: {
|
||||||
icon: 'ti ti-upload',
|
icon: 'ti ti-upload',
|
||||||
text: i18n.ts.upload,
|
text: i18n.ts.upload,
|
||||||
action: () => {
|
action: () => {
|
||||||
uploadOne(item);
|
this.uploadOne(item);
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-x',
|
icon: 'ti ti-x',
|
||||||
text: i18n.ts.remove,
|
text: i18n.ts.remove,
|
||||||
danger: true,
|
danger: true,
|
||||||
action: () => {
|
action: () => {
|
||||||
removeItem(item);
|
this.removeItem(item);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (item.uploading) {
|
} else if (item.uploading) {
|
||||||
|
@ -409,13 +421,13 @@ export function useUploader(options: {
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadOne(item: UploaderItem): Promise<void> {
|
private async uploadOne(item: UploaderItem): Promise<void> {
|
||||||
item.uploadFailed = false;
|
item.uploadFailed = false;
|
||||||
item.uploading = true;
|
item.uploading = true;
|
||||||
|
|
||||||
const { filePromise, abort } = uploadFile(item.preprocessedFile ?? item.file, {
|
const { filePromise, abort } = uploadFile(item.preprocessedFile ?? item.file, {
|
||||||
name: item.uploadName ?? item.name,
|
name: item.uploadName ?? item.name,
|
||||||
folderId: options.folderId === undefined ? prefer.s.uploadFolder : options.folderId,
|
folderId: this.options.folderId === undefined ? prefer.s.uploadFolder : this.options.folderId,
|
||||||
isSensitive: item.isSensitive ?? false,
|
isSensitive: item.isSensitive ?? false,
|
||||||
caption: item.caption ?? null,
|
caption: item.caption ?? null,
|
||||||
onProgress: (progress) => {
|
onProgress: (progress) => {
|
||||||
|
@ -438,7 +450,7 @@ export function useUploader(options: {
|
||||||
await filePromise.then((file) => {
|
await filePromise.then((file) => {
|
||||||
item.uploaded = file;
|
item.uploaded = file;
|
||||||
item.abort = null;
|
item.abort = null;
|
||||||
events.emit('itemUploaded', { item });
|
this.events.emit('itemUploaded', { item });
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
item.uploadFailed = true;
|
item.uploadFailed = true;
|
||||||
item.progress = null;
|
item.progress = null;
|
||||||
|
@ -450,26 +462,26 @@ export function useUploader(options: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload() { // エラーハンドリングなどを考慮してシーケンシャルにやる
|
public async upload() { // エラーハンドリングなどを考慮してシーケンシャルにやる
|
||||||
items.value = items.value.map(item => ({
|
this.items.value = this.items.value.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
aborted: false,
|
aborted: false,
|
||||||
uploadFailed: false,
|
uploadFailed: false,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for (const item of items.value.filter(item => item.uploaded == null)) {
|
for (const item of this.items.value.filter(item => item.uploaded == null)) {
|
||||||
// アップロード処理途中で値が変わる場合(途中で全キャンセルされたりなど)もあるので、Array filterではなくここでチェック
|
// アップロード処理途中で値が変わる場合(途中で全キャンセルされたりなど)もあるので、Array filterではなくここでチェック
|
||||||
if (item.aborted) {
|
if (item.aborted) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await uploadOne(item);
|
await this.uploadOne(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function abortAll() {
|
public abortAll() {
|
||||||
for (const item of items.value) {
|
for (const item of this.items.value) {
|
||||||
if (item.uploaded != null) {
|
if (item.uploaded != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -482,12 +494,12 @@ export function useUploader(options: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preprocess(item: UploaderItem): Promise<void> {
|
private async preprocess(item: UploaderItem): Promise<void> {
|
||||||
item.preprocessing = true;
|
item.preprocessing = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
|
if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
|
||||||
await preprocessForImage(item);
|
await this.preprocessForImage(item);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to preprocess image', err);
|
console.error('Failed to preprocess image', err);
|
||||||
|
@ -498,12 +510,12 @@ export function useUploader(options: {
|
||||||
item.preprocessing = false;
|
item.preprocessing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preprocessForImage(item: UploaderItem): Promise<void> {
|
private async preprocessForImage(item: UploaderItem): Promise<void> {
|
||||||
const imageBitmap = await window.createImageBitmap(item.file);
|
const imageBitmap = await window.createImageBitmap(item.file);
|
||||||
|
|
||||||
let preprocessedFile: Blob | File = item.file;
|
let preprocessedFile: Blob | File = item.file;
|
||||||
|
|
||||||
const needsWatermark = item.watermarkPresetId != null && WATERMARK_SUPPORTED_TYPES.includes(preprocessedFile.type) && $i.policies.watermarkAvailable;
|
const needsWatermark = item.watermarkPresetId != null && WATERMARK_SUPPORTED_TYPES.includes(preprocessedFile.type) && this.$i.policies.watermarkAvailable;
|
||||||
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
||||||
if (needsWatermark && preset != null) {
|
if (needsWatermark && preset != null) {
|
||||||
const canvas = window.document.createElement('canvas');
|
const canvas = window.document.createElement('canvas');
|
||||||
|
@ -563,24 +575,4 @@ export function useUploader(options: {
|
||||||
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
|
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
|
||||||
item.preprocessedFile = markRaw(preprocessedFile);
|
item.preprocessedFile = markRaw(preprocessedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
for (const item of items.value) {
|
|
||||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
items,
|
|
||||||
addFiles,
|
|
||||||
removeItem,
|
|
||||||
abortAll,
|
|
||||||
upload,
|
|
||||||
getMenu,
|
|
||||||
uploading: computed(() => items.value.some(item => item.uploading)),
|
|
||||||
readyForUpload: computed(() => items.value.length > 0 && items.value.some(item => item.uploaded == null) && !items.value.some(item => item.uploading || item.preprocessing)),
|
|
||||||
allItemsUploaded: computed(() => items.value.every(item => item.uploaded != null)),
|
|
||||||
events,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:initialText="initialText"
|
:initialText="initialText"
|
||||||
:initialVisibility="visibility"
|
:initialVisibility="visibility"
|
||||||
:initialFiles="files"
|
:initialFiles="files"
|
||||||
|
:initialLocalFiles="tempFiles"
|
||||||
:initialLocalOnly="localOnly"
|
:initialLocalOnly="localOnly"
|
||||||
:reply="reply"
|
:reply="reply"
|
||||||
:renote="renote"
|
:renote="renote"
|
||||||
|
@ -33,6 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { get } from 'idb-keyval';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -41,7 +43,19 @@ import { definePage } from '@/page.js';
|
||||||
import { postMessageToParentWindow } from '@/utility/post-message.js';
|
import { postMessageToParentWindow } from '@/utility/post-message.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
//#region parameters
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
// merge hash parameters
|
||||||
|
try {
|
||||||
|
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||||
|
for (const [key, value] of hashParams.entries()) {
|
||||||
|
urlParams.set(key, value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse hash parameters:', e);
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
const localOnlyQuery = urlParams.get('localOnly');
|
const localOnlyQuery = urlParams.get('localOnly');
|
||||||
const visibilityQuery = urlParams.get('visibility') as typeof Misskey.noteVisibilities[number];
|
const visibilityQuery = urlParams.get('visibility') as typeof Misskey.noteVisibilities[number];
|
||||||
|
|
||||||
|
@ -56,6 +70,7 @@ const visibility = ref(Misskey.noteVisibilities.includes(visibilityQuery) ? visi
|
||||||
const localOnly = ref(localOnlyQuery === '0' ? false : localOnlyQuery === '1' ? true : undefined);
|
const localOnly = ref(localOnlyQuery === '0' ? false : localOnlyQuery === '1' ? true : undefined);
|
||||||
const files = ref([] as Misskey.entities.DriveFile[]);
|
const files = ref([] as Misskey.entities.DriveFile[]);
|
||||||
const visibleUsers = ref([] as Misskey.entities.UserDetailed[]);
|
const visibleUsers = ref([] as Misskey.entities.UserDetailed[]);
|
||||||
|
const tempFiles = ref([] as File[]);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
let noteText = '';
|
let noteText = '';
|
||||||
|
@ -182,6 +197,29 @@ async function init() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#region Local files
|
||||||
|
// If the browser supports IndexedDB, try to get the temporary files from temp.
|
||||||
|
if (typeof window !== 'undefined' ? !!(window.indexedDB && typeof window.indexedDB.open === 'function') : true) {
|
||||||
|
const filesFromIdb = await get<File[]>('share-files-temp');
|
||||||
|
if (Array.isArray(filesFromIdb) && filesFromIdb.length > 0 && filesFromIdb.every(file => file instanceof Blob)) {
|
||||||
|
tempFiles.value = filesFromIdb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlParams.has('file')) {
|
||||||
|
try {
|
||||||
|
const file = await window.fetch(urlParams.get('file')).then(res => res.blob());
|
||||||
|
if (file instanceof Blob) {
|
||||||
|
tempFiles.value.push(file as File);
|
||||||
|
} else {
|
||||||
|
console.error('Fetched file is not a Blob:', file);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch file:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
state.value = 'writing';
|
state.value = 'writing';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +243,26 @@ function onPosted(): void {
|
||||||
postMessageToParentWindow('misskey:shareForm:shareCompleted');
|
postMessageToParentWindow('misskey:shareForm:shareCompleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => [
|
||||||
|
{
|
||||||
|
icon: 'ti ti-dots',
|
||||||
|
text: i18n.ts.menu,
|
||||||
|
handler: (ev: MouseEvent) => {
|
||||||
|
os.popupMenu([
|
||||||
|
{
|
||||||
|
icon: 'ti ti-home',
|
||||||
|
text: i18n.ts.goToMisskey,
|
||||||
|
action: () => goToMisskey(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'ti ti-x',
|
||||||
|
text: i18n.ts.close,
|
||||||
|
action: () => close(),
|
||||||
|
},
|
||||||
|
], ev.currentTarget ?? ev.target);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ export interface PostFormProps {
|
||||||
initialCw?: string;
|
initialCw?: string;
|
||||||
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
|
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
|
||||||
initialFiles?: Misskey.entities.DriveFile[];
|
initialFiles?: Misskey.entities.DriveFile[];
|
||||||
|
initialLocalFiles?: File[];
|
||||||
initialLocalOnly?: boolean;
|
initialLocalOnly?: boolean;
|
||||||
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
||||||
initialNote?: Misskey.entities.Note;
|
initialNote?: Misskey.entities.Note;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { get } from 'idb-keyval';
|
import { get, set } from 'idb-keyval';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { PushNotificationDataMap } from '@/types.js';
|
import type { PushNotificationDataMap } from '@/types.js';
|
||||||
import type { I18n } from '@@/js/i18n.js';
|
import type { I18n } from '@@/js/i18n.js';
|
||||||
|
@ -39,7 +39,32 @@ async function offlineContentHTML() {
|
||||||
return `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1"name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#b4e900;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#c6ff03}</style></head><body><svg class="icon"fill="none"height="24"stroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"fill="none"stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(!0)}</script></body></html>`;
|
return `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1"name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#b4e900;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#c6ff03}</style></head><body><svg class="icon"fill="none"height="24"stroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"fill="none"stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(!0)}</script></body></html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.addEventListener('fetch', ev => {
|
globalThis.addEventListener('fetch', async ev => {
|
||||||
|
//#region /sw/share
|
||||||
|
const url = new URL(ev.request.url);
|
||||||
|
if (url.pathname === '/sw/share') {
|
||||||
|
ev.respondWith((async () => {
|
||||||
|
const responseUrl = new URL(ev.request.url);
|
||||||
|
responseUrl.pathname = '/share';
|
||||||
|
const formData = await ev.request.formData();
|
||||||
|
|
||||||
|
if (formData.has('files')) {
|
||||||
|
const files = formData.getAll('files');
|
||||||
|
if (files.length > 0 && files.every(file => file instanceof Blob)) {
|
||||||
|
set('share-files-temp', files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.delete('files');
|
||||||
|
for (const [key, value] of formData.entries()) {
|
||||||
|
responseUrl.searchParams.set(key, value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect(responseUrl, 303);
|
||||||
|
})());
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region others
|
||||||
let isHTMLRequest = false;
|
let isHTMLRequest = false;
|
||||||
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
||||||
isHTMLRequest = true;
|
isHTMLRequest = true;
|
||||||
|
@ -62,6 +87,7 @@ globalThis.addEventListener('fetch', ev => {
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
//#endregion
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('push', ev => {
|
globalThis.addEventListener('push', ev => {
|
||||||
|
|
Loading…
Reference in New Issue