Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.

## [0.1.49](https://github.com/CodingBlackFemales/wordpress/compare/v0.1.48...v0.1.49) (2026-05-11)

## [0.1.48](https://github.com/CodingBlackFemales/wordpress/compare/v0.1.47...v0.1.48) (2026-05-11)


Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cbf-wordpress",
"version": "0.1.48",
"version": "0.1.49",
"description": "[![Packagist](https://img.shields.io/packagist/v/roots/bedrock.svg?style=flat-square)](https://packagist.org/packages/roots/bedrock) [![Build Status](https://img.shields.io/travis/roots/bedrock.svg?style=flat-square)](https://travis-ci.org/roots/bedrock)",
"devDependencies": {
"@commitlint/cli": "^19.0",
Expand Down
120 changes: 120 additions & 0 deletions scripts/sync-export-rsync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# Upload CSV files and media/ to a remote user's export dir via rsync over SSH.
#
# Connection settings are taken from flags first, then environment variables,
# then built-in fallback defaults. This lets import-slides.sh feed the target
# derived from wp-cli.yml (single source of truth) instead of editing this file.
#
# Usage (from the directory that contains your CSVs and media/):
# ./scripts/sync-export-rsync.sh
# ./scripts/sync-export-rsync.sh --dry-run
# ./scripts/sync-export-rsync.sh --host 1.2.3.4 --user bob --port 65002 ./MyLesson.csv ./media
#
# Flags (all optional):
# --user <user> SSH user (env SSH_USER)
# --host <host> SSH host (env SSH_HOST)
# --port <port> SSH port (env SSH_PORT, default 22)
# --identity <path> SSH private key (env SSH_IDENTITY)
# --remote-dir <name> Dir under remote ~ (env REMOTE_EXPORT, default "export")
# --dry-run Preview without transferring
# [paths...] Explicit sources; default: *.csv in cwd + ./media if present
#
# Remote layout: ~/<remote-dir>/<same basenames and media/ subtree>. Uses rsync --update (-u).

set -euo pipefail

# --- fallback defaults (overridden by env vars or flags) ---
SSH_USER="${SSH_USER:-u544495502}"
SSH_HOST="${SSH_HOST:-82.29.186.233}"
SSH_PORT="${SSH_PORT:-65002}"
# Leave empty to use ssh default identity; otherwise path to a private key.
SSH_IDENTITY="${SSH_IDENTITY:-}"
# Directory name under the remote user's home (destination is ~/${REMOTE_EXPORT}/).
REMOTE_EXPORT="${REMOTE_EXPORT:-export}"
# --- end defaults ---

dry_run=()
paths=()

while [[ "$#" -gt 0 ]]; do
case "$1" in
--dry-run) dry_run=(--dry-run); shift ;;
--user) SSH_USER="$2"; shift 2 ;;
--host) SSH_HOST="$2"; shift 2 ;;
--port) SSH_PORT="$2"; shift 2 ;;
--identity) SSH_IDENTITY="$2"; shift 2 ;;
--remote-dir) REMOTE_EXPORT="$2"; shift 2 ;;
--user=*) SSH_USER="${1#*=}"; shift ;;
--host=*) SSH_HOST="${1#*=}"; shift ;;
--port=*) SSH_PORT="${1#*=}"; shift ;;
--identity=*) SSH_IDENTITY="${1#*=}"; shift ;;
--remote-dir=*) REMOTE_EXPORT="${1#*=}"; shift ;;
--) shift; while [[ "$#" -gt 0 ]]; do paths+=("$1"); shift; done ;;
-*) echo "Error: unknown option: $1" >&2; exit 1 ;;
*) paths+=("$1"); shift ;;
esac
done

if [[ -z "${SSH_USER}" || -z "${SSH_HOST}" ]]; then
echo "Error: SSH user and host are required (set --user/--host or SSH_USER/SSH_HOST)." >&2
exit 1
fi

if [[ -n "${SSH_IDENTITY}" && ! -f "${SSH_IDENTITY}" ]]; then
echo "Error: SSH_IDENTITY is set but file not found: ${SSH_IDENTITY}" >&2
exit 1
fi

# rsync -e string (avoid spaces in SSH_IDENTITY paths or use ~/.ssh/config Host entries)
RSYNC_RSH="ssh"
if [[ -n "${SSH_PORT}" && "${SSH_PORT}" != "22" ]]; then
RSYNC_RSH+=" -p ${SSH_PORT}"
fi
if [[ -n "${SSH_IDENTITY}" ]]; then
RSYNC_RSH+=" -i ${SSH_IDENTITY}"
fi

sources=()
if [[ "${#paths[@]}" -gt 0 ]]; then
for p in "${paths[@]}"; do
if [[ ! -e "$p" ]]; then
echo "Error: source does not exist: $p" >&2
exit 1
fi
# Preserve directory names on the remote side: "media" keeps ~/export/media,
# while "media/" would flatten contents into ~/export.
if [[ -d "$p" ]]; then
p="${p%/}"
fi
sources+=("$p")
done
else
shopt -s nullglob
for f in *.csv; do
sources+=("$f")
done
shopt -u nullglob
if [[ -d media ]]; then
sources+=(media)
fi
if [[ "${#sources[@]}" -eq 0 ]]; then
echo "Error: no arguments and no *.csv or ./media/ in the current directory." >&2
echo " cd to your export folder or pass explicit paths." >&2
exit 1
fi
fi

dest="${SSH_USER}@${SSH_HOST}:~/${REMOTE_EXPORT}/"

if [[ "${#dry_run[@]}" -gt 0 ]]; then
echo "rsync (dry-run) → ${dest}"
else
echo "rsync → ${dest}"
fi
rsync -auvz -h \
--exclude=".DS_Store" \
--exclude="*/.DS_Store" \
"${dry_run[@]}" \
-e "${RSYNC_RSH}" \
"${sources[@]}" \
"$dest"
13 changes: 13 additions & 0 deletions tools/slides-to-learndash/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
.venv/
__pycache__/
*.py[cod]
*.egg-info/
dist/
build/
.pytest_cache/
.coverage
htmlcov/
media/
*.pptx
*.csv
12 changes: 12 additions & 0 deletions tools/slides-to-learndash/.gitrepo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
; DO NOT EDIT (unless you know what you are doing)
;
; This subdirectory is a git "subrepo", and this file is maintained by the
; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme
;
[subrepo]
remote = /Users/gary/Dev/CodingBlackFemales/slides-to-learndash
branch = main
commit = 28b10e664db3915ec85409f5b443c8c8f7bf8b12
parent = 06d1fe87d0bb3a87f531e42759b4eac89b973180
method = merge
cmdver = 0.4.9
126 changes: 126 additions & 0 deletions tools/slides-to-learndash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# slides-to-learndash

