diff --git a/src/main/java/run/halo/app/infra/SystemSetting.java b/src/main/java/run/halo/app/infra/SystemSetting.java
index 0e7efa64c..a44d93a7a 100644
--- a/src/main/java/run/halo/app/infra/SystemSetting.java
+++ b/src/main/java/run/halo/app/infra/SystemSetting.java
@@ -27,4 +27,15 @@ public class SystemSetting {
private String post;
private String tags;
}
+
+ @Data
+ public static class CodeInjection {
+ public static final String GROUP = "codeInjection";
+
+ private String globalHead;
+
+ private String contentHead;
+
+ private String footer;
+ }
}
diff --git a/src/main/java/run/halo/app/theme/TemplateEngineManager.java b/src/main/java/run/halo/app/theme/TemplateEngineManager.java
index f61812571..d4f6529cf 100644
--- a/src/main/java/run/halo/app/theme/TemplateEngineManager.java
+++ b/src/main/java/run/halo/app/theme/TemplateEngineManager.java
@@ -12,6 +12,7 @@ import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import run.halo.app.infra.exception.NotFoundException;
+import run.halo.app.theme.dialect.HaloProcessorDialect;
import run.halo.app.theme.engine.SpringWebFluxTemplateEngine;
import run.halo.app.theme.message.ThemeMessageResolver;
@@ -80,6 +81,7 @@ public class TemplateEngineManager {
var mainResolver = haloTemplateResolver();
mainResolver.setPrefix(theme.getPath() + "/templates/");
engine.addTemplateResolver(mainResolver);
+ engine.addDialect(new HaloProcessorDialect());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
diff --git a/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java b/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java
new file mode 100644
index 000000000..c4c3929e0
--- /dev/null
+++ b/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java
@@ -0,0 +1,82 @@
+package run.halo.app.theme.dialect;
+
+import java.util.Collection;
+import org.springframework.context.ApplicationContext;
+import org.thymeleaf.context.ITemplateContext;
+import org.thymeleaf.model.IModel;
+import org.thymeleaf.model.IModelFactory;
+import org.thymeleaf.processor.element.AbstractElementModelProcessor;
+import org.thymeleaf.processor.element.IElementModelStructureHandler;
+import org.thymeleaf.spring6.context.SpringContextUtils;
+import org.thymeleaf.templatemode.TemplateMode;
+
+/**
+ * Global head injection processor.
+ *
+ * @author guqing
+ * @since 2.0.0
+ */
+public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor {
+ /**
+ * Inserting tag will re-trigger this processor, in order to avoid the loop out trigger,
+ * this flag is required to prevent the loop problem.
+ */
+ private static final String PROCESS_FLAG =
+ GlobalHeadInjectionProcessor.class.getName() + ".PROCESSED";
+
+ private static final String TAG_NAME = "head";
+ private static final int PRECEDENCE = 1000;
+
+ public GlobalHeadInjectionProcessor(final String dialectPrefix) {
+ super(
+ TemplateMode.HTML, // This processor will apply only to HTML mode
+ dialectPrefix, // Prefix to be applied to name for matching
+ TAG_NAME, // Tag name: match specifically this tag
+ false, // Apply dialect prefix to tag name
+ null, // No attribute name: will match by tag name
+ false, // No prefix to be applied to attribute name
+ PRECEDENCE); // Precedence (inside dialect's own precedence)
+ }
+
+ @Override
+ protected void doProcess(ITemplateContext context, IModel model,
+ IElementModelStructureHandler structureHandler) {
+
+ // note that this is important!!
+ Object processedAlready = context.getVariable(PROCESS_FLAG);
+ if (processedAlready != null) {
+ return;
+ }
+ structureHandler.setLocalVariable(PROCESS_FLAG, true);
+
+ // handle
tag
+ /*
+ * Obtain the Spring application context.
+ */
+ final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);
+
+ /*
+ * Create the DOM structure that will be substituting our custom tag.
+ * The headline will be shown inside a '
' tag, and so this must
+ * be created first and then a Text node must be added to it.
+ */
+ final IModelFactory modelFactory = context.getModelFactory();
+ IModel modelToInsert = modelFactory.createModel();
+
+ // apply processors to modelToInsert
+ Collection templateHeadProcessors =
+ getTemplateHeadProcessors(appCtx);
+ for (TemplateHeadProcessor processor : templateHeadProcessors) {
+ processor.process(context, modelToInsert, structureHandler)
+ .subscribe();
+ }
+
+ // add to target model
+ model.insertModel(model.size() - 1, modelToInsert);
+ }
+
+ private Collection getTemplateHeadProcessors(ApplicationContext ctx) {
+ return ctx.getBeansOfType(TemplateHeadProcessor.class)
+ .values();
+ }
+}
diff --git a/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java b/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java
new file mode 100644
index 000000000..7a8cb77dd
--- /dev/null
+++ b/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java
@@ -0,0 +1,32 @@
+package run.halo.app.theme.dialect;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.thymeleaf.dialect.AbstractProcessorDialect;
+import org.thymeleaf.processor.IProcessor;
+import org.thymeleaf.standard.StandardDialect;
+
+/**
+ * Thymeleaf processor dialect for Halo.
+ *
+ * @author guqing
+ * @since 2.0.0
+ */
+public class HaloProcessorDialect extends AbstractProcessorDialect {
+ private static final String DIALECT_NAME = "Halo Theme Dialect";
+
+ public HaloProcessorDialect() {
+ // We will set this dialect the same "dialect processor" precedence as
+ // the Standard Dialect, so that processor executions can interleave.
+ super(DIALECT_NAME, "halo", StandardDialect.PROCESSOR_PRECEDENCE);
+ }
+
+ @Override
+ public Set getProcessors(String dialectPrefix) {
+ final Set processors = new HashSet();
+ // add more processors
+ processors.add(new GlobalHeadInjectionProcessor(dialectPrefix));
+ processors.add(new TemplateFooterElementTagProcessor(dialectPrefix));
+ return processors;
+ }
+}
diff --git a/src/main/java/run/halo/app/theme/dialect/PostTemplateHeadProcessor.java b/src/main/java/run/halo/app/theme/dialect/PostTemplateHeadProcessor.java
new file mode 100644
index 000000000..1ccf15570
--- /dev/null
+++ b/src/main/java/run/halo/app/theme/dialect/PostTemplateHeadProcessor.java
@@ -0,0 +1,61 @@
+package run.halo.app.theme.dialect;
+
+import java.util.List;
+import java.util.Map;
+import org.springframework.stereotype.Component;
+import org.thymeleaf.context.ITemplateContext;
+import org.thymeleaf.model.IModel;
+import org.thymeleaf.model.IModelFactory;
+import org.thymeleaf.processor.element.IElementModelStructureHandler;
+import reactor.core.publisher.Mono;
+import run.halo.app.theme.DefaultTemplateEnum;
+import run.halo.app.theme.finders.PostFinder;
+
+/**
+ *
The head html snippet injection processor for post template.