Bump base to 12.116.1

This commit is contained in:
ltlapy 2022-07-17 12:15:41 +09:00
commit ce618c294f
45 changed files with 625 additions and 198 deletions

View file

@ -9,6 +9,28 @@
You should also include the user name that made the change.
-->
## 12.116.1 (2022/07/17)
### Bugfixes
- Client: デッキUI時に ページで表示 ボタンが機能しない問題を修正 @syuilo
- Error During Migration Run to 12.111.x
## 12.116.0 (2022/07/16)
### Improvements
- Client: registry editor @syuilo
- Client: UIのブラッシュアップ @syuilo
### Bugfixes
- Error During Migration Run to 12.111.x
- Server: TypeError: Cannot convert undefined or null to object @syuilo
## 12.115.0 (2022/07/16)
### Improvements
- Client: Deckのプロファイル切り替えを簡単に @syuilo
- Client: UIのブラッシュアップ @syuilo
## 12.114.0 (2022/07/15)
### Improvements

View file

@ -889,7 +889,7 @@ enableAutoSensitiveDescription: "Setzt soweit möglich durch Verwendung von Mach
activeEmailValidationDescription: "Aktivert strengere Überprüfung von E-Mail-Adressen, d.h. Testen auf Wegwerfadressen und darauf, ob mit der Adresse tatsächlich kommuniziert werden kann. Ist dies deaktiviert, so wird nur das Format der E-Mail überprüft."
navbar: "Navigationsleiste"
shuffle: "Mischen"
account: "Benutzerkonten"
account: "Benutzerkonto"
_sensitiveMediaDetection:
description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
sensitivity: "Erkennungssensitivität"
@ -1703,6 +1703,8 @@ _deck:
stackLeft: "Auf linke Spalte stapeln"
popRight: "Nach rechts vom Stapel nehmen"
profile: "Profil"
newProfile: "Neues Profil"
deleteProfile: "Profil löschen"
introduction: "Erstelle eine auf dich zugeschneiderte Benutzeroberfläche durch das Aneinanderreihen von Spalten!"
introduction2: "Klicke auf das + rechts um wann immer du möchtest neue Spalten hinzuzufügen."
widgetsIntroduction: "Drücke bitte \"Widgets bearbeiten\" im Spaltenmenü und füge ein Widget hinzu."

View file

@ -892,7 +892,7 @@ enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW
activeEmailValidationDescription: "Enables stricter validation of email addresses, which includes checking for disposable addresses and by whether it can actually be communicated with. When unchecked, only the format of the email is validated."
navbar: "Navigation bar"
shuffle: "Shuffle"
account: "Accounts"
account: "Account"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
sensitivity: "Detection sensitivity"
@ -1706,6 +1706,8 @@ _deck:
stackLeft: "Stack with the left column"
popRight: "Pop column to the right"
profile: "Profile"
newProfile: "New profile"
deleteProfile: "Delete profile"
introduction: "Create the perfect interface for you by arranging columns freely!"
introduction2: "Click on the + on the right of the screen to add new colums whenever you want."
widgetsIntroduction: "Please select \"Edit widgets\" in the column menu and add a widget."

View file

@ -1758,6 +1758,7 @@ _deck:
alwaysShowMainColumn: "常にメインカラムを表示"
columnAlign: "カラムの寄せ"
addColumn: "カラムを追加"
configureColumn: "カラムの設定"
swapLeft: "左に移動"
swapRight: "右に移動"
swapUp: "上に移動"
@ -1765,6 +1766,8 @@ _deck:
stackLeft: "左に重ねる"
popRight: "右に出す"
profile: "プロファイル"
newProfile: "新規プロファイル"
deleteProfile: "プロファイルを削除"
introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!"
introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。"
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください"

View file

@ -842,6 +842,9 @@ reverse: "Переворот"
colored: "Выделена цветом"
label: "Метка"
localOnly: "Локально"
beta: "Бета"
enableAutoSensitive: "Автоматическое определение NSFW"
enableAutoSensitiveDescription: "Если доступно, используйте машинное обучение для автоматической установки флага NSFW на носителе. Даже если эта функция отключена, она может быть установлена ​​автоматически в зависимости от инстанта."
account: "Учётные записи"
_sensitiveMediaDetection:
description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."

View file

@ -1698,6 +1698,8 @@ _deck:
stackLeft: "Priložiť do ľavého stĺpca"
popRight: "Vybrať napravo"
profile: "Profil"
newProfile: "Nový profil"
deleteProfile: "Odstrániť profil"
introduction: "Kombinujte stĺpce a vytvorte si svoje vlastné rozhranie!"
introduction2: "Stlačením tlačidla + v pravej časti obrazovky môžete kedykoľvek pridať stĺpce."
widgetsIntroduction: "V ponuke stĺpca vyberte možnosť \"Upraviť widget\" a pridajte widget"

View file

