Compare commits
No commits in common. "07c8e31c66e4eef35560cddf8e95498855bcff18" and "8cd9538a50fd4b585118c3719ee82d7542cc7d37" have entirely different histories.
07c8e31c66
...
8cd9538a50
4 changed files with 10 additions and 226 deletions
|
|
@ -3,8 +3,6 @@
|
||||||
* Fix a few small matters identified with tests
|
* Fix a few small matters identified with tests
|
||||||
* Make locales dynamically detected from the locales dir rather than hardcoded
|
* Make locales dynamically detected from the locales dir rather than hardcoded
|
||||||
* Add version information in the navigation
|
* Add version information in the navigation
|
||||||
* Increase line spacing between lines (except for code blocks)
|
|
||||||
* Add Italian translations (thanks @mdaleo404)
|
|
||||||
|
|
||||||
# 0.2.1.8
|
# 0.2.1.8
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
{
|
|
||||||
"db_sqlcipher_integrity_check_failed": "Controllo di integrità SQLCipher fallito",
|
|
||||||
"db_issues_reported": "problema/i segnalato/i",
|
|
||||||
"db_reopen_failed_after_rekey": "Riapertura fallita dopo il cambio chiave",
|
|
||||||
"db_version_id_does_not_belong_to_the_given_date": "version_id non appartiene alla data indicata",
|
|
||||||
"db_key_incorrect": "La chiave è probabilmente errata",
|
|
||||||
"db_database_error": "Errore del database",
|
|
||||||
"database_path": "Percorso del database",
|
|
||||||
"database_maintenance": "Manutenzione del database",
|
|
||||||
"database_compact": "Compatta il database",
|
|
||||||
"database_compact_explanation": "La compattazione esegue VACUUM sul database. Può aiutare a ridurne le dimensioni.",
|
|
||||||
"database_compacted_successfully": "Database compattato con successo!",
|
|
||||||
"encryption": "Crittografia",
|
|
||||||
"remember_key": "Ricorda la chiave",
|
|
||||||
"change_encryption_key": "Cambia chiave di crittografia",
|
|
||||||
"enter_a_new_encryption_key": "Inserisci una nuova chiave di crittografia",
|
|
||||||
"reenter_the_new_key": "Reinserisci la nuova chiave",
|
|
||||||
"key_mismatch": "Le chiavi non corrispondono",
|
|
||||||
"key_mismatch_explanation": "Le due chiavi inserite non corrispondono.",
|
|
||||||
"empty_key": "Chiave vuota",
|
|
||||||
"empty_key_explanation": "La chiave non può essere vuota.",
|
|
||||||
"key_changed": "Chiave cambiata",
|
|
||||||
"key_changed_explanation": "Il blocco note è stato criptato nuovamente con la nuova chiave!",
|
|
||||||
"error": "Errore",
|
|
||||||
"success": "Successo",
|
|
||||||
"close": "Chiudi",
|
|
||||||
"find": "Trova",
|
|
||||||
"file": "File",
|
|
||||||
"locale": "Lingua",
|
|
||||||
"locale_restart": "Per favore riavvia l'applicazione per caricare la nuova lingua.",
|
|
||||||
"settings": "Impostazioni",
|
|
||||||
"theme": "Tema",
|
|
||||||
"system": "Sistema",
|
|
||||||
"light": "Chiaro",
|
|
||||||
"dark": "Scuro",
|
|
||||||
"behaviour": "Comportamento",
|
|
||||||
"never": "Mai",
|
|
||||||
"browse": "Sfoglia",
|
|
||||||
"previous": "Precedente",
|
|
||||||
"previous_day": "Giorno precedente",
|
|
||||||
"next": "Successivo",
|
|
||||||
"next_day": "Giorno successivo",
|
|
||||||
"today": "Oggi",
|
|
||||||
"show": "Mostra",
|
|
||||||
"history": "Cronologia",
|
|
||||||
"view_history": "Visualizza cronologia",
|
|
||||||
"export": "Esporta",
|
|
||||||
"export_accessible_flag": "&Esporta",
|
|
||||||
"export_entries": "Esporta voci",
|
|
||||||
"export_complete": "Esportazione completata",
|
|
||||||
"export_failed": "Esportazione fallita",
|
|
||||||
"backup": "Backup",
|
|
||||||
"backup_complete": "Backup completato",
|
|
||||||
"backup_failed": "Backup fallito",
|
|
||||||
"quit": "Esci",
|
|
||||||
"help": "Aiuto",
|
|
||||||
"saved": "Salvato",
|
|
||||||
"saved_to": "Salvato in",
|
|
||||||
"documentation": "Documentazione",
|
|
||||||
"couldnt_open": "Impossibile aprire",
|
|
||||||
"report_a_bug": "Segnala un bug",
|
|
||||||
"version": "Versione",
|
|
||||||
"navigate": "Naviga",
|
|
||||||
"current": "corrente",
|
|
||||||
"selected": "selezionato",
|
|
||||||
"find_on_page": "Trova nella pagina",
|
|
||||||
"find_next": "Trova successivo",
|
|
||||||
"find_previous": "Trova precedente",
|
|
||||||
"find_bar_type_to_search": "Digita per cercare",
|
|
||||||
"find_bar_match_case": "Distingui maiuscole/minuscole",
|
|
||||||
"history_dialog_preview": "Anteprima",
|
|
||||||
"history_dialog_diff": "Differenze",
|
|
||||||
"history_dialog_revert_to_selected": "Ripristina alla versione selezionata",
|
|
||||||
"history_dialog_revert_failed": "Ripristino fallito",
|
|
||||||
"key_prompt_enter_key": "Inserisci la chiave",
|
|
||||||
"lock_overlay_locked_due_to_inactivity": "Bloccato per inattività",
|
|
||||||
"lock_overlay_unlock": "Sblocca",
|
|
||||||
"main_window_ready": "Pronto",
|
|
||||||
"main_window_save_a_version": "Salva una versione",
|
|
||||||
"main_window_settings_accessible_flag": "Impo&stazioni",
|
|
||||||
"set_an_encryption_key": "Imposta una chiave di crittografia",
|
|
||||||
"set_an_encryption_key_explanation": "Bouquin cripta i tuoi dati.\n\nCrea una passphrase sicura per criptare il blocco note.\n\nPuoi sempre cambiarla in seguito!",
|
|
||||||
"unlock_encrypted_notebook": "Sblocca il blocco note criptato",
|
|
||||||
"unlock_encrypted_notebook_explanation": "Inserisci la chiave per sbloccare il blocco note",
|
|
||||||
"open_in_new_tab": "Apri in una nuova scheda",
|
|
||||||
"autosave": "salvataggio automatico",
|
|
||||||
"unchecked_checkbox_items_moved_to_next_day": "Le caselle non spuntate sono state spostate al giorno successivo",
|
|
||||||
"move_yesterdays_unchecked_todos_to_today_on_startup": "Sposta i TODO non completati di ieri a oggi all'avvio",
|
|
||||||
"insert_images": "Inserisci immagini",
|
|
||||||
"images": "Immagini",
|
|
||||||
"reopen_failed": "Riapertura fallita",
|
|
||||||
"unlock_failed": "Sblocco fallito",
|
|
||||||
"could_not_unlock_database_at_new_path": "Impossibile sbloccare il database nel nuovo percorso.",
|
|
||||||
"unencrypted_export": "Esportazione non criptata",
|
|
||||||
"unencrypted_export_warning": "L'esportazione del database sarà non criptata!\nVuoi davvero continuare?\nSe desideri un backup criptato, scegli Backup invece di Esporta.",
|
|
||||||
"unrecognised_extension": "Estensione non riconosciuta!",
|
|
||||||
"backup_encrypted_notebook": "Backup del blocco note criptato",
|
|
||||||
"enter_a_name_for_this_version": "Inserisci un nome per questa versione",
|
|
||||||
"new_version_i_saved_at": "Nuova versione salvata il",
|
|
||||||
"save_key_warning": "Se non vuoi che ti venga richiesta la chiave di crittografia, seleziona questa opzione per ricordarla.\nATTENZIONE: la chiave viene salvata sul disco e potrebbe essere recuperabile se il disco fosse compromesso.",
|
|
||||||
"lock_screen_when_idle": "Blocca lo schermo quando inattivo",
|
|
||||||
"autolock_explanation": "Bouquin bloccherà automaticamente il blocco note dopo questo intervallo di tempo, dopodiché sarà necessario reinserire la chiave per sbloccarlo.\nImposta a 0 (mai) per non bloccarlo mai.",
|
|
||||||
"search_for_notes_here": "Cerca note qui",
|
|
||||||
"toolbar_format": "Formato",
|
|
||||||
"toolbar_bold": "Grassetto",
|
|
||||||
"toolbar_italic": "Corsivo",
|
|
||||||
"toolbar_strikethrough": "Barrato",
|
|
||||||
"toolbar_normal_paragraph_text": "Testo normale",
|
|
||||||
"toolbar_bulleted_list": "Elenco puntato",
|
|
||||||
"toolbar_numbered_list": "Elenco numerato",
|
|
||||||
"toolbar_code_block": "Blocco di codice",
|
|
||||||
"toolbar_heading": "Titolo",
|
|
||||||
"toolbar_toggle_checkboxes": "Attiva/disattiva caselle di controllo"
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,6 @@ from PySide6.QtGui import (
|
||||||
QTextCursor,
|
QTextCursor,
|
||||||
QTextDocument,
|
QTextDocument,
|
||||||
QTextFormat,
|
QTextFormat,
|
||||||
QTextBlockFormat,
|
|
||||||
QTextImageFormat,
|
QTextImageFormat,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QRect, QTimer
|
from PySide6.QtCore import Qt, QRect, QTimer
|
||||||
|
|
@ -44,8 +43,6 @@ class MarkdownEditor(QTextEdit):
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
self.setFont(font)
|
self.setFont(font)
|
||||||
|
|
||||||
self._apply_line_spacing() # 1.25× initial spacing
|
|
||||||
|
|
||||||
# Checkbox characters (Unicode for display, markdown for storage)
|
# Checkbox characters (Unicode for display, markdown for storage)
|
||||||
self._CHECK_UNCHECKED_DISPLAY = "☐"
|
self._CHECK_UNCHECKED_DISPLAY = "☐"
|
||||||
self._CHECK_CHECKED_DISPLAY = "☑"
|
self._CHECK_CHECKED_DISPLAY = "☑"
|
||||||
|
|
@ -76,8 +73,6 @@ class MarkdownEditor(QTextEdit):
|
||||||
# reattach the highlighter to the new document
|
# reattach the highlighter to the new document
|
||||||
if hasattr(self, "highlighter") and self.highlighter:
|
if hasattr(self, "highlighter") and self.highlighter:
|
||||||
self.highlighter.setDocument(self.document())
|
self.highlighter.setDocument(self.document())
|
||||||
self._apply_line_spacing()
|
|
||||||
self._apply_code_block_spacing()
|
|
||||||
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
|
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
|
||||||
|
|
||||||
def showEvent(self, e):
|
def showEvent(self, e):
|
||||||
|
|
@ -145,10 +140,9 @@ class MarkdownEditor(QTextEdit):
|
||||||
def _update_code_block_row_backgrounds(self):
|
def _update_code_block_row_backgrounds(self):
|
||||||
"""Paint a full-width background for each line that is in a fenced code block."""
|
"""Paint a full-width background for each line that is in a fenced code block."""
|
||||||
doc = self.document()
|
doc = self.document()
|
||||||
if doc is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
sels = []
|
sels = []
|
||||||
|
|
||||||
|
# Use the same bg color as the highlighter's code block
|
||||||
bg_brush = self.highlighter.code_block_format.background()
|
bg_brush = self.highlighter.code_block_format.background()
|
||||||
|
|
||||||
inside = False
|
inside = False
|
||||||
|
|
@ -164,12 +158,16 @@ class MarkdownEditor(QTextEdit):
|
||||||
fmt = QTextCharFormat()
|
fmt = QTextCharFormat()
|
||||||
fmt.setBackground(bg_brush)
|
fmt.setBackground(bg_brush)
|
||||||
fmt.setProperty(QTextFormat.FullWidthSelection, True)
|
fmt.setProperty(QTextFormat.FullWidthSelection, True)
|
||||||
|
# mark so we can merge with other selections safely
|
||||||
fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg")
|
fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg")
|
||||||
sel.format = fmt
|
sel.format = fmt
|
||||||
|
|
||||||
cur = QTextCursor(doc)
|
cur = QTextCursor(doc)
|
||||||
cur.setPosition(block.position())
|
cur.setPosition(
|
||||||
|
block.position()
|
||||||
|
) # collapsed cursor = whole line when FullWidthSelection
|
||||||
sel.cursor = cur
|
sel.cursor = cur
|
||||||
|
|
||||||
sels.append(sel)
|
sels.append(sel)
|
||||||
|
|
||||||
if is_fence:
|
if is_fence:
|
||||||
|
|
@ -184,60 +182,6 @@ class MarkdownEditor(QTextEdit):
|
||||||
]
|
]
|
||||||
self.setExtraSelections(others + sels)
|
self.setExtraSelections(others + sels)
|
||||||
|
|
||||||
def _apply_line_spacing(self, height: float = 125.0):
|
|
||||||
"""Apply proportional line spacing to the whole document."""
|
|
||||||
doc = self.document()
|
|
||||||
if doc is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
cursor = QTextCursor(doc)
|
|
||||||
cursor.beginEditBlock()
|
|
||||||
cursor.select(QTextCursor.Document)
|
|
||||||
|
|
||||||
fmt = QTextBlockFormat()
|
|
||||||
fmt.setLineHeight(
|
|
||||||
height, # 125.0 = 1.25×
|
|
||||||
QTextBlockFormat.LineHeightTypes.ProportionalHeight.value,
|
|
||||||
)
|
|
||||||
cursor.mergeBlockFormat(fmt)
|
|
||||||
cursor.endEditBlock()
|
|
||||||
|
|
||||||
def _apply_code_block_spacing(self):
|
|
||||||
"""
|
|
||||||
Make all fenced code-block lines (including ``` fences) single-spaced.
|
|
||||||
Call this AFTER _apply_line_spacing().
|
|
||||||
"""
|
|
||||||
doc = self.document()
|
|
||||||
if doc is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
cursor = QTextCursor(doc)
|
|
||||||
cursor.beginEditBlock()
|
|
||||||
|
|
||||||
inside = False
|
|
||||||
block = doc.begin()
|
|
||||||
while block.isValid():
|
|
||||||
text = block.text()
|
|
||||||
stripped = text.strip()
|
|
||||||
is_fence = stripped.startswith("```")
|
|
||||||
is_code_line = is_fence or inside
|
|
||||||
|
|
||||||
if is_code_line:
|
|
||||||
fmt = block.blockFormat()
|
|
||||||
fmt.setLineHeight(
|
|
||||||
0.0,
|
|
||||||
QTextBlockFormat.LineHeightTypes.SingleHeight.value,
|
|
||||||
)
|
|
||||||
cursor.setPosition(block.position())
|
|
||||||
cursor.setBlockFormat(fmt)
|
|
||||||
|
|
||||||
if is_fence:
|
|
||||||
inside = not inside
|
|
||||||
|
|
||||||
block = block.next()
|
|
||||||
|
|
||||||
cursor.endEditBlock()
|
|
||||||
|
|
||||||
def to_markdown(self) -> str:
|
def to_markdown(self) -> str:
|
||||||
"""Export current content as markdown."""
|
"""Export current content as markdown."""
|
||||||
# First, extract any embedded images and convert to markdown
|
# First, extract any embedded images and convert to markdown
|
||||||
|
|
@ -311,9 +255,6 @@ class MarkdownEditor(QTextEdit):
|
||||||
finally:
|
finally:
|
||||||
self._updating = False
|
self._updating = False
|
||||||
|
|
||||||
self._apply_line_spacing()
|
|
||||||
self._apply_code_block_spacing()
|
|
||||||
|
|
||||||
# Render any embedded images
|
# Render any embedded images
|
||||||
self._render_images()
|
self._render_images()
|
||||||
|
|
||||||
|
|
@ -511,33 +452,9 @@ class MarkdownEditor(QTextEdit):
|
||||||
|
|
||||||
block_state = current_block.userState()
|
block_state = current_block.userState()
|
||||||
|
|
||||||
stripped = current_line.strip()
|
# If current line is opening code fence, or we're inside a code block
|
||||||
is_fence_line = stripped.startswith("```")
|
if current_line.strip().startswith("```") or block_state == 1:
|
||||||
|
# Just insert a regular newline - the highlighter will format it as code
|
||||||
if is_fence_line:
|
|
||||||
# Work out if this fence is closing (inside block before it)
|
|
||||||
inside_before = self._is_inside_code_block(current_block.previous())
|
|
||||||
|
|
||||||
# Insert the newline as usual
|
|
||||||
super().keyPressEvent(event)
|
|
||||||
|
|
||||||
if inside_before:
|
|
||||||
# We were on the *closing* fence; the new line is outside the block.
|
|
||||||
# Give that new block normal 1.25× spacing.
|
|
||||||
new_block = self.textCursor().block()
|
|
||||||
fmt = new_block.blockFormat()
|
|
||||||
fmt.setLineHeight(
|
|
||||||
125.0,
|
|
||||||
QTextBlockFormat.LineHeightTypes.ProportionalHeight.value,
|
|
||||||
)
|
|
||||||
cur2 = self.textCursor()
|
|
||||||
cur2.setBlockFormat(fmt)
|
|
||||||
self.setTextCursor(cur2)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
# Inside a code block (but not on a fence): newline stays code-style
|
|
||||||
if block_state == 1:
|
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -729,9 +646,6 @@ class MarkdownEditor(QTextEdit):
|
||||||
c.insertText(f"```\n{selected.rstrip()}\n```\n")
|
c.insertText(f"```\n{selected.rstrip()}\n```\n")
|
||||||
if hasattr(self, "_update_code_block_row_backgrounds"):
|
if hasattr(self, "_update_code_block_row_backgrounds"):
|
||||||
self._update_code_block_row_backgrounds()
|
self._update_code_block_row_backgrounds()
|
||||||
# tighten spacing for the new code block
|
|
||||||
self._apply_code_block_spacing()
|
|
||||||
|
|
||||||
self.setFocus()
|
self.setFocus()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -796,10 +710,6 @@ class MarkdownEditor(QTextEdit):
|
||||||
|
|
||||||
if hasattr(self, "_update_code_block_row_backgrounds"):
|
if hasattr(self, "_update_code_block_row_backgrounds"):
|
||||||
self._update_code_block_row_backgrounds()
|
self._update_code_block_row_backgrounds()
|
||||||
|
|
||||||
# tighten spacing for the new code block
|
|
||||||
self._apply_code_block_spacing()
|
|
||||||
|
|
||||||
self.setFocus()
|
self.setFocus()
|
||||||
|
|
||||||
def apply_heading(self, size: int):
|
def apply_heading(self, size: int):
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,3 @@ def test_load_strings_uses_system_locale_and_fallback():
|
||||||
# pass a bogus locale to trigger fallback-to-default
|
# pass a bogus locale to trigger fallback-to-default
|
||||||
strings.load_strings("zz")
|
strings.load_strings("zz")
|
||||||
assert strings._("next") # key exists in base translations
|
assert strings._("next") # key exists in base translations
|
||||||
|
|
||||||
|
|
||||||
def test_load_strings_french():
|
|
||||||
strings.load_strings("fr")
|
|
||||||
assert strings._("today") == "Aujourd'hui" # translation exists in French
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_strings_italian():
|
|
||||||
strings.load_strings("it")
|
|
||||||
assert strings._("today") == "Oggi" # translation exists in Italian
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue