[ANN] StippleCodeMirror.jl - A highlighting editor for the GenieFramework

I’m happy to announce the release of StippleCodeMirror.jl, a new package in the GenieFramework ecosystem that brings the powerful CodeMirror code editor to your web apps, with built-in syntax highlighting for Julia, Python, and JavaScript β€” and support for many more languages with minimal setup.

Julia syntax highlighting is supported both via CodeMirror’s native mode or using OhMyREPL color schemes server-side.

:light_bulb: Use case highlight: StippleCodeMirror.jl can be especially useful for editing server-side files in environments where direct file access is limited or restricted.

Beyond its functionality, this package also serves as a reference for integrating external JavaScript libraries into the GenieFramework in a clean, reactive, and idiomatic way.

:backhand_index_pointing_right: Give it a try and feel free to share feedback or contributions!

7 Likes

This is a useful feature! It can be used to build page editors like Gutenberg, Elementor, or Shopware’s Shopping Experiences, enabling interactive editing interfaces within a web app.

(It would be great if GenieFramework, or a third party, offered a Docker container preconfigured with user management and a page builder. This would make deployment much easier for those who aren’t deeply familiar with Genie’s source code.)

Interesting thought, could be be more precise in what you think the docker image should do? How would you want to use that image?
Maybe a better idea would be a template that contains a preconfigured Genie app with user management and an admin page and including a Dockerfile to build your own image?

This I already have, but it’s not (yet?) published. It’s built using the source code of GenieAuthentication and GenieAuthorisation, but has been heavily modified. I still need to complete the password reset functionality via email and implement multi-factor authentication and more security things.

xxx@xxx:~/.julia/dev/AuthPlugin$ tree # Small adjustments
β”œβ”€β”€ files
β”‚   β”œβ”€β”€ app
β”‚   β”‚   β”œβ”€β”€ layouts
β”‚   β”‚   β”‚   β”œβ”€β”€ authapp.jl.html
β”‚   β”‚   β”‚   └── authapp_stipple.jl.html
β”‚   β”‚   └── resources
β”‚   β”‚       β”œβ”€β”€ accounts
β”‚   β”‚       β”‚   β”œβ”€β”€ AccountsController.jl
β”‚   β”‚       β”‚   └── views
β”‚   β”‚       β”‚       └── account_index.jl.html
β”‚   β”‚       β”œβ”€β”€ admins
β”‚   β”‚       β”‚   β”œβ”€β”€ AdminsController.jl
β”‚   β”‚       β”‚   └── views
β”‚   β”‚       β”‚       └── index.jl.html
β”‚   β”‚       β”œβ”€β”€ legals
β”‚   β”‚       β”‚   β”œβ”€β”€ LegalsController.jl
β”‚   β”‚       β”‚   └── views
β”‚   β”‚       β”‚       β”œβ”€β”€ imprint.jl.html
β”‚   β”‚       β”‚       └── privacy.jl.html
β”‚   β”‚       β”œβ”€β”€ mains
β”‚   β”‚       β”‚   β”œβ”€β”€ MainsController.jl
β”‚   β”‚       β”‚   └── views
β”‚   β”‚       β”‚       └── index.jl.html
β”‚   β”‚       └── panels
β”‚   β”‚           β”œβ”€β”€ PanelsController.jl
β”‚   β”‚           └── views
β”‚   β”‚               └── panels.jl
β”‚   β”œβ”€β”€ config
β”‚   β”‚   └── initializers
β”‚   β”‚       └── authplugin_init.jl
β”‚   β”œβ”€β”€ locales
β”‚   β”‚   β”œβ”€β”€ de
β”‚   β”‚   └── en
β”‚   β”‚       β”œβ”€β”€ accounts.toml
β”‚   β”‚       β”œβ”€β”€ admins.toml
β”‚   β”‚       β”œβ”€β”€ layout.toml
β”‚   β”‚       β”œβ”€β”€ legals.toml
β”‚   β”‚       └── mains.toml
β”‚   β”œβ”€β”€ plugins
β”‚   β”‚   └── authplugin.jl
β”‚   └── public
β”‚       └── css
β”‚           β”œβ”€β”€ authpluginstyle.css
β”œβ”€β”€ index.md
β”œβ”€β”€ LICENSE
β”œβ”€β”€ Manifest.toml
β”œβ”€β”€ Project.toml
β”œβ”€β”€ README.md
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ Authentication.jl
β”‚   β”œβ”€β”€ Authorisation.jl
β”‚   β”œβ”€β”€ AuthPlugin.jl
β”‚   β”œβ”€β”€ install.jl
β”‚   β”œβ”€β”€ internal
β”‚   β”‚   β”œβ”€β”€ AuthautoRoutes.jl
β”‚   β”‚   β”œβ”€β”€ db
β”‚   β”‚   β”‚   β”œβ”€β”€ migrations
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2019052410085235_create_table_users.jl
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2021061519495560_create_table_roles.jl
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2021061519503270_create_table_permissions.jl
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2021061519532446_create_table_roles_users.jl
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ 2021061519540214_create_table_permissions_roles.jl
β”‚   β”‚   β”‚   β”‚   └── 2024010112000000_create_table_bananas.jl
β”‚   β”‚   β”‚   └── seeds
β”‚   β”‚   β”‚       └── AuthSeeds.jl
β”‚   β”‚   β”œβ”€β”€ helpers
β”‚   β”‚   β”‚   └── AuthenticationViewHelper.jl
β”‚   β”‚   β”œβ”€β”€ locales
β”‚   β”‚   β”‚   β”œβ”€β”€ de
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ authentication.toml
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ mailers.toml
β”‚   β”‚   β”‚   β”‚   └── users.toml
β”‚   β”‚   β”‚   β”œβ”€β”€ en
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ authentication.toml
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ mailers.toml
β”‚   β”‚   β”‚   β”‚   └── users.toml
β”‚   β”‚   β”‚   β”œβ”€β”€ es
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ authentication.toml
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ mailers.toml
β”‚   β”‚   β”‚   β”‚   └── users.toml
β”‚   β”‚   β”‚   └── fr
β”‚   β”‚   β”‚       β”œβ”€β”€ authentication.toml
β”‚   β”‚   β”‚       β”œβ”€β”€ mailers.toml
β”‚   β”‚   β”‚       └── users.toml
β”‚   β”‚   └── resources
β”‚   β”‚       β”œβ”€β”€ authentication
β”‚   β”‚       β”‚   β”œβ”€β”€ AuthenticationController.jl
β”‚   β”‚       β”‚   └── views
β”‚   β”‚       β”‚       β”œβ”€β”€ forgot.jl.html
β”‚   β”‚       β”‚       β”œβ”€β”€ login.jl.html
β”‚   β”‚       β”‚       β”œβ”€β”€ register.jl.html
β”‚   β”‚       β”‚       β”œβ”€β”€ reset.jl.html
β”‚   β”‚       β”‚       └── success.jl.html
β”‚   β”‚       └── users
β”‚   β”‚           β”œβ”€β”€ UsersController.jl
β”‚   β”‚           β”œβ”€β”€ Users.jl
β”‚   β”‚           β”œβ”€β”€ UsersValidator.jl
β”‚   β”‚           └── views
β”‚   β”‚               β”œβ”€β”€ admin_access_index.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_permissions_edit.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_permissions_index.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_permissions_new.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_roles_edit.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_roles_index.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_roles_new.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_users_edit.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_users_index.jl.html
β”‚   β”‚               β”œβ”€β”€ admin_users_new.jl.html
β”‚   β”‚               β”œβ”€β”€ edit.jl.html
β”‚   β”‚               └── index.jl.html
β”‚   β”œβ”€β”€ Mailers.jl
β”‚   └── SearchLightExt.jl
└── test
    └── runtests.jl

