Compare commits

..

1 commit

Author SHA1 Message Date
555699635f chore: fix backend activitypub.ts lints and logic
lints: mostly function signatures
logic: remote user logic is incorrect and overly lengthy,
       public key checks are leaky
TODO:  default export lint
2022-08-17 19:47:09 -04:00
95 changed files with 8921 additions and 448 deletions

View file

@ -15,24 +15,14 @@ For older Misskey versions, see [CHANGELOG-OLD.md](./CHANGELOG-OLD.md).
### Changed
- Client: Use consistent date formatting based on language setting
- Client: Add threshold to reduce occurances of "future" timestamps
- Pages have been considerably simplified, several of the very complex features have been removed.
Pages are now MFM only.
**For admins:** There is a migration in place to convert page contents to text, but not everything can be migrated.
You might want to check if you have any more complex pages on your instance and ask users to migrate them by hand.
Or generally advise all users to simplify their pages to only text.
### Removed
- Okteto config and Helm chart
- Client: acrylic styling
- Client: Twitter embeds, the standard URL preview is used instead.
- Promotion entities and endpoints
### Fixed
- Server: Blocking remote accounts
### Security
- Server: Update `multer` dependency to resolve [CVE-2022-24434](https://nvd.nist.gov/vuln/detail/CVE-2022-24434)
## 13.0.0-preview1 - 2022-08-05
### Added
- Server: Replies can now be fetched recursively.

View file

@ -465,6 +465,8 @@ dayOverDayChanges: "يوميا"
appearance: "المظهر"
clientSettings: "إعدادات العميل"
accountSettings: "إعدادات الحساب"
promotion: "ترقية"
promote: "روِّج"
numberOfDays: "عدد الأيام"
hideThisNote: "إخفاء هذه الملاحظة"
showFeaturedNotesInTimeline: "أظهر الملاحظات الشائعة في الخيط الزمني"
@ -529,6 +531,7 @@ poll: "استطلاع رأي"
useCw: "إخفاء المحتوى"
enablePlayer: "افتح مشغل الفيديو"
disablePlayer: "أغلق مشغل الفيديو"
expandTweet: "وسّع التغريدة"
themeEditor: "مصمم القوالب"
description: "الوصف"
describeFile: "أضف تعليقًا توضيحيًا"
@ -1229,6 +1232,7 @@ _pages:
liked: "الصفحات المُعجب بها"
featured: "الأكثر شعبية"
contents: "المحتوى"
variables: "متغيّرات"
title: "العنوان"
url: "رابط الصفحة"
summary: "ملخص الصفحة"
@ -1239,6 +1243,238 @@ _pages:
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "عيّن صورة مصغّرة"
eyeCatchingImageRemove: "احذف صورة مصغّرة"
chooseBlock: "إضافة كتلة"
selectType: "اختر النوع"
enterVariableName: "أدخل اسم المتغيّر"
variableNameIsAlreadyUsed: "هذا الاسم محجوز"
contentBlocks: "المحتوى"
inputBlocks: "مُدخل"
specialBlocks: "خاص"
blocks:
text: "نص"
textarea: "حقل نصي"
section: "قسم"
image: "الصور"
button: "زرّ"
_if:
variable: "متغيّر"
post: "أنشئ ملاحظة"
_post:
text: "المحتوى"
textInput: "مُدخل نصي"
_textInput:
name: "اسم المتغير"
text: "العنوان"
default: "القيمة الافتراضية"
textareaInput: "مدخل نصي متعدد الأسطر"
_textareaInput:
name: "اسم المتغير"
text: "العنوان"
default: "القيمة الافتراضية"
numberInput: "مُدخل رقمي"
_numberInput:
name: "اسم المتغير"
text: "العنوان"
default: "القيمة الافتراضية"
_canvas:
width: "العُرض"
height: "الإرتفاع"
note: "ملاحظة مضمّنة"
_note:
id: "معرّف الملاحظة"
idDescription: "كبديل يمكنك إدخال رابك الملاحظة هنا"
detailed: "عرض مفصّل"
switch: "بدّل"
_switch:
name: "اسم المتغير"
text: "العنوان"
default: "القيمة الافتراضية"
counter: "العداد"
_counter:
name: "اسم المتغير"
text: "العنوان"
inc: "زِد"
_button:
text: "العنوان"
colored: "ملوّن"
action: "الإجراء عند ضغط الزّر"
_action:
dialog: "أظهر مربع حوار"
_dialog:
content: "المحتوى"
resetRandom: "صفِّر البذرة"
pushEvent: "أرسل حدثًا"
_pushEvent:
event: "اسم الحدث"
message: "إظهار رسالة عند التفعيل"
variable: "أرسل المتغيّر"
no-variable: "لا شيء"
_callAiScript:
functionName: "اسم الدالة"
radioButton: "الخيار "
_radioButton:
name: "اسم المتغير"
title: "العنوان"
values: "قائمة الخيارات (كل خيار في سطر لوحده)"
default: "القيمة الافتراضية"
script:
categories:
logical: "عمليّة منطقيّة"
operation: "حساب"
comparison: "مقارنة"
random: "عشوائي"
value: "القيم"
fn: "دوال"
text: "إجراءات على النصوص"
convert: "تحويل"
list: "القوائم"
blocks:
text: "نص"
textList: "قائمة نصية"
_textList:
info: "اجعل كل مدخل في سطر لوحده"
strLen: "طول النص"
_strLen:
arg1: "نص"
strPick: "استخرج محرفًا"
_strPick:
arg1: "نص"
arg2: "موضع المحرف"
strReplace: "استبدال النّص"
_strReplace:
arg1: "نص"
arg2: "استُبدِل بـ"
arg3: "استُبدِل بـ"
strReverse: "اقلب النص"
_strReverse:
arg1: "نص"
_join:
arg1: "القوائم"
arg2: "فاصل"
add: "إضافة"
_add:
arg1: "أ"
arg2: "ب"
subtract: "اطرح"
_subtract:
arg1: "أ"
arg2: "ب"
multiply: "اضرب"
_multiply:
arg1: "أ"
arg2: "ب"
divide: "اقسم"
_divide:
arg1: "أ"
arg2: "ب"
mod: "الباقي"
_mod:
arg1: "أ"
arg2: "ب"
round: "تقريب عدد عشري"
_round:
arg1: "رقم"
eq: "أ و ب متساويان"
_eq:
arg1: "أ"
arg2: "ب"
notEq: "أ و ب مختلفان"
_notEq:
arg1: "أ"
arg2: "ب"
and: "أ و ب"
_and:
arg1: "أ"
arg2: "ب"
or: "أ أو ب"
_or:
arg1: "أ"
arg2: "ب"
lt: "أ أصغر من ب"
_lt:
arg1: "أ"
arg2: "ب"
gt: "أ أكبر من ب"
_gt:
arg1: "أ"
arg2: "ب"
ltEq: "أ أصغر من أو يساوي ب"
_ltEq:
arg1: "أ"
arg2: "ب"
gtEq: "أ أكبر من أو يساوي ب"
_gtEq:
arg1: "أ"
arg2: "ب"
if: "فرع"
random: "عشوائي"
rannum: "رقم عشوائي"
_rannum:
arg1: "أدنى قيمة"
arg2: "أقصى قيمة"
randomPick: "اختر عشوائيًا من القائمة"
_randomPick:
arg1: "القوائم"
dailyRandom: "عشوائي (يتغير مرة يوميًا لكل مستخدم)"
dailyRannum: "رقم عشوائي (يتغير مرة يوميًا لكل مستخدم)"
_dailyRannum:
arg1: "أدنى قيمة"
arg2: "أقصى قيمة"
dailyRandomPick: "اختيار عشوائي من قائمة (يتغير مرة يوميًا لكل مستخدم)"
_dailyRandomPick:
arg1: "القوائم"
seedRandom: "عشوائي (عبر بذرة)"
_seedRandom:
arg1: "البذرة"
seedRannum: "رقم عشوائي (عبر بذرة)"
_seedRannum:
arg1: "البذرة"
arg2: "أدنى قيمة"
arg3: "أقصى قيمة"
seedRandomPick: "اختيار عشوائي من القائمة (عبر بذرة)"
_seedRandomPick:
arg1: "البذرة"
arg2: "القوائم"
DRPWPM: "اختيار عشوائي من قائمة الاحتمالات (تتغير مرة يوميًا لكل مستخدم)"
_DRPWPM:
arg1: "قائمة نصية"
pick: "اختر من القائمة"
_pick:
arg1: "القوائم"
arg2: "الموضع"
listLen: "طول القائمة"
_listLen:
arg1: "القوائم"
number: "رقم"
stringToNumber: "حوّل نصًا إلى رقم"
_stringToNumber:
arg1: "نص"
numberToString: "حوّل رقمًا إلى نص"
_numberToString:
arg1: "رقم"
_splitStrByLine:
arg1: "نص"
ref: "متغيّر"
aiScriptVar: "متغيّر AiScript"
fn: "دالة"
_fn:
slots: "خانات"
arg1: "المُخرج"
for: "حلقة تكرار"
_for:
arg1: "عدد مرات التكرار"
arg2: "الإجراء"
typeError: "الخانة {slot} تقبل \"{expect}\" لكن القيمة المعطاة هي \"{actual}\"!"
thereIsEmptySlot: "الخانة {slot} فارغة!"
types:
string: "نص"
number: "رقم"
array: "القوائم"
stringArray: "قائمة نصية"
emptySlot: "خانة فارغة"
enviromentVariables: "متغيرات البيئة"
pageVariables: "متغيرات الصفحة"
argVariables: "خانة إدخال"
_relayStatus:
requesting: "مُعلّق"
accepted: "مقبول"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "গতকাল"
appearance: "অবয়ব"
clientSettings: "ক্লায়েন্ট সেটিংস"
accountSettings: "অ্যাকাউন্ট সেটিংস"
promotion: "প্রমোশন"
promote: "প্রচার করুন"
numberOfDays: "দিনের সংখ্যা"
hideThisNote: "নোটটি লুকান"
showFeaturedNotesInTimeline: "টাইমলাইনে সুপারিশকৃত নোটগুলি দেখান"
@ -547,6 +549,7 @@ poll: "জরিপ"
useCw: "কন্টেন্ট লুকান"
enablePlayer: "ভিডিও প্লেয়ার খুলুন"
disablePlayer: "ভিডিও প্লেয়ার বন্ধ করুন"
expandTweet: "টুইট বিস্তারিত করুন"
themeEditor: "থিম সম্পাদক"
description: "বর্ণনা"
describeFile: "ক্যাপশন যোগ করুন"
@ -1321,7 +1324,10 @@ _pages:
my: "আমার পৃষ্ঠাগুলি"
liked: "পছন্দ করা পৃষ্ঠাগুলি"
featured: "জনপ্রিয়"
inspector: "ইনিস্পেক্টর"
contents: "বিষয়বস্তু"
content: "পৃষ্ঠার ব্লক"
variables: "চলকগুলি"
title: "শিরোনাম"
url: "পৃষ্ঠার URL"
summary: "পৃষ্ঠার বর্ণনা"
@ -1332,6 +1338,262 @@ _pages:
fontSansSerif: "স্যান্স সেরিফ"
eyeCatchingImageSet: "থাম্বনেইল সেট করুন"
eyeCatchingImageRemove: "থাম্বনেইল সরান"
chooseBlock: "ব্লক যোগ করুন"
selectType: "ধরন নির্বাচন করুন"
enterVariableName: "চলকের নাম লিখুন"
variableNameIsAlreadyUsed: "চলকের নামটি ইতিপূর্বে ব্যাবহৃত হয়েছে"
contentBlocks: "বিষয়বস্তু"
inputBlocks: "ইনপুট"
specialBlocks: "বিশেষ"
blocks:
text: "লেখা"
textarea: "টেক্সট এরিয়া"
section: "বিভাগ"
image: "ছবি"
button: "বাটন"
if: "যদি"
_if:
variable: "চলকগুলি"
post: "নোট লিখুন"
_post:
text: "বিষয়বস্তু"
attachCanvasImage: "ক্যানভাস ছবিসহ পোস্ট করুন"
canvasId: "ক্যানভাস ID"
textInput: "টেক্সট ইনপুট"
_textInput:
name: "চলকের নাম"
text: "শিরোনাম"
default: "ডিফল্ট মান"
textareaInput: "একাধিক লাইনের টেক্সট ইনপুট"
_textareaInput:
name: "চলকের নাম"
text: "শিরোনাম"
default: "ডিফল্ট মান"
numberInput: "সংখ্যা ইনপুট"
_numberInput:
name: "চলকের নাম"
text: "শিরোনাম"
default: "ডিফল্ট মান"
canvas: "ক্যানভাস"
_canvas:
id: "ক্যানভাস ID"
width: "প্রস্থ"
height: "উচ্চতা"
note: "এম্বেড নোট"
_note:
id: "নোট ID"
idDescription: "আপনি এর বদলে নোটের URL পেস্ট করতে পারেন."
detailed: "বিস্তারিত দেখুন"
switch: "সুইচ"
_switch:
name: "চলকের নাম"
text: "শিরোনাম"
default: "ডিফল্ট মান"
counter: "কাউন্টার"
_counter:
name: "চলকের নাম"
text: "শিরোনাম"
inc: "এভাবে মান বাড়ান"
_button:
text: "শিরোনাম"
colored: "রঙ্গিন"
action: "বাটনে ক্লিক করলে যা হবে"
_action:
dialog: "ডায়ালগ দেখান "
_dialog:
content: "বিষয়বস্তু"
resetRandom: "র‍্যানডম সিড রিসেট করুন"
pushEvent: "ইভেন্ট পাঠান"
_pushEvent:
event: "ইভেন্টের নাম"
message: "চালু হলে প্রদর্শনের জন্য বার্তা"
variable: "পাঠানো চলক"
no-variable: "কিছুই না"
callAiScript: "AiScript চালান"
_callAiScript:
functionName: "ফাংশনের নাম"
radioButton: "বহুনির্বাচনী"
_radioButton:
name: "চলকের নাম"
title: "শিরোনাম"
values: "বিকল্পগুলিকে আলাদা লাইনে লিখুন"
default: "ডিফল্ট মান"
script:
categories:
flow: "নিয়ন্ত্রণ"
logical: "লজিক্যাল অপারেশন"
operation: "হিসাব-নিকাশ"
comparison: "তুলনা"
random: "র‍্যান্ডম"
value: "মান"
fn: "ফাংশন"
text: "টেক্সট ম্যানিপুলেশন"
convert: "রুপান্তর"
list: "লিস্ট"
blocks:
text: "লেখা"
multiLineText: "লেখা (একাধিক লাইন)"
textList: "লেখার লিস্ট"
_textList:
info: "প্রতিটি এন্ট্রিকে আলাদা লাইনে লিখুন"
strLen: "লেখার দৈর্ঘ্য"
_strLen:
arg1: "লেখা"
strPick: "অক্ষর বের করে আনুন"
_strPick:
arg1: "লেখা"
arg2: "অক্ষরের অবস্থান"
strReplace: "লেখা প্রতিস্থাপন"
_strReplace:
arg1: "লেখা"
arg2: "যে লেখা প্রতিস্থাপন করা হবে"
arg3: "যা দ্বারা প্রতিস্থাপন করা হবে"
strReverse: "লেখা উল্টান"
_strReverse:
arg1: "লেখা"
join: "লেখা যুক্ত করুন"
_join:
arg1: "লিস্ট"
arg2: "বিভাজক"
add: "যোগ"
_add:
arg1: "A"
arg2: "B"
subtract: "বিয়োগ"
_subtract:
arg1: "A"
arg2: "B"
multiply: "গুন"
_multiply:
arg1: "A"
arg2: "B"
divide: "ভাগ"
_divide:
arg1: "A"
arg2: "B"
mod: "ভাগশেষ"
_mod:
arg1: "A"
arg2: "B"
round: "দশমিক রাউন্ড করুন"
_round:
arg1: "সংখ্যা"
eq: "A ও B সমান"
_eq:
arg1: "A"
arg2: "B"
notEq: "A ও B সমান না"
_notEq:
arg1: "A"
arg2: "B"
and: "A এবং B"
_and:
arg1: "A"
arg2: "B"
or: "A অথবা B"
_or:
arg1: "A"
arg2: "B"
lt: "< A , B হতে কম"
_lt:
arg1: "A"
arg2: "B"
gt: "> A , B হতে বেশী"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A , B হতে কম বা সমান"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A , B হতে বেশী বা সমান"
_gtEq:
arg1: "A"
arg2: "B"
if: "যদি"
_if:
arg1: "যদি"
arg2: "তাহলে"
arg3: "তাছাড়া"
not: "না"
_not:
arg1: "না"
random: "র‍্যান্ডম"
_random:
arg1: "সম্ভাব্যতা"
rannum: "র‍্যানডম সংখ্যা"
_rannum:
arg1: "ন্যূনতম মান"
arg2: "সর্বোচ্চ মান"
randomPick: "তালিকা থেকে দৈবচয়ন করুন"
_randomPick:
arg1: "লিস্ট"
dailyRandom: "র‍্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
_dailyRandom:
arg1: "সম্ভাব্যতা"
dailyRannum: "র‍্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
_dailyRannum:
arg1: "ন্যূনতম মান"
arg2: "সর্বোচ্চ মান"
dailyRandomPick: "তালিকা থেকে এলোমেলোভাবে নির্বাচন করুন (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
_dailyRandomPick:
arg1: "লিস্ট"
seedRandom: "র‍্যানডম (সীড দ্বারা)"
_seedRandom:
arg1: "সীড"
arg2: "সম্ভাব্যতা"
seedRannum: "র‍্যানডম সংখ্যা (সীড দ্বারা)"
_seedRannum:
arg1: "সীড"
arg2: "ন্যূনতম মান"
arg3: "সর্বোচ্চ মান"
seedRandomPick: "তালিকা থেকে দৈবচয়ন করুন (সীড দ্বারা)"
_seedRandomPick:
arg1: "সীড"
arg2: "লিস্ট"
DRPWPM: "সম্ভাব্যতা সহ একটি তালিকা থেকে এলোমেলোভাবে নির্বাচন করুন (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন)"
_DRPWPM:
arg1: "লেখার লিস্ট"
pick: "তালিকা থেকে নির্বাচন করুন"
_pick:
arg1: "লিস্ট"
arg2: "অবস্থান"
listLen: "লিস্টের দৈর্ঘ্য পান"
_listLen:
arg1: "লিস্ট"
number: "সংখ্যা"
stringToNumber: "পাঠ্য থেকে সংখ্যা"
_stringToNumber:
arg1: "লেখা"
numberToString: "সংখ্যা থেকে পাঠ্য"
_numberToString:
arg1: "সংখ্যা"
splitStrByLine: "পাঠ্যকে লাইনে বিভক্ত করুন"
_splitStrByLine:
arg1: "লেখা"
ref: "চলক"
aiScriptVar: "AiScript চলক"
fn: "ফাংশন"
_fn:
slots: "স্লটগুলি"
slots-info: "প্রতিটি স্লটকে আলাদা লাইনে লিখুন"
arg1: "আউটপুট"
for: "for-লুপ"
_for:
arg1: "কতবার চলবে"
arg2: "অ্যাকশন"
typeError: "স্লট {slot}, {expect} ধরনের মান গ্রহণ করে, কিন্তু {actual} ধরনের মান দেওয়া হয়েছে!"
thereIsEmptySlot: "স্লট {slot} খালি!"
types:
string: "লেখা"
number: "সংখ্যা"
boolean: "ফ্ল্যাগ"
array: "লিস্ট"
stringArray: "লেখার লিস্ট"
emptySlot: "খালি স্লট"
enviromentVariables: "এনভাইরনমেন্ট ভ্যারিয়েবল"
pageVariables: "পেজের চলক"
argVariables: "ইনপুটের জায়গা"
_relayStatus:
requesting: "অপেক্ষমান"
accepted: "অনুমোদিত"

View file

@ -147,6 +147,25 @@ _profile:
_exportOrImport:
followingList: "Seguint"
userLists: "Llistes"
_pages:
script:
categories:
list: "Llistes"
blocks:
_join:
arg1: "Llistes"
_randomPick:
arg1: "Llistes"
_dailyRandomPick:
arg1: "Llistes"
_seedRandomPick:
arg2: "Llistes"
_pick:
arg1: "Llistes"
_listLen:
arg1: "Llistes"
types:
array: "Llistes"
_notification:
youWereFollowed: "t'ha seguit"
_types:

View file

@ -406,6 +406,8 @@ dayOverDayChanges: "Denně"
appearance: "Vzhled"
clientSettings: "Nastavení klienta"
accountSettings: "Nastavení účtu"
promotion: "Propagace"
promote: "Propagovat"
numberOfDays: "Počet dní"
deleteAll: "Smazat vše"
showFixedPostForm: "Zobrazit formulář pro nové příspěvky nad časovou osou"
@ -510,6 +512,27 @@ _charts:
federation: "Federace"
_timelines:
home: "Domů"
_pages:
blocks:
image: "Obrázky"
script:
categories:
list: "Seznamy"
blocks:
_join:
arg1: "Seznamy"
_randomPick:
arg1: "Seznamy"
_dailyRandomPick:
arg1: "Seznamy"
_seedRandomPick:
arg2: "Seznamy"
_pick:
arg1: "Seznamy"
_listLen:
arg1: "Seznamy"
types:
array: "Seznamy"
_notification:
youWereFollowed: "Máte nového následovníka"
youWereInvitedToGroup: "Pozvat do skupiny"

View file

@ -473,6 +473,8 @@ dayOverDayChanges: "Veränderung zu Gestern"
appearance: "Aussehen"
clientSettings: "Client-Einstellungen"
accountSettings: "Benutzerkonto-Einstellungen"
promotion: "Werbung"
promote: "Werbung schalten"
numberOfDays: "Anzahl der Tage"
hideThisNote: "Diese Notiz verstecken"
showFeaturedNotesInTimeline: "Beliebte Notizen in der Chronik anzeigen"
@ -548,6 +550,7 @@ poll: "Umfrage"
useCw: "Inhaltswarnung verwenden"
enablePlayer: "Video-Player öffnen"
disablePlayer: "Video-Player schließen"
expandTweet: "Tweet ausklappen"
themeEditor: "Farbschema-Editor"
description: "Beschreibung"
describeFile: "Beschreibung hinzufügen"
@ -1324,7 +1327,10 @@ _pages:
my: "Meine Seiten"
liked: "Seiten, die mir gefallen"
featured: "Beliebt"
inspector: "Inspektor"
contents: "Inhalte"
content: "Seitenblock"
variables: "Variablen"
title: "Titel"
url: "Seiten-URL"
summary: "Zusammenfassung"
@ -1335,6 +1341,262 @@ _pages:
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Vorschaubild festlegen"
eyeCatchingImageRemove: "Vorschaubild entfernen"
chooseBlock: "Block hinzufügen"
selectType: "Typ auswählen"
enterVariableName: "Gib einen Variablennamen ein"
variableNameIsAlreadyUsed: "Dieser Name wird bereits von einer anderen Variable verwendet"
contentBlocks: "Inhalt"
inputBlocks: "Eingabe"
specialBlocks: "Spezial"
blocks:
text: "Text"
textarea: "Textfeld"
section: "Abschnitt"
image: "Bild"
button: "Knopf"
if: "Falls"
_if:
variable: "Variable"
post: "Notizfenster"
_post:
text: "Inhalt"
attachCanvasImage: "Leinwandbild anfügen"
canvasId: "Leinwand-ID"
textInput: "Texteingabe"
_textInput:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
textareaInput: "Mehrzeiliges Texteingabefeld"
_textareaInput:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
numberInput: "Zahleneingabe"
_numberInput:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
canvas: "Leinwand"
_canvas:
id: "Leinwand-ID"
width: "Breite"
height: "Höhe"
note: "Eingebettete Notiz"
_note:
id: "Notiz-ID"
idDescription: "Du kannst alternativ auch die Notiz-URL angeben."
detailed: "Detailierte Ansicht"
switch: "Fallunterscheidung"
_switch:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
counter: "Zähler"
_counter:
name: "Variablenname"
text: "Titel"
inc: "Schrittgröße"
_button:
text: "Titel"
colored: "Farbig"
action: "Aktion, die bei Knopfdruck ausgeführt wird"
_action:
dialog: "Dialogfenster anzeigen"
_dialog:
content: "Inhalt"
resetRandom: "Zufallswert zurücksetzen"
pushEvent: "Ein Event senden"
_pushEvent:
event: "Eventname"
message: "Nachricht, die bei Auslösung des Events angezeigt werden soll"
variable: "Variable, die gesendet werden soll"
no-variable: "Keine"
callAiScript: "AiScript ausführen"
_callAiScript:
functionName: "Funktionsname"
radioButton: "Optionsfeld"
_radioButton:
name: "Variablenname"
title: "Titel"
values: "Durch Zeilenümbrüche getrennte Auswahlmöglichkeiten"
default: "Standardwert"
script:
categories:
flow: "Steuerung"
logical: "Logische Operationen"
operation: "Berechnungen"
comparison: "Vergleiche"
random: "Zufällig"
value: "Werte"
fn: "Funktionen"
text: "Textoperationen"
convert: "Konvertierungen"
list: "Listen"
blocks:
text: "Text"
multiLineText: "Text (Mehrzeilig)"
textList: "Textliste"
_textList:
info: "Trenne jeden Eintrag mit einem Zeilenumbruch"
strLen: "Textlänge"
_strLen:
arg1: "Text"
strPick: "Text extrahieren"
_strPick:
arg1: "Text"
arg2: "Textposition"
strReplace: "Textersetzung"
_strReplace:
arg1: "Text"
arg2: "Zu ersetzender Text"
arg3: "Ersetzen mit"
strReverse: "Text umkehren"
_strReverse:
arg1: "Text"
join: "Text zusammenfügen"
_join:
arg1: "Liste"
arg2: "Trennzeichen"
add: "Addieren"
_add:
arg1: "A"
arg2: "B"
subtract: "Subtrahieren"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiplizieren"
_multiply:
arg1: "A"
arg2: "B"
divide: "Teilen"
_divide:
arg1: "A"
arg2: "B"
mod: "Rest"
_mod:
arg1: "A"
arg2: "B"
round: "Rundung von Dezimalstellen"
_round:
arg1: "Nummer"
eq: "A und B sind gleich"
_eq:
arg1: "A"
arg2: "B"
notEq: "A und B sind nicht gleich"
_notEq:
arg1: "A"
arg2: "B"
and: "A UND B"
_and:
arg1: "A"
arg2: "B"
or: "A ODER B"
_or:
arg1: "A"
arg2: "B"
lt: "< A ist kleiner als B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A ist größer als B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A ist kleiner als oder gleich B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A ist größer als oder gleich B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Kondition"
_if:
arg1: "Falls"
arg2: "Wenn wahr"
arg3: "Sonst"
not: "NICHT"
_not:
arg1: "NICHT"
random: "Zufällig"
_random:
arg1: "Warscheinlichkeit"
rannum: "Zufallsnummer"
_rannum:
arg1: "Minimum"
arg2: "Maximum"
randomPick: "Zufallswahl aus Liste"
_randomPick:
arg1: "Liste"
dailyRandom: "Zufällig (Pro Nutzer jeden Tag verschieden)"
_dailyRandom:
arg1: "Warscheinlichkeit"
dailyRannum: "Zufallsnummer (Pro Nutzer jeden Tag verschieden)"
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
dailyRandomPick: "Zufallsauswahl aus einer Liste (Pro Nutzer jeden Tag verschieden)"
_dailyRandomPick:
arg1: "Liste"
seedRandom: "Zufällig (mit Startwert / Seed)"
_seedRandom:
arg1: "Startwert / Seed"
arg2: "Warscheinlichkeit"
seedRannum: "Zufallsnummer (mit Startwert / Seed)"
_seedRannum:
arg1: "Startwert / Seed"
arg2: "Minimum"
arg3: "Maximum"
seedRandomPick: "Zufallsauswahl aus Liste (mit Startwert / Seed)"
_seedRandomPick:
arg1: "Startwert / Seed"
arg2: "Liste"
DRPWPM: "Zufallsauswahl aus gewichteter Liste (Pro Nutzer jeden Tag verschieden)"
_DRPWPM:
arg1: "Textliste"
pick: "Aus einer Liste wählen"
_pick:
arg1: "Liste"
arg2: "Position"
listLen: "Listenlänge abrufen"
_listLen:
arg1: "Liste"
number: "Nummer"
stringToNumber: "Text zu Nummer"
_stringToNumber:
arg1: "Text"
numberToString: "Nummer zu Text"
_numberToString:
arg1: "Nummer"
splitStrByLine: "Text nach Zeilenumbrüchen aufteilen"
_splitStrByLine:
arg1: "Text"
ref: "Variable"
aiScriptVar: "AiScript Variable"
fn: "Funktion"
_fn:
slots: "Slots"
slots-info: "Trenne jeden Slot mit einem Zeilenumbruch"
arg1: "Ausgabe"
for: "for-Schleife"
_for:
arg1: "Anzahl der Schleifendurchläufe"
arg2: "Aktion"
typeError: "Slot {slot} akzeptiert Werte vom Typ „{expect}“, aber es wurde ein „{actual}“ Wert angegeben!"
thereIsEmptySlot: "Slot {slot} ist leer!"
types:
string: "Text"
number: "Nummer"
boolean: "Wahrheitswert"
array: "Liste"
stringArray: "Textliste"
emptySlot: "Leerer Slot"
enviromentVariables: "Umgebungsvariable"
pageVariables: "Seitenelemente"
argVariables: "Eingabeslots"
_relayStatus:
requesting: "Ausstehend"
accepted: "Akzeptiert"

View file

@ -473,6 +473,8 @@ dayOverDayChanges: "Changes to yesterday"
appearance: "Appearance"
clientSettings: "Client Settings"
accountSettings: "Account Settings"
promotion: "Promoted"
promote: "Promote"
numberOfDays: "Number of days"
hideThisNote: "Hide this note"
showFeaturedNotesInTimeline: "Show featured notes in timelines"
@ -548,6 +550,7 @@ poll: "Poll"
useCw: "Hide content"
enablePlayer: "Open video player"
disablePlayer: "Close video player"
expandTweet: "Expand tweet"
themeEditor: "Theme editor"
description: "Description"
describeFile: "Add caption"
@ -1339,7 +1342,10 @@ _pages:
my: "My Pages"
liked: "Liked Pages"
featured: "Popular"
inspector: "Inspector"
contents: "Contents"
content: "Page block"
variables: "Variables"
title: "Title"
url: "Page URL"
summary: "Page summary"
@ -1350,6 +1356,262 @@ _pages:
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Set thumbnail"
eyeCatchingImageRemove: "Delete thumbnail"
chooseBlock: "Add a block"
selectType: "Select a type"
enterVariableName: "Enter a variable name"
variableNameIsAlreadyUsed: "This variable name is already in use"
contentBlocks: "Content"
inputBlocks: "Input"
specialBlocks: "Special"
blocks:
text: "Text"
textarea: "Text area"
section: "Section"
image: "Images"
button: "Button"
if: "If"
_if:
variable: "Variable"
post: "Posting form"
_post:
text: "Content"
attachCanvasImage: "Attach canvas image"
canvasId: "Canvas ID"
textInput: "Text input"
_textInput:
name: "Variable name"
text: "Title"
default: "Default value"
textareaInput: "Multiline text input"
_textareaInput:
name: "Variable name"
text: "Title"
default: "Default value"
numberInput: "Numeric input"
_numberInput:
name: "Variable name"
text: "Title"
default: "Default value"
canvas: "Canvas"
_canvas:
id: "Canvas ID"
width: "Width"
height: "Height"
note: "Embedded note"
_note:
id: "Note ID"
idDescription: "You can alternatively paste the note URL here."
detailed: "Detailed view"
switch: "Switch"
_switch:
name: "Variable name"
text: "Title"
default: "Default value"
counter: "Counter"
_counter:
name: "Variable name"
text: "Title"
inc: "Step"
_button:
text: "Title"
colored: "Colored"
action: "Behavior when the button is pressed"
_action:
dialog: "Show a dialog"
_dialog:
content: "Content"
resetRandom: "Reset the random seed"
pushEvent: "Send an event"
_pushEvent:
event: "Event name"
message: "Message to display when activated"
variable: "Variable to send"
no-variable: "None"
callAiScript: "Invoke AiScript"
_callAiScript:
functionName: "Function name"
radioButton: "Choice"
_radioButton:
name: "Variable name"
title: "Title"
values: "List of choices separated by line breaks"
default: "Default value"
script:
categories:
flow: "Flow control"
logical: "Logical operation"
operation: "Computation"
comparison: "Comparison"
random: "Random"
value: "Values"
fn: "Functions"
text: "Text operations"
convert: "Transformations"
list: "Lists"
blocks:
text: "Text"
multiLineText: "Text (multiline)"
textList: "Text list"
_textList:
info: "Separate each entry with a line break"
strLen: "Text length"
_strLen:
arg1: "Text"
strPick: "Extract string"
_strPick:
arg1: "Text"
arg2: "String location"
strReplace: "Replacement string"
_strReplace:
arg1: "Text"
arg2: "Text to be replaced"
arg3: "Replace with"
strReverse: "Flip text"
_strReverse:
arg1: "Text"
join: "Text concatenation"
_join:
arg1: "Lists"
arg2: "Separator"
add: "Add"
_add:
arg1: "A"
arg2: "B"
subtract: "Subtract"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiply"
_multiply:
arg1: "A"
arg2: "B"
divide: "Divide"
_divide:
arg1: "A"
arg2: "B"
mod: "Remainder"
_mod:
arg1: "A"
arg2: "B"
round: "Decimal rounding"
_round:
arg1: "Number"
eq: "A and B are equal"
_eq:
arg1: "A"
arg2: "B"
notEq: "A and B are different"
_notEq:
arg1: "A"
arg2: "B"
and: "A AND B"
_and:
arg1: "A"
arg2: "B"
or: "A OR B"
_or:
arg1: "A"
arg2: "B"
lt: "< A is less than B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A is larger than B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A is less than or equal to B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A is greater than or equal to B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Branch"
_if:
arg1: "If"
arg2: "Then"
arg3: "Else"
not: "NOT"
_not:
arg1: "NOT"
random: "Random"
_random:
arg1: "Probability"
rannum: "Random number"
_rannum:
arg1: "Minimum value"
arg2: "Maximum value"
randomPick: "Randomly choose from list"
_randomPick:
arg1: "List"
dailyRandom: "Random (Changes once a day for each user)"
_dailyRandom:
arg1: "Probability"
dailyRannum: "Random number (Changes once a day for each user)"
_dailyRannum:
arg1: "Minimum value"
arg2: "Maximum value"
dailyRandomPick: "Randomly choose from a list (Changes once a day for each user)"
_dailyRandomPick:
arg1: "List"
seedRandom: "Random (with seed)"
_seedRandom:
arg1: "Seed"
arg2: "Probability"
seedRannum: "Random number (with seed)"
_seedRannum:
arg1: "Seed"
arg2: "Minimum value"
arg3: "Maximum value"
seedRandomPick: "Randomly choose from list (with seed)"
_seedRandomPick:
arg1: "Seed"
arg2: "List"
DRPWPM: "Randomly choose from weighted list (Changes once a day for each user)"
_DRPWPM:
arg1: "Text list"
pick: "Select from list"
_pick:
arg1: "List"
arg2: "Position"
listLen: "Get length of list"
_listLen:
arg1: "List"
number: "Number"
stringToNumber: "Text to number"
_stringToNumber:
arg1: "Text"
numberToString: "Number to text"
_numberToString:
arg1: "Number"
splitStrByLine: "Split text by line breaks"
_splitStrByLine:
arg1: "Text"
ref: "Variable"
aiScriptVar: "AiScript Variable"
fn: "Function"
_fn:
slots: "Slots"
slots-info: "Separate each slot with a line break"
arg1: "Output"
for: "for-Loop"
_for:
arg1: "Number of times to repeat"
arg2: "Action"
typeError: "Slot {slot} accepts values of type \"{expect}\", but the provided value is of type \"{actual}\"!"
thereIsEmptySlot: "Slot {slot} is empty!"
types:
string: "Text"
number: "Number"
boolean: "Flag"
array: "List"
stringArray: "Text list"
emptySlot: "Empty slot"
enviromentVariables: "Environment variables"
pageVariables: "Page variables"
argVariables: "Input slots"
_relayStatus:
requesting: "Pending"
accepted: "Accepted"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "Dif diaria"
appearance: "Apariencia"
clientSettings: "Configuración del cliente"
accountSettings: "Ajustes de cuenta"
promotion: "Promovido"
promote: "Promover"
numberOfDays: "Cantidad de dias"
hideThisNote: "Ocultar esta nota"
showFeaturedNotesInTimeline: "Mostrar notas destacadas en la línea de tiempo"
@ -547,6 +549,7 @@ poll: "Encuesta"
useCw: "Esconder contenidos"
enablePlayer: "Abrir reproductor"
disablePlayer: "Cerrar reproductor"
expandTweet: "Expandir tweet"
themeEditor: "Editor de temas"
description: "Descripción"
describeFile: "Añade una descripción"
@ -1204,7 +1207,10 @@ _pages:
unlike: "Quitar me gusta"
my: "Mis páginas"
liked: "Páginas que me gustan"
inspector: "Inspector"
contents: "Contenido"
content: "Bloque de página"
variables: "Variables"
title: "Título"
url: "URL de la página"
summary: "Resumen de la página"
@ -1215,6 +1221,257 @@ _pages:
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Elegir imagen llamativa"
eyeCatchingImageRemove: "Borrar imagen llamativa"
chooseBlock: "Agregar bloque"
selectType: "Elegir tipo"
enterVariableName: "Ingrese el nombre de la variable"
variableNameIsAlreadyUsed: "El nombre de la variable ya está en uso"
contentBlocks: "Contenido"
inputBlocks: "Entrada"
specialBlocks: "Especial"
blocks:
text: "Texto"
textarea: "Área de texto"
section: "Sección"
image: "Imagen"
button: "Botón"
if: "si"
_if:
variable: "Variable"
post: "Formulario"
_post:
text: "Contenido"
attachCanvasImage: "Nota con lienzo como imagen"
canvasId: "Lienzo ID"
textInput: "Entrada de texto"
_textInput:
name: "Nombre de variable"
text: "Título"
default: "Valor predeterminado"
textareaInput: "Entrada de texto en múltiples lineas"
_textareaInput:
name: "Nombre de variable"
text: "Título"
default: "Valor predeterminado"
numberInput: "Entrada numérica"
_numberInput:
name: "Nombre de variable"
text: "Título"
default: "Valor predeterminado"
canvas: "Lienzo"
_canvas:
id: "Lienzo ID"
width: "Ancho"
height: "Altura"
switch: "Interruptor"
_switch:
name: "Nombre de variable"
text: "Título"
default: "Valor predeterminado"
counter: "Contador"
_counter:
name: "Nombre de variable"
text: "Título"
inc: "Aumentar cantidad"
_button:
text: "Título"
colored: "Color"
action: "Acción al presionar el botón"
_action:
dialog: "Mostrar cuadro de diálogo"
_dialog:
content: "Contenido"
resetRandom: "Resetear número aleatorio"
pushEvent: "Enviar evento"
_pushEvent:
event: "Nombre del evento"
message: "Mensaje mostrado al apretar"
variable: "Variable a enviar"
no-variable: "Ninguna"
callAiScript: "Invocar AiScript"
_callAiScript:
functionName: "Nombre de la función"
radioButton: "Botón de opción"
_radioButton:
name: "Nombre de variable"
title: "Título"
values: "Opciones separadas por una nueva linea"
default: "Valor predeterminado"
script:
categories:
flow: "Control de flujo"
logical: "Operación lógica"
operation: "Cálculo"
comparison: "Comparar"
random: "Aleatorio"
value: "Valores"
fn: "funciones"
text: "Manejo de texto"
convert: "Conversion"
list: "Listas"
blocks:
text: "Texto"
multiLineText: "Texto (multilinea)"
textList: "Lista de texto"
_textList:
info: "Separe cada texto con una linea nueva"
strLen: "Largo del texto"
_strLen:
arg1: "Texto"
strPick: "Extraer caracteres"
_strPick:
arg1: "Texto"
arg2: "Posición del caracter"
strReplace: "Sustituir texto"
_strReplace:
arg1: "Texto"
arg2: "Texto a reemplazar"
arg3: "Texto reemplazado"
strReverse: "Invertir texto"
_strReverse:
arg1: "Texto"
join: "Concatenar texto"
_join:
arg1: "Listas"
arg2: "Separador"
add: "Suma"
_add:
arg1: "A"
arg2: "B"
subtract: "Resta"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiplicación"
_multiply:
arg1: "A"
arg2: "B"
divide: "División"
_divide:
arg1: "A"
arg2: "B"
mod: "Resto"
_mod:
arg1: "A"
arg2: "B"
round: "Redondear decimales"
_round:
arg1: "Número"
eq: "A y B son iguales"
_eq:
arg1: "A"
arg2: "B"
notEq: "A y B son distintos"
_notEq:
arg1: "A"
arg2: "B"
and: "A y B"
_and:
arg1: "A"
arg2: "B"
or: "A o B"
_or:
arg1: "A"
arg2: "B"
lt: "< A es menor que B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A es mayor que B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A es menor o igual que B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A es mayor o igual que B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Si"
_if:
arg1: "si"
arg2: "Entonces"
arg3: "Si no"
not: "Negación"
_not:
arg1: "Negación"
random: "Aleatorio"
_random:
arg1: "probabilidad"
rannum: "Número aleatorio"
_rannum:
arg1: "Mínimo"
arg2: "Máximo"
randomPick: "Elegir aleatoriamente de la lista"
_randomPick:
arg1: "Listas"
dailyRandom: "Aleatorio (Diariamente para cada usuario)"
_dailyRandom:
arg1: "probabilidad"
dailyRannum: "Número aleatorio (Diariamente para cada usuario)"
_dailyRannum:
arg1: "Mínimo"
arg2: "Máximo"
dailyRandomPick: "Elegir aleatoriamente de la lista (Diariamente para cada usuario)"
_dailyRandomPick:
arg1: "Listas"
seedRandom: "Aleatorio (semilla)"
_seedRandom:
arg1: "Semilla"
arg2: "probabilidad"
seedRannum: "Número aleatorio (semilla)"
_seedRannum:
arg1: "Semilla"
arg2: "Mínimo"
arg3: "Máximo"
seedRandomPick: "Elegir aleatoriamente de la lista (semilla)"
_seedRandomPick:
arg1: "Semilla"
arg2: "Listas"
DRPWPM: "Elegir aleatoriamente de la lista ponderada (Diariamente para cada usuario)"
_DRPWPM:
arg1: "Lista de texto"
pick: "Elegir de la lista"
_pick:
arg1: "Listas"
arg2: "Posición"
listLen: "Obtener largo de la lista"
_listLen:
arg1: "Listas"
number: "Número"
stringToNumber: "De texto a número"
_stringToNumber:
arg1: "Texto"
numberToString: "De número a texto"
_numberToString:
arg1: "Número"
splitStrByLine: "Separar texto en lineas"
_splitStrByLine:
arg1: "Texto"
ref: "Variables"
aiScriptVar: "Variable de AiScript"
fn: "funciones"
_fn:
slots: "Slots"
slots-info: "Separe cada uno de los slots con una linea nueva"
arg1: "Salida"
for: "Repetir"
_for:
arg1: "Cantidad de repeticiones"
arg2: "Acción"
typeError: "El slot {slot} acepta el tipo {expect} pero fue ingresado el tipo {actual}"
thereIsEmptySlot: "El slot {slot} está vacío"
types:
string: "Texto"
number: "Número"
boolean: "Booleano"
array: "Listas"
stringArray: "Lista de texto"
emptySlot: "Slot vacío"
enviromentVariables: "Variables de entorno"
pageVariables: "Items de la página"
argVariables: "Slot de entrada"
_relayStatus:
requesting: "Pendiente"
accepted: "Aceptar"

View file

@ -469,6 +469,8 @@ dayOverDayChanges: "Journalier"
appearance: "Apparence"
clientSettings: "Paramètres du client"
accountSettings: "Paramètres du compte"
promotion: "Promu"
promote: "Promouvoir"
numberOfDays: "Nombre de jours"
hideThisNote: "Masquer cette note"
showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'actualité"
@ -544,6 +546,7 @@ poll: "Sondage"
useCw: "Masquer le contenu"
enablePlayer: "Ouvrir dans le lecteur vidéo"
disablePlayer: "Fermer le lecteur vidéo"
expandTweet: "Étendre le tweet"
themeEditor: "Éditeur de thèmes"
description: "Description"
describeFile: "Ajouter une description d'image"
@ -1307,7 +1310,10 @@ _pages:
my: "Mes pages"
liked: "Pages favorites"
featured: "Populaire"
inspector: "Inspecteur"
contents: "Contenu"
content: "Bloc de page"
variables: "Variables"
title: "Titre"
url: "URL de la page"
summary: "Résumé de page"
@ -1318,6 +1324,262 @@ _pages:
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Définir une image attractive"
eyeCatchingImageRemove: "Supprimer l'image attractive"
chooseBlock: "Ajouter un bloc"
selectType: "Choisir un type"
enterVariableName: "Veuillez entrer un nom pour votre variable"
variableNameIsAlreadyUsed: "Ce nom de variable est déjà utilisé"
contentBlocks: "Contenu"
inputBlocks: "Blocs d'entrée"
specialBlocks: "Spécial"
blocks:
text: "Texte"
textarea: "Zone de texte"
section: "Section"
image: "Images"
button: "Bouton"
if: "Si"
_if:
variable: "Variables"
post: "Formulaire de publication"
_post:
text: "Contenu"
attachCanvasImage: "Publier avec Toile comme image"
canvasId: "Toile ID"
textInput: "Entrée textuelle"
_textInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
textareaInput: "Entrée textuelle multi-ligne"
_textareaInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
numberInput: "Entrée numérique"
_numberInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
canvas: "Toile"
_canvas:
id: "Toile ID"
width: "Largeur"
height: "Hauteur"
note: "Note intégrée"
_note:
id: "Identifiant de la note"
idDescription: "Pour configurer la note, vous pouvez aussi coller ici l'URL correspondante."
detailed: "Afficher les détails"
switch: "Interrupteur"
_switch:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
counter: "Compteur"
_counter:
name: "Nom de la variable"
text: "Titre"
inc: "Augmenter de"
_button:
text: "Titre"
colored: "Coloré"
action: "Opération à effectuer lorsque le bouton est pressé"
_action:
dialog: "Afficher une fenêtre de dialogue"
_dialog:
content: "Contenu"
resetRandom: "Réinitialiser un nombre aléatoire"
pushEvent: "Envoyer un évènement"
_pushEvent:
event: "Nom de lévènement"
message: "Message à afficher lorsquil est activé"
variable: "Variable à envoyer"
no-variable: "Rien"
callAiScript: "Appeler AiScript"
_callAiScript:
functionName: "Nom de la fonction"
radioButton: "Choix"
_radioButton:
name: "Nom de la variable"
title: "Titre"
values: "Liste des choix (un par ligne)"
default: "Valeur par défaut"
script:
categories:
flow: "Contrôle"
logical: "Opération logique"
operation: "Calculer"
comparison: "Comparer"
random: "Aléatoire"
value: "Valeur"
fn: "Fonction"
text: "Manipulation de texte"
convert: "Convertir"
list: "Listes"
blocks:
text: "Texte"
multiLineText: "Texte (multi-ligne)"
textList: "Liste de texte"
_textList:
info: "Veuillez séparer chaque entrée avec un saut de ligne"
strLen: "Longueur du texte"
_strLen:
arg1: "Texte"
strPick: "Extraire un caractère"
_strPick:
arg1: "Texte"
arg2: "Position du joueur"
strReplace: "Remplacement de texte"
_strReplace:
arg1: "Texte"
arg2: "Avant le remplacement"
arg3: "Après le remplacement"
strReverse: "Inverser le texte"
_strReverse:
arg1: "Texte"
join: "Concaténer du texte"
_join:
arg1: "Listes"
arg2: "Séparateur"
add: "Ajouter"
_add:
arg1: "A"
arg2: "B"
subtract: "Soustraire"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiplier par"
_multiply:
arg1: "A"
arg2: "B"
divide: "Diviser par"
_divide:
arg1: "A"
arg2: "B"
mod: "Reste"
_mod:
arg1: "A"
arg2: "B"
round: "Arrondir les décimales"
_round:
arg1: "Numérique"
eq: "A et B sont égaux"
_eq:
arg1: "A"
arg2: "B"
notEq: "A et B sont différents"
_notEq:
arg1: "A"
arg2: "B"
and: "A et B"
_and:
arg1: "A"
arg2: "B"
or: "A ou B"
_or:
arg1: "A"
arg2: "B"
lt: "A est inférieur à B"
_lt:
arg1: "A"
arg2: "B"
gt: "A est supérieur à B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "A est inférieur ou égal à B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: "A est supérieur ou égal à B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Branche"
_if:
arg1: "Si"
arg2: "Si"
arg3: "Sinon"
not: "Nier"
_not:
arg1: "Nier"
random: "Aléatoire"
_random:
arg1: "Probabilité"
rannum: "Nombre aléatoire"
_rannum:
arg1: "Minimum"
arg2: "Maximum"
randomPick: "Sélectionner au hasard dans la liste"
_randomPick:
arg1: "Listes"
dailyRandom: "Aléatoire (Quotidien pour chaque utilisateur)"
_dailyRandom:
arg1: "Probabilité"
dailyRannum: "Numéros aléatoires (Quotidien pour chaque utilisateur)"
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque utilisateur)"
_dailyRandomPick:
arg1: "Listes"
seedRandom: "Aléatoire (graine)"
_seedRandom:
arg1: "Graine"
arg2: "Probabilité"
seedRannum: "Nombre aléatoire (Graine)"
_seedRannum:
arg1: "Graine"
arg2: "Minimum"
arg3: "Maximum"
seedRandomPick: "Sélectionné au hasard dans la liste (graine)"
_seedRandomPick:
arg1: "Graine"
arg2: "Listes"
DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour chaque utilisateur)"
_DRPWPM:
arg1: "Liste de texte"
pick: "Sélectionner dans la liste"
_pick:
arg1: "Listes"
arg2: "Position"
listLen: "Longueur de la liste"
_listLen:
arg1: "Listes"
number: "Numérique"
stringToNumber: "Convertir du texte en numérique"
_stringToNumber:
arg1: "Texte"
numberToString: "Convertir du numérique en texte"
_numberToString:
arg1: "Numérique"
splitStrByLine: "Séparer le texte par des sauts de lignes"
_splitStrByLine:
arg1: "Texte"
ref: "Variables"
aiScriptVar: "Variable d'AiScript"
fn: "Fonction"
_fn:
slots: "Slots"
slots-info: "Veuillez insérer un seul slot par ligne"
arg1: "Sortie"
for: "Répéter"
_for:
arg1: "Compter"
arg2: "Action"
typeError: "Le slot {slot} accepte \"{expect}\" mais a \"{actual}\" !"
thereIsEmptySlot: "Slot {slot} est vide !"
types:
string: "Texte"
number: "Numérique"
boolean: "Marqueur"
array: "Listes"
stringArray: "Liste de texte"
emptySlot: "Slot vide"
enviromentVariables: "Variables d'environnement"
pageVariables: "Élément de page"
argVariables: "Entrée slot"
_relayStatus:
requesting: "En attente"
accepted: "Accepté"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "Harian"
appearance: "Tampilan"
clientSettings: "Pengaturan Klien"
accountSettings: "Pengaturan Akun"
promotion: "Promosi"
promote: "Promosikan"
numberOfDays: "Jumlah hari"
hideThisNote: "Sembunyikan catatan ini"
showFeaturedNotesInTimeline: "Tampilkan catatan yang diunggulkan di linimasa"
@ -547,6 +549,7 @@ poll: "Angket"
useCw: "Sembunyikan konten"
enablePlayer: "Buka pemutar video"
disablePlayer: "Tutup pemutar video"
expandTweet: "Perluas utas"
themeEditor: "Penyunting tema"
description: "Deskripsi"
describeFile: "Tambahkan keterangan"
@ -1323,7 +1326,10 @@ _pages:
my: "Halaman saya"
liked: "Halaman yang disukai"
featured: "Populer"
inspector: "Inspektor"
contents: "Konten"
content: "Blokir Halaman"
variables: "Variabel"
title: "Judul"
url: "URL Halaman"
summary: "Ringkasan Halaman"
@ -1334,6 +1340,262 @@ _pages:
fontSansSerif: "Sans-serif"
eyeCatchingImageSet: "Setel gambar yang menarik"
eyeCatchingImageRemove: "Hapus gambar yang menarik"
chooseBlock: "Tambahkan blokir"
selectType: "Pilih jenis"
enterVariableName: "Mohon masukkan nama untuk variabel kamu"
variableNameIsAlreadyUsed: "Nama ini sudah digunakan oleh variabel lain"
contentBlocks: "Konten"
inputBlocks: "Masukan"
specialBlocks: "Khusus"
blocks:
text: "Teks"
textarea: "Area teks"
section: "Bagian"
image: "Gambar"
button: "Tombol"
if: "Jika"
_if:
variable: "Variabel"
post: "Buat catatan"
_post:
text: "Isi"
attachCanvasImage: "Posting dengan kanvas sebagai gambar"
canvasId: "ID Kanvas"
textInput: "Masukan teks"
_textInput:
name: "Nama variabel"
text: "Judul"
default: "Nilai bawaan"
textareaInput: "Masukan teks multibaris"
_textareaInput:
name: "Nama variabel"
text: "Judul"
default: "Nilai bawaan"
numberInput: "Masukan angka"
_numberInput:
name: "Nama variabel"
text: "Judul"
default: "Nilai bawaan"
canvas: "Kanvas"
_canvas:
id: "ID Kanvas"
width: "Lebar"
height: "Tinggi"
note: "Catatan yang ditanam"
_note:
id: "ID Catatan"
idDescription: "Kamu dapat menyetel ini dengan menempelkan tautan URL Catatan."
detailed: "Tampilan rincian"
switch: "Beralih"
_switch:
name: "Nama variabel"
text: "Judul"
default: "Nilai bawaan"
counter: "Penghitung"
_counter:
name: "Nama variabel"
text: "Judul"
inc: "Meningkat dengan"
_button:
text: "Judul"
colored: "Diwarnai"
action: "Operasi akan dimulai ketika tombol ditekan"
_action:
dialog: "Tampilkan dialog"
_dialog:
content: "Isi"
resetRandom: "Atur ulang benih acak"
pushEvent: "Kirim event"
_pushEvent:
event: "Nama event"
message: "Pesan yang tampil ketika diaktifkan"
variable: "Variable untuk kirim"
no-variable: "Tidak ada"
callAiScript: "Panggil AiScript"
_callAiScript:
functionName: "Nama fungsi"
radioButton: "Pilihan"
_radioButton:
name: "Nama variabel"
title: "Judul"
values: "Daftar pilihan (dipisahkan dengan garis baru)"
default: "Nilai bawaan"
script:
categories:
flow: "Arus kendali"
logical: "Operasi logis"
operation: "Menghitung"
comparison: "Membandingkan"
random: "Acak"
value: "Nilai"
fn: "Fungsi"
text: "Operasi teks"
convert: "Mengubah"
list: "Daftar"
blocks:
text: "Teks"
multiLineText: "Teks (multibaris)"
textList: "Daftar teks"
_textList:
info: "Pisahkan setiap entri dengan baris baru"
strLen: "Panjang teks"
_strLen:
arg1: "Teks"
strPick: "Ekstrak karakter"
_strPick:
arg1: "Teks"
arg2: "Lokasi karakter"
strReplace: "Penggantian teks"
_strReplace:
arg1: "Teks"
arg2: "Teks yang akan diganti"
arg3: "Diganti dengan"
strReverse: "Balikkan teks"
_strReverse:
arg1: "Teks"
join: "Rangkaian teks"
_join:
arg1: "Daftar"
arg2: "Pemisah"
add: "Tambah"
_add:
arg1: "A"
arg2: "B"
subtract: "Kurangi"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Kali"
_multiply:
arg1: "A"
arg2: "B"
divide: "Bagi"
_divide:
arg1: "A"
arg2: "B"
mod: "Sisa"
_mod:
arg1: "A"
arg2: "B"
round: "Bulat desimal"
_round:
arg1: "Angka"
eq: "A dan B adalah sama"
_eq:
arg1: "A"
arg2: "B"
notEq: "A dan B adalah berbeda"
_notEq:
arg1: "A"
arg2: "B"
and: "A DAN B"
_and:
arg1: "A"
arg2: "B"
or: "A ATAU B"
_or:
arg1: "A"
arg2: "B"
lt: "< A ikurang dari B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A lebih dari B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A kurang dari sama dengan B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A lebih dari sama dengan B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Cabang"
_if:
arg1: "Jika"
arg2: "Jika benar"
arg3: "Jika salah"
not: "BUKAN"
_not:
arg1: "NOT"
random: "Acak"
_random:
arg1: "Probabilitas"
rannum: "Angka acak"
_rannum:
arg1: "Nilai minimum"
arg2: "Nilai maksimum"
randomPick: "Pilih secara acak dari daftar"
_randomPick:
arg1: "Daftar"
dailyRandom: "Acak (bertahan sehari)"
_dailyRandom:
arg1: "Probabilitas"
dailyRannum: "Angka acak (bertahan sehari)"
_dailyRannum:
arg1: "Nilai minimum"
arg2: "Nilai maksimum"
dailyRandomPick: "Pilih secara acak dari daftar (bertahan sehari)"
_dailyRandomPick:
arg1: "Daftar"
seedRandom: "Acak (dengan seed)"
_seedRandom:
arg1: "Seed"
arg2: "Probabilitas"
seedRannum: "Angka acak (dengan seed)"
_seedRannum:
arg1: "Seed"
arg2: "Nilai minimum"
arg3: "Nilai maksimum"
seedRandomPick: "Pilih secara acak dari daftar (dengan seed)"
_seedRandomPick:
arg1: "Seed"
arg2: "Daftar"
DRPWPM: "Pilih secara acak dari daftar berbobot (bertahan sehari)"
_DRPWPM:
arg1: "Daftar teks"
pick: "Pilih dari daftar"
_pick:
arg1: "Daftar"
arg2: "Posisi"
listLen: "Dapatkan panjangnya dari daftar"
_listLen:
arg1: "Daftar"
number: "Angka"
stringToNumber: "Teks ke angka"
_stringToNumber:
arg1: "Teks"
numberToString: "Angka ke teks"
_numberToString:
arg1: "Angka"
splitStrByLine: "Pisahkan teks dengan baris baru"
_splitStrByLine:
arg1: "Teks"
ref: "Variabel"
aiScriptVar: "Variabel AiScript"
fn: "Fungsi"
_fn:
slots: "Slot"
slots-info: "Pisahkan setiap slot dengan baris baru"
arg1: "Keluaran"
for: "Ulangi"
_for:
arg1: "Jumlah angka untuk diulangi"
arg2: "Aksi"
typeError: "Slot {slot} menerima tipe \"{expect}\", sayangnya nilai yang disediakan adalah \"{actual}\"!"
thereIsEmptySlot: "Slot {slot} kosong!"
types:
string: "Teks"
number: "Angka"
boolean: "Markah"
array: "Daftar"
stringArray: "Daftar teks"
emptySlot: "Slot kosong"
enviromentVariables: "Variabel Lingkungan"
pageVariables: "Elemen halaman"
argVariables: "Masukan slot"
_relayStatus:
requesting: "Menunggu"
accepted: "Disetujui"

