Compare commits

..

24 Commits

Author SHA1 Message Date
2314133112 12.36.1 2020-04-23 18:25:51 +09:00
06a47a7bd3 New translations ja-JP.yml (Chinese Simplified) (#6292) 2020-04-23 18:24:50 +09:00
05a785ebd0 chore: Update dep 2020-04-23 18:23:54 +09:00
2ee5835186 fix(client): Fix a bug that if block of pages not working 2020-04-23 08:57:10 +09:00
19e1abe110 12.36.0 2020-04-22 21:43:54 +09:00
d19441f3ae fix(client): Fix lint 2020-04-22 19:51:09 +09:00
6e3ee05cb6 refactor(client): 2020-04-22 19:36:28 +09:00
51476ad06f New Crowdin translations (#6281)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)
2020-04-22 10:10:43 +09:00
1f998168e2 feat(client): Implement default upload folder setting
Resolve #5985
2020-04-22 00:34:56 +09:00
e72011f1da chore: Update deps 2020-04-22 00:08:29 +09:00
0df3e22e51 Fix #6289 2020-04-21 20:26:54 +09:00
5a9530ccd4 Fix #6229 2020-04-21 20:16:37 +09:00
0a4d119d86 fix(lint): Use const 2020-04-20 21:36:58 +09:00
2ee0e07bb6 refactor(client): 2020-04-20 21:35:27 +09:00
533c9a4fe1 Merge pull request #6282 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-04-20 01:02:14 +09:00
30c000116f Update README.md [AUTOGEN] 2020-04-19 20:51:06 +09:00
dc649fe420 12.35.2 2020-04-19 18:43:24 +09:00
4a8ec173ae Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-04-19 18:41:16 +09:00
26d6fe9a4e chore: Update dep 2020-04-19 18:41:02 +09:00
46f5175a0d New Crowdin translations (#6278)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)
2020-04-19 18:39:22 +09:00
f704e7a602 enhance(pages): Improve hcart 2020-04-19 17:41:01 +09:00
8cefcaa55f fix(client): Fix bug that cannot post when image only 2020-04-19 17:40:46 +09:00
164c6505f2 enhance(client): Use icon instead of text 2020-04-19 17:39:54 +09:00
0a1b83c70f feat(aiscript): Better env vars 2020-04-19 16:28:19 +09:00
50 changed files with 594 additions and 1178 deletions

View File

@ -152,7 +152,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
@ -162,7 +162,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin </a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI </a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td>
<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
@ -204,7 +204,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Fri, 03 Apr 2020 11:52:08 UTC
**Last updated:** Sun, 19 Apr 2020 11:51:06 UTC
<!-- PATREON_END -->
[backer-url]: #backers

View File

@ -27,7 +27,7 @@ uploading: "Upload läuft"
save: "Speichern"
users: "Benutzer"
addUser: "Benutzer hinzufügen"
favorite: "Favorit"
favorite: "Zu Favoriten hinzufügen"
favorites: "Favoriten"
unfavorite: "Aus Favoriten entfernen"
pin: "Anheften"
@ -239,6 +239,8 @@ drive: "Drive"
fileName: "Dateiname"
selectFile: "Datei auswählen"
selectFiles: "Dateien auswählen"
selectFolder: "Wähle einen Ordner"
selectFolders: "Wähle Ordner"
renameFile: "Datei umbenennen"
folderName: "Ordnername"
createFolder: "Ordner erstellen"
@ -317,7 +319,7 @@ caseSensitive: "Groß-/Kleinschreibung unterscheiden"
withReplies: "Antworten beinhalten"
connectedTo: "Mit folgenden Benutzerkonten verknüpft"
notesAndReplies: "Notizen und Antworten"
withFiles: "Dateien beinhalten"
withFiles: "Notizen mit Dateien"
silence: "Instanzweit stummschalten"
silenceConfirm: "Möchtest du diesen Benutzer wirklich instanzweit stummschalten?"
unsilence: "Instanzweite Stummschaltung aufheben"
@ -352,8 +354,6 @@ unregister: "Deaktivieren"
passwordLessLogin: "Passwortloses Anmelden einrichten"
resetPassword: "Passwort zurücksetzen"
newPasswordIs: "Das neue Passwort ist \"{password}\""
post: "Beitrag"
posted: "Gesendet"
autoReloadWhenDisconnected: "Automatisch aktualisieren wenn die Serververbindung getrennt wird"
autoNoteWatch: "Notizen automatisch beobachten"
autoNoteWatchDescription: "Werde über Notizen, auf die du reagiert oder geantwortet hast, informiert"
@ -755,6 +755,7 @@ _pages:
post: "Neue Notiz anfertigen"
_post:
text: "Inhalt"
attachCanvasImage: "Leinwand als Bild anfügen"
canvasId: "Leinwand-ID"
textInput: "Texteingabe"
_textInput:

View File

@ -239,6 +239,8 @@ drive: "Drive"
fileName: "Filename"
selectFile: "Select a file"
selectFiles: "Select files"
selectFolder: "Select a folder"
selectFolders: "Select folders"
renameFile: "Rename file"
folderName: "Folder name"
createFolder: "Create a folder"
@ -352,8 +354,6 @@ unregister: "Unregister"
passwordLessLogin: "Set up password-less login"
resetPassword: "Reset password"
newPasswordIs: "The new password is \"{password}\""
post: "Post"
posted: "Posted!"
autoReloadWhenDisconnected: "Auto refresh when disconnected from server"
autoNoteWatch: "Watch note automatically"
autoNoteWatchDescription: "Get notified about the notes which you reactioned or replied."
@ -755,6 +755,7 @@ _pages:
post: "Compose a note"
_post:
text: "Content"
attachCanvasImage: "Post with Canvas as Image"
canvasId: "Canvas ID"
textInput: "Text input"
_textInput:

View File

@ -239,6 +239,8 @@ drive: "Drive"
fileName: "Nombre de archivo"
selectFile: "Elegir archivo"
selectFiles: "Elegir archivos"
selectFolder: "Seleccione una carpeta"
selectFolders: "Seleccione carpetas"
renameFile: "Renombrar archivo"
folderName: "Nombre de la carpeta"
createFolder: "Crear carpeta"
@ -352,8 +354,6 @@ unregister: "Cancelar registro"
passwordLessLogin: "Iniciar sesión sin contraseña"
resetPassword: "Resetear contraseña"
newPasswordIs: "La nueva contraseña es \"{password}\""
post: "Nota"
posted: "Posteado"
autoReloadWhenDisconnected: "Recargar automáticamente cuando el servidor está desconectado"
autoNoteWatch: "Ver nota automáticamente"
autoNoteWatchDescription: "Recibe notificaciones sobre las notas de otros usuarios que a los que respondiste y reaccionaste"
@ -755,6 +755,7 @@ _pages:
post: "Formulario"
_post:
text: "Contenido"
attachCanvasImage: "Nota con lienzo como imagen"
canvasId: "Lienzo ID"
textInput: "Entrada de texto"
_textInput:

View File

@ -239,6 +239,8 @@ drive: "Drive"
fileName: "Nom du fichier"
selectFile: "Choisir le fichier"
selectFiles: "Choisir le fichiers"
selectFolder: "Sélectionnez un dossier"
selectFolders: "Sélectionnez dossiers"
renameFile: "Renommer le ficher"
folderName: "Nom du dossier"
createFolder: "Créer un dossier"
@ -352,8 +354,6 @@ unregister: "Se désinscrire"
passwordLessLogin: "Connectez-vous sans mot de passe"
resetPassword: "Réinitialiser mot de passe"
newPasswordIs: "Votre nouveau mot de passe est \"{password}\""
post: "Publier"
posted: "Publié !"
autoReloadWhenDisconnected: "Rechargement automatique lorsque le serveur se déconnecte"
autoNoteWatch: "Surveiller automatique pour les notes"
autoNoteWatchDescription: "Soyez informé des notes auxquelles vous avez réagi ou répondu."
@ -755,6 +755,7 @@ _pages:
post: "Formulaire à publier"
_post:
text: "Contenu"
attachCanvasImage: "Publier avec Toile comme image"
canvasId: "Toile ID"
textInput: "Entrée de textuelle"
_textInput:

View File

@ -239,6 +239,8 @@ drive: "ドライブ"
fileName: "ファイル名"
selectFile: "ファイルを選択"
selectFiles: "ファイルを選択"
selectFolder: "フォルダーを選択"
selectFolders: "フォルダーを選択"
renameFile: "ファイル名を変更"
folderName: "フォルダー名"
createFolder: "フォルダーを作成"
@ -352,8 +354,6 @@ unregister: "登録を解除"
passwordLessLogin: "パスワード無しログイン"
resetPassword: "パスワードをリセット"
newPasswordIs: "新しいパスワードは「{password}」です"
post: "投稿"
posted: "投稿しました"
autoReloadWhenDisconnected: "サーバー切断時に自動リロード"
autoNoteWatch: "ノートの自動ウォッチ"
autoNoteWatchDescription: "あなたがリアクションしたり返信したりした他のユーザーのノートに関する通知を受け取るようにします。"

View File

@ -239,6 +239,8 @@ drive: "드라이브"
fileName: "파일명"
selectFile: "파일 선택"
selectFiles: "파일 선택"
selectFolder: "폴더 선택"
selectFolders: "폴더 선택"
renameFile: "파일 이름 변경"
folderName: "폴더명"
createFolder: "폴더 만들기"
@ -352,8 +354,6 @@ unregister: "등록 해제"
passwordLessLogin: "비밀번호 없이 로그인"
resetPassword: "비밀번호 재설정"
newPasswordIs: "새로운 비밀번호는 \"{password}\" 입니다"
post: "작성"
posted: "게시하였습니다"
autoReloadWhenDisconnected: "서버와의 연결이 끊기면 자동 새로고침"
autoNoteWatch: "노트를 자동으로 지켜보기"
autoNoteWatchDescription: "리액션하거나 답글을 남긴 다른 유저의 노트에 대한 알림을 받습니다."
@ -755,6 +755,7 @@ _pages:
post: "글 입력란"
_post:
text: "내용"
attachCanvasImage: "캔버스의 이미지와 함께 게시하기"
canvasId: "캔버스 ID"
textInput: "텍스트 입력"
_textInput:

View File

@ -239,6 +239,8 @@ drive: "网盘"
fileName: "文件名称"
selectFile: "选择文件"
selectFiles: "选择文件"
selectFolder: "选择文件夹"
selectFolders: "选择多个文件夹"
renameFile: "重命名文件"
folderName: "文件夹名称"
createFolder: "创建文件夹"
@ -265,6 +267,7 @@ watch: "关注"
unwatch: "取消关注"
accept: "允许"
reject: "拒绝"
normal: "正常"
instanceName: "实例名称"
instanceDescription: "实例介绍"
maintainerName: "管理员名称"
@ -319,6 +322,7 @@ notesAndReplies: "帖子与回复"
withFiles: "附件"
silence: "禁言"
silenceConfirm: "确认要禁言吗?"
unsilence: "解除禁言"
unsilenceConfirm: "要解除禁言吗?"
popularUsers: "热门用户"
recentlyUpdatedUsers: "最近投稿用户"
@ -350,8 +354,6 @@ unregister: "删除账户"
passwordLessLogin: "无密码登录"
resetPassword: "重置密码"
newPasswordIs: "新的密码是「{password}」"
post: "投稿"
posted: "已投稿"
autoReloadWhenDisconnected: "断开连接时自动重新加载"
autoNoteWatch: "自动关注帖子"
autoNoteWatchDescription: "让您能够收到关于「反应」和回复其他用户的帖子的通知。"
@ -454,6 +456,8 @@ objectStorageRegion: "可用区"
objectStorageRegionDesc: "指定一个可用区例如“xx-east-1”。 如果您的对象存储服务没有可用区概念请将其留空或填写“us-east-1”。"
objectStorageUseSSL: "使用SSL"
objectStorageUseSSLDesc: "如果不使用https进行API连接请关闭。"
objectStorageUseProxy: "使用代理"
objectStorageUseProxyDesc: "如果您不使用代理进行API连接请将其关闭。"
serverLogs: "服务器日志"
deleteAll: "删除全部"
showFixedPostForm: "在时间线顶部显示帖子表单"
@ -476,7 +480,18 @@ state: "状态"
sort: "排序"
ascendingOrder: "升序"
descendingOrder: "降序"
scratchpad: "暂存器"
scratchpadDescription: "暂存器为AiScript提供了实验环境。您可以编写代码以与Misskey交互运行它并查看结果。"
output: "输出"
script: "脚本"
disablePagesScript: "禁用页面脚本"
updateRemoteUser: "更新远程用户信息"
deleteAllFiles: "删除所有文件"
deleteAllFilesConfirm: "要删除所有文件吗?"
removeAllFollowing: "取消所有关注"
removeAllFollowingDescription: "取消{host}的所有关注者。当实例不存在时执行。"
userSuspended: "该用户已被冻结。"
userSilenced: "该用户已被禁言。"
_theme:
explore: "寻找主题"
install: "安装主题"
@ -740,6 +755,8 @@ _pages:
post: "投稿窗口"
_post:
text: "内容"
attachCanvasImage: "附加画布图像"
canvasId: "画布ID"
textInput: "文本输入"
_textInput:
name: "变量名"
@ -755,6 +772,11 @@ _pages:
name: "变量名"
text: "标题"
default: "默认值"
canvas: "画布"
_canvas:
id: "画布ID"
width: "宽度"
height: "高度"
switch: "开关"
_switch:
name: "变量名"
@ -780,6 +802,9 @@ _pages:
message: "按下时显示的消息"
variable: "发送的变量"
no-variable: "空"
callAiScript: "调用AiScript"
_callAiScript:
functionName: "函数名"
radioButton: "选择项"
_radioButton:
name: "变量名"
@ -940,6 +965,7 @@ _pages:
_splitStrByLine:
arg1: "文本"
ref: "变量"
aiScriptVar: "AiScript变量"
fn: "函数"
_fn:
slots: "槽函数"

View File

@ -256,8 +256,6 @@ userList: "清單"
passwordLessLogin: "設置無密碼登入"
resetPassword: "重置密碼"
newPasswordIs: "新密碼為「{password}」"
post: "投稿"
posted: "投稿完成"
autoReloadWhenDisconnected: "和伺服器斷線時自動重新載入"
autoNoteWatch: "自動關注筆記"
autoNoteWatchDescription: "收到反應或回覆過的筆記的通知"

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.35.1",
"version": "12.36.1",
"codename": "indigo",
"repository": {
"type": "git",
@ -42,7 +42,7 @@
"@koa/cors": "3.0.0",
"@koa/multer": "2.0.2",
"@koa/router": "8.0.8",
"@syuilo/aiscript": "0.4.0",
"@syuilo/aiscript": "0.6.0",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.12.2",
"@types/cbor": "5.0.0",
@ -189,7 +189,6 @@
"postcss-loader": "3.0.0",
"prismjs": "1.20.0",
"probe-image-size": "5.0.0",
"progress-bar-webpack-plugin": "2.1.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
"pug": "2.0.4",
@ -223,7 +222,6 @@
"syslog-pro": "1.0.0",
"systeminformation": "4.23.3",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.3.5",
"textarea-caret": "3.1.0",
"three": "0.115.0",
"tinycolor2": "1.4.1",
@ -258,7 +256,7 @@
"vuex": "3.1.3",
"vuex-persistedstate": "3.0.1",
"web-push": "3.4.3",
"webpack": "4.42.1",
"webpack": "5.0.0-beta.15",
"webpack-cli": "3.3.11",
"websocket": "1.0.31",
"ws": "7.2.3",

View File

@ -163,7 +163,6 @@ import { v4 as uuid } from 'uuid';
import i18n from './i18n';
import { host, instanceName } from './config';
import { search } from './scripts/search';
import MkToast from './components/toast.vue';
const DESKTOP_THRESHOLD = 1100;
@ -535,14 +534,14 @@ export default Vue.extend({
});
},
onNotification(notification) {
async onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
if (true) {
this.$root.stream.send('readNotification', {
id: notification.id
});
this.$root.new(MkToast, {
this.$root.new(await import('./components/toast.vue').then(m => m.default), {
notification
});
}

View File

@ -1,8 +1,11 @@
<template>
<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="selected.length === 0" @ok="ok()">
<template #header>{{ multiple ? $t('selectFiles') : $t('selectFile') }}<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ selected.length | number }})</span></template>
<x-window ref="window" :width="800" :height="500" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="(type === 'file') && (selected.length === 0)" @ok="ok()">
<template #header>
{{ multiple ? ((type === 'file') ? $t('selectFiles') : $t('selectFolders')) : ((type === 'file') ? $t('selectFile') : $t('selectFolder')) }}
<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ selected.length | number }})</span>
</template>
<div>
<x-drive :multiple="multiple" @change-selection="onChangeSelection" :select-mode="true"/>
<x-drive :multiple="multiple" @change-selection="onChangeSelection" :select="type"/>
</div>
</x-window>
</template>
@ -25,7 +28,7 @@ export default Vue.extend({
type: {
type: String,
required: false,
default: undefined
default: 'file'
},
multiple: {
type: Boolean,
@ -45,8 +48,8 @@ export default Vue.extend({
this.$refs.window.close();
},
onChangeSelection(files) {
this.selected = files;
onChangeSelection(xs) {
this.selected = xs;
}
}
});

View File

@ -42,11 +42,20 @@ import { faDownload, faLink, faICursor, faTrashAlt } from '@fortawesome/free-sol
export default Vue.extend({
i18n,
components: {
XFileThumbnail
},
props: {
file: {
type: Object,
required: true,
},
isSelected: {
type: Boolean,
required: false,
default: false,
},
selectMode: {
type: Boolean,
required: false,
@ -54,10 +63,6 @@ export default Vue.extend({
}
},
components: {
XFileThumbnail
},
data() {
return {
isDragging: false
@ -65,12 +70,10 @@ export default Vue.extend({
},
computed: {
// TODO: parentへの参照を無くす
browser(): any {
return this.$parent;
},
isSelected(): boolean {
return this.browser.selectedFiles.some(f => f.id == this.file.id);
},
title(): string {
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.size)}`;
}
@ -79,7 +82,7 @@ export default Vue.extend({
methods: {
onClick(ev) {
if (this.selectMode) {
this.browser.chooseFile(this.file);
this.$emit('chosen', this.file);
} else {
this.$root.menu({
items: [{

View File

@ -21,6 +21,7 @@
<p class="upload" v-if="$store.state.settings.uploadFolder == folder.id">
{{ $t('uploadFolder') }}
</p>
<button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button>
</div>
</template>
@ -36,6 +37,16 @@ export default Vue.extend({
folder: {
type: Object,
required: true,
},
isSelected: {
type: Boolean,
required: false,
default: false,
},
selectMode: {
type: Boolean,
required: false,
default: false,
}
},
@ -56,7 +67,12 @@ export default Vue.extend({
return this.folder.name;
}
},
methods: {
checkboxClicked(e) {
this.$emit('chosen', this.folder);
},
onClick() {
this.browser.move(this.folder);
},
@ -241,10 +257,24 @@ export default Vue.extend({
cursor: pointer;
}
* {
*:not(.checkbox) {
pointer-events: none;
}
> .checkbox {
position: absolute;
bottom: 8px;
right: 8px;
width: 16px;
height: 16px;
background: #fff;
border: solid 1px #000;
&.checked {
background: var(--accent);
}
}
&[data-draghover] {
&:after {
content: "";

View File

@ -3,9 +3,9 @@
<nav>
<div class="path" @contextmenu.prevent.stop="() => {}">
<x-nav-folder :class="{ current: folder == null }"/>
<template v-for="folder in hierarchyFolders">
<span class="separator"><fa :icon="faAngleRight"/></span>
<x-nav-folder :folder="folder" :key="folder.id"/>
<template v-for="f in hierarchyFolders">
<span class="separator" :key="f.id + ':separator'"><fa :icon="faAngleRight"/></span>
<x-nav-folder :folder="f" :key="f.id"/>
</template>
<span class="separator" v-if="folder != null"><fa :icon="faAngleRight"/></span>
<span class="folder current" v-if="folder != null">{{ folder.name }}</span>
@ -20,15 +20,15 @@
>
<div class="contents" ref="contents">
<div class="folders" ref="foldersContainer" v-if="folders.length > 0">
<x-folder v-for="folder in folders" :key="folder.id" class="folder" :folder="folder"/>
<x-folder v-for="f in folders" :key="f.id" class="folder" :folder="f" :select-mode="select === 'folder'" :is-selected="selectedFolders.some(x => x.id === f.id)" @chosen="chooseFolder"/>
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
<div class="padding" v-for="n in 16"></div>
<div class="padding" v-for="(n, i) in 16" :key="i"></div>
<mk-button v-if="moreFolders">{{ $t('loadMore') }}</mk-button>
</div>
<div class="files" ref="filesContainer" v-if="files.length > 0">
<x-file v-for="file in files" :key="file.id" class="file" :file="file" :select-mode="selectMode"/>
<x-file v-for="file in files" :key="file.id" class="file" :file="file" :select-mode="select === 'file'" :is-selected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile"/>
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
<div class="padding" v-for="n in 16"></div>
<div class="padding" v-for="(n, i) in 16" :key="i"></div>
<mk-button v-if="moreFiles" @click="fetchMoreFiles">{{ $t('loadMore') }}</mk-button>
</div>
<div class="empty" v-if="files.length == 0 && folders.length == 0 && !fetching">
@ -81,10 +81,10 @@ export default Vue.extend({
required: false,
default: false
},
selectMode: {
type: Boolean,
select: {
type: String,
required: false,
default: false
default: null
}
},
@ -102,6 +102,7 @@ export default Vue.extend({
moreFolders: false,
hierarchyFolders: [],
selectedFiles: [],
selectedFolders: [],
uploadings: [],
connection: null,
@ -392,6 +393,25 @@ export default Vue.extend({
}
},
chooseFolder(folder) {
const isAlreadySelected = this.selectedFolders.some(f => f.id == folder.id);
if (this.multiple) {
if (isAlreadySelected) {
this.selectedFolders = this.selectedFolders.filter(f => f.id != folder.id);
} else {
this.selectedFolders.push(folder);
}
this.$emit('change-selection', this.selectedFolders);
} else {
if (isAlreadySelected) {
this.$emit('selected', folder);
} else {
this.selectedFolders = [folder];
this.$emit('change-selection', [folder]);
}
}
},
move(target) {
if (target == null) {
this.goRoot();

View File

@ -1,5 +1,5 @@
<template>
<component :is="'x-' + value.type" :value="value" :page="page" :script="script" :key="value.id" :h="h"/>
<component :is="'x-' + value.type" :value="value" :page="page" :hpml="hpml" :key="value.id" :h="h"/>
</template>
<script lang="ts">
@ -27,7 +27,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
},
page: {

View File

@ -1,6 +1,6 @@
<template>
<div>
<mk-button class="kudkigyw" @click="click()" :primary="value.primary">{{ script.interpolate(value.text) }}</mk-button>
<mk-button class="kudkigyw" @click="click()" :primary="value.primary">{{ hpml.interpolate(value.text) }}</mk-button>
</div>
</template>
@ -16,35 +16,35 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
methods: {
click() {
if (this.value.action === 'dialog') {
this.script.eval();
this.hpml.eval();
this.$root.dialog({
text: this.script.interpolate(this.value.content)
text: this.hpml.interpolate(this.value.content)
});
} else if (this.value.action === 'resetRandom') {
this.script.aoiScript.updateRandomSeed(Math.random());
this.script.eval();
this.hpml.updateRandomSeed(Math.random());
this.hpml.eval();
} else if (this.value.action === 'pushEvent') {
this.$root.api('page-push', {
pageId: this.script.page.id,
pageId: this.hpml.page.id,
event: this.value.event,
...(this.value.var ? {
var: this.script.vars[this.value.var]
var: this.hpml.vars[this.value.var]
} : {})
});
this.$root.dialog({
type: 'success',
text: this.script.interpolate(this.value.message)
text: this.hpml.interpolate(this.value.message)
});
} else if (this.value.action === 'callAiScript') {
this.script.callAiScript(this.value.fn);
this.hpml.callAiScript(this.value.fn);
}
}
}

View File

@ -12,12 +12,12 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
mounted() {
this.script.aoiScript.registerCanvas(this.value.name, this.$refs.canvas);
this.hpml.registerCanvas(this.value.name, this.$refs.canvas);
}
});
</script>

View File

@ -1,6 +1,6 @@
<template>
<div>
<mk-button class="llumlmnx" @click="click()">{{ script.interpolate(value.text) }}</mk-button>
<mk-button class="llumlmnx" @click="click()">{{ hpml.interpolate(value.text) }}</mk-button>
</div>
</template>
@ -16,7 +16,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
@ -27,8 +27,8 @@ export default Vue.extend({
},
watch: {
v() {
this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
this.hpml.updatePageVar(this.value.name, this.v);
this.hpml.eval();
}
},
methods: {

View File

@ -1,6 +1,6 @@
<template>
<div v-show="script.vars[value.var]">
<x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h"/>
<div v-show="hpml.vars[value.var]">
<x-block v-for="child in value.children" :value="child" :page="page" :hpml="hpml" :key="child.id" :h="h"/>
</div>
</template>
@ -12,7 +12,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
},
page: {

View File

@ -1,6 +1,6 @@
<template>
<div>
<mk-input class="kudkigyw" v-model="v" type="number">{{ script.interpolate(value.text) }}</mk-input>
<mk-input class="kudkigyw" v-model="v" type="number">{{ hpml.interpolate(value.text) }}</mk-input>
</div>
</template>
@ -16,7 +16,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
@ -27,8 +27,8 @@ export default Vue.extend({
},
watch: {
v() {
this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
this.hpml.updatePageVar(this.value.name, this.v);
this.hpml.eval();
}
}
});

View File

@ -1,12 +1,13 @@
<template>
<div class="ngbfujlo">
<mk-textarea :value="text" readonly style="margin: 0;"></mk-textarea>
<mk-button class="button" primary @click="post()" :disabled="posting || posted">{{ posted ? $t('posted') : $t('post') }}</mk-button>
<mk-button class="button" primary @click="post()" :disabled="posting || posted"><fa v-if="posted" :icon="faCheck"/><fa v-else :icon="faPaperPlane"/></mk-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faCheck, faPaperPlane } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../i18n';
import MkTextarea from '../ui/textarea.vue';
import MkButton from '../ui/button.vue';
@ -22,21 +23,22 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
data() {
return {
text: this.script.interpolate(this.value.text),
text: this.hpml.interpolate(this.value.text),
posted: false,
posting: false,
faCheck, faPaperPlane
};
},
watch: {
'script.vars': {
'hpml.vars': {
handler() {
this.text = this.script.interpolate(this.value.text);
this.text = this.hpml.interpolate(this.value.text);
},
deep: true
}
@ -51,11 +53,14 @@ export default Vue.extend({
showCancelButton: false,
cancelableByBgClick: false
});
const canvas = this.script.aoiScript.canvases[this.value.canvasId];
const canvas = this.hpml.canvases[this.value.canvasId];
canvas.toBlob(blob => {
const data = new FormData();
data.append('file', blob);
data.append('i', this.$store.state.i.token);
if (this.$store.state.settings.uploadFolder) {
data.append('folderId', this.$store.state.settings.uploadFolder);
}
fetch(apiUrl + '/drive/files/create', {
method: 'POST',
@ -73,7 +78,7 @@ export default Vue.extend({
this.posting = true;
const file = this.value.attachCanvasImage ? await this.upload() : null;
this.$root.api('notes/create', {
text: this.text,
text: this.text === '' ? null : this.text,
fileIds: file ? [file.id] : undefined,
}).then(() => {
this.posted = true;

View File

@ -1,6 +1,6 @@
<template>
<div>
<div>{{ script.interpolate(value.title) }}</div>
<div>{{ hpml.interpolate(value.title) }}</div>
<mk-radio v-for="x in value.values" v-model="v" :value="x" :key="x">{{ x }}</mk-radio>
</div>
</template>
@ -17,7 +17,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
@ -28,8 +28,8 @@ export default Vue.extend({
},
watch: {
v() {
this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
this.hpml.updatePageVar(this.value.name, this.v);
this.hpml.eval();
}
}
});

View File

@ -3,7 +3,7 @@
<component :is="'h' + h">{{ value.title }}</component>
<div class="children">
<x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h + 1"/>
<x-block v-for="child in value.children" :value="child" :page="page" :hpml="hpml" :key="child.id" :h="h + 1"/>
</div>
</section>
</template>
@ -16,7 +16,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
},
page: {

View File

@ -1,6 +1,6 @@
<template>
<div class="hkcxmtwj">
<mk-switch v-model="v">{{ script.interpolate(value.text) }}</mk-switch>
<mk-switch v-model="v">{{ hpml.interpolate(value.text) }}</mk-switch>
</div>
</template>
@ -16,7 +16,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
@ -27,8 +27,8 @@ export default Vue.extend({
},
watch: {
v() {
this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
this.hpml.updatePageVar(this.value.name, this.v);
this.hpml.eval();
}
}
});

View File

@ -1,6 +1,6 @@
<template>
<div>
<mk-input class="kudkigyw" v-model="v" type="text">{{ script.interpolate(value.text) }}</mk-input>
<mk-input class="kudkigyw" v-model="v" type="text">{{ hpml.interpolate(value.text) }}</mk-input>
</div>
</template>
@ -16,7 +16,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
@ -27,8 +27,8 @@ export default Vue.extend({
},
watch: {
v() {
this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
this.hpml.updatePageVar(this.value.name, this.v);
this.hpml.eval();
}
}
});

View File

@ -15,13 +15,13 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
data() {
return {
text: this.script.interpolate(this.value.text),
text: this.hpml.interpolate(this.value.text),
};
},
computed: {
@ -38,9 +38,9 @@ export default Vue.extend({
}
},
watch: {
'script.vars': {
'hpml.vars': {
handler() {
this.text = this.script.interpolate(this.value.text);
this.text = this.hpml.interpolate(this.value.text);
},
deep: true
}

View File

@ -1,6 +1,6 @@
<template>
<div>
<mk-textarea v-model="v">{{ script.interpolate(value.text) }}</mk-textarea>
<mk-textarea v-model="v">{{ hpml.interpolate(value.text) }}</mk-textarea>
</div>
</template>
@ -16,7 +16,7 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
@ -27,8 +27,8 @@ export default Vue.extend({
},
watch: {
v() {
this.script.aoiScript.updatePageVar(this.value.name, this.v);
this.script.eval();
this.hpml.updatePageVar(this.value.name, this.v);
this.hpml.eval();
}
}
});

View File

@ -14,19 +14,19 @@ export default Vue.extend({
value: {
required: true
},
script: {
hpml: {
required: true
}
},
data() {
return {
text: this.script.interpolate(this.value.text),
text: this.hpml.interpolate(this.value.text),
};
},
watch: {
'script.vars': {
'hpml.vars': {
handler() {
this.text = this.script.interpolate(this.value.text);
this.text = this.hpml.interpolate(this.value.text);
},
deep: true
}

View File

@ -1,56 +1,19 @@
<template>
<div class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }" v-if="script">
<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :script="script" :key="child.id" :h="2"/>
<div class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }" v-if="hpml">
<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :hpml="hpml" :key="child.id" :h="2"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { AiScript, parse, values } from '@syuilo/aiscript';
import { parse } from '@syuilo/aiscript';
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
import { faHeart } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../i18n';
import XBlock from './page.block.vue';
import { ASEvaluator } from '../../scripts/aoiscript/evaluator';
import { collectPageVars } from '../../scripts/collect-page-vars';
import { Hpml } from '../../scripts/hpml/evaluator';
import { url } from '../../config';
class Script {
public aoiScript: ASEvaluator;
private onError: any;
public vars: Record<string, any>;
public page: Record<string, any>;
constructor(page, aoiScript, onError) {
this.page = page;
this.aoiScript = aoiScript;
this.onError = onError;
this.eval();
}
public eval() {
try {
this.vars = this.aoiScript.evaluateVars();
} catch (e) {
this.onError(e);
}
}
public interpolate(str: string) {
if (str == null) return null;
return str.replace(/{(.+?)}/g, match => {
const v = this.vars ? this.vars[match.slice(1, -1).trim()] : null;
return v == null ? 'NULL' : v.toString();
});
}
public callAiScript(fn: string) {
try {
if (this.aoiScript.aiscript) this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []);
} catch (e) {}
}
}
export default Vue.extend({
i18n,
@ -67,35 +30,26 @@ export default Vue.extend({
data() {
return {
script: null,
hpml: null,
faHeartS, faHeart
};
},
created() {
const pageVars = this.getPageVars();
this.script = new Script(this.page, new ASEvaluator(this, this.page.variables, pageVars, {
this.hpml = new Hpml(this, this.page, {
randomSeed: Math.random(),
visitor: this.$store.state.i,
page: this.page,
url: url,
enableAiScript: !this.$store.state.device.disablePagesScript
}), e => {
console.dir(e);
});
if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => {
this.script.eval();
};
},
mounted() {
this.$nextTick(() => {
if (this.script.page.script && this.script.aoiScript.aiscript) {
if (this.page.script && this.hpml.aiscript) {
let ast;
try {
ast = parse(this.script.page.script);
ast = parse(this.page.script);
} catch (e) {
console.error(e);
/*this.$root.dialog({
@ -104,8 +58,8 @@ export default Vue.extend({
});*/
return;
}
this.script.aoiScript.aiscript.exec(ast).then(() => {
this.script.eval();
this.hpml.aiscript.exec(ast).then(() => {
this.hpml.eval();
}).catch(e => {
console.error(e);
/*this.$root.dialog({
@ -114,20 +68,14 @@ export default Vue.extend({
});*/
});
} else {
this.script.eval();
this.hpml.eval();
}
});
},
beforeDestroy() {
if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.abort();
if (this.hpml.aiscript) this.hpml.aiscript.abort();
},
methods: {
getPageVars() {
return collectPageVars(this.page.content);
},
}
});
</script>

View File

@ -1,6 +1,6 @@
<template>
<x-modal ref="modal" @closed="() => { $emit('closed'); destroyDom(); }">
<div class="ebkgoccj" :class="{ noPadding }" @keydown="onKeydown">
<div class="ebkgoccj" :class="{ noPadding }" @keydown="onKeydown" :style="{ width: `${width}px`, height: `${height}px` }">
<div class="header">
<button class="_button" v-if="withOkButton" @click="close()"><fa :icon="faTimes"/></button>
<span class="title">
@ -49,7 +49,17 @@ export default Vue.extend({
type: Boolean,
required: false,
default: false
}
},
width: {
type: Number,
required: false,
default: 400
},
height: {
type: Number,
required: false,
default: 400
},
},
data() {
@ -76,19 +86,12 @@ export default Vue.extend({
<style lang="scss" scoped>
.ebkgoccj {
width: 400px;
height: 400px;
background: var(--panel);
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
@media (max-width: 500px) {
width: 350px;
height: 350px;
}
> .header {
$height: 58px;
$height-narrow: 42px;

View File

@ -29,7 +29,6 @@ import { faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faListUl, faSatell
import { faComments } from '@fortawesome/free-regular-svg-icons';
import Progress from '../scripts/loading';
import XTimeline from '../components/timeline.vue';
import XTutorial from './index.home.tutorial.vue';
import XPostForm from '../components/post-form.vue';
export default Vue.extend({
@ -41,7 +40,7 @@ export default Vue.extend({
components: {
XTimeline,
XTutorial,
XTutorial: () => import('./index.home.tutorial.vue').then(m => m.default),
XPostForm,
},

View File

@ -2,107 +2,57 @@
<section class="uawsfosz _card">
<div class="_title"><fa :icon="faCloud"/> {{ $t('drive') }}</div>
<div class="_content">
<mk-pagination :pagination="drivePagination" #default="{items}" class="drive" ref="drive">
<div class="file" v-for="(file, i) in items" :key="file.id" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
<div class="body">
<p class="name">
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
</p>
<footer>
<span class="type"><x-file-type-icon :type="file.type" class="icon"/>{{ file.type }}</span>
<span class="separator"></span>
<span class="data-size">{{ file.size | bytes }}</span>
<span class="separator"></span>
<span class="created-at"><fa :icon="faClock"/><mk-time :time="file.createdAt"/></span>
<template v-if="file.isSensitive">
<span class="separator"></span>
<span class="nsfw"><fa :icon="faEyeSlash"/> {{ $t('nsfw') }}</span>
</template>
</footer>
</div>
</div>
</mk-pagination>
</div>
<div class="_footer">
<mk-button primary inline :disabled="selected == null" @click="download()"><fa :icon="faDownload"/> {{ $t('download') }}</mk-button>
<mk-button inline :disabled="selected == null" @click="del()"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</mk-button>
<span>{{ $t('uploadFolder') }}: {{ uploadFolder ? uploadFolder.name : '-' }}</span>
<mk-button primary @click="chooseUploadFolder()"><fa :icon="faFolderOpen"/> {{ $t('selectFolder') }}</mk-button>
</div>
</section>
</template>
<script lang="ts">
import Vue from 'vue';
import { faCloud, faDownload } from '@fortawesome/free-solid-svg-icons';
import { faCloud, faFolderOpen } from '@fortawesome/free-solid-svg-icons';
import { faClock, faEyeSlash, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import XFileTypeIcon from '../../components/file-type-icon.vue';
import XFileThumbnail from '../../components/drive-file-thumbnail.vue';
import MkButton from '../../components/ui/button.vue';
import MkPagination from '../../components/ui/pagination.vue';
import i18n from '../../i18n';
import { selectDriveFolder } from '../../scripts/select-drive-folder';
export default Vue.extend({
i18n,
components: {
XFileTypeIcon,
XFileThumbnail,
MkPagination,
MkButton,
},
data() {
return {
selected: null,
connection: null,
drivePagination: {
endpoint: 'drive/files',
limit: 10,
},
faCloud, faClock, faEyeSlash, faDownload, faTrashAlt
uploadFolder: null,
faCloud, faClock, faEyeSlash, faFolderOpen, faTrashAlt
}
},
created() {
this.connection = this.$root.stream.useSharedConnection('drive');
this.connection.on('fileCreated', this.onStreamDriveFileCreated);
this.connection.on('fileUpdated', this.onStreamDriveFileUpdated);
this.connection.on('fileDeleted', this.onStreamDriveFileDeleted);
},
beforeDestroy() {
this.connection.dispose();
async created() {
if (this.$store.state.settings.uploadFolder) {
this.uploadFolder = await this.$root.api('drive/folders/show', {
folderId: this.$store.state.settings.uploadFolder
});
}
},
methods: {
onStreamDriveFileCreated(file) {
this.$refs.drive.prepend(file);
},
onStreamDriveFileUpdated(file) {
// TODO
},
onStreamDriveFileDeleted(fileId) {
this.$refs.drive.remove(x => x.id === fileId);
},
download() {
window.open(this.selected.url, '_blank');
},
async del() {
const { canceled } = await this.$root.dialog({
type: 'warning',
text: this.$t('driveFileDeleteConfirm', { name: this.selected.name }),
showCancelButton: true
});
if (canceled) return;
this.$root.api('drive/files/delete', {
fileId: this.selected.id
chooseUploadFolder() {
selectDriveFolder(this.$root, false).then(async folder => {
await this.$store.dispatch('settings/set', { key: 'uploadFolder', value: folder ? folder.id : null });
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
if (this.$store.state.settings.uploadFolder) {
this.uploadFolder = await this.$root.api('drive/folders/show', {
folderId: this.$store.state.settings.uploadFolder
});
} else {
this.uploadFolder = null;
}
});
}
}
@ -111,102 +61,6 @@ export default Vue.extend({
<style lang="scss" scoped>
.uawsfosz {
> ._content {
max-height: 350px;
overflow: auto;
> .drive {
> .file {
display: grid;
margin: 0 auto;
grid-template-columns: 64px 1fr;
grid-column-gap: 10px;
cursor: pointer;
&.selected {
background: var(--accent);
box-shadow: 0 0 0 8px var(--accent);
color: #fff;
}
&:not(:last-child) {
margin-bottom: 16px;
}
> .thumbnail {
width: 64px;
height: 64px;
}
> .body {
display: block;
word-break: break-all;
padding-top: 4px;
> .name {
display: block;
margin: 0;
padding: 0;
font-size: 0.9em;
font-weight: bold;
word-break: break-word;
> .ext {
opacity: 0.5;
}
}
> .tags {
display: block;
margin: 4px 0 0 0;
padding: 0;
list-style: none;
font-size: 0.5em;
> .tag {
display: inline-block;
margin: 0 5px 0 0;
padding: 1px 5px;
border-radius: 2px;
}
}
> footer {
display: block;
margin: 4px 0 0 0;
font-size: 0.7em;
> .separator {
padding: 0 4px;
}
> .type {
opacity: 0.7;
> .icon {
margin-right: 4px;
}
}
> .data-size {
opacity: 0.7;
}
> .created-at {
opacity: 0.7;
> [data-icon] {
margin-right: 2px;
}
}
> .nsfw {
color: #bf4633;
}
}
}
}
}
}
}
</style>

View File

@ -21,12 +21,12 @@
<mk-select v-model="value.var">
<template #label>{{ $t('_pages.blocks._button._action._pushEvent.variable') }}</template>
<option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option>
<option v-for="v in aoiScript.getVarsByType()" :value="v.name">{{ v.name }}</option>
<option v-for="v in hpml.getVarsByType()" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('_pages.script.pageVariables')">
<option v-for="v in aoiScript.getPageVarsByType()" :value="v">{{ v }}</option>
<option v-for="v in hpml.getPageVarsByType()" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$t('_pages.script.enviromentVariables')">
<option v-for="v in aoiScript.getEnvVarsByType()" :value="v">{{ v }}</option>
<option v-for="v in hpml.getEnvVarsByType()" :value="v">{{ v }}</option>
</optgroup>
</mk-select>
</template>
@ -57,7 +57,7 @@ export default Vue.extend({
value: {
required: true
},
aoiScript: {
hpml: {
required: true,
},
},

View File

@ -10,16 +10,16 @@
<section class="romcojzs">
<mk-select v-model="value.var">
<template #label>{{ $t('_pages.blocks._if.variable') }}</template>
<option v-for="v in aoiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
<option v-for="v in hpml.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('_pages.script.pageVariables')">
<option v-for="v in aoiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
<option v-for="v in hpml.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$t('_pages.script.enviromentVariables')">
<option v-for="v in aoiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
<option v-for="v in hpml.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
</mk-select>
<x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/>
<x-blocks class="children" v-model="value.children" :hpml="hpml"/>
</section>
</x-container>
</template>
@ -45,7 +45,7 @@ export default Vue.extend({
value: {
required: true
},
aoiScript: {
hpml: {
required: true,
},
},

View File

@ -11,7 +11,7 @@
</template>
<section class="ilrvjyvi">
<x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/>
<x-blocks class="children" v-model="value.children" :hpml="hpml"/>
</section>
</x-container>
</template>
@ -37,7 +37,7 @@ export default Vue.extend({
value: {
required: true
},
aoiScript: {
hpml: {
required: true,
},
},

View File

@ -1,6 +1,6 @@
<template>
<x-draggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :aoi-script="aoiScript"/>
<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :hpml="hpml"/>
</x-draggable>
</template>
@ -32,7 +32,7 @@ export default Vue.extend({
type: Array,
required: true
},
aoiScript: {
hpml: {
required: true,
},
},

View File

@ -24,15 +24,15 @@
</section>
<section v-else-if="value.type === 'ref'" class="hpdwcrvs">
<select v-model="value.value">
<option v-for="v in aoiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
<option v-for="v in hpml.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
<optgroup :label="$t('_pages.script.argVariables')">
<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
</optgroup>
<optgroup :label="$t('_pages.script.pageVariables')">
<option v-for="v in aoiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
<option v-for="v in hpml.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$t('_pages.script.enviromentVariables')">
<option v-for="v in aoiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
<option v-for="v in hpml.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
</select>
</section>
@ -44,13 +44,13 @@
<span>{{ $t('_pages.script.blocks._fn.slots') }}</span>
<template #desc>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
</mk-textarea>
<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :aoi-script="aoiScript" :fn-slots="value.value.slots" :name="name"/>
<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="value.value.slots" :name="name"/>
</section>
<section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;">
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aoiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :aoi-script="aoiScript" :name="name" :key="i"/>
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="hpml.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name" :key="i"/>
</section>
<section v-else class="" style="padding:16px;">
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :aoi-script="aoiScript" :name="name" :fn-slots="fnSlots" :key="i"/>
<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots" :key="i"/>
</section>
</x-container>
</template>
@ -62,7 +62,7 @@ import { v4 as uuid } from 'uuid';
import i18n from '../../i18n';
import XContainer from './page-editor.container.vue';
import MkTextarea from '../../components/ui/textarea.vue';
import { isLiteralBlock, funcDefs, blockDefs } from '../../scripts/aoiscript/index';
import { isLiteralBlock, funcDefs, blockDefs } from '../../scripts/hpml/index';
export default Vue.extend({
i18n,
@ -88,7 +88,7 @@ export default Vue.extend({
required: false,
default: false
},
aoiScript: {
hpml: {
required: true,
},
name: {
@ -156,7 +156,7 @@ export default Vue.extend({
if (this.value.type && this.value.type.startsWith('fn:')) {
const fnName = this.value.type.split(':')[1];
const fn = this.aoiScript.getVarByName(fnName);
const fn = this.hpml.getVarByName(fnName);
const empties = [];
for (let i = 0; i < fn.value.slots.length; i++) {
@ -202,9 +202,9 @@ export default Vue.extend({
deep: true
});
this.$watch('aoiScript.variables', () => {
this.$watch('hpml.variables', () => {
if (this.type != null && this.value) {
this.error = this.aoiScript.typeCheck(this.value);
this.error = this.hpml.typeCheck(this.value);
}
}, {
deep: true
@ -226,7 +226,7 @@ export default Vue.extend({
},
_getExpectedType(slot: number) {
return this.aoiScript.getExpectedType(this.value, slot);
return this.hpml.getExpectedType(this.value, slot);
}
}
});

View File

@ -46,7 +46,7 @@
</div>
</template>
<x-blocks class="content" v-model="content" :aoi-script="aoiScript"/>
<x-blocks class="content" v-model="content" :hpml="hpml"/>
<mk-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></mk-button>
</section>
@ -62,7 +62,7 @@
@input="v => updateVariable(v)"
@remove="() => removeVariable(variable)"
:key="variable.name"
:aoi-script="aoiScript"
:hpml="hpml"
:name="variable.name"
:title="variable.name"
:draggable="true"
@ -100,8 +100,8 @@ import MkButton from '../../components/ui/button.vue';
import MkSelect from '../../components/ui/select.vue';
import MkSwitch from '../../components/ui/switch.vue';
import MkInput from '../../components/ui/input.vue';
import { blockDefs } from '../../scripts/aoiscript/index';
import { ASTypeChecker } from '../../scripts/aoiscript/type-checker';
import { blockDefs } from '../../scripts/hpml/index';
import { HpmlTypeChecker } from '../../scripts/hpml/type-checker';
import { url } from '../../config';
import { collectPageVars } from '../../scripts/collect-page-vars';
import { selectDriveFile } from '../../scripts/select-drive-file';
@ -145,7 +145,7 @@ export default Vue.extend({
alignCenter: false,
hideTitleWhenPinned: false,
variables: [],
aoiScript: null,
hpml: null,
script: '',
showOptions: false,
url,
@ -166,14 +166,14 @@ export default Vue.extend({
},
async created() {
this.aoiScript = new ASTypeChecker();
this.hpml = new HpmlTypeChecker();
this.$watch('variables', () => {
this.aoiScript.variables = this.variables;
this.hpml.variables = this.variables;
}, { deep: true });
this.$watch('content', () => {
this.aoiScript.pageVars = collectPageVars(this.content);
this.hpml.pageVars = collectPageVars(this.content);
}, { deep: true });
if (this.initPageId) {
@ -322,7 +322,7 @@ export default Vue.extend({
name = name.trim();
if (this.aoiScript.isUsedName(name)) {
if (this.hpml.isUsedName(name)) {
this.$root.dialog({
type: 'error',
text: this.$t('_pages.variableNameIsAlreadyUsed')

View File

@ -3,8 +3,9 @@ import { utils, values } from '@syuilo/aiscript';
export function createAiScriptEnv(vm, opts) {
let apiRequests = 0;
return {
USER_ID: values.STR(vm.$store.state.i.id),
USER_USERNAME: values.STR(vm.$store.state.i.username),
USER_ID: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.id) : values.NULL,
USER_NAME: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.name) : values.NULL,
USER_USERNAME: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.username) : values.NULL,
'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => {
await vm.$root.dialog({
type: type ? type.value : 'info',

View File

@ -1,153 +1,45 @@
import autobind from 'autobind-decorator';
import * as seedrandom from 'seedrandom';
import Chart from 'chart.js';
import * as tinycolor from 'tinycolor2';
import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.';
import { version } from '../../config';
import { AiScript, utils, parse, values } from '@syuilo/aiscript';
import { AiScript, utils, values } from '@syuilo/aiscript';
import { createAiScriptEnv } from '../create-aiscript-env';
// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
Chart.pluginService.register({
beforeDraw: function (chart, easing) {
if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
var ctx = chart.chart.ctx;
ctx.save();
ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
ctx.fillRect(0, 0, chart.chart.width, chart.chart.height);
ctx.restore();
}
}
});
import { collectPageVars } from '../collect-page-vars';
import { initLib } from './lib';
type Fn = {
slots: string[];
exec: (args: Record<string, any>) => ReturnType<ASEvaluator['evaluate']>;
exec: (args: Record<string, any>) => ReturnType<Hpml['evaluate']>;
};
/**
* AoiScript evaluator
* Hpml evaluator
*/
export class ASEvaluator {
export class Hpml {
private variables: Variable[];
private pageVars: PageVar[];
private envVars: Record<keyof typeof envVarsDef, any>;
public aiscript?: AiScript;
private pageVarUpdatedCallback;
public canvases: Record<string, HTMLCanvasElement> = {};
public vars: Record<string, any>;
public page: Record<string, any>;
private opts: {
randomSeed: string; visitor?: any; page?: any; url?: string;
randomSeed: string; visitor?: any; url?: string;
enableAiScript: boolean;
};
constructor(vm: any, variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) {
this.variables = variables;
this.pageVars = pageVars;
constructor(vm: any, page: Hpml['page'], opts: Hpml['opts']) {
this.page = page;
this.variables = this.page.variables;
this.pageVars = collectPageVars(this.page.content);
this.opts = opts;
if (this.opts.enableAiScript) {
this.aiscript = new AiScript({ ...createAiScriptEnv(vm, {
storageKey: 'pages:' + opts.page.id
}), ...{
'MkPages:updated': values.FN_NATIVE(([callback]) => {
this.pageVarUpdatedCallback = callback;
}),
'MkPages:get_canvas': values.FN_NATIVE(([id]) => {
utils.assertString(id);
const canvas = this.canvases[id.value];
const ctx = canvas.getContext('2d');
return values.OBJ(new Map([
['clear_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.clearRect(x.value, y.value, width.value, height.value) })],
['fill_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.fillRect(x.value, y.value, width.value, height.value) })],
['stroke_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.strokeRect(x.value, y.value, width.value, height.value) })],
['fill_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.fillText(text.value, x.value, y.value, width ? width.value : undefined) })],
['stroke_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.strokeText(text.value, x.value, y.value, width ? width.value : undefined) })],
['set_line_width', values.FN_NATIVE(([width]) => { ctx.lineWidth = width.value })],
['set_font', values.FN_NATIVE(([font]) => { ctx.font = font.value })],
['set_fill_style', values.FN_NATIVE(([style]) => { ctx.fillStyle = style.value })],
['set_stroke_style', values.FN_NATIVE(([style]) => { ctx.strokeStyle = style.value })],
['begin_path', values.FN_NATIVE(() => { ctx.beginPath() })],
['close_path', values.FN_NATIVE(() => { ctx.closePath() })],
['move_to', values.FN_NATIVE(([x, y]) => { ctx.moveTo(x.value, y.value) })],
['line_to', values.FN_NATIVE(([x, y]) => { ctx.lineTo(x.value, y.value) })],
['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value) })],
['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value) })],
['fill', values.FN_NATIVE(() => { ctx.fill() })],
['stroke', values.FN_NATIVE(() => { ctx.stroke() })],
]));
}),
'MkPages:chart': values.FN_NATIVE(([id, opts]) => {
utils.assertString(id);
utils.assertObject(opts);
const canvas = this.canvases[id.value];
const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
const chart = new Chart(canvas, {
type: opts.value.get('type').value,
data: {
labels: opts.value.get('labels').value.map(x => x.value),
datasets: opts.value.get('datasets').value.map(x => ({
label: x.value.has('label') ? x.value.get('label').value : '',
data: x.value.get('data').value.map(x => x.value),
pointRadius: 0,
lineTension: 0,
borderWidth: 2,
borderColor: x.value.has('color') ? x.value.get('color') : color,
backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(),
}))
},
options: {
responsive: false,
title: {
display: opts.value.has('title'),
text: opts.value.has('title') ? opts.value.get('title').value : '',
fontSize: 14,
},
layout: {
padding: {
left: 32,
right: 32,
top: opts.value.has('title') ? 16 : 32,
bottom: 16
}
},
legend: {
display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true,
position: 'bottom',
labels: {
boxWidth: 16,
}
},
tooltips: {
enabled: false,
},
chartArea: {
backgroundColor: '#fff'
},
...(opts.value.get('type').value === 'radar' ? {
scale: {
ticks: {
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
},
pointLabels: {
fontSize: 12
}
}
} : {
scales: {
yAxes: [{
ticks: {
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
}
}]
}
})
}
});
}),
}}, {
storageKey: 'pages:' + this.page.id
}), ...initLib(this)}, {
in: (q) => {
return new Promise(ok => {
vm.$root.dialog({
@ -164,6 +56,10 @@ export class ASEvaluator {
log: (type, params) => {
},
});
this.aiscript.scope.opts.onUpdated = (name, value) => {
this.eval();
};
}
const date = new Date();
@ -171,7 +67,7 @@ export class ASEvaluator {
this.envVars = {
AI: 'kawaii',
VERSION: version,
URL: opts.page ? `${opts.url}/@${opts.page.user.username}/pages/${opts.page.name}` : '',
URL: this.page ? `${opts.url}/@${this.page.user.username}/pages/${this.page.name}` : '',
LOGIN: opts.visitor != null,
NAME: opts.visitor ? opts.visitor.name || opts.visitor.username : '',
USERNAME: opts.visitor ? opts.visitor.username : '',
@ -185,8 +81,36 @@ export class ASEvaluator {
AISCRIPT_DISABLED: !this.opts.enableAiScript,
NULL: null
};
this.eval();
}
@autobind
public eval() {
try {
this.vars = this.evaluateVars();
} catch (e) {
//this.onError(e);
}
}
@autobind
public interpolate(str: string) {
if (str == null) return null;
return str.replace(/{(.+?)}/g, match => {
const v = this.vars ? this.vars[match.slice(1, -1).trim()] : null;
return v == null ? 'NULL' : v.toString();
});
}
@autobind
public callAiScript(fn: string) {
try {
if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []);
} catch (e) {}
}
@autobind
public registerCanvas(id: string, canvas: any) {
this.canvases[id] = canvas;
}
@ -200,7 +124,7 @@ export class ASEvaluator {
if (this.aiscript) this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]);
}
} else {
throw new AoiScriptError(`No such page var '${name}'`);
throw new HpmlError(`No such page var '${name}'`);
}
}
@ -211,7 +135,7 @@ export class ASEvaluator {
}
@autobind
private interpolate(str: string, scope: Scope) {
private _interpolate(str: string, scope: Scope) {
return str.replace(/{(.+?)}/g, match => {
const v = scope.getState(match.slice(1, -1).trim());
return v == null ? 'NULL' : v.toString();
@ -248,11 +172,11 @@ export class ASEvaluator {
}
if (block.type === 'text' || block.type === 'multiLineText') {
return this.interpolate(block.value || '', scope);
return this._interpolate(block.value || '', scope);
}
if (block.type === 'textList') {
return this.interpolate(block.value || '', scope).trim().split('\n');
return this._interpolate(block.value || '', scope).trim().split('\n');
}
if (block.type === 'ref') {
@ -367,14 +291,14 @@ export class ASEvaluator {
const fnName = block.type;
const fn = (funcs as any)[fnName];
if (fn == null) {
throw new AoiScriptError(`No such function '${fnName}'`);
throw new HpmlError(`No such function '${fnName}'`);
} else {
return fn(...block.args.map(x => this.evaluate(x, scope)));
}
}
}
class AoiScriptError extends Error {
class HpmlError extends Error {
public info?: any;
constructor(message: string, info?: any) {
@ -384,7 +308,7 @@ class AoiScriptError extends Error {
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, AoiScriptError);
Error.captureStackTrace(this, HpmlError);
}
}
}
@ -417,7 +341,7 @@ class Scope {
}
}
throw new AoiScriptError(
throw new HpmlError(
`No such variable '${name}' in scope '${this.name}'`, {
scope: this.layerdStates
});

View File

@ -1,5 +1,5 @@
/**
* AoiScript
* Hpml
*/
import {

View File

@ -0,0 +1,124 @@
import * as tinycolor from 'tinycolor2';
import Chart from 'chart.js';
import { Hpml } from './evaluator';
import { values, utils } from '@syuilo/aiscript';
// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
Chart.pluginService.register({
beforeDraw: (chart, easing) => {
if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
const ctx = chart.chart.ctx;
ctx.save();
ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
ctx.fillRect(0, 0, chart.chart.width, chart.chart.height);
ctx.restore();
}
}
});
export function initLib(hpml: Hpml) {
return {
'MkPages:updated': values.FN_NATIVE(([callback]) => {
hpml.pageVarUpdatedCallback = callback;
}),
'MkPages:get_canvas': values.FN_NATIVE(([id]) => {
utils.assertString(id);
const canvas = hpml.canvases[id.value];
const ctx = canvas.getContext('2d');
return values.OBJ(new Map([
['clear_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.clearRect(x.value, y.value, width.value, height.value) })],
['fill_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.fillRect(x.value, y.value, width.value, height.value) })],
['stroke_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.strokeRect(x.value, y.value, width.value, height.value) })],
['fill_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.fillText(text.value, x.value, y.value, width ? width.value : undefined) })],
['stroke_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.strokeText(text.value, x.value, y.value, width ? width.value : undefined) })],
['set_line_width', values.FN_NATIVE(([width]) => { ctx.lineWidth = width.value })],
['set_font', values.FN_NATIVE(([font]) => { ctx.font = font.value })],
['set_fill_style', values.FN_NATIVE(([style]) => { ctx.fillStyle = style.value })],
['set_stroke_style', values.FN_NATIVE(([style]) => { ctx.strokeStyle = style.value })],
['begin_path', values.FN_NATIVE(() => { ctx.beginPath() })],
['close_path', values.FN_NATIVE(() => { ctx.closePath() })],
['move_to', values.FN_NATIVE(([x, y]) => { ctx.moveTo(x.value, y.value) })],
['line_to', values.FN_NATIVE(([x, y]) => { ctx.lineTo(x.value, y.value) })],
['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value) })],
['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value) })],
['fill', values.FN_NATIVE(() => { ctx.fill() })],
['stroke', values.FN_NATIVE(() => { ctx.stroke() })],
]));
}),
'MkPages:chart': values.FN_NATIVE(([id, opts]) => {
utils.assertString(id);
utils.assertObject(opts);
const canvas = hpml.canvases[id.value];
const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
Chart.defaults.global.defaultFontColor = '#555';
const chart = new Chart(canvas, {
type: opts.value.get('type').value,
data: {
labels: opts.value.get('labels').value.map(x => x.value),
datasets: opts.value.get('datasets').value.map(x => ({
label: x.value.has('label') ? x.value.get('label').value : '',
data: x.value.get('data').value.map(x => x.value),
pointRadius: 0,
lineTension: 0,
borderWidth: 2,
borderColor: x.value.has('color') ? x.value.get('color') : color,
backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(),
}))
},
options: {
responsive: false,
devicePixelRatio: 1.5,
title: {
display: opts.value.has('title'),
text: opts.value.has('title') ? opts.value.get('title').value : '',
fontSize: 14,
},
layout: {
padding: {
left: 32,
right: 32,
top: opts.value.has('title') ? 16 : 32,
bottom: 16
}
},
legend: {
display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true,
position: 'bottom',
labels: {
boxWidth: 16,
}
},
tooltips: {
enabled: false,
},
chartArea: {
backgroundColor: '#fff'
},
...(opts.value.get('type').value === 'radar' ? {
scale: {
ticks: {
display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false,
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
maxTicksLimit: 8,
},
pointLabels: {
fontSize: 12
}
}
} : {
scales: {
yAxes: [{
ticks: {
display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true,
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
}
}]
}
})
}
});
})
};
}

View File

@ -8,13 +8,13 @@ type TypeError = {
};
/**
* AoiScript type checker
* Hpml type checker
*/
export class ASTypeChecker {
export class HpmlTypeChecker {
public variables: Variable[];
public pageVars: PageVar[];
constructor(variables: ASTypeChecker['variables'] = [], pageVars: ASTypeChecker['pageVars'] = []) {
constructor(variables: HpmlTypeChecker['variables'] = [], pageVars: HpmlTypeChecker['pageVars'] = []) {
this.variables = variables;
this.pageVars = pageVars;
}

View File

@ -1,12 +1,13 @@
import DriveWindow from '../components/drive-window.vue';
export function selectDriveFile($root: any, multiple) {
return new Promise((res, rej) => {
const w = $root.new(DriveWindow, {
multiple
});
w.$once('selected', files => {
res(multiple ? files : files[0]);
import('../components/drive-window.vue').then(m => m.default).then(dialog => {
const w = $root.new(dialog, {
type: 'file',
multiple
});
w.$once('selected', files => {
res(multiple ? files : files[0]);
});
});
});
}

View File

@ -0,0 +1,13 @@
export function selectDriveFolder($root: any, multiple) {
return new Promise((res, rej) => {
import('../components/drive-window.vue').then(m => m.default).then(dialog => {
const w = $root.new(dialog, {
type: 'folder',
multiple
});
w.$once('selected', folders => {
res(multiple ? folders : (folders.length === 0 ? null : folders[0]));
});
});
});
}

View File

@ -43,7 +43,7 @@ export async function checkHitAntenna(antenna: Antenna, note: Note, noteUser: Us
if (note.text == null) return false;
const matched = antenna.keywords.some(keywords =>
keywords.every(keyword =>
keywords.filter(keyword => keyword !== '').every(keyword =>
antenna.caseSensitive
? note.text!.includes(keyword)
: note.text!.toLowerCase().includes(keyword.toLowerCase())
@ -56,7 +56,7 @@ export async function checkHitAntenna(antenna: Antenna, note: Note, noteUser: Us
if (note.text == null) return false;
const matched = antenna.excludeKeywords.some(keywords =>
keywords.every(keyword =>
keywords.filter(keyword => keyword !== '').every(keyword =>
antenna.caseSensitive
? note.text!.includes(keyword)
: note.text!.toLowerCase().includes(keyword.toLowerCase())

View File

@ -25,7 +25,7 @@ export const meta = {
},
category: {
validator: $.optional.str
validator: $.optional.nullable.str
},
aliases: {

View File

@ -4,10 +4,7 @@
import * as fs from 'fs';
import * as webpack from 'webpack';
import * as chalk from 'chalk';
const { VueLoaderPlugin } = require('vue-loader');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
class WebpackOnBuildPlugin {
constructor(readonly callback: (stats: any) => void) {
@ -118,10 +115,7 @@ module.exports = {
}]
},
plugins: [
new ProgressBarPlugin({
format: chalk` {cyan.bold Yes we can} {bold [}:bar{bold ]} {green.bold :percent} {gray :elapseds}`,
clear: false
}),
new webpack.ProgressPlugin({}),
new webpack.DefinePlugin({
_VERSION_: JSON.stringify(meta.version),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]: [string, any]) => [k, v._lang_])),
@ -135,7 +129,6 @@ module.exports = {
output: {
path: __dirname + '/built/client/assets',
filename: `[name].${meta.version}.js`,
chunkFilename: '[hash:5].[id].js',
publicPath: `/assets/`
},
resolve: {
@ -149,13 +142,13 @@ module.exports = {
resolveLoader: {
modules: ['node_modules']
},
externals: {
moment: 'moment'
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
optimization: {
minimizer: [new TerserPlugin()]
},
cache: true,
devtool: false, //'source-map',
mode: isProduction ? 'production' : 'development'
};

715
yarn.lock

File diff suppressed because it is too large Load Diff