Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9b947cd
Changed convert format to GFM to support checkboxes
reactorcoremeltdown Feb 28, 2023
a8c7514
Not stripping form related tags to allow checkboxes
reactorcoremeltdown Feb 28, 2023
5a839bb
Revert "Changed convert format to GFM to support checkboxes"
reactorcoremeltdown Mar 27, 2023
fdebdfc
Trying to move buttons to the top
reactorcoremeltdown Mar 27, 2023
371e08f
Adjusted toolbar
reactorcoremeltdown Mar 27, 2023
57c4b71
Fixed wrong placement of buttons
reactorcoremeltdown Mar 27, 2023
aa1f8b5
Updated buttons and toolbars
reactorcoremeltdown Mar 27, 2023
973973c
Wrong button placement
reactorcoremeltdown Mar 27, 2023
a738b8f
Moving things around
reactorcoremeltdown Mar 27, 2023
4c21990
Trying to align navbar items
reactorcoremeltdown Mar 27, 2023
30f9966
Trying to put the save button at the top
reactorcoremeltdown Mar 27, 2023
5ac7eec
Revert "Trying to put the save button at the top"
reactorcoremeltdown Mar 27, 2023
de58ecb
Updated button classes
reactorcoremeltdown Mar 27, 2023
562e3fa
Playing around aligning items
reactorcoremeltdown Mar 27, 2023
8e4fa86
Moving stuff around
reactorcoremeltdown Mar 27, 2023
cd97383
Adjusted layout
reactorcoremeltdown Mar 27, 2023
3912b43
Tried to wrap visible buttons block into container div
reactorcoremeltdown Mar 27, 2023
2f68c1a
Revert "Tried to wrap visible buttons block into container div"
reactorcoremeltdown Mar 27, 2023
8c1a278
Poking around navbar
reactorcoremeltdown Mar 27, 2023
da590ad
Removed the div
reactorcoremeltdown Mar 27, 2023
5e32959
Added svg save icon
reactorcoremeltdown Mar 27, 2023
b2638b3
Fixed floppy icon
reactorcoremeltdown Mar 27, 2023
c7a5aba
Changed blocks placement
reactorcoremeltdown Mar 29, 2023
a467785
Regrouping
reactorcoremeltdown Mar 29, 2023
785aa18
Making Delete button collapsible
reactorcoremeltdown Mar 29, 2023
2ff70b1
Changed spacing
reactorcoremeltdown Mar 29, 2023
637835d
Using nav-link class
reactorcoremeltdown Mar 29, 2023
b2a5dce
Changed link class
reactorcoremeltdown Mar 29, 2023
7a6c1b7
Updated button class
reactorcoremeltdown Mar 29, 2023
7fd33d9
Trying to wrap a button into a link
reactorcoremeltdown Mar 29, 2023
15711b3
Removed list items for context-related buttons
reactorcoremeltdown Mar 29, 2023
1372b2b
Trying without div
reactorcoremeltdown Mar 29, 2023
912438a
Added GFM and its dependencies
reactorcoremeltdown Apr 3, 2023
6ca7603
Added GFM and its dependencies
reactorcoremeltdown Apr 3, 2023
ecc4c0b
Merge branch 'codemirror-gfm' of github.com:reactorcoremeltdown/wikmd…
reactorcoremeltdown Apr 3, 2023
1d50d19
Added uploaded file permissions
reactorcoremeltdown May 1, 2023
e716a6a
Wrong object reference
reactorcoremeltdown May 1, 2023
1d726f6
Added an option to disable images cleanup
reactorcoremeltdown Sep 21, 2023
283a077
Merge branch 'toolbar-buttons'
reactorcoremeltdown Sep 21, 2023
e6098a7
Merge remote-tracking branch 'origin/explicit-file-perms'
reactorcoremeltdown Sep 21, 2023
44192c0
Updated config fields, added images_cleanup option
reactorcoremeltdown Sep 22, 2023
d958ca6
Initialized config structure for (un)protected routes
reactorcoremeltdown Jan 28, 2024
9a4f073
Trying to setup a protected route for image uploads
reactorcoremeltdown Feb 4, 2024
9ff4c3b
Fixed duplicated func name
reactorcoremeltdown Feb 4, 2024
ac01cf1
Fixed fn dupe
reactorcoremeltdown Feb 4, 2024
42a3318
Checking map
reactorcoremeltdown Feb 4, 2024
441e1cc
Added support for upload prefix
reactorcoremeltdown Oct 16, 2024
0656e70
Fixed borked config parameter
reactorcoremeltdown Oct 16, 2024
329ef9d
Create linked images by default
reactorcoremeltdown Oct 24, 2024
24b87b2
Fixed image linking
reactorcoremeltdown Oct 24, 2024
79fa0ca
Also use title variable for page title
reactorcoremeltdown Mar 17, 2025
19301b5
Bundled in some js and css assets
reactorcoremeltdown Mar 31, 2025
a86462b
Added link anchors
Jul 29, 2025
67ba86a
Added scrolling target to aid anchor.js
Jul 29, 2025
ecc71a7
Attached folder path to simplify browsing and creating records
Jan 8, 2026
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
14 changes: 14 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@
HOMEPAGE_DEFAULT = "homepage.md"
HOMEPAGE_TITLE_DEFAULT = "homepage"
IMAGES_ROUTE_DEFAULT = "img"
IMAGES_PROTECTED_ROUTE_DEFAULT = "img"
IMAGES_UPLOAD_PREFIX_DEFAULT = ""
IMAGES_FILE_UID = os.getuid()
IMAGES_FILE_GID = os.getgid()
IMAGES_FILE_MODE = '600'
IMAGES_CLEANUP = False