View file

@ -468,6 +468,8 @@ dayOverDayChanges: "Giornaliero"
appearance: "Aspetto"
clientSettings: "Impostazioni client"
accountSettings: "Impostazioni account"
promotion: "Promossa"
promote: "Pubblicizza"
numberOfDays: "Numero di giorni"
hideThisNote: "Nasconda la nota"
showFeaturedNotesInTimeline: "Mostrare le note di tendenza nella tua timeline"
@ -543,6 +545,7 @@ poll: "Sondaggio"
useCw: "Nascondere media"
enablePlayer: "Apri in lettore video"
disablePlayer: "Chiudi lettore video"
expandTweet: "Espandi tweet"
themeEditor: "Editor di temi"
description: "Descrizione"
describeFile: "Aggiungi una descrizione d'immagine"
@ -1223,6 +1226,8 @@ _pages:
liked: "Pagine che mi piacciono"
featured: "Popolari"
contents: "Contenuto"
content: "Blocco di pagina"
variables: "Variabili"
title: "Titolo"
url: "URL della pagina"
summary: "Riassunto di pagina"
@ -1232,6 +1237,172 @@ _pages:
fontSansSerif: "Sans serif"
eyeCatchingImageSet: "Imposta un'immagine attrattiva"
eyeCatchingImageRemove: "Elimina l'immagine attrattiva"
chooseBlock: "Aggiungi blocco"
selectType: "Seleziona tipo"
enterVariableName: "Digita un nome di variabile"
variableNameIsAlreadyUsed: "Esiste già una variabile con lo stesso nome"
contentBlocks: "Contenuto"
inputBlocks: "Blocchi di input"
specialBlocks: "Speciale"
blocks:
text: "Testo"
textarea: "Area di testo"
section: "Sezione"
image: "Immagini"
button: "Pulsante"
if: "Se"
_if:
variable: "Variabili"
post: "Finestra di pubblicazione"
_post:
text: "Contenuto"
textInput: "Immissione testo"
_textInput:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
textareaInput: "Immissione testo a più righe"
_textareaInput:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
numberInput: "Immissione numerica"
_numberInput:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
_canvas:
width: "Larghezza"
height: "Altezza"
note: "Nota integrata"
_note:
id: "ID nota"
idDescription: "Qui puoi anche incollare l'URL della nota che vuoi impostare."
detailed: "Visualizzazione dettagliata"
switch: "Interruttore"
_switch:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
counter: "Contatore"
_counter:
name: "Nome della variabile"
text: "Titolo"
inc: "Valore da aggiungere"
_button:
text: "Titolo"
colored: "Colorato"
action: "Operazione da eseguire quando viene premuto il pulsante"
_action:
dialog: "Visualizzare una finestra di dialogo"
_dialog:
content: "Contenuto"
resetRandom: "Ripristinare un numero aleatorio"
pushEvent: "Inviare evento"
_pushEvent:
event: "Nome evento"
message: "Messaggio da visualizzare quando abilitato"
variable: "Variabile da inviare"
no-variable: "Nessun contenuto"
callAiScript: "Chiamare AiScript"
_callAiScript:
functionName: "Nome della funzione"
radioButton: "Opzioni"
_radioButton:
name: "Nome della variabile"
title: "Titolo"
default: "Valore predefinito"
script:
categories:
comparison: "Metodo comparativo"
random: "Aleatorietà"
value: "Valore"
fn: "Funzione"
list: "Liste"
blocks:
text: "Testo"
multiLineText: "Testo (a più righe)"
textList: "Lista di testo"
_strLen:
arg1: "Testo"
_strPick:
arg1: "Testo"
_strReplace:
arg1: "Testo"
_strReverse:
arg1: "Testo"
_join:
arg1: "Liste"
_add:
arg1: "A"
arg2: "B"
_subtract:
arg1: "A"
arg2: "B"
_multiply:
arg1: "A"
arg2: "B"
_divide:
arg1: "A"
arg2: "B"
_mod:
arg1: "A"
arg2: "B"
_eq:
arg1: "A"
arg2: "B"
notEq: "A e B sono differenti"
_notEq:
arg1: "A"
arg2: "B"
and: "A e B"
_and:
arg1: "A"
arg2: "B"
or: "A o B"
_or:
arg1: "A"
arg2: "B"
_lt:
arg1: "A"
arg2: "B"
_gt:
arg1: "A"
arg2: "B"
_ltEq:
arg1: "A"
arg2: "B"
_gtEq:
arg1: "A"
arg2: "B"
_if:
arg1: "Se"
arg2: "Se"
random: "Aleatorietà"
_randomPick:
arg1: "Liste"
_dailyRandomPick:
arg1: "Liste"
_seedRandom:
arg2: "Probabilità"
_seedRandomPick:
arg2: "Liste"
_DRPWPM:
arg1: "Lista di testo"
_pick:
arg1: "Liste"
_listLen:
arg1: "Liste"
_stringToNumber:
arg1: "Testo"
_splitStrByLine:
arg1: "Testo"
ref: "Variabili"
fn: "Funzione"
types:
string: "Testo"
array: "Liste"
stringArray: "Lista di testo"
_relayStatus:
requesting: "In attesa di approvazione"
accepted: "Approvato"

