This commit is contained in:
tamaina 2025-09-25 20:33:22 +09:00 committed by GitHub
commit 7ea86f1a45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 114 additions and 9 deletions

View File

@ -158,13 +158,19 @@ export class ClientServerService {
'purpose': 'any', 'purpose': 'any',
}], }],
'share_target': { 'share_target': {
'action': '/share/', 'action': '/sw/share',
'method': 'GET', 'method': 'POST',
'enctype': 'application/x-www-form-urlencoded', 'enctype': 'multipart/form-data',
'params': { 'params': {
'title': 'title', 'title': 'title',
'text': 'text', 'text': 'text',
'url': 'url', 'url': 'url',
'files': [
{
'name': 'files',
'accept': '*/*',
},
],
}, },
}, },
'shortcuts': [{ 'shortcuts': [{

View File

@ -26,13 +26,19 @@
} }
], ],
"share_target": { "share_target": {
"action": "/share/", "action": "/sw/share",
"method": "GET", "method": "POST",
"enctype": "application/x-www-form-urlencoded", "enctype": "multipart/form-data",
"params": { "params": {
"title": "title", "title": "title",
"text": "text", "text": "text",
"url": "url" "url": "url",
"files": [
{
"name": "files",
"accept": "*/*"
}
]
} }
}, },
"shortcuts": [ "shortcuts": [

View File

@ -227,6 +227,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}` : '';

View File

@ -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, del } 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 = '';
@ -181,6 +196,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') && urlParams.get('file').startsWith('data:')) {
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';
} }
@ -201,10 +239,31 @@ function goToMisskey(): void {
function onPosted(): void { function onPosted(): void {
state.value = 'posted'; state.value = 'posted';
// SW稿IndexedDB
del('share-files-temp');
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(() => []);

View File

@ -22,6 +22,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;

View File

@ -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';
@ -40,6 +40,34 @@ async function offlineContentHTML() {
} }
globalThis.addEventListener('fetch', ev => { globalThis.addEventListener('fetch', 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();
// とりあえず初期化 (IndexedDBの削除は時間がかかる可能性があるため空の配列をセット)
await set('share-url-temp', []);
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);
})());
return;
}
//#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 +90,7 @@ globalThis.addEventListener('fetch', ev => {
}); });
}), }),
); );
//#endregion
}); });
globalThis.addEventListener('push', ev => { globalThis.addEventListener('push', ev => {