mirror of
https://gitlab.com/octtspacc/staticoso
synced 2025-03-13 01:30:10 +01:00
Internal improvements
This commit is contained in:
parent
4cbb29d6ed
commit
cea67fe245
@ -108,17 +108,17 @@ def Main(Args, FeedEntries):
|
|||||||
HavePages, HavePosts = False, False
|
HavePages, HavePosts = False, False
|
||||||
SiteConf = LoadConfFile('Site.ini')
|
SiteConf = LoadConfFile('Site.ini')
|
||||||
|
|
||||||
ConfigLogging(OptionChoose(None, Args.Logging, ReadConf(SiteConf, 'Main', 'Logging')))
|
ConfigLogging(DefConfOptChoose('Logging', Args.Logging, ReadConf(SiteConf, 'Main', 'Logging')))
|
||||||
|
|
||||||
#if Args.InputDir:
|
#if Args.InputDir:
|
||||||
# os.chdir(Args.InputDir)
|
# os.chdir(Args.InputDir)
|
||||||
# print(f"[I] Current directory: {Args.InputDir}")
|
# print(f"[I] Current directory: {Args.InputDir}")
|
||||||
|
|
||||||
SiteName = Flags['SiteName'] = OptionChoose('', Args.SiteName, ReadConf(SiteConf, 'Site', 'Name'))
|
SiteName = Flags['SiteName'] = OptChoose('', Args.SiteName, ReadConf(SiteConf, 'Site', 'Name'))
|
||||||
if SiteName:
|
if SiteName:
|
||||||
logging.info(f"Compiling: {SiteName}")
|
logging.info(f"Compiling: {SiteName}")
|
||||||
|
|
||||||
OutDir = Flags['OutDir'] = OptionChoose('public', Args.OutputDir, ReadConf(SiteConf, 'Site', 'OutputDir'))
|
OutDir = Flags['OutDir'] = DefConfOptChoose('OutDir', Args.OutputDir, ReadConf(SiteConf, 'Site', 'OutputDir'))
|
||||||
OutDir = Flags['OutDir'] = OutDir.removesuffix('/')
|
OutDir = Flags['OutDir'] = OutDir.removesuffix('/')
|
||||||
CheckSafeOutDir(OutDir)
|
CheckSafeOutDir(OutDir)
|
||||||
logging.info(f"Outputting to: {OutDir}/")
|
logging.info(f"Outputting to: {OutDir}/")
|
||||||
@ -126,43 +126,43 @@ def Main(Args, FeedEntries):
|
|||||||
Threads = Args.Threads if Args.Threads else 0
|
Threads = Args.Threads if Args.Threads else 0
|
||||||
DiffBuild = Args.DiffBuild if Args.DiffBuild else False
|
DiffBuild = Args.DiffBuild if Args.DiffBuild else False
|
||||||
|
|
||||||
BlogName = Flags['BlogName'] = OptionChoose('', Args.BlogName, ReadConf(SiteConf, 'Site', 'BlogName'))
|
BlogName = Flags['BlogName'] = OptChoose('', Args.BlogName, ReadConf(SiteConf, 'Site', 'BlogName'))
|
||||||
SiteTagline = Flags['SiteTagline'] = OptionChoose('', Args.SiteTagline, ReadConf(SiteConf, 'Site', 'Tagline'))
|
SiteTagline = Flags['SiteTagline'] = OptChoose('', Args.SiteTagline, ReadConf(SiteConf, 'Site', 'Tagline'))
|
||||||
SiteTemplate = Flags['SiteTemplate'] = OptionChoose('Default.html', Args.SiteTemplate, ReadConf(SiteConf, 'Site', 'Template'))
|
SiteTemplate = Flags['SiteTemplate'] = DefConfOptChoose('SiteTemplate', Args.SiteTemplate, ReadConf(SiteConf, 'Site', 'Template'))
|
||||||
SiteDomain = Flags['SiteDomain'] = OptionChoose('', Args.SiteDomain, ReadConf(SiteConf, 'Site', 'Domain'))
|
SiteDomain = Flags['SiteDomain'] = OptChoose('', Args.SiteDomain, ReadConf(SiteConf, 'Site', 'Domain'))
|
||||||
SiteRoot = Flags['SiteRoot'] = OptionChoose('/', Args.SiteRoot, ReadConf(SiteConf, 'Site', 'Root'))
|
SiteRoot = Flags['SiteRoot'] = OptChoose('/', Args.SiteRoot, ReadConf(SiteConf, 'Site', 'Root'))
|
||||||
SiteLang = Flags['SiteLang'] = OptionChoose('en', Args.SiteLang, ReadConf(SiteConf, 'Site', 'Lang'))
|
SiteLang = Flags['SiteLang'] = DefConfOptChoose('SiteLang', Args.SiteLang, ReadConf(SiteConf, 'Site', 'Lang'))
|
||||||
|
|
||||||
Sorting = Flags['Sorting'] = literal_eval(OptionChoose('{}', Args.Sorting, ReadConf(SiteConf, 'Site', 'Sorting')))
|
Sorting = Flags['Sorting'] = literal_eval(OptChoose('{}', Args.Sorting, ReadConf(SiteConf, 'Site', 'Sorting')))
|
||||||
Sorting = Flags['Sorting'] = SetSorting(Sorting)
|
Sorting = Flags['Sorting'] = SetSorting(Sorting)
|
||||||
|
|
||||||
NoScripts = Flags['NoScripts'] = StringBoolChoose(False, Args.NoScripts, ReadConf(SiteConf, 'Site', 'NoScripts'))
|
NoScripts = Flags['NoScripts'] = StrBoolChoose(False, Args.NoScripts, ReadConf(SiteConf, 'Site', 'NoScripts'))
|
||||||
FolderRoots = Flags['FolderRoots'] = literal_eval(Args.FolderRoots) if Args.FolderRoots else {}
|
FolderRoots = Flags['FolderRoots'] = literal_eval(Args.FolderRoots) if Args.FolderRoots else {}
|
||||||
|
|
||||||
ActivityPubTypeFilter = Flags['ActivityPubTypeFilter'] = OptionChoose('Post', Args.ActivityPubTypeFilter, ReadConf(SiteConf, 'ActivityPub', 'TypeFilter'))
|
ActivityPubTypeFilter = Flags['ActivityPubTypeFilter'] = DefConfOptChoose('ActivityPubTypeFilter', Args.ActivityPubTypeFilter, ReadConf(SiteConf, 'ActivityPub', 'TypeFilter'))
|
||||||
ActivityPubHoursLimit = Flags['ActivityPubHoursLimit'] = OptionChoose(168, Args.ActivityPubHoursLimit, ReadConf(SiteConf, 'ActivityPub', 'HoursLimit'))
|
ActivityPubHoursLimit = Flags['ActivityPubHoursLimit'] = DefConfOptChoose('ActivityPubHoursLimit', Args.ActivityPubHoursLimit, ReadConf(SiteConf, 'ActivityPub', 'HoursLimit'))
|
||||||
|
|
||||||
MastodonURL = Flags['MastodonURL'] = OptionChoose('', Args.MastodonURL, ReadConf(SiteConf, 'Mastodon', 'URL'))
|
MastodonURL = Flags['MastodonURL'] = OptChoose('', Args.MastodonURL, ReadConf(SiteConf, 'Mastodon', 'URL'))
|
||||||
MastodonToken = Flags['MastodonToken'] = OptionChoose('', Args.MastodonToken, ReadConf(SiteConf, 'Mastodon', 'Token'))
|
MastodonToken = Flags['MastodonToken'] = OptChoose('', Args.MastodonToken, ReadConf(SiteConf, 'Mastodon', 'Token'))
|
||||||
|
|
||||||
MarkdownExts = Flags['MarkdownExts'] = literal_eval(OptionChoose(str(MarkdownExtsDefault), Args.MarkdownExts, ReadConf(SiteConf, 'Markdown', 'Exts')))
|
MarkdownExts = Flags['MarkdownExts'] = literal_eval(OptionChoose(str(MarkdownExtsDefault), Args.MarkdownExts, ReadConf(SiteConf, 'Markdown', 'Exts')))
|
||||||
SitemapOutput = Flags['SitemapOutput'] = StringBoolChoose(True, Args.SitemapOutput, ReadConf(SiteConf, 'Sitemap', 'Output'))
|
SitemapOutput = Flags['SitemapOutput'] = StrBoolChoose(True, Args.SitemapOutput, ReadConf(SiteConf, 'Sitemap', 'Output'))
|
||||||
|
|
||||||
Minify = Flags['Minify'] = StringBoolChoose(False, Args.Minify, ReadConf(SiteConf, 'Minify', 'Minify'))
|
Minify = Flags['Minify'] = StrBoolChoose(False, Args.Minify, ReadConf(SiteConf, 'Minify', 'Minify'))
|
||||||
MinifyKeepComments = Flags['MinifyKeepComments'] = StringBoolChoose(False, Args.MinifyKeepComments, ReadConf(SiteConf, 'Minify', 'KeepComments'))
|
MinifyKeepComments = Flags['MinifyKeepComments'] = StrBoolChoose(False, Args.MinifyKeepComments, ReadConf(SiteConf, 'Minify', 'KeepComments'))
|
||||||
|
|
||||||
ImgAltToTitle = Flags['ImgAltToTitle'] = StringBoolChoose(True, Args.ImgAltToTitle, ReadConf(SiteConf, 'Site', 'ImgAltToTitle'))
|
ImgAltToTitle = Flags['ImgAltToTitle'] = StrBoolChoose(True, Args.ImgAltToTitle, ReadConf(SiteConf, 'Site', 'ImgAltToTitle'))
|
||||||
ImgTitleToAlt = Flags['ImgTitleToAlt'] = StringBoolChoose(False, Args.ImgTitleToAlt, ReadConf(SiteConf, 'Site', 'ImgTitleToAlt'))
|
ImgTitleToAlt = Flags['ImgTitleToAlt'] = StrBoolChoose(False, Args.ImgTitleToAlt, ReadConf(SiteConf, 'Site', 'ImgTitleToAlt'))
|
||||||
HTMLFixPre = Flags['HTMLFixPre'] = StringBoolChoose(False, Args.HTMLFixPre, ReadConf(SiteConf, 'Site', 'HTMLFixPre'))
|
HTMLFixPre = Flags['HTMLFixPre'] = StrBoolChoose(False, Args.HTMLFixPre, ReadConf(SiteConf, 'Site', 'HTMLFixPre'))
|
||||||
|
|
||||||
CategoriesAutomatic = Flags['CategoriesAutomatic'] = StringBoolChoose(False, Args.CategoriesAutomatic, ReadConf(SiteConf, 'Categories', 'Automatic'))
|
CategoriesAutomatic = Flags['CategoriesAutomatic'] = StrBoolChoose(False, Args.CategoriesAutomatic, ReadConf(SiteConf, 'Categories', 'Automatic'))
|
||||||
CategoriesUncategorized = Flags['CategoriesUncategorized'] = OptionChoose('Uncategorized', Args.CategoriesUncategorized, ReadConf(SiteConf, 'Categories', 'Uncategorized'))
|
CategoriesUncategorized = Flags['CategoriesUncategorized'] = DefConfOptChoose('CategoriesUncategorized', Args.CategoriesUncategorized, ReadConf(SiteConf, 'Categories', 'Uncategorized'))
|
||||||
|
|
||||||
GemtextOutput = Flags['GemtextOutput'] = StringBoolChoose(False, Args.GemtextOutput, ReadConf(SiteConf, 'Gemtext', 'Output'))
|
GemtextOutput = Flags['GemtextOutput'] = StrBoolChoose(False, Args.GemtextOutput, ReadConf(SiteConf, 'Gemtext', 'Output'))
|
||||||
GemtextHeader = Flags['GemtextHeader'] = Args.GemtextHeader if Args.GemtextHeader else ReadConf(SiteConf, 'Gemtext', 'Header') if ReadConf(SiteConf, 'Gemtext', 'Header') else f"# {SiteName}\n\n" if SiteName else ''
|
GemtextHeader = Flags['GemtextHeader'] = Args.GemtextHeader if Args.GemtextHeader else ReadConf(SiteConf, 'Gemtext', 'Header') if ReadConf(SiteConf, 'Gemtext', 'Header') else f"# {SiteName}\n\n" if SiteName else ''
|
||||||
|
|
||||||
FeedCategoryFilter = Flags['FeedCategoryFilter'] = OptionChoose('Blog', Args.FeedCategoryFilter, ReadConf(SiteConf, 'Feed', 'CategoryFilter'))
|
FeedCategoryFilter = Flags['FeedCategoryFilter'] = DefConfOptChoose('FeedCategoryFilter', Args.FeedCategoryFilter, ReadConf(SiteConf, 'Feed', 'CategoryFilter'))
|
||||||
FeedEntries = Flags['FeedEntries'] = int(FeedEntries) if (FeedEntries or FeedEntries == 0) and FeedEntries != 'Default' else int(ReadConf(SiteConf, 'Feed', 'Entries')) if ReadConf(SiteConf, 'Feed', 'Entries') else 10
|
FeedEntries = Flags['FeedEntries'] = int(FeedEntries) if (FeedEntries or FeedEntries == 0) and FeedEntries != 'Default' else int(ReadConf(SiteConf, 'Feed', 'Entries')) if ReadConf(SiteConf, 'Feed', 'Entries') else DefConf['FeedEntries']
|
||||||
|
|
||||||
DynamicParts = Flags['DynamicParts'] = literal_eval(OptionChoose('{}', Args.DynamicParts, ReadConf(SiteConf, 'Site', 'DynamicParts')))
|
DynamicParts = Flags['DynamicParts'] = literal_eval(OptionChoose('{}', Args.DynamicParts, ReadConf(SiteConf, 'Site', 'DynamicParts')))
|
||||||
DynamicPartsText = Snippets['DynamicParts'] = LoadFromDir('DynamicParts', ['*.htm', '*.html'])
|
DynamicPartsText = Snippets['DynamicParts'] = LoadFromDir('DynamicParts', ['*.htm', '*.html'])
|
||||||
|
@ -10,6 +10,18 @@
|
|||||||
import configparser
|
import configparser
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
|
||||||
|
DefConf = {
|
||||||
|
'Logging': 20,
|
||||||
|
'OutDir': 'public',
|
||||||
|
'SiteLang': 'en',
|
||||||
|
'SiteTemplate': 'Default.html',
|
||||||
|
'ActivityPubTypeFilter': 'Post',
|
||||||
|
'ActivityPubHoursLimit': 168,
|
||||||
|
'CategoriesUncategorized': 'Uncategorized',
|
||||||
|
'FeedCategoryFilter': 'Blog',
|
||||||
|
'FeedEntries': 10
|
||||||
|
}
|
||||||
|
|
||||||
def LoadConfFile(File):
|
def LoadConfFile(File):
|
||||||
Conf = configparser.ConfigParser()
|
Conf = configparser.ConfigParser()
|
||||||
Conf.optionxform = str
|
Conf.optionxform = str
|
||||||
@ -37,8 +49,15 @@ def EvalOpt(Opt):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# TODO: Cleaning
|
||||||
|
|
||||||
def OptionChoose(Default, Primary, Secondary, Tertiary=None):
|
def OptionChoose(Default, Primary, Secondary, Tertiary=None):
|
||||||
return Primary if Primary != None else Secondary if Secondary != None else Tertiary if Tertiary != None else Default
|
return Primary if Primary != None else Secondary if Secondary != None else Tertiary if Tertiary != None else Default
|
||||||
|
def OptChoose(Default, Primary, Secondary, Tertiary=None):
|
||||||
|
return OptionChoose(Default, Primary, Secondary, Tertiary=None)
|
||||||
|
|
||||||
|
def DefConfOptChoose(Key, Primary, Secondary):
|
||||||
|
return OptChoose(DefConf[Key], Primary, Secondary)
|
||||||
|
|
||||||
def StringBoolChoose(Default, Primary, Secondary):
|
def StringBoolChoose(Default, Primary, Secondary):
|
||||||
Var = Default
|
Var = Default
|
||||||
@ -51,3 +70,5 @@ def StringBoolChoose(Default, Primary, Secondary):
|
|||||||
elif Check in ('False', 'None'):
|
elif Check in ('False', 'None'):
|
||||||
Var = False
|
Var = False
|
||||||
return Var
|
return Var
|
||||||
|
def StrBoolChoose(Default, Primary, Secondary):
|
||||||
|
return StringBoolChoose(Default, Primary, Secondary)
|
||||||
|
@ -76,11 +76,11 @@ def GetImage(Meta, BodyImage, Prefer='MetaImage'):
|
|||||||
|
|
||||||
def MakeContentHeader(Meta, Locale, Categories=''):
|
def MakeContentHeader(Meta, Locale, Categories=''):
|
||||||
Header = ''
|
Header = ''
|
||||||
for i in ['CreatedOn', 'EditedOn']:
|
for e in ['CreatedOn', 'EditedOn']:
|
||||||
if Meta[i]:
|
if Meta[e]:
|
||||||
Header += f'{Locale[i]}: {Meta[i]}<br>'
|
Header += f'<span class="staticoso-ContentHeader-{e}"><span class="staticoso-Label">{Locale[e]}</span>: <span class="staticoso-Value">{Meta[e]}</span></span><br>'
|
||||||
if Categories:
|
if Categories:
|
||||||
Header += f"{Locale['Categories']}:{Categories.removesuffix(' ')}<br>"
|
Header += f'<span class="staticoso-ContentHeader-Categories"><span class="staticoso-Label">{Locale["Categories"]}</span>:<span class="staticoso-Value">{Categories.removesuffix(" ")}</span></span><br>'
|
||||||
return f'<p>{Header}</p>'
|
return f'<p>{Header}</p>'
|
||||||
|
|
||||||
def MakeCategoryLine(File, Meta):
|
def MakeCategoryLine(File, Meta):
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from Modules.Config import *
|
||||||
|
|
||||||
LoggingFormat = '[%(levelname)s] %(message)s'
|
LoggingFormat = '[%(levelname)s] %(message)s'
|
||||||
LoggingLevels = {
|
LoggingLevels = {
|
||||||
@ -25,7 +26,7 @@ def SetupLogging(Level):
|
|||||||
logging.addLevelName(40, 'E')
|
logging.addLevelName(40, 'E')
|
||||||
|
|
||||||
def ConfigLogging(Level):
|
def ConfigLogging(Level):
|
||||||
Num = 20
|
Num = DefConf['Logging']
|
||||||
if Level:
|
if Level:
|
||||||
if Level.isdecimal():
|
if Level.isdecimal():
|
||||||
Num = int(Level)
|
Num = int(Level)
|
||||||
|
@ -269,6 +269,8 @@ def PatchHTML(File, HTML, StaticPartsText, DynamicParts, DynamicPartsText, HTMLP
|
|||||||
Title = GetTitle(File.split('/')[-1], Meta, Titles, 'MetaTitle', BlogName)
|
Title = GetTitle(File.split('/')[-1], Meta, Titles, 'MetaTitle', BlogName)
|
||||||
Description = GetDescription(Meta, BodyDescription, 'MetaDescription')
|
Description = GetDescription(Meta, BodyDescription, 'MetaDescription')
|
||||||
Image = GetImage(Meta, BodyImage, 'MetaImage')
|
Image = GetImage(Meta, BodyImage, 'MetaImage')
|
||||||
|
ContentHeader = MakeContentHeader(Meta, Locale, MakeCategoryLine(File, Meta))
|
||||||
|
TimeNow = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||||
|
|
||||||
for Line in HTML.splitlines():
|
for Line in HTML.splitlines():
|
||||||
Line = Line.lstrip().rstrip()
|
Line = Line.lstrip().rstrip()
|
||||||
@ -315,10 +317,10 @@ def PatchHTML(File, HTML, StaticPartsText, DynamicParts, DynamicPartsText, HTMLP
|
|||||||
'<staticoso:PageStyle>': Meta['Style'],
|
'<staticoso:PageStyle>': Meta['Style'],
|
||||||
'[staticoso:Page:Content]': Content,
|
'[staticoso:Page:Content]': Content,
|
||||||
'<staticoso:PageContent>': Content,
|
'<staticoso:PageContent>': Content,
|
||||||
'[staticoso:Page:ContentInfo]': MakeContentHeader(Meta, Locale, MakeCategoryLine(File, Meta)),
|
'[staticoso:Page:ContentInfo]': ContentHeader,
|
||||||
'<staticoso:PageContentInfo>': MakeContentHeader(Meta, Locale, MakeCategoryLine(File, Meta)),
|
'<staticoso:PageContentInfo>': ContentHeader,
|
||||||
'[staticoso:BuildTime]': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
'[staticoso:BuildTime]': TimeNow,
|
||||||
'<staticoso:BuildTime>': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
'<staticoso:BuildTime>': TimeNow,
|
||||||
'<staticoso:SiteDomain>': SiteDomain,
|
'<staticoso:SiteDomain>': SiteDomain,
|
||||||
'[staticoso:Site:Name]': SiteName,
|
'[staticoso:Site:Name]': SiteName,
|
||||||
'<staticoso:SiteName>': SiteName,
|
'<staticoso:SiteName>': SiteName,
|
||||||
|
13
TODO
13
TODO
@ -1,3 +1,5 @@
|
|||||||
|
- Set categories for all files in a folder and subfolder
|
||||||
|
- Pages with lists of all posts and all pages (? the folder categories thing might make it redundant?)
|
||||||
- Fix title detection:
|
- Fix title detection:
|
||||||
- Don't consider titles in code/comment blocks
|
- Don't consider titles in code/comment blocks
|
||||||
- Consider titles in lists (title marker preceded by chars)
|
- Consider titles in lists (title marker preceded by chars)
|
||||||
@ -8,7 +10,7 @@
|
|||||||
- Posts in draft state (will not be compiled) / show unlisted status for posts with Index = False
|
- Posts in draft state (will not be compiled) / show unlisted status for posts with Index = False
|
||||||
- Check if external tools (pug-cli, html2gmi) are installed
|
- Check if external tools (pug-cli, html2gmi) are installed
|
||||||
- Static code syntax highlighing
|
- Static code syntax highlighing
|
||||||
- Override internal HTML snippets (meta lines, page lists, ...) with config file in Templates/NAME.ini
|
- Override internal HTML snippets (meta lines, page lists, redirects, ...) with config file in Templates/NAME.ini
|
||||||
- Specify input folder(s)
|
- Specify input folder(s)
|
||||||
- Show page size/words/time in meta line
|
- Show page size/words/time in meta line
|
||||||
- Add feed support for diary-like pages
|
- Add feed support for diary-like pages
|
||||||
@ -18,10 +20,9 @@
|
|||||||
- Handle file extensions with any case sensitivity, not just lowercase; currently the bulk of the issue is finding the files on disk
|
- Handle file extensions with any case sensitivity, not just lowercase; currently the bulk of the issue is finding the files on disk
|
||||||
- Test sorting by date for files not starting with date, and dated folders
|
- Test sorting by date for files not starting with date, and dated folders
|
||||||
- Fix arguments - some are only callable from CLI and not Site.ini - make them coherent with INI categories
|
- Fix arguments - some are only callable from CLI and not Site.ini - make them coherent with INI categories
|
||||||
- Accept Macros as CLI arguments
|
- Accept Macros as CLI arguments + Deprecate FolderRoots (Macros make it redundant)
|
||||||
- Deprecate FolderRoots (Macros make it redundant)
|
|
||||||
- Fix ordering menu in Site.ini (not working for inner pages)
|
- Fix ordering menu in Site.ini (not working for inner pages)
|
||||||
- Feed generation without native libraries
|
- Feed generation optionally without native libraries
|
||||||
- JSON feeds
|
- JSON feeds
|
||||||
- Full XML sitemap
|
- Full XML sitemap
|
||||||
- SCSS support
|
- SCSS support
|
||||||
@ -32,6 +33,6 @@
|
|||||||
- Exporting the entire site text as JSON for full-text search tools
|
- Exporting the entire site text as JSON for full-text search tools
|
||||||
- Automatic guessing of .htm/.html extension for declarations of templates and stuff
|
- Automatic guessing of .htm/.html extension for declarations of templates and stuff
|
||||||
- Exporting sites to different formats (?) (single-page HTML, PDF, EPUB, ...)
|
- Exporting sites to different formats (?) (single-page HTML, PDF, EPUB, ...)
|
||||||
- Disable only ActivityPub feed for a page
|
- Disable ActivityPub feed for a specific page
|
||||||
|
- Symlinks and direct copies as option instead of redirect pages
|
||||||
- Automatic redirects/symlinks for making pages work without .html suffix
|
- Automatic redirects/symlinks for making pages work without .html suffix
|
||||||
- Override URL for a page / Add custom redirects for it
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user