View file

@ -473,6 +473,8 @@ dayOverDayChanges: "前日比"
appearance: "アピアランス"
clientSettings: "クライアント設定"
accountSettings: "アカウント設定"
promotion: "プロモーション"
promote: "プロモート"
numberOfDays: "日数"
hideThisNote: "このノートを非表示"
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する"
@ -548,6 +550,7 @@ poll: "アンケート"
useCw: "内容を隠す"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "キャプションを付ける"
@ -1380,7 +1383,10 @@ _pages:
my: "自分のページ"
liked: "いいねしたページ"
featured: "人気"
inspector: "インスペクター"
contents: "コンテンツ"
content: "ページブロック"
variables: "変数"
title: "タイトル"
url: "ページURL"
summary: "ページの要約"
@ -1391,6 +1397,274 @@ _pages:
fontSansSerif: "サンセリフ"
eyeCatchingImageSet: "アイキャッチ画像を設定"
eyeCatchingImageRemove: "アイキャッチ画像を削除"
chooseBlock: "ブロックを追加"
selectType: "種類を選択"
enterVariableName: "変数名を決めてください"
variableNameIsAlreadyUsed: "その変数名は既に使われています"
contentBlocks: "コンテンツ"
inputBlocks: "入力"
specialBlocks: "特殊"
blocks:
text: "テキスト"
textarea: "テキストエリア"
section: "セクション"
image: "画像"
button: "ボタン"
if: "もし"
_if:
variable: "変数"
post: "投稿フォーム"
_post:
text: "内容"
attachCanvasImage: "キャンバスの画像を添付する"
canvasId: "キャンバスID"
textInput: "テキスト入力"
_textInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
textareaInput: "複数行テキスト入力"
_textareaInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
numberInput: "数値入力"
_numberInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
canvas: "キャンバス"
_canvas:
id: "キャンバスID"
width: "幅"
height: "高さ"
note: "ノート埋め込み"
_note:
id: "ートID"
idDescription: "ートURLをペーストして設定することもできます。"
detailed: "詳細な表示"
switch: "スイッチ"
_switch:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
counter: "カウンター"
_counter:
name: "変数名"
text: "タイトル"
inc: "増加値"
_button:
text: "タイトル"
colored: "色付き"
action: "ボタンを押したときの動作"
_action:
dialog: "ダイアログを表示する"
_dialog:
content: "内容"
resetRandom: "乱数をリセット"
pushEvent: "イベントを送信させる"
_pushEvent:
event: "イベント名"
message: "押したときに表示するメッセージ"
variable: "送信する変数"
no-variable: "なし"
callAiScript: "AiScript呼び出し"
_callAiScript:
functionName: "関数名"
radioButton: "選択肢"
_radioButton:
name: "変数名"
title: "タイトル"
values: "改行で区切った選択肢"
default: "デフォルト値"
script:
categories:
flow: "制御"
logical: "論理演算"
operation: "計算"
comparison: "比較"
random: "ランダム"
value: "値"
fn: "関数"
text: "テキスト操作"
convert: "変換"
list: "リスト"
blocks:
text: "テキスト"
multiLineText: "テキスト(複数行)"
textList: "テキストのリスト"
_textList:
info: "ひとつひとつを改行で区切ってください"
strLen: "テキストの長さ"
_strLen:
arg1: "テキスト"
strPick: "文字取り出し"
_strPick:
arg1: "テキスト"
arg2: "文字の位置"
strReplace: "テキスト置き換え"
_strReplace:
arg1: "テキスト"
arg2: "置き換え前"
arg3: "置き換え後"
strReverse: "テキストを反転"
_strReverse:
arg1: "テキスト"
join: "テキストを連結"
_join:
arg1: "リスト"
arg2: "区切り"
add: "足す"
_add:
arg1: "A"
arg2: "B"
subtract: "引く"
_subtract:
arg1: "A"
arg2: "B"
multiply: "掛ける"
_multiply:
arg1: "A"
arg2: "B"
divide: "割る"
_divide:
arg1: "A"
arg2: "B"
mod: "割った余り"
_mod:
arg1: "A"
arg2: "B"
round: "小数を丸める"
_round:
arg1: "数値"
eq: "AとBが同じ"
_eq:
arg1: "A"
arg2: "B"
notEq: "AとBが異なる"
_notEq:
arg1: "A"
arg2: "B"
and: "AかつB"
_and:
arg1: "A"
arg2: "B"
or: "AまたはB"
_or:
arg1: "A"
arg2: "B"
lt: "< AがBより小さい"
_lt:
arg1: "A"
arg2: "B"
gt: "> AがBより大きい"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= AがBと同じか小さい"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= AがBと同じか大きい"
_gtEq:
arg1: "A"
arg2: "B"
if: "分岐"
_if:
arg1: "もし"
arg2: "なら"
arg3: "そうでなければ"
not: "否定"
_not:
arg1: "否定"
random: "ランダム"
_random:
arg1: "確率"
rannum: "乱数"
_rannum:
arg1: "最小"
arg2: "最大"
randomPick: "リストからランダムに選択"
_randomPick:
arg1: "リスト"
dailyRandom: "ランダム (ユーザーごとに日替わり)"
_dailyRandom:
arg1: "確率"
dailyRannum: "乱数 (ユーザーごとに日替わり)"
_dailyRannum:
arg1: "最小"
arg2: "最大"
dailyRandomPick: "リストからランダムに選択 (ユーザーごとに日替わり)"
_dailyRandomPick:
arg1: "リスト"
seedRandom: "ランダム (シード)"
_seedRandom:
arg1: "シード"
arg2: "確率"
seedRannum: "乱数 (シード)"
_seedRannum:
arg1: "シード"
arg2: "最小"
arg3: "最大"
seedRandomPick: "リストからランダムに選択 (シード)"
_seedRandomPick:
arg1: "シード"
arg2: "リスト"
DRPWPM: "確率付きリストからランダムに選択 (ユーザーごとに日替わり)"
_DRPWPM:
arg1: "テキストのリスト"
pick: "リストから選択"
_pick:
arg1: "リスト"
arg2: "位置"
listLen: "リストの長さを取得"
_listLen:
arg1: "リスト"
number: "数値"
stringToNumber: "テキストを数値に"
_stringToNumber:
arg1: "テキスト"
numberToString: "数値をテキストに"
_numberToString:
arg1: "数値"
splitStrByLine: "テキストを行で分割"
_splitStrByLine:
arg1: "テキスト"
ref: "変数"
aiScriptVar: "AiScript変数"
fn: "関数"
_fn:
slots: "スロット"
slots-info: "スロットひとつひとつを改行で区切ってください"
arg1: "出力"
for: "繰り返し"
_for:
arg1: "回数"
arg2: "処理"
typeError: "スロット{slot}は\"{expect}\"を受け付けますが、\"{actual}\"が入れられています!"
thereIsEmptySlot: "スロット{slot}が空です!"
types:
string: "テキスト"
number: "数値"
boolean: "フラグ"
array: "リスト"
stringArray: "テキストのリスト"
emptySlot: "空のスロット"
enviromentVariables: "環境変数"
pageVariables: "ページ要素"
argVariables: "入力スロット"
_relayStatus:
requesting: "承認待ち"

View file

@ -462,6 +462,8 @@ dayOverDayChanges: "前日比"
appearance: "見た目"
clientSettings: "クライアントの設定"
accountSettings: "アカウントの設定"
promotion: "宣伝"
promote: "宣伝"
numberOfDays: "日数"
hideThisNote: "このノートは表示せんでいい"
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示してや"
@ -522,6 +524,7 @@ addedRelays: "追加済みのリレー"
poll: "アンケート"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター"
description: "説明"
author: "作者"
@ -932,6 +935,252 @@ _pages:
fontSansSerif: "サンセリフ"
eyeCatchingImageSet: "アイキャッチ画像を設定"
eyeCatchingImageRemove: "アイキャッチ画像を削除"
chooseBlock: "ブロックを追加"
selectType: "種類を選択"
contentBlocks: "コンテンツ"
inputBlocks: "入力"
specialBlocks: "特殊"
blocks:
text: "テキスト"
textarea: "テキストエリア"
section: "セクション"
image: "画像"
button: "ボタン"
if: "もし"
_if:
variable: "変数"
post: "投稿フォーム"
_post:
text: "内容"
canvasId: "キャンバスID"
textInput: "テキスト入力"
_textInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
textareaInput: "複数行テキスト入力"
_textareaInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
numberInput: "数値入力"
_numberInput:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
canvas: "キャンバス"
_canvas:
id: "キャンバスID"
width: "幅"
height: "高さ"
note: "ノート埋め込み"
_note:
id: "ートID"
detailed: "詳細な表示"
switch: "スイッチ"
_switch:
name: "変数名"
text: "タイトル"
default: "デフォルト値"
counter: "カウンター"
_counter:
name: "変数名"
text: "タイトル"
inc: "増加値"
_button:
text: "タイトル"
colored: "色付き"
action: "ボタンを押したときの動作"
_action:
dialog: "ダイアログを表示する"
_dialog:
content: "内容"
resetRandom: "乱数をリセット"
pushEvent: "イベントを送信させる"
_pushEvent:
event: "イベント名"
no-variable: "なし"
callAiScript: "AiScript呼び出し"
_callAiScript:
functionName: "関数名"
radioButton: "選択肢"
_radioButton:
name: "変数名"
title: "タイトル"
values: "改行で区切った選択肢"
default: "デフォルト値"
script:
categories:
flow: "制御"
logical: "論理演算"
operation: "計算"
comparison: "比較"
random: "ランダム"
value: "値"
fn: "関数"
text: "関数"
convert: "変換"
list: "リスト"
blocks:
text: "テキスト"
multiLineText: "テキスト(複数行)"
textList: "テキストのリスト"
strLen: "テキストの長さ"
_strLen:
arg1: "テキスト"
strPick: "文字取り出し"
_strPick:
arg1: "テキスト"
arg2: "文字の位置"
strReplace: "テキスト置き換え"
_strReplace:
arg1: "テキスト"
arg2: "置き換え前"
arg3: "置き換え後"
strReverse: "テキストを反転"
_strReverse:
arg1: "テキスト"
join: "テキストを連結"
_join:
arg1: "リスト"
arg2: "区切り"
add: "足す"
_add:
arg1: "A"
arg2: "B"
subtract: "引く"
_subtract:
arg1: "A"
arg2: "A"
multiply: "掛ける"
_multiply:
arg1: "A"
arg2: "B"
divide: "割る"
_divide:
arg1: "A"
arg2: "B"
mod: "割った余り"
_mod:
arg1: "A"
arg2: "B"
round: "小数を丸める"
_round:
arg1: "数値"
eq: "AとBが同じ"
_eq:
arg1: "A"
arg2: "B"
notEq: "AとBが異なる"
_notEq:
arg1: "A"
arg2: "B"
and: "AかつB"
_and:
arg1: "A"
arg2: "B"
or: "AまたはB"
_or:
arg1: "A"
arg2: "B"
lt: "< AがBより小さい"
_lt:
arg1: "A"
arg2: "B"
gt: "> AがBより大きい"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= AがBと同じか小さい"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= AがBと同じか大きい"
_gtEq:
arg1: "A"
arg2: "B"
if: "分岐"
_if:
arg1: "もし"
arg2: "なら"
arg3: "そうでなければ"
not: "否定"
_not:
arg1: "否定"
random: "ランダム"
_random:
arg1: "確率"
rannum: "乱数"
_rannum:
arg1: "最小"
arg2: "最大"
randomPick: "リストからランダムに選ぶ"
_randomPick:
arg1: "リスト"
dailyRandom: "ランダム (ユーザーごとに日替わり)"
_dailyRandom:
arg1: "確率"
dailyRannum: "乱数 (ユーザーごとに日替わり)"
_dailyRannum:
arg1: "最小"
arg2: "最大"
dailyRandomPick: "リストからランダムに選ぶ (ユーザーごとに日替わり)"
_dailyRandomPick:
arg1: "リスト"
seedRandom: "ランダム (シード)"
_seedRandom:
arg1: "シード"
arg2: "確率"
seedRannum: "乱数 (シード)"
_seedRannum:
arg1: "シード"
arg2: "最小"
arg3: "最大"
seedRandomPick: "リストからランダムに選択 (シード)"
_seedRandomPick:
arg1: "シード"
arg2: "リスト"
DRPWPM: "確率付きリストからランダムに選ぶ (ユーザーごとに日替わり)"
_DRPWPM:
arg1: "テキストのリスト"
pick: "リストから選ぶ"
_pick:
arg1: "リスト"
arg2: "位置"
listLen: "リストの長さを取得"
_listLen:
arg1: "リスト"
number: "数値"
stringToNumber: "テキストを数値に"
_stringToNumber:
arg1: "テキスト"
numberToString: "数値をテキストに"
_numberToString:
arg1: "数値"
splitStrByLine: "テキストを行で分割"
_splitStrByLine:
arg1: "テキスト"
ref: "変数"
aiScriptVar: "AiScript変数"
fn: "関数"
_fn:
slots: "スロット"
arg1: "出力"
for: "繰り返し"
_for:
arg1: "回数"
arg2: "処理"
thereIsEmptySlot: "スロット{slot}が空っぽやで!"
types:
string: "テキスト"
number: "数値"
boolean: "フラグ"
array: "リスト"
stringArray: "テキストのリスト"
emptySlot: "空のスロット"
enviromentVariables: "環境変数"
pageVariables: "ページ要素"
argVariables: "入力スロット"
_notification:
fileUploaded: "ファイルが無事アップロードされたで。"
youGotMention: "{name}からのメンション"

View file

@ -88,6 +88,28 @@ _pages:
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
eyeCatchingImageRemove: "Kkes tugna i d-ijebden"
selectType: "Fren anaw"
contentBlocks: "Agbur"
inputBlocks: "Anekcum"
specialBlocks: "Uzzig"
script:
categories:
list: "Tibdarin"
blocks:
_join:
arg1: "Tibdarin"
_randomPick:
arg1: "Tibdarin"
_dailyRandomPick:
arg1: "Tibdarin"
_seedRandomPick:
arg2: "Tibdarin"
_pick:
arg1: "Tibdarin"
_listLen:
arg1: "Tibdarin"
types:
array: "Tibdarin"
_notification:
youWereFollowed: "Yeṭṭafaṛ-ik·em-id"
_types:

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "어제보다"
appearance: "모양"
clientSettings: "클라이언트 설정"
accountSettings: "계정 설정"
promotion: "프로모션"
promote: "프로모션하기"
numberOfDays: "며칠동안"
hideThisNote: "이 노트를 숨기기"
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
@ -547,6 +549,7 @@ poll: "투표"
useCw: "내용 숨기기"
enablePlayer: "플레이어 열기"
disablePlayer: "플레이어 닫기"
expandTweet: "트윗 확장하기"
themeEditor: "테마 에디터"
description: "설명"
describeFile: "캡션 추가"
@ -1323,7 +1326,10 @@ _pages:
my: "내 페이지"
liked: "좋아요한 페이지"
featured: "인기"
inspector: "인스펙터"
contents: "콘텐츠"
content: "페이지 블록"
variables: "변수"
title: "제목"
url: "페이지 URL"
summary: "페이지 요약"
@ -1334,6 +1340,262 @@ _pages:
fontSansSerif: "고딕체"
eyeCatchingImageSet: "아이캐치 이미지를 설정"
eyeCatchingImageRemove: "아이캐치 이미지를 삭제"
chooseBlock: "블록 추가"
selectType: "종류 선택"
enterVariableName: "변수명을 지정해주세요"
variableNameIsAlreadyUsed: "해당 변수명은 이미 사용중입니다"
contentBlocks: "콘텐츠"
inputBlocks: "입력"
specialBlocks: "특수"
blocks:
text: "텍스트"
textarea: "텍스트 영역"
section: "섹션"
image: "이미지"
button: "버튼"
if: "조건문"
_if:
variable: "변수"
post: "글 입력란"
_post:
text: "내용"
attachCanvasImage: "캔버스의 이미지와 함께 게시하기"
canvasId: "캔버스 ID"
textInput: "텍스트 입력"
_textInput:
name: "변수명"
text: "제목"
default: "기본값"
textareaInput: "여러 줄 텍스트 입력"
_textareaInput:
name: "변수명"
text: "제목"
default: "기본값"
numberInput: "수치 입력"
_numberInput:
name: "변수명"
text: "제목"
default: "기본값"
canvas: "캔버스"
_canvas:
id: "캔버스 ID"
width: "폭"
height: "높이"
note: "노트필기"
_note:
id: "노트 ID"
idDescription: "노트 URL을 붙여넣어 설정할 수도 있습니다."
detailed: "세부 정보 보기"
switch: "스위치"
_switch:
name: "변수명"
text: "제목"
default: "기본값"
counter: "카운터"
_counter:
name: "변수명"
text: "제목"
inc: "증가치"
_button:
text: "제목"
colored: "색 입히기"
action: "버튼을 눌렀을 때의 동작"
_action:
dialog: "대화상자를 표시"
_dialog:
content: "내용"
resetRandom: "난수를 초기화"
pushEvent: "이벤트 보내기"
_pushEvent:
event: "이벤트 이름"
message: "눌렀을 때 표시할 페이지"
variable: "보낼 변수"
no-variable: "없음"
callAiScript: "AiScript 호출"
_callAiScript:
functionName: "함수명"
radioButton: "선택지"
_radioButton:
name: "변수명"
title: "제목"
values: "줄바꿈으로 구분된 선택지"
default: "기본값"
script:
categories:
flow: "흐름 제어"
logical: "논리 연산"
operation: "계산"
comparison: "비교"
random: "랜덤"
value: "값"
fn: "함수"
text: "텍스트 조작"
convert: "변환"
list: "리스트"
blocks:
text: "텍스트"
multiLineText: "텍스트 (여러 줄)"
textList: "텍스트 목록"
_textList:
info: "각각을 줄바꿈으로 구분해주세요"
strLen: "텍스트의 길이"
_strLen:
arg1: "텍스트"
strPick: "문자 추출"
_strPick:
arg1: "텍스트"
arg2: "문자 위치"
strReplace: "텍스트 대체"
_strReplace:
arg1: "텍스트"
arg2: "대체될 텍스트"
arg3: "대체할 텍스트"
strReverse: "텍스트 뒤집기"
_strReverse:
arg1: "텍스트"
join: "텍스트 합치기"
_join:
arg1: "리스트"
arg2: "구분자"
add: "더하기"
_add:
arg1: "A"
arg2: "B"
subtract: "빼기"
_subtract:
arg1: "A"
arg2: "B"
multiply: "곱하기"
_multiply:
arg1: "A"
arg2: "B"
divide: "나누기"
_divide:
arg1: "A"
arg2: "B"
mod: "나눈 나머지"
_mod:
arg1: "A"
arg2: "B"
round: "소수점을 반올림"
_round:
arg1: "수치"
eq: "A와 B가 동일"
_eq:
arg1: "A"
arg2: "B"
notEq: "A와 B가 다름"
_notEq:
arg1: "A"
arg2: "B"
and: "A와 B가 둘 다 참"
_and:
arg1: "A"
arg2: "B"
or: "A, B중 하나 이상이 참"
_or:
arg1: "A"
arg2: "B"
lt: "< A가 B보다 작음"
_lt:
arg1: "A"
arg2: "B"
gt: "> A가 B보다 큼"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A가 B보다 작거나 같음"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A가 B보다 크거나 같음"
_gtEq:
arg1: "A"
arg2: "B"
if: "분기"
_if:
arg1: "조건문"
arg2: "참일 경우"
arg3: "거짓일 경우"
not: "부정"
_not:
arg1: "부정"
random: "랜덤"
_random:
arg1: "확률"
rannum: "난수"
_rannum:
arg1: "최솟값"
arg2: "최댓값"
randomPick: "목록에서 임의로 선택"
_randomPick:
arg1: "리스트"
dailyRandom: "랜덤 (하루동안 결과 유지)"
_dailyRandom:
arg1: "확률"
dailyRannum: "난수 (하루동안 결과 유지)"
_dailyRannum:
arg1: "최솟값"
arg2: "최댓값"
dailyRandomPick: "목록에서 임의로 선택 (하루동안 결과 유지)"
_dailyRandomPick:
arg1: "리스트"
seedRandom: "무작위 (시드)"
_seedRandom:
arg1: "시드"
arg2: "확률"
seedRannum: "난수 (시드)"
_seedRannum:
arg1: "시드"
arg2: "최솟값"
arg3: "최댓값"
seedRandomPick: "목록에서 무작위로 선택 (시드)"
_seedRandomPick:
arg1: "시드"
arg2: "리스트"
DRPWPM: "확률형 목록에서 임의로 선택 (하루동안 결과 유지)"
_DRPWPM:
arg1: "텍스트 목록"
pick: "목록에서 선택"
_pick:
arg1: "리스트"
arg2: "위치"
listLen: "리스트의 길이 가져오기"
_listLen:
arg1: "리스트"
number: "수치"
stringToNumber: "텍스트를 수치로"
_stringToNumber:
arg1: "텍스트"
numberToString: "수치를 텍스트로"
_numberToString:
arg1: "수치"
splitStrByLine: "텍스트를 행 단위로 분할"
_splitStrByLine:
arg1: "텍스트"
ref: "변수"
aiScriptVar: "AiScript 변수"
fn: "함수"
_fn:
slots: "슬롯"
slots-info: "각 슬롯을 줄바꿈으로 구분하여 주세요"
arg1: "출력"
for: "반복"
_for:
arg1: "횟수"
arg2: "처리"
typeError: "슬롯 {slot}은 \"{expect}\"를 사용할 수 있지만 \"{actual}이 들어있습니다!"
thereIsEmptySlot: "슬롯 {slot}이(가) 비었습니다!"
types:
string: "텍스트"
number: "수치"
boolean: "플래그"
array: "리스트"
stringArray: "텍스트 목록"
emptySlot: "빈 슬롯"
enviromentVariables: "환경 변수"
pageVariables: "페이지 요소"
argVariables: "입력 슬롯"
_relayStatus:
requesting: "대기 중"
accepted: "승인됨"

View file

@ -343,6 +343,27 @@ _charts:
federation: "Federatie"
_timelines:
home: "Startpagina"
_pages:
blocks:
image: "Afbeeldingen"
script:
categories:
list: "Lijsten"
blocks:
_join:
arg1: "Lijsten"
_randomPick:
arg1: "Lijsten"
_dailyRandomPick:
arg1: "Lijsten"
_seedRandomPick:
arg2: "Lijsten"
_pick:
arg1: "Lijsten"
_listLen:
arg1: "Lijsten"
types:
array: "Lijsten"
_notification:
youWereFollowed: "volgde jou"
_types:

View file

@ -465,6 +465,8 @@ dayOverDayChanges: "Codziennie"
appearance: "Wygląd"
clientSettings: "Ustawienia klienta"
accountSettings: "Ustawienia konta"
promotion: "Promowane"
promote: "Promuj"
numberOfDays: "Liczba dni"
hideThisNote: "Ukryj ten wpis"
showFeaturedNotesInTimeline: "Pokazuj wyróżnione wpisy w osi czasu"
@ -534,6 +536,7 @@ poll: "Ankieta"
useCw: "Ukryj zawartość"
enablePlayer: "Otwórz odtwarzacz wideo"
disablePlayer: "Zamknij odtwarzacz wideo"
expandTweet: "Rozwiń tweet"
themeEditor: "Edytor motywu"
description: "Opis"
describeFile: "dodaj podpis"
@ -1113,7 +1116,10 @@ _pages:
my: "Moje strony"
liked: "Polubione strony"
featured: "Wyróżnione"
inspector: "Inspektor"
contents: "Zawartość"
content: "Blokada strony"
variables: "Zmienne"
title: "Tytuł"
url: "URL strony"
summary: "Podsumowanie strony"
@ -1124,6 +1130,243 @@ _pages:
fontSansSerif: "Bezszeryfowa"
eyeCatchingImageSet: "Ustaw przyciągające wzrok zdjęcie"
eyeCatchingImageRemove: "Usuń przyciągające wzrok zdjęcie"
chooseBlock: "Dodaj blok"
selectType: "Wybierz typ"
enterVariableName: "Wprowadź nazwę dla swojej zmiennej"
variableNameIsAlreadyUsed: "Ta nazwa jest już używana przez inną zmienną"
contentBlocks: "Zawartość"
inputBlocks: "Wejście"
specialBlocks: "Specjalne"
blocks:
text: "Tekst"
textarea: "Pole tekstowe"
section: "Sekcja"
image: "Zdjęcia"
button: "Przycisk"
if: "Jeżeli"
_if:
variable: "Zmienna"
post: "Utwórz wpis"
_post:
text: "Treść"
textInput: "Pole tekstowe"
_textInput:
name: "Nazwa zmiennej"
text: "Tytuł"
default: "Domyślna wartość"
textareaInput: "Pole tekstowe na wiele wierszy"
_textareaInput:
name: "Nazwa zmiennej"
text: "Tytuł"
default: "Domyślna wartość"
numberInput: "Pole na liczbę"
_numberInput:
name: "Nazwa zmiennej"
text: "Tytuł"
default: "Domyślna wartość"
_canvas:
width: "Szerokość"
height: "Wysokość"
note: "Osadzony wpis"
_note:
id: "ID wpisu"
idDescription: "Możesz też wkleić adres URL wpisu, aby go ustawić."
detailed: "Szczegółowy widok"
switch: "Przełącznik"
_switch:
name: "Nazwa zmiennej"
text: "Tytuł"
default: "Domyślna wartość"
counter: "Licznik"
_counter:
name: "Nazwa zmiennej"
text: "Tytuł"
inc: "Zwiększ o"
_button:
text: "Tytuł"
colored: "Kolorowe"
action: "Działanie wykonywane przy naciśnięciu przycisku"
_action:
dialog: "Pokazuj okno dialogowe"
_dialog:
content: "Treść"
resetRandom: "Resetuj losowe ziarno"
pushEvent: "Wyślij zdarzenie"
_pushEvent:
event: "Nazwa zdarzenia"
message: "Wiadomość do wyświetlenia po aktywowaniu"
variable: "Zmienna do wysłania"
no-variable: "Brak"
callAiScript: "Wywołaj AiScript"
_callAiScript:
functionName: "Nazwa funkcji"
radioButton: "Wybór"
_radioButton:
name: "Nazwa zmiennej"
title: "Tytuł"
values: "Lista wyborów (oddzielonych znakiem nowego wiersza)"
default: "Domyślna wartość"
script:
categories:
flow: "Kontrola przepływu"
logical: "Operacje logiczne"
operation: "Obliczanie"
comparison: "Porównanie"
random: "Losowe"
value: "Wartość"
fn: "Funkcje"
text: "Działania na tekście"
convert: "Transformacja"
list: "Listy"
blocks:
text: "Tekst"
multiLineText: "Tekst (w wielu wierszach)"
_textList:
info: "Oddziel każdy wpis znakiem nowego wiersza"
strLen: "Długość tekstu"
_strLen:
arg1: "Tekst"
_strPick:
arg1: "Tekst"
arg2: "Położenie znaku"
strReplace: "Zamiana tekstu"
_strReplace:
arg1: "Tekst"
arg2: "Tekst do zamiany"
arg3: "Zamieniono z"
_strReverse:
arg1: "Tekst"
_join:
arg1: "Listy"
arg2: "Odstęp"
add: "Dodaj"
_add:
arg1: "A"
arg2: "B"
subtract: "Odejmij"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Pomnóż"
_multiply:
arg1: "A"
arg2: "B"
divide: "Podziel"
_divide:
arg1: "A"
arg2: "B"
mod: "Reszta"
_mod:
arg1: "A"
arg2: "B"
_round:
arg1: "Liczba"
eq: "A i B są sobie równe"
_eq:
arg1: "A"
arg2: "B"
notEq: "A i B różnią się"
_notEq:
arg1: "A"
arg2: "B"
and: "A I B"
_and:
arg1: "A"
arg2: "B"
or: "A LUB B"
_or:
arg1: "A"
arg2: "B"
lt: "< A jest mniejsze niż B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A jest większe od B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A jest mniejsze lub równe B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A jest większe lub równe B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Warunek"
_if:
arg1: "Jeżeli"
arg2: "Jeżeli prawda"
not: "NIE"
_not:
arg1: "NIE"
random: "Losowe"
_random:
arg1: "Prawdopodobieństwo"
rannum: "Losowa liczba"
_rannum:
arg1: "Minimalna wartość"
arg2: "Maksymalna wartość"
randomPick: "Wybierz losowo z listy"
_randomPick:
arg1: "Listy"
dailyRandom: "Losowo (zostaje na dzień)"
_dailyRandom:
arg1: "Prawdopodobieństwo"
dailyRannum: "Losowa liczba (zostaje na dzień)"
_dailyRannum:
arg1: "Minimalna wartość"
arg2: "Maksymalna wartość"
dailyRandomPick: "Wybierz losowo z listy (zostaje na dzień)"
_dailyRandomPick:
arg1: "Listy"
seedRandom: "Losowo (z ziarnem)"
_seedRandom:
arg1: "Ziarno"
arg2: "Prawdopodobieństwo"
seedRannum: "Losowa liczba (z ziarnem)"
_seedRannum:
arg1: "Ziarno"
arg2: "Minimalna wartość"
arg3: "Maksymalna wartość"
seedRandomPick: "Wybierz losowo z listy (z ziarnem)"
_seedRandomPick:
arg1: "Ziarno"
arg2: "Listy"
DRPWPM: "Wybierz losowo z ważonej listy (zostaje na dzień)"
pick: "Wybierz z listy"
_pick:
arg1: "Listy"
arg2: "Położenie"
listLen: "Uzyskaj długość listy"
_listLen:
arg1: "Listy"
number: "Liczba"
stringToNumber: "Tekst na liczbę"
_stringToNumber:
arg1: "Tekst"
numberToString: "Liczba na tekst"
_numberToString:
arg1: "Liczba"
splitStrByLine: "Rozdziel tekst znakami nowej linii"
_splitStrByLine:
arg1: "Tekst"
ref: "Zmienne"
aiScriptVar: "Zmienna AiScript"
fn: "Funkcje"
_fn:
arg1: "Wyjście"
for: "Powtórzenie"
_for:
arg1: "Liczba powtórzeń"
arg2: "Działanie"
types:
string: "Tekst"
number: "Liczba"
boolean: "Flaguj"
array: "Listy"
enviromentVariables: "Zmienna środowiskowa"
pageVariables: "Element strony"
_relayStatus:
requesting: "Oczekujące"
accepted: "Zaakceptowano"

