Skip to content

Commit

Permalink
Use ElementTagPostProcessor to handle image replacement (#15)
Browse files Browse the repository at this point in the history
Signed-off-by: JohnNiang <[email protected]>
  • Loading branch information
JohnNiang authored Oct 14, 2024
1 parent 5b6a057 commit fe96fdc
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 199 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repositories {
}

dependencies {
implementation platform('run.halo.tools.platform:plugin:2.17.0-SNAPSHOT')
implementation platform('run.halo.tools.platform:plugin:2.20.0-SNAPSHOT')
compileOnly 'run.halo.app:api'

testImplementation 'run.halo.app:api'
Expand Down
111 changes: 111 additions & 0 deletions src/main/java/se/webp/plugin/ImageTagProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package se.webp.plugin;

import static org.thymeleaf.templatemode.TemplateMode.HTML;
import static se.webp.plugin.Settings.BasicConfig.GROUP;

import java.net.URI;
import java.net.URL;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.ElementNames;
import org.thymeleaf.model.IAttribute;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.MatchingElementName;
import org.thymeleaf.spring6.context.SpringContextUtils;
import org.thymeleaf.spring6.context.webflux.SpringWebFluxThymeleafRequestContext;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.plugin.ReactiveSettingFetcher;
import run.halo.app.theme.dialect.ElementTagPostProcessor;

@Component
public class ImageTagProcessor implements ElementTagPostProcessor {

private final MatchingElementName matchingElementName;

private final ReactiveSettingFetcher settingFetcher;

private final ExternalUrlSupplier externalUrlSupplier;

public ImageTagProcessor(ReactiveSettingFetcher settingFetcher,
ExternalUrlSupplier externalUrlSupplier) {
this.settingFetcher = settingFetcher;
this.externalUrlSupplier = externalUrlSupplier;
this.matchingElementName =
MatchingElementName.forElementName(HTML, ElementNames.forHTMLName("img"));
}

private static boolean urlMatches(URL firstUrl, URL secondUrl) {
return Objects.equals(firstUrl.getAuthority(), secondUrl.getAuthority());
}

@Override
public Mono<IProcessableElementTag> process(ITemplateContext context,
IProcessableElementTag tag) {
if (!matchingElementName.matches(tag.getElementDefinition().getElementName())) {
return Mono.empty();
}
var requestContext = SpringContextUtils.getRequestContext(context);
if (!(requestContext instanceof SpringWebFluxThymeleafRequestContext springWebContext)) {
return Mono.empty();
}
var srcValue = Optional.ofNullable(tag.getAttribute("src"))
.map(IAttribute::getValue)
.filter(StringUtils::hasText)
.map(URI::create);
if (srcValue.isEmpty()) {
return Mono.empty();
}
var exchange = springWebContext.getServerWebExchange();
var externalUrl = externalUrlSupplier.getURL(exchange.getRequest());

return settingFetcher.fetch(GROUP, Settings.BasicConfig.class)
.filter(config -> !ArrayUtils.isEmpty(config.getProxies()))
.flatMapMany(config -> Flux.fromArray(config.getProxies()))
.filter(proxy ->
Objects.nonNull(proxy.getProxyUrl()) && Objects.nonNull(proxy.getOriginUrl())
)
.filter(proxy -> urlMatches(externalUrl, proxy.getOriginUrl()))
.next()
.map(proxy -> {
var srcBuilder = UriComponentsBuilder.fromHttpUrl(proxy.getProxyUrl().toString())
.path(srcValue.get().getPath())
.query(srcValue.get().getQuery())
.fragment(srcValue.get().getFragment());
var newSrc = srcBuilder.toUriString();
var modelFactory = context.getModelFactory();
var newTag = tag;
if (!tag.hasAttribute("srcset")) {
// calculate srcset and sizes
newTag = modelFactory.setAttribute(newTag, "sizes",
"""
(max-width: 400px) 400px, (max-width: 800px) 800px, \
(max-width: 1200px) 1200px, (max-width: 1600px) 1600px\
""");
var w400Src = srcBuilder.cloneBuilder().queryParam("width", 400)
.toUriString();
var w800Src = srcBuilder.cloneBuilder().queryParam("width", 800)
.toUriString();
var w1200Src = srcBuilder.cloneBuilder().queryParam("width", 1200)
.toUriString();
var w1600Src = srcBuilder.cloneBuilder().queryParam("width", 1600)
.toUriString();
newTag = modelFactory.setAttribute(newTag, "srcset",
w400Src + " 400w, "
+ w800Src + " 800w, "
+ w1200Src + " 1200w, "
+ w1600Src + " 1600w"
);
}
newTag = modelFactory.setAttribute(newTag, "src",
newSrc);
return newTag;
});
}
}
17 changes: 9 additions & 8 deletions src/main/java/se/webp/plugin/Settings.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
package se.webp.plugin;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URL;
import lombok.Data;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.ReactiveSettingFetcher;

public class Settings {

public static Mono<BasicConfig> getBasicConfig(ReactiveSettingFetcher settingFetcher) {
return settingFetcher.fetch(BasicConfig.GROUP, BasicConfig.class);
}

@Data
public static class BasicConfig {

public static final String GROUP = "basic";

String apiKeySecret;

Proxy[] proxies;

}

@Data
public static class Proxy {
String origin_url;

String proxy_url;
@JsonProperty("origin_url")
URL originUrl;

@JsonProperty("proxy_url")
URL proxyUrl;
}
}
189 changes: 0 additions & 189 deletions src/main/java/se/webp/plugin/WebpCloudImageOptimizerWebFilter.java

This file was deleted.

2 changes: 1 addition & 1 deletion src/main/resources/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: plugin-webp-se-cloud
spec:
enabled: true
requires: ">=2.7.0"
requires: ">=2.20.0"
author:
name: WebP Cloud Services
website: https://webp.se/
Expand Down

0 comments on commit fe96fdc

Please sign in to comment.