@ -820,6 +820,20 @@ ffVisibilityDescription: "ช่วยให้คุณสามารถกำ
continueThread: "ดูความต่อเนื่องเธรด"
deleteAccountConfirm: "การดำเนินการนี้จะลบบัญชีของคุณอย่างถาวรเลยนะ แน่ใจหรอดำเนินการ?"
incorrectPassword: "รหัสผ่านไม่ถูกต้อง"
voteConfirm: "ยืนยันการโหวต \"{choice}\" มั้ย?"
hide: "ซ่อน"
leaveGroup: "ออกจากกลุ่ม"
leaveGroupConfirm: "คุณแน่ใจหรอว่าต้องการออกจาก \"{name}\""
useDrawerReactionPickerForMobile: "แสดงผล ตัวเลือกปฏิกิริยาเป็นลิ้นชักบนมือถือ"
welcomeBackWithName: "ยินดีต้อนรับการกลับมานะค่ะ, {name}"
clickToFinishEmailVerification: "กรุณาคลิก [{ok}] เพื่อดำเนินการยืนยันอีเมลให้เสร็จสมบูรณ์นะ"
overridedDeviceKind: "ประเภทอุปกรณ์"
smartphone: "สมาร์ทโฟน"
tablet: "แท็บเล็ต"
auto: "อัตโนมัติ"
themeColor: "อินสแตนซ์ Ticker Color"
size: "ขนาด"
numberOfColumn: "จำนวนคอลัมน์"
searchByGoogle: "ค้นหา"
file: "ไฟล์"
account: "บัญชีผู้ใช้"

View file

@ -1703,6 +1703,8 @@ _deck:
stackLeft: "Xếp chồng với cột bên trái"
popRight: "Xếp chồng với cột bên trái"
profile: "Hồ sơ"
newProfile: "Hồ sơ mới"
deleteProfile: "Xóa hồ sơ"
introduction: "Kết hợp các cột để tạo giao diện của riêng bạn!"
introduction2: "Bạn có thể thêm cột bất kỳ lúc nào bằng cách nhấn + ở bên phải màn hình."
widgetsIntroduction: "Chọn \"Sửa widget\" trong menu cột và thêm một widget."

View file

@ -203,6 +203,7 @@ done: "完成"
processing: "正在处理"
preview: "预览"
default: "默认"
defaultValueIs: "默认值: {value}"
noCustomEmojis: "没有自定义表情符号"
noJobs: "没有任务"
federating: "联合中"
@ -336,7 +337,7 @@ pinnedUsers: "置顶用户"
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
pinnedPages: "固定页面"
pinnedPagesDescription: "输入您要固定到实例首页的页面路径,以换行符分隔。"
pinnedClipId: "置顶的签ID"
pinnedClipId: "置顶的便签ID"
pinnedNotes: "已置顶的帖子"
hcaptcha: "hCaptcha"
enableHcaptcha: "启用 hCaptcha"
@ -640,10 +641,12 @@ random: "随机"
system: "系统"
switchUi: "切换界面"
desktop: "桌面"
clip: "签"
clip: "便签"
createNew: "新建"
optional: "可选"
createNewClip: "新建书签"
createNewClip: "新建便签"
unclip: "移除便签"
confirmToUnclipAlreadyClippedNote: "本帖已包含在便签\"{name}\"里。您想要将本帖从该便签中移除吗?"
public: "公开"
i18nInfo: "Misskey已经被志愿者们翻译成了各种语言。如果你也有兴趣可以通过{link}帮助翻译。"
manageAccessTokens: "管理 Access Tokens"
@ -677,7 +680,7 @@ pageLikesCount: "页面点赞次数"
pageLikedCount: "页面被点赞次数"
contact: "联系人"
useSystemFont: "使用系统默认字体"
clips: "签"
clips: "便签"
experimentalFeatures: "实验性功能"
developer: "开发者"
makeExplorable: "使账号可见。"
@ -860,6 +863,7 @@ requireAdminForView: "需要使用管理员账户登录才能查看。"
isSystemAccount: "该账号由系统自动创建和管理。"
typeToConfirm: "输入 {x} 以确认操作。"
deleteAccount: "删除账户"
document: "文档"
numberOfPageCache: "缓存页数"
numberOfPageCacheDescription: "设置较高的值会更方便用户,但服务器负载和内存使用量会增加。"
logoutConfirm: "是否确认登出?"
@ -870,6 +874,7 @@ reverse: "翻转"
colored: "彩色"
refreshInterval: "刷新间隔"
label: "标签"
type: "类型"
speed: "速度"
slow: "慢"
fast: "快"
@ -881,6 +886,8 @@ cannotUploadBecauseInappropriate: "因为可能含有不适宜的内容,无法
cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。"
beta: "测试"
enableAutoSensitive: "自动 NSFW 识别"
navbar: "导航栏"
shuffle: "随机"
account: "账户"
_sensitiveMediaDetection:
description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"

View file

@ -1702,6 +1702,8 @@ _deck:
stackLeft: "向左折疊"
popRight: "向右彈出"
profile: "個人檔案"
newProfile: "新建個人檔案"
deleteProfile: "刪除個人檔案"
introduction: "組合欄位來製作屬於自己的介面吧!"
introduction2: "您可以隨時透過按畫面右方的 + 來添加欄位。"
widgetsIntroduction: "請從欄位的選單中,選擇「編輯小工具」來添加小工具"

