mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 17:50:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6ff21c069 | ||
|
|
1fcb86ac8b | ||
|
|
2b29d71ac5 | ||
|
|
46360ca6ea | ||
|
|
03b1c51e27 | ||
|
|
fc628fb9a0 | ||
|
|
91391b1dd7 | ||
|
|
d61a7b4c92 | ||
|
|
2dabd93809 | ||
|
|
ee5024678d |
No files matched your search
@@ -69,7 +69,7 @@ jobs:
|
||||
--add-data "voxvera/locales${SEP}voxvera/locales" \
|
||||
--add-data "voxvera/src${SEP}voxvera/src" \
|
||||
--add-data "voxvera/templates${SEP}voxvera/templates" \
|
||||
--add-data "voxvera/resources${SEP}voxvera/resources" \
|
||||
--add-data "voxvera/resources/tor${SEP}voxvera/resources/tor" \
|
||||
--add-data "requirements.txt${SEP}." \
|
||||
--name "${{ matrix.artifact_name }}" \
|
||||
voxvera/cli.py
|
||||
|
||||
+7
-4
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,14 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Recommended local check**: Run `bash scripts/check.sh` before opening a PR. It mirrors the main lint/test/build checks in one command.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@ pip install .
|
||||
voxvera vendorize # Optional: pull dependencies locally for offline use
|
||||
```
|
||||
|
||||
### 5. Local Validation
|
||||
Run the full local validation sweep with:
|
||||
```bash
|
||||
bash scripts/check.sh
|
||||
```
|
||||
This runs linting, Python tests, Electron smoke tests, dependency checks, and the docs/site rebuild steps used during development.
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
@@ -53,6 +53,7 @@ Generates an architecture-specific binary (e.g., `voxvera-linux-x86_64`) in `vox
|
||||
```bash
|
||||
./scripts/build_binaries.sh
|
||||
```
|
||||
This directory is local build output only. Do not treat old files in `voxvera/resources/bin/` as canonical release history; the GitHub Release artifacts are the publish surface.
|
||||
|
||||
### AppImage (Linux)
|
||||
Wraps the standard binary into a self-contained AppImage.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
+13
-4
@@ -15,9 +15,12 @@ echo "Starting E2E verification at $(date)"
|
||||
if [ -n "${VOXVERA_BIN:-}" ] && [ -f "$VOXVERA_BIN" ]; then
|
||||
voxvera_cmd="$VOXVERA_BIN"
|
||||
echo "Using provided VoxVera binary: $voxvera_cmd" >&2
|
||||
elif command -v voxvera >/dev/null 2>&1; then
|
||||
voxvera_cmd="$(command -v voxvera)"
|
||||
echo "Using existing VoxVera on PATH: $voxvera_cmd" >&2
|
||||
else
|
||||
echo "Installing VoxVera in editable mode..."
|
||||
python3 -m pip install --break-system-packages -e ".[e2e]"
|
||||
python3 -m pip install --break-system-packages --no-build-isolation -e ".[e2e]"
|
||||
voxvera_cmd="$(command -v voxvera || echo "$HOME/.local/bin/voxvera")"
|
||||
fi
|
||||
|
||||
@@ -30,16 +33,22 @@ DemoUser
|
||||
voxvera
|
||||
EOI
|
||||
|
||||
config_path="$HOME/host/voxvera/config.json"
|
||||
if [ ! -f "$config_path" ]; then
|
||||
echo "CRITICAL: Expected config file not found after init: $config_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building demo flyer..."
|
||||
"$voxvera_cmd" --lang en build
|
||||
"$voxvera_cmd" --lang en --config "$config_path" build
|
||||
|
||||
# Verify build output exists in host/
|
||||
folder_name="voxvera"
|
||||
host_dir="voxvera/host/$folder_name"
|
||||
host_dir="$HOME/host/$folder_name"
|
||||
|
||||
if [ ! -d "$host_dir" ]; then
|
||||
echo "CRITICAL: Host directory $host_dir not found!" >&2
|
||||
ls -R voxvera/host/ || true
|
||||
ls -R "$HOME/host/" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
Vendored
+6
-4
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
|
||||
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
|
||||
|
||||
### 2. The Landing Page
|
||||
The main landing page (`site/index.html`) must be synchronized to include your new language.
|
||||
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
|
||||
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
|
||||
The main landing page is generated from the shared template and locale files.
|
||||
- Add your language in `voxvera/locales/{lang_code}.json`.
|
||||
- Do **not** edit `site/index.html` directly; it is generated output.
|
||||
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
|
||||
|
||||
### 3. Documentation
|
||||
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
|
||||
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
|
||||
- `voxvera --lang {lang_code} check` (Verify CLI translation)
|
||||
- `voxvera build-docs` (Generate localized manuals)
|
||||
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
|
||||
- Open `site/index.html` in a browser and test the language switcher.
|
||||
- Open the generated `site/index.html` in a browser and test the language switcher.
|
||||
|
||||
## Code Contributions
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div><label>URL Message <input type="text" id="url_message"></label></div>
|
||||
<div><label>URL <input type="text" id="url"></label></div>
|
||||
<div><label>Tear-off link <input type="text" id="tear_off_link"></label></div>
|
||||
<div><label>Binary message <input type="text" id="binary_message"></label></div>
|
||||
<div><label>Footer message <input type="text" id="footer_message"></label></div>
|
||||
<button type="button" id="quickstart">Generate & Serve</button>
|
||||
</form>
|
||||
<p id="onion-address"></p>
|
||||
@@ -42,7 +42,7 @@
|
||||
document.addEventListener('DOMContentLoaded', load);
|
||||
|
||||
document.getElementById('quickstart').addEventListener('click', async () => {
|
||||
const ids = ['name','folder_name','title','subtitle','headline','content','url_message','url','tear_off_link','binary_message'];
|
||||
const ids = ['name','folder_name','title','subtitle','headline','content','url_message','url','tear_off_link','footer_message'];
|
||||
const cfg = {};
|
||||
ids.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
|
||||
+16
-6
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const which = require('which');
|
||||
const fs = require('fs');
|
||||
const { launchTor } = require('./tor.js');
|
||||
const { extractOnionUrl } = require('./runtime-utils');
|
||||
|
||||
let mainWindow;
|
||||
let onionProc;
|
||||
@@ -74,8 +75,12 @@ async function runServe (retry = false) {
|
||||
|
||||
onionProc.stdout.on('data', buf => {
|
||||
const line = buf.toString();
|
||||
const m = line.match(/OnionShare is hosting at (http.*\.onion)/);
|
||||
if (m) { gotURL = true; clearTimeout(softTimeout); }
|
||||
const onionUrl = extractOnionUrl(line);
|
||||
if (onionUrl) {
|
||||
gotURL = true;
|
||||
clearTimeout(softTimeout);
|
||||
mainWindow.webContents.send('onion-url', onionUrl);
|
||||
}
|
||||
mainWindow.webContents.send('log', { text: line });
|
||||
});
|
||||
|
||||
@@ -105,7 +110,12 @@ function getConfigPath() {
|
||||
ipcMain.handle('load-config', async () => {
|
||||
const p = getConfigPath();
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
const config = JSON.parse(raw);
|
||||
if (config.footer_message === undefined && config.binary_message !== undefined) {
|
||||
config.footer_message = config.binary_message;
|
||||
}
|
||||
delete config.binary_message;
|
||||
return config;
|
||||
});
|
||||
|
||||
ipcMain.handle('run-quickstart', async (_, config) => {
|
||||
@@ -134,9 +144,9 @@ ipcMain.handle('run-quickstart', async (_, config) => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('log', { text: line, isError: false });
|
||||
}
|
||||
const m = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
|
||||
if (m && mainWindow) {
|
||||
mainWindow.webContents.send('onion-url', m[1]);
|
||||
const onionUrl = extractOnionUrl(line);
|
||||
if (onionUrl && mainWindow) {
|
||||
mainWindow.webContents.send('onion-url', onionUrl);
|
||||
}
|
||||
});
|
||||
proc.stderr.on('data', data => {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"lint": "echo 'lint pass'"
|
||||
"lint": "echo 'lint pass'",
|
||||
"test": "node --test runtime-utils.test.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^29.0.0",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
function extractOnionUrl(line) {
|
||||
const match = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i)
|
||||
|| line.match(/OnionShare is hosting at\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function platformDirName(platform = process.platform) {
|
||||
if (platform === 'win32') return 'win';
|
||||
if (platform === 'darwin') return 'mac';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extractOnionUrl,
|
||||
platformDirName,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { extractOnionUrl, platformDirName } = require('./runtime-utils');
|
||||
|
||||
test('extractOnionUrl handles current CLI output', () => {
|
||||
assert.equal(
|
||||
extractOnionUrl('[voxvera] Onion URL: http://test123.onion'),
|
||||
'http://test123.onion'
|
||||
);
|
||||
});
|
||||
|
||||
test('extractOnionUrl handles legacy OnionShare output', () => {
|
||||
assert.equal(
|
||||
extractOnionUrl('OnionShare is hosting at http://legacy456.onion'),
|
||||
'http://legacy456.onion'
|
||||
);
|
||||
});
|
||||
|
||||
test('extractOnionUrl returns null when no onion URL is present', () => {
|
||||
assert.equal(extractOnionUrl('still bootstrapping'), null);
|
||||
});
|
||||
|
||||
test('platformDirName maps bundled resource folders correctly', () => {
|
||||
assert.equal(platformDirName('linux'), 'linux');
|
||||
assert.equal(platformDirName('darwin'), 'mac');
|
||||
assert.equal(platformDirName('win32'), 'win');
|
||||
});
|
||||
+4
-2
@@ -3,14 +3,16 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const which = require('which');
|
||||
const getPort = (...args) => import('get-port').then(m => m.default(...args));
|
||||
const { platformDirName } = require('./runtime-utils');
|
||||
|
||||
async function launchTor() {
|
||||
const socks = await getPort();
|
||||
const control = await getPort();
|
||||
const torBase = path.join(__dirname, '..', '..', 'voxvera', 'resources', 'tor');
|
||||
let exe = path.join(torBase, process.platform,
|
||||
const platformDir = platformDirName(process.platform);
|
||||
let exe = path.join(torBase, platformDir,
|
||||
process.platform === 'win32' ? 'tor.exe' : 'tor');
|
||||
let obfs4 = path.join(torBase, process.platform,
|
||||
let obfs4 = path.join(torBase, platformDir,
|
||||
process.platform === 'win32' ? 'obfs4proxy.exe' : 'obfs4proxy');
|
||||
|
||||
const missing = p => {
|
||||
|
||||
+2
-2
@@ -7,6 +7,6 @@
|
||||
"content": "The Android Open Source Project (AOSP) represents the essence of what Android was originally intended to be: an open, transparent, and adaptable platform. It strips away the layers of proprietary software and corporate oversight that have become synonymous with mainstream Android distributions. With AOSP, you gain access to a raw, unfiltered version of Android, allowing you to truly make your device your own.<br><br>For developers, AOSP offers a playground of possibilities, where the only limits are your skills and imagination. It serves as the foundation for countless custom ROMs, each tailored to specific needs, whether for enhanced performance, privacy, or unique features that mainstream versions lack. Privacy enthusiasts will appreciate the absence of pre-installed bloatware and tracking services that often come wit stock Android, providing a cleaner, more secure user experience.<br><br>AOSP isn't just for developers or tech enthusiasts; it's for anyone who values digital freedom. It allows you to reclaim control over your device, tailoring it to your needs without the restrictions of carriers or manufacturers. Discover how AOSP can transform your mobile experience. Start your journey towards a more open, customizable future today.",
|
||||
"url_message": "Visit the link below to learn more.",
|
||||
"url": "https://source.android.com",
|
||||
"tear_off_link": "http://aosp.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "What is Bitcoin? It's more than just digital money; it's a revolutionary technology designed to fix some of the world's most pressing problems. Bitcoin operates on a decentralized network, free from the control of any government or central authority. This ensures that no one can manipulate its supply or value, making it a sound form of money that protects your wealth from inflation.<br><br>Our current financial system is built on flawed incentives—encouraging debt, dependency, and centralization of power. Bitcoin changes that by creating a system where honesty, transparency, and individual responsibility are rewarded. In the Bitcoin network, there is no room for corruption or manipulation because the rules are enforced by mathematics, not by people.<br><br>Bitcoin aligns incentives in a way that promotes long-term thinking, savings, and innovation. It empowers individuals to take control of their financial future, fostering a global community where value can be transferred freely and securely without intermediaries. This is why we need Bitcoin—because it creates a world where incentives are properly aligned, leading to a more fair and prosperous society for all.<br><br>Take the step to learn more about Bitcoin and how it can transform not just your financial life, but the world. Tear off a link to the left and explore this groundbreaking technology. Join the movement towards a better future.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://bitcoin.org/en",
|
||||
"tear_off_link": "http://bitcoin.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+1
-1
@@ -8,5 +8,5 @@
|
||||
"url_message": "",
|
||||
"url": "",
|
||||
"tear_off_link": "",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "content",
|
||||
"url_message": "url message",
|
||||
"url": "url",
|
||||
"tear_off_link": "http://example-folder_name.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+3
-3
@@ -7,6 +7,6 @@
|
||||
"content": "Who is Jesus? He is more than just a historical figure; He is the Son of God, the Savior of the world. Jesus Himself said, 'I am the way, the truth, and the life. No one comes to the Father except through Me' (John 14:6). He came to bridge the gap between God and humanity, offering us a path to forgiveness, peace, and eternal life.<br><br>We all face brokenness in our lives—whether it's in our relationships, our hearts, or our world. Jesus came to heal that brokenness. He lived a perfect life, died on the cross for our sins, and rose again, defeating death and offering us the hope of eternal life with God.<br><br>Without Jesus, we are lost in our sin, separated from God. But through Him, we can be forgiven and restored. He offers us not just a better life now, but the promise of an eternal life with Him. This is why we need Him—because only in Jesus can we find true peace, purpose, and the assurance of eternal life.<br><br>Take the step to learn more about Jesus and the life-changing impact He can have on your life. Tear off a link to the left and explore His teachings. Find the hope you've been searching for.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "http://media.faithforthefamily.com/ra/godsoloved/english.mp3",
|
||||
"tear_off_link": "http://jesus.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "Why Meshtastic? In times of crisis, traditional communication networks can fail, leaving us isolated and vulnerable. Meshtastic provides a powerful solution—an alternative local communication network that operates independently of centralized systems. It’s designed for emergency situations, allowing you to stay connected when it matters most.<br><br>Meshtastic devices create a mesh network that can relay messages over long distances, even without internet or cellular coverage. This decentralized approach ensures that your communications are resilient and reliable, no matter the circumstances. But it’s not just about staying connected—Meshtastic also offers encryption, so your messages remain secure and private.<br><br>In a world where privacy is increasingly compromised, Meshtastic empowers you to take control of your communication. Whether you’re coordinating a local community response, staying in touch with loved ones during a disaster, or simply preparing for the unexpected, Meshtastic provides the tools you need to communicate securely and effectively.<br><br>Take the step to learn more about Meshtastic and how you can build your own emergency communication network. Tear off a link to the left and explore this innovative technology. Ensure you’re prepared for whatever comes your way.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://meshtastic.org",
|
||||
"tear_off_link": "http://meshtastic.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+3
-3
@@ -7,6 +7,6 @@
|
||||
"content": "What is Nostr? Unlike traditional social media platforms, Nostr is a protocol—a decentralized system that empowers users to connect and share without the constraints of a central authority. Nostr is censorship-resistant, meaning your voice can’t be silenced by corporations or governments. It’s designed to give you control over your online presence, free from the influence of algorithms or ads that prioritize engagement over meaningful connections.<br><br>On Nostr, there are no hidden agendas to keep you doomscrolling. You engage with content on your terms, not based on what an algorithm thinks will keep you online the longest. Nostr allows you to build genuine connections and participate in conversations that matter, all while maintaining your privacy and autonomy.<br><br>In a world where online speech is increasingly controlled and manipulated, Nostr offers a refreshing alternative. It’s not just a platform; it’s a movement towards true digital freedom, where your interactions are driven by your interests and values, not by what generates the most ad revenue.<br><br>Take the step to learn more about Nostr and how it can transform the way you connect online. Tear off a link the left and explore this groundbreaking protocol. Join a community that values freedom and authentic interaction.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://nostr.how/en/what-is-nostr",
|
||||
"tear_off_link": "http://nostr.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://voxvera.org/",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+24
@@ -182,6 +182,23 @@ if [ -d "$HOME/imports" ] && ls "$HOME/imports"/*.json &>/dev/null; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean generated files from existing site directories so rebuild-all
|
||||
# regenerates them from the current template. Preserves:
|
||||
# config.json — site content and settings
|
||||
# .onionshare-session — Tor domain identity key
|
||||
# server.pid — running process marker
|
||||
# onionshare.log — current log (may be in use)
|
||||
# Everything else is regenerated by build_assets().
|
||||
if [ -d "$HOME/host" ]; then
|
||||
for site_dir in "$HOME"/host/*/; do
|
||||
[ -f "${site_dir}config.json" ] || continue
|
||||
msg "Cleaning generated files in ${site_dir}..."
|
||||
rm -f "${site_dir}index.html" 2>/dev/null || true
|
||||
rm -f "${site_dir}qrcode-content.png" "${site_dir}qrcode-tear-offs.png" 2>/dev/null || true
|
||||
rm -rf "${site_dir}download" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
# --- Helper: build pip flags for this distro ---
|
||||
pip_flags() {
|
||||
local flags="--user --upgrade"
|
||||
@@ -364,6 +381,13 @@ for strategy in install_tarball install_shallow install_pipx install_pip_git ins
|
||||
fi
|
||||
# Clear bash's cached command path so the new binary is found immediately
|
||||
hash -r 2>/dev/null || true
|
||||
|
||||
# Rebuild all existing sites so they pick up template/CSS changes
|
||||
if command_exists voxvera; then
|
||||
msg "Rebuilding existing sites with updated template..."
|
||||
voxvera rebuild-all 2>/dev/null || true
|
||||
fi
|
||||
|
||||
msg "\nVoxVera installed/updated successfully. Run 'voxvera check' to verify."
|
||||
# Detect if the parent shell will still have a stale hash
|
||||
msg "If 'voxvera' is not found, run: hash -r (or open a new terminal)"
|
||||
|
||||
@@ -6,13 +6,20 @@ cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
|
||||
OUTPUT="voxvera/resources/bin/VoxVera-$ARCH.AppImage"
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY. Run scripts/build_binaries.sh first." >&2
|
||||
echo "Binary not found: $BINARY. Building it first..."
|
||||
bash scripts/build_binaries.sh
|
||||
fi
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary still not found after build: $BINARY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APPDIR=build/AppDir
|
||||
rm -rf "$APPDIR"
|
||||
mkdir -p "$APPDIR/usr/bin"
|
||||
cp "$BINARY" "$APPDIR/usr/bin/voxvera"
|
||||
chmod +x "$APPDIR/usr/bin/voxvera"
|
||||
@@ -46,7 +53,8 @@ chmod +x appimagetool
|
||||
echo "Running appimagetool..."
|
||||
export ARCH
|
||||
mkdir -p voxvera/resources/bin
|
||||
./appimagetool --appimage-extract-and-run "$APPDIR" voxvera/resources/bin/VoxVera-x86_64.AppImage
|
||||
rm -f "$OUTPUT"
|
||||
./appimagetool --appimage-extract-and-run "$APPDIR" "$OUTPUT"
|
||||
rm appimagetool
|
||||
|
||||
echo "AppImage created at voxvera/resources/bin/VoxVera-x86_64.AppImage"
|
||||
echo "AppImage created at $OUTPUT"
|
||||
+10
-3
@@ -16,13 +16,19 @@ fi
|
||||
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary not found: $BINARY." >&2
|
||||
echo "Binary not found: $BINARY. Building it first..."
|
||||
bash scripts/build_binaries.sh
|
||||
fi
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Binary still not found after build: $BINARY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
VERSION=$(grep "__version__" voxvera/__init__.py | cut -d '"' -f 2)
|
||||
PKG_DIR="build/voxvera_${VERSION}_${DEB_ARCH}"
|
||||
OUTPUT="voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
|
||||
|
||||
echo "Building .deb package for version $VERSION ($DEB_ARCH)..."
|
||||
|
||||
@@ -40,6 +46,7 @@ sed -i "s/^Architecture: .*/Architecture: $DEB_ARCH/" "$PKG_DIR/DEBIAN/control"
|
||||
|
||||
# Build the package
|
||||
mkdir -p voxvera/resources/bin
|
||||
dpkg-deb --build "$PKG_DIR" "voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
|
||||
rm -f "$OUTPUT"
|
||||
dpkg-deb --build "$PKG_DIR" "$OUTPUT"
|
||||
|
||||
echo "Debian package created at voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
|
||||
echo "Debian package created at $OUTPUT"
|
||||
@@ -1,5 +1,5 @@
|
||||
$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera.exe'
|
||||
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe'
|
||||
|
||||
$packageArgs = @{
|
||||
packageName = 'voxvera'
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
|
||||
|
||||
[project]
|
||||
name = "voxvera"
|
||||
version = "0.3.1"
|
||||
version = "0.3.6"
|
||||
description = "VoxVera CLI utilities"
|
||||
readme = "README.md"
|
||||
authors = [{name = "VoxVera"}]
|
||||
|
||||
@@ -13,9 +13,19 @@ if ! command -v pyinstaller &> /dev/null; then
|
||||
pip install pyinstaller --break-system-packages
|
||||
fi
|
||||
|
||||
ARTIFACT_DIR="voxvera/resources/bin"
|
||||
BUILD_BINARY_NAME="voxvera-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)"
|
||||
|
||||
# Clean previous builds
|
||||
rm -rf build/ dist_bin/
|
||||
mkdir -p voxvera/resources/bin
|
||||
python3 - <<'PY'
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
for path in (Path("build"), Path("dist_bin")):
|
||||
if path.exists():
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
PY
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
|
||||
echo "Building VoxVera binary for $(uname)..."
|
||||
|
||||
@@ -34,11 +44,11 @@ $PY_CMD --onefile \
|
||||
--add-data "voxvera/locales:voxvera/locales" \
|
||||
--add-data "voxvera/src:voxvera/src" \
|
||||
--add-data "voxvera/templates:voxvera/templates" \
|
||||
--add-data "voxvera/resources:voxvera/resources" \
|
||||
--add-data "voxvera/resources/tor:voxvera/resources/tor" \
|
||||
--add-data "requirements.txt:." \
|
||||
--name "$BINARY_NAME" \
|
||||
--distpath voxvera/resources/bin \
|
||||
--distpath "$ARTIFACT_DIR" \
|
||||
voxvera/cli.py
|
||||
|
||||
echo "Binary build complete. Located in voxvera/resources/bin/"
|
||||
ls -lh voxvera/resources/bin/
|
||||
echo "Binary build complete. Located in $ARTIFACT_DIR/"
|
||||
ls -lh "$ARTIFACT_DIR"/
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "==> flake8"
|
||||
python3 -m flake8 voxvera tests \
|
||||
--jobs=1 \
|
||||
--exclude=voxvera/vendor \
|
||||
--ignore=E501,W503,W293,E402,E306,E704,E261,F841,E302,F401,E401,F541
|
||||
|
||||
echo "==> pytest"
|
||||
python3 -m pytest tests/ -v
|
||||
|
||||
echo "==> electron smoke tests"
|
||||
(cd gui/electron && npm test)
|
||||
|
||||
echo "==> dependency check"
|
||||
python3 -m voxvera.cli --lang en check
|
||||
|
||||
echo "==> build docs"
|
||||
python3 -m voxvera.cli build-docs
|
||||
|
||||
echo "==> build site"
|
||||
python3 -m voxvera.cli build-site
|
||||
|
||||
echo "All local checks passed."
|
||||
+2
-2
@@ -7,6 +7,6 @@
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
"url": "https://github.com/PR0M3TH3AN/VoxVera",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"tear_off_link": "http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
+92
-43
@@ -40,12 +40,13 @@
|
||||
|
||||
[dir="rtl"] .qr-code-body {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code-url {
|
||||
text-align: right;
|
||||
margin-right: 0;
|
||||
margin-left: 10px;
|
||||
padding-right: 4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .message {
|
||||
@@ -94,6 +95,7 @@
|
||||
|
||||
.print-button, .download-button, .lang-select {
|
||||
padding: 10px 20px;
|
||||
min-height: 42px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
@@ -101,6 +103,12 @@
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
line-height: 1.2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
@@ -127,6 +135,7 @@
|
||||
.tear-off {
|
||||
display: flex;
|
||||
height: 1in; /* Fixed height for consistency */
|
||||
gap: 12px;
|
||||
padding: 5px;
|
||||
border: 1px dashed #000;
|
||||
font-size: 10px;
|
||||
@@ -139,9 +148,11 @@
|
||||
.tear-off-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-top: 1px;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.tear-off a {
|
||||
@@ -157,7 +168,6 @@
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 0px solid #000;
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -169,20 +179,21 @@
|
||||
|
||||
.content {
|
||||
width: calc(8.5in - 3.75in);
|
||||
padding: 20px;
|
||||
padding: 15px 20px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
page-break-inside: avoid;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -192,7 +203,10 @@
|
||||
|
||||
.distribute {
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 6px;
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -204,8 +218,8 @@
|
||||
}
|
||||
|
||||
.message {
|
||||
height: 6in; /* Fixed height to prevent footer push-out */
|
||||
margin-top: 10px;
|
||||
flex: 1;
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
@@ -236,8 +250,9 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
@@ -253,19 +268,21 @@
|
||||
.qr-code-url {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
max-width: 60%;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #000;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -294,6 +311,14 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .left-tear-offs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body, html {
|
||||
width: 8.5in;
|
||||
@@ -326,10 +351,20 @@
|
||||
|
||||
.button-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
#download-link {
|
||||
display: inline-flex;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#download-link .download-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
@@ -378,7 +413,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// VoxVera Multi-Language Engine
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f", "download_attachment": "Download Attachment"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.<br><br>\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.<br><br>\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.<br><br>\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\u0631 \u0631\u0633\u0627\u0644\u062a\u0643.", "name": "\u0645\u0646\u0634\u0648\u0631\u0627\u062a Vox Vera \u0627\u0644Line truncated
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f", "download_attachment": "\u062a\u0646\u0632\u064a\u0644 \u0627\u0644\u0645\u0631\u0641\u0642", "tor_browser_download_url": "https://www.torproject.org/ar/download/"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.<br><br>\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.<br><br>\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.<br><br>\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\Line truncated
|
||||
|
||||
function t(path, lang) {
|
||||
const parts = path.split('.');
|
||||
@@ -413,6 +448,19 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-href]').forEach(el => {
|
||||
const path = el.getAttribute('data-i18n-href');
|
||||
const href = t(path, lang);
|
||||
if (href) {
|
||||
el.href = href;
|
||||
}
|
||||
});
|
||||
const torBrowserHref = t('web.tor_browser_download_url', lang);
|
||||
if (torBrowserHref && torBrowserHref !== 'web.tor_browser_download_url') {
|
||||
document.querySelectorAll('.tor-browser-link').forEach(el => {
|
||||
el.href = torBrowserHref;
|
||||
});
|
||||
}
|
||||
document.getElementById('lang-selector').value = lang;
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
@@ -437,13 +485,13 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container ">
|
||||
<div class="left-tear-offs">
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -453,9 +501,9 @@
|
||||
<!-- Repeat for other tear-offs -->
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -464,9 +512,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -475,9 +523,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -486,9 +534,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -497,9 +545,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -508,9 +556,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -519,9 +567,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -530,9 +578,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -541,9 +589,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -562,6 +610,7 @@
|
||||
|
||||
<div class="qr-code-url">
|
||||
<span class="url-message" data-i18n="landing.url_message">Get VoxVera and start spreading your message.</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<a id="qr-link-2" href="https://github.com/PR0M3TH3AN/VoxVera" target="_blank">https://github.com/PR0M3TH3AN/VoxVera</a>
|
||||
</div>
|
||||
<img src="qrcode-content.png" alt="QR Code">
|
||||
@@ -570,7 +619,7 @@
|
||||
<hr>
|
||||
<div class="footer">
|
||||
<p class="credit"><span data-i18n="web.built_with">built with</span> <a href="https://github.com/PR0M3TH3AN/VoxVera">voxvera</a></p>
|
||||
<p class="binary" id="binary-message">0110010 0101011 0110010 0111101 0110100</p>
|
||||
<p class="binary" id="footer-message">0110010 0101011 0110010 0111101 0110100</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -582,4 +631,4 @@
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 568 B |
@@ -126,6 +126,31 @@ def test_import_preserves_session(tmp_path, monkeypatch):
|
||||
assert (host_dir / "index.html").exists()
|
||||
|
||||
|
||||
def test_import_rejects_zip_path_traversal(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
malicious_zip = tmp_path / "malicious.zip"
|
||||
with zipfile.ZipFile(malicious_zip, "w") as zf:
|
||||
zf.writestr("../escape.txt", "owned")
|
||||
zf.writestr("config.json", json.dumps({"folder_name": "evil"}))
|
||||
|
||||
cli.import_site(str(malicious_zip))
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Refusing to extract path outside destination" in captured.out
|
||||
assert not (tmp_path / "escape.txt").exists()
|
||||
assert not ((tmp_path / "data" / "host" / "evil").exists())
|
||||
|
||||
|
||||
def test_load_config_migrates_binary_message_to_footer_message(tmp_path):
|
||||
config_path = tmp_path / "legacy-config.json"
|
||||
config_path.write_text(json.dumps({"binary_message": "0101", "folder_name": "legacy"}), encoding="utf-8")
|
||||
|
||||
loaded = cli.load_config(str(config_path))
|
||||
|
||||
assert loaded["footer_message"] == "0101"
|
||||
assert "binary_message" not in loaded
|
||||
|
||||
|
||||
def test_check_all_present(capsys, monkeypatch):
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/" + cmd)
|
||||
cli.main(["check"])
|
||||
@@ -336,11 +361,31 @@ def test_build_without_url_skips_qr(tmp_path, monkeypatch, capsys):
|
||||
assert dest.is_dir()
|
||||
assert (dest / "config.json").exists()
|
||||
assert (dest / "index.html").exists()
|
||||
html = (dest / "index.html").read_text(encoding="utf-8")
|
||||
assert 'class="container no-tear-offs"' in html
|
||||
# QR codes should not exist since no URLs were provided
|
||||
assert not (dest / "qrcode-content.png").exists()
|
||||
assert not (dest / "qrcode-tear-offs.png").exists()
|
||||
|
||||
|
||||
def test_build_injects_localized_tor_browser_download_links(tmp_path, monkeypatch):
|
||||
"""Test that the localized Tor Browser CTA gets the expected localized URL."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
|
||||
res_root = tmp_path / "data" / "voxvera"
|
||||
test_data_dir = tmp_path / "data"
|
||||
config = json.load(open(res_root / "src" / "config.json"))
|
||||
config["lang"] = "de"
|
||||
with open(test_data_dir / "config.json", "w") as f:
|
||||
json.dump(config, f)
|
||||
|
||||
cli.main(["--config", str(test_data_dir / "config.json"), "build"])
|
||||
|
||||
html = (test_data_dir / "host" / config["folder_name"] / "index.html").read_text(encoding="utf-8")
|
||||
assert 'href="https://www.torproject.org/de/download/"' in html
|
||||
assert "Tor-Browser verwenden" in html
|
||||
|
||||
|
||||
def test_build_with_only_url(tmp_path, monkeypatch):
|
||||
"""Test that QR codes work when only url (content link) is set."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
@@ -388,6 +433,8 @@ def test_build_with_only_tear_off_link(tmp_path, monkeypatch):
|
||||
|
||||
test_data_dir = tmp_path / "data"
|
||||
dest = test_data_dir / "host" / config["folder_name"]
|
||||
html = (dest / "index.html").read_text(encoding="utf-8")
|
||||
assert 'class="container no-tear-offs"' not in html
|
||||
# Only tear-off QR should exist (will be updated by serve)
|
||||
assert not (dest / "qrcode-content.png").exists()
|
||||
assert (dest / "qrcode-tear-offs.png").exists()
|
||||
|
||||
+3
-3
@@ -131,7 +131,7 @@ class TestFullWorkflow:
|
||||
"url": "https://example.com/external",
|
||||
"tear_off_link": "", # Will be set by serve
|
||||
"url_message": "Visit our site",
|
||||
"binary_message": "0101010",
|
||||
"footer_message": "0101010",
|
||||
}
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
@@ -230,7 +230,7 @@ class TestFullWorkflow:
|
||||
"url": "https://example.com",
|
||||
"tear_off_link": "",
|
||||
"url_message": "Test message",
|
||||
"binary_message": "101010",
|
||||
"footer_message": "101010",
|
||||
}
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
@@ -365,7 +365,7 @@ class TestSiteFiles:
|
||||
"url": "https://example.com",
|
||||
"tear_off_link": "",
|
||||
"url_message": "",
|
||||
"binary_message": "",
|
||||
"footer_message": "",
|
||||
}
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
|
||||
@@ -45,6 +45,24 @@ def test_locale_completeness():
|
||||
assert not missing_info, "\n".join(missing_info)
|
||||
|
||||
|
||||
def test_critical_locale_labels_are_not_left_in_english():
|
||||
"""Verify key translated labels are not left as raw English in non-English locales."""
|
||||
locales_dir = REPO_ROOT / "voxvera" / "locales"
|
||||
en_data = json.loads((locales_dir / "en.json").read_text(encoding="utf-8"))
|
||||
critical_paths = [
|
||||
("cli", "attachment_path_label"),
|
||||
("cli", "manage_action_edit"),
|
||||
("web", "download_attachment"),
|
||||
]
|
||||
|
||||
for lp in locales_dir.glob("*.json"):
|
||||
if lp.name == "en.json":
|
||||
continue
|
||||
data = json.loads(lp.read_text(encoding="utf-8"))
|
||||
for section, key in critical_paths:
|
||||
assert data[section][key] != en_data[section][key], f"{lp.name} leaves {section}.{key} in English"
|
||||
|
||||
|
||||
def test_export_import_roundtrip(tmp_path, monkeypatch):
|
||||
"""Verify that export -> import preserves the Tor identity key perfectly."""
|
||||
# Setup tmp environment
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.3.1"
|
||||
__version__ = "0.3.6"
|
||||
|
||||
# Add the vendor directory to sys.path ONLY for PyInstaller (frozen) builds.
|
||||
# For pip/pipx installs the system/venv packages are correct and the vendored
|
||||
|
||||
+185
-20
@@ -16,11 +16,11 @@ from importlib.resources.abc import Traversable
|
||||
# avoid pulling in vendored copies that may be incomplete.
|
||||
if getattr(sys, 'frozen', False):
|
||||
try:
|
||||
import pkg_resources
|
||||
import setuptools
|
||||
import jaraco
|
||||
import platformdirs
|
||||
import packaging
|
||||
import pkg_resources # noqa: F401
|
||||
import setuptools # noqa: F401
|
||||
import jaraco # noqa: F401
|
||||
import platformdirs # noqa: F401
|
||||
import packaging # noqa: F401
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -28,6 +28,25 @@ from InquirerPy import prompt, inquirer
|
||||
from rich.console import Console
|
||||
from voxvera import __version__
|
||||
|
||||
GITHUB_RECOMMENDED_BINARY_LIMIT = 95 * 1024 * 1024
|
||||
LEGACY_CONFIG_KEYS = {
|
||||
"binary_message": "footer_message",
|
||||
}
|
||||
TOR_BROWSER_DOWNLOAD_URLS = {
|
||||
"ar": "https://www.torproject.org/ar/download/",
|
||||
"de": "https://www.torproject.org/de/download/",
|
||||
"en": "https://www.torproject.org/download/",
|
||||
"es": "https://www.torproject.org/es/download/",
|
||||
"fa": "https://www.torproject.org/fa/download/",
|
||||
"fr": "https://www.torproject.org/fr/download/",
|
||||
"he": "https://www.torproject.org/he/download/",
|
||||
"ja": "https://www.torproject.org/ja/download/",
|
||||
"pt": "https://www.torproject.org/pt-BR/download/",
|
||||
"ru": "https://www.torproject.org/ru/download/",
|
||||
"tr": "https://www.torproject.org/tr/download/",
|
||||
"zh": "https://www.torproject.org/zh-CN/download/",
|
||||
}
|
||||
|
||||
# package root (contains bundled templates and src/)
|
||||
if getattr(sys, 'frozen', False):
|
||||
ROOT = Path(sys._MEIPASS).joinpath("voxvera")
|
||||
@@ -44,6 +63,30 @@ DATA_DIR = Path(os.environ.get("VOXVERA_DIR", Path.home()))
|
||||
CURRENT_LOCALE = {}
|
||||
|
||||
|
||||
def tor_browser_download_url(lang_code: str) -> str:
|
||||
"""Return the best Tor Browser download URL for a supported locale."""
|
||||
return TOR_BROWSER_DOWNLOAD_URLS.get(lang_code, TOR_BROWSER_DOWNLOAD_URLS["en"])
|
||||
|
||||
|
||||
def normalize_config(data: dict) -> dict:
|
||||
"""Normalize config keys while preserving compatibility with older exports."""
|
||||
normalized = dict(data)
|
||||
for old_key, new_key in LEGACY_CONFIG_KEYS.items():
|
||||
if new_key not in normalized and old_key in normalized:
|
||||
normalized[new_key] = normalized[old_key]
|
||||
for old_key in LEGACY_CONFIG_KEYS:
|
||||
normalized.pop(old_key, None)
|
||||
normalized.setdefault("footer_message", "")
|
||||
return normalized
|
||||
|
||||
|
||||
def build_render_context(data: dict) -> dict:
|
||||
"""Add derived template values without mutating persisted config shape."""
|
||||
render_data = dict(data)
|
||||
render_data["tear_off_state_class"] = "" if render_data.get("tear_off_link", "").strip() else "no-tear-offs"
|
||||
return render_data
|
||||
|
||||
|
||||
def _newlines_to_br(text: str) -> str:
|
||||
"""Convert all forms of newline encoding to <br> for static HTML output.
|
||||
|
||||
@@ -192,12 +235,12 @@ def check_deps():
|
||||
|
||||
def load_config(path: str) -> dict:
|
||||
with open(path, "r") as fh:
|
||||
return json.load(fh)
|
||||
return normalize_config(json.load(fh))
|
||||
|
||||
|
||||
def save_config(data: dict, path: str):
|
||||
with open(path, "w") as fh:
|
||||
json.dump(data, fh, indent=2)
|
||||
json.dump(normalize_config(data), fh, indent=2)
|
||||
|
||||
|
||||
def _open_editor_terminal(initial: str) -> str:
|
||||
@@ -398,15 +441,15 @@ def interactive_update(config_path: str):
|
||||
data.update(prompt(meta_qs))
|
||||
|
||||
console.rule(t("cli.init_body"))
|
||||
# Start with existing/default content; preserve user edits across retries
|
||||
body = _br_to_newlines(get_default("content"))
|
||||
while True:
|
||||
# Use existing content if present, else fallback to localized landing default
|
||||
initial_content = _br_to_newlines(get_default("content"))
|
||||
|
||||
body = open_editor(initial_content)
|
||||
body = open_editor(body)
|
||||
length = len(body)
|
||||
if length > 1000:
|
||||
console.print(f"{t('cli.body_length')}: {length}/1000 {t('cli.body_limit_exceeded')}", style="red")
|
||||
if not inquirer.confirm(message=t("cli.edit_again"), default=True).execute():
|
||||
console.print(t("cli.body_over_limit_warning"), style="yellow")
|
||||
break
|
||||
else:
|
||||
console.print(f"{t('cli.body_length')}: {length}/1000", style="green")
|
||||
@@ -438,6 +481,14 @@ def interactive_update(config_path: str):
|
||||
"transformer": _len_transform(120),
|
||||
"validate": _len_validator(120),
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"message": t("cli.footer_message_label"),
|
||||
"name": "footer_message",
|
||||
"default": data.get("footer_message", ""),
|
||||
"transformer": _len_transform(120),
|
||||
"validate": _len_validator(120),
|
||||
},
|
||||
]
|
||||
data.update(prompt(link_qs))
|
||||
|
||||
@@ -504,13 +555,14 @@ def build_assets(
|
||||
config_path = Path(config_path)
|
||||
|
||||
data = load_config(config_path)
|
||||
render_data = build_render_context(data)
|
||||
with open(src_dir / "index-master.html", "r") as fh:
|
||||
html = fh.read()
|
||||
|
||||
# Bundle locales for the web engine
|
||||
all_locales = {}
|
||||
locale_files = sorted(glob.glob(str(ROOT / "locales" / "*.json")))
|
||||
|
||||
|
||||
# Load master defaults from src/config.json
|
||||
src_config_path = ROOT / "src" / "config.json"
|
||||
master_defaults = {}
|
||||
@@ -527,6 +579,7 @@ def build_assets(
|
||||
"web": l_data.get("web", {}),
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
all_locales[code]["web"]["tor_browser_download_url"] = tor_browser_download_url(code)
|
||||
|
||||
# Override landing data with user custom config data if it differs from master defaults
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
@@ -569,7 +622,7 @@ def build_assets(
|
||||
html = html.replace(f"{{{{t_landing_{field}}}}}", val_str)
|
||||
|
||||
# 2. Statically replace config placeholders {{key}}
|
||||
for key, value in data.items():
|
||||
for key, value in render_data.items():
|
||||
val_str = str(value)
|
||||
# Support Markdown-style redaction ~~text~~ for user fields too
|
||||
val_str = re.sub(r"~~(.*?)~~", r'<span class="redacted">\1</span>', val_str)
|
||||
@@ -586,20 +639,20 @@ def build_assets(
|
||||
data["url"] = f"download/{att_filename}"
|
||||
save_config(data, config_path)
|
||||
html = html.replace("{{url}}", data["url"])
|
||||
|
||||
|
||||
folder_name = data["folder_name"]
|
||||
dest = DATA_DIR / "host" / folder_name
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
|
||||
# 1. Refresh QR codes directly in destination
|
||||
# generate_qr only creates them if URLs are present in config
|
||||
|
||||
|
||||
# We generate to a temp dir first to ensure we ONLY get what's in current config
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as qr_tmp:
|
||||
qr_tmp_path = Path(qr_tmp)
|
||||
generate_qr(config_path, qr_tmp_path)
|
||||
|
||||
|
||||
# Clear old QR codes from destination and copy new ones
|
||||
for qr_file in ["qrcode-content.png", "qrcode-tear-offs.png"]:
|
||||
qr_dest = dest / qr_file
|
||||
@@ -731,6 +784,12 @@ def serve(config_path: str) -> str | None:
|
||||
if not dir_path.is_dir():
|
||||
print(f"Directory {dir_path} not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Auto-rebuild if index.html is missing (e.g. after install cleanup)
|
||||
if not (dir_path / "index.html").exists():
|
||||
print(f"Rebuilding {folder_name} from current template...")
|
||||
build_assets(config_path)
|
||||
|
||||
logfile = dir_path / "onionshare.log"
|
||||
|
||||
# Set environment variables for OnionShare to use the detected Tor ports
|
||||
@@ -847,10 +906,24 @@ def _clear_host_dir(dir_path: Path):
|
||||
session.write_bytes(saved)
|
||||
|
||||
|
||||
import base64
|
||||
import zipfile
|
||||
import tempfile
|
||||
|
||||
|
||||
def _safe_extract_zip(zipf: zipfile.ZipFile, dest_dir: Path) -> None:
|
||||
"""Extract a ZIP archive without allowing path traversal."""
|
||||
dest_dir = dest_dir.resolve()
|
||||
for member in zipf.infolist():
|
||||
member_path = Path(member.filename)
|
||||
if member_path.is_absolute():
|
||||
raise ValueError(f"Refusing to extract absolute path from archive: {member.filename}")
|
||||
target_path = (dest_dir / member_path).resolve()
|
||||
if not str(target_path).startswith(str(dest_dir) + os.sep) and target_path != dest_dir:
|
||||
raise ValueError(f"Refusing to extract path outside destination: {member.filename}")
|
||||
zipf.extractall(dest_dir)
|
||||
|
||||
|
||||
def get_export_dir() -> Path:
|
||||
"""Get the default export directory (~/voxvera-exports)."""
|
||||
export_dir = Path.home() / "voxvera-exports"
|
||||
@@ -904,7 +977,11 @@ def import_site(zip_path: str):
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zipf:
|
||||
zipf.extractall(tmpdir)
|
||||
try:
|
||||
_safe_extract_zip(zipf, Path(tmpdir))
|
||||
except ValueError as e:
|
||||
print(f"Error: Invalid export file. {e}")
|
||||
return
|
||||
|
||||
tmp_path = Path(tmpdir)
|
||||
config_file = tmp_path / "config.json"
|
||||
@@ -954,6 +1031,58 @@ def import_multiple_sites(source_dir: str = None):
|
||||
print("\nImport multiple complete.")
|
||||
|
||||
|
||||
def export_keys(folder_name: str):
|
||||
"""Export the onion service session key as base64 for easy copy/paste."""
|
||||
console = Console()
|
||||
session_file = DATA_DIR / "host" / folder_name / ".onionshare-session"
|
||||
if not session_file.exists():
|
||||
console.print(f"[red]{t('cli.keys_not_found', name=folder_name)}[/red]")
|
||||
return
|
||||
raw = session_file.read_bytes()
|
||||
encoded = base64.b64encode(raw).decode("ascii")
|
||||
console.print(f"\n[bold]{t('cli.keys_export_header', name=folder_name)}[/bold]")
|
||||
console.print("[yellow]" + "─" * 60 + "[/yellow]")
|
||||
console.print(encoded)
|
||||
console.print("[yellow]" + "─" * 60 + "[/yellow]")
|
||||
console.print(f"[dim]{t('cli.keys_export_hint')}[/dim]\n")
|
||||
|
||||
|
||||
def import_keys(folder_name: str):
|
||||
"""Import an onion service session key from base64 pasted by the user."""
|
||||
console = Console()
|
||||
site_dir = DATA_DIR / "host" / folder_name
|
||||
if not site_dir.exists():
|
||||
console.print(f"[red]{t('cli.keys_site_not_found', name=folder_name)}[/red]")
|
||||
return
|
||||
|
||||
session_file = site_dir / ".onionshare-session"
|
||||
if session_file.exists():
|
||||
overwrite = inquirer.confirm(message=t("cli.keys_overwrite_confirm", name=folder_name), default=False).execute()
|
||||
if not overwrite:
|
||||
console.print(f"[yellow]{t('cli.keys_import_cancelled')}[/yellow]")
|
||||
return
|
||||
|
||||
console.print(f"\n[bold]{t('cli.keys_import_prompt')}[/bold]")
|
||||
try:
|
||||
raw_input = input("> ").strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
console.print(f"\n[yellow]{t('cli.keys_import_cancelled')}[/yellow]")
|
||||
return
|
||||
|
||||
if not raw_input:
|
||||
console.print(f"[red]{t('cli.keys_import_empty')}[/red]")
|
||||
return
|
||||
|
||||
try:
|
||||
decoded = base64.b64decode(raw_input)
|
||||
except Exception:
|
||||
console.print(f"[red]{t('cli.keys_import_invalid')}[/red]")
|
||||
return
|
||||
|
||||
session_file.write_bytes(decoded)
|
||||
console.print(f"[green]{t('cli.keys_import_success', name=folder_name)}[/green]")
|
||||
|
||||
|
||||
def build_docs():
|
||||
"""Generate localized documentation from templates and locale data."""
|
||||
if getattr(sys, 'frozen', False):
|
||||
@@ -1037,6 +1166,7 @@ def build_site():
|
||||
|
||||
# 2. Build the HTML from master template
|
||||
data = load_config(config_path)
|
||||
render_data = build_render_context(data)
|
||||
res = _src_res("index-master.html")
|
||||
if getattr(sys, 'frozen', False):
|
||||
template_path = res
|
||||
@@ -1050,7 +1180,7 @@ def build_site():
|
||||
# Bundle locales (Include landing tokens for the main site)
|
||||
all_locales = {}
|
||||
locale_files = sorted(glob.glob(str(ROOT / "locales" / "*.json")))
|
||||
|
||||
|
||||
# Load master defaults from src/config.json
|
||||
src_config_path = ROOT / "src" / "config.json"
|
||||
master_defaults = {}
|
||||
@@ -1067,6 +1197,7 @@ def build_site():
|
||||
"web": l_data.get("web", {}),
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
all_locales[code]["web"]["tor_browser_download_url"] = tor_browser_download_url(code)
|
||||
|
||||
# Override landing data with user custom config data if it differs from master defaults
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
@@ -1111,7 +1242,7 @@ def build_site():
|
||||
html = html.replace("download/download.zip", "download/voxvera-source.zip")
|
||||
|
||||
# 4. Inject site-specific config data (handles any remaining {{key}} placeholders)
|
||||
for key, value in data.items():
|
||||
for key, value in render_data.items():
|
||||
val_str = _newlines_to_br(str(value))
|
||||
html = html.replace(f"{{{{{key}}}}}", val_str)
|
||||
|
||||
@@ -1126,11 +1257,15 @@ def build_site():
|
||||
# Bundle source version
|
||||
bundle_source(download_dir / "voxvera-source.zip")
|
||||
|
||||
# Copy standalone binaries if they exist
|
||||
# Copy standalone binaries if they exist, but avoid writing oversized
|
||||
# artifacts into the tracked site tree where GitHub rejects >100 MB files.
|
||||
bin_dir = ROOT / "resources" / "bin"
|
||||
if bin_dir.exists():
|
||||
for bin_file in bin_dir.iterdir():
|
||||
if bin_file.is_file():
|
||||
if bin_file.stat().st_size > GITHUB_RECOMMENDED_BINARY_LIMIT:
|
||||
print(f"Skipping oversized site download artifact: {bin_file.name}")
|
||||
continue
|
||||
shutil.copy(bin_file, download_dir / bin_file.name)
|
||||
|
||||
print("site/ directory successfully synchronized.")
|
||||
@@ -1259,6 +1394,27 @@ def _seed_bundled_sites():
|
||||
shutil.copytree(src, dest)
|
||||
|
||||
|
||||
def rebuild_all_sites():
|
||||
"""Rebuild index.html for all existing sites from the current master template.
|
||||
|
||||
Preserves config.json, .onionshare-session keys, and download/ content.
|
||||
Only regenerates the HTML and QR codes.
|
||||
"""
|
||||
servers = get_servers()
|
||||
if not servers:
|
||||
print("No sites found to rebuild.")
|
||||
return
|
||||
print(f"Rebuilding {len(servers)} site(s) from current template...")
|
||||
for name in servers:
|
||||
config_path = DATA_DIR / "host" / name / "config.json"
|
||||
try:
|
||||
build_assets(str(config_path))
|
||||
print(f" Rebuilt: {name}")
|
||||
except Exception as e:
|
||||
print(f" Failed to rebuild {name}: {e}")
|
||||
print("Rebuild complete.")
|
||||
|
||||
|
||||
def get_servers() -> list[str]:
|
||||
"""Return server names found in DATA_DIR/host only."""
|
||||
host_dir = DATA_DIR / "host"
|
||||
@@ -1597,6 +1753,8 @@ def manage_servers():
|
||||
Choice("toggle", t("cli.manage_action_toggle_stop") if running else t("cli.manage_action_toggle_start")),
|
||||
Choice("edit", t("cli.manage_action_edit")),
|
||||
Choice("export", t("cli.manage_action_export")),
|
||||
Choice("export_keys", t("cli.manage_action_export_keys")),
|
||||
Choice("import_keys", t("cli.manage_action_import_keys")),
|
||||
Choice("delete", t("cli.manage_action_delete")),
|
||||
Choice(None, t("cli.manage_action_back"))
|
||||
]
|
||||
@@ -1629,6 +1787,10 @@ def manage_servers():
|
||||
console.print("\n[yellow]Update cancelled.[/yellow]")
|
||||
elif action == "export":
|
||||
export_site(selected)
|
||||
elif action == "export_keys":
|
||||
export_keys(selected)
|
||||
elif action == "import_keys":
|
||||
import_keys(selected)
|
||||
elif action == "delete":
|
||||
confirm = inquirer.confirm(message=t("cli.manage_delete_confirm", name=selected)).execute()
|
||||
if confirm:
|
||||
@@ -1733,6 +1895,7 @@ def main(argv=None):
|
||||
sub.add_parser("start-all", help="Start all configured sites (non-interactive)")
|
||||
sub.add_parser("stop-all", help="Stop all running sites (non-interactive)")
|
||||
sub.add_parser("autostart", help="Install/remove a systemd user service to start sites on boot")
|
||||
sub.add_parser("rebuild-all", help="Rebuild all existing sites from the current template (preserves keys and config)")
|
||||
sub.add_parser("_internal_onionshare", help=argparse.SUPPRESS)
|
||||
|
||||
args, unknown_args = parser.parse_known_args(argv)
|
||||
@@ -1832,6 +1995,8 @@ def main(argv=None):
|
||||
build_docs()
|
||||
elif args.command == "build-site":
|
||||
build_site()
|
||||
elif args.command == "rebuild-all":
|
||||
rebuild_all_sites()
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
"url": "https://github.com/PR0M3TH3AN/VoxVera",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
Binary file not shown.
@@ -40,12 +40,13 @@
|
||||
|
||||
[dir="rtl"] .qr-code-body {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code-url {
|
||||
text-align: right;
|
||||
margin-right: 0;
|
||||
margin-left: 10px;
|
||||
padding-right: 4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .message {
|
||||
@@ -94,6 +95,7 @@
|
||||
|
||||
.print-button, .download-button, .lang-select {
|
||||
padding: 10px 20px;
|
||||
min-height: 42px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
@@ -101,6 +103,12 @@
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
line-height: 1.2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
@@ -127,6 +135,7 @@
|
||||
.tear-off {
|
||||
display: flex;
|
||||
height: 1in; /* Fixed height for consistency */
|
||||
gap: 12px;
|
||||
padding: 5px;
|
||||
border: 1px dashed #000;
|
||||
font-size: 10px;
|
||||
@@ -139,9 +148,11 @@
|
||||
.tear-off-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-top: 1px;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.tear-off a {
|
||||
@@ -157,7 +168,6 @@
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 0px solid #000;
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -169,20 +179,21 @@
|
||||
|
||||
.content {
|
||||
width: calc(8.5in - 3.75in);
|
||||
padding: 20px;
|
||||
padding: 15px 20px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
page-break-inside: avoid;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -192,7 +203,10 @@
|
||||
|
||||
.distribute {
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 6px;
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -204,8 +218,8 @@
|
||||
}
|
||||
|
||||
.message {
|
||||
height: 6in; /* Fixed height to prevent footer push-out */
|
||||
margin-top: 10px;
|
||||
flex: 1;
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
@@ -236,8 +250,9 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
@@ -253,19 +268,21 @@
|
||||
.qr-code-url {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
max-width: 60%;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #000;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -294,6 +311,14 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .left-tear-offs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body, html {
|
||||
width: 8.5in;
|
||||
@@ -326,10 +351,20 @@
|
||||
|
||||
.button-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
#download-link {
|
||||
display: inline-flex;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#download-link .download-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
@@ -378,7 +413,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// VoxVera Multi-Language Engine
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f", "download_attachment": "Download Attachment"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.<br><br>\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.<br><br>\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.<br><br>\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\u0631 \u0631\u0633\u0627\u0644\u062a\u0643.", "name": "\u0645\u0646\u0634\u0648\u0631\u0627\u062a Vox Vera \u0627\u0644Line truncated
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f", "download_attachment": "\u062a\u0646\u0632\u064a\u0644 \u0627\u0644\u0645\u0631\u0641\u0642", "tor_browser_download_url": "https://www.torproject.org/ar/download/"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.<br><br>\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.<br><br>\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.<br><br>\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\Line truncated
|
||||
|
||||
function t(path, lang) {
|
||||
const parts = path.split('.');
|
||||
@@ -413,6 +448,19 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-href]').forEach(el => {
|
||||
const path = el.getAttribute('data-i18n-href');
|
||||
const href = t(path, lang);
|
||||
if (href) {
|
||||
el.href = href;
|
||||
}
|
||||
});
|
||||
const torBrowserHref = t('web.tor_browser_download_url', lang);
|
||||
if (torBrowserHref && torBrowserHref !== 'web.tor_browser_download_url') {
|
||||
document.querySelectorAll('.tor-browser-link').forEach(el => {
|
||||
el.href = torBrowserHref;
|
||||
});
|
||||
}
|
||||
document.getElementById('lang-selector').value = lang;
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
@@ -437,13 +485,13 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container no-tear-offs">
|
||||
<div class="left-tear-offs">
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -453,9 +501,9 @@
|
||||
<!-- Repeat for other tear-offs -->
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -464,9 +512,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -475,9 +523,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -486,9 +534,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -497,9 +545,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -508,9 +556,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -519,9 +567,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -530,9 +578,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -541,9 +589,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="" target="_blank"></a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -562,6 +610,7 @@
|
||||
|
||||
<div class="qr-code-url">
|
||||
<span class="url-message" data-i18n="landing.url_message">Get VoxVera and start spreading your message.</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<a id="qr-link-2" href="https://github.com/PR0M3TH3AN/VoxVera" target="_blank">https://github.com/PR0M3TH3AN/VoxVera</a>
|
||||
</div>
|
||||
<img src="qrcode-content.png" alt="QR Code">
|
||||
@@ -570,7 +619,7 @@
|
||||
<hr>
|
||||
<div class="footer">
|
||||
<p class="credit"><span data-i18n="web.built_with">built with</span> <a href="https://github.com/PR0M3TH3AN/VoxVera">voxvera</a></p>
|
||||
<p class="binary" id="binary-message">0110010 0101011 0110010 0111101 0110100</p>
|
||||
<p class="binary" id="footer-message">0110010 0101011 0110010 0111101 0110100</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -582,4 +631,4 @@
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 376 B |
+18
-5
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "يتجاوز الحد",
|
||||
"url_label": "URL (رابط المحتوى - مورد خارجي)",
|
||||
"url_message_label": "رسالة الـ URL",
|
||||
"binary_message_label": "رسالة ثنائية",
|
||||
"footer_message_label": "رسالة التذييل",
|
||||
"template_not_found": "القالب {name} غير موجود",
|
||||
"template_copied": "تم نسخ القالب إلى {dest}",
|
||||
"flyer_created": "تم إنشاء ملفات المنشور تحت {dest}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] تمهيد Tor: {progress}% ...",
|
||||
"no_urls_qr_skip": "لم يتم تكوين أي روابط بعد. سيتم إنشاء أكواد QR بعد 'voxvera serve'",
|
||||
"input_file_not_found": "ملف الإدخال {path} غير موجود.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "مسار ملف المرفق (اختياري - سيتم تقديمه من موقع Tor)",
|
||||
"manage_action_edit": "تحرير",
|
||||
"manage_templates": "--- إنشاء من قالب ---",
|
||||
"template_select": "اختر قالباً:"
|
||||
"template_select": "اختر قالباً:",
|
||||
"body_over_limit_warning": "تحذير: نص المحتوى يتجاوز 1000 حرف. قد يتم قطعه في المنشور.",
|
||||
"manage_action_export_keys": "تصدير المفاتيح",
|
||||
"manage_action_import_keys": "استيراد المفاتيح",
|
||||
"keys_not_found": "لم يتم العثور على مفتاح جلسة لـ '{name}'. قم بتشغيل الموقع أولاً لتوليد المفاتيح.",
|
||||
"keys_export_header": "مفتاح الجلسة لـ '{name}':",
|
||||
"keys_export_hint": "انسخ هذا المفتاح واحفظه بأمان. استخدم 'استيراد المفاتيح' في نسخة أخرى للاستعادة.",
|
||||
"keys_site_not_found": "الموقع '{name}' غير موجود.",
|
||||
"keys_overwrite_confirm": "الموقع '{name}' لديه مفتاح بالفعل. هل تريد الكتابة فوقه؟",
|
||||
"keys_import_cancelled": "تم إلغاء الاستيراد.",
|
||||
"keys_import_prompt": "الصق مفتاح الجلسة base64 أدناه:",
|
||||
"keys_import_empty": "لم يتم تقديم مفتاح.",
|
||||
"keys_import_invalid": "تنسيق المفتاح غير صالح. يجب أن يكون سلسلة base64 صالحة.",
|
||||
"keys_import_success": "تم استيراد مفتاح الجلسة لـ '{name}'. أعد تشغيل الموقع لاستخدام المفتاح الجديد."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "استخدم متصفح Tor",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "بني بواسطة",
|
||||
"print_button": "اطبع المنشور",
|
||||
"download_button": "تحميل الأداة والكود",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "تنزيل المرفق"
|
||||
},
|
||||
"landing": {
|
||||
"title": "سري للغاية",
|
||||
|
||||
+18
-5
@@ -21,7 +21,7 @@
|
||||
"body_limit_exceeded": "überschreitet das Limit",
|
||||
"url_label": "URL (Inhaltslink - externe Ressource)",
|
||||
"url_message_label": "URL-Nachricht",
|
||||
"binary_message_label": "Binäre Nachricht",
|
||||
"footer_message_label": "Fußzeilen-Nachricht",
|
||||
"template_not_found": "Vorlage {name} nicht gefunden",
|
||||
"template_copied": "Vorlage nach {dest} kopiert",
|
||||
"flyer_created": "Flyer-Dateien erstellt unter {dest}",
|
||||
@@ -72,10 +72,23 @@
|
||||
"bootstrapping_progress": "[{name}] Tor-Bootstrapping: {progress}% ...",
|
||||
"no_urls_qr_skip": "Noch keine URLs konfiguriert. QR-Codes werden nach 'voxvera serve' generiert",
|
||||
"input_file_not_found": "Eingabedatei {path} existiert nicht.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Pfad zur Anhangsdatei (optional - wird über die Tor-Seite bereitgestellt)",
|
||||
"manage_action_edit": "Bearbeiten",
|
||||
"manage_templates": "--- Aus Vorlage erstellen ---",
|
||||
"template_select": "Wählen Sie eine Vorlage:"
|
||||
"template_select": "Wählen Sie eine Vorlage:",
|
||||
"body_over_limit_warning": "Warnung: Der Textkörper überschreitet 1000 Zeichen. Er könnte auf dem Flyer abgeschnitten werden.",
|
||||
"manage_action_export_keys": "Schlüssel exportieren",
|
||||
"manage_action_import_keys": "Schlüssel importieren",
|
||||
"keys_not_found": "Kein Sitzungsschlüssel für '{name}' gefunden. Starten Sie die Seite zuerst, um Schlüssel zu generieren.",
|
||||
"keys_export_header": "Sitzungsschlüssel für '{name}':",
|
||||
"keys_export_hint": "Kopieren Sie diesen Schlüssel und bewahren Sie ihn sicher auf. Verwenden Sie 'Schlüssel importieren' auf einer anderen Instanz zur Wiederherstellung.",
|
||||
"keys_site_not_found": "Seite '{name}' nicht gefunden.",
|
||||
"keys_overwrite_confirm": "Seite '{name}' hat bereits einen Schlüssel. Überschreiben?",
|
||||
"keys_import_cancelled": "Import abgebrochen.",
|
||||
"keys_import_prompt": "Fügen Sie den base64-Sitzungsschlüssel unten ein:",
|
||||
"keys_import_empty": "Kein Schlüssel angegeben.",
|
||||
"keys_import_invalid": "Ungültiges Schlüsselformat. Muss eine gültige base64-Zeichenkette sein.",
|
||||
"keys_import_success": "Sitzungsschlüssel für '{name}' importiert. Starten Sie die Seite neu, um den neuen Schlüssel zu verwenden."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Tor-Browser verwenden",
|
||||
@@ -84,7 +97,7 @@
|
||||
"built_with": "erstellt mit",
|
||||
"print_button": "Flyer drucken",
|
||||
"download_button": "Tool & Quellcode herunterladen",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Anhang herunterladen"
|
||||
},
|
||||
"landing": {
|
||||
"title": "STRENG GEHEIM",
|
||||
|
||||
+16
-3
@@ -21,7 +21,7 @@
|
||||
"body_limit_exceeded": "exceeds limit",
|
||||
"url_label": "URL (content link - external resource)",
|
||||
"url_message_label": "URL message",
|
||||
"binary_message_label": "Binary message",
|
||||
"footer_message_label": "Footer message",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"template_not_found": "Template {name} not found",
|
||||
"template_copied": "Template copied to {dest}",
|
||||
@@ -75,7 +75,20 @@
|
||||
"no_urls_qr_skip": "No URLs configured yet. QR codes will be generated after 'voxvera serve'",
|
||||
"input_file_not_found": "Input file {path} does not exist.",
|
||||
"manage_templates": "--- Create From Template ---",
|
||||
"template_select": "Select a template:"
|
||||
"template_select": "Select a template:",
|
||||
"body_over_limit_warning": "Warning: Body text exceeds 1000 characters. It may be cut off on the flyer.",
|
||||
"manage_action_export_keys": "Export Keys",
|
||||
"manage_action_import_keys": "Import Keys",
|
||||
"keys_not_found": "No session key found for '{name}'. Start the site first to generate keys.",
|
||||
"keys_export_header": "Session key for '{name}':",
|
||||
"keys_export_hint": "Copy this key and save it securely. Use 'Import Keys' on another instance to restore.",
|
||||
"keys_site_not_found": "Site '{name}' not found.",
|
||||
"keys_overwrite_confirm": "Site '{name}' already has a key. Overwrite?",
|
||||
"keys_import_cancelled": "Import cancelled.",
|
||||
"keys_import_prompt": "Paste the base64 session key below:",
|
||||
"keys_import_empty": "No key provided.",
|
||||
"keys_import_invalid": "Invalid key format. Must be a valid base64 string.",
|
||||
"keys_import_success": "Session key imported for '{name}'. Restart the site to use the new key."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Use Tor Browser",
|
||||
@@ -141,7 +154,7 @@
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser."
|
||||
"url_message": "Follow this link to learn more."
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-5
@@ -21,7 +21,7 @@
|
||||
"body_limit_exceeded": "excede el límite",
|
||||
"url_label": "URL (enlace de contenido - recurso externo)",
|
||||
"url_message_label": "Mensaje de la URL",
|
||||
"binary_message_label": "Mensaje binario",
|
||||
"footer_message_label": "Mensaje de pie de página",
|
||||
"template_not_found": "Plantilla {name} no encontrada",
|
||||
"template_copied": "Plantilla copiada a {dest}",
|
||||
"flyer_created": "Archivos del volante creados en {dest}",
|
||||
@@ -72,10 +72,23 @@
|
||||
"bootstrapping_progress": "[{name}] Arranque de Tor: {progress}% ...",
|
||||
"no_urls_qr_skip": "No hay URLs configuradas todavía. Los códigos QR se generarán después de 'voxvera serve'",
|
||||
"input_file_not_found": "El archivo de entrada {path} no existe.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Ruta del archivo adjunto (opcional - se servirá desde el sitio Tor)",
|
||||
"manage_action_edit": "Editar",
|
||||
"manage_templates": "--- Crear Desde Plantilla ---",
|
||||
"template_select": "Seleccione una plantilla:"
|
||||
"template_select": "Seleccione una plantilla:",
|
||||
"body_over_limit_warning": "Advertencia: El texto del cuerpo supera los 1000 caracteres. Puede cortarse en el volante.",
|
||||
"manage_action_export_keys": "Exportar Claves",
|
||||
"manage_action_import_keys": "Importar Claves",
|
||||
"keys_not_found": "No se encontró clave de sesión para '{name}'. Inicie el sitio primero para generar claves.",
|
||||
"keys_export_header": "Clave de sesión para '{name}':",
|
||||
"keys_export_hint": "Copie esta clave y guárdela de forma segura. Use 'Importar Claves' en otra instancia para restaurar.",
|
||||
"keys_site_not_found": "Sitio '{name}' no encontrado.",
|
||||
"keys_overwrite_confirm": "El sitio '{name}' ya tiene una clave. ¿Sobrescribir?",
|
||||
"keys_import_cancelled": "Importación cancelada.",
|
||||
"keys_import_prompt": "Pegue la clave de sesión base64 a continuación:",
|
||||
"keys_import_empty": "No se proporcionó clave.",
|
||||
"keys_import_invalid": "Formato de clave no válido. Debe ser una cadena base64 válida.",
|
||||
"keys_import_success": "Clave de sesión importada para '{name}'. Reinicie el sitio para usar la nueva clave."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Use el Navegador Tor",
|
||||
@@ -84,7 +97,7 @@
|
||||
"built_with": "construido con",
|
||||
"print_button": "Imprimir volante",
|
||||
"download_button": "Descargar Herramienta y Código",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Descargar archivo adjunto"
|
||||
},
|
||||
"landing": {
|
||||
"title": "ALTO SECRETO",
|
||||
|
||||
+18
-5
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "بیش از حد مجاز",
|
||||
"url_label": "URL (پیوند محتوا - منبع خارجی)",
|
||||
"url_message_label": "پیام URL",
|
||||
"binary_message_label": "پیام دودویی (Binary)",
|
||||
"footer_message_label": "پیام پابرگ",
|
||||
"template_not_found": "قالب {name} یافت نشد",
|
||||
"template_copied": "قالب به {dest} کپی شد",
|
||||
"flyer_created": "فایلهای اعلامیه در {dest} ایجاد شدند",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] راهاندازی Tor: {progress}% ...",
|
||||
"no_urls_qr_skip": "هنوز آدرسی پیکربندی نشده است. کدهای QR پس از 'voxvera serve' ایجاد میشوند",
|
||||
"input_file_not_found": "فایل ورودی {path} وجود ندارد.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "مسیر فایل پیوست (اختیاری - از طریق سایت Tor ارائه میشود)",
|
||||
"manage_action_edit": "ویرایش",
|
||||
"manage_templates": "--- ایجاد از قالب ---",
|
||||
"template_select": "یک قالب انتخاب کنید:"
|
||||
"template_select": "یک قالب انتخاب کنید:",
|
||||
"body_over_limit_warning": "هشدار: متن بدنه از 1000 کاراکتر بیشتر است. ممکن است در بروشور بریده شود.",
|
||||
"manage_action_export_keys": "صادرات کلیدها",
|
||||
"manage_action_import_keys": "واردات کلیدها",
|
||||
"keys_not_found": "کلید جلسه برای '{name}' یافت نشد. ابتدا سایت را اجرا کنید تا کلیدها تولید شوند.",
|
||||
"keys_export_header": "کلید جلسه برای '{name}':",
|
||||
"keys_export_hint": "این کلید را کپی کرده و به صورت امن ذخیره کنید. از 'واردات کلیدها' در نمونه دیگر برای بازیابی استفاده کنید.",
|
||||
"keys_site_not_found": "سایت '{name}' یافت نشد.",
|
||||
"keys_overwrite_confirm": "سایت '{name}' از قبل کلید دارد. رونویسی شود؟",
|
||||
"keys_import_cancelled": "واردات لغو شد.",
|
||||
"keys_import_prompt": "کلید جلسه base64 را در زیر جایگذاری کنید:",
|
||||
"keys_import_empty": "کلیدی ارائه نشد.",
|
||||
"keys_import_invalid": "فرمت کلید نامعتبر است. باید یک رشته base64 معتبر باشد.",
|
||||
"keys_import_success": "کلید جلسه برای '{name}' وارد شد. سایت را مجدداً راهاندازی کنید تا از کلید جدید استفاده شود."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "از مرورگر Tor استفاده کنید",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "ساخته شده با",
|
||||
"print_button": "چاپ اعلامیه",
|
||||
"download_button": "دانلود ابزار و کد منبع",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "دانلود پیوست"
|
||||
},
|
||||
"landing": {
|
||||
"title": "بهکلی محرمانه",
|
||||
|
||||
+18
-5
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "dépasse la limite",
|
||||
"url_label": "URL (lien de contenu - ressource externe)",
|
||||
"url_message_label": "Message de l'URL",
|
||||
"binary_message_label": "Message binaire",
|
||||
"footer_message_label": "Message de pied de page",
|
||||
"template_not_found": "Modèle {name} non trouvé",
|
||||
"template_copied": "Modèle copié vers {dest}",
|
||||
"flyer_created": "Fichiers du flyer créés sous {dest}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Amorçage de Tor : {progress}% ...",
|
||||
"no_urls_qr_skip": "Aucune URL configurée pour le moment. Les codes QR seront générés après 'voxvera serve'",
|
||||
"input_file_not_found": "Le fichier d'entrée {path} n'existe pas.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Chemin du fichier joint (optionnel - sera servi depuis le site Tor)",
|
||||
"manage_action_edit": "Modifier",
|
||||
"manage_templates": "--- Créer à partir d'un modèle ---",
|
||||
"template_select": "Sélectionnez un modèle :"
|
||||
"template_select": "Sélectionnez un modèle :",
|
||||
"body_over_limit_warning": "Avertissement : Le texte dépasse 1000 caractères. Il pourrait être coupé sur le tract.",
|
||||
"manage_action_export_keys": "Exporter les clés",
|
||||
"manage_action_import_keys": "Importer les clés",
|
||||
"keys_not_found": "Aucune clé de session trouvée pour '{name}'. Démarrez le site d'abord pour générer les clés.",
|
||||
"keys_export_header": "Clé de session pour '{name}' :",
|
||||
"keys_export_hint": "Copiez cette clé et conservez-la en sécurité. Utilisez 'Importer les clés' sur une autre instance pour restaurer.",
|
||||
"keys_site_not_found": "Site '{name}' introuvable.",
|
||||
"keys_overwrite_confirm": "Le site '{name}' a déjà une clé. Écraser ?",
|
||||
"keys_import_cancelled": "Importation annulée.",
|
||||
"keys_import_prompt": "Collez la clé de session base64 ci-dessous :",
|
||||
"keys_import_empty": "Aucune clé fournie.",
|
||||
"keys_import_invalid": "Format de clé invalide. Doit être une chaîne base64 valide.",
|
||||
"keys_import_success": "Clé de session importée pour '{name}'. Redémarrez le site pour utiliser la nouvelle clé."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Utilisez le navigateur Tor",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "créé avec",
|
||||
"print_button": "Imprimer le flyer",
|
||||
"download_button": "Télécharger l'outil et le code",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Télécharger la pièce jointe"
|
||||
},
|
||||
"landing": {
|
||||
"title": "TOP SECRET",
|
||||
|
||||
+18
-5
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "חורג מהמגבלה",
|
||||
"url_label": "URL (קישור לתוכן - משאב חיצוני)",
|
||||
"url_message_label": "הודעת URL",
|
||||
"binary_message_label": "הודעה בינארית",
|
||||
"footer_message_label": "הודעת כותרת תחתונה",
|
||||
"template_not_found": "התבנית {name} לא נמצאה",
|
||||
"template_copied": "התבנית הועתקה אל {dest}",
|
||||
"flyer_created": "קובצי הפלייר נוצרו תחת {dest}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] איתחול Tor: {progress}% ...",
|
||||
"no_urls_qr_skip": "עדיין לא הוגדרו כתובות URL. קודי QR ייווצרו לאחר 'voxvera serve'",
|
||||
"input_file_not_found": "קובץ הקלט {path} אינו קיים.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "נתיב קובץ מצורף (אופציונלי - יוגש מאתר ה-Tor)",
|
||||
"manage_action_edit": "עריכה",
|
||||
"manage_templates": "--- יצירה מתבנית ---",
|
||||
"template_select": "בחר תבנית:"
|
||||
"template_select": "בחר תבנית:",
|
||||
"body_over_limit_warning": "אזהרה: טקסט הגוף חורג מ-1000 תווים. הוא עלול להיחתך בעלון.",
|
||||
"manage_action_export_keys": "ייצוא מפתחות",
|
||||
"manage_action_import_keys": "ייבוא מפתחות",
|
||||
"keys_not_found": "לא נמצא מפתח הפעלה עבור '{name}'. הפעל את האתר תחילה ליצירת מפתחות.",
|
||||
"keys_export_header": "מפתח הפעלה עבור '{name}':",
|
||||
"keys_export_hint": "העתק מפתח זה ושמור אותו בבטחה. השתמש ב'ייבוא מפתחות' במופע אחר לשחזור.",
|
||||
"keys_site_not_found": "האתר '{name}' לא נמצא.",
|
||||
"keys_overwrite_confirm": "לאתר '{name}' כבר יש מפתח. לדרוס?",
|
||||
"keys_import_cancelled": "הייבוא בוטל.",
|
||||
"keys_import_prompt": "הדבק את מפתח ההפעלה base64 למטה:",
|
||||
"keys_import_empty": "לא סופק מפתח.",
|
||||
"keys_import_invalid": "פורמט מפתח לא חוקי. חייב להיות מחרוזת base64 חוקית.",
|
||||
"keys_import_success": "מפתח הפעלה יובא עבור '{name}'. הפעל מחדש את האתר כדי להשתמש במפתח החדש."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "השתמש בדפדפן Tor",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "נבנה עם",
|
||||
"print_button": "הדפס פלייר",
|
||||
"download_button": "הורד כלי וקוד מקור",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "הורד קובץ מצורף"
|
||||
},
|
||||
"landing": {
|
||||
"title": "סודי ביותר",
|
||||
|
||||
+19
-6
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "सीमा से अधिक है",
|
||||
"url_label": "URL (सामग्री लिंक - बाहरी संसाधन)",
|
||||
"url_message_label": "URL संदेश",
|
||||
"binary_message_label": "बाइनरी संदेश",
|
||||
"footer_message_label": "फ़ुटर संदेश",
|
||||
"template_not_found": "टेम्पलेट {name} नहीं मिला",
|
||||
"template_copied": "टेम्पलेट {dest} पर कॉपी किया गया",
|
||||
"flyer_created": "फ्लायर फाइलें {dest} के अंतर्गत बनाई गई हैं",
|
||||
@@ -30,7 +30,7 @@
|
||||
"onionshare_error": "{folder_name} के लिए OnionShare अप्रत्याशित रूप से बंद हो गया।",
|
||||
"onionshare_causes": "सामान्य कारण: नेटवर्क अवरुद्ध, गलत सिस्टम घड़ी, या OnionShare पहले से ही चल रहा है।",
|
||||
"onionshare_logs_tip": "पूरे लॉग के लिए {logfile} देखें।",
|
||||
"onion_url_msg": "[{folder_name}] Onion URL: {onion_url}",
|
||||
"onion_url_msg": "[{folder_name}] ओनियन URL: {onion_url}",
|
||||
"onionshare_running_pid": "[{folder_name}] OnionShare चल रहा है (PID {pid})।",
|
||||
"export_not_found": "त्रुटि: {host_root} में साइट फ़ोल्डर '{folder_name}' नहीं मिला",
|
||||
"export_success": "'{folder_name}' को {output_path} पर निर्यात किया गया",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Tor बूटस्ट्रैपिंग: {progress}% ...",
|
||||
"no_urls_qr_skip": "अभी तक कोई URL कॉन्फ़िगर नहीं किया गया है। QR कोड 'voxvera serve' के बाद जेनरेट किए जाएंगे",
|
||||
"input_file_not_found": "इनपुट फ़ाइल {path} मौजूद नहीं है।",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "अटैचमेंट फ़ाइल पथ (वैकल्पिक - Tor साइट से परोसा जाएगा)",
|
||||
"manage_action_edit": "संपादित करें",
|
||||
"manage_templates": "--- टेम्पलेट से बनाएं ---",
|
||||
"template_select": "एक टेम्पलेट चुनें:"
|
||||
"template_select": "एक टेम्पलेट चुनें:",
|
||||
"body_over_limit_warning": "चेतावनी: मुख्य पाठ 1000 अक्षरों से अधिक है। यह पर्चे पर कटा हुआ दिख सकता है।",
|
||||
"manage_action_export_keys": "कुंजियाँ निर्यात करें",
|
||||
"manage_action_import_keys": "कुंजियाँ आयात करें",
|
||||
"keys_not_found": "'{name}' के लिए सत्र कुंजी नहीं मिली। कुंजियाँ उत्पन्न करने के लिए पहले साइट शुरू करें।",
|
||||
"keys_export_header": "'{name}' के लिए सत्र कुंजी:",
|
||||
"keys_export_hint": "इस कुंजी को कॉपी करें और सुरक्षित रूप से सहेजें। पुनर्स्थापित करने के लिए किसी अन्य इंस्टेंस पर 'कुंजियाँ आयात करें' का उपयोग करें।",
|
||||
"keys_site_not_found": "साइट '{name}' नहीं मिली।",
|
||||
"keys_overwrite_confirm": "साइट '{name}' में पहले से कुंजी है। अधिलेखित करें?",
|
||||
"keys_import_cancelled": "आयात रद्द किया गया।",
|
||||
"keys_import_prompt": "नीचे base64 सत्र कुंजी चिपकाएँ:",
|
||||
"keys_import_empty": "कोई कुंजी प्रदान नहीं की गई।",
|
||||
"keys_import_invalid": "अमान्य कुंजी प्रारूप। एक वैध base64 स्ट्रिंग होनी चाहिए।",
|
||||
"keys_import_success": "'{name}' के लिए सत्र कुंजी आयात की गई। नई कुंजी का उपयोग करने के लिए साइट को पुनः प्रारंभ करें।"
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Tor Browser का उपयोग करें",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "बनाया गया",
|
||||
"print_button": "फ्लायर प्रिंट करें",
|
||||
"download_button": "टूल और कोड डाउनलोड करें",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "अटैचमेंट डाउनलोड करें"
|
||||
},
|
||||
"landing": {
|
||||
"title": "अति गोपनीय",
|
||||
|
||||
+19
-6
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "制限を超えています",
|
||||
"url_label": "URL (コンテンツリンク - 外部リソース)",
|
||||
"url_message_label": "URLメッセージ",
|
||||
"binary_message_label": "バイナリメッセージ",
|
||||
"footer_message_label": "フッターメッセージ",
|
||||
"template_not_found": "テンプレート {name} が見つかりません",
|
||||
"template_copied": "テンプレートを {dest} にコピーしました",
|
||||
"flyer_created": "フライヤーファイルが {dest} に作成されました",
|
||||
@@ -30,7 +30,7 @@
|
||||
"onionshare_error": "{folder_name} の OnionShare が予期せず終了しました。",
|
||||
"onionshare_causes": "主な原因: ネットワークの遮断、システム時刻の不一致、または OnionShare が既に実行されている可能性があります。",
|
||||
"onionshare_logs_tip": "詳細は {logfile} のログを確認してください。",
|
||||
"onion_url_msg": "[{folder_name}] Onion URL: {onion_url}",
|
||||
"onion_url_msg": "[{folder_name}] オニオンURL: {onion_url}",
|
||||
"onionshare_running_pid": "[{folder_name}] OnionShare 実行中 (PID {pid})",
|
||||
"export_not_found": "エラー: サイトフォルダ '{folder_name}' が {host_root} 内に見つかりません",
|
||||
"export_success": "'{folder_name}' を {output_path} にエクスポートしました",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Tor 起動中: {progress}% ...",
|
||||
"no_urls_qr_skip": "URLがまだ設定されていません。QRコードは 'voxvera serve' の後に生成されます",
|
||||
"input_file_not_found": "入力ファイル {path} が存在しません。",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "添付ファイルのパス(任意 - Torサイトから配信されます)",
|
||||
"manage_action_edit": "編集",
|
||||
"manage_templates": "--- テンプレートから作成 ---",
|
||||
"template_select": "テンプレートを選択してください:"
|
||||
"template_select": "テンプレートを選択してください:",
|
||||
"body_over_limit_warning": "警告:本文が1000文字を超えています。チラシ上で途切れる可能性があります。",
|
||||
"manage_action_export_keys": "鍵をエクスポート",
|
||||
"manage_action_import_keys": "鍵をインポート",
|
||||
"keys_not_found": "'{name}' のセッションキーが見つかりません。まずサイトを起動してキーを生成してください。",
|
||||
"keys_export_header": "'{name}' のセッションキー:",
|
||||
"keys_export_hint": "このキーをコピーして安全に保管してください。別のインスタンスで「鍵をインポート」を使用して復元できます。",
|
||||
"keys_site_not_found": "サイト '{name}' が見つかりません。",
|
||||
"keys_overwrite_confirm": "サイト '{name}' にはすでにキーがあります。上書きしますか?",
|
||||
"keys_import_cancelled": "インポートがキャンセルされました。",
|
||||
"keys_import_prompt": "base64セッションキーを以下に貼り付けてください:",
|
||||
"keys_import_empty": "キーが提供されていません。",
|
||||
"keys_import_invalid": "無効なキー形式です。有効なbase64文字列である必要があります。",
|
||||
"keys_import_success": "'{name}' のセッションキーがインポートされました。新しいキーを使用するにはサイトを再起動してください。"
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Tor Browserを使用してください",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "built with",
|
||||
"print_button": "フライヤーを印刷",
|
||||
"download_button": "ツールとソースをダウンロード",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "添付ファイルをダウンロード"
|
||||
},
|
||||
"landing": {
|
||||
"title": "極秘事項",
|
||||
|
||||
+18
-5
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "excede o limite",
|
||||
"url_label": "URL (link de conteúdo - recurso externo)",
|
||||
"url_message_label": "Mensagem da URL",
|
||||
"binary_message_label": "Mensagem binária",
|
||||
"footer_message_label": "Mensagem de rodapé",
|
||||
"template_not_found": "Modelo {name} não encontrado",
|
||||
"template_copied": "Modelo copiado para {dest}",
|
||||
"flyer_created": "Arquivos do panfleto criados em {dest}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Inicialização do Tor: {progress}% ...",
|
||||
"no_urls_qr_skip": "Nenhuma URL configurada ainda. Os códigos QR serão gerados após 'voxvera serve'",
|
||||
"input_file_not_found": "Arquivo de entrada {path} não existe.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Caminho do arquivo anexo (opcional - será servido pelo site Tor)",
|
||||
"manage_action_edit": "Editar",
|
||||
"manage_templates": "--- Criar a partir de Modelo ---",
|
||||
"template_select": "Selecione um modelo:"
|
||||
"template_select": "Selecione um modelo:",
|
||||
"body_over_limit_warning": "Aviso: O texto do corpo excede 1000 caracteres. Pode ser cortado no panfleto.",
|
||||
"manage_action_export_keys": "Exportar Chaves",
|
||||
"manage_action_import_keys": "Importar Chaves",
|
||||
"keys_not_found": "Nenhuma chave de sessão encontrada para '{name}'. Inicie o site primeiro para gerar as chaves.",
|
||||
"keys_export_header": "Chave de sessão para '{name}':",
|
||||
"keys_export_hint": "Copie esta chave e guarde-a com segurança. Use 'Importar Chaves' em outra instância para restaurar.",
|
||||
"keys_site_not_found": "Site '{name}' não encontrado.",
|
||||
"keys_overwrite_confirm": "O site '{name}' já possui uma chave. Sobrescrever?",
|
||||
"keys_import_cancelled": "Importação cancelada.",
|
||||
"keys_import_prompt": "Cole a chave de sessão base64 abaixo:",
|
||||
"keys_import_empty": "Nenhuma chave fornecida.",
|
||||
"keys_import_invalid": "Formato de chave inválido. Deve ser uma string base64 válida.",
|
||||
"keys_import_success": "Chave de sessão importada para '{name}'. Reinicie o site para usar a nova chave."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Use o Tor Browser",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "construído com",
|
||||
"print_button": "Imprimir Panfleto",
|
||||
"download_button": "Baixar Ferramenta e Código",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Baixar anexo"
|
||||
},
|
||||
"landing": {
|
||||
"title": "ALTO SECRETO",
|
||||
|
||||
+19
-6
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "превышает лимит",
|
||||
"url_label": "URL (ссылка на контент - внешний ресурс)",
|
||||
"url_message_label": "Сообщение URL",
|
||||
"binary_message_label": "Бинарное сообщение",
|
||||
"footer_message_label": "Сообщение внизу страницы",
|
||||
"template_not_found": "Шаблон {name} не найден",
|
||||
"template_copied": "Шаблон скопирован в {dest}",
|
||||
"flyer_created": "Файлы листовки созданы в {dest}",
|
||||
@@ -30,7 +30,7 @@
|
||||
"onionshare_error": "OnionShare для {folder_name} неожиданно завершил работу.",
|
||||
"onionshare_causes": "Общие причины: заблокирована сеть, неточное системное время или OnionShare уже запущен.",
|
||||
"onionshare_logs_tip": "Полные логи см. в {logfile}.",
|
||||
"onion_url_msg": "[{folder_name}] Onion URL: {onion_url}",
|
||||
"onion_url_msg": "[{folder_name}] Onion-URL: {onion_url}",
|
||||
"onionshare_running_pid": "[{folder_name}] OnionShare запущен (PID {pid}).",
|
||||
"export_not_found": "Ошибка: папка сайта '{folder_name}' не найдена в {host_root}",
|
||||
"export_success": "Экспортировано '{folder_name}' в {output_path}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Загрузка Tor: {progress}% ...",
|
||||
"no_urls_qr_skip": "URL еще не настроены. QR-коды будут созданы после 'voxvera serve'",
|
||||
"input_file_not_found": "Входной файл {path} не существует.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Путь к файлу вложения (необязательно - будет доступен с сайта Tor)",
|
||||
"manage_action_edit": "Редактировать",
|
||||
"manage_templates": "--- Создать из шаблона ---",
|
||||
"template_select": "Выберите шаблон:"
|
||||
"template_select": "Выберите шаблон:",
|
||||
"body_over_limit_warning": "Предупреждение: Текст превышает 1000 символов. Он может быть обрезан на листовке.",
|
||||
"manage_action_export_keys": "Экспорт ключей",
|
||||
"manage_action_import_keys": "Импорт ключей",
|
||||
"keys_not_found": "Сессионный ключ для '{name}' не найден. Сначала запустите сайт для генерации ключей.",
|
||||
"keys_export_header": "Сессионный ключ для '{name}':",
|
||||
"keys_export_hint": "Скопируйте этот ключ и сохраните его в безопасности. Используйте 'Импорт ключей' на другом экземпляре для восстановления.",
|
||||
"keys_site_not_found": "Сайт '{name}' не найден.",
|
||||
"keys_overwrite_confirm": "У сайта '{name}' уже есть ключ. Перезаписать?",
|
||||
"keys_import_cancelled": "Импорт отменён.",
|
||||
"keys_import_prompt": "Вставьте сессионный ключ base64 ниже:",
|
||||
"keys_import_empty": "Ключ не предоставлен.",
|
||||
"keys_import_invalid": "Неверный формат ключа. Должна быть допустимая строка base64.",
|
||||
"keys_import_success": "Сессионный ключ импортирован для '{name}'. Перезапустите сайт для использования нового ключа."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Используйте Tor Browser",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "создано с помощью",
|
||||
"print_button": "Печать листовки",
|
||||
"download_button": "Скачать инструмент и код",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Скачать вложение"
|
||||
},
|
||||
"landing": {
|
||||
"title": "СОВЕРШЕННО СЕКРЕТНО",
|
||||
|
||||
+18
-5
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "imezidi kikomo",
|
||||
"url_label": "URL (kiungo cha maudhui - rasilimali ya nje)",
|
||||
"url_message_label": "Ujumbe wa URL",
|
||||
"binary_message_label": "Ujumbe wa mfumo wa binary",
|
||||
"footer_message_label": "Ujumbe wa chini ya ukurasa",
|
||||
"template_not_found": "Kiolezo {name} hakijapatikana",
|
||||
"template_copied": "Kiolezo kimeandaliwa kwenda {dest}",
|
||||
"flyer_created": "Faili za kipeperushi zimeundwa chini ya {dest}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Tor Inapakia: {progress}% ...",
|
||||
"no_urls_qr_skip": "Hakuna URL zilizowekwa bado. QR codes zitaundwa baada ya 'voxvera serve'",
|
||||
"input_file_not_found": "Faili ya kuingiza {path} haipo.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Njia ya faili ya kiambatisho (si lazima - itatolewa kutoka tovuti ya Tor)",
|
||||
"manage_action_edit": "Hariri",
|
||||
"manage_templates": "--- Tengeneza Kutoka Kiolezo ---",
|
||||
"template_select": "Chagua kiolezo:"
|
||||
"template_select": "Chagua kiolezo:",
|
||||
"body_over_limit_warning": "Onyo: Maandishi ya mwili yanazidi herufi 1000. Yanaweza kukatwa kwenye kipeperushi.",
|
||||
"manage_action_export_keys": "Hamisha Funguo",
|
||||
"manage_action_import_keys": "Ingiza Funguo",
|
||||
"keys_not_found": "Hakuna funguo ya kipindi iliyopatikana kwa '{name}'. Anzisha tovuti kwanza ili kuzalisha funguo.",
|
||||
"keys_export_header": "Funguo ya kipindi kwa '{name}':",
|
||||
"keys_export_hint": "Nakili funguo hii na uihifadhi kwa usalama. Tumia 'Ingiza Funguo' kwenye mfano mwingine kurejesha.",
|
||||
"keys_site_not_found": "Tovuti '{name}' haijapatikana.",
|
||||
"keys_overwrite_confirm": "Tovuti '{name}' tayari ina funguo. Andika juu yake?",
|
||||
"keys_import_cancelled": "Uingizaji umeghairiwa.",
|
||||
"keys_import_prompt": "Bandika funguo ya kipindi ya base64 hapa chini:",
|
||||
"keys_import_empty": "Hakuna funguo iliyotolewa.",
|
||||
"keys_import_invalid": "Muundo wa funguo si sahihi. Lazima iwe mfuatano halali wa base64.",
|
||||
"keys_import_success": "Funguo ya kipindi imeingizwa kwa '{name}'. Anzisha upya tovuti ili kutumia funguo mpya."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Tumia Kivinjari cha Tor",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "imejengwa na",
|
||||
"print_button": "Chapa Kipeperushi",
|
||||
"download_button": "Pakua Zana na Chanzo",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Pakua kiambatisho"
|
||||
},
|
||||
"landing": {
|
||||
"title": "SIRI KUBWA",
|
||||
|
||||
+19
-6
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "sınırı aşıyor",
|
||||
"url_label": "URL (içerik bağlantısı - harici kaynak)",
|
||||
"url_message_label": "URL mesajı",
|
||||
"binary_message_label": "İkili (Binary) mesaj",
|
||||
"footer_message_label": "Alt bilgi mesajı",
|
||||
"template_not_found": "Şablon {name} bulunamadı",
|
||||
"template_copied": "Şablon {dest} konumuna kopyalandı",
|
||||
"flyer_created": "İlan dosyaları {dest} altında oluşturuldu",
|
||||
@@ -30,7 +30,7 @@
|
||||
"onionshare_error": "{folder_name} için OnionShare beklenmedik bir şekilde kapandı.",
|
||||
"onionshare_causes": "Yaygın nedenler: Ağ engellenmiş, sistem saati yanlış veya OnionShare zaten çalışıyor olabilir.",
|
||||
"onionshare_logs_tip": "Tam günlükler için {logfile} dosyasına bakın.",
|
||||
"onion_url_msg": "[{folder_name}] Onion URL: {onion_url}",
|
||||
"onion_url_msg": "[{folder_name}] Onion URL'si: {onion_url}",
|
||||
"onionshare_running_pid": "[{folder_name}] OnionShare çalışıyor (PID {pid}).",
|
||||
"export_not_found": "Hata: '{folder_name}' site klasörü {host_root} içinde bulunamadı",
|
||||
"export_success": "'{folder_name}' şuraya dışa aktarıldı: {output_path}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Tor Başlatılıyor: %{progress} ...",
|
||||
"no_urls_qr_skip": "Henüz URL yapılandırılmadı. QR kodları 'voxvera serve' komutundan sonra oluşturulacak",
|
||||
"input_file_not_found": "{path} giriş dosyası mevcut değil.",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "Ek dosyası yolu (isteğe bağlı - Tor sitesinden sunulacaktır)",
|
||||
"manage_action_edit": "Düzenle",
|
||||
"manage_templates": "--- Şablondan Oluştur ---",
|
||||
"template_select": "Bir şablon seçin:"
|
||||
"template_select": "Bir şablon seçin:",
|
||||
"body_over_limit_warning": "Uyarı: Gövde metni 1000 karakteri aşıyor. El ilanında kesilebilir.",
|
||||
"manage_action_export_keys": "Anahtarları Dışa Aktar",
|
||||
"manage_action_import_keys": "Anahtarları İçe Aktar",
|
||||
"keys_not_found": "'{name}' için oturum anahtarı bulunamadı. Anahtarları oluşturmak için önce siteyi başlatın.",
|
||||
"keys_export_header": "'{name}' için oturum anahtarı:",
|
||||
"keys_export_hint": "Bu anahtarı kopyalayın ve güvenli bir şekilde saklayın. Geri yüklemek için başka bir örnekte 'Anahtarları İçe Aktar' kullanın.",
|
||||
"keys_site_not_found": "Site '{name}' bulunamadı.",
|
||||
"keys_overwrite_confirm": "Site '{name}' zaten bir anahtara sahip. Üzerine yazılsın mı?",
|
||||
"keys_import_cancelled": "İçe aktarma iptal edildi.",
|
||||
"keys_import_prompt": "Base64 oturum anahtarını aşağıya yapıştırın:",
|
||||
"keys_import_empty": "Anahtar sağlanmadı.",
|
||||
"keys_import_invalid": "Geçersiz anahtar biçimi. Geçerli bir base64 dizesi olmalıdır.",
|
||||
"keys_import_success": "'{name}' için oturum anahtarı içe aktarıldı. Yeni anahtarı kullanmak için siteyi yeniden başlatın."
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "Tor Tarayıcı Kullanın",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "ile yapıldı",
|
||||
"print_button": "İlanı Yazdır",
|
||||
"download_button": "Aracı ve Kaynak Kodunu İndir",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "Eki indir"
|
||||
},
|
||||
"landing": {
|
||||
"title": "ÇOK GİZLİ",
|
||||
|
||||
+19
-6
@@ -22,7 +22,7 @@
|
||||
"body_limit_exceeded": "超过限制",
|
||||
"url_label": "URL (内容链接 - 外部资源)",
|
||||
"url_message_label": "URL 消息",
|
||||
"binary_message_label": "二进制消息",
|
||||
"footer_message_label": "页脚消息",
|
||||
"template_not_found": "模板 {name} 未找到",
|
||||
"template_copied": "模板已复制到 {dest}",
|
||||
"flyer_created": "传单文件已创建在 {dest}",
|
||||
@@ -30,7 +30,7 @@
|
||||
"onionshare_error": "{folder_name} 的 OnionShare 意外退出。",
|
||||
"onionshare_causes": "常见原因:网络受阻、系统时间不准,或 OnionShare 已在运行。",
|
||||
"onionshare_logs_tip": "查看 {logfile} 获取完整日志。",
|
||||
"onion_url_msg": "[{folder_name}] Onion URL: {onion_url}",
|
||||
"onion_url_msg": "[{folder_name}] Onion 地址: {onion_url}",
|
||||
"onionshare_running_pid": "[{folder_name}] OnionShare 正在运行 (PID {pid})。",
|
||||
"export_not_found": "错误:在 {host_root} 中未找到站点文件夹 '{folder_name}'",
|
||||
"export_success": "已将 '{folder_name}' 导出到 {output_path}",
|
||||
@@ -73,10 +73,23 @@
|
||||
"bootstrapping_progress": "[{name}] Tor 引导中: {progress}% ...",
|
||||
"no_urls_qr_skip": "尚未配置 URL。QR 码将在 'voxvera serve' 后生成",
|
||||
"input_file_not_found": "输入文件 {path} 不存在。",
|
||||
"attachment_path_label": "Attachment file path (optional - will be served from Tor site)",
|
||||
"manage_action_edit": "Edit",
|
||||
"attachment_path_label": "附件文件路径(可选 - 将由 Tor 站点提供)",
|
||||
"manage_action_edit": "编辑",
|
||||
"manage_templates": "--- 从模板创建 ---",
|
||||
"template_select": "选择一个模板:"
|
||||
"template_select": "选择一个模板:",
|
||||
"body_over_limit_warning": "警告:正文超过1000个字符。传单上可能会被截断。",
|
||||
"manage_action_export_keys": "导出密钥",
|
||||
"manage_action_import_keys": "导入密钥",
|
||||
"keys_not_found": "未找到 '{name}' 的会话密钥。请先启动站点以生成密钥。",
|
||||
"keys_export_header": "'{name}' 的会话密钥:",
|
||||
"keys_export_hint": "复制此密钥并安全保存。在另一个实例上使用\u201c导入密钥\u201d来恢复。",
|
||||
"keys_site_not_found": "未找到站点 '{name}'。",
|
||||
"keys_overwrite_confirm": "站点 '{name}' 已有密钥。是否覆盖?",
|
||||
"keys_import_cancelled": "导入已取消。",
|
||||
"keys_import_prompt": "请在下方粘贴 base64 会话密钥:",
|
||||
"keys_import_empty": "未提供密钥。",
|
||||
"keys_import_invalid": "密钥格式无效。必须是有效的 base64 字符串。",
|
||||
"keys_import_success": "已导入 '{name}' 的会话密钥。请重启站点以使用新密钥。"
|
||||
},
|
||||
"web": {
|
||||
"tear_off_tor_browser": "使用 Tor 浏览器",
|
||||
@@ -85,7 +98,7 @@
|
||||
"built_with": "构建自",
|
||||
"print_button": "打印传单",
|
||||
"download_button": "下载工具与源码",
|
||||
"download_attachment": "Download Attachment"
|
||||
"download_attachment": "下载附件"
|
||||
},
|
||||
"landing": {
|
||||
"title": "绝密",
|
||||
|
||||
@@ -13,7 +13,7 @@ The `voxvera build` command processes these source files:
|
||||
|
||||
1. **QR code generation** -- Reads URLs from `config.json` and generates PNG QR codes using the `qrcode` and `Pillow` Python libraries.
|
||||
2. **HTML minification** -- Minifies embedded JavaScript with `jsmin` and the overall HTML with `htmlmin2`. Produces `index.html` and `nostr.html` from the master templates.
|
||||
3. **Binary message injection** -- Writes the configured binary message into the built HTML.
|
||||
3. **Footer message injection** -- Writes the configured footer message into the built HTML.
|
||||
4. **Asset packaging** -- Copies all built files into `host/<folder_name>/`.
|
||||
|
||||
No external tools (Node.js, ImageMagick, qrencode, etc.) are required. The entire build pipeline uses pure Python packages.
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
"url": "https://github.com/PR0M3TH3AN/VoxVera",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -40,12 +40,13 @@
|
||||
|
||||
[dir="rtl"] .qr-code-body {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code-url {
|
||||
text-align: right;
|
||||
margin-right: 0;
|
||||
margin-left: 10px;
|
||||
padding-right: 4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .message {
|
||||
@@ -94,6 +95,7 @@
|
||||
|
||||
.print-button, .download-button, .lang-select {
|
||||
padding: 10px 20px;
|
||||
min-height: 42px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
@@ -101,6 +103,12 @@
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
line-height: 1.2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
@@ -127,6 +135,7 @@
|
||||
.tear-off {
|
||||
display: flex;
|
||||
height: 1in; /* Fixed height for consistency */
|
||||
gap: 12px;
|
||||
padding: 5px;
|
||||
border: 1px dashed #000;
|
||||
font-size: 10px;
|
||||
@@ -139,9 +148,11 @@
|
||||
.tear-off-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-top: 1px;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.tear-off a {
|
||||
@@ -157,7 +168,6 @@
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 0px solid #000;
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -169,20 +179,21 @@
|
||||
|
||||
.content {
|
||||
width: calc(8.5in - 3.75in);
|
||||
padding: 20px;
|
||||
padding: 15px 20px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
page-break-inside: avoid;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -192,7 +203,10 @@
|
||||
|
||||
.distribute {
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 6px;
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -204,8 +218,8 @@
|
||||
}
|
||||
|
||||
.message {
|
||||
height: 6in; /* Fixed height to prevent footer push-out */
|
||||
margin-top: 10px;
|
||||
flex: 1;
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
@@ -236,8 +250,9 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
@@ -253,19 +268,21 @@
|
||||
.qr-code-url {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
max-width: 60%;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #000;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -294,6 +311,14 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .left-tear-offs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body, html {
|
||||
width: 8.5in;
|
||||
@@ -326,10 +351,20 @@
|
||||
|
||||
.button-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
#download-link {
|
||||
display: inline-flex;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#download-link .download-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
@@ -413,6 +448,19 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-href]').forEach(el => {
|
||||
const path = el.getAttribute('data-i18n-href');
|
||||
const href = t(path, lang);
|
||||
if (href) {
|
||||
el.href = href;
|
||||
}
|
||||
});
|
||||
const torBrowserHref = t('web.tor_browser_download_url', lang);
|
||||
if (torBrowserHref && torBrowserHref !== 'web.tor_browser_download_url') {
|
||||
document.querySelectorAll('.tor-browser-link').forEach(el => {
|
||||
el.href = torBrowserHref;
|
||||
});
|
||||
}
|
||||
document.getElementById('lang-selector').value = lang;
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
@@ -437,11 +485,11 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container {{tear_off_state_class}}">
|
||||
<div class="left-tear-offs">
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -453,7 +501,7 @@
|
||||
<!-- Repeat for other tear-offs -->
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -464,7 +512,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -475,7 +523,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -486,7 +534,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -497,7 +545,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -508,7 +556,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -519,7 +567,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -530,7 +578,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -541,7 +589,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -562,6 +610,7 @@
|
||||
|
||||
<div class="qr-code-url">
|
||||
<span class="url-message" data-i18n="landing.url_message">{{t_landing_url_message}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<a id="qr-link-2" href="{{url}}" target="_blank">{{url}}</a>
|
||||
</div>
|
||||
<img src="qrcode-content.png" alt="QR Code">
|
||||
@@ -570,7 +619,7 @@
|
||||
<hr>
|
||||
<div class="footer">
|
||||
<p class="credit"><span data-i18n="web.built_with">{{t_web_built_with}}</span> <a href="https://github.com/PR0M3TH3AN/VoxVera">voxvera</a></p>
|
||||
<p class="binary" id="binary-message">{{binary_message}}</p>
|
||||
<p class="binary" id="footer-message">{{footer_message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -582,4 +631,4 @@
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "The Android Open Source Project (AOSP) represents the essence of what Android was originally intended to be: an open, transparent, and adaptable platform. It strips away the layers of proprietary software and corporate oversight that have become synonymous with mainstream Android distributions. With AOSP, you gain access to a raw, unfiltered version of Android, allowing you to truly make your device your own.<br><br>For developers, AOSP offers a playground of possibilities, where the only limits are your skills and imagination. It serves as the foundation for countless custom ROMs, each tailored to specific needs, whether for enhanced performance, privacy, or unique features that mainstream versions lack. Privacy enthusiasts will appreciate the absence of pre-installed bloatware and tracking services that often come wit stock Android, providing a cleaner, more secure user experience.<br><br>AOSP isn't just for developers or tech enthusiasts; it's for anyone who values digital freedom. It allows you to reclaim control over your device, tailoring it to your needs without the restrictions of carriers or manufacturers. Discover how AOSP can transform your mobile experience. Start your journey towards a more open, customizable future today.",
|
||||
"url_message": "Visit the link below to learn more.",
|
||||
"url": "https://source.android.com",
|
||||
"tear_off_link": "http://aosp.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "What is Bitcoin? It's more than just digital money; it's a revolutionary technology designed to fix some of the world's most pressing problems. Bitcoin operates on a decentralized network, free from the control of any government or central authority. This ensures that no one can manipulate its supply or value, making it a sound form of money that protects your wealth from inflation.<br><br>Our current financial system is built on flawed incentives—encouraging debt, dependency, and centralization of power. Bitcoin changes that by creating a system where honesty, transparency, and individual responsibility are rewarded. In the Bitcoin network, there is no room for corruption or manipulation because the rules are enforced by mathematics, not by people.<br><br>Bitcoin aligns incentives in a way that promotes long-term thinking, savings, and innovation. It empowers individuals to take control of their financial future, fostering a global community where value can be transferred freely and securely without intermediaries. This is why we need Bitcoin—because it creates a world where incentives are properly aligned, leading to a more fair and prosperous society for all.<br><br>Take the step to learn more about Bitcoin and how it can transform not just your financial life, but the world. Tear off a link to the left and explore this groundbreaking technology. Join the movement towards a better future.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://bitcoin.org/en",
|
||||
"tear_off_link": "http://bitcoin.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"url_message": "",
|
||||
"url": "",
|
||||
"tear_off_link": "",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "content",
|
||||
"url_message": "url message",
|
||||
"url": "url",
|
||||
"tear_off_link": "http://example-folder_name.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "Who is Jesus? He is more than just a historical figure; He is the Son of God, the Savior of the world. Jesus Himself said, 'I am the way, the truth, and the life. No one comes to the Father except through Me' (John 14:6). He came to bridge the gap between God and humanity, offering us a path to forgiveness, peace, and eternal life.<br><br>We all face brokenness in our lives—whether it's in our relationships, our hearts, or our world. Jesus came to heal that brokenness. He lived a perfect life, died on the cross for our sins, and rose again, defeating death and offering us the hope of eternal life with God.<br><br>Without Jesus, we are lost in our sin, separated from God. But through Him, we can be forgiven and restored. He offers us not just a better life now, but the promise of an eternal life with Him. This is why we need Him—because only in Jesus can we find true peace, purpose, and the assurance of eternal life.<br><br>Take the step to learn more about Jesus and the life-changing impact He can have on your life. Tear off a link to the left and explore His teachings. Find the hope you've been searching for.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "http://media.faithforthefamily.com/ra/godsoloved/english.mp3",
|
||||
"tear_off_link": "http://jesus.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "Why Meshtastic? In times of crisis, traditional communication networks can fail, leaving us isolated and vulnerable. Meshtastic provides a powerful solution—an alternative local communication network that operates independently of centralized systems. It’s designed for emergency situations, allowing you to stay connected when it matters most.<br><br>Meshtastic devices create a mesh network that can relay messages over long distances, even without internet or cellular coverage. This decentralized approach ensures that your communications are resilient and reliable, no matter the circumstances. But it’s not just about staying connected—Meshtastic also offers encryption, so your messages remain secure and private.<br><br>In a world where privacy is increasingly compromised, Meshtastic empowers you to take control of your communication. Whether you’re coordinating a local community response, staying in touch with loved ones during a disaster, or simply preparing for the unexpected, Meshtastic provides the tools you need to communicate securely and effectively.<br><br>Take the step to learn more about Meshtastic and how you can build your own emergency communication network. Tear off a link to the left and explore this innovative technology. Ensure you’re prepared for whatever comes your way.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://meshtastic.org",
|
||||
"tear_off_link": "http://meshtastic.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"content": "What is Nostr? Unlike traditional social media platforms, Nostr is a protocol—a decentralized system that empowers users to connect and share without the constraints of a central authority. Nostr is censorship-resistant, meaning your voice can’t be silenced by corporations or governments. It’s designed to give you control over your online presence, free from the influence of algorithms or ads that prioritize engagement over meaningful connections.<br><br>On Nostr, there are no hidden agendas to keep you doomscrolling. You engage with content on your terms, not based on what an algorithm thinks will keep you online the longest. Nostr allows you to build genuine connections and participate in conversations that matter, all while maintaining your privacy and autonomy.<br><br>In a world where online speech is increasingly controlled and manipulated, Nostr offers a refreshing alternative. It’s not just a platform; it’s a movement towards true digital freedom, where your interactions are driven by your interests and values, not by what generates the most ad revenue.<br><br>Take the step to learn more about Nostr and how it can transform the way you connect online. Tear off a link the left and explore this groundbreaking protocol. Join a community that values freedom and authentic interaction.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://nostr.how/en/what-is-nostr",
|
||||
"tear_off_link": "http://nostr.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://voxvera.org/",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://voxvera.org/",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": ""
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<title id="site-title">{{name}}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-family: 'Courier New', Courier, 'Nimbus Mono L', 'Liberation Mono', 'Noto Sans Arabic', 'Noto Sans Hebrew', 'Noto Sans Devanagari', 'Noto Sans JP', 'Noto Sans SC', 'Noto Sans Arabic UI', monospace;
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
@@ -19,6 +19,46 @@
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* RTL Layout Overrides */
|
||||
[dir="rtl"] .container {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
[dir="rtl"] .left-tear-offs {
|
||||
border-right: none;
|
||||
border-left: 1px dashed #000;
|
||||
}
|
||||
|
||||
[dir="rtl"] .tear-off {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code {
|
||||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code-body {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code-url {
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .message {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
[dir="rtl"] .text-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ... existing styles ... */
|
||||
|
||||
a {
|
||||
color: #b80000;
|
||||
text-decoration: none;
|
||||
@@ -55,6 +95,7 @@
|
||||
|
||||
.print-button, .download-button, .lang-select {
|
||||
padding: 10px 20px;
|
||||
min-height: 42px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
@@ -62,6 +103,12 @@
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
line-height: 1.2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
@@ -87,20 +134,25 @@
|
||||
|
||||
.tear-off {
|
||||
display: flex;
|
||||
height: 1in; /* Fixed height for consistency */
|
||||
gap: 12px;
|
||||
padding: 5px;
|
||||
border: 1px dashed #000;
|
||||
font-size: 10px;
|
||||
box-sizing: border-box;
|
||||
page-break-inside: avoid;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tear-off-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-top: 1px;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.tear-off a {
|
||||
@@ -116,7 +168,6 @@
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 0px solid #000;
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -128,26 +179,38 @@
|
||||
|
||||
.content {
|
||||
width: calc(8.5in - 3.75in);
|
||||
padding: 20px;
|
||||
padding: 15px 20px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
page-break-inside: avoid;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.distribute {
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 6px;
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.redacted {
|
||||
@@ -156,14 +219,14 @@
|
||||
|
||||
.message {
|
||||
flex: 1;
|
||||
margin-top: 10px;
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
overflow-wrap: break-word;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
white-space: pre-wrap;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
@@ -187,8 +250,9 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
@@ -204,19 +268,21 @@
|
||||
.qr-code-url {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
max-width: 60%;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #000;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -245,6 +311,14 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .left-tear-offs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body, html {
|
||||
width: 8.5in;
|
||||
@@ -277,10 +351,65 @@
|
||||
|
||||
.button-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
#download-link {
|
||||
display: inline-flex;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#download-link .download-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
background-color: #f9f9f9;
|
||||
min-width: 200px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
border: 1px solid #000;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dropdown-content a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown:hover .download-button {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
// VoxVera Multi-Language Engine
|
||||
@@ -299,10 +428,39 @@
|
||||
function setLanguage(lang) {
|
||||
if (!locales[lang]) lang = 'en';
|
||||
localStorage.setItem('voxvera_lang', lang);
|
||||
|
||||
// Set text direction
|
||||
const direction = locales[lang].meta.direction || 'ltr';
|
||||
document.documentElement.dir = direction;
|
||||
document.documentElement.lang = lang;
|
||||
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const path = el.getAttribute('data-i18n');
|
||||
el.innerText = t(path, lang);
|
||||
let translation = t(path, lang);
|
||||
if (translation) {
|
||||
// Support Markdown-style redaction ~~text~~ -> <span class="redacted">text</span>
|
||||
translation = translation.replace(/~~(.*?)~~/g, '<span class="redacted">$1</span>');
|
||||
|
||||
if (path.startsWith('landing.') || path.includes('subtitle') || path.includes('content')) {
|
||||
el.innerHTML = translation;
|
||||
} else {
|
||||
el.innerText = translation;
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-href]').forEach(el => {
|
||||
const path = el.getAttribute('data-i18n-href');
|
||||
const href = t(path, lang);
|
||||
if (href) {
|
||||
el.href = href;
|
||||
}
|
||||
});
|
||||
const torBrowserHref = t('web.tor_browser_download_url', lang);
|
||||
if (torBrowserHref && torBrowserHref !== 'web.tor_browser_download_url') {
|
||||
document.querySelectorAll('.tor-browser-link').forEach(el => {
|
||||
el.href = torBrowserHref;
|
||||
});
|
||||
}
|
||||
document.getElementById('lang-selector').value = lang;
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
@@ -327,11 +485,11 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container {{tear_off_state_class}}">
|
||||
<div class="left-tear-offs">
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -343,7 +501,7 @@
|
||||
<!-- Repeat for other tear-offs -->
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -354,7 +512,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -365,7 +523,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -376,7 +534,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -387,7 +545,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -398,7 +556,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -409,7 +567,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -420,7 +578,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -431,7 +589,7 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">{{t_web_tear_off_tor_browser}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">{{t_web_tear_off_desc}}</span><br>
|
||||
<a class="tear-off-link" href="{{tear_off_link}}" target="_blank">{{tear_off_link}}</a><br>
|
||||
<span data-i18n="web.tear_off_footer">{{t_web_tear_off_footer}}</span>
|
||||
@@ -449,8 +607,10 @@
|
||||
<div class="message">
|
||||
<div class="text-container" id="main-content" contenteditable="false" data-i18n="landing.content">{{t_landing_content}}</div>
|
||||
<div class="qr-code-body">
|
||||
|
||||
<div class="qr-code-url">
|
||||
<span class="url-message" data-i18n="landing.url_message">{{t_landing_url_message}}</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="{{t_web_tor_browser_download_url}}" target="_blank" rel="noopener noreferrer">{{t_web_tear_off_tor_browser}}</a><br><br>
|
||||
<a id="qr-link-2" href="{{url}}" target="_blank">{{url}}</a>
|
||||
</div>
|
||||
<img src="qrcode-content.png" alt="QR Code">
|
||||
@@ -459,14 +619,16 @@
|
||||
<hr>
|
||||
<div class="footer">
|
||||
<p class="credit"><span data-i18n="web.built_with">{{t_web_built_with}}</span> <a href="https://github.com/PR0M3TH3AN/VoxVera">voxvera</a></p>
|
||||
<p class="binary" id="binary-message">{{binary_message}}</p>
|
||||
<p class="binary" id="footer-message">{{footer_message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="print-button" onclick="window.print()" data-i18n="web.print_button">{{t_web_print_button}}</button>
|
||||
<a class="download-button" href="download/download.zip" download data-i18n="web.download_button">{{t_web_download_button}}</a>
|
||||
<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>
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in new issue
Block a user