View file

@ -190,6 +190,173 @@ _exportOrImport:
muteList: "Silenciar"
blockingList: "Bloquear"
userLists: "Listas"
_pages:
blocks:
_button:
_action:
_pushEvent:
event: "Nome do evento"
message: "Mostrar mensagem quando ativado"
variable: "Variável a mandar"
no-variable: "Nenhum"
callAiScript: "Invocar AiScript"
_callAiScript:
functionName: "Nome da função"
radioButton: "Escolha"
_radioButton:
values: "Lista de escolhas separadas por quebras de texto"
script:
categories:
logical: "Operação lógica"
operation: "Cálculos"
comparison: "Comparação"
list: "Listas"
blocks:
_strReplace:
arg2: "Texto que irá ser substituído"
arg3: "Substituir com"
strReverse: "Virar texto"
join: "Sequência de texto"
_join:
arg1: "Listas"
arg2: "Separador"
add: "Somar"
_add:
arg1: "A"
arg2: "B"
subtract: "Subtrair"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiplicar"
_multiply:
arg1: "A"
arg2: "B"
divide: "Dividir"
_divide:
arg1: "A"
arg2: "B"
mod: "O resto de"
_mod:
arg1: "A"
arg2: "B"
round: "Arredondar decimal"
_round:
arg1: "Numérico"
eq: "A e B são iguais"
_eq:
arg1: "A"
arg2: "B"
notEq: "A e B são diferentes"
_notEq:
arg1: "A"
arg2: "B"
and: "A e B"
_and:
arg1: "A"
arg2: "B"
or: "A OU B"
_or:
arg1: "A"
arg2: "B"
lt: "< A é menor do que B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A é maior do que B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A é maior ou igual a B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A é maior ou igual a B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Galho"
_if:
arg1: "Se"
arg2: "Então"
arg3: "Se não"
not: "NÃO"
_not:
arg1: "NÃO"
random: "Aleatório"
_random:
arg1: "Probabilidade"
rannum: "Numeral aleatório"
_rannum:
arg1: "Valor mínimo"
arg2: "Valor máximo"
randomPick: "Escolher aleatoriamente de uma lista"
_randomPick:
arg1: "Listas"
dailyRandom: "Aleatório (Muda uma vez por dia para cada usuário)"
_dailyRandom:
arg1: "Probabilidade"
dailyRannum: "Numeral aleatório (Muda uma vez por dia para cada usuário)"
_dailyRannum:
arg1: "Valor mínimo"
arg2: "Valor máximo"
dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuário)"
_dailyRandomPick:
arg1: "Listas"
seedRandom: "Aleatório (com semente)"
_seedRandom:
arg1: "Semente"
arg2: "Probabilidade"
seedRannum: "Número aleatório (com semente)"
_seedRannum:
arg1: "Semente"
arg2: "Valor mínimo"
arg3: "Valor máximo"
seedRandomPick: "Escolher aleatoriamente de uma lista (com uma semente)"
_seedRandomPick:
arg1: "Semente"
arg2: "Listas"
DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuário)"
_DRPWPM:
arg1: "Lista de texto"
pick: "Escolhe a partir da lista"
_pick:
arg1: "Listas"
arg2: "Posição"
listLen: "Pegar comprimento da lista"
_listLen:
arg1: "Listas"
number: "Numérico"
stringToNumber: "Texto para numérico"
_stringToNumber:
arg1: "Texto"
numberToString: "Numérico para texto"
_numberToString:
arg1: "Numérico"
splitStrByLine: "Dividir texto por quebras"
_splitStrByLine:
arg1: "Texto"
ref: "Variável"
aiScriptVar: "Variável AiScript"
fn: "Função"
_fn:
slots: "Espaços"
slots-info: "Separar cada espaço com uma quebra de texto"
arg1: "Resultado"
for: "Repetição 'for'"
_for:
arg1: "Número de repetições"
arg2: "Ação"
typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado é do tipo \"{actual}\"!"
thereIsEmptySlot: "O espaço {slot} está vazio!"
types:
string: "Texto"
number: "Numérico"
array: "Listas"
stringArray: "Lista de texto"
emptySlot: "Espaço vazio"
enviromentVariables: "Variáveis de ambiente"
pageVariables: "Variáveis de página"
_relayStatus:
requesting: "Pendente"
accepted: "Aprovado"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "Schimbări până ieri"
appearance: "Aspect"
clientSettings: "Setări client"
accountSettings: "Setări cont"
promotion: "Promovat"
promote: "Promovează"
numberOfDays: "Numărul zilelor"
hideThisNote: "Ascunde această notă"
showFeaturedNotesInTimeline: "Arată notele recomandate în cronologii"
@ -547,6 +549,7 @@ poll: "Sondaj"
useCw: "Ascunde conținutul"
enablePlayer: "Deschide player-ul video"
disablePlayer: "Închide player-ul video"
expandTweet: "Expandează tweet"
themeEditor: "Editor de teme"
description: "Descriere"
describeFile: "Adaugă titrări"
@ -680,6 +683,27 @@ _charts:
federation: "Federație"
_timelines:
home: "Acasă"
_pages:
blocks:
image: "Imagini"
script:
categories:
list: "Liste"
blocks:
_join:
arg1: "Liste"
_randomPick:
arg1: "Liste"
_dailyRandomPick:
arg1: "Liste"
_seedRandomPick:
arg2: "Liste"
_pick:
arg1: "Liste"
_listLen:
arg1: "Liste"
types:
array: "Liste"
_notification:
youWereFollowed: "te-a urmărit"
youWereInvitedToGroup: "Ai fost invitat într-un grup"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "За день"
appearance: "Внешний вид"
clientSettings: "Настройки клиента"
accountSettings: "Настройки учетной записи"
promotion: "Продвинуто"
promote: "Продвинуть"
numberOfDays: "Количество дней"
hideThisNote: "Спрятать эту запись"
showFeaturedNotesInTimeline: "Показывать в ленте заметки из «Горячего»"
@ -547,6 +549,7 @@ poll: "Опрос"
useCw: "Скрывать содержимое под предупреждением"
enablePlayer: "Включить проигрыватель"
disablePlayer: "Выключить проигрыватель"
expandTweet: "Развернуть твит"
themeEditor: "Редактор темы оформления"
description: "Описание"
describeFile: "Добавить подпись"
@ -1306,7 +1309,10 @@ _pages:
my: "Свои страницы"
liked: "Понравившиеся страницы"
featured: "Популярные"
inspector: "Инспектор"
contents: "Содержимое"
content: "Содержимое"
variables: "Переменные"
title: "Заголовок"
url: "Адрес страницы"
summary: "Краткое содержание"
@ -1317,6 +1323,262 @@ _pages:
fontSansSerif: "Гротеск (без засечек)"
eyeCatchingImageSet: "Добавить картинку для привлечения внимания"
eyeCatchingImageRemove: "Убрать картинку для привлечения внимания"
chooseBlock: "Добавить блок"
selectType: "Выберите вид"
enterVariableName: "Ведите имя переменной"
variableNameIsAlreadyUsed: "Это имя уже есть у другой переменной"
contentBlocks: "Содержательные"
inputBlocks: "Для ввода"
specialBlocks: "Особые"
blocks:
text: "Текст"
textarea: "Текст в рамке"
section: "Раздел"
image: "Изображения"
button: "Кнопка"
if: "Условный"
_if:
variable: "Переменная"
post: "Создание заметки"
_post:
text: "Текст"
attachCanvasImage: "Прикрепить изображение с холста"
canvasId: "Метка холста"
textInput: "Поле ввода текста"
_textInput:
name: "Имя переменной"
text: "Подпись"
default: "Исходное содержимое"
textareaInput: "Многострочное поле ввода текста"
_textareaInput:
name: "Имя переменной"
text: "Подпись"
default: "Исходное содержимое"
numberInput: "Поле для ввода числа"
_numberInput:
name: "Имя переменной"
text: "Подпись"
default: "Исходное значение"
canvas: "Холст"
_canvas:
id: "Метка холста"
width: "Ширина"
height: "Высота"
note: "Встроенная заметка"
_note:
id: "Идентификатор заметки"
idDescription: "Можно также вставить ссылку на заметку."
detailed: "Подробный вид"
switch: "Выключатель"
_switch:
name: "Имя переменной"
text: "Подпись"
default: "Исходное содержимое"
counter: "Кнопка со счётчиком"
_counter:
name: "Имя переменной"
text: "Надпись"
inc: "Увеличивать на"
_button:
text: "Надпись"
colored: "Выделена цветом"
action: "Действие по нажатию"
_action:
dialog: "Показать всплывающий текст"
_dialog:
content: "Всплывающий текст"
resetRandom: "Сброс генератора случайности"
pushEvent: "Вызвать событие"
_pushEvent:
event: "Имя события"
message: "Сообщение при нажатии"
variable: "Передать переменную с событием"
no-variable: "нет"
callAiScript: "Вызвать AiScript"
_callAiScript:
functionName: "Имя функции"
radioButton: "Кнопка-переключатель"
_radioButton:
name: "Имя переменной"
title: "Заголовок"
values: "Значения"
default: "Исходное значение"
script:
categories:
flow: "Управление исполнением"
logical: "Логические"
operation: "Арифметические"
comparison: "Сравнение"
random: "Случайные"
value: "Значения"
fn: "Функции"
text: "Текстовые"
convert: "Преобразование"
list: "Список"
blocks:
text: "Строка текста"
multiLineText: "Многострочный текст"
textList: "Список строк текста"
_textList:
info: "Пишите каждый пункт с новой строки"
strLen: "Длина текста"
_strLen:
arg1: "Текст"
strPick: "Взять знак из текста"
_strPick:
arg1: "Текст"
arg2: "Позиция знака"
strReplace: "Замена текста"
_strReplace:
arg1: "Текст, в котором заменять"
arg2: "Заменяемый текст"
arg3: "Менять на"
strReverse: "В обратном порядке"
_strReverse:
arg1: "Текст"
join: "Объединение"
_join:
arg1: "Списки"
arg2: "Разделитель"
add: "Добавить"
_add:
arg1: "A"
arg2: "B"
subtract: "Вычитание"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Умножение"
_multiply:
arg1: "A"
arg2: "B"
divide: "Деление"
_divide:
arg1: "A"
arg2: "B"
mod: "Остаток от деления"
_mod:
arg1: "A"
arg2: "B"
round: "Округление до целого"
_round:
arg1: "Число"
eq: "A равно B"
_eq:
arg1: "А"
arg2: "B"
notEq: "A не равно B"
_notEq:
arg1: "A"
arg2: "B"
and: "A и B"
_and:
arg1: "A"
arg2: "B"
or: "A или B"
_or:
arg1: "A"
arg2: "B"
lt: "A < B (меньше)"
_lt:
arg1: "A"
arg2: "B"
gt: "A > B (больше)"
_gt:
arg1: "A"
arg2: "B"
ltEq: "A ⩽ B (меньше или равно)"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: "A ⩾ B (больше или равно)"
_gtEq:
arg1: "A"
arg2: "B"
if: "Условный"
_if:
arg1: "Условие"
arg2: "Если правда"
arg3: "Если ложь"
not: "Отрицание"
_not:
arg1: "Условие"
random: "Случайность"
_random:
arg1: "Вероятность"
rannum: "Случайное число"
_rannum:
arg1: "Минимум"
arg2: "Максимум"
randomPick: "Случайный выбор из списка"
_randomPick:
arg1: "Списки"
dailyRandom: "Случайность (на день для пользователя)"
_dailyRandom:
arg1: "Вероятность"
dailyRannum: "Случайное число (на день для пользователя)"
_dailyRannum:
arg1: "Минимум"
arg2: "Максимум"
dailyRandomPick: "Случайный выбор из списка (на день для пользователя)"
_dailyRandomPick:
arg1: "Списки"
seedRandom: "Псевдослучайность (заданная зерном)"
_seedRandom:
arg1: "Зерно"
arg2: "Вероятность"
seedRannum: "Псевдослучайное число (заданное зерном)"
_seedRannum:
arg1: "Зерно"
arg2: "Минимум"
arg3: "Максимум"
seedRandomPick: "Псевдослучайный выбор из списка (заданный зерном)"
_seedRandomPick:
arg1: "Зерно"
arg2: "Списки"
DRPWPM: "Случайный выбор из взвешенного списка (на день для пользователя)"
_DRPWPM:
arg1: "Список строк текста"
pick: "Выбор из списка"
_pick:
arg1: "Списки"
arg2: "Индекс"
listLen: "Количество элементов в списке"
_listLen:
arg1: "Списки"
number: "Число"
stringToNumber: "Число из текста"
_stringToNumber:
arg1: "Текст"
numberToString: "Число в текст"
_numberToString:
arg1: "Число"
splitStrByLine: "Разделение текста на строки"
_splitStrByLine:
arg1: "Текст"
ref: "Переменная"
aiScriptVar: "Переменная AiScript"
fn: "Свои функции"
_fn:
slots: "Аргументы"
slots-info: "Напишите имя каждого аргумента с новой строки"
arg1: "Формула"
for: "Цикл"
_for:
arg1: "Количество повторений"
arg2: "Действие"
typeError: "Аргумент {slot} должен быть иметь тип «{expect}», а передали «{actual}»!"
thereIsEmptySlot: "Аргумент {slot} не заполнен!"
types:
string: "Текст"
number: "Число"
boolean: "Логический"
array: "Списки"
stringArray: "Список строк текста"
emptySlot: "Пустой аргумент"
enviromentVariables: "Переменная окружения"
pageVariables: "Элемент страницы"
argVariables: "Аргументы"
_relayStatus:
requesting: "В ожидании одобрения"
accepted: "Одобрено."

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "Medzidenné zmeny"
appearance: "Vzhľad"
clientSettings: "Nastavenia klienta"
accountSettings: "Nastavenia účtu"
promotion: "Propagácia"
promote: "Propagovať"
numberOfDays: "Počet dní"
hideThisNote: "Skryť túto poznámku"
showFeaturedNotesInTimeline: "Zobraziť významné poznámky v časovej osi"
@ -547,6 +549,7 @@ poll: "Hlasovanie"
useCw: "Skryť obsah"
enablePlayer: "Otvoriť video prehrávač"
disablePlayer: "Zavrieť video prehrávač"
expandTweet: "Rozšíriť tweet"
themeEditor: "Editor tém"
description: "Popis"
describeFile: "Pridať nadpis"
@ -1320,7 +1323,10 @@ _pages:
my: "Moje stránky"
liked: "Obľúbené stránky"
featured: "Význačné"
inspector: "Inšpektor"
contents: "Obsah"
content: "Blok stránky"
variables: "Premenné"
title: "Nadpis"
url: "URL stránky"
summary: "Zhrnutie stránky"
@ -1331,6 +1337,262 @@ _pages:
fontSansSerif: "Bezpätkové"
eyeCatchingImageSet: "Nastaviť miniatúru"
eyeCatchingImageRemove: "Odstrániť miniatúru"
chooseBlock: "Pridať blok"
selectType: "Vyberte typ"
enterVariableName: "Zadajte meno premennej"
variableNameIsAlreadyUsed: "Meno premennej s už používa"
contentBlocks: "Obsah"
inputBlocks: "Vstup"
specialBlocks: "Špeciálne"
blocks:
text: "Text"
textarea: "Textové pole"
section: "Sekcia"
image: "Obrázky"
button: "Tlačidlo"
if: "Ak"
_if:
variable: "Premenné"
post: "Napísať poznámku"
_post:
text: "Obsah"
attachCanvasImage: "Príspevok s obrázkom na plátne"
canvasId: "ID plátna"
textInput: "Textový vstup"
_textInput:
name: "Meno premennej"
text: "Nadpis"
default: "Predvolená hodnota"
textareaInput: "Viacriadkový textový vstup"
_textareaInput:
name: "Meno premennej"
text: "Nadpis"
default: "Predvolená hodnota"
numberInput: "Číselný vstup"
_numberInput:
name: "Meno premennej"
text: "Nadpis"
default: "Predvolená hodnota"
canvas: "Plátno"
_canvas:
id: "ID plátna"
width: "Šírka"
height: "Výška"
note: "Vložená poznámka"
_note:
id: "ID poznámky"
idDescription: "Alebo môžete vložiť URL poznámky sem"
detailed: "Podrobný pohľad"
switch: "Prepnúť"
_switch:
name: "Meno premennej"
text: "Nadpis"
default: "Predvolená hodnota"
counter: "Počítadlo"
_counter:
name: "Meno premennej"
text: "Nadpis"
inc: "Pripočítať"
_button:
text: "Nadpis"
colored: "Farebné"
action: "Operácia po stlačení tlačidla"
_action:
dialog: "Zobraziť dialóg"
_dialog:
content: "Obsah"
resetRandom: "Resetovať zdroj náhodnosti"
pushEvent: "Poslať udalosť"
_pushEvent:
event: "Názov udalosti"
message: "Zobrazená správa po aktivácii"
variable: "Odoslaná premenná"
no-variable: "Žiadne"
callAiScript: "Spustiť AiScript"
_callAiScript:
functionName: "Názov funkcie"
radioButton: "Možnosť"
_radioButton:
name: "Meno premennej"
title: "Nadpis"
values: "Zoznam možností oddelené novými riadkami"
default: "Predvolená hodnota"
script:
categories:
flow: "Riadenie behu"
logical: "Logická operácia"
operation: "Výpočet"
comparison: "Porovnanie"
random: "Náhodné"
value: "Hodnoty"
fn: "Funkcie"
text: "Textové operácie"
convert: "Transformácie"
list: "Zoznamy"
blocks:
text: "Text"
multiLineText: "Text (viacriadkový)"
textList: "Zoznam textov"
_textList:
info: "Oddeľte každú položku novým riadkom"
strLen: "Dĺžka textu"
_strLen:
arg1: "Text"
strPick: "Vybrať znak"
_strPick:
arg1: "Text"
arg2: "Pozícia znaku"
strReplace: "Náhradný text"
_strReplace:
arg1: "Text"
arg2: "Nahradený text"
arg3: "Nahradiť s"
strReverse: "Otočiť text"
_strReverse:
arg1: "Text"
join: "Spojiť texty"
_join:
arg1: "Zoznamy"
arg2: "Oddeľovač"
add: "Pridať"
_add:
arg1: "A"
arg2: "B"
subtract: "Odčítať"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Násobiť"
_multiply:
arg1: "A"
arg2: "B"
divide: "Deliť"
_divide:
arg1: "A"
arg2: "B"
mod: "Zvyšok po delení"
_mod:
arg1: "A"
arg2: "B"
round: "Zaokrúhliť"
_round:
arg1: "Číslo"
eq: "A a B sa rovnajú"
_eq:
arg1: "A"
arg2: "B"
notEq: "A a B sa nerovnajú"
_notEq:
arg1: "A"
arg2: "B"
and: "A a zároveň B"
_and:
arg1: "A"
arg2: "B"
or: "A alebo B"
_or:
arg1: "A"
arg2: "B"
lt: "< A je menšie ako B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A je väčšie ako B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A je menšie alebo rovné B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A je väčšie alebo rovné B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Vetva"
_if:
arg1: "Ak"
arg2: "Potom"
arg3: "Inak"
not: "Opak"
_not:
arg1: "Opak"
random: "Náhodné"
_random:
arg1: "Pravdepodobnosť"
rannum: "Náhodné číslo"
_rannum:
arg1: "Minimálna hodnota"
arg2: "Maximálna hodnota"
randomPick: "Náhodný výber zo zoznamu"
_randomPick:
arg1: "Zoznam"
dailyRandom: "Náhodne (zmení sa raz denne pre každého používateľa)"
_dailyRandom:
arg1: "Pravdepodobnosť"
dailyRannum: "Náhodné číslo (Mení sa denne pre každého používateľa)"
_dailyRannum:
arg1: "Minimálna hodnota"
arg2: "Maximálna hodnota"
dailyRandomPick: "Náhodný výber zo zoznamu (Mení sa denne pre každého používateľa)"
_dailyRandomPick:
arg1: "Zoznam"
seedRandom: "Náhodne (so seedom)"
_seedRandom:
arg1: "Seed"
arg2: "Pravdepodobnosť"
seedRannum: "Náhodné číslo (so seedom)"
_seedRannum:
arg1: "Seed"
arg2: "Minimálna hodnota"
arg3: "Maximálna hodnota"
seedRandomPick: "Náhodný výber zo zoznamu (so seedom)"
_seedRandomPick:
arg1: "Seed"
arg2: "Zoznam"
DRPWPM: "Náhodný výber z váženého zoznamu (Mení sa denne pre každého používateľa)"
_DRPWPM:
arg1: "Zoznam textov"
pick: "Vybrať zo zoznamu"
_pick:
arg1: "Zoznam"
arg2: "Pozícia"
listLen: "Získať dĺžku zoznamu"
_listLen:
arg1: "Zoznam"
number: "Číslo"
stringToNumber: "Text na číslo"
_stringToNumber:
arg1: "Text"
numberToString: "Číslo na text"
_numberToString:
arg1: "Číslo"
splitStrByLine: "Rozdelí text po riadkoch"
_splitStrByLine:
arg1: "Text"
ref: "Premenné"
aiScriptVar: "AiScript premenná"
fn: "Funkcie"
_fn:
slots: "Sloty"
slots-info: "Oddeľte každý slot novým riadkom"
arg1: "Výstup"
for: "For cyklus"
_for:
arg1: "Počet opakovaní"
arg2: "Akcia"
typeError: "Slot {slot} akceptuje hodnoty typu \"{expect}\", ale dodaná hodnota je typu \"{actual}\"!"
thereIsEmptySlot: "Slot {slot} je prázdny!"
types:
string: "Text"
number: "Číslo"
boolean: "Boolean"
array: "Zoznamy"
stringArray: "Zoznam textov"
emptySlot: "Prázdny slot"
enviromentVariables: "Premenné prostredia"
pageVariables: "Premenné stránky"
argVariables: "Vstupné sloty"
_relayStatus:
requesting: "Čaká sa"
accepted: "Akceptované"

View file