Convert a `.pptx` file (e.g. exported from Google Slides) into CSV for the [LearnDash Bulk Lessons Or Topics](https://github.com/serenichron/learndash-bulk-lessons-or-topics) WordPress plugin. Content is emitted as **WordPress block markup** on a single CSV line per row (compatible with the plugin’s CSV reader).

## Setup

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```

## List slide layout names

Provide exactly one `.pptx` as a positional argument or with `--input` / `-i`:

```bash
slides-to-learndash --list-layouts ./deck.pptx
slides-to-learndash --list-layouts --input ./deck.pptx
slides-to-learndash --list-layouts -i ./deck.pptx
```

Use this to choose a `--heading-layout` regex for topic mode.

## Export: one merged lesson (`lesson-only`)

**Default mode** is `lesson-only`. **Default output** is the same path as each input file with a `.csv` extension (e.g. `./deck.pptx` → `./deck.csv`).

Minimal example:

```bash
slides-to-learndash ./deck.pptx
```

Several decks in one run (each gets its own CSV next to the `.pptx`):

```bash
slides-to-learndash ./a.pptx ./b.pptx
```

Optional **`--out` / `-o`** (single input only): override the output CSV path. With multiple inputs, omit `--out` (it is not supported).

Legacy style (positional or `--input`, not both):

```bash
slides-to-learndash --input ./deck.pptx --course-id 123 --out ./build.csv
slides-to-learndash ./deck.pptx --course-id 123 -o ./build.csv
```

Optional: `--lesson-title`, `--slide-headings` / `--no-slide-headings` (default: add each slide’s title as an **H2** before that slide’s body), `--skip-images`, `--media-dir`.

Slide **1** is treated as the cover slide: its content is **not** included in `post_content` (the lesson/topic title can still come from it via `--lesson-title` or the first slide’s title). Slides are **not** separated with `wp:separator` blocks.

When a slide title is emitted as an **H2**, the same title is **dropped** from the start of that slide’s body so it is not repeated as a paragraph.

### Rich text → blocks

Paragraph runs are mapped to HTML inside `wp:paragraph`, list items, and headings: **bold**, *italic*, underline, strikethrough, monospace/`code`, and combined emphasis. Bullet paragraphs become `wp:list` / `wp:list-item`. Paragraphs with larger font sizes (typical subtitle lines) become `wp:heading` level **3** or **4**. All-monospace paragraphs become `wp:code` (pre/code). Images are still `wp:image` with files under `media/`.

## Export: lesson + topics (`lesson-with-topics`)

Slides whose **`slide_layout.name`** matches the regex (Python `re.fullmatch` on the trimmed name) start a new **topic**. All slides **before** the first match are merged into the **lesson** row only.

Writes two files next to the output base path (default: same directory as the input, stem from the input basename):

- `{stem}_lesson.csv`
- `{stem}_topics.csv`

Example:

```bash
slides-to-learndash ./deck.pptx \
--mode lesson-with-topics \
--course-id 123 \
--heading-layout 'SECTION_HEADER|Title - Top_1'
```

With an explicit base path (single file only):

```bash
slides-to-learndash ./deck.pptx \
--mode lesson-with-topics \
--course-id 123 \
-o ./build.csv \
--heading-layout 'SECTION_HEADER|Title - Top_1'
```

(`./build.csv` supplies the stem `build`, so outputs are `build_lesson.csv` and `build_topics.csv` next to `./build.csv`.)

### Import order

1. Import **`_lesson.csv`** with the bulk plugin, content type **Lessons**, action **Create**.
2. Note the new lesson **post ID** in WordPress.
3. Edit **`_topics.csv`**: set the `lesson_id` column on each topic row to that ID.
4. Import **`_topics.csv`** with content type **Topics**, action **Create**.

**Media folders:** by default, images go under `media/<sanitized_csv_stem>/` beside the output CSV (e.g. `media/Introduction_to_Git/slide_001_img_01.png` in HTML), so different decks do not share one flat `media/` directory. With **multiple** inputs and a shared **`--media-dir`**, each export uses a subfolder named after that CSV stem under that directory so files do not overwrite each other. Override layout with **`--media-dir`** (paths in the CSV are relative to the CSV’s directory when possible). Or use **`--skip-images`**.

## Upload export to server (rsync)

The script [scripts/sync-export-rsync.sh](scripts/sync-export-rsync.sh) uploads CSV files and the `media/` tree to **`~/export`** on a remote host over SSH using **rsync** (`-a` archive, **`-u` / `--update`** so the receiver is not overwritten when its copy is newer, **`-z`** compression). It does **not** use `--delete`.

1. Edit the configuration block at the top of the script: `SSH_USER`, `SSH_HOST`, optional `SSH_PORT` (default `22`), optional `SSH_IDENTITY`, and `REMOTE_EXPORT` (default `export`, i.e. `~/export` on the server).
2. Ensure the remote directory exists once, e.g. `ssh youruser@yourhost 'mkdir -p ~/export'`.
3. From the folder that contains your generated `*.csv` and `media/`:

```bash
chmod +x scripts/sync-export-rsync.sh
cd /path/to/that/folder
/path/to/slides-to-learndash/scripts/sync-export-rsync.sh --dry-run
/path/to/slides-to-learndash/scripts/sync-export-rsync.sh
```

With **no path arguments**, the script syncs all **`*.csv` in the current directory** and **`./media`** if present. You can pass explicit paths instead, e.g. `./MyLesson.csv ./media`. Basenames and the `media/…` subtree are mirrored under `~/export/` on the server.

Avoid committing real credentials in the script if the repository is shared; use placeholders or a private fork.

## Requirements for export

| Input | Required when |
|------|----------------|
| One or more `.pptx` paths (positional) **or** `--input` / `-i` (single file) | Always (except help). Do not pass both. |
| `--mode` | Optional; defaults to **`lesson-only`**. |
| `--course-id` | Optional (empty in CSV if omitted). |
| `--out` / `-o` | Optional; default is `<input>.csv`. **Not allowed** with multiple positional inputs. |
| `--heading-layout` | **`lesson-with-topics` only** (regex). |
20 changes: 20 additions & 0 deletions tools/slides-to-learndash/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[project]
name = "slides-to-learndash"
version = "0.1.0"
description = "Convert PPTX decks to LearnDash bulk-import CSV (WIP)"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"python-pptx>=0.6.21",
"typer>=0.9.0",
]

[project.scripts]
slides-to-learndash = "slides_to_learndash.cli:app"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["slides_to_learndash"]
3 changes: 3 additions & 0 deletions tools/slides-to-learndash/slides_to_learndash/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""PPTX → LearnDash CSV tooling."""

__version__ = "0.1.0"
Loading
Loading