Compare commits

...
2 Commits
7 changed files with 36 additions and 40 deletions

No files matched your search

+2
View File
@@ -49,6 +49,8 @@ jobs:
run: |
if [ "$RUNNER_OS" == "Windows" ]; then SEP=";"; else SEP=":"; fi
pyinstaller --onefile \
--hidden-import pkg_resources \
--hidden-import setuptools \
--collect-all onionshare_cli \
--collect-all flask_socketio \
--collect-all engineio \
+9 -6
View File
@@ -1,8 +1,11 @@
# VoxVera v0.2.4
# VoxVera v0.2.11
## New Features
- **Optional File Attachment:** Users can now attach a file (e.g., a .torrent, image, or document) to their flyers. This file is packaged into the Tor-hosted site and made available via a "Download Attachment" button on the landing page.
- **Interactive Attachment Selection:** Added a prompt during `voxvera init` to easily specify the path to an attachment.
## Changes and Improvements
## Localization
- Added new tokens for attachment handling across all 14+ supported languages.
### Logic and UI Fixes
- **Attachment overriding URL:** Modified the core HTML template and config generation so that a provided file attachment fully replaces the external URL in the main content area and QR codes, eliminating redundancy. The "Download Attachment" button at the bottom was removed, keeping the main interface clean and focusing on the attachment directly via the primary URL slot.
- **Interactive Prompt:** The interactive CLI now treats the URL and attachment path as an "either/or" choice conditionally, improving clarity.
### Stability and Bug Fixes
- **Nano Terminal Crash:** Fixed an issue where the `nano` editor would crash with "Code: 11" when launched from the interactive interface within a PyInstaller binary. The application now properly inherits standard input/output streams for standard terminal editors.
- **ModuleNotFoundError Fix:** Added explicit `pkg_resources` and `setuptools` hidden imports to the PyInstaller build configuration, permanently fixing an error where `onionshare-cli` would fail to initialize in the standalone binary with a `ModuleNotFoundError: No module named 'pkg_resources'`.
+1 -1
View File
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
[project]
name = "voxvera"
version = "0.2.9"
version = "0.2.11"
description = "VoxVera CLI utilities"
readme = "README.md"
authors = [{name = "VoxVera"}]
+1
View File
@@ -51,6 +51,7 @@ def test_export_import_roundtrip(tmp_path, monkeypatch):
shutil.copytree(REPO_ROOT / "voxvera" / "src", tmp_path / "src")
shutil.copytree(REPO_ROOT / "voxvera" / "locales", tmp_path / "locales")
monkeypatch.setattr(cli, "ROOT", tmp_path)
monkeypatch.setattr(cli, "DATA_DIR", tmp_path)
monkeypatch.setattr(cli, "_src_res", lambda *p: tmp_path / "src" / Path(*p))
monkeypatch.setattr(cli, "_locale_res", lambda *p: tmp_path / "locales" / Path(*p))
+1 -1
View File
@@ -1,7 +1,7 @@
import sys
from pathlib import Path
__version__ = "0.2.9"
__version__ = "0.2.11"
# Add the vendor directory to sys.path. We prepend it to ensure our bundled
# dependencies take priority over potentially incompatible system packages
+22 -29
View File
@@ -180,7 +180,7 @@ def _open_editor_terminal(initial: str) -> str:
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
fh.write(initial or "")
subprocess.call([editor, path])
subprocess.run([editor, path], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
with open(path, "r", encoding="utf-8") as fh:
return fh.read()
except Exception as e:
@@ -375,11 +375,18 @@ def interactive_update(config_path: str):
console.rule(t("cli.init_links"))
link_qs = [
{
"type": "input",
"message": t("cli.attachment_path_label"),
"name": "attachment_path",
"default": data.get("attachment_path", ""),
},
{
"type": "input",
"message": t("cli.url_label"),
"name": "url",
"default": data.get("url", "https://voxvera.org/"),
"default": data.get("url", "https://voxvera.org/") if not data.get("url", "").startswith("download/") else "",
"when": lambda result: not result.get("attachment_path", "").strip(),
"transformer": _len_transform(200),
"validate": _len_validator(200),
},
@@ -391,21 +398,6 @@ def interactive_update(config_path: str):
"transformer": _len_transform(120),
"validate": _len_validator(120),
},
{
"type": "input",
"message": t("cli.binary_message_label"),
"name": "binary_message",
"default": data.get("binary_message", ""),
"transformer": _len_transform(120),
"validate": _len_validator(120),
},
{
"type": "input",
"message": t("cli.attachment_path_label"),
"name": "attachment_path",
"default": data.get("attachment_path", ""),
},
]
data.update(prompt(link_qs))
@@ -542,18 +534,19 @@ def build_assets(
val_str = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', val_str)
html = html.replace(f"{{{{{key}}}}}", val_str)
# Handle optional file attachment
# Handle optional file attachment logic
attachment_path = data.get("attachment_path")
attachment_url = ""
attachment_display = "none"
if attachment_path and os.path.isfile(attachment_path):
att_filename = os.path.basename(attachment_path)
attachment_url = f"download/{att_filename}"
attachment_display = "inline-block"
html = html.replace("{{attachment_url}}", attachment_url)
html = html.replace("{{attachment_display}}", attachment_display)
# We don't replace the URL here if it's already an onion link (e.g. during serve rebuild)
if not data.get("url", "").startswith("http://") or ".onion" not in data.get("url", ""):
data["url"] = f"download/{att_filename}"
# Must write back to config so generate_qr uses the new URL
save_config(data, config_path)
# Also update the HTML we just replaced if we changed it
html = html.replace("{{url}}", data["url"])
folder_name = data["folder_name"]
dest = DATA_DIR / "host" / folder_name
os.makedirs(dest, exist_ok=True)
@@ -803,9 +796,9 @@ def get_export_dir() -> Path:
def export_site(folder_name: str, output_path: str = None):
"""Export a site folder and its keys into a zip archive."""
source_dir = ROOT / "host" / folder_name
source_dir = DATA_DIR / "host" / folder_name
if not source_dir.exists():
print(f"Error: Site folder '{folder_name}' not found in {ROOT / 'host'}")
print(f"Error: Site folder '{folder_name}' not found in {DATA_DIR / 'host'}")
return
if not output_path:
@@ -861,7 +854,7 @@ def import_site(zip_path: str):
print("Error: Invalid config.json. Missing 'folder_name'.")
return
dest_dir = ROOT / "host" / folder_name
dest_dir = DATA_DIR / "host" / folder_name
if dest_dir.exists():
confirm = inquirer.confirm(message=f"Site '{folder_name}' already exists. Overwrite?").execute()
if not confirm:
-3
View File
@@ -579,9 +579,6 @@
<a id="download-link" href="download/voxvera-source.zip" download>
<button class="download-button" data-i18n="web.download_button">{{t_web_download_button}}</button>
</a>
<a id="attachment-link" href="{{attachment_url}}" download style="display: {{attachment_display}};">
<button class="download-button" data-i18n="web.download_attachment">{{t_web_download_attachment}}</button>
</a>
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
</div>
</body>