This app basically keeps most of the user management in the plugin, so updating is easier. The one how installs it can modify the accounts and admins resources and has some other nice template files to edit.

Installing is simple:

julia> using Genie, AuthPlugin, SearchLight

julia> # Create a new MVC APP with Genie Generator

julia> AuthPlugin.install(@__DIR__; force=true)

julia> SearchLight.Migrations.init()

julia>  AuthPlugin.SearchLightExt.all_up_for!!(AuthPlugin) # run all plugin migrations

julia> AuthPlugin.seed_admin_account!() # create an β€œadmin” user

julia> AuthPlugin.seed_permissions_and_roles!() # populate users/roles/perms

The layout can be modified by adjusting a constant dict using plugins/authplugin.jl in his app.

AuthPlugin.LAYOUT[:user] = "authapp.jl.html" # "authapp_stipple.jl.html"
AuthPlugin.LAYOUT[:admin] = "authapp.jl.html" # "authapp_stipple.jl.html"

and so forth…

I also have a nice bash script for deployment which does basically this (the real script is a little different):

  • Local: Prepare Secrets

    • Reads API‐keys and the secret from ~/.bashrc
    • Writes each to ~/.secrets/VAR.txt with 600 perms
  • Local: Build & Package

    • Builds a Docker image (thisapp:latest) from the project directory
    • Saves it to thisapp.tar
  • Local β†’ Remote: Transfer

    • Uses scp (one‑time SSH login with key) to copy thisapp.tar to server:~/thisapp
    • Also copies the prepared .secrets directory to the remote host
  • Remote: Deploy
    Over a single ssh server <<EOF…EOF block it does:

    • cd ~/deploy
    • docker compose down (stop/remove old container)
    • docker rmi -f thisapp (remove old image)
    • docker load -i thisapp.tar && rm thisapp.tar (load new image & delete tar)
    • docker compose up -d (start new container)
  • Local: Cleanup

    • Deletes the local thisapp.tar

But I do believe that having a ready container where you only need to define a new Stipple app and can adjust other pages via a pagebuilder like in Wordpress is easier for most. And this is where your new package might be handy.

Then one just needs to have a docker file like this (untested) in a folder and the respective Caddyfile:

services:
  web:
    image: genieframework:latest
    container_name: genieframework
    restart: always
    environment:
      GENIE_ENV: prod
    volumes:
      - genie_db:/app/db/prod.sqlite3
      #- geniedata ?
    secrets:
      - genie_secret
      - openai_api_key

  caddy:
    image: caddy:latest
    container_name: caddy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

secrets:
  genie_secret:
    file: /home/thisuser/.secrets/GENIE_SECRET.txt
  openai_api_key:
    file: /home/thisuser/.secrets/OPENAI_API_KEY.txt

volumes:
  db:
  #geniedata: ?
  caddy_data:
  caddy_config:
thiswebsite.de {
    reverse_proxy web:8000
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    }
    tls max.mustermann@musterdomain.de
}

and one has just to do:

cd ~/genieapp
docker compose up -d

But this is kind of a whole new product based on the Framework and needs much time to be implemented.

Here are some screenshots. I know that the design could be better… but it’s work in progress.





3 Likes