View file

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "12.114.0+klapy",
"version": "12.116.1+klapy",
"codename": "indigo",
"repository": {
"type": "git",

View file

@ -28,11 +28,11 @@ export class foreignKeyReports1651224615271 {
queryRunner.query(`CREATE INDEX "IDX_315c779174fe8247ab324f036e" ON "drive_file" ("isLink")`),
queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId")`),
queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`),
//queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`),
queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => {
queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}),
//queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => {
// queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
//}),
queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`),
queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`),

View file

@ -16,6 +16,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
this.onNote = this.onNote.bind(this);
this.emitTypers = this.emitTypers.bind(this);
}
public async init(params: any) {

View file

@ -77,9 +77,9 @@ const inputEl = ref<HTMLElement>();
const prefixEl = ref<HTMLElement>();
const suffixEl = ref<HTMLElement>();
const height =
props.small ? 38 :
props.large ? 42 :
40;
props.small ? 36 :
props.large ? 40 :
38;
const focus = () => inputEl.value.focus();
const onInput = (ev: KeyboardEvent) => {

View file

@ -63,9 +63,9 @@ const prefixEl = ref(null);
const suffixEl = ref(null);
const container = ref(null);
const height =
props.small ? 38 :
props.large ? 42 :
40;
props.small ? 36 :
props.large ? 40 :
38;
const focus = () => inputEl.value.focus();
const onInput = (ev) => {

View file

@ -3,7 +3,7 @@
<div v-if="!showMenu" class="main" :class="ad.place">
<a :href="ad.url" target="_blank">
<img :src="ad.imageUrl">
<button class="_button menu" @click.prevent.stop="toggleMenu"><span class="fas fa-info-circle"></span></button>
<button class="_button menu" @click.prevent.stop="toggleMenu"><span class="fas fa-info-circle info-circle"></span></button>
</a>
</div>
<div v-else class="menu">
@ -135,13 +135,19 @@ export default defineComponent({
display: block;
object-fit: contain;
margin: auto;
border-radius: 5px;
}
> .menu {
position: absolute;
top: 0;
right: 0;
background: var(--panel);
top: 1px;
right: 1px;
> .info-circle {
border: 3px solid var(--panel);
border-radius: 50%;
background: var(--panel);
}
}
}

View file

@ -1,68 +1,41 @@
char2filePath<template>
<template>
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt" decoding="async"/>
<span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
<span v-else>{{ emoji }}</span>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue';
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { CustomEmoji } from 'misskey-js/built/entities';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { char2filePath } from '@/scripts/twemoji-base';
import { defaultStore } from '@/store';
import { instance } from '@/instance';
export default defineComponent({
props: {
emoji: {
type: String,
required: true
},
normal: {
type: Boolean,
required: false,
default: false
},
noStyle: {
type: Boolean,
required: false,
default: false
},
customEmojis: {
required: false
},
isReaction: {
type: Boolean,
default: false
},
},
const props = defineProps<{
emoji: string;
normal?: boolean;
noStyle?: boolean;
customEmojis?: CustomEmoji[];
isReaction?: boolean;
}>();
setup(props) {
const isCustom = computed(() => props.emoji.startsWith(':'));
const char = computed(() => isCustom.value ? null : props.emoji);
const useOsNativeEmojis = computed(() => defaultStore.state.useOsNativeEmojis && !props.isReaction);
const ce = computed(() => props.customEmojis || instance.emojis || []);
const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : null);
const url = computed(() => {
if (char.value) {
return char2filePath(char.value);
} else {
return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(customEmoji.value.url)
: customEmoji.value.url;
}
});
const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value);
return {
url,
char,
alt,
customEmoji,
useOsNativeEmojis,
};
},
const isCustom = computed(() => props.emoji.startsWith(':'));
const char = computed(() => isCustom.value ? null : props.emoji);
const useOsNativeEmojis = computed(() => defaultStore.state.useOsNativeEmojis && !props.isReaction);
const ce = computed(() => props.customEmojis ?? instance.emojis ?? []);
const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : null);
const url = computed(() => {
if (char.value) {
return char2filePath(char.value);
} else {
return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(customEmoji.value.url)
: customEmoji.value.url;
}
});
const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value);
</script>
<style lang="scss" scoped>

View file

@ -18,7 +18,7 @@
</div>
</div>
<div v-if="!narrow || hideTitle" class="tabs">
<button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = el" v-tooltip="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)">
<button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = el" v-tooltip.noDelay="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)">
<i v-if="tab.icon" class="icon" :class="tab.icon"></i>
<span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span>
</button>
@ -27,7 +27,7 @@
</template>
<div class="buttons right">
<template v-for="action in actions">
<button v-tooltip="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
<button v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
</div>
</div>

View file