@ -280,6 +280,25 @@ _exportOrImport:
userLists: "Listor"
_charts:
federation: "Federation"
_pages:
script:
categories:
list: "Listor"
blocks:
_join:
arg1: "Listor"
_randomPick:
arg1: "Listor"
_dailyRandomPick:
arg1: "Listor"
_seedRandomPick:
arg2: "Listor"
_pick:
arg1: "Listor"
_listLen:
arg1: "Listor"
types:
array: "Listor"
_notification:
youWereFollowed: "följde dig"
_types:

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "Доба"
appearance: "Вигляд"
clientSettings: "Налаштування клієнта"
accountSettings: "Налаштування акаунта"
promotion: "Виділене"
promote: "Виділити"
numberOfDays: "Кількість днів"
hideThisNote: "Сховати цю нотатку"
showFeaturedNotesInTimeline: "Показувати популярні нотатки у стрічці"
@ -547,6 +549,7 @@ poll: "Опитування"
useCw: "Приховати вміст"
enablePlayer: "Відкрити відеоплеєр"
disablePlayer: "Закрити відеоплеєр"
expandTweet: "Розгорнути твіт"
themeEditor: "Редактор тем"
description: "Опис"
describeFile: "Додати підпис"
@ -1124,7 +1127,10 @@ _pages:
my: "Мої сторінки"
liked: "Вподобані сторінки"
featured: "Популярні"
inspector: "Інспектор"
contents: "Вміст"
content: "Блок сторінки"
variables: "Змінні"
title: "Заголовок"
url: "URL сторінки"
summary: "Короткий зміст"
@ -1135,6 +1141,261 @@ _pages:
fontSansSerif: "Sans serif"
eyeCatchingImageSet: "Встановити привабливе зображення"
eyeCatchingImageRemove: "Видалити привабливе зображення"
chooseBlock: "Додати блок"
selectType: "Виберіть тип"
enterVariableName: "Введіть назву для змінної"
variableNameIsAlreadyUsed: "Ця назва вже використовується іншою змінною"
contentBlocks: "Контент"
inputBlocks: "Ввід"
specialBlocks: "Особливе"
blocks:
text: "Текст"
textarea: "Текстова область"
section: "Розділ"
image: "Зображення"
button: "Кнопка"
if: "Якщо"
_if:
variable: "Змінні"
post: "Створення нотатки"
_post:
text: "Вміст"
canvasId: "Ідентифікатор полотна"
textInput: "Введення тексту"
_textInput:
name: "Ім'я змінної"
text: "Назва"
default: "Значення за замовчуванням"
textareaInput: "Багаторядкове введення тексту"
_textareaInput:
name: "Ім'я змінної"
text: "Назва"
default: "Значення за замовчуванням"
numberInput: "Числове введення"
_numberInput:
name: "Ім'я змінної"
text: "Назва"
default: "Значення за замовчуванням"
canvas: "Полотно"
_canvas:
id: "Ідентифікатор полотна"
width: "Ширина"
height: "Висота"
note: "Вбудована нотатка"
_note:
id: "Ідентифікатор нотатки"
idDescription: "Також можна вказати посилання на нотатку"
detailed: "Детальний вигляд"
switch: "Перемикач"
_switch:
name: "Ім'я змінної"
text: "Назва"
default: "Значення за замовчуванням"
counter: "Лічильник"
_counter:
name: "Ім'я змінної"
text: "Назва"
inc: "Збільшити на"
_button:
text: "Напис"
colored: "Кольоровий"
action: "Дія кнопки"
_action:
dialog: "Показати повідомлення"
_dialog:
content: "Вміст"
resetRandom: "Скидання генератора випадковості"
pushEvent: "Надіслати подію"
_pushEvent:
event: "Назві події"
message: "Повідомлення для відображення при активації"
variable: "Змінна для надсилання"
no-variable: "Відсутньо"
callAiScript: "Виклик AiScript"
_callAiScript:
functionName: "Ім'я функції"
radioButton: "Вибір"
_radioButton:
name: "Ім'я змінної"
title: "Напис"
values: "Варіанти, розділені розривами рядків"
default: "Значення за замовчуванням"
script:
categories:
flow: "Керування потоком"
logical: "Логічні операції"
operation: "Обчислення"
comparison: "Порівняння"
random: "Випадковість"
value: "Значення"
fn: "Функції"
text: "Дії з текстом"
convert: "Перетворення"
list: "Списки"
blocks:
text: "Текст"
multiLineText: "Текст (багаторядковий)"
textList: "Текстовий список"
_textList:
info: "Використовувати новий рядок як роздільник для вводу"
strLen: "Довжина тексту"
_strLen:
arg1: "Текст"
strPick: "Вибрати символ"
_strPick:
arg1: "Текст"
arg2: "Розташування символу"
strReplace: "Заміна тексту"
_strReplace:
arg1: "Текст"
arg2: "Текст, який потрібно замінити"
arg3: "Заміняти на"
strReverse: "Перевернути текст"
_strReverse:
arg1: "Текст"
join: "Конкатенація тексту"
_join:
arg1: "Списки"
arg2: "Розділювач"
add: "Додати"
_add:
arg1: "A"
arg2: "B"
subtract: "Відняти"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Помножити"
_multiply:
arg1: "A"
arg2: "B"
divide: "Поділити"
_divide:
arg1: "A"
arg2: "B"
mod: "Остача"
_mod:
arg1: "A"
arg2: "B"
round: "Десяткове округлення"
_round:
arg1: "Число"
eq: "A дорівнює B"
_eq:
arg1: "A"
arg2: "B"
notEq: "A не дорівнює B"
_notEq:
arg1: "A"
arg2: "B"
and: "А І Б"
_and:
arg1: "A"
arg2: "B"
or: "A АБО B"
_or:
arg1: "A"
arg2: "B"
lt: "< A менше, ніж B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A більше, ніж B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A менше або дорівнює B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A більше або дорівнює B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Умова"
_if:
arg1: "Якщо"
arg2: "Якщо так"
arg3: "Якщо ні"
not: "НЕ"
_not:
arg1: "НЕ"
random: "Випадково"
_random:
arg1: "Імовірність"
rannum: "Випадкове число"
_rannum:
arg1: "Мінімальне значення"
arg2: "Максимальне значення"
randomPick: "Випадковий вибір зі списку"
_randomPick:
arg1: "Списки"
dailyRandom: "Випадково (триває добу)"
_dailyRandom:
arg1: "Імовірність"
dailyRannum: "Випадкове число (триває добу)"
_dailyRannum:
arg1: "Мінімальне значення"
arg2: "Максимальне значення"
dailyRandomPick: "Випадково вибрати зі списку (триває добу)"
_dailyRandomPick:
arg1: "Списки"
seedRandom: "Випадковість (з насінням)"
_seedRandom:
arg1: "Насіння"
arg2: "Імовірність"
seedRannum: "Випадкове число (з насінням)"
_seedRannum:
arg1: "Насіння"
arg2: "Мінімальне значення"
arg3: "Максимальне значення"
seedRandomPick: "Випадково вибрати зі списку (з насінням)"
_seedRandomPick:
arg1: "Насіння"
arg2: "Списки"
DRPWPM: "Випадково вибрати зі зваженого списку (триває добу)"
_DRPWPM:
arg1: "Текстовий список"
pick: "Вибір зі списку"
_pick:
arg1: "Списки"
arg2: "Позиція"
listLen: "Отримати довжину списку"
_listLen:
arg1: "Списки"
number: "Число"
stringToNumber: "Текст на число"
_stringToNumber:
arg1: "Текст"
numberToString: "Число на текст"
_numberToString:
arg1: "Число"
splitStrByLine: "Розбиття тексту на рядки"
_splitStrByLine:
arg1: "Текст"
ref: "Змінні"
aiScriptVar: "Змінна AiScript"
fn: "Функції"
_fn:
slots: "Паз"
slots-info: "Використовувати нову лінію як роздільник пазів"
arg1: "Вивід"
for: "Повторення"
_for:
arg1: "Кількість повторень"
arg2: "Дія"
typeError: "Паз {slot} приймає \"{expect}\" тип, але надана змінна має тип \"{actual}\"!"
thereIsEmptySlot: "Паз {slot} пустий!"
types:
string: "Текст"
number: "Число"
boolean: "Прапорець"
array: "Списки"
stringArray: "Текстовий список"
emptySlot: "Пустий паз"
enviromentVariables: "Змінні середовища"
pageVariables: "Елемент сторінки"
argVariables: "Стрічка вводу"
_relayStatus:
requesting: "Очікує затвердження"
accepted: "Затверджено"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "Thay đổi hôm qua"
appearance: "Giao diện"
clientSettings: "Cài đặt Client"
accountSettings: "Cài đặt tài khoản"
promotion: "Quảng cáo"
promote: "Quảng cáo"
numberOfDays: "Số ngày"
hideThisNote: "Ẩn tút này"
showFeaturedNotesInTimeline: "Hiện tút nổi bật trong bảng tin"
@ -547,6 +549,7 @@ poll: "Bình chọn"
useCw: "Ẩn nội dung"
enablePlayer: "Mở trình phát video"
disablePlayer: "Đóng trình phát video"
expandTweet: "Mở rộng tweet"
themeEditor: "Công cụ thiết kế theme"
description: "Mô tả"
describeFile: "Thêm mô tả"
@ -1321,7 +1324,10 @@ _pages:
my: "Trang của tôi"
liked: "Trang đã thích"
featured: "Nổi tiếng"
inspector: "Thanh tra"
contents: "Nội dung"
content: "Chặn Trang"
variables: "Biến thể"
title: "Tựa đề"
url: "URL Trang"
summary: "Mô tả Trang"
@ -1332,6 +1338,262 @@ _pages:
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Đặt ảnh thu nhỏ"
eyeCatchingImageRemove: "Xóa ảnh thu nhỏ"
chooseBlock: "Thêm khối"
selectType: "Chọn kiểu"
enterVariableName: "Nhập tên một biến thể"
variableNameIsAlreadyUsed: "Tên biến thể này đã được sử dụng"
contentBlocks: "Nội dung"
inputBlocks: "Nhập"
specialBlocks: "Đặc biệt"
blocks:
text: "Văn bản"
textarea: "Khu vực văn bản"
section: "Mục "
image: "Hình ảnh"
button: "Nút"
if: "Nếu"
_if:
variable: "Biến thể"
post: "Mẫu đăng"
_post:
text: "Nội dung"
attachCanvasImage: "Đính kèm hình canva"
canvasId: "ID Canva"
textInput: "Văn bản đầu vào"
_textInput:
name: "Tên biến thể"
text: "Tựa đề"
default: "Giá trị mặc định"
textareaInput: "Văn bản nhiều dòng đầu vào"
_textareaInput:
name: "Tên biến thể"
text: "Tựa đề"
default: "Giá trị mặc định"
numberInput: "Đầu vào số"
_numberInput:
name: "Tên biến thể"
text: "Tựa đề"
default: "Giá trị mặc định"
canvas: "Canva"
_canvas:
id: "ID Canva"
width: "Chiều rộng"
height: "Chiều cao"
note: "Tút đã nhúng"
_note:
id: "ID tút"
idDescription: "Ngoài ra, bạn có thể dán URL tút vào đây."
detailed: "Xem chi tiết"
switch: "Chuyển đổi"
_switch:
name: "Tên biến thể"
text: "Tựa đề"
default: "Giá trị mặc định"
counter: "Bộ đếm"
_counter:
name: "Tên biến thể"
text: "Tựa đề"
inc: "Bước"
_button:
text: "Tựa đề"
colored: "Với màu"
action: "Thao tác khi nhấn nút"
_action:
dialog: "Hiện hộp thoại"
_dialog:
content: "Nội dung"
resetRandom: "Đặt lại seed ngẫu nhiên"
pushEvent: "Gửi một sự kiện"
_pushEvent:
event: "Tên sự kiện"
message: "Tin nhắn hiển thị khi kích hoạt"
variable: "Biển thể để gửi"
no-variable: "Không"
callAiScript: "Gọi AiScript"
_callAiScript:
functionName: "Tên tính năng"
radioButton: "Lựa chọn"
_radioButton:
name: "Tên biến thể"
title: "Tựa đề"
values: "Phân tách các mục bằng cách xuống dòng"
default: "Giá trị mặc định"
script:
categories:
flow: "Điều khiển"
logical: "Hoạt động logic"
operation: "Tính toán"
comparison: "So sánh"
random: "Ngẫu nhiên"
value: "Giá trị"
fn: "Tính năng"
text: "Tác vụ văn bản"
convert: "Chuyển đổi"
list: "Danh sách"
blocks:
text: "Văn bản"
multiLineText: "Văn bản (nhiều dòng)"
textList: "Văn bản liệt kê"
_textList:
info: "Phân tách mục bằng cách xuống dòng"
strLen: "Độ dài văn bản"
_strLen:
arg1: "Văn bản"
strPick: "Trích xuất chuỗi"
_strPick:
arg1: "Văn bản"
arg2: "Vị trí chuỗi"
strReplace: "Thay thế chuỗi"
_strReplace:
arg1: "Nội dung"
arg2: "Văn bản thay thế"
arg3: "Thay thế bằng"
strReverse: "Lật văn bản"
_strReverse:
arg1: "Văn bản"
join: "Nối văn bản"
_join:
arg1: "Danh sách"
arg2: "Phân cách"
add: "Cộng"
_add:
arg1: "A"
arg2: "B"
subtract: "Trừ"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Nhân"
_multiply:
arg1: "A"
arg2: "B"
divide: "Chia"
_divide:
arg1: "A"
arg2: "B"
mod: "Phần còn lại"
_mod:
arg1: "A"
arg2: "B"
round: "Làm tròn thập phân"
_round:
arg1: "Số"
eq: "A và B bằng nhau"
_eq:
arg1: "A"
arg2: "B"
notEq: "A và B khác nhau"
_notEq:
arg1: "A"
arg2: "B"
and: "A VÀ B"
_and:
arg1: "A"
arg2: "B"
or: "A HOẶC B"
_or:
arg1: "A"
arg2: "B"
lt: "< A nhỏ hơn B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A lớn hơn B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A nhỏ hơn hoặc bằng B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A lớn hơn hoặc bằng B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Nhánh"
_if:
arg1: "Nếu"
arg2: "Sau đó"
arg3: "Khác"
not: "KHÔNG"
_not:
arg1: "KHÔNG"
random: "Ngẫu nhiên"
_random:
arg1: "Xác suất"
rannum: "Số ngẫu nhiên"
_rannum:
arg1: "Giá trị tối thiểu"
arg2: "Giá trị tối đa"
randomPick: "Chọn ngẫu nhiên từ danh sách"
_randomPick:
arg1: "Danh sách"
dailyRandom: "Ngẫu nhiên (Đổi mỗi người một lần mỗi ngày)"
_dailyRandom:
arg1: "Xác suất"
dailyRannum: "Số ngẫu nhiên (Đổi mỗi người một lần mỗi ngày)"
_dailyRannum:
arg1: "Giá trị tối thiểu"
arg2: "Giá trị tối đa"
dailyRandomPick: "Chọn ngẫu nhiên từ một danh sách (Đổi mỗi người một lần mỗi ngày)"
_dailyRandomPick:
arg1: "Danh sách"
seedRandom: "Ngẫu nhiên (với seed)"
_seedRandom:
arg1: "Seed"
arg2: "Xác suất"
seedRannum: "Số ngẫu nhiên (với seed)"
_seedRannum:
arg1: "Seed"
arg2: "Giá trị tối thiểu"
arg3: "Giá trị tối đa"
seedRandomPick: "Chọn ngẫu nhiên từ danh sách (với seed)"
_seedRandomPick:
arg1: "Seed"
arg2: "Danh sách"
DRPWPM: "Chọn ngẫu nhiên từ danh sách nặng (Đổi mỗi người một lần mỗi ngày)"
_DRPWPM:
arg1: "Văn bản liệt kê"
pick: "Chọn từ danh sách"
_pick:
arg1: "Danh sách"
arg2: "Vị trí"
listLen: "Lấy độ dài danh sách"
_listLen:
arg1: "Danh sách"
number: "Số"
stringToNumber: "Chữ thành số"
_stringToNumber:
arg1: "Văn bản"
numberToString: "Số thành chữ"
_numberToString:
arg1: "Số"
splitStrByLine: "Phân cách văn bản bằng cách xuống dòng"
_splitStrByLine:
arg1: "Văn bản"
ref: "Biến thể"
aiScriptVar: "Biển thể AiScript"
fn: "Tính năng"
_fn:
slots: "Chỗ"
slots-info: "Phân cách chỗ bằng cách xuống dòng"
arg1: "Đầu ra"
for: "để-Lặp lại"
_for:
arg1: "Số lần lặp lại"
arg2: "Hành động"
typeError: "Chỗ {slot} chấp nhận các giá trị thuộc loại \"{expect}\", nhưng giá trị được cung cấp thuộc loại \"{actual}\"!"
thereIsEmptySlot: "Chỗ {slot} đang trống!"
types:
string: "Văn bản"
number: "Số"
boolean: "Cờ"
array: "Danh sách"
stringArray: "Văn bản liệt kê"
emptySlot: "Chỗ trống"
enviromentVariables: "Biến môi trường"
pageVariables: "Biến trang"
argVariables: "Đầu vào chỗ"
_relayStatus:
requesting: "Đang chờ"
accepted: "Đã duyệt"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "与前一日相比"
appearance: "外观"
clientSettings: "客户端设置"
accountSettings: "账户设置"
promotion: "推广"
promote: "推广"
numberOfDays: "天数"
hideThisNote: "隐藏这条帖子"
showFeaturedNotesInTimeline: "在时间线上显示热门推荐"
@ -547,6 +549,7 @@ poll: "调查问卷"
useCw: "隐藏内容"
enablePlayer: "打开播放器"
disablePlayer: "关闭播放器"
expandTweet: "展开帖子"
themeEditor: "主题编辑器"
description: "描述"
describeFile: "添加标题"
@ -1320,7 +1323,10 @@ _pages:
my: "我的页面"
liked: "喜欢的页面"
featured: "热门"
inspector: "检查器"
contents: "内容"
content: "页面内容"
variables: "变量"
title: "标题"
url: "页面URL"
summary: "页面摘要"
@ -1331,6 +1337,262 @@ _pages:
fontSansSerif: "无衬线字体"
eyeCatchingImageSet: "设置封面图片"
eyeCatchingImageRemove: "删除封面图片"
chooseBlock: "添加块"
selectType: "选择类型"
enterVariableName: "请输入变量名"
variableNameIsAlreadyUsed: "变量名已使用"
contentBlocks: "内容"
inputBlocks: "输入"
specialBlocks: "特殊"
blocks:
text: "文本"
textarea: "文本区域"
section: "章节"
image: "图片"
button: "按钮"
if: "如果"
_if:
variable: "变量"
post: "投稿窗口"
_post:
text: "内容"
attachCanvasImage: "附加画布图像"
canvasId: "画布ID"
textInput: "文本输入"
_textInput:
name: "变量名"
text: "标题"
default: "默认值"
textareaInput: "多行文本输入"
_textareaInput:
name: "变量名"
text: "标题"
default: "默认值"
numberInput: "输入数值"
_numberInput:
name: "变量名"
text: "标题"
default: "默认值"
canvas: "画布"
_canvas:
id: "画布ID"
width: "宽度"
height: "高度"
note: "嵌入的帖子"
_note:
id: "帖子ID"
idDescription: "您也可以通过粘贴帖子的URL来进行设置。"
detailed: "显示详细信息"
switch: "开关"
_switch:
name: "变量名"
text: "标题"
default: "默认值"
counter: "计数器"
_counter:
name: "变量名"
text: "标题"
inc: "增加值"
_button:
text: "标题"
colored: "彩色"
action: "按下按钮时的行为"
_action:
dialog: "显示对话框"
_dialog:
content: "内容"
resetRandom: "重置随机值"
pushEvent: "发送事件"
_pushEvent:
event: "事件名称"
message: "按下时显示的消息"
variable: "发送的变量"
no-variable: "空"
callAiScript: "调用AiScript"
_callAiScript:
functionName: "函数名"
radioButton: "选择项"
_radioButton:
name: "变量名"
title: "标题"
values: "使用换行区分的选择项"
default: "默认值"
script:
categories:
flow: "控制"
logical: "逻辑运算"
operation: "计算"
comparison: "比较"
random: "随机"
value: "值"
fn: "函数"
text: "文本操作"
convert: "转换"
list: "列表"
blocks:
text: "文本"
multiLineText: "文本 (多行)"
textList: "文本列表"
_textList:
info: "请使用换行符分隔每行"
strLen: "文本长度"
_strLen:
arg1: "文本"
strPick: "提取字符"
_strPick:
arg1: "文本"
arg2: "字符位置"
strReplace: "替换文本"
_strReplace:
arg1: "文本"
arg2: "替换之前"
arg3: "替换之后"
strReverse: "文本反向"
_strReverse:
arg1: "文本"
join: "合并文本"
_join:
arg1: "列表"
arg2: "分隔符"
add: "加"
_add:
arg1: "A"
arg2: "B"
subtract: "减"
_subtract:
arg1: "A"
arg2: "B"
multiply: "乘"
_multiply:
arg1: "A"
arg2: "B"
divide: "除"
_divide:
arg1: "A"
arg2: "B"
mod: "取模(MOD)"
_mod:
arg1: "A"
arg2: "B"
round: "四舍五入"
_round:
arg1: "数值"
eq: "A和B相等"
_eq:
arg1: "A"
arg2: "B"
notEq: "A和B不等"
_notEq:
arg1: "A"
arg2: "B"
and: "A和B"
_and:
arg1: "A"
arg2: "B"
or: "A或B"
_or:
arg1: "A"
arg2: "B"
lt: "< A小于B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A大于B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A小于等于B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A大于等于B"
_gtEq:
arg1: "A"
arg2: "B"
if: "分支"
_if:
arg1: "如果"
arg2: "如果"
arg3: "否则"
not: "否"
_not:
arg1: "否"
random: "随机"
_random:
arg1: "概率"
rannum: "随机数"
_rannum:
arg1: "最小值"
arg2: "最大值"
randomPick: "从列表中随机选择"
_randomPick:
arg1: "列表"
dailyRandom: "随机(每个用户每日)"
_dailyRandom:
arg1: "概率"
dailyRannum: "随机数(每个用户每日)"
_dailyRannum:
arg1: "最小值"
arg2: "最大值"
dailyRandomPick: "从列表中随机选择(每个用户每日)"
_dailyRandomPick:
arg1: "列表"
seedRandom: "随机 (种子)"
_seedRandom:
arg1: "种子"
arg2: "概率"
seedRannum: "随机数(种子)"
_seedRannum:
arg1: "种子"
arg2: "最小值"
arg3: "最大值"
seedRandomPick: "从列表中随机选择 (种子)"
_seedRandomPick:
arg1: "种子"
arg2: "列表"
DRPWPM: "从概率列表中随机选择(每用户每天)"
_DRPWPM:
arg1: "文本列表"
pick: "从列表中选择"
_pick:
arg1: "列表"
arg2: "位置"
listLen: "获取列表长度"
_listLen:
arg1: "列表"
number: "数值"
stringToNumber: "文本到数字"
_stringToNumber:
arg1: "文本"
numberToString: "数字到文本"
_numberToString:
arg1: "数值"
splitStrByLine: "将文本按行拆分"
_splitStrByLine:
arg1: "文本"
ref: "变量"
aiScriptVar: "AiScript变量"
fn: "函数"
_fn:
slots: "槽函数"
slots-info: "请使用换行符分隔每个槽函数"
arg1: "输出"
for: "重复"
_for:
arg1: "次数"
arg2: "处理"
typeError: "槽函数{slot}需要传入“{expect}”,但是实际传入为“{actual}”!"
thereIsEmptySlot: "槽函数{slot}为空!"
types:
string: "文字"
number: "数值"
boolean: "Flag"
array: "列表"
stringArray: "文本列表"
emptySlot: "空白槽函数"
enviromentVariables: "环境变量"
pageVariables: "页面元素"
argVariables: "输入变量"
_relayStatus:
requesting: "待批准"
accepted: "已批准"

View file

@ -472,6 +472,8 @@ dayOverDayChanges: "與前一日相比"
appearance: "外觀"
clientSettings: "用戶端設定"
accountSettings: "帳戶設定"
promotion: "推廣"
promote: "推廣"
numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文"
showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
@ -547,6 +549,7 @@ poll: "投票"
useCw: "隱藏內容"
enablePlayer: "打開播放器"
disablePlayer: "關閉播放器"
expandTweet: "展開推文"
themeEditor: "主題編輯器"
description: "描述"
describeFile: "添加標題 "
@ -1320,7 +1323,10 @@ _pages:
my: "我的頁面"
liked: "已喜歡的頁面"
featured: "人氣"
inspector: "面板檢查"
contents: "內容"
content: "頁面方塊"
variables: "變數"
title: "標題"
url: "頁面網址"
summary: "頁面摘要"
@ -1331,6 +1337,262 @@ _pages:
fontSansSerif: "無襯線體"
eyeCatchingImageSet: "設定封面影像"
eyeCatchingImageRemove: "刪除封面影像"
chooseBlock: "新增方塊"
selectType: "選擇類型"
enterVariableName: "請輸入變數名稱"
variableNameIsAlreadyUsed: "變數名稱已被佔用"
contentBlocks: "內容"
inputBlocks: "輸入"
specialBlocks: "特殊"
blocks:
text: "字串"
textarea: "字串區域"
section: "區段"
image: "圖片"
button: "按鈕"
if: "如果"
_if:
variable: "變數"
post: "發佈窗口"
_post:
text: "内容"
attachCanvasImage: "附加相簿圖像 "
canvasId: "畫布ID"
textInput: "插入字串"
_textInput:
name: "變數名稱"
text: "標題"
default: "預設值"
textareaInput: "多行文字输入"
_textareaInput:
name: "變數名稱"
text: "標題"
default: "預設值"
numberInput: "輸入數值"
_numberInput:
name: "變數名稱"
text: "標題"
default: "預設值"
canvas: "畫布"
_canvas:
id: "畫布ID"
width: "寬度"
height: "高度"
note: "嵌式貼文"
_note:
id: "貼文ID"
idDescription: "您也可以粘貼筆記 URL 並進行設置。 "
detailed: "顯示詳細內容"
switch: "開關"
_switch:
name: "變數名稱"
text: "標題"
default: "預設值"
counter: "計數器"
_counter:
name: "變數名稱"
text: "標題"
inc: "増加値"
_button:
text: "標題"
colored: "彩色"
action: "按下按鈕後發生的行為"
_action:
dialog: "顯示對話框 "
_dialog:
content: "内容"
resetRandom: "重設亂數"
pushEvent: "發送事件"
_pushEvent:
event: "事件名稱"
message: "按下時顯示的消息 "
variable: "要發送的變數"
no-variable: "沒有"
callAiScript: "調用AiScript"
_callAiScript:
functionName: "函數名稱"
radioButton: "選項"
_radioButton:
name: "變數名稱"
title: "標題"
values: "由換行符分隔的選項"
default: "預設值"
script:
categories:
flow: "控制"
logical: "邏輯運算"
operation: "計算"
comparison: "對比"
random: "隨機"
value: "數值 "
fn: "函数"
text: "文本操作"
convert: "轉換"
list: "清單"
blocks:
text: "字串"
multiLineText: "字串(多行)"
textList: "字串串列"
_textList:
info: "請分開每個換行符 "
strLen: "字串長度"
_strLen:
arg1: "字串"
strPick: "提取字元"
_strPick:
arg1: "字串"
arg2: "字元位置"
strReplace: "替換字串"
_strReplace:
arg1: "字串"
arg2: "替換前"
arg3: "替換後"
strReverse: "倒轉字串"
_strReverse:
arg1: "字串"
join: "合併字串"
_join:
arg1: "清單"
arg2: "分隔字元"
add: "加"
_add:
arg1: "A"
arg2: "B"
subtract: "减去"
_subtract:
arg1: "A"
arg2: "B"
multiply: "乘"
_multiply:
arg1: "A"
arg2: "B"
divide: "除"
_divide:
arg1: "A"
arg2: "B"
mod: "餘數"
_mod:
arg1: "A"
arg2: "B"
round: "四舍五入"
_round:
arg1: "數值"
eq: "A和B相等"
_eq:
arg1: "A"
arg2: "B"
notEq: "A和B不等"
_notEq:
arg1: "A"
arg2: "B"
and: "A和B"
_and:
arg1: "A"
arg2: "B"
or: "A或B"
_or:
arg1: "A"
arg2: "B"
lt: "< A小於B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A大於B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A小於或等於B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A大於或等於B"
_gtEq:
arg1: "A"
arg2: "B"
if: "分支"
_if:
arg1: "如果"
arg2: "如果"
arg3: "除此以外 "
not: "否"
_not:
arg1: "否"
random: "隨機"
_random:
arg1: "機率"
rannum: "亂數"
_rannum:
arg1: "下限"
arg2: "上限"
randomPick: "從列表中隨機選擇 "
_randomPick:
arg1: "清單"
dailyRandom: "隨機(使用者每日變化 )"
_dailyRandom:
arg1: "機率"
dailyRannum: "亂數(使用者每日變化)"
_dailyRannum:
arg1: "下限"
arg2: "上限"
dailyRandomPick: "從列表中隨機選擇(使用者每日變化 "
_dailyRandomPick:
arg1: "清單"
seedRandom: "隨機抽選種子碼"
_seedRandom:
arg1: "種子"
arg2: "機率"
seedRannum: "亂數 (種子)"
_seedRannum:
arg1: "種子"
arg2: "最小值"
arg3: "最大值"
seedRandomPick: "從列表中隨機選擇 (種子)"
_seedRandomPick:
arg1: "種子"
arg2: "清單"
DRPWPM: "从機率列表中隨機選擇(每個用户每天)"
_DRPWPM:
arg1: "字串串列"
pick: "從清單中選取"
_pick:
arg1: "清單"
arg2: "位置"
listLen: "取得清單長度"
_listLen:
arg1: "清單"
number: "數值"
stringToNumber: "將字串轉換至數値"
_stringToNumber:
arg1: "字串"
numberToString: "將數値轉換至字串"
_numberToString:
arg1: "數值"
splitStrByLine: "於換行時分割字串"
_splitStrByLine:
arg1: "字串"
ref: "變數"
aiScriptVar: "AiScript的變數"
fn: "函数"
_fn:
slots: "欄位"
slots-info: "用換行符分隔每個欄位"
arg1: "輸出"
for: "重複 "
_for:
arg1: "重複次數"
arg2: "處理"
typeError: "槽參數{slot}需要傳入“{expect}”,但是實際傳入為“{actual}”!"
thereIsEmptySlot: "參數{slot}是空的!"
types:
string: "字串"
number: "数值"
boolean: "標記"
array: "清單"
stringArray: "字串列表"
emptySlot: "空欄位"
enviromentVariables: "環境變數"
pageVariables: "頁面元素"
argVariables: "輸入欄位"
_relayStatus:
requesting: "等待核准"
accepted: "已通過核准"

View file

@ -1,82 +0,0 @@
export class pagesToPlaintext1659335999000 {
name = 'pagesToPlaintext1659335999000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "page" ADD "text" text`);
async function noteUrl(noteId) {
const note = await queryRunner.query(`SELECT "uri", "userHost" FROM "note" WHERE "id" = $1`, [noteId]);
if (note.uri) return note.uri;
// don't really have access to the configuration here so just guess
else return `https://${note.userHost}/notes/${noteId}`;
}
async function fileUrl(fileId) {
const file = await queryRunner.query(`SELECT "url" from "drive_file" WHERE "id" = $1`, [fileId]);
return file.url;
}
async function convertBlock(block) {
switch (block.type) {
case 'note':
if (block.note) return await noteUrl(block.note);
else break;
case 'section':
return (await Promise.all(block.children.map(convertBlock))).join('\n');
case 'text':
return block.text;
case 'textarea':
return '```\n' + block.text + '```';
case 'image':
if (block.fileId) return '![image](' + await fileUrl(block.fileId) + ')';
else break;
case 'if': // no idea how to convert these
case 'post': // new note form, why?
case 'canvas': // there is some aiscript api for these but dont think anyone ever used it
// interactive elements can also not be converted
case 'button':
case 'numberInput':
case 'textInput':
case 'switch':
case 'radioButton':
case 'counter':
break;
}
return `(There was a/an ${block.type} here in a previous version but it is no longer supported.)`;
}
await queryRunner.query(`SELECT id, "content" FROM "page"`)
.then(pages => Promise.all(pages.map(page => {
return Promise.all(page.content.map(convertBlock))
.then(texts => {
queryRunner.query(`UPDATE "page" SET "text" = $1 WHERE "id" = $2`, [texts.join('\n'), page.id]);
});
})));
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "content"`);
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "variables"`);
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "script"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "page" ADD "content" jsonb default '[]'::jsonb`);
await queryRunner.query(`ALTER TABLE "page" ADD "variables" jsonb default '[]'::jsonb`);
await queryRunner.query(`ALTER TABLE "page" ADD "script" character varying(16384) default ''`);
// The conversion from the previous page content to text is lossy,
// so we can just convert it back to a big text block.
await queryRunner.query(`SELECT "id", "text" FROM "page"`)
.then(pages => Promise.all(pages.map(page => {
const content = [{
// just a random UUID to keep the data structure
id: '0730b23f-ab5b-4d56-8bd1-f4ead3f72af7',
type: 'text',
text: page.text,
}];
return queryRunner.query(`UPDATE "page" SET "content" = $1 WHERE "id" = $2`, [JSON.stringify(content), page.id]);
})));
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "text"`);
}
}

View file

@ -1,25 +0,0 @@
export class removePromoEntities1660251834642 {
name = 'removePromoEntities1660251834642';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "promo_read" DROP CONSTRAINT "FK_a46a1a603ecee695d7db26da5f4"`, undefined);
await queryRunner.query(`ALTER TABLE "promo_read" DROP CONSTRAINT "FK_9657d55550c3d37bfafaf7d4b05"`, undefined);
await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_2882b8a1a07c7d281a98b6db16"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_9657d55550c3d37bfafaf7d4b0"`, undefined);
await queryRunner.query(`DROP TABLE "promo_read"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_83f0862e9bae44af52ced7099e"`, undefined);
await queryRunner.query(`DROP TABLE "promo_note"`, undefined);
}
async down(queryRunner) {
await queryRunner.query(`CREATE TABLE "promo_note" ("noteId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "REL_e263909ca4fe5d57f8d4230dd5" UNIQUE ("noteId"), CONSTRAINT "PK_e263909ca4fe5d57f8d4230dd5c" PRIMARY KEY ("noteId"))`, undefined);
await queryRunner.query(`CREATE INDEX "IDX_83f0862e9bae44af52ced7099e" ON "promo_note" ("userId") `, undefined);
await queryRunner.query(`CREATE TABLE "promo_read" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_61917c1541002422b703318b7c9" PRIMARY KEY ("id"))`, undefined);
await queryRunner.query(`CREATE INDEX "IDX_9657d55550c3d37bfafaf7d4b0" ON "promo_read" ("userId") `, undefined);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2882b8a1a07c7d281a98b6db16" ON "promo_read" ("userId", "noteId") `, undefined);
await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
await queryRunner.query(`ALTER TABLE "promo_read" ADD CONSTRAINT "FK_9657d55550c3d37bfafaf7d4b05" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
await queryRunner.query(`ALTER TABLE "promo_read" ADD CONSTRAINT "FK_a46a1a603ecee695d7db26da5f4" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
}
}

View file

@ -69,7 +69,7 @@
"mime-types": "2.1.35",
"misskey-js": "0.0.14",
"mocha": "10.0.0",
"multer": "1.4.5-lts.1",
"multer": "1.4.4",
"nested-property": "4.0.0",
"node-fetch": "3.2.6",
"nodemailer": "6.7.6",

View file

