#toc または #toc=show は目次を強制的に表示し、#toc=hide は目次を強制的に非表示にします。ラベルが存在しない場合は、グローバル設定が適用されます",
+ "toc": "#toc または #toc=show は目次を強制的に表示し、#toc=hide は目次を強制的に非表示にします。ラベルが存在しない場合は、グローバル設定が適用されます",
"color": "ノートツリー、リンクなどのノートの色を定義します。 'red' や #a13d5f などの有効な CSS カラー値を使用します",
"keyboard_shortcut": "このノートにすぐにジャンプするキーボードショートカットを定義します。例: 「ctrl+alt+e」。変更を有効にするには、フロントエンドをリロードする必要があります。",
"keep_current_hoisting": "このリンクを開いても、ノートが現在のホイストされたサブツリーに表示できない場合でも、ホイストは変更されません。",
@@ -1077,7 +1117,29 @@
"run_on_child_note_creation": "このリレーションが定義されているノートの下に新しいノートが作成されたときに実行されます",
"render_note": "「HTMLノートをレンダリング」タイプのノートは、コードノート(HTMLまたはスクリプト)を使用してレンダリングされます。このリレーションを使用して、どのノートをレンダリングするかを指定する必要があります",
"other_notes_with_name": "{{attributeType}} の名前が「{{attributeName}}」であるその他のノート",
- "color_type": "色"
+ "color_type": "色",
+ "share_description": "説明用にHTMLメタタグに追加するテキストを定義します",
+ "title_template": "このノートの子として作成されるノートのデフォルトのタイトルです。値は JavaScript 文字列として評価されるため、\n 挿入された now および parentNote 変数を介して動的なコンテンツを追加できます。例: \n \n \n ${parentNote.getLabelValue('authorName')} の文学作品 \n ${now.format('YYYY-MM-DD HH:mm:ss')} のログ \n
\n \n 詳細については Wiki の詳細 、および parentNote と now の API ドキュメントを参照してください。",
+ "run_on_note_title_change": "ノートのタイトルが変更されたときに実行されます(ノートの作成も含む)",
+ "run_on_note_content_change": "ノートの内容が変更されたときに実行されます(ノートの作成も含む)。",
+ "run_on_note_change": "ノートが変更されたときに実行されます(ノートの作成も含む)。内容の変更は含まれません",
+ "run_on_note_deletion": "ノートが削除されたときに実行されます",
+ "run_on_branch_creation": "ブランチが作成されたときに実行されます。ブランチは親ノートと子ノート間のリンクであり、例えばノートの複製や移動の際に作成されます。",
+ "run_on_branch_change": "ブランチが更新されたときに実行されます。",
+ "run_on_branch_deletion": "ブランチが削除されたときに実行されます。ブランチは親ノートと子ノート間のリンクであり、ノートを移動した場合などに削除されます(古いブランチ/リンクは削除されます)。",
+ "run_on_attribute_creation": "このリレーションを定義するノートに新しい属性が作成された場合に実行されます",
+ "run_on_attribute_change": " このリレーションを定義するノートの属性が変更されたときに実行されます。属性が削除された場合にも実行されます",
+ "relation_template": "ノートの属性は親子関係がなくても継承されます。ノートの内容とサブツリーは、空の場合はインスタンスノートに追加されます。詳細はドキュメントをご覧ください。",
+ "inherit": "親子関係がなくても、ノートの属性は継承されます。同様の概念については、テンプレートリレーションを参照してください。ドキュメントの属性継承を参照してください。",
+ "widget_relation": "このリレーションのターゲットが実行され、サイドバーのウィジェットとしてレンダリングされます",
+ "share_css": "共有ページに挿入されるCSSノートです。CSSノートは共有サブツリーにも含まれている必要があります。「share_hidden_from_tree」と「share_omit_default_css」の使用も検討してください。",
+ "share_js": "共有ページに挿入されるJavaScriptノートです。JavaScriptノートは共有サブツリー内にも存在する必要があります。「share_hidden_from_tree」の使用を検討してください。",
+ "share_template": "共有ノートを表示するためのテンプレートとして使用される埋め込みJavaScriptノートです。デフォルトのテンプレートにフォールバックします。「share_hidden_from_tree」の使用を検討してください。",
+ "share_favicon": "共有ページに設定するファビコンのノートです。通常は共有ルートに設定し、継承可能にします。ファビコンのノートは共有サブツリーにも設定する必要があります。「share_hidden_from_tree」の使用を検討してください。",
+ "is_owned_by_note": "ノートによって所有されています",
+ "and_more": "...その他 {{count}} 件。",
+ "print_landscape": "PDF にエクスポートするときに、ページの向きを縦向きではなく横向きに変更します。",
+ "print_page_size": "PDF にエクスポートするときに、ページのサイズを変更します。サポートされる値: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger。"
},
"link_context_menu": {
"open_note_in_popup": "クイックエディット",
@@ -1104,11 +1166,14 @@
},
"template_switch": {
"template": "テンプレート",
- "toggle-on-hint": "ノートをテンプレート化する"
+ "toggle-on-hint": "ノートをテンプレート化する",
+ "toggle-off-hint": "テンプレートとしてのノートを削除する"
},
"open-help-page": "ヘルプページを開く",
"shared_info": {
- "help_link": "ヘルプについては、wikiをご覧ください。"
+ "help_link": "ヘルプについては、wikiをご覧ください。",
+ "shared_locally": "このノートは {{- link}} でローカルに共有されています。",
+ "shared_publicly": "このノートは {{- link}} で一般公開されています。"
},
"highlights_list_2": {
"title": "ハイライトリスト",
@@ -1132,7 +1197,10 @@
"auto-collapsing-notes-after-inactivity": "非アクティブ状態が続いたためノートが自動で折りたたまれます...",
"create-child-note": "子ノートを作成",
"hoist-this-note-workspace": "このノートをホイストする(ワークスペース)",
- "unhoist": "ホイスト解除"
+ "unhoist": "ホイスト解除",
+ "saved-search-note-refreshed": "保存した検索ノートが更新されました。",
+ "refresh-saved-search-results": "保存した検索結果を更新",
+ "toggle-sidebar": "サイドバーを切り替え"
},
"bulk_actions": {
"bulk_actions": "一括操作",
@@ -1143,7 +1211,7 @@
"execute_bulk_actions": "一括操作を実行",
"bulk_actions_executed": "一括操作が成功に実行されました。",
"labels": "ラベル",
- "relations": "関係",
+ "relations": "リレーション",
"notes": "ノート",
"other": "その他",
"none_yet": "アクションを上のリストからクリックして追加。"
@@ -1272,11 +1340,17 @@
"continue_anyway": "とにかく続ける",
"message_windows": "TriliumNext は現在エミュレーションを実行しています。つまり、ARM版WindowsデバイスでIntel(x64)版を使用していることになります。これはパフォーマンスとバッテリー寿命に重大な影響を及ぼします。",
"recommendation": "最適なエクスペリエンスを得るには、リリース ページから TriliumNext のネイティブ ARM64 バージョンをダウンロードしてください。",
- "download_link": "ネイティブ版をダウンロード"
+ "download_link": "ネイティブ版をダウンロード",
+ "message_macos": "TriliumNext は現在、Rosetta 2による翻訳環境で実行されています。つまり、Apple Silicon MacではIntel (x64)バージョンを使用していることになります。これはパフォーマンスとバッテリー寿命に重大な影響を及ぼします。"
},
"editorfeatures": {
"emoji_completion_enabled": "絵文字のオートコンプリートを有効",
- "note_completion_enabled": "ノートのオートコンプリートを有効"
+ "note_completion_enabled": "ノートのオートコンプリートを有効",
+ "emoji_completion_description": "有効にすると、「:」に続けて絵文字の名前を入力することで、テキストに絵文字を簡単に挿入できます。",
+ "note_completion_description": "有効にすると、「@」 に続けてノートのタイトルを入力することで、ノートへのリンクを作成できます。",
+ "slash_commands_enabled": "スラッシュコマンドを有効",
+ "slash_commands_description": "有効にすると、改行や見出しの挿入などの編集コマンドは、「/」 と入力して切り替えることができます。",
+ "title": "機能"
},
"table_context_menu": {
"delete_row": "行を削除"
@@ -1344,7 +1418,8 @@
"agent": {
"processing": "処理中...",
"loading": "読み込み中...",
- "generating": "生成中..."
+ "generating": "生成中...",
+ "thinking": "考え中..."
},
"name": "AI",
"openai": "OpenAI",
@@ -1352,7 +1427,7 @@
"ollama_no_url": "Ollamaは設定されていません。有効なURLを入力してください。",
"chat": {
"root_note_title": "AIチャット",
- "root_note_content": "このメモには、保存されたAIチャットの会話が含まれています。",
+ "root_note_content": "このノートには、保存されたAIチャットの会話が含まれています。",
"new_chat_title": "新しいチャット",
"create_new_ai_chat": "新しいAIチャットを作成"
},
@@ -1362,7 +1437,71 @@
"selected_provider_description": "チャットおよび補完機能のAIプロバイダーを選択する",
"selected_provider": "プロバイダーを選択",
"select_model": "モデルを選択...",
- "select_provider": "プロバイダーを選択..."
+ "select_provider": "プロバイダーを選択...",
+ "not_started": "開始されていません",
+ "processed_notes": "処理済みノート",
+ "total_notes": "ノートの総数",
+ "progress": "進行状況",
+ "queued_notes": "キューに登録されたノート",
+ "failed_notes": "失敗したノート",
+ "last_processed": "最終処理日時",
+ "refresh_stats": "統計情報を更新",
+ "temperature": "Temperature(温度)",
+ "url": "ベースURL",
+ "base_url": "ベースURL",
+ "enable_automatic_indexing": "自動インデックス作成を有効にする",
+ "rebuild_index": "インデックスの再構築",
+ "rebuild_index_error": "インデックスの再構築開始時にエラーが発生しました。詳細はログをご確認ください。",
+ "note_title": "ノートのタイトル",
+ "last_attempt": "最終試行日時",
+ "actions": "アクション",
+ "retry_queued": "ノートが再試行キューに追加されました",
+ "retry_failed": "ノートを再試行キューに追加できませんでした",
+ "max_notes_per_llm_query": "クエリあたりの最大ノート数",
+ "max_notes_per_llm_query_description": "AIコンテキストに含める類似ノートの最大数",
+ "active_providers": "アクティブなプロバイダー",
+ "disabled_providers": "無効なプロバイダー",
+ "remove_provider": "検索からプロバイダーを削除",
+ "restore_provider": "検索にプロバイダーを復元",
+ "similarity_threshold": "類似度のしきい値",
+ "similarity_threshold_description": "LLMクエリのコンテキストに含めるノートの最小類似度スコア(0~1)",
+ "reprocess_index": "検索インデックスを再構築",
+ "reprocessing_index": "再構築中...",
+ "reprocess_index_started": "検索インデックスの最適化がバックグラウンドで開始されました",
+ "reprocess_index_error": "検索インデックスの再構築中にエラーが発生しました",
+ "index_rebuild_progress": "インデックスの再構築の進行状況",
+ "index_rebuilding": "インデックスの最適化中({{percentage}}%)",
+ "index_rebuild_complete": "インデックスの最適化が完了しました",
+ "index_rebuild_status_error": "インデックスの再構築ステータスの確認中にエラーが発生しました",
+ "never": "なし",
+ "incomplete": "未完了 ({{percentage}}%)",
+ "note_queued_for_retry": "ノートが再試行キューに追加されました",
+ "failed_to_retry_note": "ノートの再試行に失敗しました",
+ "all_notes_queued_for_retry": "失敗したすべてのノートは再試行のためにキューに入れられました",
+ "failed_to_retry_all": "ノートの再試行に失敗しました",
+ "use_enhanced_context": "拡張されたコンテキストを使用する",
+ "enhanced_context_description": "ノートとその関連ノートからより多くのコンテキストをAIに提供し、より良い応答を実現します",
+ "show_thinking": "思考を表示",
+ "show_thinking_description": "AIの思考プロセスをチェーン表示",
+ "enter_message": "メッセージを入力...",
+ "error_generating_response": "AI応答の生成中にエラーが発生しました",
+ "index_all_notes": "すべてのノートをインデックスに登録",
+ "index_status": "インデックスのステータス",
+ "indexed_notes": "インデックス登録済みのノート",
+ "indexing_stopped": "インデックス登録を停止しました",
+ "indexing_in_progress": "インデックス登録中です...",
+ "last_indexed": "最終インデックス作成日時",
+ "n_notes_queued_0": "{{ count }} 件のノートがインデックス作成待ちです",
+ "note_chat": "ノートチャット",
+ "notes_indexed_0": "{{ count }} 件のノートをインデックスしました",
+ "sources": "ソース",
+ "start_indexing": "インデックス作成を開始",
+ "use_advanced_context": "高度なコンテキストを使用",
+ "ai_enabled": "AI 機能が有効",
+ "ai_disabled": "AI 機能が無効",
+ "no_models_found_online": "モデルが見つかりません。API キーと設定を確認してください。",
+ "no_models_found_ollama": "Ollama モデルが見つかりません。Ollama が実行中かどうかを確認してください。",
+ "error_fetching": "モデルの取得エラー: {{error}}"
},
"add_label": {
"add_label": "ラベルを追加",
@@ -1370,7 +1509,10 @@
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。",
"new_value_placeholder": "新しい値",
"help_text": "一致したすべてのノートに:",
- "help_text_note": "このメソッドを値なしで呼び出すこともできます。その場合、ラベルは値なしでノートに割り当てられます。"
+ "help_text_note": "このメソッドを値なしで呼び出すこともできます。その場合、ラベルは値なしでノートに割り当てられます。",
+ "to_value": "値",
+ "help_text_item1": "ノートにまだラベルがない場合は、指定されたラベルを作成します",
+ "help_text_item2": "または既存のラベルの値を変更する"
},
"delete_label": {
"delete_label": "ラベルを削除",
@@ -1391,32 +1533,39 @@
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。",
"new_value_placeholder": "新しい値",
"help_text_note": "このメソッドは値なしで呼び出すこともできます。その場合、値なしでラベルがノートに割り当てられます。",
- "help_text": "一致したすべてのノートで、既存のラベルの値を変更する。"
+ "help_text": "一致したすべてのノートで、既存のラベルの値を変更する。",
+ "to_value": "値"
},
"add_relation": {
- "add_relation": "関係を追加",
- "relation_name": "関係の名前",
+ "add_relation": "リレーションを追加",
+ "relation_name": "リレーションの名前",
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。",
- "create_relation_on_all_matched_notes": "一致したすべてのノートに対して、指定されたリレーションを作成します。"
+ "create_relation_on_all_matched_notes": "一致したすべてのノートに対して、指定されたリレーションを作成します。",
+ "target_note": "対象のノート",
+ "to": "を"
},
"delete_relation": {
- "delete_relation": "関係を削除",
- "relation_name": "関係の名前",
+ "delete_relation": "リレーションを削除",
+ "relation_name": "リレーションの名前",
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
},
"rename_relation": {
- "rename_relation": "関係の名前を変更",
- "rename_relation_from": "関係の名前を",
+ "rename_relation": "リレーションの名前を変更",
+ "rename_relation_from": "リレーションの名前を",
"old_name": "古い名前",
"to": "から",
"new_name": "新しい名前",
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
},
"update_relation_target": {
- "update_relation": "関係の更新",
- "relation_name": "関係の名前",
+ "update_relation": "リレーションの更新",
+ "relation_name": "リレーションの名前",
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。",
- "on_all_matched_notes": "一致したすべてのノートに"
+ "on_all_matched_notes": "一致したすべてのノートに",
+ "target_note": "対象のノート",
+ "change_target_note": "既存のリレーション対象のノートを変更する",
+ "update_relation_target": "リレーション対象の更新",
+ "to": "を"
},
"revisions_button": {
"note_revisions": "ノートの変更履歴"
@@ -1427,21 +1576,24 @@
"zpetne_odkazy": {
"backlink": "{{count}} バックリンク",
"backlinks": "{{count}} バックリンク",
- "relation": "関係"
+ "relation": "リレーション"
},
"mobile_detail_menu": {
"delete_this_note": "このノートを削除",
- "error_unrecognized_command": "認識されないコマンド {{command}}"
+ "error_unrecognized_command": "認識されないコマンド {{command}}",
+ "insert_child_note": "子ノートを挿入",
+ "error_cannot_get_branch_id": "ノートパス 「{{notePath}} のbranchIdを取得できません"
},
"inherited_attribute_list": {
"title": "継承属性",
- "no_inherited_attributes": "継承属性ではありません。"
+ "no_inherited_attributes": "継承属性はありません。"
},
"note_map": {
"open_full": "拡大表示",
"collapse": "通常サイズに折りたたむ",
"title": "ノートマップ",
- "link-distance": "リンク距離"
+ "link-distance": "リンク距離",
+ "fix-nodes": "ノードを修正"
},
"owned_attribute_list": {
"owned_attributes": "所有属性"
@@ -1453,7 +1605,9 @@
"unknown_attribute_type": "不明な属性タイプ '{{type}}'",
"add_new_attribute": "新しい属性を追加",
"remove_this_attribute": "この属性を削除",
- "remove_color": "このカラーラベルを削除"
+ "remove_color": "このカラーラベルを削除",
+ "promoted_attributes": "プロモート属性",
+ "url_placeholder": "http://ウェブサイト..."
},
"relation_map": {
"open_in_new_tab": "新しいタブで開く",
@@ -1461,14 +1615,17 @@
"edit_title": "タイトルを編集",
"rename_note": "ノート名を変更",
"enter_new_title": "新しいノート名を入力:",
- "remove_relation": "関係を削除",
- "confirm_remove_relation": "本当にこの関係を削除しますか?",
- "specify_new_relation_name": "新しい関係の名前(使用可能な文字: 英数字、コロン、アンダースコア)を指定:",
+ "remove_relation": "リレーションを削除",
+ "confirm_remove_relation": "本当にこのリレーションを削除しますか?",
+ "specify_new_relation_name": "新しいリレーションの名前(使用可能な文字: 英数字、コロン、アンダースコア)を指定:",
"note_not_found": "ノート {{noteId}} が見つかりません!",
"enter_title_of_new_note": "新しいノートのタイトルを入力",
"default_new_note_title": "新しいノート",
"cannot_match_transform": "変換を一致させることができません: {{transform}}",
- "click_on_canvas_to_place_new_note": "キャンバスをクリックして新しいノートを配置"
+ "click_on_canvas_to_place_new_note": "キャンバスをクリックして新しいノートを配置",
+ "connection_exists": "これらのノート間の接続 '{{name}}' は既に存在します。",
+ "start_dragging_relations": "ここからリレーションをドラッグして、別のノートにドロップします。",
+ "note_already_in_diagram": "ノート「{{title}}」はすでに図に含まれています。"
},
"database_anonymization": {
"title": "データベースの匿名化",
@@ -1534,7 +1691,7 @@
"native-title-bar": "ネイティブタイトルバー",
"native-title-bar-description": "WindowsとmacOSでは、ネイティブタイトルバーをオフにしておくと、アプリケーションがよりコンパクトに見えます。Linuxでは、ネイティブタイトルバーを表示したままの方が、他のシステムとの統一性が高まります。",
"background-effects": "背景効果を有効化(Windows 11のみ)",
- "background-effects-description": "Mica効果は、アプリのウィンドウにぼかされたスタイリッシュな背景を追加し、奥行きとモダンな外観を作り出します。",
+ "background-effects-description": "Mica効果は、アプリのウィンドウにぼかされたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
"restart-app-button": "アプリケーションを再起動して変更を反映",
"zoom-factor": "ズーム倍率"
},
@@ -1577,10 +1734,23 @@
"attribute_editor": {
"help_text_body1": "ラベルを追加するには、例 #rock と入力します。値も追加したい場合は、例 #year = 2020 と入力します",
"help_text_body2": "リレーションについては、~author = @ と入力すると、オートコンプリートが表示され、目的のノートを検索できるようになります。",
- "placeholder": "ここにラベルとリレーションを入力"
+ "placeholder": "ここにラベルとリレーションを入力",
+ "help_text_body3": "代わりに、右側の + ボタンを使用してラベルとリレーションを追加することもできます。",
+ "save_attributes": "属性を保存 ",
+ "add_a_new_attribute": "新しい属性を追加",
+ "add_new_label": "新しいラベルを追加 ",
+ "add_new_relation": "新しいリレーションを追加 ",
+ "add_new_label_definition": "新しいラベル定義を追加",
+ "add_new_relation_definition": "新しいリレーション定義を追加"
},
"move_note": {
- "on_all_matched_notes": "一致したすべてのノートに"
+ "on_all_matched_notes": "一致したすべてのノートに",
+ "move_note": "ノートを移動",
+ "target_parent_note": "対象の親ノート",
+ "move_note_new_parent": "ノートに親が 1 つしかない場合は、ノートを新しい親に移動します (つまり、古いブランチが削除され、新しい親に新しいブランチが作成されます)",
+ "clone_note_new_parent": "ノートに複数のクローン/ブランチがある場合、ノートを新しい親にクローンします(どのブランチを削除すべきか不明なため)",
+ "nothing_will_happen": "ノートをターゲットノートに移動できない場合は何も起こりません(つまり、ツリーサイクルが生じるため)",
+ "to": "次へ"
},
"onclick_button": {
"no_click_handler": "ボタン ウィジェット '{{componentId}}' にはクリック ハンドラーが定義されていません"
@@ -1596,20 +1766,35 @@
"placeholder": "ここにノートの内容を入力..."
},
"empty": {
- "open_note_instruction": "以下の入力欄にノートのタイトルを入力するか、ツリー内のノートを選択してノートを開きます。"
+ "open_note_instruction": "以下の入力欄にノートのタイトルを入力するか、ツリー内のノートを選択してノートを開きます。",
+ "search_placeholder": "名前でノートを検索",
+ "enter_workspace": "ワークスペース {{title}} に入る"
},
"file": {
- "too_big": "パフォーマンス上の理由により、プレビューではファイルの最初の {{maxNumChars}} 文字のみが表示されます。ファイル全体を表示するには、ファイルをダウンロードして外部で開いてください。"
+ "too_big": "パフォーマンス上の理由により、プレビューではファイルの最初の {{maxNumChars}} 文字のみが表示されます。ファイル全体を表示するには、ファイルをダウンロードして外部で開いてください。",
+ "file_preview_not_available": "このファイル形式ではファイルプレビューは利用できません。"
},
"protected_session": {
- "enter_password_instruction": "保護されたノートを表示するにはパスワードを入力する必要があります:"
+ "enter_password_instruction": "保護されたノートを表示するにはパスワードを入力する必要があります:",
+ "start_session_button": "保護されたセッションを開始 enter",
+ "started": "保護されたセッションが開始されました。",
+ "wrong_password": "パスワードが間違っています。",
+ "protecting-finished-successfully": "保護が正常に完了しました。",
+ "unprotecting-finished-successfully": "保護の解除が正常に完了しました。",
+ "protecting-in-progress": "保護中: {{count}}",
+ "unprotecting-in-progress-count": "保護解除中: {{count}}",
+ "protecting-title": "保護の状態",
+ "unprotecting-title": "保護解除の状態"
},
"render": {
- "note_detail_render_help_1": "このヘルプノートが表示されるのは、このノートの「HTML のレンダリング」タイプには、正常に機能するために必要なリレーションがないためです。"
+ "note_detail_render_help_1": "このヘルプノートが表示されるのは、このノートの「HTML のレンダリング」タイプには、正常に機能するために必要なリレーションがないためです。",
+ "note_detail_render_help_2": "レンダリングHTMLノートタイプは、スクリプティングに使用されます。簡単に言うと、HTMLコードノート(オプションでJavaScriptを含む)があり、このノートがそれをレンダリングします。これを動作させるには、レンダリングするHTMLノートを指す「renderNote」というリレーションを定義する必要があります。"
},
"consistency_checks": {
"find_and_fix_button": "一貫性の問題を見つけて修正する",
- "finding_and_fixing_message": "一貫性の問題を見つけて修正中…"
+ "finding_and_fixing_message": "一貫性の問題を見つけて修正中…",
+ "title": "一貫性をチェック",
+ "issues_fixed_message": "発見された可能性のある一貫性の問題はすべて修正されました。"
},
"vacuum_database": {
"title": "データベースのバキューム",
@@ -1620,12 +1805,16 @@
},
"ribbon": {
"promoted_attributes_message": "プロモート属性がノートに存在する場合、プロモート属性のリボンタブが自動的に開きます",
- "edited_notes_message": "編集したノートのリボンタブは、dayノートで自動的に開きます"
+ "edited_notes_message": "編集したノートのリボンタブは、dayノートで自動的に開きます",
+ "widgets": "リボンウィジェット"
},
"ui-performance": {
"enable-motion": "トランジションとアニメーションを有効にする",
"enable-shadows": "影を有効にする",
- "enable-backdrop-effects": "メニュー、ポップアップ、パネルの背景効果を有効にする"
+ "enable-backdrop-effects": "メニュー、ポップアップ、パネルの背景効果を有効にする",
+ "title": "パフォーマンス",
+ "enable-smooth-scroll": "スムーズスクロールを有効にする",
+ "app-restart-required": "(変更を有効にするにはアプリケーションの再起動が必要です)"
},
"code_mime_types": {
"title": "ドロップダウンで利用可能なMIMEタイプ"
@@ -1664,7 +1853,12 @@
"erase_excess_revision_snapshots_prompt": "余分な変更履歴が消去されました。"
},
"editability_select": {
- "note_is_read_only": "ノートは読み取り専用ですが、ボタンをクリックすると編集できます。"
+ "note_is_read_only": "ノートは読み取り専用ですが、ボタンをクリックすると編集できます。",
+ "auto": "自動",
+ "read_only": "読み取り専用",
+ "always_editable": "常に編集可能",
+ "note_is_editable": "ノートは長すぎなければ編集可能です。",
+ "note_is_always_editable": "ノートは長さに関係なく常に編集可能です。"
},
"find": {
"case_sensitive": "大文字と小文字を区別",
@@ -1681,7 +1875,9 @@
"could_not_find_typewidget": "タイプ {{type}} の typeWidget が見つかりませんでした"
},
"watched_file_update_status": {
- "ignore_this_change": "この変更を無視する"
+ "ignore_this_change": "この変更を無視する",
+ "file_last_modified": "ファイル は に最後に変更されました。",
+ "upload_modified_file": "変更したファイルをアップロード"
},
"image": {
"copied-to-clipboard": "画像への参照がクリップボードにコピーされました。これは任意のテキストノートに貼り付けることができます。",
@@ -1689,7 +1885,8 @@
},
"entrypoints": {
"note-revision-created": "ノートの改訂版が作成されました。",
- "sql-error": "SQL クエリの実行中にエラーが発生しました: {{message}}"
+ "sql-error": "SQL クエリの実行中にエラーが発生しました: {{message}}",
+ "note-executed": "ノートが実行されました。"
},
"branches": {
"cannot-move-notes-here": "ここにノートを移動することはできません。",
@@ -1706,7 +1903,9 @@
"ws": {
"sync-check-failed": "同期チェックに失敗しました!",
"consistency-checks-failed": "整合性チェックに失敗しました! 詳細はログを参照してください。",
- "encountered-error": "エラー「{{message}}」が発生しました。コンソールを確認してください。"
+ "encountered-error": "エラー「{{message}}」が発生しました。コンソールを確認してください。",
+ "lost-websocket-connection-title": "サーバーへの接続が失われました",
+ "lost-websocket-connection-message": "リバースプロキシ(例: nginx や Apache など)の設定を確認し、WebSocket 接続が適切に許可され、ブロックされていないことを確認してください。"
},
"hoisted_note": {
"confirm_unhoisting": "要求されたノート「{{requestedNote}}」は、ホイストされたノート「{{hoistedNote}}」サブツリーの外部にあるため、ノートにアクセスするにはホイストを解除する必要があります。ホイスト解除を続行しますか?"
@@ -1752,7 +1951,10 @@
"display-week-numbers": "週番号を表示",
"map-style": "マップスタイル:",
"max-nesting-depth": "最大階層の深さ:",
- "show-scale": "スケールを表示"
+ "show-scale": "スケールを表示",
+ "raster": "Raster",
+ "vector_light": "Vector(ライト)",
+ "vector_dark": "Vector (ダーク)"
},
"call_to_action": {
"next_theme_title": "新しいTriliumテーマをお試しください",
@@ -1779,5 +1981,89 @@
},
"collections": {
"rendering_error": "エラーのためコンテンツを表示できません。"
+ },
+ "abstract_bulk_action": {
+ "remove_this_search_action": "この検索アクションを削除"
+ },
+ "update_available": {
+ "update_available": "アップデートが利用可能"
+ },
+ "copy_image_reference_button": {
+ "button_title": "画像参照をクリップボードにコピーし、テキストノートに貼り付けることができます。"
+ },
+ "attachment_detail": {
+ "open_help_page": "添付ファイルのヘルプページを開く",
+ "owning_note": "所有ノート: ",
+ "you_can_also_open": "、開くこともできます ",
+ "list_of_all_attachments": "すべての添付ファイルのリスト",
+ "attachment_deleted": "この添付ファイルは削除されました。"
+ },
+ "attachment_list": {
+ "open_help_page": "添付ファイルのヘルプページを開く",
+ "owning_note": "所有ノート: ",
+ "upload_attachments": "添付ファイルをアップロード",
+ "no_attachments": "このノートには添付ファイルはありません。"
+ },
+ "book": {
+ "no_children_help": "このコレクションには子ノートがないため、表示するものがありません。詳細はwikiをご覧ください。",
+ "drag_locked_title": "編集をロック中",
+ "drag_locked_message": "コレクションは編集がロックされているため、ドラッグは許可されていません。"
+ },
+ "native_title_bar": {
+ "title": "ネイティブタイトルバー(アプリの再起動が必要です)",
+ "enabled": "有効",
+ "disabled": "無効"
+ },
+ "options_widget": {
+ "options_change_saved": "オプションの変更が保存されました。",
+ "options_status": "オプションステータス"
+ },
+ "multi_factor_authentication": {
+ "title": "多要素認証",
+ "description": "多要素認証(MFA)は、アカウントのセキュリティをさらに強化します。ログイン時にパスワードを入力するだけでなく、MFAでは本人確認のために1つ以上の追加情報の入力を求められます。これにより、たとえ誰かがパスワードを入手したとしても、2つ目の情報がなければアカウントにアクセスできません。まるでドアに鍵を追加したようなもので、他人が侵入するのがはるかに困難になります。
MFAを有効にするには、以下の手順に従ってください。正しく設定しないと、ログインはパスワードのみで行われます。",
+ "mfa_enabled": "多要素認証を有効にする",
+ "mfa_method": "MFAメソッド",
+ "electron_disabled": "現在、デスクトップ ビルドでは多要素認証はサポートされていません。",
+ "totp_title": "時間ベースのワンタイムパスワード (TOTP)",
+ "totp_description": "TOTP(時間ベースのワンタイムパスワード)は、30秒ごとに変化する一意の一時的なコードを生成するセキュリティ機能です。このコードとパスワードを使用してアカウントにログインすることで、他人がアクセスするのを大幅に困難にします。",
+ "totp_secret_title": "TOTPシークレットを生成する",
+ "totp_secret_generate": "TOTPシークレットを生成する",
+ "totp_secret_regenerate": "TOTPシークレットを再生成する",
+ "no_totp_secret_warning": "TOTP を有効にするには、まず TOTP シークレットを生成する必要があります。",
+ "totp_secret_description_warning": "新しい TOTP シークレットを生成したら、新しい TOTP シークレットを使用して再度ログインする必要があります。",
+ "totp_secret_generated": "TOTPシークレットが生成されました",
+ "totp_secret_warning": "生成されたシークレットは安全な場所に保存してください。再度表示されることはありません。",
+ "totp_secret_regenerate_confirm": "OTP シークレットを再生成してもよろしいですか?これにより、以前の TOTP シークレットと既存のすべての回復コードが無効になります。",
+ "recovery_keys_title": "シングルサインオン回復キー",
+ "recovery_keys_description": "シングル サインオン回復キーは、認証コードにアクセスできない場合でもログインするために使用されます。",
+ "recovery_keys_description_warning": "回復キーはページを離れた後は再度表示されませんので、安全な場所に保管してください。
回復キーは一度使用すると再度使用できなくなります。",
+ "recovery_keys_error": "回復コードの生成中にエラーが発生しました",
+ "recovery_keys_no_key_set": "回復コードが設定されていません",
+ "recovery_keys_generate": "回復コードを生成する",
+ "recovery_keys_regenerate": "回復コードの再生成",
+ "recovery_keys_used": "使用日: {{date}}",
+ "recovery_keys_unused": "回復コード {{index}} は未使用です",
+ "oauth_title": "OAuth/OpenID",
+ "oauth_description": "OpenIDは、Googleなどの他のサービスのアカウントを使用してウェブサイトにログインし、本人確認を行うための標準化された方法です。デフォルトの発行者はGoogleですが、他のOpenIDプロバイダに変更できます。詳しくはこちらをご覧ください。Google経由でOpenIDサービスを設定するには、こちらの手順に従ってください。",
+ "oauth_description_warning": "OAuth/OpenIDを有効にするには、config.iniファイルにOAuth/OpenIDのベースURL、クライアントID、クライアントシークレットを設定し、アプリケーションを再起動する必要があります。環境変数から設定する場合は、TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID and TRILIUM_OAUTH_CLIENT_SECRET を設定してください。",
+ "oauth_missing_vars": "設定がありません: {{-variables}}",
+ "oauth_user_account": "ユーザーアカウント: ",
+ "oauth_user_email": "ユーザーEメール: ",
+ "oauth_user_not_logged_in": "ログインしていません!"
+ },
+ "note-map": {
+ "button-link-map": "リンクマップ",
+ "button-tree-map": "ツリーマップ"
+ },
+ "spacer": {
+ "configure_launchbar": "ランチャーバーを設定"
+ },
+ "attachment_detail_2": {
+ "will_be_deleted_in": "この添付ファイルは {{time}} 後に自動的に削除されます",
+ "will_be_deleted_soon": "この添付ファイルはすぐに自動的に削除されます",
+ "deletion_reason": "、添付ファイルがノートのコンテンツにリンクされていないためです。削除されないようにするには、添付ファイルのリンクをコンテンツに再度追加するか、添付ファイルをノートに変換してください。",
+ "role_and_size": "ロール: {{role}},サイズ: {{size}}",
+ "link_copied": "添付ファイルのリンクをクリップボードにコピーしました。",
+ "unrecognized_role": "添付ファイルのロール「{{role}}」は認識されません。"
}
}
diff --git a/apps/client/src/translations/pt_br/translation.json b/apps/client/src/translations/pt_br/translation.json
index 915cbf64b3..5bdd1ae543 100644
--- a/apps/client/src/translations/pt_br/translation.json
+++ b/apps/client/src/translations/pt_br/translation.json
@@ -840,7 +840,7 @@
"cannot_find_day_note": "Nota do dia não encontrada",
"cannot_find_week_note": "Nota semanal não encontrada",
"january": "Janeiro",
- "febuary": "Fevereiro",
+ "february": "Fevereiro",
"march": "Março",
"april": "Abril",
"may": "Maio",
diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json
index 794babe090..073b1c0571 100644
--- a/apps/client/src/translations/ro/translation.json
+++ b/apps/client/src/translations/ro/translation.json
@@ -322,7 +322,7 @@
"august": "August",
"cannot_find_day_note": "Nu se poate găsi notița acelei zile",
"december": "Decembrie",
- "febuary": "Februarie",
+ "february": "Februarie",
"fri": "Vin",
"january": "Ianuarie",
"july": "Iulie",
diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json
index c59116edee..bc10e287ea 100644
--- a/apps/client/src/translations/ru/translation.json
+++ b/apps/client/src/translations/ru/translation.json
@@ -282,13 +282,13 @@
"editBranchPrefix": "изменить префикс клона активной заметки",
"multiSelectNote": "множественный выбор заметки выше/ниже",
"selectNote": "выбрать заметку",
- "copyNotes": "скопировать активную заметку (или выделение) в буфер обмер (используется для клонирования)",
+ "copyNotes": "скопировать активную заметку (или выделение) в буфер обмена (используется для клонирования)",
"createEditLink": "создать/редактировать внешнюю ссылку",
"headings": "##, ###, #### и т. д., за которыми следует пробел для заголовков",
"bulletList": "* или - с последующим пробелом для маркированного списка",
"numberedList": "1. или 1) с последующим пробелом для нумерованного списка",
"blockQuote": "начните строку с >, а затем пробела для блока цитаты",
- "quickSearch": "сфокусироваться на полее ввода быстрого поиска",
+ "quickSearch": "сфокусироваться на поле ввода быстрого поиска",
"editNoteTitle": "в области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. Ctrl+. переключит обратно с редактора на область дерева.",
"title": "Справка"
},
@@ -370,7 +370,7 @@
"confirm_delete": "Вы хотите удалить эту версию?",
"revisions_deleted": "Версии заметки были удалены.",
"revision_restored": "Версия заметки была восстановлена.",
- "revision_deleted": "Версия заметки были удалены.",
+ "revision_deleted": "Версия заметки была удалена.",
"download_button": "Скачать",
"file_size": "Размер файла:",
"preview": "Предпросмотр:",
@@ -463,7 +463,7 @@
"run_at_hour": "В какой час это должно выполняться? Следует использовать вместе с #run=hourly. Можно задать несколько раз для большего количества запусков в течение дня.",
"disable_inclusion": "скрипты с этой меткой не будут включены в выполнение родительского скрипта.",
"sorted": "сохраняет алфавитную сортировку дочерних заметок",
- "sort_direction": "ASC (по возрастани, по умолчанию) или DESC (по убыванию)",
+ "sort_direction": "ASC (по возрастанию, по умолчанию) или DESC (по убыванию)",
"sort_folders_first": "Папки (заметки, включая дочерние) должны быть отсортированы вверх",
"top": "закрепить заданную заметку наверху в ее родителе (применяется только к отсортированным родительским заметкам)",
"hide_promoted_attributes": "Скрыть продвигаемых атрибуты в этой заметке",
@@ -479,7 +479,7 @@
"workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки при перемещении их к какому-либо предку этой заметки рабочей области",
"workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства",
"hide_highlight_widget": "Скрыть виджет «Выделенное»",
- "is_owned_by_note": "принадлежит записке",
+ "is_owned_by_note": "принадлежит заметке",
"and_more": "... и ещё {{count}}.",
"app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium.",
"title_template": "Заголовок по умолчанию для заметок, создаваемых как дочерние элементы данной заметки. Значение вычисляется как строка JavaScript\n и, таким образом, может быть дополнено динамическим контентом с помощью внедренных переменных now и parentNote. Примеры:\n \n \n Литературные произведения ${parentNote.getLabelValue('authorName')} \n Лог для ${now.format('YYYY-MM-DD HH:mm:ss')} \n
\n \n Подробности см. в вики, документации API для parentNote и now.",
@@ -528,8 +528,8 @@
"run_on_note_deletion": "выполняется при удалении заметки",
"run_on_branch_creation": "выполняется при создании ветви. Ветвь — это связующее звено между родительской и дочерней заметками и создаётся, например, при клонировании или перемещении заметки.",
"run_on_branch_change": "выполняется при обновлении ветки.",
- "run_on_attribute_creation": "выполняется, когда создается новый атрибут для заметка, определяющей это отношение",
- "run_on_attribute_change": " выполняется при изменении атрибута заметки, определяющей это отношение. Также срабатывает при удалении атрибута",
+ "run_on_attribute_creation": "выполняется, когда создается новый атрибут для заметки, определяющей это отношение",
+ "run_on_attribute_change": "выполняется при изменении атрибута заметки, определяющей это отношение. Также срабатывает при удалении атрибута",
"relation_template": "атрибуты заметки будут унаследованы даже без родительско-дочерних отношений. Содержимое заметки и её поддерево будут добавлены к экземпляру заметки, если оно пустое. Подробности см. в документации.",
"inherit": "атрибуты заметки будут унаследованы даже без родительско-дочерних отношений. См. описание шаблонных отношений для получения аналогичной информации. См. раздел «Наследование атрибутов» в документации.",
"render_note": "заметки типа «Рендер HTML» будут отображаться с использованием кодовой заметки (HTML или скрипта), и необходимо указать с помощью этой связи, какую заметку следует отобразить",
@@ -712,7 +712,7 @@
},
"code_block": {
"copy_title": "Копировать в буфер обмена",
- "theme_group_dark": "Темныце темы",
+ "theme_group_dark": "Темные темы",
"theme_group_light": "Светлые темы",
"theme_none": "Нет подсветки синтаксиса",
"word_wrapping": "Перенос слов"
@@ -783,7 +783,7 @@
"beta-feature": "Бета",
"widget": "Виджет",
"image": "Изображение",
- "file": "Файд",
+ "file": "Файл",
"canvas": "Холст",
"mermaid-diagram": "Диаграмма Mermaid",
"book": "Коллекция",
@@ -811,7 +811,7 @@
"export": "Экспорт",
"open-in-a-new-tab": "Открыть в новой вкладке",
"open-in-a-new-split": "Открыть в новой панели",
- "unhoist-note": "Отрепить заметку",
+ "unhoist-note": "Открепить заметку",
"hoist-note": "Закрепить заметку",
"protect-subtree": "Защитить поддерево",
"unprotect-subtree": "Снять защиту с поддерева",
@@ -864,7 +864,7 @@
"to": "в",
"move_note": "Переместить заметку",
"target_parent_note": "целевая версии заметки",
- "on_all_matched_notes": "На всех совпадающих нотах",
+ "on_all_matched_notes": "На всех совпадающих заметках",
"move_note_new_parent": "переместить заметку в новый родительский элемент, если у заметки есть только один родительский элемент (т. е. старая ветвь удаляется и создается новая ветвь в новом родительском элементе)",
"clone_note_new_parent": "клонировать заметку в новый родительский элемент, если у заметки есть несколько клонов/ветвей (неясно, какую ветвь следует удалить)",
"nothing_will_happen": "ничего не произойдет, если эту заметку невозможно переместить в целевую заметку (т.е. это создаст цикл дерева)"
@@ -902,7 +902,7 @@
"delete_attachment": "Удалить вложение",
"upload_new_revision": "Загрузить новую версию",
"open_custom": "Открыть как...",
- "open_custom_client_only": "Иной способ открытие вложений возможен только из десктопного приложения.",
+ "open_custom_client_only": "Открытие вложений другим способом возможно только в десктопном приложении.",
"open_externally_detail_page": "Открытие вложения извне доступно только из детальной страницы. Для этого сначала нажмите на сведения о вложении и повторите действие.",
"open_custom_title": "Файл будет открыт во внешнем приложении и отслеживаться на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.",
"open_externally_title": "Файл будет открыт во внешнем приложении и отслеживаться на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.",
@@ -925,7 +925,7 @@
"sat": "Сбт",
"sun": "Вс",
"january": "Январь",
- "febuary": "Февраль",
+ "february": "Февраль",
"march": "Март",
"april": "Апрель",
"may": "Май",
@@ -1020,8 +1020,8 @@
"file_properties": {
"download": "Скачать",
"open": "Открыть",
- "title": "Файд",
- "upload_success": "Загрузка новой версии файла не удалась.",
+ "title": "Файл",
+ "upload_success": "Новая версия файла успешно загружена.",
"upload_new_revision": "Загрузить новую версию",
"file_size": "Размер файла",
"file_type": "Тип файла",
@@ -1153,7 +1153,7 @@
"label_abc": "возвращает заметки с меткой abc",
"label_year": "ищет заметки с меткой year, имеющей значение 2019",
"label_rock_pop": "соответствует заметкам с метками как rock, так и pop",
- "label_rock_or_pop": "должна присутствовать только одна из vtnjr",
+ "label_rock_or_pop": "должна присутствовать только одна из меток",
"label_year_comparison": "числовое сравнение (также >, >=, <).",
"label_date_created": "заметки, созданные за последний месяц",
"error": "Ошибка поиска: {{error}}",
@@ -1413,7 +1413,7 @@
"first-week-contains-first-day": "Первая неделя содержит первый день года",
"first-week-contains-first-thursday": "Первая неделя содержит первый четверг года",
"first-week-has-minimum-days": "Первая неделя имеет минимальное количество дней",
- "min-days-in-first-week": "Минимальное количество дней в первую неделю",
+ "min-days-in-first-week": "Минимальное количество дней в первой неделе",
"first-week-info": "Первая неделя содержит первый четверг года в соответствии со стандартом ISO 8601.",
"first-week-warning": "Изменение параметров первой недели может привести к дублированию существующих недельных заметок, и существующие недельные заметки не будут обновлены соответствующим образом.",
"formatting-locale": "Формат даты и числа"
@@ -1719,7 +1719,7 @@
"database_vacuumed": "База данных была сжата"
},
"vim_key_bindings": {
- "use_vim_keybindings_in_code_notes": "Раскладка клавиш VIM",
+ "use_vim_keybindings_in_code_notes": "Сочетания клавиш Vim",
"enable_vim_keybindings": "Включить сочетания клавиш Vim в заметках кода (без режима ex)"
},
"network_connections": {
@@ -1802,7 +1802,7 @@
"button_title": "Экспортировать диаграмму как SVG"
},
"copy_image_reference_button": {
- "button_title": "Скопировать ссылку на изображение в буфер обмена, можент быть вставлена в текстовую заметку."
+ "button_title": "Скопировать ссылку на изображение в буфер обмена, может быть вставлена в текстовую заметку."
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Этот лаунчер не определяет целевую заметку."
@@ -1973,7 +1973,9 @@
"ws": {
"sync-check-failed": "Проверка синхронизации не удалась!",
"encountered-error": "Обнаружена ошибка \"{{message}}\", проверьте консоль.",
- "consistency-checks-failed": "Проверка целостности не пройдена! Подробности смотрите в логах."
+ "consistency-checks-failed": "Проверка целостности не пройдена! Подробности смотрите в логах.",
+ "lost-websocket-connection-title": "Потеряно соединение с сервером",
+ "lost-websocket-connection-message": "Проверьте конфигурацию обратного прокси (например, nginx или Apache), чтобы убедиться, что соединения WebSocket должным образом разрешены и не заблокированы."
},
"attachment_detail_2": {
"role_and_size": "Роль: {{role}}, Размер: {{size}}",
@@ -2045,7 +2047,7 @@
"could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'"
},
"book": {
- "no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. на wiki.",
+ "no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. в wiki.",
"drag_locked_title": "Защищено от изменения",
"drag_locked_message": "Перетаскивание не допускается, так как коллекция защищена от редактирования."
},
diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json
index bf1d83f976..8bf6461b13 100644
--- a/apps/client/src/translations/tw/translation.json
+++ b/apps/client/src/translations/tw/translation.json
@@ -581,7 +581,7 @@
"sun": "日",
"cannot_find_day_note": "無法找到日記",
"january": "一月",
- "febuary": "二月",
+ "february": "二月",
"march": "三月",
"april": "四月",
"may": "五月",
@@ -1582,7 +1582,9 @@
"ws": {
"sync-check-failed": "同步檢查失敗!",
"consistency-checks-failed": "一致性檢查失敗!請查看日誌以了解詳細資訊。",
- "encountered-error": "遇到錯誤 \"{{message}}\",請查看控制台。"
+ "encountered-error": "遇到錯誤 \"{{message}}\",請查看控制台。",
+ "lost-websocket-connection-title": "與伺服器的連線中斷",
+ "lost-websocket-connection-message": "檢查您的反向代理(如 nginx 或 Apache)設定以確保 Websocket 連線沒有被阻擋。"
},
"hoisted_note": {
"confirm_unhoisting": "請求的筆記 '{{requestedNote}}' 位於聚焦的筆記 '{{hoistedNote}}' 的子階層之外,您必須取消聚焦才能訪問該筆記。是否繼續取消聚焦?"
@@ -1952,7 +1954,11 @@
"editorfeatures": {
"title": "功能",
"emoji_completion_enabled": "啟用 Emoji 自動完成",
- "note_completion_enabled": "啟用筆記自動完成"
+ "note_completion_enabled": "啟用筆記自動完成",
+ "emoji_completion_description": "如果啟用,emoji 可以輕易地經由輸入 `:` 加上 emoji 名稱來插入。",
+ "note_completion_description": "如果啟用,導向筆記的連結可以經由輸入 `@` 加上筆記標題來建立。",
+ "slash_commands_enabled": "啟用斜線命令",
+ "slash_commands_description": "如果啟用,可以經由輸入 `/` 來觸發命令,如插入換行符或標題。"
},
"table_view": {
"new-row": "新增列",
diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json
index 090c999594..82c9e5ae3d 100644
--- a/apps/client/src/translations/uk/translation.json
+++ b/apps/client/src/translations/uk/translation.json
@@ -722,7 +722,7 @@
"cannot_find_day_note": "Не вдається знайти денну нотатку",
"cannot_find_week_note": "Не вдається знайти тижневу нотатку",
"january": "Січень",
- "febuary": "Лютий",
+ "february": "Лютий",
"march": "Березень",
"april": "Квітень",
"may": "Травень",
diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts
index 51e76dd614..56e9ff3b82 100644
--- a/apps/client/src/widgets/buttons/calendar.ts
+++ b/apps/client/src/widgets/buttons/calendar.ts
@@ -18,7 +18,7 @@ dayjs.extend(isSameOrAfter);
const MONTHS = [
t("calendar.january"),
- t("calendar.febuary"),
+ t("calendar.february"),
t("calendar.march"),
t("calendar.april"),
t("calendar.may"),
diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx
index 7eac3f57af..e721d3eb9b 100644
--- a/apps/client/src/widgets/collections/NoteList.tsx
+++ b/apps/client/src/widgets/collections/NoteList.tsx
@@ -189,6 +189,7 @@ function useViewModeConfig(note: FNote | null | undefined, vie
useEffect(() => {
if (!note || !viewType) return;
+ setViewConfig(undefined);
const viewStorage = new ViewModeStorage(note, viewType);
viewStorage.restore().then(config => {
const storeFn = (config: T) => {
diff --git a/apps/client/src/widgets/collections/table/columns.tsx b/apps/client/src/widgets/collections/table/columns.tsx
index 6351ba5987..74db6ddb71 100644
--- a/apps/client/src/widgets/collections/table/columns.tsx
+++ b/apps/client/src/widgets/collections/table/columns.tsx
@@ -30,7 +30,8 @@ const labelTypeMappings: Record> = {
editor: "datetime"
},
number: {
- editor: "number"
+ editor: "number",
+ sorter: "number"
},
time: {
editor: "input"
diff --git a/apps/client/src/widgets/collections/table/index.tsx b/apps/client/src/widgets/collections/table/index.tsx
index 40f5310c6e..d99d912d6a 100644
--- a/apps/client/src/widgets/collections/table/index.tsx
+++ b/apps/client/src/widgets/collections/table/index.tsx
@@ -16,9 +16,10 @@ import useColTableEditing from "./col_editing";
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
import attributes from "../../../services/attributes";
import { RefObject } from "preact";
+import SpacedUpdate from "../../../services/spaced_update";
interface TableConfig {
- tableData?: {
+ tableData: {
columns?: ColumnDefinition[];
};
}
@@ -53,12 +54,12 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
return (
- {columnDefs && (
+ {persistenceProps && (
<>
}
@@ -94,23 +95,31 @@ function TableFooter({ note }: { note: FNote }) {
)
}
-function usePersistence(initialConfig: TableConfig | null | undefined, saveConfig: (newConfig: TableConfig) => void) {
- const config = useRef(initialConfig);
- const spacedUpdate = useSpacedUpdate(() => {
- if (config.current) {
- saveConfig(config.current);
- }
- }, 5_000);
- const persistenceWriterFunc = useCallback((_id, type: string, data: object) => {
- if (!config.current) config.current = {};
- if (!config.current.tableData) config.current.tableData = {};
- (config.current.tableData as Record)[type] = data;
- spacedUpdate.scheduleUpdate();
- }, []);
- const persistenceReaderFunc = useCallback((_id, type: string) => {
- return config.current?.tableData?.[type];
- }, []);
- return { persistenceReaderFunc, persistenceWriterFunc };
+function usePersistence(viewConfig: TableConfig | null | undefined, saveConfig: (newConfig: TableConfig) => void) {
+ const [ persistenceProps, setPersistenceProps ] = useState>();
+
+ useEffect(() => {
+ const viewConfigLocal = viewConfig ?? { tableData: {} };
+ const spacedUpdate = new SpacedUpdate(() => {
+ saveConfig(viewConfigLocal);
+ }, 5_000);
+
+ setPersistenceProps({
+ persistenceReaderFunc(_, type) {
+ return viewConfigLocal.tableData?.[type];
+ },
+ persistenceWriterFunc(_, type, data) {
+ (viewConfigLocal.tableData as Record)[type] = data;
+ spacedUpdate.scheduleUpdate();
+ },
+ });
+
+ return () => {
+ spacedUpdate.updateNowIfNecessary();
+ };
+ }, [ viewConfig, saveConfig ])
+
+ return persistenceProps;
}
function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undefined, newAttributePosition: RefObject, resetNewAttributePosition: () => void) {
@@ -125,6 +134,7 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
function refresh() {
const info = getAttributeDefinitionInformation(note);
+
buildRowDefinitions(note, info, includeArchived, maxDepth).then(({ definitions: rowData, hasSubtree: hasChildren, rowNumber }) => {
const columnDefs = buildColumnDefinitions({
info,
diff --git a/apps/client/src/widgets/dialogs/add_link.tsx b/apps/client/src/widgets/dialogs/add_link.tsx
index 287a19379c..82ac9fced5 100644
--- a/apps/client/src/widgets/dialogs/add_link.tsx
+++ b/apps/client/src/widgets/dialogs/add_link.tsx
@@ -25,6 +25,7 @@ export default function AddLinkDialog() {
const [ linkType, setLinkType ] = useState();
const [ suggestion, setSuggestion ] = useState(null);
const [ shown, setShown ] = useState(false);
+ const hasSubmittedRef = useRef(false);
useTriliumEvent("showAddLinkDialog", opts => {
setOpts(opts);
@@ -85,14 +86,11 @@ export default function AddLinkDialog() {
}
function onSubmit() {
- if (suggestion?.notePath) {
- // Handle note link
+ hasSubmittedRef.current = true;
+
+ if (suggestion) {
+ // Insertion logic in onHidden because it needs focus.
setShown(false);
- opts?.addLink(suggestion.notePath, linkType === "reference-link" ? null : linkTitle);
- } else if (suggestion?.externalLink) {
- // Handle external link
- setShown(false);
- opts?.addLink(suggestion.externalLink, linkTitle, true);
} else {
logError("No link to add.");
}
@@ -111,6 +109,19 @@ export default function AddLinkDialog() {
onSubmit={onSubmit}
onShown={onShown}
onHidden={() => {
+ // Insert the link.
+ if (hasSubmittedRef.current && suggestion && opts) {
+ hasSubmittedRef.current = false;
+
+ if (suggestion.notePath) {
+ // Handle note link
+ opts.addLink(suggestion.notePath, linkType === "reference-link" ? null : linkTitle);
+ } else if (suggestion.externalLink) {
+ // Handle external link
+ opts.addLink(suggestion.externalLink, linkTitle, true);
+ }
+ }
+
setSuggestion(null);
setShown(false);
}}
diff --git a/apps/client/src/widgets/react/HelpRemoveButtons.tsx b/apps/client/src/widgets/react/HelpRemoveButtons.tsx
index 7eda18b971..79c0a138f7 100644
--- a/apps/client/src/widgets/react/HelpRemoveButtons.tsx
+++ b/apps/client/src/widgets/react/HelpRemoveButtons.tsx
@@ -14,17 +14,20 @@ export default function HelpRemoveButtons({ help, removeText, onRemove }: HelpRe
{help && <>
{help}
{" "}
- >}
+ >}
{
+ e.preventDefault();
+ onRemove?.();
+ }}
/>
);
-}
\ No newline at end of file
+}
diff --git a/apps/client/src/widgets/react/Modal.tsx b/apps/client/src/widgets/react/Modal.tsx
index f13257a5d6..37a0d96c03 100644
--- a/apps/client/src/widgets/react/Modal.tsx
+++ b/apps/client/src/widgets/react/Modal.tsx
@@ -36,9 +36,9 @@ interface ModalProps {
onSubmit?: () => void;
/** Called when the modal is shown. */
onShown?: () => void;
- /**
+ /**
* Called when the modal is hidden, either via close button, backdrop click or submit.
- *
+ *
* Here it's generally a good idea to set `show` to false to reflect the actual state of the modal.
*/
onHidden: () => void;
@@ -71,23 +71,25 @@ export default function Modal({ children, className, size, title, header, footer
useEffect(() => {
const modalElement = modalRef.current;
- if (!modalElement) {
- return;
- }
+ if (!modalElement) return;
+
if (onShown) {
modalElement.addEventListener("shown.bs.modal", onShown);
}
- modalElement.addEventListener("hidden.bs.modal", () => {
+
+ function onModalHidden() {
onHidden();
if (elementToFocus.current && "focus" in elementToFocus.current) {
(elementToFocus.current as HTMLElement).focus();
}
- });
+ }
+
+ modalElement.addEventListener("hidden.bs.modal", onModalHidden);
return () => {
if (onShown) {
modalElement.removeEventListener("shown.bs.modal", onShown);
}
- modalElement.removeEventListener("hidden.bs.modal", onHidden);
+ modalElement.removeEventListener("hidden.bs.modal", onModalHidden);
};
}, [ onShown, onHidden ]);
@@ -180,4 +182,4 @@ const ModalInner = memo(({ children, footer, footerAlignment, bodyStyle, footerS
)}
>
);
-});
\ No newline at end of file
+});
diff --git a/apps/client/tsconfig.app.json b/apps/client/tsconfig.app.json
index 16b93e1ced..4abc845e41 100644
--- a/apps/client/tsconfig.app.json
+++ b/apps/client/tsconfig.app.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "lib": [ "ESNext" ],
+ "lib": [ "ESNext", "DOM.Iterable" ],
"outDir": "dist",
"types": [
"node",
diff --git a/apps/desktop/electron-forge/forge.config.ts b/apps/desktop/electron-forge/forge.config.ts
index b433ee4d71..4b5dcde754 100644
--- a/apps/desktop/electron-forge/forge.config.ts
+++ b/apps/desktop/electron-forge/forge.config.ts
@@ -114,7 +114,7 @@ const config: ForgeConfig = {
config: {
name: EXECUTABLE_NAME,
productName: PRODUCT_NAME,
- iconUrl: "https://raw.githubusercontent.com/TriliumNext/Notes/develop/images/app-icons/icon.ico",
+ iconUrl: "https://raw.githubusercontent.com/TriliumNext/Trilium/refs/heads/main/apps/desktop/electron-forge/app-icon/icon.ico",
setupIcon: path.join(ELECTRON_FORGE_DIR, "setup-icon/setup.ico"),
loadingGif: path.join(ELECTRON_FORGE_DIR, "setup-icon/setup-banner.gif"),
windowsSign: windowsSignConfiguration
@@ -130,7 +130,7 @@ const config: ForgeConfig = {
name: "@electron-forge/maker-zip",
config: {
options: {
- iconUrl: "https://raw.githubusercontent.com/TriliumNext/Notes/develop/images/app-icons/icon.ico",
+ iconUrl: "https://raw.githubusercontent.com/TriliumNext/Trilium/refs/heads/main/apps/desktop/electron-forge/app-icon/icon.ico",
icon: path.join(APP_ICON_PATH, "icon.ico")
}
}
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 613b92bfb2..63ad808e25 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -8,7 +8,7 @@
"author": {
"name": "Trilium Notes Team",
"email": "contact@eliandoran.me",
- "url": "https://github.com/TriliumNext/Notes"
+ "url": "https://triliumnotes.org"
},
"scripts": {
"dev": "cross-env TRILIUM_PORT=37742 TRILIUM_DATA_DIR=data tsx ../../scripts/electron-start.mts src/main.ts",
@@ -34,7 +34,7 @@
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"copy-webpack-plugin": "13.0.1",
- "electron": "38.1.2",
+ "electron": "38.2.0",
"@electron-forge/cli": "7.9.0",
"@electron-forge/maker-deb": "7.9.0",
"@electron-forge/maker-dmg": "7.9.0",
diff --git a/apps/desktop/scripts/build.ts b/apps/desktop/scripts/build.ts
index 679dc71f03..945a4cfebf 100644
--- a/apps/desktop/scripts/build.ts
+++ b/apps/desktop/scripts/build.ts
@@ -15,7 +15,6 @@ async function main() {
// Copy node modules dependencies
build.copyNodeModules([ "better-sqlite3", "bindings", "file-uri-to-path", "@electron/remote" ]);
- build.copy("/node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js", "xhr-sync-worker.js");
build.copy("/node_modules/ckeditor5/dist/ckeditor5-content.css", "ckeditor5-content.css");
// Integrate the client.
diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json
index 5f1b2ca988..95e7236ba4 100644
--- a/apps/desktop/tsconfig.json
+++ b/apps/desktop/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
- "include": [],
+ "include": [],
"references": [
{
"path": "../server"
diff --git a/apps/edit-docs/package.json b/apps/edit-docs/package.json
index 309851713f..bb664f634b 100644
--- a/apps/edit-docs/package.json
+++ b/apps/edit-docs/package.json
@@ -12,7 +12,7 @@
"@triliumnext/desktop": "workspace:*",
"@types/fs-extra": "11.0.4",
"copy-webpack-plugin": "13.0.1",
- "electron": "38.1.2",
+ "electron": "38.2.0",
"fs-extra": "11.3.2"
},
"scripts": {
diff --git a/apps/server/package.json b/apps/server/package.json
index 84699fb27c..4b58cf52cc 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -25,10 +25,11 @@
"docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine"
},
"dependencies": {
- "better-sqlite3": "12.4.1"
+ "better-sqlite3": "12.4.1",
+ "node-html-parser": "7.0.1"
},
"devDependencies": {
- "@anthropic-ai/sdk": "0.63.1",
+ "@anthropic-ai/sdk": "0.64.0",
"@braintree/sanitize-url": "7.1.1",
"@electron/remote": "2.1.3",
"@preact/preset-vite": "2.10.2",
@@ -49,7 +50,6 @@
"@types/html": "1.0.4",
"@types/ini": "4.1.1",
"@types/js-yaml": "4.0.9",
- "@types/jsdom": "21.1.7",
"@types/mime-types": "3.0.1",
"@types/multer": "2.0.0",
"@types/safe-compare": "1.1.2",
@@ -81,7 +81,7 @@
"debounce": "2.2.0",
"debug": "4.4.3",
"ejs": "3.1.10",
- "electron": "38.1.2",
+ "electron": "38.2.0",
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
@@ -105,7 +105,6 @@
"is-svg": "6.1.0",
"jimp": "1.6.0",
"js-yaml": "4.1.0",
- "jsdom": "26.1.0",
"marked": "16.3.0",
"mime-types": "3.0.1",
"multer": "2.0.2",
diff --git a/apps/server/scripts/build.ts b/apps/server/scripts/build.ts
index 84862d060d..d2ed99ee2a 100644
--- a/apps/server/scripts/build.ts
+++ b/apps/server/scripts/build.ts
@@ -11,7 +11,6 @@ async function main() {
// Copy node modules dependencies
build.copyNodeModules([ "better-sqlite3", "bindings", "file-uri-to-path" ]);
- build.copy("/node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js", "xhr-sync-worker.js");
build.copy("/node_modules/ckeditor5/dist/ckeditor5-content.css", "ckeditor5-content.css");
// Integrate the client.
diff --git a/apps/server/src/assets/translations/cs/server.json b/apps/server/src/assets/translations/cs/server.json
index bb74146c55..72710a16ed 100644
--- a/apps/server/src/assets/translations/cs/server.json
+++ b/apps/server/src/assets/translations/cs/server.json
@@ -10,6 +10,15 @@
"expand-subtree": "Rozbalit podstrom aktivní poznámky",
"collapse-tree": "Sbalit celý strom poznámek",
"collapse-subtree": "Sbalí podstrom aktivní poznámky",
- "sort-child-notes": "Seřadit podřízené poznámky"
+ "sort-child-notes": "Seřadit podřízené poznámky",
+ "creating-and-moving-notes": "Vytváření a přesouvání poznámek",
+ "create-note-after": "Vytvořit poznámku za aktivní poznámkou",
+ "create-note-into": "Vytvořit poznámku jako potomka aktivní poznámky",
+ "create-note-into-inbox": "Vytvořit poznámku v inboxu (pokud je definován) nebo denní poznámku",
+ "delete-note": "Smazat poznámku",
+ "move-note-up": "Posunout poznámku nahoru",
+ "move-note-down": "Posunout poznámku dolů",
+ "move-note-up-in-hierarchy": "Přesunout poznámku o úroveň výše",
+ "move-note-down-in-hierarchy": "Přesunout poznámku ve struktuře níže"
}
}
diff --git a/apps/server/src/assets/translations/hr/server.json b/apps/server/src/assets/translations/hr/server.json
new file mode 100644
index 0000000000..425f08c5f3
--- /dev/null
+++ b/apps/server/src/assets/translations/hr/server.json
@@ -0,0 +1,102 @@
+{
+ "keyboard_actions": {
+ "back-in-note-history": "Idi na prethodnu povijesnu bilješku",
+ "forward-in-note-history": "Idi na sljedeću povijesnu bilješku",
+ "open-jump-to-note-dialog": "Otvori prozor \"Skokni na bilješku\"",
+ "open-command-palette": "Otvori naredbenu ploču",
+ "scroll-to-active-note": "Pomakni stablo bilješke na aktivnu bilješku",
+ "quick-search": "Uključi traku brzog pretraživanja",
+ "search-in-subtree": "Traži bilješke u podstablu aktivne bilješke",
+ "expand-subtree": "Proširi podstablo trenutne bilješke",
+ "collapse-tree": "Zatvara cijelo stablo bilješke",
+ "collapse-subtree": "Zatvara podstablo trenutne bilješke",
+ "sort-child-notes": "Sortiraj podbilješke",
+ "creating-and-moving-notes": "Stvaranje i premještanje bilješki",
+ "create-note-after": "Stvori bilješku nakon aktivne bilješke",
+ "create-note-into": "Stvori bilješku kao podbilješku aktivne bilješke",
+ "delete-note": "Obriši bilješku",
+ "move-note-up": "Premjesti bilješku gore",
+ "move-note-down": "Premjesti bilješku dolje",
+ "move-note-up-in-hierarchy": "Premjesti bilješku više u hijerarhiju",
+ "move-note-down-in-hierarchy": "Premjesti bilješku niže u hijerarhiju",
+ "edit-branch-prefix": "Prikaži prozor \"Uredi prefiks grane\"",
+ "clone-notes-to": "Kloniraj označene bilješke",
+ "move-notes-to": "Premjesti označene bilješke",
+ "note-clipboard": "Međuspremnik bilješki",
+ "copy-notes-to-clipboard": "Kopiraj označene bilješke u međuspremnik",
+ "paste-notes-from-clipboard": "Zalijepi bilješke iz međuspremnika u aktivnu bilješku",
+ "cut-notes-to-clipboard": "Izreži označene bilješke u međuspremnik",
+ "select-all-notes-in-parent": "Označi sve bilješke u trenutnoj razini bilješke",
+ "add-note-above-to-the-selection": "Dodaj bilješku iznad označenog",
+ "add-note-below-to-selection": "Dodaj bilješku ispod označenog",
+ "duplicate-subtree": "Udvostruči podstablo",
+ "tabs-and-windows": "Kartice i Prozori",
+ "open-new-tab": "Otvori novu karticu",
+ "close-active-tab": "Zatvori aktivnu karticu",
+ "reopen-last-tab": "Ponovno otvori zadnju zatvorenu karticu",
+ "activate-next-tab": "Aktiviraj karticu nadesno",
+ "activate-previous-tab": "Aktiviraj karticu nalijevo",
+ "open-new-window": "Otvori novi prazni prozor",
+ "toggle-tray": "Prikaži/sakrij aplikaciju unutar trake sustava",
+ "first-tab": "Aktiviraj prvu karticu na popisu",
+ "second-tab": "Aktiviraj drugu karticu na popisu",
+ "third-tab": "Aktiviraj treću karticu na popisu",
+ "fourth-tab": "Aktiviraj četvrtu karticu na popisu",
+ "fifth-tab": "Aktiviraj petu karticu na popisu",
+ "sixth-tab": "Aktiviraj šestu karticu na popisu",
+ "seventh-tab": "Aktiviraj sedmu karticu na popisu",
+ "eight-tab": "Aktiviraj osmu karticu na popisu",
+ "ninth-tab": "Aktiviraj devetu karticu na popisu",
+ "last-tab": "Aktiviraj posljednju karticu na popisu",
+ "dialogs": "Prozori",
+ "show-note-source": "Prikaži prozor \"Izvor Bilješke\"",
+ "show-options": "Otvori stranicu \"Postavke\"",
+ "show-revisions": "Prikaži prozor \"Ispravci Bilješki\"",
+ "show-recent-changes": "Prikaži prozor \"Nedavne Promjene\"",
+ "show-sql-console": "Otvori stranicu \"SQL konzola\"",
+ "show-backend-log": "Otvori stranicu \"Dnevnik Pozadine\"",
+ "show-help": "Otvori ugrađeni Vodič za Korisnike",
+ "show-cheatsheet": "Prikaži modalni prozor s uobičajenim radnjama na tipkovnici",
+ "text-note-operations": "Radnje tekstualnih bilješki",
+ "add-link-to-text": "Otvori prozor za dodavanje poveznice u tekst",
+ "follow-link-under-cursor": "Slijedi poveznice unutar kojih je stavljen znak umetanja",
+ "insert-date-and-time-to-text": "Umetni trenutni datum i vrijeme u tekst",
+ "paste-markdown-into-text": "Lijepi Markdown iz međuspremnika u tekstualnu bilješku",
+ "cut-into-note": "Reže označeno u trenutnoj bilježnici i stvara podbilješku s označenim tekstom",
+ "add-include-note-to-text": "Otvara prozor za uključivanje bilješke",
+ "edit-readonly-note": "Uredi bilješku samo za čitanje",
+ "attributes-labels-and-relations": "Atributi (oznake i relacije)",
+ "add-new-label": "Stvori novu oznaku",
+ "create-new-relation": "Stvori novu relaciju",
+ "ribbon-tabs": "Vrpčane kartice",
+ "toggle-basic-properties": "Uključi/Isključi Osnova Svojstva",
+ "toggle-file-properties": "Uključi/Isključi Svojstva Datoteke",
+ "toggle-image-properties": "Uključi/Isključi Svojstva Slike",
+ "toggle-owned-attributes": "Uključi/Isključi Atribute u Vlasništvu",
+ "toggle-inherited-attributes": "Uključi/Isključi Naslijeđene Atribute",
+ "toggle-promoted-attributes": "Uključi/Isključi Promovirane Atribute",
+ "toggle-link-map": "Uključi/Isključi Kartu Poveznica",
+ "toggle-note-info": "Uključi/Isključi Informacije Bilješke",
+ "toggle-note-paths": "Uključi/Isključi Puteve Bilješke",
+ "toggle-similar-notes": "Uključi/Isključi Slične Bilješke",
+ "other": "Ostalo",
+ "toggle-right-pane": "Uključi/isključi prikaz desnog okna, koje uključuje Sadržaj i Istaknuto",
+ "print-active-note": "Ispiši aktivnu bilješku",
+ "open-note-externally": "Otvori bilješku kao datoteku u zadanoj aplikaciji",
+ "render-active-note": "Iscrtaj (ponovno iscrtaj) aktivnu bilješku",
+ "run-active-note": "Pokreni bilješku aktivnog JavaScript (frontend/backend) koda",
+ "toggle-note-hoisting": "Uključi/isključi podizanje aktivne bilješke",
+ "unhoist": "Poništi podizanje od bilokud",
+ "reload-frontend-app": "Ponovno učitaj frontend",
+ "open-dev-tools": "Otvori alate za razvojne programere",
+ "find-in-text": "Uključi/Isključi traku za pretraživanje",
+ "toggle-left-note-tree-panel": "Uključi/isključi lijevu (stablo bilješki) ploču",
+ "toggle-full-screen": "Uključi/isključi prikaz na cijelom zaslonu",
+ "zoom-out": "Odzumiraj",
+ "zoom-in": "Zumiraj",
+ "note-navigation": "Navigacija bilješki",
+ "reset-zoom-level": "Resetiraj razinu zumiranja",
+ "copy-without-formatting": "Kopiraj označeni tekst bez oblikovanja",
+ "force-save-revision": "Prisilno stvori/pohrani novi ispravak bilješke aktivne bilješke"
+ }
+}
diff --git a/apps/server/src/assets/translations/ja/server.json b/apps/server/src/assets/translations/ja/server.json
index 511e836cf5..1c19777aaf 100644
--- a/apps/server/src/assets/translations/ja/server.json
+++ b/apps/server/src/assets/translations/ja/server.json
@@ -46,7 +46,7 @@
"last-tab": "最後のタブをアクティブにする",
"dialogs": "ダイアログ",
"show-note-source": "「ノートのソース」ダイアログを表示",
- "show-options": "「オプション」ページを開く",
+ "show-options": "「設定」ページを開く",
"show-recent-changes": "「最近の変更」ダイアログを表示",
"show-sql-console": "「SQLコンソール」ページを開く",
"show-backend-log": "「バックエンドログ」ページを開く",
@@ -88,9 +88,9 @@
"duplicate-subtree": "サブツリーの複製",
"edit-branch-prefix": "「ブランチ接頭辞の編集」ダイアログを表示",
"show-revisions": "「ノートの変更履歴」ダイアログを表示",
- "attributes-labels-and-relations": "属性(ラベルと関係)",
+ "attributes-labels-and-relations": "属性(ラベルとリレーション)",
"add-new-label": "新しいラベルを作成する",
- "create-new-relation": "新しい関係を作成する",
+ "create-new-relation": "新しいリレーションを作成する",
"toggle-basic-properties": "基本属性切り替え",
"toggle-file-properties": "ファイル属性切り替え",
"toggle-image-properties": "画像属性切り替え",
@@ -184,7 +184,7 @@
"edit-branch-prefix": "ブランチ接頭辞の編集",
"show-revisions": "変更履歴を表示",
"add-new-label": "ラベルを追加",
- "add-new-relation": "関係を追加",
+ "add-new-relation": "リレーションを追加",
"toggle-ribbon-tab-basic-properties": "リボンタブ切り替え:基本属性",
"toggle-ribbon-tab-book-properties": "リボンタブ切り替え:書籍属性",
"toggle-ribbon-tab-file-properties": "リボンタブ切り替え:ファイル属性",
@@ -253,7 +253,8 @@
"password": "パスワード",
"password-placeholder": "パスワード",
"finish-setup": "セットアップ完了",
- "back": "戻る"
+ "back": "戻る",
+ "note": "ノート:"
},
"setup_sync-in-progress": {
"heading": "同期中",
@@ -341,7 +342,8 @@
"multi-factor-authentication-title": "多要素認証",
"etapi-title": "ETAPI",
"visible-launchers-title": "可視化されたランチャー",
- "inbox-title": "Inbox"
+ "inbox-title": "Inbox",
+ "base-abstract-launcher-title": "ベース アブストラクトランチャー"
},
"notes": {
"new-note": "新しいノート",
@@ -417,8 +419,17 @@
},
"share_page": {
"clipped-from": "このノートは元々{{- url}}から切り取られたものです",
- "no-content": "このノートには内容がありません。"
+ "no-content": "このノートには内容がありません。",
+ "parent": "親:",
+ "child-notes": "子ノート:"
},
"weekdayNumber": "第{weekNumber}週",
- "quarterNumber": "四半期 {quarterNumber}"
+ "quarterNumber": "四半期 {quarterNumber}",
+ "sql_init": {
+ "db_not_initialized_desktop": "DB が初期化されていません。画面の指示に従ってください。",
+ "db_not_initialized_server": "DB が初期化されていません。セットアップ ページ 「http://[your-server-host]:{{port}}」 にアクセスして、Trilium を初期化する方法の説明を確認してください。"
+ },
+ "desktop": {
+ "instance_already_running": "すでにインスタンスが実行されているので、代わりにそのインスタンスにフォーカスします。"
+ }
}
diff --git a/apps/server/src/becca/similarity.spec.ts b/apps/server/src/becca/similarity.spec.ts
new file mode 100644
index 0000000000..4809e1f130
--- /dev/null
+++ b/apps/server/src/becca/similarity.spec.ts
@@ -0,0 +1,20 @@
+import { trimIndentation } from "@triliumnext/commons";
+import { buildNote } from "../test/becca_easy_mocking";
+import { buildRewardMap } from "./similarity";
+
+describe("buildRewardMap", () => {
+ it("calculates heading rewards", () => {
+ const note = buildNote({
+ content: trimIndentation`\
+ Heading 1
+ Heading 2
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer eget purus et eros faucibus dignissim. Vestibulum lacinia urna quis eleifend consectetur. Aenean elementum pellentesque ultrices. Donec tincidunt, felis vel pretium suscipit, nibh lorem gravida est, quis tincidunt metus nibh a tortor. Aenean erat libero, faucibus ac mattis non, imperdiet eget nunc. Pellentesque aliquam molestie nibh eu interdum. Sed augue velit, varius id lacinia ut, dictum in dolor. Praesent posuere quam vel porta eleifend. Nullam porta tempus convallis. Aliquam auctor dui nec consectetur suscipit. Mauris laoreet commodo dapibus. Donec sodales justo velit, at placerat nulla cursus sit amet. Aliquam erat volutpat. Donec nec mauris iaculis, ullamcorper lectus et, feugiat arcu. Nunc vel ligula quis lectus efficitur porta non at nulla.
+ Heading 3
+ `
+ });
+ const map = buildRewardMap(note);
+ for (const key of [ "new", "note", "heading", "1", "2", "3" ]) {
+ expect(typeof map.get(key)).toStrictEqual("number");
+ }
+ });
+});
diff --git a/apps/server/src/becca/similarity.ts b/apps/server/src/becca/similarity.ts
index dc0a4c3f8c..10a0e706dc 100644
--- a/apps/server/src/becca/similarity.ts
+++ b/apps/server/src/becca/similarity.ts
@@ -2,7 +2,7 @@ import becca from "./becca.js";
import log from "../services/log.js";
import beccaService from "./becca_service.js";
import dateUtils from "../services/date_utils.js";
-import { JSDOM } from "jsdom";
+import { parse } from "node-html-parser";
import type BNote from "./entities/bnote.js";
import { SimilarNote } from "@triliumnext/commons";
@@ -44,7 +44,7 @@ function filterUrlValue(value: string) {
.replace(/(\.net|\.com|\.org|\.info|\.edu)/gi, "");
}
-function buildRewardMap(note: BNote) {
+export function buildRewardMap(note: BNote) {
// Need to use Map instead of object: https://github.com/zadam/trilium/issues/1895
const map = new Map();
@@ -123,10 +123,12 @@ function buildRewardMap(note: BNote) {
if (note.type === "text" && note.isDecrypted) {
const content = note.getContent();
- const dom = new JSDOM(content);
+ if (typeof content !== "string") return map;
+
+ const dom = parse(content);
const addHeadingsToRewardMap = (elName: string, rewardFactor: number) => {
- for (const el of dom.window.document.querySelectorAll(elName)) {
+ for (const el of dom.querySelectorAll(elName)) {
addToRewardMap(el.textContent, rewardFactor);
}
};
diff --git a/apps/server/src/routes/api/clipper.spec.ts b/apps/server/src/routes/api/clipper.spec.ts
new file mode 100644
index 0000000000..1efa6915e5
--- /dev/null
+++ b/apps/server/src/routes/api/clipper.spec.ts
@@ -0,0 +1,50 @@
+import { BNote } from "../../services/backend_script_entrypoint";
+import { buildNote } from "../../test/becca_easy_mocking";
+import { processContent } from "./clipper";
+
+let note!: BNote;
+
+describe("processContent", () => {
+ beforeAll(() => {
+ note = buildNote({});
+ note.saveAttachment = () => {};
+ vi.mock("../../services/image.js", () => ({
+ default: {
+ saveImageToAttachment() {
+ return {
+ attachmentId: "foo",
+ title: "encodedTitle",
+ }
+ }
+ }
+ }));
+ });
+
+ it("processes basic note", () => {
+ const processed = processContent([], note, "Hello world.
");
+ expect(processed).toStrictEqual("Hello world.
")
+ });
+
+ it("processes plain text", () => {
+ const processed = processContent([], note, "Hello world.");
+ expect(processed).toStrictEqual("Hello world.
")
+ });
+
+ it("replaces images", () => {
+ const processed = processContent(
+ [{"imageId":"OKZxZA3MonZJkwFcEhId","src":"inline.png","dataUrl":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAF0lEQVQoU2P8DwQMBADjqKLRIGAgKggAzHs/0SoYCGwAAAAASUVORK5CYII="}],
+ note, `
`
+ );
+ expect(processed).toStrictEqual(`
`);
+ });
+
+ it("skips over non-data images", () => {
+ for (const url of [ "foo", "" ]) {
+ const processed = processContent(
+ [{"imageId":"OKZxZA3MonZJkwFcEhId","src":"inline.png","dataUrl": url}],
+ note, `
`
+ );
+ expect(processed).toStrictEqual(`
`);
+ }
+ });
+});
diff --git a/apps/server/src/routes/api/clipper.ts b/apps/server/src/routes/api/clipper.ts
index 2535a26e21..133c35a88e 100644
--- a/apps/server/src/routes/api/clipper.ts
+++ b/apps/server/src/routes/api/clipper.ts
@@ -1,5 +1,5 @@
import type { Request } from "express";
-import jsdom from "jsdom";
+import { parse } from "node-html-parser";
import path from "path";
import type BNote from "../../becca/entities/bnote.js";
@@ -16,7 +16,6 @@ import log from "../../services/log.js";
import noteService from "../../services/notes.js";
import utils from "../../services/utils.js";
import ws from "../../services/ws.js";
-const { JSDOM } = jsdom;
interface Image {
src: string;
@@ -147,7 +146,7 @@ async function createNote(req: Request) {
};
}
-function processContent(images: Image[], note: BNote, content: string) {
+export function processContent(images: Image[], note: BNote, content: string) {
let rewrittenContent = htmlSanitizer.sanitize(content);
if (images) {
@@ -181,10 +180,10 @@ function processContent(images: Image[], note: BNote, content: string) {
rewrittenContent = `${rewrittenContent}
`;
}
// Create a JSDOM object from the existing HTML content
- const dom = new JSDOM(rewrittenContent);
+ const dom = parse(rewrittenContent);
// Get the content inside the body tag and serialize it
- rewrittenContent = dom.window.document.body.innerHTML;
+ rewrittenContent = dom.innerHTML ?? "";
return rewrittenContent;
}
diff --git a/apps/server/src/routes/api/note_map.spec.ts b/apps/server/src/routes/api/note_map.spec.ts
new file mode 100644
index 0000000000..0ab5d1d2d7
--- /dev/null
+++ b/apps/server/src/routes/api/note_map.spec.ts
@@ -0,0 +1,99 @@
+import { trimIndentation } from "@triliumnext/commons";
+import { buildNote, buildNotes } from "../../test/becca_easy_mocking";
+import note_map from "./note_map";
+
+describe("Note map service", () => {
+ it("correctly identifies backlinks", () => {
+ const note = buildNote({ id: "dUtgloZIckax", title: "Backlink text" });
+ buildNotes([
+ {
+ title: "First",
+ id: "first",
+ "~internalLink": "dUtgloZIckax",
+ content: trimIndentation`\
+
+ The quick brownie
+
+
+
+
+
+ `
+ },
+ {
+ title: "Second",
+ id: "second",
+ "~internalLink": "dUtgloZIckax",
+ content: trimIndentation`\
+
+
+
+ First
+
+
+
+
+ Second
+
+
+ `
+ }
+ ]);
+
+ const backlinksResponse = note_map.getBacklinks({
+ params: {
+ noteId: note.noteId
+ }
+ } as any);
+ expect(backlinksResponse).toMatchObject([
+ {
+ excerpts: [
+ trimIndentation`\
+ `
+ ],
+ noteId: "first",
+ },
+ {
+ excerpts: [
+ trimIndentation`\
+ `
+ ],
+ noteId: "second"
+ }
+ ]);
+ });
+});
diff --git a/apps/server/src/routes/api/note_map.ts b/apps/server/src/routes/api/note_map.ts
index 3a2bed5b25..473388af09 100644
--- a/apps/server/src/routes/api/note_map.ts
+++ b/apps/server/src/routes/api/note_map.ts
@@ -1,10 +1,10 @@
"use strict";
import becca from "../../becca/becca.js";
-import { JSDOM } from "jsdom";
import type BNote from "../../becca/entities/bnote.js";
import type BAttribute from "../../becca/entities/battribute.js";
import type { Request } from "express";
+import { HTMLElement, parse, TextNode } from "node-html-parser";
import { BacklinkCountResponse, BacklinksResponse } from "@triliumnext/commons";
interface TreeLink {
@@ -241,25 +241,26 @@ function updateDescendantCountMapForSearch(noteIdToDescendantCountMap: Record 0) {
- images[0]?.parentNode?.removeChild(images[0]);
+ for (const image of images) {
+ image.remove();
}
}
const EXCERPT_CHAR_LIMIT = 200;
-type ElementOrText = Element | Text;
+type ElementOrText = HTMLElement | TextNode;
-function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
+export function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
const html = sourceNote.getContent();
- const document = new JSDOM(html).window.document;
+ const document = parse(html.toString());
const excerpts: string[] = [];
removeImages(document);
for (const linkEl of document.querySelectorAll("a")) {
+ console.log("Got ", linkEl.innerHTML);
const href = linkEl.getAttribute("href");
if (!href || !href.endsWith(referencedNoteId)) {
@@ -270,8 +271,8 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
let centerEl: HTMLElement = linkEl;
- while (centerEl.tagName !== "BODY" && centerEl.parentElement && (centerEl.parentElement?.textContent?.length || 0) <= EXCERPT_CHAR_LIMIT) {
- centerEl = centerEl.parentElement;
+ while (centerEl.tagName !== "BODY" && centerEl.parentNode && (centerEl.parentNode?.textContent?.length || 0) <= EXCERPT_CHAR_LIMIT) {
+ centerEl = centerEl.parentNode;
}
const excerptEls: ElementOrText[] = [centerEl];
@@ -282,7 +283,7 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
while (excerptLength < EXCERPT_CHAR_LIMIT) {
let added = false;
- const prev: Element | null = left.previousElementSibling;
+ const prev: HTMLElement | null = left.previousElementSibling;
if (prev) {
const prevText = prev.textContent || "";
@@ -290,7 +291,7 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
if (prevText.length + excerptLength > EXCERPT_CHAR_LIMIT) {
const prefix = prevText.substr(prevText.length - (EXCERPT_CHAR_LIMIT - excerptLength));
- const textNode = document.createTextNode(`…${prefix}`);
+ const textNode = new TextNode(`…${prefix}`);
excerptEls.unshift(textNode);
break;
@@ -302,7 +303,7 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
added = true;
}
- const next: Element | null = right.nextElementSibling;
+ const next: HTMLElement | null = right.nextElementSibling;
if (next) {
const nextText = next.textContent;
@@ -310,7 +311,7 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
if (nextText && nextText.length + excerptLength > EXCERPT_CHAR_LIMIT) {
const suffix = nextText.substr(nextText.length - (EXCERPT_CHAR_LIMIT - excerptLength));
- const textNode = document.createTextNode(`${suffix}…`);
+ const textNode = new TextNode(`${suffix}…`);
excerptEls.push(textNode);
break;
@@ -327,7 +328,7 @@ function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
}
}
- const excerptWrapper = document.createElement("div");
+ const excerptWrapper = new HTMLElement("div", {});
excerptWrapper.classList.add("ck-content");
excerptWrapper.classList.add("backlink-excerpt");
diff --git a/apps/server/src/share/content_renderer.spec.ts b/apps/server/src/share/content_renderer.spec.ts
index 85d1c9ddee..8f3f70622d 100644
--- a/apps/server/src/share/content_renderer.spec.ts
+++ b/apps/server/src/share/content_renderer.spec.ts
@@ -1,7 +1,88 @@
import { describe, it, expect } from "vitest";
-import { renderCode, type Result } from "./content_renderer.js";
+import { getContent, renderCode, type Result } from "./content_renderer.js";
+import { trimIndentation } from "@triliumnext/commons";
+import { buildShareNote, buildShareNotes } from "../test/shaca_mocking.js";
describe("content_renderer", () => {
+ beforeAll(() => {
+ vi.mock("../becca/becca_loader.js", () => ({
+ default: {
+ load: vi.fn(),
+ loaded: Promise.resolve()
+ }
+ }));
+ });
+
+ it("Reports protected notes not being renderable", () => {
+ const note = buildShareNote({ isProtected: true });
+ const result = getContent(note);
+ expect(result.content).toStrictEqual("Protected note cannot be displayed
");
+ });
+
+ describe("Text note", () => {
+ it("parses simple note", () => {
+ const content = trimIndentation`\
+
+
+
+
+
+ Welcome to Trilium Notes!
+
+
`;
+ const note = buildShareNote({ content });
+ const result = getContent(note);
+ expect(result.content).toStrictEqual(content);
+ });
+
+ it("handles attachment link", () => {
+ const content = trimIndentation`\
+ Test
+
+
+ 5863845791835102555.mp4
+
+
+
+ `;
+ const note = buildShareNote({
+ content,
+ attachments: [ { id: "q14s2Id7V6pp", title: "5863845791835102555.mp4" } ]
+ });
+ const result = getContent(note);
+ expect(result.content).toStrictEqual(trimIndentation`\
+ Test
+
+ 5863845791835102555.mp4
+
+
+ `);
+ });
+
+ it("renders included notes", () => {
+ buildShareNotes([
+ { id: "subnote1", content: `Foo
Bar` },
+ { id: "subnote2", content: `Baz` }
+ ]);
+ const note = buildShareNote({
+ id: "note1",
+ content: trimIndentation`\
+ Before
+
+
+ After
+ `
+ });
+ const result = getContent(note);
+ expect(result.content).toStrictEqual(trimIndentation`\
+ Before
+ Foo
Bar
+ Baz
+ After
+ `);
+ });
+ });
+
describe("renderCode", () => {
it("identifies empty content", () => {
const emptyResult: Result = {
diff --git a/apps/server/src/share/content_renderer.ts b/apps/server/src/share/content_renderer.ts
index c1c16e48d0..83ddfde2e5 100644
--- a/apps/server/src/share/content_renderer.ts
+++ b/apps/server/src/share/content_renderer.ts
@@ -1,4 +1,4 @@
-import { JSDOM } from "jsdom";
+import { parse, HTMLElement, TextNode } from "node-html-parser";
import shaca from "./shaca/shaca.js";
import assetPath from "../services/asset_path.js";
import shareRoot from "./share_root.js";
@@ -16,7 +16,7 @@ export interface Result {
isEmpty?: boolean;
}
-function getContent(note: SNote) {
+export function getContent(note: SNote) {
if (note.isProtected) {
return {
header: "",
@@ -66,9 +66,27 @@ function renderIndex(result: Result) {
}
function renderText(result: Result, note: SNote) {
- const document = new JSDOM(result.content || "").window.document;
+ if (typeof result.content !== "string") return;
+ const document = parse(result.content || "");
- result.isEmpty = document.body.textContent?.trim().length === 0 && document.querySelectorAll("img").length === 0;
+ // Process include notes.
+ for (const includeNoteEl of document.querySelectorAll("section.include-note")) {
+ const noteId = includeNoteEl.getAttribute("data-note-id");
+ if (!noteId) continue;
+
+ const note = shaca.getNote(noteId);
+ if (!note) continue;
+
+ const includedResult = getContent(note);
+ if (typeof includedResult.content !== "string") continue;
+
+ const includedDocument = parse(includedResult.content).childNodes;
+ if (includedDocument) {
+ includeNoteEl.replaceWith(...includedDocument);
+ }
+ }
+
+ result.isEmpty = document.textContent?.trim().length === 0 && document.querySelectorAll("img").length === 0;
if (!result.isEmpty) {
for (const linkEl of document.querySelectorAll("a")) {
@@ -84,7 +102,7 @@ function renderText(result: Result, note: SNote) {
}
}
- result.content = document.body.innerHTML;
+ result.content = document.innerHTML ?? "";
if (result.content.includes(``)) {
result.header += `
@@ -105,7 +123,7 @@ document.addEventListener("DOMContentLoaded", function() {
}
}
-function handleAttachmentLink(linkEl: HTMLAnchorElement, href: string) {
+function handleAttachmentLink(linkEl: HTMLElement, href: string) {
const linkRegExp = /attachmentId=([a-zA-Z0-9_]+)/g;
let attachmentMatch;
if ((attachmentMatch = linkRegExp.exec(href))) {
@@ -116,7 +134,8 @@ function handleAttachmentLink(linkEl: HTMLAnchorElement, href: string) {
linkEl.setAttribute("href", `api/attachments/${attachmentId}/download`);
linkEl.classList.add(`attachment-link`);
linkEl.classList.add(`role-${attachment.role}`);
- linkEl.innerText = attachment.title;
+ linkEl.childNodes.length = 0;
+ linkEl.appendChild(new TextNode(attachment.title));
} else {
linkEl.removeAttribute("href");
}
@@ -149,11 +168,8 @@ export function renderCode(result: Result) {
if (typeof result.content !== "string" || !result.content?.trim()) {
result.isEmpty = true;
} else {
- const document = new JSDOM().window.document;
-
- const preEl = document.createElement("pre");
- preEl.appendChild(document.createTextNode(result.content));
-
+ const preEl = new HTMLElement("pre", {});
+ preEl.appendChild(new TextNode(result.content));
result.content = preEl.outerHTML;
}
}
diff --git a/apps/server/src/test/becca_easy_mocking.ts b/apps/server/src/test/becca_easy_mocking.ts
new file mode 100644
index 0000000000..6df198a5a4
--- /dev/null
+++ b/apps/server/src/test/becca_easy_mocking.ts
@@ -0,0 +1,92 @@
+import utils from "../services/utils.js";
+import BNote from "../becca/entities/bnote.js";
+import BAttribute from "../becca/entities/battribute.js";
+
+type AttributeDefinitions = { [key in `#${string}`]: string; };
+type RelationDefinitions = { [key in `~${string}`]: string; };
+
+interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
+ id?: string | undefined;
+ title?: string;
+ content?: string;
+}
+
+/**
+ * Creates the given notes with the given title and optionally one or more attributes.
+ *
+ * For a label to be created, simply pass on a key prefixed with `#` and any desired value.
+ *
+ * The notes and attributes will be injected in the froca.
+ *
+ * @param notes
+ * @returns an array containing the IDs of the created notes.
+ * @example
+ * buildNotes([
+ * { title: "A", "#startDate": "2025-05-05" },
+ * { title: "B", "#startDate": "2025-05-07" }
+ * ]);
+ */
+export function buildNotes(notes: NoteDefinition[]) {
+ const ids: string[] = [];
+
+ for (const noteDef of notes) {
+ ids.push(buildNote(noteDef).noteId);
+ }
+
+ return ids;
+}
+
+export function buildNote(noteDef: NoteDefinition) {
+ const note = new BNote({
+ noteId: noteDef.id ?? utils.randomString(12),
+ title: noteDef.title ?? "New note",
+ type: "text",
+ mime: "text/html",
+ isProtected: false,
+ blobId: ""
+ });
+
+ // Handle content.
+ if (noteDef.content) {
+ note.getContent = () => noteDef.content!;
+ }
+
+ // Handle labels and relations.
+ let position = 0;
+ for (const [ key, value ] of Object.entries(noteDef)) {
+ const attributeId = utils.randomString(12);
+ const name = key.substring(1);
+
+ let attribute: BAttribute | null = null;
+ if (key.startsWith("#")) {
+ attribute = new BAttribute({
+ noteId: note.noteId,
+ attributeId,
+ type: "label",
+ name,
+ value,
+ position,
+ isInheritable: false
+ });
+ }
+
+ if (key.startsWith("~")) {
+ attribute = new BAttribute({
+ noteId: note.noteId,
+ attributeId,
+ type: "relation",
+ name,
+ value,
+ position,
+ isInheritable: false
+ });
+ }
+
+ if (!attribute) {
+ continue;
+ }
+
+ position++;
+ }
+ return note;
+}
diff --git a/apps/server/src/test/shaca_mocking.ts b/apps/server/src/test/shaca_mocking.ts
new file mode 100644
index 0000000000..8b2068a0ec
--- /dev/null
+++ b/apps/server/src/test/shaca_mocking.ts
@@ -0,0 +1,135 @@
+import utils from "../services/utils.js";
+import SAttachment from "../share/shaca/entities/sattachment.js";
+import SAttribute from "../share/shaca/entities/sattribute.js";
+import SNote from "../share/shaca/entities/snote.js";
+import shaca from "../share/shaca/shaca.js";
+
+type AttributeDefinitions = { [key in `#${string}`]: string; };
+type RelationDefinitions = { [key in `~${string}`]: string; };
+
+interface AttachementDefinition {
+ id?: string;
+ role?: string;
+ mime?: string;
+ title?: string;
+}
+
+interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
+ id?: string | undefined;
+ title?: string;
+ content?: string | Buffer;
+ children?: NoteDefinition[];
+ attachments?: AttachementDefinition[];
+ isProtected?: boolean;
+}
+
+/**
+ * Creates the given notes with the given title and optionally one or more attributes.
+ *
+ * For a label to be created, simply pass on a key prefixed with `#` and any desired value.
+ *
+ * The notes and attributes will be injected in the froca.
+ *
+ * @param notes
+ * @returns an array containing the IDs of the created notes.
+ * @example
+ * buildShareNotes([
+ * { title: "A", "#startDate": "2025-05-05" },
+ * { title: "B", "#startDate": "2025-05-07" }
+ * ]);
+ */
+export function buildShareNotes(notes: NoteDefinition[]) {
+ const ids: string[] = [];
+
+ for (const noteDef of notes) {
+ ids.push(buildShareNote(noteDef).noteId);
+ }
+
+ return ids;
+}
+
+export function buildShareNote(noteDef: NoteDefinition) {
+ const blobId = "foo";
+ const note = new SNote([
+ noteDef.id ?? utils.randomString(12),
+ noteDef.title ?? "New note",
+ "text",
+ "text/html",
+ blobId,
+ new Date().toUTCString(), // utcDateModified
+ !!noteDef.isProtected
+ ]);
+ shaca.notes[note.noteId] = note;
+
+ // Handle content
+ if (noteDef.content) {
+ note.getContent = () => {
+ if (noteDef.isProtected) return undefined;
+ return noteDef.content;
+ };
+ }
+
+ // Handle attachments.
+ if (noteDef.attachments) {
+ for (const attachmentDef of noteDef.attachments) {
+ new SAttachment([
+ attachmentDef.id ?? utils.randomString(12),
+ note.noteId,
+ attachmentDef.role ?? "file",
+ attachmentDef.mime ?? "application/blob",
+ attachmentDef.title ?? "New attachment",
+ blobId,
+ new Date().toUTCString(), // utcDateModified
+ ]);
+ }
+ }
+
+ // Handle children.
+ if (noteDef.children) {
+ for (const childDef of noteDef.children) {
+ const childNote = buildShareNote(childDef);
+
+ // TODO: Create corresponding SBranch.
+ }
+ }
+
+ // Handle labels & relations
+ let position = 0;
+ for (const [ key, value ] of Object.entries(noteDef)) {
+ const attributeId = utils.randomString(12);
+ const name = key.substring(1);
+
+ let attribute: SAttribute | null = null;
+ if (key.startsWith("#")) {
+ attribute = new SAttribute([
+ attributeId,
+ note.noteId,
+ "label",
+ name,
+ value,
+ false, // isInheritable
+ position // position
+ ]);
+ }
+
+ if (key.startsWith("~")) {
+ attribute = new SAttribute([
+ attributeId,
+ note.noteId,
+ "relation",
+ name,
+ value,
+ false, // isInheritable
+ position // position
+ ]);
+ }
+
+ if (!attribute) {
+ continue;
+ }
+
+ shaca.attributes[attributeId] = attribute;
+ position++;
+ }
+ return note;
+}
diff --git a/apps/website/.gitignore b/apps/website/.gitignore
index 5a3bb85067..23f5d64255 100644
--- a/apps/website/.gitignore
+++ b/apps/website/.gitignore
@@ -1,28 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
node_modules
+dist
+dist-ssr
+*.local
-# Output
-.output
-.vercel
-.netlify
-.wrangler
-/.svelte-kit
-/build
-
-# OS
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
.DS_Store
-Thumbs.db
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
-# Env
-.env
-.env.*
-!.env.example
-!.env.test
-
-# Vite
-vite.config.js.timestamp-*
-vite.config.ts.timestamp-*
-
-# Paraglide
-src/lib/paraglide
-
-project.inlang/cache
\ No newline at end of file
+*.d.ts
+!types-assets.d.ts
+*.map
\ No newline at end of file
diff --git a/apps/website/.npmrc b/apps/website/.npmrc
deleted file mode 100644
index b6f27f1359..0000000000
--- a/apps/website/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-engine-strict=true
diff --git a/apps/website/README.md b/apps/website/README.md
index 88ad0a1c69..1ebc2149e9 100644
--- a/apps/website/README.md
+++ b/apps/website/README.md
@@ -1,11 +1,15 @@
-# apps/website
+# `create-preact`
-Landing page for Trilium Notes powered by [Svelte](https://github.com/sveltejs/cli) and [Tailwind CSS](https://tailwindcss.com/).
+
+
+
-## Developing
+Get started using Preact and Vite!
-To run a dev server that will hot-reload changes: `pnpm dev`
+## Getting Started
-## Building
+- `npm run dev` - Starts a dev server at http://localhost:5173/
-To create a production build: `pnpm build`
+- `npm run build` - Builds for production, emitting to `dist/`. Prerenders all found routes in app to static HTML
+
+- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally
diff --git a/apps/website/eslint.config.js b/apps/website/eslint.config.js
deleted file mode 100644
index 1652371850..0000000000
--- a/apps/website/eslint.config.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import js from '@eslint/js';
-import { includeIgnoreFile } from '@eslint/compat';
-import svelte from 'eslint-plugin-svelte';
-import globals from 'globals';
-import { fileURLToPath } from 'node:url';
-import ts from 'typescript-eslint';
-import svelteConfig from './svelte.config.js';
-
-const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
-
-export default ts.config(
- includeIgnoreFile(gitignorePath),
- js.configs.recommended,
- ...ts.configs.recommended,
- ...svelte.configs.recommended,
- {
- languageOptions: {
- globals: { ...globals.browser, ...globals.node }
- },
- rules: { // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
- // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
- "no-undef": 'off' }
- },
- {
- files: [
- '**/*.svelte',
- '**/*.svelte.ts',
- '**/*.svelte.js'
- ],
- languageOptions: {
- parserOptions: {
- projectService: true,
- extraFileExtensions: ['.svelte'],
- parser: ts.parser,
- svelteConfig
- }
- }
- }
-);
diff --git a/apps/website/index.html b/apps/website/index.html
new file mode 100644
index 0000000000..59b5a1e3c6
--- /dev/null
+++ b/apps/website/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Trilium Notes
+
+
+
+
+
+
diff --git a/apps/website/messages/en.json b/apps/website/messages/en.json
deleted file mode 100644
index 37a9894402..0000000000
--- a/apps/website/messages/en.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "$schema": "https://inlang.com/schema/inlang-message-format",
- "hello_world": "Hello, {name} from en!"
-}
diff --git a/apps/website/package.json b/apps/website/package.json
index aa8f73e17c..6258f33019 100644
--- a/apps/website/package.json
+++ b/apps/website/package.json
@@ -1,37 +1,25 @@
{
- "name": "website",
"private": true,
- "version": "0.0.1",
+ "name": "@triliumnext/website",
"type": "module",
"scripts": {
- "dev": "vite dev",
+ "dev": "vite",
"build": "vite build",
- "preview": "vite preview",
- "prepare": "svelte-kit sync || echo ''",
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
- "lint": "eslint ."
- },
- "devDependencies": {
- "@eslint/compat": "^1.2.5",
- "@eslint/js": "^9.18.0",
- "@sveltejs/adapter-auto": "^6.0.0",
- "@sveltejs/kit": "^2.16.0",
- "@sveltejs/vite-plugin-svelte": "^6.0.0",
- "@tailwindcss/typography": "^0.5.15",
- "@tailwindcss/vite": "^4.0.0",
- "eslint": "^9.18.0",
- "eslint-plugin-svelte": "^3.0.0",
- "globals": "^16.0.0",
- "mdsvex": "^0.12.3",
- "svelte": "^5.0.0",
- "svelte-check": "^4.0.0",
- "tailwindcss": "^4.0.0",
- "typescript": "^5.0.0",
- "typescript-eslint": "^8.20.0",
- "vite": "^7.0.0"
+ "preview": "vite preview"
},
"dependencies": {
- "@inlang/paraglide-js": "^2.0.0"
+ "preact": "^10.26.9",
+ "preact-iso": "^2.10.0",
+ "preact-render-to-string": "^6.6.1"
+ },
+ "devDependencies": {
+ "@preact/preset-vite": "^2.10.2",
+ "eslint": "^9.36.0",
+ "eslint-config-preact": "^2.0.0",
+ "typescript": "^5.9.2",
+ "vite": "^7.0.4"
+ },
+ "eslintConfig": {
+ "extends": "preact"
}
}
diff --git a/apps/website/project.inlang/project_id b/apps/website/project.inlang/project_id
deleted file mode 100644
index 2002b3bce0..0000000000
--- a/apps/website/project.inlang/project_id
+++ /dev/null
@@ -1 +0,0 @@
-dv1iXGpHP2mMvuQQo4
\ No newline at end of file
diff --git a/apps/website/project.inlang/settings.json b/apps/website/project.inlang/settings.json
deleted file mode 100644
index acfd0d3d45..0000000000
--- a/apps/website/project.inlang/settings.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "https://inlang.com/schema/project-settings",
- "modules": [
- "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
- "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
- ],
- "plugin.inlang.messageFormat": {
- "pathPattern": "./messages/{locale}.json"
- },
- "baseLocale": "en",
- "locales": [
- "en"
- ]
-}
diff --git a/apps/website/public/collection_board.webp b/apps/website/public/collection_board.webp
new file mode 100644
index 0000000000..6ed49dd449
Binary files /dev/null and b/apps/website/public/collection_board.webp differ
diff --git a/apps/website/public/collection_calendar.webp b/apps/website/public/collection_calendar.webp
new file mode 100644
index 0000000000..34b7961e0f
Binary files /dev/null and b/apps/website/public/collection_calendar.webp differ
diff --git a/apps/website/public/collection_geomap.webp b/apps/website/public/collection_geomap.webp
new file mode 100644
index 0000000000..e564fbfc6f
Binary files /dev/null and b/apps/website/public/collection_geomap.webp differ
diff --git a/apps/website/public/collection_table.webp b/apps/website/public/collection_table.webp
new file mode 100644
index 0000000000..2113caf65f
Binary files /dev/null and b/apps/website/public/collection_table.webp differ
diff --git a/apps/website/public/screenshot_desktop_mac_dark.webp b/apps/website/public/screenshot_desktop_mac_dark.webp
new file mode 100644
index 0000000000..347838682f
Binary files /dev/null and b/apps/website/public/screenshot_desktop_mac_dark.webp differ
diff --git a/apps/website/public/screenshot_desktop_mac_light.webp b/apps/website/public/screenshot_desktop_mac_light.webp
new file mode 100644
index 0000000000..2415bbd3ff
Binary files /dev/null and b/apps/website/public/screenshot_desktop_mac_light.webp differ
diff --git a/apps/website/public/screenshot_desktop_win_dark.webp b/apps/website/public/screenshot_desktop_win_dark.webp
new file mode 100644
index 0000000000..051d8f1c22
Binary files /dev/null and b/apps/website/public/screenshot_desktop_win_dark.webp differ
diff --git a/apps/website/public/screenshot_desktop_win_light.webp b/apps/website/public/screenshot_desktop_win_light.webp
new file mode 100644
index 0000000000..a44801ea3b
Binary files /dev/null and b/apps/website/public/screenshot_desktop_win_light.webp differ
diff --git a/apps/website/public/type_canvas.webp b/apps/website/public/type_canvas.webp
new file mode 100644
index 0000000000..839818606a
Binary files /dev/null and b/apps/website/public/type_canvas.webp differ
diff --git a/apps/website/public/type_code.webp b/apps/website/public/type_code.webp
new file mode 100644
index 0000000000..91816541a7
Binary files /dev/null and b/apps/website/public/type_code.webp differ
diff --git a/apps/website/public/type_file.webp b/apps/website/public/type_file.webp
new file mode 100644
index 0000000000..6423d4fc43
Binary files /dev/null and b/apps/website/public/type_file.webp differ
diff --git a/apps/website/public/type_mermaid.webp b/apps/website/public/type_mermaid.webp
new file mode 100644
index 0000000000..c7c7eb3c6e
Binary files /dev/null and b/apps/website/public/type_mermaid.webp differ
diff --git a/apps/website/public/type_mindmap.webp b/apps/website/public/type_mindmap.webp
new file mode 100644
index 0000000000..23addc0658
Binary files /dev/null and b/apps/website/public/type_mindmap.webp differ
diff --git a/apps/website/public/type_text.webp b/apps/website/public/type_text.webp
new file mode 100644
index 0000000000..dd897dbca4
Binary files /dev/null and b/apps/website/public/type_text.webp differ
diff --git a/apps/website/src/app.css b/apps/website/src/app.css
deleted file mode 100644
index e6f2515013..0000000000
--- a/apps/website/src/app.css
+++ /dev/null
@@ -1,10 +0,0 @@
-@import 'tailwindcss';
-@plugin '@tailwindcss/typography';
-
-main a {
- text-decoration: revert;
-}
-
-a.rounded-full, a.rounded-xl {
- text-decoration: none;
-}
diff --git a/apps/website/src/app.d.ts b/apps/website/src/app.d.ts
deleted file mode 100644
index da08e6da59..0000000000
--- a/apps/website/src/app.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// See https://svelte.dev/docs/kit/types#app.d.ts
-// for information about these interfaces
-declare global {
- namespace App {
- // interface Error {}
- // interface Locals {}
- // interface PageData {}
- // interface PageState {}
- // interface Platform {}
- }
-}
-
-export {};
diff --git a/apps/website/src/app.html b/apps/website/src/app.html
deleted file mode 100644
index fee9ee80f9..0000000000
--- a/apps/website/src/app.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
- %sveltekit.head%
-
-
- %sveltekit.body%
-
-
diff --git a/apps/website/src/assets/boxicons/bx-arrow-in-down-square-half.svg b/apps/website/src/assets/boxicons/bx-arrow-in-down-square-half.svg
new file mode 100644
index 0000000000..6f4f20ac74
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-arrow-in-down-square-half.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-buy-me-a-coffee.svg b/apps/website/src/assets/boxicons/bx-buy-me-a-coffee.svg
new file mode 100644
index 0000000000..5e105e2f76
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-buy-me-a-coffee.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-calendar.svg b/apps/website/src/assets/boxicons/bx-calendar.svg
new file mode 100644
index 0000000000..a9e2839960
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-calendar.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-chevrons-up.svg b/apps/website/src/assets/boxicons/bx-chevrons-up.svg
new file mode 100644
index 0000000000..ffb1feeef1
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-chevrons-up.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-code.svg b/apps/website/src/assets/boxicons/bx-code.svg
new file mode 100644
index 0000000000..56dfdf85b9
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-code.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-columns-3.svg b/apps/website/src/assets/boxicons/bx-columns-3.svg
new file mode 100644
index 0000000000..646faa6dac
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-columns-3.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-discussion.svg b/apps/website/src/assets/boxicons/bx-discussion.svg
new file mode 100644
index 0000000000..221197340d
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-discussion.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-docker.svg b/apps/website/src/assets/boxicons/bx-docker.svg
new file mode 100644
index 0000000000..b6d9253777
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-docker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-extension.svg b/apps/website/src/assets/boxicons/bx-extension.svg
new file mode 100644
index 0000000000..92fc514721
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-extension.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-file.svg b/apps/website/src/assets/boxicons/bx-file.svg
new file mode 100644
index 0000000000..99f70f539b
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-folder.svg b/apps/website/src/assets/boxicons/bx-folder.svg
new file mode 100644
index 0000000000..af88fa32b0
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-folder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-github.svg b/apps/website/src/assets/boxicons/bx-github.svg
new file mode 100644
index 0000000000..d027cc0db4
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-globe.svg b/apps/website/src/assets/boxicons/bx-globe.svg
new file mode 100644
index 0000000000..fbd0bb7119
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-help-circle.svg b/apps/website/src/assets/boxicons/bx-help-circle.svg
new file mode 100644
index 0000000000..73335bc118
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-help-circle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-history.svg b/apps/website/src/assets/boxicons/bx-history.svg
new file mode 100644
index 0000000000..a87acdea8b
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-history.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-map.svg b/apps/website/src/assets/boxicons/bx-map.svg
new file mode 100644
index 0000000000..3a27e2fa6c
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-map.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-menu.svg b/apps/website/src/assets/boxicons/bx-menu.svg
new file mode 100644
index 0000000000..1c1eebcb57
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-message-dots.svg b/apps/website/src/assets/boxicons/bx-message-dots.svg
new file mode 100644
index 0000000000..5217978562
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-message-dots.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-network-chart.svg b/apps/website/src/assets/boxicons/bx-network-chart.svg
new file mode 100644
index 0000000000..2693f0baca
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-network-chart.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-note.svg b/apps/website/src/assets/boxicons/bx-note.svg
new file mode 100644
index 0000000000..7cf848727a
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-note.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-paperclip.svg b/apps/website/src/assets/boxicons/bx-paperclip.svg
new file mode 100644
index 0000000000..7d392cf8c6
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-paperclip.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-paypal.svg b/apps/website/src/assets/boxicons/bx-paypal.svg
new file mode 100644
index 0000000000..e4f3db18c7
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-paypal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-pen.svg b/apps/website/src/assets/boxicons/bx-pen.svg
new file mode 100644
index 0000000000..e26e07d9ab
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-pen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-reddit.svg b/apps/website/src/assets/boxicons/bx-reddit.svg
new file mode 100644
index 0000000000..e6090b397b
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-reddit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-refresh-cw.svg b/apps/website/src/assets/boxicons/bx-refresh-cw.svg
new file mode 100644
index 0000000000..c25ae9c9db
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-refresh-cw.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-search.svg b/apps/website/src/assets/boxicons/bx-search.svg
new file mode 100644
index 0000000000..5f648745d7
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-send-alt.svg b/apps/website/src/assets/boxicons/bx-send-alt.svg
new file mode 100644
index 0000000000..68ed3e1e23
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-send-alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-shield.svg b/apps/website/src/assets/boxicons/bx-shield.svg
new file mode 100644
index 0000000000..babfd1ec11
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-shield.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-swap-horizontal.svg b/apps/website/src/assets/boxicons/bx-swap-horizontal.svg
new file mode 100644
index 0000000000..dc45d78e38
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-swap-horizontal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-table.svg b/apps/website/src/assets/boxicons/bx-table.svg
new file mode 100644
index 0000000000..7a3cd410c5
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-tag.svg b/apps/website/src/assets/boxicons/bx-tag.svg
new file mode 100644
index 0000000000..6e698bd71a
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-tag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/boxicons/bx-vector-square.svg b/apps/website/src/assets/boxicons/bx-vector-square.svg
new file mode 100644
index 0000000000..57eefdeb98
--- /dev/null
+++ b/apps/website/src/assets/boxicons/bx-vector-square.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/src/assets/favicon.ico b/apps/website/src/assets/favicon.ico
new file mode 100644
index 0000000000..398e3854e6
Binary files /dev/null and b/apps/website/src/assets/favicon.ico differ
diff --git a/apps/website/static/icon-color.svg b/apps/website/src/assets/icon-color.svg
similarity index 100%
rename from apps/website/static/icon-color.svg
rename to apps/website/src/assets/icon-color.svg
diff --git a/apps/website/src/components/Button.css b/apps/website/src/components/Button.css
new file mode 100644
index 0000000000..4eabe906a5
--- /dev/null
+++ b/apps/website/src/components/Button.css
@@ -0,0 +1,20 @@
+a.button {
+ display: block;
+ background-color: var(--brand-1);
+ padding: 0.5em 1em;
+ border-radius: 6px;
+ color: var(--brand-foreground-color);
+ text-decoration: none;
+ text-align: center;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.15);
+
+ &.outline {
+ border: 1px solid var(--brand-1);
+ color: var(--brand-1);
+ background-color: transparent;
+ }
+
+ .text {
+ vertical-align: middle;
+ }
+}
\ No newline at end of file
diff --git a/apps/website/src/components/Button.tsx b/apps/website/src/components/Button.tsx
new file mode 100644
index 0000000000..729346e58d
--- /dev/null
+++ b/apps/website/src/components/Button.tsx
@@ -0,0 +1,44 @@
+import { ComponentChildren } from "preact";
+import Icon from "./Icon.js";
+import "./Button.css";
+
+interface LinkProps {
+ className?: string;
+ href?: string;
+ openExternally?: boolean;
+ children: ComponentChildren;
+ title?: string;
+ onClick?: (e: MouseEvent) => void;
+}
+
+interface ButtonProps extends Omit {
+ href?: string;
+ iconSvg?: string;
+ text: ComponentChildren;
+ openExternally?: boolean;
+ outline?: boolean;
+}
+
+export default function Button({ iconSvg, text, className, outline, ...restProps }: ButtonProps) {
+ return (
+
+ {iconSvg && <> {" "}>}
+ {text}
+
+ )
+}
+
+export function Link({ openExternally, children, ...restProps }: LinkProps) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/website/src/components/Card.tsx b/apps/website/src/components/Card.tsx
new file mode 100644
index 0000000000..49c357132e
--- /dev/null
+++ b/apps/website/src/components/Card.tsx
@@ -0,0 +1,37 @@
+import { ComponentChildren, HTMLAttributes } from "preact";
+import { Link } from "./Button.js";
+import Icon from "./Icon.js";
+
+interface CardProps extends Omit, "title"> {
+ title: ComponentChildren;
+ imageUrl?: string;
+ iconSvg?: string;
+ className?: string;
+ moreInfoUrl?: string;
+ children: ComponentChildren;
+}
+
+export default function Card({ title, children, imageUrl, iconSvg, className, moreInfoUrl, ...restProps }: CardProps) {
+ return (
+
+ {imageUrl &&
}
+
+
+
+ {iconSvg && }{" "}
+ {title}
+
+
+
+ {children}
+
+
+ {moreInfoUrl && (
+
+ Learn more...
+
+ )}
+
+
+ )
+}
diff --git a/apps/website/src/components/DownloadButton.css b/apps/website/src/components/DownloadButton.css
new file mode 100644
index 0000000000..d7ed4cf8fb
--- /dev/null
+++ b/apps/website/src/components/DownloadButton.css
@@ -0,0 +1,27 @@
+a.download-button {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ gap: 1em;
+
+ @media (min-width: 720px) {
+ display: flex !important;
+ }
+
+ .platform {
+ font-size: 0.75em;
+ opacity: 0.75;
+ }
+
+ &.big {
+ padding: 0.5em 3.5em;
+ margin: 1em 0;
+ gap: m;
+ text-align: left;
+
+ .platform {
+ display: block;
+ }
+ }
+}
+
diff --git a/apps/website/src/components/DownloadButton.tsx b/apps/website/src/components/DownloadButton.tsx
new file mode 100644
index 0000000000..8ed8cf18a8
--- /dev/null
+++ b/apps/website/src/components/DownloadButton.tsx
@@ -0,0 +1,30 @@
+import { getRecommendedDownload, RecommendedDownload } from "../download-helper.js";
+import "./DownloadButton.css";
+import Button from "./Button.js";
+import downloadIcon from "../assets/boxicons/bx-arrow-in-down-square-half.svg?raw";
+import packageJson from "../../../../package.json" with { type: "json" };
+import { useEffect, useState } from "preact/hooks";
+
+interface DownloadButtonProps {
+ big?: boolean;
+}
+
+export default function DownloadButton({ big }: DownloadButtonProps) {
+ const [ recommendedDownload, setRecommendedDownload ] = useState();
+ useEffect(() => setRecommendedDownload(getRecommendedDownload()), []);
+
+ return (recommendedDownload &&
+