@ -390,7 +390,7 @@ if (appearNote.replyId) {
> .article {
padding: 32px;
font-size: 1.1em;
font-size: 1.2em;
> .header {
display: flex;

View file

@ -554,6 +554,13 @@ function readPromo() {
&.max-width_500px {
font-size: 0.9em;
> .article {
> .avatar {
width: 50px;
height: 50px;
}
}
}
&.max-width_450px {
@ -570,8 +577,8 @@ function readPromo() {
> .avatar {
margin: 0 10px 8px 0;
width: 50px;
height: 50px;
width: 46px;
height: 46px;
top: calc(14px + var(--stickyTop, 0px));
}
}

View file

@ -122,7 +122,7 @@ function close() {
}
function expand() {
mainRouter.push(router.getCurrentPath());
mainRouter.push(router.getCurrentPath(), 'forcePage');
windowEl.close();
}

View file

@ -1,5 +1,6 @@
<template>
<button v-if="!link" class="bghgjjyj _button"
<button
v-if="!link" class="bghgjjyj _button"
:class="{ inline, primary, gradate, danger, rounded, full }"
:type="type"
@click="$emit('click', $event)"
@ -10,7 +11,8 @@
<slot></slot>
</div>
</button>
<MkA v-else class="bghgjjyj _button"
<MkA
v-else class="bghgjjyj _button"
:class="{ inline, primary, gradate, danger, rounded, full }"
:to="to"
@mousedown="onMousedown"
@ -29,56 +31,56 @@ export default defineComponent({
props: {
type: {
type: String,
required: false
required: false,
},
primary: {
type: Boolean,
required: false,
default: false
default: false,
},
gradate: {
type: Boolean,
required: false,
default: false
default: false,
},
rounded: {
type: Boolean,
required: false,
default: false
default: false,
},
inline: {
type: Boolean,
required: false,
default: false
default: false,
},
link: {
type: Boolean,
required: false,
default: false
default: false,
},
to: {
type: String,
required: false
required: false,
},
autofocus: {
type: Boolean,
required: false,
default: false
default: false,
},
wait: {
type: Boolean,
required: false,
default: false
default: false,
},
danger: {
type: Boolean,
required: false,
default: false
default: false,
},
full: {
type: Boolean,
required: false,
default: false
default: false,
},
},
emits: ['click'],
@ -127,8 +129,8 @@ export default defineComponent({
window.setTimeout(() => {
if (this.$refs.ripples) this.$refs.ripples.removeChild(ripple);
}, 2000);
}
}
},
},
});
</script>
@ -142,8 +144,7 @@ export default defineComponent({
padding: 8px 14px;
text-align: center;
font-weight: normal;
font-size: 0.9em;
line-height: 22px;
font-size: 1em;
box-shadow: none;
text-decoration: none;
background: var(--buttonBg);

View file

@ -7,10 +7,11 @@ import { popup, alert } from '@/os';
const start = isTouchUsing ? 'touchstart' : 'mouseover';
const end = isTouchUsing ? 'touchend' : 'mouseleave';
const delay = 100;
export default {
mounted(el: HTMLElement, binding, vn) {
const delay = binding.modifiers.noDelay ? 0 : 100;
const self = (el as any)._tooltipDirective_ = {} as any;
self.text = binding.value as string;

View file

@ -71,7 +71,7 @@ export class Router extends EventEmitter<{
private currentKey = Date.now().toString();
public currentRoute: ShallowRef<RouteDef | null> = shallowRef(null);
public navHook: ((path: string) => boolean) | null = null;
public navHook: ((path: string, flag?: any) => boolean) | null = null;
constructor(routes: Router['routes'], currentPath: Router['currentPath']) {
super();
@ -213,14 +213,14 @@ export class Router extends EventEmitter<{
return this.currentKey;
}
public push(path: string) {
public push(path: string, flag?: any) {
const beforePath = this.currentPath;
if (path === beforePath) {
this.emit('same');
return;
}
if (this.navHook) {
const cancel = this.navHook(path);
const cancel = this.navHook(path, flag);
if (cancel) return;
}
this.navigate(path, null);

View file

@ -9,7 +9,7 @@
</div>
</div>
<div class="tabs">
<button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = el" v-tooltip="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)">
<button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = el" v-tooltip.noDelay="tab.title" class="tab _button" :class="{ active: tab.key != null && tab.key === props.tab }" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)">
<i v-if="tab.icon" class="icon" :class="tab.icon"></i>
<span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span>
</button>
@ -20,7 +20,7 @@
<template v-if="actions">
<template v-for="action in actions">
<MkButton v-if="action.asFullButton" class="fullButton" primary @click.stop="action.handler"><i :class="action.icon" style="margin-right: 6px;"></i>{{ action.text }}</MkButton>
<button v-else v-tooltip="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
<button v-else v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
</template>
</div>

View file

@ -0,0 +1,96 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="600" :margin-min="16">
<FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts._registry.domain }}</template>
<template #value>{{ $ts.system }}</template>
</MkKeyValue>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts._registry.scope }}</template>
<template #value>{{ scope.join('/') }}</template>
</MkKeyValue>
</FormSplit>
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
<FormSection v-if="keys">
<template #label>{{ i18n.ts.keys }}</template>
<div class="_formLinks">
<FormLink v-for="key in keys" :to="`/registry/value/system/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
</div>
</FormSection>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import JSON5 from 'json5';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
import MkKeyValue from '@/components/key-value.vue';
import FormSplit from '@/components/form/split.vue';
const props = defineProps<{
path: string;
}>();
const scope = $computed(() => props.path.split('/'));
let keys = $ref(null);
function fetchKeys() {
os.api('i/registry/keys-with-type', {
scope: scope,
}).then(res => {
keys = Object.entries(res).sort((a, b) => a[0].localeCompare(b[0]));
});
}
async function createKey() {
const { canceled, result } = await os.form(i18n.ts._registry.createKey, {
key: {
type: 'string',
label: i18n.ts._registry.key,
},
value: {
type: 'string',
multiline: true,
label: i18n.ts.value,
},
scope: {
type: 'string',
label: i18n.ts._registry.scope,
default: scope.join('/'),
},
});
if (canceled) return;
os.apiWithDialog('i/registry/set', {
scope: result.scope.split('/'),
key: result.key,
value: JSON5.parse(result.value),
}).then(() => {
fetchKeys();
});
}
watch(() => props.path, fetchKeys, { immediate: true });
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.registry,
icon: 'fas fa-cogs',
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -0,0 +1,123 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="600" :margin-min="16">
<FormInfo warn>{{ $ts.editTheseSettingsMayBreakAccount }}</FormInfo>
<template v-if="value">
<FormSplit>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts._registry.domain }}</template>
<template #value>{{ $ts.system }}</template>
</MkKeyValue>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts._registry.scope }}</template>
<template #value>{{ scope.join('/') }}</template>
</MkKeyValue>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts._registry.key }}</template>
<template #value>{{ key }}</template>
</MkKeyValue>
</FormSplit>
<FormTextarea v-model="valueForEditor" tall class="_formBlock _monospace">
<template #label>{{ $ts.value }} (JSON)</template>
</FormTextarea>
<MkButton class="_formBlock" primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
<MkKeyValue class="_formBlock">
<template #key>{{ $ts.updatedAt }}</template>
<template #value><MkTime :time="value.updatedAt" mode="detail"/></template>
</MkKeyValue>
<MkButton danger @click="del"><i class="fas fa-trash"></i> {{ $ts.delete }}</MkButton>
</template>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import JSON5 from 'json5';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
import MkKeyValue from '@/components/key-value.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormSplit from '@/components/form/split.vue';
import FormInfo from '@/components/ui/info.vue';
const props = defineProps<{
path: string;
}>();
const scope = $computed(() => props.path.split('/').slice(0, -1));
const key = $computed(() => props.path.split('/').at(-1));
let value = $ref(null);
let valueForEditor = $ref(null);
function fetchValue() {
os.api('i/registry/get-detail', {
scope,
key,
}).then(res => {
value = res;
valueForEditor = JSON5.stringify(res.value, null, '\t');
});
}
async function save() {
try {
JSON5.parse(valueForEditor);
} catch (e) {
os.alert({
type: 'error',
text: i18n.ts.invalidValue,
});
return;
}
os.confirm({
type: 'warning',
text: i18n.ts.saveConfirm,
}).then(({ canceled }) => {
if (canceled) return;
os.apiWithDialog('i/registry/set', {
scope,
key,
value: JSON5.parse(valueForEditor),
});
});
}
function del() {
os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled) return;
os.apiWithDialog('i/registry/remove', {
scope,
key,
});
});
}
watch(() => props.path, fetchValue, { immediate: true });
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.registry,
icon: 'fas fa-cogs',
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -0,0 +1,74 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="600" :margin-min="16">
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
<FormSection v-if="scopes">
<template #label>{{ i18n.ts.system }}</template>
<div class="_formLinks">
<FormLink v-for="scope in scopes" :to="`/registry/keys/system/${scope.join('/')}`" class="_monospace">{{ scope.join('/') }}</FormLink>
</div>
</FormSection>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import JSON5 from 'json5';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
let scopes = $ref(null);
function fetchScopes() {
os.api('i/registry/scopes').then(res => {
scopes = res.slice().sort((a, b) => a.join('/').localeCompare(b.join('/')));
});
}
async function createKey() {
const { canceled, result } = await os.form(i18n.ts._registry.createKey, {
key: {
type: 'string',
label: i18n.ts._registry.key,
},
value: {
type: 'string',
multiline: true,
label: i18n.ts.value,
},
scope: {
type: 'string',
label: i18n.ts._registry.scope,
},
});
if (canceled) return;
os.apiWithDialog('i/registry/set', {
scope: result.scope.split('/'),
key: result.key,
value: JSON5.parse(result.value),
}).then(() => {
fetchScopes();
});
}
fetchScopes();
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.registry,
icon: 'fas fa-cogs',
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -9,8 +9,6 @@
<option value="left">{{ i18n.ts.left }}</option>
<option value="center">{{ i18n.ts.center }}</option>
</FormRadios>
<FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
</div>
</template>
@ -29,18 +27,6 @@ import { definePageMetadata } from '@/scripts/page-metadata';
const navWindow = computed(deckStore.makeGetterSetter('navWindow'));
const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn'));
const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
const profile = computed(deckStore.makeGetterSetter('profile'));
async function setProfile() {
const { canceled, result: name } = await os.inputText({
title: i18n.ts._deck.profile,
allowEmpty: false,
});
if (canceled) return;
profile.value = name;
unisonReload();
}
const headerActions = $computed(() => []);