@ -57,6 +57,8 @@ import { Clip } from '@/models/entities/clip.js';
import { ClipNote } from '@/models/entities/clip-note.js';
import { Antenna } from '@/models/entities/antenna.js';
import { AntennaNote } from '@/models/entities/antenna-note.js';
import { PromoNote } from '@/models/entities/promo-note.js';
import { PromoRead } from '@/models/entities/promo-read.js';
import { Relay } from '@/models/entities/relay.js';
import { MutedNote } from '@/models/entities/muted-note.js';
import { Channel } from '@/models/entities/channel.js';
@ -157,6 +159,8 @@ export const entities = [
ClipNote,
Antenna,
AntennaNote,
PromoNote,
PromoRead,
Relay,
MutedNote,
Channel,

View file

@ -75,10 +75,21 @@ export class Page {
@JoinColumn()
public eyeCatchingImage: DriveFile | null;
@Column('text', {
@Column('jsonb', {
default: [],
})
public content: Record<string, any>[];
@Column('jsonb', {
default: [],
})
public variables: Record<string, any>[];
@Column('varchar', {
length: 16384,
default: '',
})
public text: string;
public script: string;
/**
* public ...

View file

@ -0,0 +1,28 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm';
import { id } from '../id.js';
import { Note } from './note.js';
import { User } from './user.js';
@Entity()
export class PromoNote {
@PrimaryColumn(id())
public noteId: Note['id'];
@OneToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
public note: Note | null;
@Column('timestamp with time zone')
public expiresAt: Date;
//#region Denormalized fields
@Index()
@Column({
...id(),
comment: '[Denormalized]',
})
public userId: User['id'];
//#endregion
}

View file

@ -0,0 +1,35 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from '../id.js';
import { Note } from './note.js';
import { User } from './user.js';
@Entity()
@Index(['userId', 'noteId'], { unique: true })
export class PromoRead {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
comment: 'The created date of the PromoRead.',
})
public createdAt: Date;
@Index()
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
public note: Note | null;
}

View file

@ -49,6 +49,8 @@ import { ClipRepository } from './repositories/clip.js';
import { ClipNote } from './entities/clip-note.js';
import { AntennaRepository } from './repositories/antenna.js';
import { AntennaNote } from './entities/antenna-note.js';
import { PromoNote } from './entities/promo-note.js';
import { PromoRead } from './entities/promo-read.js';
import { EmojiRepository } from './repositories/emoji.js';
import { RelayRepository } from './repositories/relay.js';
import { ChannelRepository } from './repositories/channel.js';
@ -113,6 +115,8 @@ export const Clips = (ClipRepository);
export const ClipNotes = db.getRepository(ClipNote);
export const Antennas = (AntennaRepository);
export const AntennaNotes = db.getRepository(AntennaNote);
export const PromoNotes = db.getRepository(PromoNote);
export const PromoReads = db.getRepository(PromoRead);
export const Relays = (RelayRepository);
export const MutedNotes = db.getRepository(MutedNote);
export const Channels = (ChannelRepository);

View file

@ -2,6 +2,7 @@ import { db } from '@/db/postgre.js';
import { Page } from '@/models/entities/page.js';
import { Packed } from '@/misc/schema.js';
import { awaitAll } from '@/prelude/await-all.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { User } from '@/models/entities/user.js';
import { Users, DriveFiles, PageLikes } from '../index.js';
@ -13,21 +14,66 @@ export const PageRepository = db.getRepository(Page).extend({
const meId = me ? me.id : null;
const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const attachedFiles: Promise<DriveFile | null>[] = [];
const collectFile = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'image') {
attachedFiles.push(DriveFiles.findOneBy({
id: x.fileId,
userId: page.userId,
}));
}
if (x.children) {
collectFile(x.children);
}
}
};
collectFile(page.content);
// 後方互換性のため
let migrated = false;
const migrate = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'input') {
if (x.inputType === 'text') {
x.type = 'textInput';
}
if (x.inputType === 'number') {
x.type = 'numberInput';
if (x.default) x.default = parseInt(x.default, 10);
}
migrated = true;
}
if (x.children) {
migrate(x.children);
}
}
};
migrate(page.content);
if (migrated) {
this.update(page.id, {
content: page.content,
});
}
return await awaitAll({
id: page.id,
createdAt: page.createdAt.toISOString(),
updatedAt: page.updatedAt.toISOString(),
userId: page.userId,
user: Users.pack(page.user || page.userId, me), // { detail: true } すると無限ループするので注意
text: page.text,
content: page.content,
variables: page.variables,
title: page.title,
name: page.name,
summary: page.summary,
hideTitleWhenPinned: page.hideTitleWhenPinned,
alignCenter: page.alignCenter,
font: page.font,
script: page.script,
eyeCatchingImageId: page.eyeCatchingImageId,
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)),
likedCount: page.likedCount,
isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
});

View file

@ -29,8 +29,12 @@ export const packedPageSchema = {
type: 'string',
optional: false, nullable: true,
},
text: {
type: 'string',
content: {
type: 'array',
optional: false, nullable: false,
},
variables: {
type: 'array',
optional: false, nullable: false,
},
userId: {

View file

@ -25,7 +25,7 @@ const router = new Router();
//#region Routing
function inbox(ctx: Router.RouterContext) {
function inbox(ctx: Router.RouterContext): void {
let signature;
try {
@ -43,13 +43,13 @@ function inbox(ctx: Router.RouterContext) {
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
function isActivityPubReq(ctx: Router.RouterContext) {
function isActivityPubReq(ctx: Router.RouterContext): boolean {
ctx.response.vary('Accept');
const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
return typeof accepted === 'string' && !accepted.match(/html/);
}
export function setResponseType(ctx: Router.RouterContext) {
export function setResponseType(ctx: Router.RouterContext): void {
const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
if (accept === LD_JSON) {
ctx.response.type = LD_JSON;
@ -77,13 +77,9 @@ router.get('/notes/:note', async (ctx, next) => {
return;
}
// リモートだったらリダイレクト
if (note.userHost != null) {
if (note.uri == null || isSelfHost(note.userHost)) {
ctx.status = 500;
return;
}
ctx.redirect(note.uri);
if (!isSelfHost(note.userHost)) {
if (note.uri != null) ctx.redirect(note.uri);
else ctx.status = 500;
return;
}
@ -132,24 +128,19 @@ router.get('/users/:user/publickey', async ctx => {
host: IsNull(),
});
if (user == null) {
if (user == null || !Users.isLocalUser(user)) {
ctx.status = 404;
return;
}
const keypair = await getUserKeypair(user.id);
if (Users.isLocalUser(user)) {
ctx.body = renderActivity(renderKey(user, keypair));
ctx.set('Cache-Control', 'public, max-age=180');
setResponseType(ctx);
} else {
ctx.status = 400;
}
ctx.body = renderActivity(renderKey(user, keypair));
ctx.set('Cache-Control', 'public, max-age=180');
setResponseType(ctx);
});
// user
async function userInfo(ctx: Router.RouterContext, user: User | null) {
async function userInfo(ctx: Router.RouterContext, user: User | null): void {
if (user == null) {
ctx.status = 404;
return;

View file

@ -0,0 +1,34 @@
import rndstr from 'rndstr';
import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js';
import { PromoReads, PromoNotes, Notes, Users } from '@/models/index.js';
export async function injectPromo(timeline: Note[], user?: User | null) {
if (timeline.length < 5) return;
// TODO: readやexpireフィルタはクエリ側でやる
const reads = user ? await PromoReads.findBy({
userId: user.id,
}) : [];
let promos = await PromoNotes.find();
promos = promos.filter(n => n.expiresAt.getTime() > Date.now());
promos = promos.filter(n => !reads.map(r => r.noteId).includes(n.noteId));
if (promos.length === 0) return;
// Pick random promo
const promo = promos[Math.floor(Math.random() * promos.length)];
const note = await Notes.findOneByOrFail({ id: promo.noteId });
// Join
note.user = await Users.findOneByOrFail({ id: note.userId });
(note as any)._prId_ = rndstr('a-z0-9', 8);
// Inject promo
timeline.splice(3, 0, note);
}

View file

@ -33,6 +33,7 @@ import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'
import * as ep___admin_invite from './endpoints/admin/invite.js';
import * as ep___admin_moderators_add from './endpoints/admin/moderators/add.js';
import * as ep___admin_moderators_remove from './endpoints/admin/moderators/remove.js';
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
@ -260,6 +261,7 @@ import * as ep___pages_unlike from './endpoints/pages/unlike.js';
import * as ep___pages_update from './endpoints/pages/update.js';
import * as ep___ping from './endpoints/ping.js';
import * as ep___pinnedUsers from './endpoints/pinned-users.js';
import * as ep___promo_read from './endpoints/promo/read.js';
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
import * as ep___resetDb from './endpoints/reset-db.js';
import * as ep___resetPassword from './endpoints/reset-password.js';
@ -340,6 +342,7 @@ const eps = [
['admin/invite', ep___admin_invite],
['admin/moderators/add', ep___admin_moderators_add],
['admin/moderators/remove', ep___admin_moderators_remove],
['admin/promo/create', ep___admin_promo_create],
['admin/queue/clear', ep___admin_queue_clear],
['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed],
@ -567,6 +570,7 @@ const eps = [
['pages/update', ep___pages_update],
['ping', ep___ping],
['pinned-users', ep___pinnedUsers],
['promo/read', ep___promo_read],
['request-reset-password', ep___requestResetPassword],
['reset-db', ep___resetDb],
['reset-password', ep___resetPassword],

View file

@ -0,0 +1,54 @@
import { PromoNotes } from '@/models/index.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';
import { getNote } from '../../../common/getters.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'ee449fbe-af2a-453b-9cae-cf2fe7c895fc',
},
alreadyPromoted: {
message: 'The note has already promoted.',
code: 'ALREADY_PROMOTED',
id: 'ae427aa2-7a41-484f-a18c-2c1104051604',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
noteId: { type: 'string', format: 'misskey:id' },
expiresAt: { type: 'integer' },
},
required: ['noteId', 'expiresAt'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
const note = await getNote(ps.noteId, user).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err;
});
const exist = await PromoNotes.findOneBy({ noteId: note.id });
if (exist != null) {
throw new ApiError(meta.errors.alreadyPromoted);
}
await PromoNotes.insert({
noteId: note.id,
expiresAt: new Date(ps.expiresAt),
userId: note.userId,
});
});

View file

@ -43,13 +43,19 @@ export const paramDef = {
title: { type: 'string' },
name: { type: 'string', minLength: 1 },
summary: { type: 'string', nullable: true },
text: { type: 'string', minLength: 1 },
content: { type: 'array', items: {
type: 'object', additionalProperties: true,
} },
variables: { type: 'array', items: {
type: 'object', additionalProperties: true,
} },
script: { type: 'string' },
eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true },
font: { type: 'string', enum: ['serif', 'sans-serif'], default: 'sans-serif' },
alignCenter: { type: 'boolean', default: false },
hideTitleWhenPinned: { type: 'boolean', default: false },
},
required: ['title', 'name', 'text'],
required: ['title', 'name', 'content', 'variables', 'script'],
} as const;
// eslint-disable-next-line import/no-default-export
@ -82,7 +88,9 @@ export default define(meta, paramDef, async (ps, user) => {
title: ps.title,
name: ps.name,
summary: ps.summary,
text: ps.text,
content: ps.content,
variables: ps.variables,
script: ps.script,
eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
userId: user.id,
visibility: 'public',

View file

@ -49,13 +49,19 @@ export const paramDef = {
title: { type: 'string' },
name: { type: 'string', minLength: 1 },
summary: { type: 'string', nullable: true },
text: { type: 'string', minLength: 1 },
content: { type: 'array', items: {
type: 'object', additionalProperties: true,
} },
variables: { type: 'array', items: {
type: 'object', additionalProperties: true,
} },
script: { type: 'string' },
eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true },
font: { type: 'string', enum: ['serif', 'sans-serif'] },
alignCenter: { type: 'boolean' },
hideTitleWhenPinned: { type: 'boolean' },
},
required: ['pageId', 'title', 'name', 'text'],
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
} as const;
// eslint-disable-next-line import/no-default-export
@ -95,7 +101,9 @@ export default define(meta, paramDef, async (ps, user) => {
title: ps.title,
name: ps.name === undefined ? page.name : ps.name,
summary: ps.name === undefined ? page.summary : ps.summary,
text: ps.text,
content: ps.content,
variables: ps.variables,
script: ps.script,
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
font: ps.font === undefined ? page.font : ps.font,

View file

@ -0,0 +1,51 @@
import { PromoReads } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['notes'],
requireCredential: true,
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'd785b897-fcd3-4fe9-8fc3-b85c26e6c932',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
noteId: { type: 'string', format: 'misskey:id' },
},
required: ['noteId'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
const note = await getNote(ps.noteId, user).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err;
});
const exist = await PromoReads.findOneBy({
noteId: note.id,
userId: user.id,
});
if (exist != null) {
return;
}
await PromoReads.insert({
id: genId(),
createdAt: new Date(),
noteId: note.id,
userId: user.id,
});
});

View file

@ -1486,12 +1486,13 @@ bull@4.8.4:
semver "^7.3.2"
uuid "^8.3.0"
busboy@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
busboy@^0.2.11:
version "0.2.14"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=
dependencies:
streamsearch "^1.1.0"
dicer "0.2.5"
readable-stream "1.1.x"
bytes@3.1.0, bytes@^3.1.0:
version "3.1.0"
@ -2236,6 +2237,14 @@ detect-libc@^2.0.0:
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.0.tgz#c528bc09bc6d1aa30149228240917c225448f204"
integrity sha512-S55LzUl8HUav8l9E2PBTlC5PAJrHK7tkM+XXFGD+fbsbkTzhCpG6K05LxJcUOEWzMa4v6ptcMZ9s3fOdJDu0Zw==
dicer@0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=
dependencies:
readable-stream "1.1.x"
streamsearch "0.1.2"
diff@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
@ -3543,7 +3552,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -3852,6 +3861,11 @@ is-whitespace@^0.3.0:
resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f"
integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38=
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@ -4751,16 +4765,17 @@ msgpackr@^1.5.2:
optionalDependencies:
msgpackr-extract "^1.0.14"
multer@1.4.5-lts.1:
version "1.4.5-lts.1"
resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.5-lts.1.tgz#803e24ad1984f58edffbc79f56e305aec5cfd1ac"
integrity sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==
multer@1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c"
integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==
dependencies:
append-field "^1.0.0"
busboy "^1.0.0"
busboy "^0.2.11"
concat-stream "^1.5.2"
mkdirp "^0.5.4"
object-assign "^4.1.1"
on-finished "^2.3.0"
type-is "^1.6.4"
xtend "^4.0.0"
@ -5756,6 +5771,16 @@ re2@1.17.7:
nan "^2.16.0"
node-gyp "^9.0.0"
readable-stream@1.1.x:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@ -6278,10 +6303,10 @@ stream-parser@~0.3.1:
dependencies:
debug "2"
streamsearch@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
streamsearch@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
strict-event-emitter-types@2.0.0:
version "2.0.0"
@ -6346,6 +6371,11 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"

View file

@ -11,6 +11,7 @@
>
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
<div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ i18n.ts.pinnedNote }}</div>
<div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="fas fa-times"></i></button></div>
<div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ i18n.ts.featured }}</div>
<div v-if="isRenote" class="renote">
<MkAvatar class="avatar" :user="note.user"/>
@ -280,6 +281,13 @@ function focusBefore() {
function focusAfter() {
focusNext(el.value);
}
function readPromo() {
os.api('promo/read', {
noteId: appearNote.id,
});
isDeleted.value = true;
}
</script>
<style lang="scss" scoped>

View file

@ -10,7 +10,7 @@
<template #default="{ items: notes }">
<div class="giivymft" :class="{ noGap }">
<XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" class="notes">
<XNote :key="note._featuredId_ || note.id" class="qtqtichx" :note="note"/>
<XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/>
</XList>
</div>
</template>

View file

@ -0,0 +1,44 @@
<template>
<component :is="'x-' + block.type" :key="block.id" :block="block" :hpml="hpml" :h="h"/>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import XText from './page.text.vue';
import XSection from './page.section.vue';
import XImage from './page.image.vue';
import XButton from './page.button.vue';
import XNumberInput from './page.number-input.vue';
import XTextInput from './page.text-input.vue';
import XTextareaInput from './page.textarea-input.vue';
import XSwitch from './page.switch.vue';
import XIf from './page.if.vue';
import XTextarea from './page.textarea.vue';
import XPost from './page.post.vue';
import XCounter from './page.counter.vue';
import XRadioButton from './page.radio-button.vue';
import XCanvas from './page.canvas.vue';
import XNote from './page.note.vue';
import { Hpml } from '@/scripts/hpml/evaluator';
import { Block } from '@/scripts/hpml/block';
export default defineComponent({
components: {
XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote,
},
props: {
block: {
type: Object as PropType<Block>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
h: {
type: Number,
required: true,
},
},
});
</script>

View file

@ -0,0 +1,66 @@
<template>
<div>
<MkButton class="kudkigyw" :primary="block.primary" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, unref } from 'vue';
import MkButton from '../ui/button.vue';
import * as os from '@/os';
import { ButtonBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({
components: {
MkButton,
},
props: {
block: {
type: Object as PropType<ButtonBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
methods: {
click() {
if (this.block.action === 'dialog') {
this.hpml.eval();
os.alert({
text: this.hpml.interpolate(this.block.content),
});
} else if (this.block.action === 'resetRandom') {
this.hpml.updateRandomSeed(Math.random());
this.hpml.eval();
} else if (this.block.action === 'pushEvent') {
os.api('page-push', {
pageId: this.hpml.page.id,
event: this.block.event,
...(this.block.var ? {
var: unref(this.hpml.vars)[this.block.var],
} : {}),
});
os.alert({
type: 'success',
text: this.hpml.interpolate(this.block.message),
});
} else if (this.block.action === 'callAiScript') {
this.hpml.callAiScript(this.block.fn);
}
},
},
});
</script>
<style lang="scss" scoped>
.kudkigyw {
display: inline-block;
min-width: 200px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -0,0 +1,49 @@
<template>
<div class="ysrxegms">
<canvas ref="canvas" :width="block.width" :height="block.height"/>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
import * as os from '@/os';
import { CanvasBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({
props: {
block: {
type: Object as PropType<CanvasBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const canvas: Ref<any> = ref(null);
onMounted(() => {
props.hpml.registerCanvas(props.block.name, canvas.value);
});
return {
canvas,
};
},
});
</script>
<style lang="scss" scoped>
.ysrxegms {
display: inline-block;
vertical-align: bottom;
overflow: auto;
max-width: 100%;
> canvas {
display: block;
}
}
</style>

View file

@ -0,0 +1,52 @@
<template>
<div>
<MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkButton from '../ui/button.vue';
import * as os from '@/os';
import { CounterVarBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({
components: {
MkButton,
},
props: {
block: {
type: Object as PropType<CounterVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const value = computed(() => {
return props.hpml.vars.value[props.block.name];
});
function click() {
props.hpml.updatePageVar(props.block.name, value.value + (props.block.inc || 1));
props.hpml.eval();
}
return {
click,
};
},
});
</script>
<style lang="scss" scoped>
.llumlmnx {
display: inline-block;
min-width: 300px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -0,0 +1,31 @@
<template>
<div v-show="hpml.vars.value[block.var]">
<XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h"/>
</div>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, PropType } from 'vue';
import { IfBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({
components: {
XBlock: defineAsyncComponent(() => import('./page.block.vue')),
},
props: {
block: {
type: Object as PropType<IfBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
h: {
type: Number,
required: true,
},
},
});
</script>

View file

@ -0,0 +1,28 @@
<template>
<div class="lzyxtsnt">
<ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/>
</div>
</template>
<script lang="ts" setup>
import { defineComponent, PropType } from 'vue';
import ImgWithBlurhash from '@/components/img-with-blurhash.vue';
import * as os from '@/os';
import { ImageBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
const props = defineProps<{
block: PropType<ImageBlock>,
hpml: PropType<Hpml>,
}>();
const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
</script>
<style lang="scss" scoped>
.lzyxtsnt {
> img {
max-width: 100%;
}
}
</style>

View file

@ -0,0 +1,47 @@
<template>
<div class="voxdxuby">
<XNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/>
<XNoteDetailed v-if="note && block.detailed" :key="note.id + ':detail'" v-model:note="note"/>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
import XNote from '@/components/note.vue';
import XNoteDetailed from '@/components/note-detailed.vue';
import * as os from '@/os';
import { NoteBlock } from '@/scripts/hpml/block';
export default defineComponent({
components: {
XNote,
XNoteDetailed,
},
props: {
block: {
type: Object as PropType<NoteBlock>,
required: true,
},
},
setup(props, ctx) {
const note: Ref<Record<string, any> | null> = ref(null);
onMounted(() => {
os.api('notes/show', { noteId: props.block.note })
.then(result => {
note.value = result;
});
});
return {
note,
};
},
});
</script>
<style lang="scss" scoped>
.voxdxuby {
margin: 1em 0;
}
</style>

View file

@ -0,0 +1,55 @@
<template>
<div>
<MkInput class="kudkigyw" :model-value="value" type="number" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkInput>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../form/input.vue';
import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { NumberInputVarBlock } from '@/scripts/hpml/block';
export default defineComponent({
components: {
MkInput,
},
props: {
block: {
type: Object as PropType<NumberInputVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const value = computed(() => {
return props.hpml.vars.value[props.block.name];
});
function updateValue(newValue) {
props.hpml.updatePageVar(props.block.name, newValue);
props.hpml.eval();
}
return {
value,
updateValue,
};
},
});
</script>
<style lang="scss" scoped>
.kudkigyw {
display: inline-block;
min-width: 300px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -0,0 +1,111 @@
<template>
<div class="ngbfujlo">
<MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea>
<MkButton class="button" primary :disabled="posting || posted" @click="post()">
<i v-if="posted" class="fas fa-check"></i>
<i v-else class="fas fa-paper-plane"></i>
</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import MkTextarea from '../form/textarea.vue';
import MkButton from '../ui/button.vue';
import { apiUrl } from '@/config';
import * as os from '@/os';
import { PostBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({
components: {
MkTextarea,
MkButton,
},
props: {
block: {
type: Object as PropType<PostBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
data() {
return {
text: this.hpml.interpolate(this.block.text),
posted: false,
posting: false,
};
},
watch: {
'hpml.vars': {
handler() {
this.text = this.hpml.interpolate(this.block.text);
},
deep: true,
},
},
methods: {
upload() {
const promise = new Promise((ok) => {
const canvas = this.hpml.canvases[this.block.canvasId];
canvas.toBlob(blob => {
const formData = new FormData();
formData.append('file', blob);
if (this.$store.state.uploadFolder) {
formData.append('folderId', this.$store.state.uploadFolder);
}
fetch(apiUrl + '/drive/files/create', {
method: 'POST',
body: formData,
headers: {
authorization: `Bearer ${this.$i.token}`,
},
})
.then(response => response.json())
.then(f => {
ok(f);
});
});
});
os.promiseDialog(promise);
return promise;
},
async post() {
this.posting = true;
const file = this.block.attachCanvasImage ? await this.upload() : null;
os.apiWithDialog('notes/create', {
text: this.text === '' ? null : this.text,
fileIds: file ? [file.id] : undefined,
}).then(() => {
this.posted = true;
});
},
},
});
</script>
<style lang="scss" scoped>
.ngbfujlo {
position: relative;
padding: 32px;
border-radius: 6px;
box-shadow: 0 2px 8px var(--shadow);
z-index: 1;
> .button {
margin-top: 32px;
}
@media (max-width: 600px) {
padding: 16px;
> .button {
margin-top: 16px;
}
}
}
</style>

View file

@ -0,0 +1,45 @@
<template>
<div>
<div>{{ hpml.interpolate(block.title) }}</div>
<MkRadio v-for="item in block.values" :key="item" :model-value="value" :value="item" @update:modelValue="updateValue($event)">{{ item }}</MkRadio>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkRadio from '../form/radio.vue';
import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { RadioButtonVarBlock } from '@/scripts/hpml/block';
export default defineComponent({
components: {
MkRadio,
},
props: {
block: {
type: Object as PropType<RadioButtonVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const value = computed(() => {
return props.hpml.vars.value[props.block.name];
});
function updateValue(newValue: string) {
props.hpml.updatePageVar(props.block.name, newValue);
props.hpml.eval();
}
return {
value,
updateValue,
};
},
});
</script>

View file

@ -0,0 +1,60 @@
<template>
<section class="sdgxphyu">
<component :is="'h' + h">{{ block.title }}</component>
<div class="children">
<XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h + 1"/>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, PropType } from 'vue';
import * as os from '@/os';
import { SectionBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({
components: {
XBlock: defineAsyncComponent(() => import('./page.block.vue')),
},
props: {
block: {
type: Object as PropType<SectionBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
h: {
required: true,
},
},
});
</script>
<style lang="scss" scoped>
.sdgxphyu {
margin: 1.5em 0;
> h2 {
font-size: 1.35em;
margin: 0 0 0.5em 0;
}
> h3 {
font-size: 1em;
margin: 0 0 0.5em 0;
}
> h4 {
font-size: 1em;
margin: 0 0 0.5em 0;
}
> .children {
//padding 16px
}
}
</style>

View file

@ -0,0 +1,55 @@
<template>
<div class="hkcxmtwj">
<MkSwitch :model-value="value" @update:modelValue="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkSwitch from '../form/switch.vue';
import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { SwitchVarBlock } from '@/scripts/hpml/block';
export default defineComponent({
components: {
MkSwitch,
},
props: {
block: {
type: Object as PropType<SwitchVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const value = computed(() => {
return props.hpml.vars.value[props.block.name];
});
function updateValue(newValue: boolean) {
props.hpml.updatePageVar(props.block.name, newValue);
props.hpml.eval();
}
return {
value,
updateValue,
};
},
});
</script>
<style lang="scss" scoped>
.hkcxmtwj {
display: inline-block;
margin: 16px auto;
& + .hkcxmtwj {
margin-left: 16px;
}
}
</style>

View file

@ -0,0 +1,55 @@
<template>
<div>
<MkInput class="kudkigyw" :model-value="value" type="text" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkInput>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../form/input.vue';
import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { TextInputVarBlock } from '@/scripts/hpml/block';
export default defineComponent({
components: {
MkInput,
},
props: {
block: {
type: Object as PropType<TextInputVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const value = computed(() => {
return props.hpml.vars.value[props.block.name];
});
function updateValue(newValue) {
props.hpml.updatePageVar(props.block.name, newValue);
props.hpml.eval();
}
return {
value,
updateValue,
};
},
});
</script>
<style lang="scss" scoped>
.kudkigyw {
display: inline-block;
min-width: 300px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -0,0 +1,54 @@
<template>
<div class="mrdgzndn">
<Mfm :key="text" :text="text" :is-note="false"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" class="url"/>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, watch, computed } from 'vue';
import * as mfm from 'mfm-js';
import { TextBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
const props = defineProps<{
block: TextBlock;
hpml: Hpml;
}>();
const MkUrlPreview = defineAsyncComponent(() => import('@/components/url-preview.vue'));
let text: string = $ref('');
const urls = computed((): string[] => {
if (text) {
return extractUrlFromMfm(mfm.parse(text));
} else {
return [];
}
});
watch(props.hpml.vars, () => {
text = props.hpml.interpolate(props.block.text) as string;
}, {
deep: true,
immediate: true,
});
</script>
<style lang="scss" scoped>
.mrdgzndn {
&:not(:first-child) {
margin-top: 0.5em;
}
&:not(:last-child) {
margin-bottom: 0.5em;
}
> .url {
margin: 0.5em 0;
}
}
</style>

View file

@ -0,0 +1,47 @@
<template>
<div>
<MkTextarea :model-value="value" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkTextarea>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkTextarea from '../form/textarea.vue';
import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { HpmlTextInput } from '@/scripts/hpml';
import { TextInputVarBlock } from '@/scripts/hpml/block';
export default defineComponent({
components: {
MkTextarea,
},
props: {
block: {
type: Object as PropType<TextInputVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const value = computed(() => {
return props.hpml.vars.value[props.block.name];
});
function updateValue(newValue) {
props.hpml.updatePageVar(props.block.name, newValue);
props.hpml.eval();
}
return {
value,
updateValue,
};
},
});
</script>

View file

@ -0,0 +1,24 @@
<template>
<MkTextarea :model-value="text" readonly></MkTextarea>
</template>
<script lang="ts" setup>
import { watch } from 'vue';
import MkTextarea from '../form/textarea.vue';
import { TextBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
const props = defineProps<{
block: TextBlock;
hpml: Hpml;
}>();
let text = $ref('');
watch(props.hpml.vars, () => {
text = props.hpml.interpolate(props.block.text) as string;
}, {
deep: true,
immediate: true,
});
</script>

View file

@ -0,0 +1,85 @@
<template>
<div v-if="hpml" class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }">
<XBlock v-for="child in page.content" :key="child.id" :block="child" :hpml="hpml" :h="2"/>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, nextTick, onUnmounted, PropType } from 'vue';
import { parse } from '@syuilo/aiscript';
import XBlock from './page.block.vue';
import { Hpml } from '@/scripts/hpml/evaluator';
import { url } from '@/config';
import { $i } from '@/account';
import { defaultStore } from '@/store';
export default defineComponent({
components: {
XBlock,
},
props: {
page: {
type: Object as PropType<Record<string, any>>,
required: true,
},
},
setup(props, ctx) {
const hpml = new Hpml(props.page, {
randomSeed: Math.random(),
visitor: $i,
url,
enableAiScript: !defaultStore.state.disablePagesScript,
});
onMounted(() => {
nextTick(() => {
if (props.page.script && hpml.aiscript) {
let ast;
try {
ast = parse(props.page.script);
} catch (err) {
console.error(err);
/*os.alert({
type: 'error',
text: 'Syntax error :('
});*/
return;
}
hpml.aiscript.exec(ast).then(() => {
hpml.eval();
}).catch(err => {
console.error(err);
/*os.alert({
type: 'error',
text: err
});*/
});
} else {
hpml.eval();
}
});
onUnmounted(() => {
if (hpml.aiscript) hpml.aiscript.abort();
});
});
return {
hpml,
};
},
});
</script>
<style lang="scss" scoped>
.iroscrza {
&.serif {
> div {
font-family: serif;
}
}
&.center {
text-align: center;
}
}
</style>

View file

@ -3,6 +3,9 @@
<button class="disablePlayer" :title="i18n.ts.disablePlayer" @click="playerEnabled = false"><i class="fas fa-times"></i></button>
<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
</div>
<div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter">
<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${$store.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
</div>
<div v-else v-size="{ max: [400, 350] }" class="mk-url-preview">
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<component :is="self ? 'MkA' : 'a'" v-if="!fetching" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
@ -21,10 +24,16 @@
</article>
</component>
</transition>
<div v-if="tweetId" class="expandTweet">
<a @click="tweetExpanded = true">
<i class="fab fa-twitter"></i> {{ i18n.ts.expandTweet }}
</a>
</div>
</div>
</template>
<script lang="ts" setup>
import { onUnmounted } from 'vue';
import { url as local, lang } from '@/config';
import { i18n } from '@/i18n';
@ -52,14 +61,23 @@ let player = $ref({
height: null,
});
let playerEnabled = $ref(false);
let tweetId = $ref<string | null>(null);
let tweetExpanded = $ref(props.detail);
const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
let tweetHeight = $ref(150);
const requestUrl = new URL(props.url);
if (requestUrl.hostname === 'twitter.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
if (m) tweetId = m[1];
}
if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) {
requestUrl.hostname = 'www.youtube.com';
}
const requestLang = lang || 'en-US';
const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP');
requestUrl.hash = '';
@ -75,6 +93,21 @@ fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).the
player = info.player;
});
});
function adjustTweetHeight(message: any) {
if (message.origin !== 'https://platform.twitter.com') return;
const embed = message.data?.['twttr.embed'];
if (embed?.method !== 'twttr.private.resize') return;
if (embed?.id !== embedId) return;
const height = embed?.params[0]?.height;
if (height) tweetHeight = height;
}
(window as any).addEventListener('message', adjustTweetHeight);
onUnmounted(() => {
(window as any).removeEventListener('message', adjustTweetHeight);
});
</script>
<style lang="scss" scoped>

View file