HIDE_FOLDER_IN_WIKI = []

PLUGINS = []
UNPROTECTED_ROUTES = []

PROTECT_EDIT_BY_PASSWORD = 0
PASSWORD_IN_SHA_256 = "0E9C700FAB2D5B03B0581D080E74A2D7428758FC82BD423824C6C11D6A7F155E" #pw: wikmd
Expand Down Expand Up @@ -87,6 +94,13 @@ def __init__(self):
self.homepage = os.getenv("HOMEPAGE") or yaml_config["homepage"] or HOMEPAGE_DEFAULT
self.homepage_title = os.getenv("HOMEPAGE_TITLE") or yaml_config["homepage_title"] or HOMEPAGE_TITLE_DEFAULT
self.images_route = os.getenv("IMAGES_ROUTE") or yaml_config["images_route"] or IMAGES_ROUTE_DEFAULT
self.images_protected_route = os.getenv("IMAGES_PROTECTED_ROUTE") or yaml_config["images_protected_route"] or IMAGES_PROTECTED_ROUTE_DEFAULT
self.unprotected_routes = os.getenv("UNPROTECTED_ROUTES")or yaml_config["unprotected_routes"] or UNPROTECTED_ROUTES
self.images_upload_prefix = os.getenv("IMAGES_UPLOAD_PREFIX") or yaml_config["images_upload_prefix"] or IMAGES_UPLOAD_PREFIX_DEFAULT
self.images_file_uid = os.getenv("IMAGES_FILE_UID") or yaml_config["images_file_uid"] or IMAGES_FILE_UID
self.images_file_gid = os.getenv("IMAGES_FILE_GID") or yaml_config["images_file_gid"] or IMAGES_FILE_GID
self.images_file_mode = os.getenv("IMAGES_FILE_MODE") or yaml_config["images_file_mode"] or IMAGES_FILE_MODE
self.images_cleanup = os.getenv("IMAGES_CLEANUP") or yaml_config["images_cleanup"] or IMAGES_CLEANUP

self.hide_folder_in_wiki = os.getenv("hide_folder_in_wiki")or yaml_config["hide_folder_in_wiki"] or HIDE_FOLDER_IN_WIKI

Expand Down
75 changes: 39 additions & 36 deletions image_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, app, cfg):
if not self.can_optimize and self.cfg.optimize_images in ["lossless", "lossy"]:
self.logger.error("To use image optimization webp and gif2webp need to be installed and in the $PATH. They could not be found.")

def save_images(self, file):
def save_images(self, file, images_path):
"""
Saves the image from the filepond upload.
The image is renamed to the hash of the content, so the image is immutable.
Expand All @@ -50,52 +50,55 @@ def save_images(self, file):
# Using base32 instead of urlsafe base64, because the Windows file system is case-insensitive
img_digest = b32encode(hasher.digest()).decode("utf-8").lower()[:-4]
hash_file_name = secure_filename(f"{original_file_name}-{img_digest}{img_extension}")
hash_file_path = os.path.join(self.images_path, hash_file_name)
hash_file_path = os.path.join(images_path, hash_file_name)

# We can skip writing the file if it already exists. It is the same file, because it has the same hash
if os.path.exists(hash_file_path):
self.logger.info(f"Image already exists '{img_file.filename}' as '{hash_file_name}'")
else:
self.logger.info(f"Saving image >>> '{img_file.filename}' as '{hash_file_name}' ...")
shutil.move(temp_file_path, hash_file_path)
os.chmod(hash_file_path, int(self.cfg.images_file_mode, base=8))
os.chown(hash_file_path, self.cfg.images_file_uid, self.cfg.images_file_gid)

return hash_file_name

def cleanup_images(self):
"""Deletes images not used by any page"""
saved_images = set(os.listdir(self.images_path))
# Don't delete .gitignore
saved_images.discard(".gitignore")

# Matches [*](/img/*) it does not matter if images_route is "/img" or "img"
image_link_pattern = fr"\[(.*?)\]\(({os.path.join('/', self.cfg.images_route)}.+?)\)"
image_link_regex = re.compile(image_link_pattern)
used_images = set()
# Searching for Markdown files
for root, sub_dir, files in os.walk(self.cfg.wiki_directory):
if os.path.join(self.cfg.wiki_directory, '.git') in root:
# We don't want to search there
continue
if self.images_path in root:
# Nothing interesting there too
continue
for filename in files:
path = os.path.join(root, filename)
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
matches = image_link_regex.findall(content)
for _caption, image_path in matches:
used_images.add(os.path.basename(image_path))
except:
self.logger.info(f"ignoring {path}")

not_used_images = saved_images.difference(used_images)
for not_used_image in not_used_images:
self.delete_image(not_used_image)

def delete_image(self, image_name):
image_path = safe_join(self.images_path, image_name)
if self.cfg.images_cleanup:
"""Deletes images not used by any page"""
saved_images = set(os.listdir(self.images_path))
# Don't delete .gitignore
saved_images.discard(".gitignore")

# Matches [*](/img/*) it does not matter if images_route is "/img" or "img"
image_link_pattern = fr"\[(.*?)\]\(({os.path.join('/', self.cfg.images_route)}.+?)\)"
image_link_regex = re.compile(image_link_pattern)
used_images = set()
# Searching for Markdown files
for root, sub_dir, files in os.walk(self.cfg.wiki_directory):
if os.path.join(self.cfg.wiki_directory, '.git') in root:
# We don't want to search there
continue
if self.images_path in root:
# Nothing interesting there too
continue
for filename in files:
path = os.path.join(root, filename)
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
matches = image_link_regex.findall(content)
for _caption, image_path in matches:
used_images.add(os.path.basename(image_path))
except:
self.logger.info(f"ignoring {path}")

not_used_images = saved_images.difference(used_images)
for not_used_image in not_used_images:
self.delete_image(not_used_image)

def delete_image(self, image_name, images_path):
image_path = safe_join(images_path, image_name)
self.logger.info(f"Deleting file >>> {image_path}")
try:
os.remove(image_path)
Expand Down
Loading