View file

@ -10,6 +10,8 @@
<FormLink to="/settings/account-info" class="_formBlock">{{ i18n.ts.accountInfo }}</FormLink>
<FormLink to="/registry" class="_formBlock"><template #icon><i class="fas fa-cogs"></i></template>{{ i18n.ts.registry }}</FormLink>
<FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink>
</div>
</template>

View file

@ -39,10 +39,10 @@
<div class="_formRoot">
<FormSplit v-for="(record, i) in fields" :min-width="250" class="_formBlock">
<FormInput v-model="record.name">
<FormInput v-model="record.name" small>
<template #label>{{ i18n.ts._profile.metadataLabel }} #{{ i + 1 }}</template>
</FormInput>
<FormInput v-model="record.value">
<FormInput v-model="record.value" small>
<template #label>{{ i18n.ts._profile.metadataContent }} #{{ i + 1 }}</template>
</FormInput>
</FormSplit>

View file

@ -155,7 +155,7 @@ const age = $computed(() => {
});
function menu(ev) {
os.popupMenu(getUserMenu(props.user), ev.currentTarget ?? ev.target);
os.popupMenu(getUserMenu(props.user, router), ev.currentTarget ?? ev.target);
}
function parallaxLoop() {

View file

@ -23,7 +23,6 @@ import calcAge from 's-age';
import * as Acct from 'misskey-js/built/acct';
import * as misskey from 'misskey-js';
import { getScrollPosition } from '@/scripts/scroll';
import { getUserMenu } from '@/scripts/get-user-menu';
import number from '@/filters/number';
import { userPage, acct as getAcct } from '@/filters/user';
import * as os from '@/os';
@ -65,10 +64,6 @@ watch(() => props.acct, fetchUser, {
immediate: true,
});
function menu(ev) {
os.popupMenu(getUserMenu(user), ev.currentTarget ?? ev.target);
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => user ? [{

View file

@ -153,6 +153,15 @@ export const routes = [{
}, {
path: '/channels',
component: page(() => import('./pages/channels.vue')),
}, {
path: '/registry/keys/system/:path(*)?',
component: page(() => import('./pages/registry.keys.vue')),
}, {
path: '/registry/value/system/:path(*)?',
component: page(() => import('./pages/registry.value.vue')),
}, {
path: '/registry',
component: page(() => import('./pages/registry.vue')),
}, {
path: '/admin/file/:fileId',
component: iAmModerator ? page(() => import('./pages/admin-file.vue')) : page(() => import('./pages/not-found.vue')),

View file

@ -7,8 +7,9 @@ import * as os from '@/os';
import { userActions } from '@/store';
import { $i, iAmModerator } from '@/account';
import { mainRouter } from '@/router';
import { Router } from '@/nirax';
export function getUserMenu(user) {
export function getUserMenu(user, router: Router = mainRouter) {
const meId = $i ? $i.id : null;
async function pushList() {
@ -161,7 +162,7 @@ export function getUserMenu(user) {
icon: 'fas fa-info-circle',
text: i18n.ts.info,
action: () => {
os.pageWindow(`/user-info/${user.id}`);
router.push(`/user-info/${user.id}`);
},
}, {
icon: 'fas fa-envelope',
@ -227,7 +228,7 @@ export function getUserMenu(user) {
icon: 'fas fa-pencil-alt',
text: i18n.ts.editProfile,
action: () => {
mainRouter.push('/settings/profile');
router.push('/settings/profile');
},
}]);
}

View file

@ -3,7 +3,7 @@
<div class="body">
<div class="top">
<div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div>
<button v-click-anime v-tooltip.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
</button>
</div>

View file

@ -3,12 +3,12 @@
<div class="body">
<div class="top">
<div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div>
<button v-click-anime v-tooltip.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
<button v-click-anime v-tooltip.noDelay.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
</button>
</div>
<div class="middle">
<MkA v-click-anime v-tooltip.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact>
<MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact>
<i class="icon fas fa-home fa-fw"></i><span class="text">{{ i18n.ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
@ -17,7 +17,7 @@
:is="navbarItemDef[item].to ? 'MkA' : 'button'"
v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)"
v-click-anime
v-tooltip.right="i18n.ts[navbarItemDef[item].title]"
v-tooltip.noDelay.right="i18n.ts[navbarItemDef[item].title]"
class="item _button"
:class="[item, { active: navbarItemDef[item].active }]"
active-class="active"
@ -29,22 +29,22 @@
</component>
</template>
<div class="divider"></div>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin">
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.noDelay.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin">
<i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span>
</MkA>
<button v-click-anime class="item _button" @click="more">
<i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ i18n.ts.more }}</span>
<span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span>
</button>
<MkA v-click-anime v-tooltip.right="i18n.ts.settings" class="item" active-class="active" to="/settings">
<MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.settings" class="item" active-class="active" to="/settings">
<i class="icon fas fa-cog fa-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
</MkA>
</div>
<div class="bottom">
<button v-tooltip.right="i18n.ts.note" class="item _button post" data-cy-open-post-form @click="os.post">
<button v-tooltip.noDelay.right="i18n.ts.note" class="item _button post" data-cy-open-post-form @click="os.post">
<i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ i18n.ts.note }}</span>
</button>
<button v-click-anime v-tooltip.right="i18n.ts.account" class="item _button account" @click="openAccountMenu">
<button v-click-anime v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button>
</div>
@ -356,7 +356,7 @@ function more(ev: MouseEvent) {
> .icon {
display: inline-block;
width: 38px;
width: 30px;
aspect-ratio: 1;
}
}

View file

@ -33,8 +33,16 @@
<div>{{ i18n.ts._deck.introduction2 }}</div>
</div>
<div class="sideMenu">
<button v-tooltip.left="i18n.ts._deck.addColumn" class="_button button" @click="addColumn"><i class="fas fa-plus"></i></button>
<button v-tooltip.left="i18n.ts.settings" class="_button button settings" @click="showSettings"><i class="fas fa-cog"></i></button>
<div class="top">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${deckStore.state.profile}`" class="_button button" @click="changeProfile"><i class="fas fa-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" class="_button button" @click="deleteProfile"><i class="fas fa-trash-can"></i></button>
</div>
<div class="middle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" class="_button button" @click="addColumn"><i class="fas fa-plus"></i></button>
</div>
<div class="bottom">
<button v-tooltip.noDelay.left="i18n.ts.settings" class="_button button settings" @click="showSettings"><i class="fas fa-cog"></i></button>
</div>
</div>
</div>
</div>
@ -67,7 +75,7 @@
import { computed, defineAsyncComponent, onMounted, provide, ref, watch } from 'vue';
import { v4 as uuid } from 'uuid';
import XCommon from './_common_/common.vue';
import { deckStore, addColumn as addColumnToStore, loadDeck } from './deck/deck-store';
import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store';
import DeckColumnCore from '@/ui/deck/column-core.vue';
import XSidebar from '@/ui/_common_/navbar.vue';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
@ -78,9 +86,11 @@ import { navbarItemDef } from '@/navbar';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { mainRouter } from '@/router';
import { unisonReload } from '@/scripts/unison-reload';
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
mainRouter.navHook = (path): boolean => {
mainRouter.navHook = (path, flag): boolean => {
if (flag === 'forcePage') return false;
const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main');
if (deckStore.state.navWindow || noMainColumn) {
os.pageWindow(path);
@ -152,8 +162,6 @@ const onContextmenu = (ev) => {
}], ev);
};
provide('shouldSpacerMin', true);
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', (ev) => {
@ -168,6 +176,51 @@ loadDeck();
function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
// TODO??
}
function changeProfile(ev: MouseEvent) {
const items = ref([{
text: deckStore.state.profile,
active: true.valueOf,
}]);
getProfiles().then(profiles => {
items.value = [{
text: deckStore.state.profile,
active: true.valueOf,
}, ...(profiles.filter(k => k !== deckStore.state.profile).map(k => ({
text: k,
action: () => {
deckStore.set('profile', k);
unisonReload();
},
}))), null, {
text: i18n.ts._deck.newProfile,
icon: 'fas fa-plus',
action: async () => {
const { canceled, result: name } = await os.inputText({
title: i18n.ts._deck.profile,
allowEmpty: false,
});
if (canceled) return;
deckStore.set('profile', name);
unisonReload();
},
}];
});
os.popupMenu(items, ev.currentTarget ?? ev.target);
}
async function deleteProfile() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('deleteAreYouSure', { x: deckStore.state.profile }),
});
if (canceled) return;
deleteProfile_(deckStore.state.profile);
deckStore.set('profile', 'default');
unisonReload();
}
</script>
<style lang="scss" scoped>
@ -271,9 +324,25 @@ function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
justify-content: center;
width: 32px;
> .button {
width: 100%;
aspect-ratio: 1;
> .top, > .middle, > .bottom {
> .button {
display: block;
width: 100%;
aspect-ratio: 1;
}
}
> .top {
margin-bottom: auto;
}
> .middle {
margin-top: auto;
margin-bottom: auto;
}
> .bottom {
margin-top: auto;
}
}
}

View file

@ -1,5 +1,5 @@
<template>
<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
@ -11,9 +11,9 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store';
import XTimeline from '@/components/timeline.vue';
import * as os from '@/os';
import { updateColumn, Column } from './deck-store';
import { i18n } from '@/i18n';
const props = defineProps<{
@ -39,15 +39,22 @@ async function setAntenna() {
const { canceled, result: antenna } = await os.select({
title: i18n.ts.selectAntenna,
items: antennas.map(x => ({
value: x, text: x.name
value: x, text: x.name,
})),
default: props.column.antennaId
default: props.column.antennaId,
});
if (canceled) return;
updateColumn(props.column.id, {
antennaId: antenna.id
antennaId: antenna.id,
});
}
const menu = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.selectAntenna,
action: setAntenna,
}];
/*
function focus() {
timeline.focus();

View file

@ -31,31 +31,25 @@
</section>
</template>
<script lang="ts">
export type DeckFunc = {
title: string;
handler: (payload: MouseEvent) => void;
icon?: string;
};
</script>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, provide, watch } from 'vue';
import { onBeforeUnmount, onMounted, provide, Ref, watch } from 'vue';
import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column , deckStore } from './deck-store';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { MenuItem } from '@/types/menu';
provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true);
provide('shouldSpacerMin', true);
const props = withDefaults(defineProps<{
column: Column;
isStacked?: boolean;
func?: DeckFunc | null;
naked?: boolean;
indicated?: boolean;
menu?: MenuItem[];
}>(), {
isStacked: false,
func: null,
naked: false,
indicated: false,
});
@ -110,9 +104,9 @@ function toggleActive() {
}
function getMenu() {
const items = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.edit,
let items = [{
icon: 'fas fa-cog',
text: i18n.ts._deck.configureColumn,
action: async () => {
const { canceled, result } = await os.form(props.column.name, {
name: {
@ -179,13 +173,9 @@ function getMenu() {
},
}];
if (props.func) {
if (props.menu) {
items.unshift(null);
items.unshift({
icon: props.func.icon,
text: props.func.title,
action: props.func.handler,
});
items = props.menu.concat(items);
}
return items;

View file

@ -72,18 +72,8 @@ export const loadDeck = async () => {
return;
}
deckStore.set('columns', [{
id: 'a',
type: 'main',
name: i18n.ts._deck._columns.main,
width: 350,
}, {
id: 'b',
type: 'notifications',
name: i18n.ts._deck._columns.notifications,
width: 330,
}]);
deckStore.set('layout', [['a'], ['b']]);
deckStore.set('columns', []);
deckStore.set('layout', []);
return;
}
throw err;
@ -105,6 +95,19 @@ export const saveDeck = throttle(1000, () => {
});
});
export async function getProfiles(): Promise<string[]> {
return await api('i/registry/keys', {
scope: ['client', 'deck', 'profiles'],
});
}
export async function deleteProfile(key: string): Promise<void> {
return await api('i/registry/remove', {
scope: ['client', 'deck', 'profiles'],
key: key,
});
}
export function addColumn(column: Column) {
if (column.name === undefined) column.name = null;
deckStore.push('columns', column);

View file

@ -1,5 +1,5 @@
<template>
<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
@ -11,9 +11,9 @@
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store';
import XTimeline from '@/components/timeline.vue';
import * as os from '@/os';
import { updateColumn, Column } from './deck-store';
import { i18n } from '@/i18n';
const props = defineProps<{
@ -37,16 +37,22 @@ async function setList() {
const { canceled, result: list } = await os.select({
title: i18n.ts.selectList,
items: lists.map(x => ({
value: x, text: x.name
value: x, text: x.name,
})),
default: props.column.listId
default: props.column.listId,
});
if (canceled) return;
updateColumn(props.column.id, {
listId: list.id
listId: list.id,
});
}
const menu = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.selectList,
action: setList,
}];
/*
function focus() {
timeline.focus();

View file

@ -1,5 +1,5 @@
<template>
<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }" @parent-focus="$event => emit('parent-focus', $event)">
<XColumn :column="column" :is-stacked="isStacked" :menu="menu" @parent-focus="$event => emit('parent-focus', $event)">
<template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotifications :include-types="column.includingTypes"/>
@ -9,10 +9,10 @@
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import XColumn from './column.vue';
import { updateColumn , Column } from './deck-store';
import XNotifications from '@/components/notifications.vue';
import * as os from '@/os';
import { updateColumn } from './deck-store';
import { Column } from './deck-store';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
@ -30,9 +30,15 @@ function func() {
done: async (res) => {
const { includingTypes } = res;
updateColumn(props.column.id, {
includingTypes: includingTypes
includingTypes: includingTypes,
});
},
}, 'closed');
}
const menu = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.notificationSetting,
action: func,
}];
</script>

View file

@ -1,5 +1,5 @@
<template>
<XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)">
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i v-if="column.tl === 'home'" class="fas fa-home"></i>
<i v-else-if="column.tl === 'local'" class="fas fa-comments"></i>
@ -22,9 +22,9 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
import XColumn from './column.vue';
import { removeColumn, updateColumn, Column } from './deck-store';
import XTimeline from '@/components/timeline.vue';
import * as os from '@/os';
import { removeColumn, updateColumn, Column } from './deck-store';
import { $i } from '@/account';
import { instance } from '@/instance';
import { i18n } from '@/i18n';
@ -57,13 +57,13 @@ async function setType() {
const { canceled, result: src } = await os.select({
title: i18n.ts.timeline,
items: [{
value: 'home' as const, text: i18n.ts._timelines.home
value: 'home' as const, text: i18n.ts._timelines.home,
}, {
value: 'local' as const, text: i18n.ts._timelines.local
value: 'local' as const, text: i18n.ts._timelines.local,
}, {
value: 'social' as const, text: i18n.ts._timelines.social
value: 'social' as const, text: i18n.ts._timelines.social,
}, {
value: 'global' as const, text: i18n.ts._timelines.global
value: 'global' as const, text: i18n.ts._timelines.global,
}],
});
if (canceled) {
@ -73,7 +73,7 @@ async function setType() {
return;
}
updateColumn(props.column.id, {
tl: src
tl: src,
});
}
@ -97,6 +97,12 @@ function onChangeActiveState(state) {
}
}
const menu = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.timeline,
action: setType,
}];
/*
export default defineComponent({
watch: {

View file

@ -1,5 +1,5 @@
<template>
<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<XColumn :menu="menu" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template>
<div class="wtdtxvec">
@ -46,6 +46,12 @@ function updateWidgets(widgets) {
function func() {
edit = !edit;
}
const menu = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.editWidgets,
action: func,
}];
</script>
<style lang="scss" scoped>