@ -33,7 +33,7 @@ function calc(src: Element) {
}
export default {
mounted(src, binding) {
mounted(src) {
const resize = new ResizeObserver(() => {
calc(src);
});

View file

@ -1,265 +0,0 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700">
<div class="jqqmcavi">
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton>
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton>
<MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
</div>
<div v-if="tab === 'settings'">
<div class="_formRoot">
<MkInput v-model="title" :readonly="readonly" class="_formBlock">
<template #label>{{ $ts._pages.title }}</template>
</MkInput>
<MkInput v-model="summary" :readonly="readonly" class="_formBlock">
<template #label>{{ $ts._pages.summary }}</template>
</MkInput>
<MkInput v-model="name" :readonly="readonly" class="_formBlock">
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
<template #label>{{ $ts._pages.url }}</template>
</MkInput>
<MkSwitch v-model="alignCenter" :disabled="readonly" class="_formBlock">{{ $ts._pages.alignCenter }}</MkSwitch>
<MkSelect v-model="font" :readonly="readonly" class="_formBlock">
<template #label>{{ $ts._pages.font }}</template>
<option value="serif">{{ $ts._pages.fontSerif }}</option>
<option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option>
</MkSelect>
<MkSwitch v-model="hideTitleWhenPinned" :disabled="readonly" class="_formBlock">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch>
<div class="eyeCatch">
<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="fas fa-plus"></i> {{ $ts._pages.eyeCatchingImageSet }}</MkButton>
<div v-else-if="eyeCatchingImage">
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
<MkButton v-if="!readonly" @click="removeEyeCatchingImage()"><i class="fas fa-trash-alt"></i> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton>
</div>
</div>
</div>
</div>
<div v-else-if="tab === 'contents'">
<MkTextarea v-model="text" :readonly="readonly"/>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { computed, watch } from 'vue';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/form/select.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkInput from '@/components/form/input.vue';
import MkTextarea from '@/components/form/textarea.vue';
import { url } from '@/config';
import * as os from '@/os';
import { selectFile } from '@/scripts/select-file';
import { mainRouter } from '@/router';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { $i } from '@/account';
const props = defineProps<{
initPageId?: string;
initPageName?: string;
initUser?: string;
}>();
let tab = $ref('settings');
let author = $ref($i);
let readonly = $ref(false);
let page = $ref(null);
let pageId = $ref(null);
let currentName = $ref(null);
let title = $ref('');
let summary = $ref(null);
let text = $ref('Hello, world!');
let name = $ref(Date.now().toString());
let eyeCatchingImage = $ref(null);
let eyeCatchingImageId = $ref(null);
let font = $ref('sans-serif');
let alignCenter = $ref(false);
let hideTitleWhenPinned = $ref(false);
watch($$(eyeCatchingImageId), async () => {
if (eyeCatchingImageId == null) {
eyeCatchingImage = null;
} else {
eyeCatchingImage = await os.api('drive/files/show', {
fileId: eyeCatchingImageId,
});
}
});
function getSaveOptions() {
return {
title: title.trim(),
name: name.trim(),
summary,
font,
hideTitleWhenPinned,
alignCenter,
text,
eyeCatchingImageId,
};
}
function save() {
const options = getSaveOptions();
const onError = err => {
if (err.id === '3d81ceae-475f-4600-b2a8-2bc116157532') {
if (err.info.param === 'name') {
os.alert({
type: 'error',
title: i18n.ts._pages.invalidNameTitle,
text: i18n.ts._pages.invalidNameText,
});
}
} else if (err.code === 'NAME_ALREADY_EXISTS') {
os.alert({
type: 'error',
text: i18n.ts._pages.nameAlreadyExists,
});
}
};
if (pageId) {
options.pageId = pageId;
os.api('pages/update', options)
.then(page => {
currentName = name.trim();
os.alert({
type: 'success',
text: i18n.ts._pages.updated,
});
}).catch(onError);
} else {
os.api('pages/create', options)
.then(created => {
pageId = created.id;
currentName = name.trim();
os.alert({
type: 'success',
text: i18n.ts._pages.created,
});
mainRouter.push(`/pages/edit/${pageId}`);
}).catch(onError);
}
}
function del() {
os.confirm({
type: 'warning',
text: i18n.t('removeAreYouSure', { x: title.trim() }),
}).then(({ canceled }) => {
if (canceled) return;
os.api('pages/delete', {
pageId,
}).then(() => {
os.alert({
type: 'success',
text: i18n.ts._pages.deleted,
});
mainRouter.push('/pages');
});
});
}
function duplicate() {
title = title + ' - copy';
name = name + '-copy';
os.api('pages/create', getSaveOptions()).then(created => {
pageId = created.id;
currentName = name.trim();
os.alert({
type: 'success',
text: i18n.ts._pages.created,
});
mainRouter.push(`/pages/edit/${pageId}`);
});
}
function setEyeCatchingImage(evt) {
selectFile(evt.currentTarget ?? evt.target, null).then(file => {
eyeCatchingImageId = file.id;
});
}
function removeEyeCatchingImage() {
eyeCatchingImageId = null;
}
async function init() {
if (props.initPageId) {
page = await os.api('pages/show', {
pageId: props.initPageId,
});
} else if (props.initPageName && props.initUser) {
page = await os.api('pages/show', {
name: props.initPageName,
username: props.initUser,
});
readonly = true;
}
if (page) {
author = page.user;
pageId = page.id;
title = page.title;
name = page.name;
currentName = page.name;
summary = page.summary;
font = page.font;
hideTitleWhenPinned = page.hideTitleWhenPinned;
alignCenter = page.alignCenter;
text = page.text;
eyeCatchingImageId = page.eyeCatchingImageId;
}
}
init();
const headerTabs = $computed(() => [{
key: 'settings',
title: i18n.ts._pages.pageSetting,
icon: 'fas fa-cog',
}, {
key: 'contents',
title: i18n.ts._pages.contents,
icon: 'fas fa-sticky-note',
}]);
definePageMetadata(computed(() => {
let title = i18n.ts._pages.newPage;
if (props.initPageId) {
title = i18n.ts._pages.editPage;
}
else if (props.initPageName && props.initUser) {
title = i18n.ts._pages.readPage;
}
return {
title,
icon: 'fas fa-pencil-alt',
};
}));
</script>
<style lang="scss" scoped>
.jqqmcavi {
margin-bottom: 1em;
> .button {
& + .button {
margin-left: 8px;
}
}
}
</style>

View file

@ -0,0 +1,69 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.button }}</template>
<section class="xfhsjczc">
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._button.text }}</template></MkInput>
<MkSwitch v-model="value.primary"><span>{{ $ts._pages.blocks._button.colored }}</span></MkSwitch>
<MkSelect v-model="value.action">
<template #label>{{ $ts._pages.blocks._button.action }}</template>
<option value="dialog">{{ $ts._pages.blocks._button._action.dialog }}</option>
<option value="resetRandom">{{ $ts._pages.blocks._button._action.resetRandom }}</option>
<option value="pushEvent">{{ $ts._pages.blocks._button._action.pushEvent }}</option>
<option value="callAiScript">{{ $ts._pages.blocks._button._action.callAiScript }}</option>
</MkSelect>
<template v-if="value.action === 'dialog'">
<MkInput v-model="value.content"><template #label>{{ $ts._pages.blocks._button._action._dialog.content }}</template></MkInput>
</template>
<template v-else-if="value.action === 'pushEvent'">
<MkInput v-model="value.event"><template #label>{{ $ts._pages.blocks._button._action._pushEvent.event }}</template></MkInput>
<MkInput v-model="value.message"><template #label>{{ $ts._pages.blocks._button._action._pushEvent.message }}</template></MkInput>
<MkSelect v-model="value.var">
<template #label>{{ $ts._pages.blocks._button._action._pushEvent.variable }}</template>
<option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option>
<option v-for="v in hpml.getVarsByType()" :value="v.name">{{ v.name }}</option>
<optgroup :label="$ts._pages.script.pageVariables">
<option v-for="v in hpml.getPageVarsByType()" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.enviromentVariables">
<option v-for="v in hpml.getEnvVarsByType()" :value="v">{{ v }}</option>
</optgroup>
</MkSelect>
</template>
<template v-else-if="value.action === 'callAiScript'">
<MkInput v-model="value.fn"><template #label>{{ $ts._pages.blocks._button._action._callAiScript.functionName }}</template></MkInput>
</template>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkSelect from '@/components/form/select.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
withDefaults(defineProps<{
value: any,
hpml: any
}>(), {
value: {
text: '',
action: 'dialog',
content: null,
event: null,
message: null,
primary: false,
var: null,
fn: null,
},
});
</script>
<style lang="scss" scoped>
.xfhsjczc {
padding: 0 16px 0 16px;
}
</style>

View file

@ -0,0 +1,37 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-paint-brush"></i> {{ $ts._pages.blocks.canvas }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name">
<template #prefix><i class="fas fa-magic"></i></template>
<template #label>{{ $ts._pages.blocks._canvas.id }}</template>
</MkInput>
<MkInput v-model="value.width" type="number">
<template #label>{{ $ts._pages.blocks._canvas.width }}</template>
<template #suffix>px</template>
</MkInput>
<MkInput v-model="value.height" type="number">
<template #label>{{ $ts._pages.blocks._canvas.height }}</template>
<template #suffix>px</template>
</MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
width: 300,
height: 200,
},
});
</script>

View file

@ -0,0 +1,33 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.counter }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name">
<template #prefix><i class="fas fa-magic"></i></template>
<template #label>{{ $ts._pages.blocks._counter.name }}</template>
</MkInput>
<MkInput v-model="value.text">
<template #label>{{ $ts._pages.blocks._counter.text }}</template>
</MkInput>
<MkInput v-model="value.inc" type="number">
<template #label>{{ $ts._pages.blocks._counter.inc }}</template>
</MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -0,0 +1,67 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-question"></i> {{ $ts._pages.blocks.if }}</template>
<template #func>
<button class="_button" @click="add()">
<i class="fas fa-plus"></i>
</button>
</template>
<section class="romcojzs">
<MkSelect v-model="value.var">
<template #label>{{ $ts._pages.blocks._if.variable }}</template>
<option v-for="v in hpml.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
<optgroup :label="$ts._pages.script.pageVariables">
<option v-for="v in hpml.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.enviromentVariables">
<option v-for="v in hpml.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
</MkSelect>
<XBlocks v-model="value.children" class="children" :hpml="hpml"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, inject } from 'vue';
import { v4 as uuid } from 'uuid';
import XContainer from '../page-editor.container.vue';
import MkSelect from '@/components/form/select.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
const props = withDefaults(defineProps<{
value: any,
hpml: any
}>(), {
value: {
children: [],
var: null,
},
});
const getPageBlockList = inject<(any) => any>('getPageBlockList');
async function add() {
const { canceled, result: type } = await os.select({
title: i18n.ts._pages.chooseBlock,
groupedItems: getPageBlockList(),
});
if (canceled) return;
const id = uuid();
props.value.children.push({ id, type });
}
</script>
<style lang="scss" scoped>
.romcojzs {
padding: 0 16px 16px 16px;
}
</style>

View file

@ -0,0 +1,60 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-image"></i> {{ $ts._pages.blocks.image }}</template>
<template #func>
<button @click="choose()">
<i class="fas fa-folder-open"></i>
</button>
</template>
<section class="oyyftmcf">
<MkDriveFileThumbnail v-if="file" class="preview" :file="file" fit="contain" @click="choose()"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { onMounted } from 'vue';
import XContainer from '../page-editor.container.vue';
import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue';
import * as os from '@/os';
const props = withDefaults(defineProps<{
value: any
}>(), {
value: {
fileId: null,
},
});
let file: any = $ref(null);
async function choose() {
os.selectDriveFile(false).then((fileResponse: any) => {
file = fileResponse;
props.value.fileId = fileResponse.id;
});
}
onMounted(async () => {
if (props.value.fileId == null) {
await choose();
} else {
os.api('drive/files/show', {
fileId: props.value.fileId,
}).then(fileResponse => {
file = fileResponse;
});
}
});
</script>
<style lang="scss" scoped>
.oyyftmcf {
> .preview {
height: 150px;
}
}
</style>

View file

@ -0,0 +1,52 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-sticky-note"></i> {{ $ts._pages.blocks.note }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="id">
<template #label>{{ $ts._pages.blocks._note.id }}</template>
<template #caption>{{ $ts._pages.blocks._note.idDescription }}</template>
</MkInput>
<MkSwitch v-model="value.detailed"><span>{{ $ts._pages.blocks._note.detailed }}</span></MkSwitch>
<XNote v-if="note && !value.detailed" :key="note.id + ':normal'" v-model:note="note" style="margin-bottom: 16px;"/>
<XNoteDetailed v-if="note && value.detailed" :key="note.id + ':detail'" v-model:note="note" style="margin-bottom: 16px;"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { watch } from 'vue';
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
import XNote from '@/components/note.vue';
import XNoteDetailed from '@/components/note-detailed.vue';
import * as os from '@/os';
const props = withDefaults(defineProps<{
value: any
}>(), {
value: {
note: null,
detailed: false,
},
});
let id: any = $ref(props.value.note);
let note: any = $ref(null);
watch(id, async () => {
if (id && (id.startsWith('http://') || id.startsWith('https://'))) {
props.value.note = (id.endsWith('/') ? id.slice(0, -1) : id).split('/').pop();
} else {
props.value.note = id;
}
note = await os.api('notes/show', { noteId: props.value.note });
}, {
immediate: true,
});
</script>

View file

@ -0,0 +1,33 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.numberInput }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name">
<template #prefix><i class="fas fa-magic"></i></template>
<template #label>{{ $ts._pages.blocks._numberInput.name }}</template>
</MkInput>
<MkInput v-model="value.text">
<template #label>{{ $ts._pages.blocks._numberInput.text }}</template>
</MkInput>
<MkInput v-model="value.default" type="number">
<template #label>{{ $ts._pages.blocks._numberInput.default }}</template>
</MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -0,0 +1,30 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-paper-plane"></i> {{ $ts._pages.blocks.post }}</template>
<section style="padding: 16px;">
<MkTextarea v-model="value.text"><template #label>{{ $ts._pages.blocks._post.text }}</template></MkTextarea>
<MkSwitch v-model="value.attachCanvasImage"><span>{{ $ts._pages.blocks._post.attachCanvasImage }}</span></MkSwitch>
<MkInput v-if="value.attachCanvasImage" v-model="value.canvasId"><template #label>{{ $ts._pages.blocks._post.canvasId }}</template></MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
text: '',
attachCanvasImage: false,
canvasId: '',
},
});
</script>

View file

@ -0,0 +1,39 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.radioButton }}</template>
<section style="padding: 0 16px 16px 16px;">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._radioButton.name }}</template></MkInput>
<MkInput v-model="value.title"><template #label>{{ $ts._pages.blocks._radioButton.title }}</template></MkInput>
<MkTextarea v-model="values"><template #label>{{ $ts._pages.blocks._radioButton.values }}</template></MkTextarea>
<MkInput v-model="value.default"><template #label>{{ $ts._pages.blocks._radioButton.default }}</template></MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { watch } from 'vue';
import XContainer from '../page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkInput from '@/components/form/input.vue';
const props = withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
title: '',
values: [],
},
});
let values: string = $ref(props.value.values.join('\n'));
watch(values, () => {
props.value.values = values.split('\n');
}, {
deep: true,
});
</script>

View file

@ -0,0 +1,75 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-sticky-note"></i> {{ value.title }}</template>
<template #func>
<button class="_button" @click="rename()">
<i class="fas fa-pencil-alt"></i>
</button>
<button class="_button" @click="add()">
<i class="fas fa-plus"></i>
</button>
</template>
<section class="ilrvjyvi">
<XBlocks v-model="value.children" class="children" :hpml="hpml"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, inject, onMounted } from 'vue';
import { v4 as uuid } from 'uuid';
import XContainer from '../page-editor.container.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
const props = withDefaults(defineProps<{
value: any,
hpml: any
}>(), {
value: {
title: null,
children: [],
},
});
const getPageBlockList = inject<(any) => any>('getPageBlockList');
async function rename() {
const { canceled, result: title } = await os.inputText({
title: 'Enter title',
default: props.value.title,
});
if (canceled) return;
props.value.title = title;
}
async function add() {
const { canceled, result: type } = await os.select({
title: i18n.ts._pages.chooseBlock,
groupedItems: getPageBlockList(),
});
if (canceled) return;
const id = uuid();
props.value.children.push({ id, type });
}
onMounted(() => {
if (props.value.title == null) {
rename();
}
});
</script>
<style lang="scss" scoped>
.ilrvjyvi {
> .children {
padding: 16px;
}
}
</style>

View file

@ -0,0 +1,33 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.switch }}</template>
<section class="kjuadyyj">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._switch.name }}</template></MkInput>
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._switch.text }}</template></MkInput>
<MkSwitch v-model="value.default"><span>{{ $ts._pages.blocks._switch.default }}</span></MkSwitch>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>
<style lang="scss" scoped>
.kjuadyyj {
padding: 0 16px 16px 16px;
}
</style>

View file

@ -0,0 +1,26 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.textInput }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._textInput.name }}</template></MkInput>
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._textInput.text }}</template></MkInput>
<MkInput v-model="value.default" type="text"><template #label>{{ $ts._pages.blocks._textInput.default }}</template></MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -0,0 +1,44 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.text }}</template>
<section class="vckmsadr">
<textarea v-model="value.text"></textarea>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
text: '',
},
});
</script>
<style lang="scss" scoped>
.vckmsadr {
> textarea {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
min-width: 100%;
min-height: 150px;
border: none;
box-shadow: none;
padding: 16px;
background: transparent;
color: var(--fg);
font-size: 14px;
box-sizing: border-box;
}
}
</style>

View file

@ -0,0 +1,27 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.textareaInput }}</template>
<section style="padding: 0 16px 16px 16px;">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._textareaInput.name }}</template></MkInput>
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._textareaInput.text }}</template></MkInput>
<MkTextarea v-model="value.default"><template #label>{{ $ts._pages.blocks._textareaInput.default }}</template></MkTextarea>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -0,0 +1,44 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.textarea }}</template>
<section class="ihymsbbe">
<textarea v-model="value.text"></textarea>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
text: '',
},
});
</script>
<style lang="scss" scoped>
.ihymsbbe {
> textarea {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
min-width: 100%;
min-height: 150px;
border: none;
box-shadow: none;
padding: 16px;
background: transparent;
color: var(--fg);
font-size: 14px;
box-sizing: border-box;
}
}
</style>

View file

@ -0,0 +1,78 @@
<template>
<XDraggable v-model="blocks" tag="div" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
<template #item="{element}">
<component :is="'x-' + element.type" :value="element" :hpml="hpml" @update:value="updateItem" @remove="() => removeItem(element)"/>
</template>
</XDraggable>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';
import XSection from './els/page-editor.el.section.vue';
import XText from './els/page-editor.el.text.vue';
import XTextarea from './els/page-editor.el.textarea.vue';
import XImage from './els/page-editor.el.image.vue';
import XButton from './els/page-editor.el.button.vue';
import XTextInput from './els/page-editor.el.text-input.vue';
import XTextareaInput from './els/page-editor.el.textarea-input.vue';
import XNumberInput from './els/page-editor.el.number-input.vue';
import XSwitch from './els/page-editor.el.switch.vue';
import XIf from './els/page-editor.el.if.vue';
import XPost from './els/page-editor.el.post.vue';
import XCounter from './els/page-editor.el.counter.vue';
import XRadioButton from './els/page-editor.el.radio-button.vue';
import XCanvas from './els/page-editor.el.canvas.vue';
import XNote from './els/page-editor.el.note.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote,
},
props: {
modelValue: {
type: Array,
required: true,
},
hpml: {
required: true,
},
},
emits: ['update:modelValue'],
computed: {
blocks: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
},
},
},
methods: {
updateItem(v) {
const i = this.blocks.findIndex(x => x.id === v.id);
const newValue = [
...this.blocks.slice(0, i),
v,
...this.blocks.slice(i + 1),
];
this.$emit('update:modelValue', newValue);
},
removeItem(el) {
const i = this.blocks.findIndex(x => x.id === el.id);
const newValue = [
...this.blocks.slice(0, i),
...this.blocks.slice(i + 1),
];
this.$emit('update:modelValue', newValue);
},
},
});
</script>

View file

@ -0,0 +1,143 @@
<template>
<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
<header>
<div class="title"><slot name="header"></slot></div>
<div class="buttons">
<slot name="func"></slot>
<button v-if="removable" class="_button" @click="emit('remove')">
<i class="fas fa-trash-alt"></i>
</button>
<button v-if="draggable" class="drag-handle _button">
<i class="fas fa-bars"></i>
</button>
<button class="_button" @click="toggleContent(!showBody)">
<template v-if="showBody"><i class="fas fa-angle-up"></i></template>
<template v-else><i class="fas fa-angle-down"></i></template>
</button>
</div>
</header>
<p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p>
<p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
<div v-show="showBody" class="body">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n';
const emit = defineEmits<{
(ev: 'toggle', v: boolean): void;
(ev: 'remove'): void;
}>();
const props = withDefaults(defineProps<{
expanded?: boolean;
removable?: boolean;
draggable?: boolean;
error?: { arg: number; expect: string; actual: string; };
warn?: { slot: number; };
}>(), {
expanded: true,
removable: true,
draggable: false,
});
let showBody = $ref(props.expanded);
function toggleContent(show: boolean) {
showBody = show;
emit('toggle', show);
}
</script>
<style lang="scss" scoped>
.cpjygsrt {
position: relative;
overflow: hidden;
background: var(--panel);
border: solid 2px var(--X12);
border-radius: 6px;
&:hover {
border: solid 2px var(--X13);
}
&.warn {
border: solid 2px #dec44c;
}
&.error {
border: solid 2px #f00;
}
& + .cpjygsrt {
margin-top: 16px;
}
> header {
> .title {
z-index: 1;
margin: 0;
padding: 0 16px;
line-height: 42px;
font-size: 0.9em;
font-weight: bold;
box-shadow: 0 1px rgba(#000, 0.07);
> i {
margin-right: 6px;
}
&:empty {
display: none;
}
}
> .buttons {
position: absolute;
z-index: 2;
top: 0;
right: 0;
> button {
padding: 0;
width: 42px;
font-size: 0.9em;
line-height: 42px;
}
.drag-handle {
cursor: move;
}
}
}
> .warn {
color: #b19e49;
margin: 0;
padding: 16px 16px 0 16px;
font-size: 14px;
}
> .error {
color: #f00;
margin: 0;
padding: 16px 16px 0 16px;
font-size: 14px;
}
> .body {
::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
&:not(.inline):first-child {
margin-top: 28px;
}
&:not(.inline):last-child {
margin-bottom: 20px;
}
}
}
}
</style>

View file

@ -0,0 +1,279 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :removable="removable" :error="error" :warn="warn" :draggable="draggable" @remove="() => $emit('remove')">
<template #header><i v-if="icon" :class="icon"></i> <template v-if="title">{{ title }} <span v-if="typeText" class="turmquns">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
<template #func>
<button class="_button" @click="changeType()">
<i class="fas fa-pencil-alt"></i>
</button>
</template>
<section v-if="modelValue.type === null" class="pbglfege" @click="changeType()">
{{ $ts._pages.script.emptySlot }}
</section>
<section v-else-if="modelValue.type === 'text'" class="tbwccoaw">
<input v-model="modelValue.value"/>
</section>
<section v-else-if="modelValue.type === 'multiLineText'" class="tbwccoaw">
<textarea v-model="modelValue.value"></textarea>
</section>
<section v-else-if="modelValue.type === 'textList'" class="tbwccoaw">
<textarea v-model="modelValue.value" :placeholder="$ts._pages.script.blocks._textList.info"></textarea>
</section>
<section v-else-if="modelValue.type === 'number'" class="tbwccoaw">
<input v-model="modelValue.value" type="number"/>
</section>
<section v-else-if="modelValue.type === 'ref'" class="hpdwcrvs">
<select v-model="modelValue.value">
<option v-for="v in hpml.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
<optgroup :label="$ts._pages.script.argVariables">
<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.pageVariables">
<option v-for="v in hpml.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.enviromentVariables">
<option v-for="v in hpml.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
</select>
</section>
<section v-else-if="modelValue.type === 'aiScriptVar'" class="tbwccoaw">
<input v-model="modelValue.value"/>
</section>
<section v-else-if="modelValue.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
<MkTextarea v-model="slots">
<template #label>{{ $ts._pages.script.blocks._fn.slots }}</template>
<template #caption>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
</MkTextarea>
<XV v-if="modelValue.value.expression" v-model="modelValue.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="modelValue.value.slots" :name="name"/>
</section>
<section v-else-if="modelValue.type.startsWith('fn:')" class="" style="padding:16px;">
<XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="hpml.getVarByName(modelValue.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name"/>
</section>
<section v-else class="" style="padding:16px;">
<XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="$t(`_pages.script.blocks._${modelValue.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots"/>
</section>
</XContainer>
</template>
<script lang="ts">
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, defineComponent } from 'vue';
import { v4 as uuid } from 'uuid';
import XContainer from './page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import { blockDefs } from '@/scripts/hpml/index';
import * as os from '@/os';
import { isLiteralValue } from '@/scripts/hpml/expr';
import { funcDefs } from '@/scripts/hpml/lib';
export default defineComponent({
components: {
XContainer, MkTextarea,
XV: defineAsyncComponent(() => import('./page-editor.script-block.vue')),
},
inject: ['getScriptBlockList'],
props: {
getExpectedType: {
required: false,
default: null,
},
modelValue: {
required: true,
},
title: {
required: false,
},
removable: {
required: false,
default: false,
},
hpml: {
required: true,
},
name: {
required: true,
},
fnSlots: {
required: false,
},
draggable: {
required: false,
default: false,
},
},
data() {
return {
error: null,
warn: null,
slots: '',
};
},
computed: {
icon(): any {
if (this.modelValue.type === null) return null;
if (this.modelValue.type.startsWith('fn:')) return 'fas fa-plug';
return blockDefs.find(x => x.type === this.modelValue.type).icon;
},
typeText(): any {
if (this.modelValue.type === null) return null;
if (this.modelValue.type.startsWith('fn:')) return this.modelValue.type.split(':')[1];
return this.$t(`_pages.script.blocks.${this.modelValue.type}`);
},
},
watch: {
slots: {
handler() {
this.modelValue.value.slots = this.slots.split('\n').map(x => ({
name: x,
type: null,
}));
},
deep: true,
},
},
created() {
if (this.modelValue.value == null) this.modelValue.value = null;
if (this.modelValue.value && this.modelValue.value.slots) this.slots = this.modelValue.value.slots.map(x => x.name).join('\n');
this.$watch(() => this.modelValue.type, (t) => {
this.warn = null;
if (this.modelValue.type === 'fn') {
const id = uuid();
this.modelValue.value = {
slots: [],
expression: { id, type: null },
};
return;
}
if (this.modelValue.type && this.modelValue.type.startsWith('fn:')) {
const fnName = this.modelValue.type.split(':')[1];
const fn = this.hpml.getVarByName(fnName);
const empties = [];
for (let i = 0; i < fn.value.slots.length; i++) {
const id = uuid();
empties.push({ id, type: null });
}
this.modelValue.args = empties;
return;
}
if (isLiteralValue(this.modelValue)) return;
const empties = [];
for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
const id = uuid();
empties.push({ id, type: null });
}
this.modelValue.args = empties;
for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
const inType = funcDefs[this.modelValue.type].in[i];
if (typeof inType !== 'number') {
if (inType === 'number') this.modelValue.args[i].type = 'number';
if (inType === 'string') this.modelValue.args[i].type = 'text';
}
}
});
this.$watch(() => this.modelValue.args, (args) => {
if (args == null) {
this.warn = null;
return;
}
const emptySlotIndex = args.findIndex(x => x.type === null);
if (emptySlotIndex !== -1 && emptySlotIndex < args.length) {
this.warn = {
slot: emptySlotIndex,
};
} else {
this.warn = null;
}
}, {
deep: true,
});
this.$watch(() => this.hpml.variables, () => {
if (this.type != null && this.modelValue) {
this.error = this.hpml.typeCheck(this.modelValue);
}
}, {
deep: true,
});
},
methods: {
async changeType() {
const { canceled, result: type } = await os.select({
title: this.$ts._pages.selectType,
groupedItems: this.getScriptBlockList(this.getExpectedType ? this.getExpectedType() : null),
});
if (canceled) return;
this.modelValue.type = type;
},
_getExpectedType(slot: number) {
return this.hpml.getExpectedType(this.modelValue, slot);
},
},
});
</script>
<style lang="scss" scoped>
.turmquns {
opacity: 0.7;
}
.pbglfege {
opacity: 0.5;
padding: 16px;
text-align: center;
cursor: pointer;
color: var(--fg);
}
.tbwccoaw {
> input,
> textarea {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
max-width: 100%;
min-width: 100%;
border: none;
box-shadow: none;
padding: 16px;
font-size: 16px;
background: transparent;
color: var(--fg);
box-sizing: border-box;
}
> textarea {
min-height: 100px;
}
}
.hpdwcrvs {
padding: 16px;
> select {
display: block;
padding: 4px;
font-size: 16px;
width: 100%;
}
}
</style>

View file

