started work on bbcode converter

This commit is contained in:
Jake Potrebic 2021-04-09 23:27:27 -07:00
parent f9bfafc0e2
commit 202c2e11b1
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
17 changed files with 134 additions and 171 deletions

View File

@ -86,10 +86,10 @@ There is a bunch of stuff to do, some of that is noted in the [**Roadmap Project
Your best bet is joining #hangar-dev on the [Paper Discord](https://discord.gg/papermc) and just discussing with us.
All contributions are very welcome, we will not be able to finish this alone!
## Licence
## License
Most of the frontend is a fork of Ore, licensed under MIT [here](https://github.com/SpongePowered/Ore/blob/staging/LICENSE.txt).
The rest is new code (but created in reference of Ore) and is licenced under the MIT licence too.
The rest is new code (but created in reference of Ore) and is licensed under the MIT license too.
[Yarn]: https://yarnpkg.com/
[HangarAuth]: https://github.com/PaperMC/HangarAuth

View File

@ -17,7 +17,7 @@ hangar:
base-url: "https://hangar.benndorf.dev"
plugin-upload-dir: "/hangar/uploads"
licences:
licenses:
- "MIT"
- "Apache 2.0"
- "GPL"

View File

@ -44,7 +44,7 @@ hangar:
plugin-upload-dir: "/uploads"
ga-code: "UA-38006759-9"
licences:
licenses:
- "MIT"
- "Apache 2.0"
- "GPL"

View File

@ -152,10 +152,10 @@ const msgs: LocaleMessageObject = {
title: 'Basic Settings',
continue: 'Continue',
back: 'Back',
userselect: 'Create as...',
projectname: 'Project name',
projectsummary: 'Project Summary',
projectcategory: 'Project Category',
userSelect: 'Create as...',
projectName: 'Project name',
projectSummary: 'Project Summary',
projectCategory: 'Project Category',
},
step3: {
title: 'Additional Settings',
@ -167,7 +167,7 @@ const msgs: LocaleMessageObject = {
issues: 'Issue Tracker',
source: 'Source Code',
support: 'External Support',
licence: 'Licence',
license: 'License',
type: 'Type',
customName: 'Name',
url: 'URL',
@ -180,8 +180,18 @@ const msgs: LocaleMessageObject = {
back: 'Back',
optional: 'Optional',
convert: 'Convert',
convertLabels: {
bbCode: 'Paste your BBCode here',
output: 'Markdown Output',
},
preview: 'Preview',
tutorial: 'How to get the BBCode',
tutorialInstructions: {
line1: 'To get the BBCode of your Spigot project, do the following:',
line2: '1. Go to your project and click on "Edit Resource".',
line3: '2. Click on the wrench symbol in the description editor.',
line4: '3. Copy paste the new contents into the upper converter textbox, do changes to the output if you like, and hit save!',
},
},
step5: {
title: 'Finishing',
@ -232,7 +242,7 @@ const msgs: LocaleMessageObject = {
support: 'External support',
supportSub: 'An external place where you can offer support to your users. Could be a forum, a Discord server, or somewhere else.',
license: 'License',
licenceSub: 'What can people do (and not do) with your project?',
licenseSub: 'What can people do (and not do) with your project?',
forum: 'Create posts on the forums',
forumSub: 'Sets if events like a new release should automatically create a post on the forums',
description: 'Description',
@ -252,9 +262,9 @@ const msgs: LocaleMessageObject = {
hardDeleteSub: 'Once you delete a project, it cannot be recovered. For real this time...',
save: 'Save changes',
optional: '(optional)',
licenceCustom: 'Custom Name',
licenceType: 'Type',
licenceUrl: 'URL',
licenseCustom: 'Custom Name',
licenseType: 'Type',
licenseUrl: 'URL',
donation: {
enable: 'Enable',
enableSub: 'Enable the donation form for this project',

View File

@ -201,7 +201,7 @@
<h2>
{{ $t('project.settings.license') }}&nbsp;<small>{{ $t('project.settings.optional') }}</small>
</h2>
<p>{{ $t('project.settings.licenceSub') }}</p>
<p>{{ $t('project.settings.licenseSub') }}</p>
<v-row>
<v-col cols="12" :md="isCustomLicense ? 4 : 6">
<v-select
@ -209,8 +209,8 @@
dense
hide-details
filled
:items="licences"
:label="$t('project.settings.licenceType')"
:items="licenses"
:label="$t('project.settings.licenseType')"
/>
</v-col>
<v-col v-if="isCustomLicense" cols="12" md="8">
@ -219,7 +219,7 @@
dense
hide-details
filled
:label="$t('project.settings.licenceCustom')"
:label="$t('project.settings.licenseCustom')"
/>
</v-col>
<v-col cols="12" :md="isCustomLicense ? 12 : 6">
@ -229,7 +229,7 @@
clearable
filled
:rules="[$util.$vc.url]"
:label="$t('project.settings.licenceUrl')"
:label="$t('project.settings.licenseUrl')"
/>
</v-col>
</v-row>
@ -398,7 +398,7 @@ import TextareaModal from '~/components/modals/TextareaModal.vue';
@ProjectPermission(NamedPermission.EDIT_SUBJECT_SETTINGS)
export default class ProjectManagePage extends HangarProjectMixin {
roles!: Role[];
licences!: string[];
licenses!: string[];
apiKey = '';
newName = '';
nameErrors: TranslateResult[] = [];
@ -582,11 +582,11 @@ export default class ProjectManagePage extends HangarProjectMixin {
generateApiKey() {}
async asyncData({ $api, $util }: Context) {
const data = await Promise.all([$api.requestInternal('data/projectRoles', false), $api.requestInternal('data/licences', false)]).catch(
const data = await Promise.all([$api.requestInternal('data/projectRoles', false), $api.requestInternal('data/licenses', false)]).catch(
$util.handlePageRequestError
);
if (typeof data === 'undefined') return;
return { roles: data[0], licences: data[1] };
return { roles: data[0], licenses: data[1] };
}
}
</script>

View File

@ -43,8 +43,8 @@
filled
item-text="name"
item-value="userId"
:label="$t('project.new.step2.userselect')"
:rules="[$util.$vc.require($t('project.new.step2.userselect'))]"
:label="$t('project.new.step2.userSelect')"
:rules="[$util.$vc.require($t('project.new.step2.userSelect'))]"
:append-icon="createAsIcon"
/>
</v-col>
@ -55,8 +55,8 @@
dense
filled
:error-messages="nameErrors"
:label="$t('project.new.step2.projectname')"
:rules="[$util.$vc.require($t('project.new.step2.projectname'))]"
:label="$t('project.new.step2.projectName')"
:rules="[$util.$vc.require($t('project.new.step2.projectName'))]"
append-icon="mdi-form-textbox"
/>
</v-col>
@ -66,8 +66,8 @@
dense
filled
clearable
:label="$t('project.new.step2.projectsummary')"
:rules="[$util.$vc.require($t('project.new.step2.projectsummary')), $util.$vc.maxLength(validations.project.desc.max)]"
:label="$t('project.new.step2.projectSummary')"
:rules="[$util.$vc.require($t('project.new.step2.projectSummary')), $util.$vc.maxLength(validations.project.desc.max)]"
append-icon="mdi-card-text"
/>
</v-col>
@ -78,10 +78,10 @@
:items="$store.getters.visibleCategories"
dense
filled
:label="$t('project.new.step2.projectcategory')"
:label="$t('project.new.step2.projectCategory')"
item-text="title"
item-value="apiName"
:rules="[$util.$vc.require($t('project.new.step2.projectcategory'))]"
:rules="[$util.$vc.require($t('project.new.step2.projectCategory'))]"
/>
</v-col>
</v-row>
@ -148,7 +148,7 @@
</v-row>
<div class="text-h6 pt-5">
<v-icon color="info" large class="mb-1">mdi-license</v-icon>
{{ $t('project.new.step3.licence') }}
{{ $t('project.new.step3.license') }}
</div>
<v-divider class="mb-2" />
<v-row>
@ -159,7 +159,7 @@
hide-details
filled
clearable
:items="licences"
:items="licenses"
:label="$t('project.new.step3.type')"
/>
</v-col>
@ -217,9 +217,37 @@
</v-tabs>
<v-tabs-items v-model="spigotConvertTab">
<!-- todo spigot bbcode converter thingy -->
<v-tab-item>1 </v-tab-item>
<v-tab-item>
<v-card-text>
<v-textarea v-model="converter.bbCode" hide-details dense :rows="6" filled :label="$t('project.new.step4.convertLabels.bbCode')" />
<div>
<v-btn block color="primary" class="my-3" :loading="converter.loading" @click="convertBBCode">
<v-icon left large>mdi-chevron-double-down</v-icon>
{{ $t('project.new.step4.convert') }}
<v-icon right large>mdi-chevron-double-down</v-icon>
</v-btn>
</div>
<v-textarea
v-model="converter.markdown"
hide-details
dense
:rows="6"
filled
:label="$t('project.new.step4.convertLabels.output')"
/>
</v-card-text>
</v-tab-item>
<v-tab-item>2 </v-tab-item>
<v-tab-item>3 </v-tab-item>
<v-tab-item>
<v-card-text class="text-center">
{{ $t('project.new.step4.tutorialInstructions.line1') }}<br />
{{ $t('project.new.step4.tutorialInstructions.line2') }}<br />
<img src="https://i.imgur.com/8CyLMf3.png" alt="Edit Project" /><br />
{{ $t('project.new.step4.tutorialInstructions.line3') }}<br />
<img src="https://i.imgur.com/FLVIuQK.png" width="425" height="198" alt="Show BBCode" /><br />
{{ $t('project.new.step4.tutorialInstructions.line4') }}<br />
</v-card-text>
</v-tab-item>
</v-tabs-items>
</StepperStepContent>
<StepperStepContent :step="5" hide-buttons>
@ -271,7 +299,7 @@ export default class NewProjectPage extends HangarComponent {
projectLoading = true;
projectError = false;
projectOwners!: ProjectOwner[];
licences!: string[];
licenses!: string[];
error = null as string | null;
form: NewProjectForm = {
category: ProjectCategory.ADMIN_TOOLS,
@ -284,6 +312,12 @@ export default class NewProjectPage extends HangarComponent {
nameErrors: TranslateResult[] = [];
converter = {
bbCode: '',
markdown: '',
loading: false,
};
forms = {
step2: false,
};
@ -305,13 +339,13 @@ export default class NewProjectPage extends HangarComponent {
}
async asyncData({ $api, $util }: Context) {
const data = await Promise.all([$api.requestInternal('data/possibleOwners'), $api.requestInternal('data/licenses', false)]).catch(
const data = await Promise.all([$api.requestInternal('projects/possibleOwners'), $api.requestInternal('data/licenses', false)]).catch(
$util.handlePageRequestError
);
if (typeof data === 'undefined') return;
return {
licenses: data[0],
projectOwners: data[1],
projectOwners: data[0],
licenses: data[1],
};
}
@ -334,6 +368,21 @@ export default class NewProjectPage extends HangarComponent {
});
}
convertBBCode() {
this.converter.loading = true;
this.$api
.requestInternal<string>('pages/convert-bbcode', false, 'post', {
content: this.converter.bbCode,
})
.then((markdown) => {
this.converter.markdown = markdown;
})
.catch(this.$util.handleRequestError)
.finally(() => {
this.converter.loading = false;
});
}
retry() {
this.step = 1;
this.projectLoading = true;

View File

@ -1,35 +1,16 @@
package io.papermc.hangar.config;
import com.fasterxml.classmate.TypeResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.FilterRegistry;
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableFilters;
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableSorters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ExampleBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.schema.Example;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.schema.ModelSpecification;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.ParameterSpecification;
import springfox.documentation.service.ParameterStyle;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.RequestParameter;
@ -39,6 +20,13 @@ import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import java.util.Set;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@ -61,7 +49,7 @@ public class SwaggerConfig {
"We're working on a session-less authentification for public endpoints. " +
"<h3>What format does dates have?</h3>" +
"Standard ISO types. Where possible we use the OpenAPI format modifier. You can view it's meanings [here](https://swagger.io/docs/specification/data-models/data-types/#format).")
.license("Unlicence")
.license("Unlicense")
.licenseUrl("http://unlicense.org")
.termsOfServiceUrl("")
.version("1.0")

View File

@ -30,7 +30,7 @@ public class HangarConfig {
private String gaCode = "";
private List<Announcement> announcements = new ArrayList<>();
private String urlRegex = "^(https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})";
private List<String> licences = new ArrayList<>();
private List<String> licenses = new ArrayList<>();
@NestedConfigurationProperty
public final FakeUserConfig fakeUser;
@ -190,12 +190,12 @@ public class HangarConfig {
this.urlRegex = urlRegex;
}
public List<String> getLicences() {
return licences;
public List<String> getLicenses() {
return licenses;
}
public void setLicences(List<String> licences) {
this.licences = licences;
public void setLicenses(List<String> licenses) {
this.licenses = licenses;
}
@Deprecated(forRemoval = true)

View File

@ -135,9 +135,9 @@ public class BackendDataController {
return ResponseEntity.ok(OrganizationRole.getAssignableRoles());
}
@GetMapping("/licences")
public ResponseEntity<List<String>> getLicences() {
return ResponseEntity.ok(config.getLicences());
@GetMapping("/licenses")
public ResponseEntity<List<String>> getLicenses() {
return ResponseEntity.ok(config.getLicenses());
}
@GetMapping("/visibilities")

View File

@ -14,6 +14,7 @@ import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type
import io.papermc.hangar.service.internal.MarkdownService;
import io.papermc.hangar.service.internal.projects.ProjectPageService;
import io.papermc.hangar.util.StringUtils;
import io.papermc.hangar.utils.BBCodeConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
@ -36,11 +38,13 @@ public class ProjectPageController extends HangarComponent {
private final ProjectPageService projectPageService;
private final MarkdownService markdownService;
private final BBCodeConverter bbCodeConverter;
@Autowired
public ProjectPageController(ProjectPageService projectPageService, MarkdownService markdownService) {
public ProjectPageController(ProjectPageService projectPageService, MarkdownService markdownService, BBCodeConverter bbCodeConverter) {
this.projectPageService = projectPageService;
this.markdownService = markdownService;
this.bbCodeConverter = bbCodeConverter;
}
@PostMapping(path = "/render", produces = MediaType.APPLICATION_JSON_VALUE)
@ -48,6 +52,13 @@ public class ProjectPageController extends HangarComponent {
return ResponseEntity.ok(markdownService.render(content.getContent()));
}
@Anyone
@ResponseBody
@PostMapping(path = "/convert-bbcode", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public String convertBBCode(@RequestBody @Valid StringContent bbCodeContent) {
return bbCodeConverter.convertToMarkdown(bbCodeContent.getContent());
}
@ResponseStatus(HttpStatus.OK)
@GetMapping("/checkName")
public void checkName(@RequestParam long projectId, @RequestParam String name, @RequestParam(required = false) Long parentId) {

View File

@ -1,7 +1,7 @@
package io.papermc.hangar.controllerold;
import io.papermc.hangar.controllerold.forms.RawPage;
import io.papermc.hangar.controllerold.util.BBCodeConverter;
import io.papermc.hangar.utils.BBCodeConverter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;

View File

@ -18,8 +18,8 @@ public class ProjectLicense {
@JdbiConstructor
public ProjectLicense(@Nullable String name, @Nullable String url) {
int index = config.getLicences().indexOf(name);
if (name != null && index > -1 && index < config.getLicences().size() - 1) {
int index = config.getLicenses().indexOf(name);
if (name != null && index > -1 && index < config.getLicenses().size() - 1) {
this.name = null;
this.type = name;
} else {

View File

@ -1,71 +0,0 @@
package io.papermc.hangar.util;
import io.papermc.hangar.exceptions.HangarApiException;
import org.jetbrains.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Deprecated(forRemoval = true)
public class AuthUtils {
private static final Pattern API_KEY_HEADER_PATTERN = Pattern.compile("(?<=apikey=\").*(?=\")", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static final Pattern SESSION_HEADER_PATTERN = Pattern.compile("(?<=session=\").*(?=\")", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private AuthUtils() { }
public static RuntimeException unAuth(String message) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.WWW_AUTHENTICATE, "HangarApi");
return new HangarApiException(HttpStatus.UNAUTHORIZED, message, headers);
}
public static AuthCredentials parseAuthHeader(@Nullable HttpServletRequest request, boolean requireHeader) {
String authHeader = request == null ? ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getHeader(HttpHeaders.AUTHORIZATION) : request.getHeader(HttpHeaders.AUTHORIZATION);
boolean missingAuthHeader = authHeader == null || authHeader.isBlank() || !authHeader.startsWith("HangarApi");
if (missingAuthHeader && requireHeader) {
throw AuthUtils.unAuth("Invalid or no header found");
} else if (missingAuthHeader) {
return new AuthCredentials(null, null);
}
return AuthUtils.AuthCredentials.parseHeader(authHeader);
}
public static class AuthCredentials {
private final String apiKey;
private final String session;
private AuthCredentials(String apiKey, String session) {
this.apiKey = apiKey;
this.session = session;
}
public String getApiKey() {
return apiKey;
}
public String getSession() {
return session;
}
public static AuthCredentials parseHeader(String authHeader) {
Matcher apiKeyMatcher = API_KEY_HEADER_PATTERN.matcher(authHeader);
Matcher sessionMatcher = SESSION_HEADER_PATTERN.matcher(authHeader);
String apiKey = null;
String sessionKey = null;
if (apiKeyMatcher.find()) {
apiKey = apiKeyMatcher.group();
} else if (sessionMatcher.find()) {
sessionKey = sessionMatcher.group();
} else {
throw AuthUtils.unAuth("Invalid Authorization header format");
}
return new AuthCredentials(apiKey, sessionKey);
}
}
}

View File

@ -1,26 +0,0 @@
package io.papermc.hangar.util;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Deprecated
public class ListUtils {
private ListUtils() { }
public static <K, V> Map<K, V> zip(List<K> keys, List<V> values) {
Map<K, V> map = new HashMap<>();
if (keys != null && values != null) {
if (keys.size() != values.size()) throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
for (int i = 0; i < keys.size(); i++) {
map.put(keys.get(i), values.get(i));
}
}
return map;
}
}

View File

@ -1,10 +1,12 @@
package io.papermc.hangar.controllerold.util;
package io.papermc.hangar.utils;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class BBCodeConverter {
private static final Map<String, TagReplacer> REPLACERS = new HashMap<>();

View File

@ -50,7 +50,7 @@ hangar:
base-url: "http://localhost:3000"
ga-code: "UA-38006759-9"
licences:
licenses:
- "MIT"
- "Apache 2.0"
- "GPL"

View File

@ -1,6 +1,6 @@
package io.papermc.hangar.util;
import io.papermc.hangar.controllerold.util.BBCodeConverter;
import io.papermc.hangar.utils.BBCodeConverter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;