mirror of
https://iceshrimp.dev/Crimekillz/jointrashposs.git
synced 2024-11-22 00:43:50 +01:00
feat(tools): 共有リンクジェネレータ
This commit is contained in:
parent
1d4f218b98
commit
8f185deff8
1
.nuxtignore
Normal file
1
.nuxtignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
pages/**/*-ignore.vue
|
@ -13,7 +13,12 @@ export default <NavSection[]>[
|
|||||||
i18n: "_aidConverter.title",
|
i18n: "_aidConverter.title",
|
||||||
description: "_aidConverter.description",
|
description: "_aidConverter.description",
|
||||||
to: "/tools/aid-converter/",
|
to: "/tools/aid-converter/",
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
}
|
i18n: "_shareLinkGenerator.title",
|
||||||
|
description: "_shareLinkGenerator.description",
|
||||||
|
to: "/tools/share-link-generator/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
@ -73,3 +73,39 @@ export const findDeepObject = (obj: NavItem, condition: (v: NavItem) => boolean)
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clipboardに値をコピー(TODO: 文字列以外も対応)
|
||||||
|
*/
|
||||||
|
export function copyText(val: string) {
|
||||||
|
if (!process.client) return;
|
||||||
|
|
||||||
|
// 空div 生成
|
||||||
|
const tmp = document.createElement('div');
|
||||||
|
// 選択用のタグ生成
|
||||||
|
const pre = document.createElement('pre');
|
||||||
|
|
||||||
|
// 親要素のCSSで user-select: none だとコピーできないので書き換える
|
||||||
|
pre.style.webkitUserSelect = 'auto';
|
||||||
|
pre.style.userSelect = 'auto';
|
||||||
|
|
||||||
|
tmp.appendChild(pre).textContent = val;
|
||||||
|
|
||||||
|
// 要素を画面外へ
|
||||||
|
const s = tmp.style;
|
||||||
|
s.position = 'fixed';
|
||||||
|
s.right = '200%';
|
||||||
|
|
||||||
|
// body に追加
|
||||||
|
document.body.appendChild(tmp);
|
||||||
|
// 要素を選択
|
||||||
|
document.getSelection()?.selectAllChildren(tmp);
|
||||||
|
|
||||||
|
// クリップボードにコピー
|
||||||
|
const result = document.execCommand('copy');
|
||||||
|
|
||||||
|
// 要素削除
|
||||||
|
document.body.removeChild(tmp);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
30
components/g/BsCopyButton.vue
Normal file
30
components/g/BsCopyButton.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<button class="btn btn-outline-primary hover:!text-white focus-visible:!text-white" @click="copy">
|
||||||
|
<CopyIco class="w-4 h-4" v-if="!copied" />
|
||||||
|
<CheckIco class="w-4 h-4" v-else />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { copyText } from '@/assets/js/misc';
|
||||||
|
import CopyIco from 'bi/clipboard.svg';
|
||||||
|
import CheckIco from 'bi/check-lg.svg';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
text: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const copied = ref(false);
|
||||||
|
|
||||||
|
function copy() {
|
||||||
|
copyText(props.text);
|
||||||
|
copied.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
copied.value = false;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -53,6 +53,7 @@ const route = useRoute();
|
|||||||
<div :class="$style.slimPageRoot">
|
<div :class="$style.slimPageRoot">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
<GFooter class="mt-12 rounded-tl-3xl lg:rounded-tl-none bg-white dark:bg-slate-950" />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ noScript: "現在Javascriptが無効になっています。サイトの表示
|
|||||||
learnMore: "詳しく知る"
|
learnMore: "詳しく知る"
|
||||||
loading: "読み込み中…"
|
loading: "読み込み中…"
|
||||||
clickToExpand: "(クリックして展開)"
|
clickToExpand: "(クリックして展開)"
|
||||||
|
copy: "コピー"
|
||||||
|
|
||||||
_seo:
|
_seo:
|
||||||
siteName: "Misskey Hub"
|
siteName: "Misskey Hub"
|
||||||
@ -238,6 +239,29 @@ _share:
|
|||||||
domain: "サーバーのドメイン"
|
domain: "サーバーのドメイン"
|
||||||
compatibleWith: "Misskeyと、一部のMisskeyフォークに対応しています。"
|
compatibleWith: "Misskeyと、一部のMisskeyフォークに対応しています。"
|
||||||
|
|
||||||
|
_noteVisibility:
|
||||||
|
public: "パブリック"
|
||||||
|
home: "ホーム"
|
||||||
|
followers: "フォロワー"
|
||||||
|
specified: "ダイレクト"
|
||||||
|
localOnly: "連合なし"
|
||||||
|
|
||||||
|
_shareLinkGenerator:
|
||||||
|
title: "共有ボタンジェネレーター"
|
||||||
|
description: "Misskey Hubの共有ボタン中継サービスを利用して、Misskey用の共有ボタンを作成できます。"
|
||||||
|
body: "本文"
|
||||||
|
bodyWarning: "どのサーバーでも共有できるようにするため、カスタム絵文字は使用できません。"
|
||||||
|
url: "URL"
|
||||||
|
urlCaption: "任意。本文の後ろに挿入されます。"
|
||||||
|
settings: "詳細設定"
|
||||||
|
visibility: "公開範囲"
|
||||||
|
recipents: "ダイレクトを受け取る人のacct(改行区切り)"
|
||||||
|
resultLink: "リンク生成結果"
|
||||||
|
resultButton: "共有ボタンのサンプル"
|
||||||
|
testLink: "共有リンクを試す"
|
||||||
|
typeSomethingToGetLink: "本文を入力するとリンクが生成されます。"
|
||||||
|
typeSomethingToGetButton: "本文を入力するとボタンが生成されます。"
|
||||||
|
|
||||||
_api:
|
_api:
|
||||||
_permissions:
|
_permissions:
|
||||||
title: "権限"
|
title: "権限"
|
||||||
|
161
pages/tools/share-link-generator.vue
Normal file
161
pages/tools/share-link-generator.vue
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<div class='container mx-auto max-w-screen-xl px-6 py-6'>
|
||||||
|
<h1 class='text-2xl lg:text-3xl font-bold mb-4'>
|
||||||
|
{{ $t('_shareLinkGenerator.title') }}
|
||||||
|
</h1>
|
||||||
|
<div class='rounded-lg grid md:grid-cols-2 gap-8'>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="p-6 space-y-4 rounded-lg bg-white dark:bg-slate-950">
|
||||||
|
<header class="-mt-6 -mx-6 px-6 py-3 font-bold text-lg border-b">
|
||||||
|
{{ $t('_shareLinkGenerator.body') }}
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<div class="flex">
|
||||||
|
<label for="shareLinkGeneratorBody">{{ $t('_shareLinkGenerator.body') }}</label>
|
||||||
|
<div
|
||||||
|
class="ml-auto"
|
||||||
|
:class="[(mfmText?.length ?? 0) >= 5000 && 'font-bold text-red-600']"
|
||||||
|
>
|
||||||
|
{{ $t('_mfmPlayground.character', [ (mfmText?.length ?? 0) ]) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
:rows="(mfmText || '').split('\n').length >= 8 ? (mfmText || '').split('\n').length + 5 : 10"
|
||||||
|
class="form-control"
|
||||||
|
id="shareLinkGeneratorBody"
|
||||||
|
v-model="mfmText"
|
||||||
|
></textarea>
|
||||||
|
<div class="form-text">{{ $t('_shareLinkGenerator.bodyWarning') }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="shareLinkGeneratorUrl">{{ $t('_shareLinkGenerator.url') }}</label>
|
||||||
|
<input class="form-control" id="shareLinkGeneratorUrl" v-model="mfmUrl" />
|
||||||
|
<div class="form-text">{{ $t('_shareLinkGenerator.urlCaption') }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('_mfmPlayground.preview') }}
|
||||||
|
<div :class="$style.mfmRoot" class="mb-2 bg-white dark:bg-[#212529] border-gray-200 dark:border-gray-600">
|
||||||
|
<MkMfm :text="mfmPreviewString" />
|
||||||
|
</div>
|
||||||
|
<div class="form-text">{{ $t('_mfmPlayground.disclaimer') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 space-y-4 rounded-lg bg-white dark:bg-slate-950">
|
||||||
|
<header class="-mt-6 -mx-6 px-6 py-3 font-bold text-lg border-b">
|
||||||
|
{{ $t('_shareLinkGenerator.settings') }}
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<label for="shareLinkGeneratorVisibility">{{ $t('_shareLinkGenerator.visibility') }}</label>
|
||||||
|
<select class="form-select" id="shareLinkGeneratorVisibility" v-model="visibility">
|
||||||
|
<option v-for="visibility in noteVisibilities" :key="visibility" :value="visibility">{{ $t(`_noteVisibility.${visibility}`) }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div v-if="visibility === 'specified'">
|
||||||
|
<label for="shareLinkGeneratorRecipents">{{ $t('_shareLinkGenerator.recipents') }}</label>
|
||||||
|
<textarea
|
||||||
|
class="form-control"
|
||||||
|
id="shareLinkGeneratorRecipents"
|
||||||
|
placeholder="@someone@misskey.example.com"
|
||||||
|
v-model="recipents"
|
||||||
|
:rows="(recipents || '').split('\n').length >= 5 ? (recipents || '').split('\n').length + 2 : 7"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" v-model="localOnly" id="shareLinkGeneratorLocalOnly">
|
||||||
|
<label class="form-check-label" for="shareLinkGeneratorLocalOnly">
|
||||||
|
{{ $t('_noteVisibility.localOnly') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="p-6 space-y-4 rounded-lg bg-white dark:bg-slate-950">
|
||||||
|
<header class="-mt-6 -mx-6 px-6 py-3 font-bold text-lg border-b">
|
||||||
|
{{ $t('_shareLinkGenerator.resultLink') }}
|
||||||
|
</header>
|
||||||
|
<div v-if="(mfmText?.length ?? 0) === 0" class="p-8 text-center font-bold text-lg">
|
||||||
|
{{ $t('_shareLinkGenerator.typeSomethingToGetLink') }}
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="input-group">
|
||||||
|
<input readonly class="form-control" :value="generatedUrl" />
|
||||||
|
<GBsCopyButton :text="generatedUrl" />
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
<GNuxtLink :to="generatedUrl" target="_blank" class="hover:underline underline-offset-1">{{ $t('_shareLinkGenerator.testLink') }}<ExtIco class="ml-1" /></GNuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts'>
|
||||||
|
import { noteVisibilities } from 'misskey-js';
|
||||||
|
import { joinURL, withQuery } from 'ufo';
|
||||||
|
import ExtIco from 'bi/box-arrow-up-right.svg';
|
||||||
|
import type { entities as MisskeyEntities } from 'misskey-js';
|
||||||
|
import type { QueryObject } from 'ufo';
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'tools',
|
||||||
|
});
|
||||||
|
|
||||||
|
const mfmText = ref<string>('');
|
||||||
|
const mfmUrl = ref<string>('');
|
||||||
|
const visibility = ref<MisskeyEntities.Note['visibility']>('public');
|
||||||
|
const localOnly = ref<boolean>(false);
|
||||||
|
const recipents = ref<string>('');
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
const mfmPreviewString = computed(() => {
|
||||||
|
if (mfmText.value != '') {
|
||||||
|
if (mfmUrl.value != '') {
|
||||||
|
return mfmText.value + '\n' + mfmUrl.value;
|
||||||
|
} else {
|
||||||
|
return mfmText.value;
|
||||||
|
}
|
||||||
|
} else if (mfmUrl.value != '') {
|
||||||
|
return mfmUrl.value;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const generatedUrl = computed(() => {
|
||||||
|
const query: QueryObject = {
|
||||||
|
text: mfmText.value,
|
||||||
|
url: (mfmUrl.value != '') ? mfmUrl.value : undefined,
|
||||||
|
visibility: visibility.value,
|
||||||
|
localOnly: localOnly.value ? '1' : '0',
|
||||||
|
visibleAccts: (visibility.value === 'specified') ? recipents.value.split('\n').join(',') : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return withQuery(joinURL(runtimeConfig.public.baseUrl, '/share/'), query);
|
||||||
|
});
|
||||||
|
|
||||||
|
const generatedHtml = computed(() => '');
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
route.meta.title = t('_shareLinkGenerator.title');
|
||||||
|
route.meta.description = t('_shareLinkGenerator.description');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.mfmRoot {
|
||||||
|
@apply rounded-lg p-4 border break-words overflow-hidden;
|
||||||
|
font-family: Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shareLink {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.shareLinkIcon {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
33
pages/tools/tools-template-ignore.vue
Normal file
33
pages/tools/tools-template-ignore.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
ツール集のテンプレート
|
||||||
|
|
||||||
|
翻訳キーの `aaaaaaaa` となっている場所を置き換えればあとは普通のVueとほぼ同じ
|
||||||
|
コンポーネントは、命名規則に従っている限り自動インポート
|
||||||
|
ref/reactiveなどのvue関連も自動インポート
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class='container mx-auto max-w-screen-xl px-6 py-6'>
|
||||||
|
<h1 class='text-2xl lg:text-3xl font-bold mb-4'>
|
||||||
|
{{ $t('_aaaaaaaa.title') }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- ここに書く! -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts'>
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'tools',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
route.meta.title = t('_aaaaaaaa.title');
|
||||||
|
route.meta.description = t('_aaaaaaaa.description');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user