@ -0,0 +1,531 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700">
<div class="jqqmcavi">
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton>
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton>
<MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
</div>
<div v-if="tab === 'settings'">
<div class="_formRoot">
<MkInput v-model="title" class="_formBlock">
<template #label>{{ $ts._pages.title }}</template>
</MkInput>
<MkInput v-model="summary" class="_formBlock">
<template #label>{{ $ts._pages.summary }}</template>
</MkInput>
<MkInput v-model="name" class="_formBlock">
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
<template #label>{{ $ts._pages.url }}</template>
</MkInput>
<MkSwitch v-model="alignCenter" class="_formBlock">{{ $ts._pages.alignCenter }}</MkSwitch>
<MkSelect v-model="font" class="_formBlock">
<template #label>{{ $ts._pages.font }}</template>
<option value="serif">{{ $ts._pages.fontSerif }}</option>
<option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option>
</MkSelect>
<MkSwitch v-model="hideTitleWhenPinned" class="_formBlock">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch>
<div class="eyeCatch">
<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="fas fa-plus"></i> {{ $ts._pages.eyeCatchingImageSet }}</MkButton>
<div v-else-if="eyeCatchingImage">
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
<MkButton v-if="!readonly" @click="removeEyeCatchingImage()"><i class="fas fa-trash-alt"></i> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton>
</div>
</div>
</div>
</div>
<div v-else-if="tab === 'contents'">
<div>
<XBlocks v-model="content" class="content" :hpml="hpml"/>
<MkButton v-if="!readonly" @click="add()"><i class="fas fa-plus"></i></MkButton>
</div>
</div>
<div v-else-if="tab === 'variables'">
<div class="qmuvgica">
<XDraggable v-show="variables.length > 0" v-model="variables" tag="div" class="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
<template #item="{element}">
<XVariable
:model-value="element"
:removable="true"
:hpml="hpml"
:name="element.name"
:title="element.name"
:draggable="true"
@remove="() => removeVariable(element)"
/>
</template>
</XDraggable>
<MkButton v-if="!readonly" class="add" @click="addVariable()"><i class="fas fa-plus"></i></MkButton>
</div>
</div>
<div v-else-if="tab === 'script'">
<div>
<MkTextarea v-model="script" class="_code"/>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, computed, provide, watch } from 'vue';
import 'prismjs';
import { highlight, languages } from 'prismjs/components/prism-core';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';
import 'prismjs/themes/prism-okaidia.css';
import 'vue-prism-editor/dist/prismeditor.min.css';
import { v4 as uuid } from 'uuid';
import XVariable from './page-editor.script-block.vue';
import XBlocks from './page-editor.blocks.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/form/select.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkInput from '@/components/form/input.vue';
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 * as os from '@/os';
import { selectFile } from '@/scripts/select-file';
import { mainRouter } from '@/router';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { $i } from '@/account';
const XDraggable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
const props = defineProps<{
initPageId?: string;
initPageName?: string;
initUser?: string;
}>();
let tab = $ref('settings');
let author = $ref($i);
let readonly = $ref(false);
let page = $ref(null);
let pageId = $ref(null);
let currentName = $ref(null);
let title = $ref('');
let summary = $ref(null);
let name = $ref(Date.now().toString());
let eyeCatchingImage = $ref(null);
let eyeCatchingImageId = $ref(null);
let font = $ref('sans-serif');
let content = $ref([]);
let alignCenter = $ref(false);
let hideTitleWhenPinned = $ref(false);
let variables = $ref([]);
let hpml = $ref(null);
let script = $ref('');
provide('readonly', readonly);
provide('getScriptBlockList', getScriptBlockList);
provide('getPageBlockList', getPageBlockList);
watch($$(eyeCatchingImageId), async () => {
if (eyeCatchingImageId == null) {
eyeCatchingImage = null;
} else {
eyeCatchingImage = await os.api('drive/files/show', {
fileId: eyeCatchingImageId,
});
}
});
function getSaveOptions() {
return {
title: title.trim(),
name: name.trim(),
summary,
font,
script,
hideTitleWhenPinned,
alignCenter,
content,
variables,
eyeCatchingImageId,
};
}
function save() {
const options = getSaveOptions();
const onError = err => {
if (err.id === '3d81ceae-475f-4600-b2a8-2bc116157532') {
if (err.info.param === 'name') {
os.alert({
type: 'error',
title: i18n.ts._pages.invalidNameTitle,
text: i18n.ts._pages.invalidNameText,
});
}
} else if (err.code === 'NAME_ALREADY_EXISTS') {
os.alert({
type: 'error',
text: i18n.ts._pages.nameAlreadyExists,
});
}
};
if (pageId) {
options.pageId = pageId;
os.api('pages/update', options)
.then(page => {
currentName = name.trim();
os.alert({
type: 'success',
text: i18n.ts._pages.updated,
});
}).catch(onError);
} else {
os.api('pages/create', options)
.then(created => {
pageId = created.id;
currentName = name.trim();
os.alert({
type: 'success',
text: i18n.ts._pages.created,
});
mainRouter.push(`/pages/edit/${pageId}`);
}).catch(onError);
}
}
function del() {
os.confirm({
type: 'warning',
text: i18n.t('removeAreYouSure', { x: title.trim() }),
}).then(({ canceled }) => {
if (canceled) return;
os.api('pages/delete', {
pageId,
}).then(() => {
os.alert({
type: 'success',
text: i18n.ts._pages.deleted,
});
mainRouter.push('/pages');
});
});
}
function duplicate() {
title = title + ' - copy';
name = name + '-copy';
os.api('pages/create', getSaveOptions()).then(created => {
pageId = created.id;
currentName = name.trim();
os.alert({
type: 'success',
text: i18n.ts._pages.created,
});
mainRouter.push(`/pages/edit/${pageId}`);
});
}
async function add() {
const { canceled, result: type } = await os.select({
type: null,
title: i18n.ts._pages.chooseBlock,
groupedItems: getPageBlockList(),
});
if (canceled) return;
const id = uuid();
content.push({ id, type });
}
async function addVariable() {
let { canceled, result: name } = await os.inputText({
title: i18n.ts._pages.enterVariableName,
});
if (canceled) return;
name = name.trim();
if (hpml.isUsedName(name)) {
os.alert({
type: 'error',
text: i18n.ts._pages.variableNameIsAlreadyUsed,
});
return;
}
const id = uuid();
variables.push({ id, name, type: null });
}
function removeVariable(v) {
variables = variables.filter(x => x.name !== v.name);
}
function getPageBlockList() {
return [{
label: i18n.ts._pages.contentBlocks,
items: [
{ value: 'section', text: i18n.ts._pages.blocks.section },
{ value: 'text', text: i18n.ts._pages.blocks.text },
{ value: 'image', text: i18n.ts._pages.blocks.image },
{ value: 'textarea', text: i18n.ts._pages.blocks.textarea },
{ value: 'note', text: i18n.ts._pages.blocks.note },
{ value: 'canvas', text: i18n.ts._pages.blocks.canvas },
],
}, {
label: i18n.ts._pages.inputBlocks,
items: [
{ value: 'button', text: i18n.ts._pages.blocks.button },
{ value: 'radioButton', text: i18n.ts._pages.blocks.radioButton },
{ value: 'textInput', text: i18n.ts._pages.blocks.textInput },
{ value: 'textareaInput', text: i18n.ts._pages.blocks.textareaInput },
{ value: 'numberInput', text: i18n.ts._pages.blocks.numberInput },
{ value: 'switch', text: i18n.ts._pages.blocks.switch },
{ value: 'counter', text: i18n.ts._pages.blocks.counter },
],
}, {
label: i18n.ts._pages.specialBlocks,
items: [
{ value: 'if', text: i18n.ts._pages.blocks.if },
{ value: 'post', text: i18n.ts._pages.blocks.post },
],
}];
}
function getScriptBlockList(type: string = null) {
const list = [];
const blocks = blockDefs.filter(block => type == null || block.out == null || block.out === type || typeof block.out === 'number');
for (const block of blocks) {
const category = list.find(x => x.category === block.category);
if (category) {
category.items.push({
value: block.type,
text: i18n.t(`_pages.script.blocks.${block.type}`),
});
} else {
list.push({
category: block.category,
label: i18n.t(`_pages.script.categories.${block.category}`),
items: [{
value: block.type,
text: i18n.t(`_pages.script.blocks.${block.type}`),
}],
});
}
}
const userFns = variables.filter(x => x.type === 'fn');
if (userFns.length > 0) {
list.unshift({
label: i18n.t('_pages.script.categories.fn'),
items: userFns.map(v => ({
value: 'fn:' + v.name,
text: v.name,
})),
});
}
return list;
}
function setEyeCatchingImage(evt) {
selectFile(evt.currentTarget ?? evt.target, null).then(file => {
eyeCatchingImageId = file.id;
});
}
function removeEyeCatchingImage() {
eyeCatchingImageId = null;
}
function highlighter(code) {
return highlight(code, languages.js, 'javascript');
}
async function init() {
hpml = new HpmlTypeChecker();
watch($$(variables), () => {
hpml.variables = variables;
}, { deep: true });
watch($$(content), () => {
hpml.pageVars = collectPageVars(content);
}, { deep: true });
if (props.initPageId) {
page = await os.api('pages/show', {
pageId: props.initPageId,
});
} else if (props.initPageName && props.initUser) {
page = await os.api('pages/show', {
name: props.initPageName,
username: props.initUser,
});
readonly = true;
}
if (page) {
author = page.user;
pageId = page.id;
title = page.title;
name = page.name;
currentName = page.name;
summary = page.summary;
font = page.font;
script = page.script;
hideTitleWhenPinned = page.hideTitleWhenPinned;
alignCenter = page.alignCenter;
content = page.content;
variables = page.variables;
eyeCatchingImageId = page.eyeCatchingImageId;
} else {
const id = uuid();
content = [{
id,
type: 'text',
text: 'Hello World!',
}];
}
}
init();
const headerActions = $computed(() => []);
const headerTabs = $computed(() => [{
key: 'settings',
title: i18n.ts._pages.pageSetting,
icon: 'fas fa-cog',
}, {
key: 'contents',
title: i18n.ts._pages.contents,
icon: 'fas fa-sticky-note',
}, {
key: 'variables',
title: i18n.ts._pages.variables,
icon: 'fas fa-magic',
}, {
key: 'script',
title: i18n.ts.script,
icon: 'fas fa-code',
}]);
definePageMetadata(computed(() => {
let title = i18n.ts._pages.newPage;
if (props.initPageId) {
title = i18n.ts._pages.editPage;
}
else if (props.initPageName && props.initUser) {
title = i18n.ts._pages.readPage;
}
return {
title,
icon: 'fas fa-pencil-alt',
};
}));
</script>
<style lang="scss" scoped>
.jqqmcavi {
> .button {
& + .button {
margin-left: 8px;
}
}
}
.gwbmwxkm {
position: relative;
> header {
> .title {
z-index: 1;
margin: 0;
padding: 0 16px;
line-height: 42px;
font-size: 0.9em;
font-weight: bold;
box-shadow: 0 1px rgba(#000, 0.07);
> i {
margin-right: 6px;
}
&:empty {
display: none;
}
}
> .buttons {
position: absolute;
z-index: 2;
top: 0;
right: 0;
> button {
padding: 0;
width: 42px;
font-size: 0.9em;
line-height: 42px;
}
}
}
> section {
padding: 0 32px 32px 32px;
@media (max-width: 500px) {
padding: 0 16px 16px 16px;
}
> .view {
display: inline-block;
margin: 16px 0 0 0;
font-size: 14px;
}
> .content {
margin-bottom: 16px;
}
> .eyeCatch {
margin-bottom: 16px;
> div {
> img {
max-width: 100%;
}
}
}
}
}
.qmuvgica {
padding: 16px;
> .variables {
margin-bottom: 16px;
}
> .add {
margin-bottom: 16px;
}
}
</style>

View file

@ -1,15 +1,20 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader/></template>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700">
<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh">
<div class="_block main">
<!--
<div class="header">
<h1>{{ page.title }}</h1>
</div>
-->
<div class="banner">
<img v-if="page.eyeCatchingImageId" :src="page.eyeCatchingImage.url"/>
</div>
<div class="content" :class="{ center: page.alignCenter, serif: page.font === 'serif' }">
<Mfm :text="page.text" :is-note="false"/>
<div class="content">
<XPage :page="page"/>
</div>
<div class="actions">
<div class="like">
@ -58,6 +63,7 @@
<script lang="ts" setup>
import { computed, watch } from 'vue';
import XPage from '@/components/page/page.vue';
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import { url } from '@/config';
@ -190,16 +196,6 @@ definePageMetadata(computed(() => page ? {
> .content {
margin-top: 1em;
padding: 1em;
&.serif {
> div {
font-family: serif;
}
}
&.center {
text-align: center;
}
}
> .actions {

View file

@ -12,7 +12,7 @@ const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
export const routes = [{
path: '/@:initUser/pages/:initPageName/view-source',
component: page(() => import('./pages/page-editor.vue')),
component: page(() => import('./pages/page-editor/page-editor.vue')),
}, {
path: '/@:username/pages/:pageName',
component: page(() => import('./pages/page.vue')),
@ -121,11 +121,11 @@ export const routes = [{
component: page(() => import('./pages/tag.vue')),
}, {
path: '/pages/new',
component: page(() => import('./pages/page-editor.vue')),
component: page(() => import('./pages/page-editor/page-editor.vue')),
loginRequired: true,
}, {
path: '/pages/edit/:initPageId',
component: page(() => import('./pages/page-editor.vue')),
component: page(() => import('./pages/page-editor/page-editor.vue')),
loginRequired: true,
}, {
path: '/pages',

View file

@ -0,0 +1,48 @@
export function collectPageVars(content) {
const pageVars = [];
const collect = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'textInput') {
pageVars.push({
name: x.name,
type: 'string',
value: x.default || '',
});
} else if (x.type === 'textareaInput') {
pageVars.push({
name: x.name,
type: 'string',
value: x.default || '',
});
} else if (x.type === 'numberInput') {
pageVars.push({
name: x.name,
type: 'number',
value: x.default || 0,
});
} else if (x.type === 'switch') {
pageVars.push({
name: x.name,
type: 'boolean',
value: x.default || false,
});
} else if (x.type === 'counter') {
pageVars.push({
name: x.name,
type: 'number',
value: 0,
});
} else if (x.type === 'radioButton') {
pageVars.push({
name: x.name,
type: 'string',
value: x.default || '',
});
} else if (x.children) {
collect(x.children);
}
}
};
collect(content);
return pageVars;
}

View file

@ -157,6 +157,19 @@ export function getNoteMenu(props: {
props.isDeleted.value = true;
}
async function promote(): Promise<void> {
const { canceled, result: days } = await os.inputNumber({
title: i18n.ts.numberOfDays,
});
if (canceled) return;
os.apiWithDialog('admin/promo/create', {
noteId: appearNote.id,
expiresAt: Date.now() + (86400000 * days),
});
}
function share(): void {
navigator.share({
title: i18n.t('noteOf', { user: appearNote.user.name }),
@ -258,6 +271,16 @@ export function getNoteMenu(props: {
text: i18n.ts.pin,
action: () => togglePin(true),
} : undefined,
/*
...($i.isModerator || $i.isAdmin ? [
null,
{
icon: 'fas fa-bullhorn',
text: i18n.ts.promote,
action: promote
}]
: []
),*/
...(appearNote.userId !== $i.id ? [
null,
{

View file

@ -0,0 +1,109 @@
// blocks
export type BlockBase = {
id: string;
type: string;
};
export type TextBlock = BlockBase & {
type: 'text';
text: string;
};
export type SectionBlock = BlockBase & {
type: 'section';
title: string;
children: (Block | VarBlock)[];
};
export type ImageBlock = BlockBase & {
type: 'image';
fileId: string | null;
};
export type ButtonBlock = BlockBase & {
type: 'button';
text: any;
primary: boolean;
action: string;
content: string;
event: string;
message: string;
var: string;
fn: string;
};
export type IfBlock = BlockBase & {
type: 'if';
var: string;
children: Block[];
};
export type TextareaBlock = BlockBase & {
type: 'textarea';
text: string;
};
export type PostBlock = BlockBase & {
type: 'post';
text: string;
attachCanvasImage: boolean;
canvasId: string;
};
export type CanvasBlock = BlockBase & {
type: 'canvas';
name: string; // canvas id
width: number;
height: number;
};
export type NoteBlock = BlockBase & {
type: 'note';
detailed: boolean;
note: string | null;
};
export type Block =
TextBlock | SectionBlock | ImageBlock | ButtonBlock | IfBlock | TextareaBlock | PostBlock | CanvasBlock | NoteBlock | VarBlock;
// variable blocks
export type VarBlockBase = BlockBase & {
name: string;
};
export type NumberInputVarBlock = VarBlockBase & {
type: 'numberInput';
text: string;
};
export type TextInputVarBlock = VarBlockBase & {
type: 'textInput';
text: string;
};
export type SwitchVarBlock = VarBlockBase & {
type: 'switch';
text: string;
};
export type RadioButtonVarBlock = VarBlockBase & {
type: 'radioButton';
title: string;
values: string[];
};
export type CounterVarBlock = VarBlockBase & {
type: 'counter';
text: string;
inc: number;
};
export type VarBlock =
NumberInputVarBlock | TextInputVarBlock | SwitchVarBlock | RadioButtonVarBlock | CounterVarBlock;
const varBlock = ['numberInput', 'textInput', 'switch', 'radioButton', 'counter'];
export function isVarBlock(block: Block): block is VarBlock {
return varBlock.includes(block.type);
}

View file

@ -0,0 +1,232 @@
import autobind from 'autobind-decorator';
import { AiScript, utils, values } from '@syuilo/aiscript';
import { markRaw, ref, Ref, unref } from 'vue';
import { createAiScriptEnv } from '../aiscript/api';
import { collectPageVars } from '../collect-page-vars';
import { initHpmlLib, initAiLib } from './lib';
import { Expr, isLiteralValue, Variable } from './expr';
import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
import { version } from '@/config';
import * as os from '@/os';
/**
* Hpml evaluator
*/
export class Hpml {
private variables: Variable[];
private pageVars: PageVar[];
private envVars: Record<keyof typeof envVarsDef, any>;
public aiscript?: AiScript;
public pageVarUpdatedCallback?: values.VFn;
public canvases: Record<string, HTMLCanvasElement> = {};
public vars: Ref<Record<string, any>> = ref({});
public page: Record<string, any>;
private opts: {
randomSeed: string; visitor?: any; url?: string;
enableAiScript: boolean;
};
constructor(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 = markRaw(new AiScript({ ...createAiScriptEnv({
storageKey: 'pages:' + this.page.id,
}), ...initAiLib(this) }, {
in: (q) => {
return new Promise(ok => {
os.inputText({
title: q,
}).then(({ canceled, result: a }) => {
ok(a);
});
});
},
out: (value) => {
console.log(value);
},
log: (type, params) => {
},
}));
this.aiscript.scope.opts.onUpdated = (name, value) => {
this.eval();
};
}
const date = new Date();
this.envVars = {
AI: 'kawaii',
VERSION: version,
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 : '',
USERID: opts.visitor ? opts.visitor.id : '',
NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0,
FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0,
IS_CAT: opts.visitor ? opts.visitor.isCat : false,
SEED: opts.randomSeed ? opts.randomSeed : '',
YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`,
AISCRIPT_DISABLED: !this.opts.enableAiScript,
NULL: null,
};
this.eval();
}
@autobind
public eval() {
try {
this.vars.value = this.evaluateVars();
} catch (err) {
//this.onError(e);
}
}
@autobind
public interpolate(str: string) {
if (str == null) return null;
return str.replace(/{(.+?)}/g, match => {
const v = unref(this.vars)[match.slice(1, -1).trim()];
return v == null ? 'NULL' : v.toString();
});
}
@autobind
public callAiScript(fn: string) {
try {
if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []);
} catch (err) {}
}
@autobind
public registerCanvas(id: string, canvas: any) {
this.canvases[id] = canvas;
}
@autobind
public updatePageVar(name: string, value: any) {
const pageVar = this.pageVars.find(v => v.name === name);
if (pageVar !== undefined) {
pageVar.value = value;
if (this.pageVarUpdatedCallback) {
if (this.aiscript) this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]);
}
} else {
throw new HpmlError(`No such page var '${name}'`);
}
}
@autobind
public updateRandomSeed(seed: string) {
this.opts.randomSeed = seed;
this.envVars.SEED = seed;
}
@autobind
private _interpolateScope(str: string, scope: HpmlScope) {
return str.replace(/{(.+?)}/g, match => {
const v = scope.getState(match.slice(1, -1).trim());
return v == null ? 'NULL' : v.toString();
});
}
@autobind
public evaluateVars(): Record<string, any> {
const values: Record<string, any> = {};
for (const [k, v] of Object.entries(this.envVars)) {
values[k] = v;
}
for (const v of this.pageVars) {
values[v.name] = v.value;
}
for (const v of this.variables) {
values[v.name] = this.evaluate(v, new HpmlScope([values]));
}
return values;
}
@autobind
private evaluate(expr: Expr, scope: HpmlScope): any {
if (isLiteralValue(expr)) {
if (expr.type === null) {
return null;
}
if (expr.type === 'number') {
return parseInt((expr.value as any), 10);
}
if (expr.type === 'text' || expr.type === 'multiLineText') {
return this._interpolateScope(expr.value || '', scope);
}
if (expr.type === 'textList') {
return this._interpolateScope(expr.value || '', scope).trim().split('\n');
}
if (expr.type === 'ref') {
return scope.getState(expr.value);
}
if (expr.type === 'aiScriptVar') {
if (this.aiscript) {
try {
return utils.valToJs(this.aiscript.scope.get(expr.value));
} catch (err) {
return null;
}
} else {
return null;
}
}
// Define user function
if (expr.type === 'fn') {
return {
slots: expr.value.slots.map(x => x.name),
exec: (slotArg: Record<string, any>) => {
return this.evaluate(expr.value.expression, scope.createChildScope(slotArg, expr.id));
},
} as Fn;
}
return;
}
// Call user function
if (expr.type.startsWith('fn:')) {
const fnName = expr.type.split(':')[1];
const fn = scope.getState(fnName);
const args = {} as Record<string, any>;
for (let i = 0; i < fn.slots.length; i++) {
const name = fn.slots[i];
args[name] = this.evaluate(expr.args[i], scope);
}
return fn.exec(args);
}
if (expr.args === undefined) return null;
const funcs = initHpmlLib(expr, scope, this.opts.randomSeed, this.opts.visitor);
// Call function
const fnName = expr.type;
const fn = (funcs as any)[fnName];
if (fn == null) {
throw new HpmlError(`No such function '${fnName}'`);
} else {
return fn(...expr.args.map(x => this.evaluate(x, scope)));
}
}
}

View file

@ -0,0 +1,79 @@
import { literalDefs, Type } from '.';
export type ExprBase = {
id: string;
};
// value
export type EmptyValue = ExprBase & {
type: null;
value: null;
};
export type TextValue = ExprBase & {
type: 'text';
value: string;
};
export type MultiLineTextValue = ExprBase & {
type: 'multiLineText';
value: string;
};
export type TextListValue = ExprBase & {
type: 'textList';
value: string;
};
export type NumberValue = ExprBase & {
type: 'number';
value: number;
};
export type RefValue = ExprBase & {
type: 'ref';
value: string; // value is variable name
};
export type AiScriptRefValue = ExprBase & {
type: 'aiScriptVar';
value: string; // value is variable name
};
export type UserFnValue = ExprBase & {
type: 'fn';
value: UserFnInnerValue;
};
type UserFnInnerValue = {
slots: {
name: string;
type: Type;
}[];
expression: Expr;
};
export type Value =
EmptyValue | TextValue | MultiLineTextValue | TextListValue | NumberValue | RefValue | AiScriptRefValue | UserFnValue;
export function isLiteralValue(expr: Expr): expr is Value {
if (expr.type == null) return true;
if (literalDefs[expr.type]) return true;
return false;
}
// call function
export type CallFn = ExprBase & { // "fn:hoge" or string
type: string;
args: Expr[];
value: null;
};
// variable
export type Variable = (Value | CallFn) & {
name: string;
};
// expression
export type Expr = Variable | Value | CallFn;

View file

@ -0,0 +1,103 @@
/**
* Hpml
*/
import autobind from 'autobind-decorator';
import { Hpml } from './evaluator';
import { funcDefs } from './lib';
export type Fn = {
slots: string[];
exec: (args: Record<string, any>) => ReturnType<Hpml['evaluate']>;
};
export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
text: { out: 'string', category: 'value', icon: 'fas fa-quote-right' },
multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left' },
textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list' },
number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up' },
ref: { out: null, category: 'value', icon: 'fas fa-magic' },
aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic' },
fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt' },
};
export const blockDefs = [
...Object.entries(literalDefs).map(([k, v]) => ({
type: k, out: v.out, category: v.category, icon: v.icon,
})),
...Object.entries(funcDefs).map(([k, v]) => ({
type: k, out: v.out, category: v.category, icon: v.icon,
})),
];
export type PageVar = { name: string; value: any; type: Type; };
export const envVarsDef: Record<string, Type> = {
AI: 'string',
URL: 'string',
VERSION: 'string',
LOGIN: 'boolean',
NAME: 'string',
USERNAME: 'string',
USERID: 'string',
NOTES_COUNT: 'number',
FOLLOWERS_COUNT: 'number',
FOLLOWING_COUNT: 'number',
IS_CAT: 'boolean',
SEED: null,
YMD: 'string',
AISCRIPT_DISABLED: 'boolean',
NULL: null,
};
export class HpmlScope {
private layerdStates: Record<string, any>[];
public name: string;
constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) {
this.layerdStates = layerdStates;
this.name = name || 'anonymous';
}
@autobind
public createChildScope(states: Record<string, any>, name?: HpmlScope['name']): HpmlScope {
const layer = [states, ...this.layerdStates];
return new HpmlScope(layer, name);
}
/**
*
* @param name
*/
@autobind
public getState(name: string): any {
for (const later of this.layerdStates) {
const state = later[name];
if (state !== undefined) {
return state;
}
}
throw new HpmlError(
`No such variable '${name}' in scope '${this.name}'`, {
scope: this.layerdStates,
});
}
}
export class HpmlError extends Error {
public info?: any;
constructor(message: string, info?: any) {
super(message);
this.info = info;
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, HpmlError);
}
}
}

View file

@ -0,0 +1,247 @@
import tinycolor from 'tinycolor2';
import { values, utils } from '@syuilo/aiscript';
import seedrandom from 'seedrandom';
import { Hpml } from './evaluator';
import { Expr } from './expr';
import { Fn, HpmlScope } from '.';
/* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color
// 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 initAiLib(hpml: Hpml) {
return {
'MkPages:updated': values.FN_NATIVE(([callback]) => {
hpml.pageVarUpdatedCallback = (callback as values.VFn);
}),
'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]) => {
/* TODO
utils.assertString(id);
utils.assertObject(opts);
const canvas = hpml.canvases[id.value];
const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
Chart.defaults.color = '#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,
}
}]
}
})
}
});
*/
}),
};
}
export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'fas fa-share-alt' },
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' },
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-plus' },
subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-minus' },
multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-times' },
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' },
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' },
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' },
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' },
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' },
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' },
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' },
strLen: { in: ['string'], out: 'number', category: 'text', icon: 'fas fa-quote-right' },
strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' },
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' },
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' },
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' },
listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' },
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' },
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // dailyRandomPickWithProbabilityMapping
};
export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) {
const date = new Date();
const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
// SHOULD be fine to ignore since it's intended + function shape isn't defined
// eslint-disable-next-line @typescript-eslint/ban-types
const funcs: Record<string, Function> = {
not: (a: boolean) => !a,
or: (a: boolean, b: boolean) => a || b,
and: (a: boolean, b: boolean) => a && b,
eq: (a: any, b: any) => a === b,
notEq: (a: any, b: any) => a !== b,
gt: (a: number, b: number) => a > b,
lt: (a: number, b: number) => a < b,
gtEq: (a: number, b: number) => a >= b,
ltEq: (a: number, b: number) => a <= b,
if: (bool: boolean, a: any, b: any) => bool ? a : b,
for: (times: number, fn: Fn) => {
const result: any[] = [];
for (let i = 0; i < times; i++) {
result.push(fn.exec({
[fn.slots[0]]: i + 1,
}));
}
return result;
},
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b,
multiply: (a: number, b: number) => a * b,
divide: (a: number, b: number) => a / b,
mod: (a: number, b: number) => a % b,
round: (a: number) => Math.round(a),
strLen: (a: string) => a.length,
strPick: (a: string, b: number) => a[b - 1],
strReplace: (a: string, b: string, c: string) => a.split(b).join(c),
strReverse: (a: string) => a.split('').reverse().join(''),
join: (texts: string[], separator: string) => texts.join(separator || ''),
stringToNumber: (a: string) => parseInt(a),
numberToString: (a: number) => a.toString(),
splitStrByLine: (a: string) => a.split('\n'),
pick: (list: any[], i: number) => list[i - 1],
listLen: (list: any[]) => list.length,
random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * 100) < probability,
rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * (max - min + 1)),
randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * list.length)],
dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${expr.id}`)() * 100) < probability,
dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${expr.id}`)() * (max - min + 1)),
dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${expr.id}`)() * list.length)],
seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability,
seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)),
seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)],
DRPWPM: (list: string[]) => {
const xs: any[] = [];
let totalFactor = 0;
for (const x of list) {
const parts = x.split(' ');
const factor = parseInt(parts.pop()!, 10);
const text = parts.join(' ');
totalFactor += factor;
xs.push({ factor, text });
}
const r = seedrandom(`${day}:${expr.id}`)() * totalFactor;
let stackedFactor = 0;
for (const x of xs) {
if (r >= stackedFactor && r <= stackedFactor + x.factor) {
return x.text;
} else {
stackedFactor += x.factor;
}
}
return xs[0].text;
},
};
return funcs;
}

View file

@ -0,0 +1,189 @@
import autobind from 'autobind-decorator';
import { Expr, isLiteralValue, Variable } from './expr';
import { funcDefs } from './lib';
import { Type, envVarsDef, PageVar } from '.';
type TypeError = {
arg: number;
expect: Type;
actual: Type;
};
/**
* Hpml type checker
*/
export class HpmlTypeChecker {
public variables: Variable[];
public pageVars: PageVar[];
constructor(variables: HpmlTypeChecker['variables'] = [], pageVars: HpmlTypeChecker['pageVars'] = []) {
this.variables = variables;
this.pageVars = pageVars;
}
@autobind
public typeCheck(v: Expr): TypeError | null {
if (isLiteralValue(v)) return null;
const def = funcDefs[v.type || ''];
if (def == null) {
throw new Error('Unknown type: ' + v.type);
}
const generic: Type[] = [];
for (let i = 0; i < def.in.length; i++) {
const arg = def.in[i];
const type = this.infer(v.args[i]);
if (type === null) continue;
if (typeof arg === 'number') {
if (generic[arg] === undefined) {
generic[arg] = type;
} else if (type !== generic[arg]) {
return {
arg: i,
expect: generic[arg],
actual: type,
};
}
} else if (type !== arg) {
return {
arg: i,
expect: arg,
actual: type,
};
}
}
return null;
}
@autobind
public getExpectedType(v: Expr, slot: number): Type {
const def = funcDefs[v.type || ''];
if (def == null) {
throw new Error('Unknown type: ' + v.type);
}
const generic: Type[] = [];
for (let i = 0; i < def.in.length; i++) {
const arg = def.in[i];
const type = this.infer(v.args[i]);
if (type === null) continue;
if (typeof arg === 'number') {
if (generic[arg] === undefined) {
generic[arg] = type;
}
}
}
if (typeof def.in[slot] === 'number') {
return generic[def.in[slot]] || null;
} else {
return def.in[slot];
}
}
@autobind
public infer(v: Expr): Type {
if (v.type === null) return null;
if (v.type === 'text') return 'string';
if (v.type === 'multiLineText') return 'string';
if (v.type === 'textList') return 'stringArray';
if (v.type === 'number') return 'number';
if (v.type === 'ref') {
const variable = this.variables.find(va => va.name === v.value);
if (variable) {
return this.infer(variable);
}
const pageVar = this.pageVars.find(va => va.name === v.value);
if (pageVar) {
return pageVar.type;
}
const envVar = envVarsDef[v.value || ''];
if (envVar !== undefined) {
return envVar;
}
return null;
}
if (v.type === 'aiScriptVar') return null;
if (v.type === 'fn') return null; // todo
if (v.type.startsWith('fn:')) return null; // todo
const generic: Type[] = [];
const def = funcDefs[v.type];
for (let i = 0; i < def.in.length; i++) {
const arg = def.in[i];
if (typeof arg === 'number') {
const type = this.infer(v.args[i]);
if (generic[arg] === undefined) {
generic[arg] = type;
} else {
if (type !== generic[arg]) {
generic[arg] = null;
}
}
}
}
if (typeof def.out === 'number') {
return generic[def.out];
} else {
return def.out;
}
}
@autobind
public getVarByName(name: string): Variable {
const v = this.variables.find(x => x.name === name);
if (v !== undefined) {
return v;
} else {
throw new Error(`No such variable '${name}'`);
}
}
@autobind
public getVarsByType(type: Type): Variable[] {
if (type == null) return this.variables;
return this.variables.filter(x => (this.infer(x) === null) || (this.infer(x) === type));
}
@autobind
public getEnvVarsByType(type: Type): string[] {
if (type == null) return Object.keys(envVarsDef);
return Object.entries(envVarsDef).filter(([k, v]) => v === null || type === v).map(([k, v]) => k);
}
@autobind
public getPageVarsByType(type: Type): string[] {
if (type == null) return this.pageVars.map(v => v.name);
return this.pageVars.filter(v => type === v.type).map(v => v.name);
}
@autobind
public isUsedName(name: string) {
if (this.variables.some(v => v.name === name)) {
return true;
}
if (this.pageVars.some(v => v.name === name)) {
return true;
}
if (envVarsDef[name]) {
return true;
}
return false;
}
}