Minor tweaks
This commit is contained in:
parent
3cba6de9f1
commit
6e70e1495e
3 changed files with 97 additions and 16 deletions
|
|
@ -265,6 +265,21 @@ public class ActivityPubController {
|
||||||
noteObject.put("to", List.of("https://www.w3.org/ns/activitystreams#Public"));
|
noteObject.put("to", List.of("https://www.w3.org/ns/activitystreams#Public"));
|
||||||
noteObject.put("cc", List.of(actorUri + "/followers"));
|
noteObject.put("cc", List.of(actorUri + "/followers"));
|
||||||
|
|
||||||
|
// Extract hashtags from user text and add as tags
|
||||||
|
List<String> hashtags = extractHashtags(activity);
|
||||||
|
if (!hashtags.isEmpty()) {
|
||||||
|
List<Map<String, String>> tags = hashtags.stream()
|
||||||
|
.map(ht -> {
|
||||||
|
Map<String, String> tag = new HashMap<>();
|
||||||
|
tag.put("type", "Hashtag");
|
||||||
|
tag.put("href", baseUrl + "/tags/" + ht.toLowerCase());
|
||||||
|
tag.put("name", "#" + ht);
|
||||||
|
return tag;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
noteObject.put("tag", tags);
|
||||||
|
}
|
||||||
|
|
||||||
// Add conversation/context for threading
|
// Add conversation/context for threading
|
||||||
noteObject.put("conversation", activityUri);
|
noteObject.put("conversation", activityUri);
|
||||||
|
|
||||||
|
|
@ -297,22 +312,18 @@ public class ActivityPubController {
|
||||||
private String formatActivityContent(Activity activity) {
|
private String formatActivityContent(Activity activity) {
|
||||||
StringBuilder content = new StringBuilder();
|
StringBuilder content = new StringBuilder();
|
||||||
|
|
||||||
// Title
|
|
||||||
if (activity.getTitle() != null && !activity.getTitle().isEmpty()) {
|
if (activity.getTitle() != null && !activity.getTitle().isEmpty()) {
|
||||||
content.append("<p><strong>").append(escapeHtml(activity.getTitle())).append("</strong></p>");
|
content.append("<p><strong>").append(linkifyHashtags(escapeHtml(activity.getTitle()))).append("</strong></p>");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Description
|
|
||||||
if (activity.getDescription() != null && !activity.getDescription().isEmpty()) {
|
if (activity.getDescription() != null && !activity.getDescription().isEmpty()) {
|
||||||
content.append("<p>").append(escapeHtml(activity.getDescription())).append("</p>");
|
content.append("<p>").append(linkifyHashtags(escapeHtml(activity.getDescription()))).append("</p>");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activity type with emoji
|
|
||||||
String activityEmoji = getActivityEmoji(activity.getActivityType());
|
String activityEmoji = getActivityEmoji(activity.getActivityType());
|
||||||
String formattedType = ActivityFormatter.formatActivityType(activity.getActivityType());
|
String formattedType = ActivityFormatter.formatActivityType(activity.getActivityType());
|
||||||
content.append("<p>").append(activityEmoji).append(" ").append(escapeHtml(formattedType)).append("</p>");
|
content.append("<p>").append(activityEmoji).append(" ").append(escapeHtml(formattedType)).append("</p>");
|
||||||
|
|
||||||
// Metrics
|
|
||||||
StringBuilder metrics = new StringBuilder();
|
StringBuilder metrics = new StringBuilder();
|
||||||
if (activity.getTotalDistance() != null) {
|
if (activity.getTotalDistance() != null) {
|
||||||
metrics.append("📏 ")
|
metrics.append("📏 ")
|
||||||
|
|
@ -341,6 +352,33 @@ public class ActivityPubController {
|
||||||
return content.toString();
|
return content.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final java.util.regex.Pattern HASHTAG_PATTERN =
|
||||||
|
java.util.regex.Pattern.compile("(?<=^|\\s)#(\\w+)", java.util.regex.Pattern.UNICODE_CHARACTER_CLASS);
|
||||||
|
|
||||||
|
private List<String> extractHashtags(Activity activity) {
|
||||||
|
List<String> hashtags = new java.util.ArrayList<>();
|
||||||
|
for (String text : List.of(
|
||||||
|
activity.getTitle() != null ? activity.getTitle() : "",
|
||||||
|
activity.getDescription() != null ? activity.getDescription() : "")) {
|
||||||
|
var matcher = HASHTAG_PATTERN.matcher(text);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String tag = matcher.group(1);
|
||||||
|
if (hashtags.stream().noneMatch(t -> t.equalsIgnoreCase(tag))) {
|
||||||
|
hashtags.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashtags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String linkifyHashtags(String escapedHtml) {
|
||||||
|
return HASHTAG_PATTERN.matcher(escapedHtml).replaceAll(match -> {
|
||||||
|
String tag = match.group(1);
|
||||||
|
return "<a href=\"" + baseUrl + "/tags/" + tag.toLowerCase()
|
||||||
|
+ "\" class=\"mention hashtag\" rel=\"tag\">#<span>" + tag + "</span></a>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static String escapeHtml(String text) {
|
private static String escapeHtml(String text) {
|
||||||
if (text == null) return "";
|
if (text == null) return "";
|
||||||
return text.replace("&", "&")
|
return text.replace("&", "&")
|
||||||
|
|
@ -349,9 +387,6 @@ public class ActivityPubController {
|
||||||
.replace("\"", """);
|
.replace("\"", """);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get emoji for activity type.
|
|
||||||
*/
|
|
||||||
private String getActivityEmoji(Activity.ActivityType activityType) {
|
private String getActivityEmoji(Activity.ActivityType activityType) {
|
||||||
return switch (activityType) {
|
return switch (activityType) {
|
||||||
case RUN -> "🏃";
|
case RUN -> "🏃";
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,21 @@ public class ActivityPostProcessingService {
|
||||||
noteObject.put("content", formatActivityContent(activity));
|
noteObject.put("content", formatActivityContent(activity));
|
||||||
noteObject.put("url", baseUrl + "/activities/" + activity.getId());
|
noteObject.put("url", baseUrl + "/activities/" + activity.getId());
|
||||||
|
|
||||||
|
// Extract hashtags from user text and add as tags
|
||||||
|
List<String> hashtags = extractHashtags(activity);
|
||||||
|
if (!hashtags.isEmpty()) {
|
||||||
|
List<Map<String, String>> tags = hashtags.stream()
|
||||||
|
.map(ht -> {
|
||||||
|
Map<String, String> tag = new HashMap<>();
|
||||||
|
tag.put("type", "Hashtag");
|
||||||
|
tag.put("href", baseUrl + "/tags/" + ht.toLowerCase());
|
||||||
|
tag.put("name", "#" + ht);
|
||||||
|
return tag;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
noteObject.put("tag", tags);
|
||||||
|
}
|
||||||
|
|
||||||
// Set visibility (to/cc fields)
|
// Set visibility (to/cc fields)
|
||||||
if (activity.getVisibility() == Activity.Visibility.PUBLIC) {
|
if (activity.getVisibility() == Activity.Visibility.PUBLIC) {
|
||||||
noteObject.put("to", List.of("https://www.w3.org/ns/activitystreams#Public"));
|
noteObject.put("to", List.of("https://www.w3.org/ns/activitystreams#Public"));
|
||||||
|
|
@ -218,19 +233,17 @@ public class ActivityPostProcessingService {
|
||||||
/**
|
/**
|
||||||
* Format activity content as HTML for ActivityPub Note.
|
* Format activity content as HTML for ActivityPub Note.
|
||||||
* Mastodon and most Fediverse software expect HTML in the content field.
|
* Mastodon and most Fediverse software expect HTML in the content field.
|
||||||
*
|
* Hashtags in user text are converted to proper HTML links.
|
||||||
* @param activity the activity to format
|
|
||||||
* @return formatted HTML content string
|
|
||||||
*/
|
*/
|
||||||
private String formatActivityContent(Activity activity) {
|
private String formatActivityContent(Activity activity) {
|
||||||
StringBuilder content = new StringBuilder();
|
StringBuilder content = new StringBuilder();
|
||||||
|
|
||||||
if (activity.getTitle() != null && !activity.getTitle().isEmpty()) {
|
if (activity.getTitle() != null && !activity.getTitle().isEmpty()) {
|
||||||
content.append("<p><strong>").append(escapeHtml(activity.getTitle())).append("</strong></p>");
|
content.append("<p><strong>").append(linkifyHashtags(escapeHtml(activity.getTitle()))).append("</strong></p>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.getDescription() != null && !activity.getDescription().isEmpty()) {
|
if (activity.getDescription() != null && !activity.getDescription().isEmpty()) {
|
||||||
content.append("<p>").append(escapeHtml(activity.getDescription())).append("</p>");
|
content.append("<p>").append(linkifyHashtags(escapeHtml(activity.getDescription()))).append("</p>");
|
||||||
}
|
}
|
||||||
|
|
||||||
String activityEmoji = getActivityEmoji(activity.getActivityType());
|
String activityEmoji = getActivityEmoji(activity.getActivityType());
|
||||||
|
|
@ -265,6 +278,39 @@ public class ActivityPostProcessingService {
|
||||||
return content.toString();
|
return content.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final java.util.regex.Pattern HASHTAG_PATTERN =
|
||||||
|
java.util.regex.Pattern.compile("(?<=^|\\s)#(\\w+)", java.util.regex.Pattern.UNICODE_CHARACTER_CLASS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all hashtags from user-provided title and description.
|
||||||
|
*/
|
||||||
|
private List<String> extractHashtags(Activity activity) {
|
||||||
|
List<String> hashtags = new java.util.ArrayList<>();
|
||||||
|
for (String text : List.of(
|
||||||
|
activity.getTitle() != null ? activity.getTitle() : "",
|
||||||
|
activity.getDescription() != null ? activity.getDescription() : "")) {
|
||||||
|
var matcher = HASHTAG_PATTERN.matcher(text);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String tag = matcher.group(1);
|
||||||
|
if (hashtags.stream().noneMatch(t -> t.equalsIgnoreCase(tag))) {
|
||||||
|
hashtags.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashtags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert #hashtag occurrences in already-escaped HTML text to ActivityPub hashtag links.
|
||||||
|
*/
|
||||||
|
private String linkifyHashtags(String escapedHtml) {
|
||||||
|
return HASHTAG_PATTERN.matcher(escapedHtml).replaceAll(match -> {
|
||||||
|
String tag = match.group(1);
|
||||||
|
return "<a href=\"" + baseUrl + "/tags/" + tag.toLowerCase()
|
||||||
|
+ "\" class=\"mention hashtag\" rel=\"tag\">#<span>" + tag + "</span></a>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static String escapeHtml(String text) {
|
private static String escapeHtml(String text) {
|
||||||
if (text == null) return "";
|
if (text == null) return "";
|
||||||
return text.replace("&", "&")
|
return text.replace("&", "&")
|
||||||
|
|
|
||||||
|
|
@ -138,9 +138,9 @@
|
||||||
Visibility <span class="text-danger">*</span>
|
Visibility <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<select class="form-select" id="visibility" name="visibility">
|
<select class="form-select" id="visibility" name="visibility">
|
||||||
<option value="PUBLIC">Public - Anyone can see</option>
|
<option value="PUBLIC" selected>Public - Anyone can see</option>
|
||||||
<option value="FOLLOWERS">Followers Only - Only your followers can see</option>
|
<option value="FOLLOWERS">Followers Only - Only your followers can see</option>
|
||||||
<option value="PRIVATE" selected>Private - Only you can see</option>
|
<option value="PRIVATE">Private - Only you can see</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<i class="bi bi-info-circle"></i>
|
<i class="bi bi-info-circle"></i>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue