mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-23 15:12:52 +08:00
frontend work
This commit is contained in:
parent
6102e05292
commit
4d78a27023
@ -30,6 +30,7 @@
|
||||
"query-string": "6.13.5",
|
||||
"swagger-ui": "3.35.0",
|
||||
"vue": "3.0.0",
|
||||
"vue-i18n": "^9.0.0-beta.4",
|
||||
"webpack-jquery-ui": "2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -120,11 +120,19 @@
|
||||
</div>
|
||||
|
||||
<div class="release-bulletin">
|
||||
<div style="position: relative">
|
||||
<div style="position: relative; z-index: 5">
|
||||
<h3>Release Bulletin</h3>
|
||||
<p>What's new in this release?</p>
|
||||
|
||||
<Editor enabled :raw="pendingVersion.description || ''" target-form="form-publish" v-model:content-prop="payload.content"></Editor>
|
||||
<Editor
|
||||
enabled
|
||||
:raw="pendingVersion.description || ''"
|
||||
target-form="form-publish"
|
||||
v-model:content-prop="payload.content"
|
||||
:saveable="false"
|
||||
:cancellable="false"
|
||||
open
|
||||
></Editor>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -429,7 +437,7 @@ export default {
|
||||
},
|
||||
{
|
||||
propName: 'content',
|
||||
selector: '.version-content-view',
|
||||
selector: '.page-content-view',
|
||||
},
|
||||
];
|
||||
|
||||
@ -509,9 +517,6 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$('.btn-edit').click();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
258
src/main/frontend/src/ProjectView.vue
Normal file
258
src/main/frontend/src/ProjectView.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 col-md-9 col-12" style="z-index: 1">
|
||||
<Editor
|
||||
:save-call="ROUTES.parse('PAGES_SAVE', project.ownerName, project.slug, page.slug)"
|
||||
:delete-call="ROUTES.parse('PAGES_DELETE', project.ownerName, project.slug, page.slug)"
|
||||
:deletable="page.isDeletable"
|
||||
:enabled="canEditPages"
|
||||
:raw="page.contents"
|
||||
subject="Page"
|
||||
:extra-form-value="page.name"
|
||||
></Editor>
|
||||
</div>
|
||||
<div class="col-md-3 col-4">
|
||||
<div v-if="project.recommendedVersionId" class="btn-group btn-download">
|
||||
<a
|
||||
:href="ROUTES.parse('VERSIONS_DOWNLOAD_RECOMMENDED', project.ownerName, project.slug)"
|
||||
:title="$t('project.download.recommend._')"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
<i class="fas fa-download"></i>
|
||||
{{ $t('general.download') }}
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a :href="ROUTES.parse('VERSIONS_DOWNLOAD_RECOMMENDED', project.ownerName, project.slug)" class="dropdown-item">
|
||||
{{ $t('general.download') }}
|
||||
</a>
|
||||
<a href="#" class="" @click.prevent></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats minor">
|
||||
<p>{{ $t('project.category.info', [formatCategory(project.category)]) }}</p>
|
||||
<p>{{ $t('project.publishDate', [moment(project.createdAt).format('MMM D, YYYY')]) }}</p>
|
||||
<p v-if="apiProject">
|
||||
<span id="view-count">{{ apiProject.stats.views }} views</span>
|
||||
</p>
|
||||
<p v-if="apiProject">
|
||||
<span id="star-count">{{ apiProject.stats.stars }}</span>
|
||||
<a :href="ROUTES.parse('PROJECTS_SHOW_STARGAZERS', project.ownerName, project.slug)">
|
||||
{{ apiProject.stats.views !== 1 ? ' stars' : ' star' }}
|
||||
</a>
|
||||
</p>
|
||||
<p v-if="apiProject">
|
||||
<span id="watcher-count">{{ apiProject.stats.watchers }}</span>
|
||||
<a :href="ROUTES.parse('PROJECTS_SHOW_WATCHERS', project.ownerName, project.slug)">
|
||||
{{ apiProject.stats.views !== 1 ? ' watchers' : ' watcher' }}
|
||||
</a>
|
||||
</p>
|
||||
<p v-if="apiProject">
|
||||
<span id="download-count">{{ apiProject.stats.downloads }} total download{{ apiProject.stats.downloads !== 1 ? 's' : '' }}</span>
|
||||
</p>
|
||||
<p v-if="project.licenseName && project.licenseUrl">
|
||||
{{ $t('project.license.link') }}
|
||||
<a :href="project.licenseUrl" target="_blank" ref="noopener">{{ project.licenseName }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title" v-text="$t('project.promotedVersions')"></h3>
|
||||
</div>
|
||||
<div v-if="apiProject" class="list-group promoted-list">
|
||||
<a
|
||||
v-for="(version, index) in apiProject.promoted_versions"
|
||||
:key="`${index}-${version.version}`"
|
||||
class="list-group-item list-group-item-action"
|
||||
:href="ROUTES.parse('VERSIONS_SHOW', project.ownerName, project.slug, version.version)"
|
||||
>
|
||||
{{ version.version }}
|
||||
<Tag v-for="(tag, index) in version.tags" :key="index" :color="tag.color" :data="tag.display_data" :name="tag.name"></Tag>
|
||||
</a>
|
||||
</div>
|
||||
<div v-else class="text-center py-4">
|
||||
<i class="fas fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="float-left card-title" v-text="$t('page.plural')"></h3>
|
||||
<button v-if="canEditPages" data-toggle="modal" data-target="#new-page" title="New" class="new-page btn btn-primary btn-sm float-right">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<li v-for="{ key: pg, value: pages } in rootPages" :key="pg.id" class="list-group-item">
|
||||
<template v-if="pages.length">
|
||||
<a v-if="expanded[pg.name]" class="toggle-collapse page-collapse" @click.prevent="expanded[pg.name] = false">
|
||||
<i class="far fa-minus-square"></i>
|
||||
</a>
|
||||
<a v-else class="toggle-collapse page-expand" @click.prevent="expanded[pg.name] = true">
|
||||
<i class="far fa-plus-square"></i>
|
||||
</a>
|
||||
</template>
|
||||
<a :href="ROUTES.parse('PAGES_SHOW', project.ownerName, project.slug, pg.slug)" class="href" v-text="pg.name"></a>
|
||||
<transition name="collapse">
|
||||
<ul v-if="pages.length && expanded[pg.name]" class="list-group sub-page-group">
|
||||
<li v-for="subpage in pages" :key="subpage.id" class="list-group-item">
|
||||
<a :href="ROUTES.parse('PAGES_SHOW', project.ownerName, project.slug, subpage.slug)" class="href" v-text="subpage.name"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</transition>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HangarModal target-id="new-page" label-id="new-page-label" modal-class="modal-lg">
|
||||
<template v-slot:modal-content>
|
||||
<div class="modal-header">
|
||||
<h4 v-show="!newPage.error" class="modal-title" id="new-page-label" v-text="$t('page.new.title')"></h4>
|
||||
<h4 v-show="newPage.error" class="modal-title" id="new-page-label-error" v-text="$t('page.new.error')"></h4>
|
||||
<button type="button" class="close" data-dismiss="modal" :aria-label="$t('general.close')">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body input-group">
|
||||
<div class="setting">
|
||||
<div class="setting-description">
|
||||
<h4 v-text="$t('project.page.name._')"></h4>
|
||||
<p v-text="$t('project.page.name.info')"></p>
|
||||
</div>
|
||||
<div class="setting-content">
|
||||
<label for="page-name" class="sr-only">Page Name</label>
|
||||
<input class="form-control" type="text" id="page-name" v-model="newPage.pageName" />
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="setting setting-no-border">
|
||||
<div class="setting-description">
|
||||
<h4 v-text="$t('project.page.parent._')"></h4>
|
||||
<p v-text="$t('project.page.parent.info')"></p>
|
||||
</div>
|
||||
<div class="setting-content">
|
||||
<label for="new-page-parent-select" class="sr-only"></label>
|
||||
<select class="form-control select-parent" id="new-page-parent-select" v-model="newPage.parentPage">
|
||||
<option disabled hidden :value="null"><none></option>
|
||||
<option v-for="pg in noHomePage(rootPages)" :key="pg.id" :value="{ id: pg.id, slug: pg.slug }" v-text="pg.name"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" v-text="$t('general.close')"></button>
|
||||
<button id="continue-page" type="button" class="btn btn-primary" v-text="$t('general.continue')" @click="createPage"></button>
|
||||
</div>
|
||||
</template>
|
||||
</HangarModal>
|
||||
</template>
|
||||
<script>
|
||||
import Editor from '@/components/Editor';
|
||||
import Tag from '@/components/Tag';
|
||||
import HangarModal from '@/components/HangarModal';
|
||||
import moment from 'moment';
|
||||
import { API } from '@/api';
|
||||
import { go, slugify } from '@/utils';
|
||||
import $ from 'jquery';
|
||||
|
||||
$.ajaxSetup(window.ajaxSettings);
|
||||
|
||||
export default {
|
||||
name: 'ProjectView',
|
||||
components: { Editor, Tag, HangarModal },
|
||||
data() {
|
||||
return {
|
||||
ROUTES: window.ROUTES,
|
||||
project: window.PROJECT,
|
||||
rootPages: window.ROOT_PAGES,
|
||||
page: window.PAGE,
|
||||
canEditPages: window.CAN_EDIT_PAGES,
|
||||
apiProject: null,
|
||||
newPage: {
|
||||
error: false,
|
||||
pageName: null,
|
||||
parentPage: null,
|
||||
},
|
||||
expanded: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
console.log(this.project);
|
||||
console.log(this.page);
|
||||
console.log(this.rootPages);
|
||||
API.request(`projects/${this.project.ownerName}/${this.project.slug}`).then((res) => {
|
||||
this.apiProject = res;
|
||||
console.log(this.apiProject);
|
||||
});
|
||||
this.expanded[this.page.name] = true;
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
formatCategory(apiName) {
|
||||
const formatted = apiName.replace('_', ' ');
|
||||
return this.capitalize(formatted);
|
||||
},
|
||||
capitalize(input) {
|
||||
return input
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.map((s) => s.charAt(0).toUpperCase() + s.substring(1))
|
||||
.join(' ');
|
||||
},
|
||||
noHomePage(pages) {
|
||||
return pages.filter((pg) => pg.key.name !== 'Home').map((pg) => pg.key);
|
||||
},
|
||||
createPage() {
|
||||
let url = `/${this.project.ownerName}/${this.project.slug}/pages/`;
|
||||
let parentId = null;
|
||||
if (this.newPage.parentPage && this.newPage.parentPage.id !== -1) {
|
||||
parentId = this.newPage.parentPage.id;
|
||||
url += `${this.newPage.parentPage.slug}/${slugify(this.newPage.pageName)}/edit`;
|
||||
} else {
|
||||
url += `${slugify(this.newPage.pageName)}/edit`;
|
||||
}
|
||||
$.ajax({
|
||||
method: 'post',
|
||||
url,
|
||||
data: {
|
||||
'parent-id': parentId,
|
||||
content: '# ' + this.newPage.pageName + '\n',
|
||||
name: this.newPage.pageName,
|
||||
},
|
||||
success() {
|
||||
go(url);
|
||||
},
|
||||
error(err) {
|
||||
console.error(err);
|
||||
this.newPage.error = true;
|
||||
setTimeout((self) => (self.newPage.error = false), 2000, this);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.toggle-collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sub-page-group {
|
||||
max-height: 400px;
|
||||
}
|
||||
.collapse-enter-active,
|
||||
.collapse-leave-active {
|
||||
transition: max-height 0.3s;
|
||||
}
|
||||
|
||||
.collapse-enter-from,
|
||||
.collapse-leave-to {
|
||||
max-height: 0;
|
||||
}
|
||||
</style>
|
26
src/main/frontend/src/VersionView.vue
Normal file
26
src/main/frontend/src/VersionView.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<Editor
|
||||
:save-call="ROUTES.parse('VERSIONS_SAVE_DESCRIPTION', project.ownerName, project.slug, version.versionString)"
|
||||
:enabled="canEditPages"
|
||||
subject="Version"
|
||||
:raw="version.description || ''"
|
||||
></Editor>
|
||||
</template>
|
||||
<script>
|
||||
import Editor from '@/components/Editor';
|
||||
|
||||
export default {
|
||||
name: 'VersionView',
|
||||
components: {
|
||||
Editor,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ROUTES: window.ROUTES,
|
||||
project: window.PROJECT,
|
||||
version: window.VERSION,
|
||||
canEditPages: window.CAN_EDIT_PAGES,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
612
src/main/frontend/src/assets/locales/en.json
Normal file
612
src/main/frontend/src/assets/locales/en.json
Normal file
@ -0,0 +1,612 @@
|
||||
{
|
||||
"activity": {
|
||||
"title": "Activity:"
|
||||
},
|
||||
"admin": {
|
||||
"health": {
|
||||
"discuss": "Missing discussion topic",
|
||||
"hidden": "Hidden projects",
|
||||
"jobs": "Failed jobs",
|
||||
"missingFile": "Missing File",
|
||||
"platform": "No platform detected",
|
||||
"stale": "Stale projects",
|
||||
"title": "Hangar Health Report",
|
||||
"topic": "Failed discussion topic updates"
|
||||
},
|
||||
"log": {
|
||||
"title": "User Action Log"
|
||||
},
|
||||
"platformVersions": {
|
||||
"title": "Platform Versions"
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"deploy": {
|
||||
"channelNotFound": "Channel not found.",
|
||||
"invalidKey": "Invalid API key.",
|
||||
"versionExists": "A version of that name already exists."
|
||||
}
|
||||
},
|
||||
"aria": {
|
||||
"dropdown": {
|
||||
"menu": "dropdownMenu{0}"
|
||||
}
|
||||
},
|
||||
"channel": {
|
||||
"delete": {
|
||||
"_": "Delete channel",
|
||||
"info": {
|
||||
"_": "Are you sure you want to delete this channel?",
|
||||
"versions": "versions will be deleted."
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"cannotDeleteLast": "You cannot delete your last channel.",
|
||||
"close": "Close",
|
||||
"maxReached": "You have reached the maximum amount of channels permitted.",
|
||||
"save": "Save changes",
|
||||
"title": "Edit channel"
|
||||
},
|
||||
"list": {
|
||||
"description": "Release channels represent the state of a plugin release. A project may have up to five release channels.",
|
||||
"pageTitle": "Channels | {0} / {1}",
|
||||
"title": "Release channels"
|
||||
},
|
||||
"name": "Channel name",
|
||||
"nameRequirements": "Name must be: unique, alphanumerical, no spaces, max 15 characters",
|
||||
"nonReviewed": {
|
||||
"_": "Exclude versions in this channel from the staff approval queue",
|
||||
"info": "Certain restrictions may apply"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"deleteConfirm": "Are you sure you want to delete {0}? This cannot be undone."
|
||||
},
|
||||
"email": {
|
||||
"accountUnlock": {
|
||||
"body": "We have detected that you've recently unlocked your account with us on Hangar. If you did not make this change, please change your password immediately and take the necessary precautions to secure your account, such as enabling two-factor authentication. Thank you.",
|
||||
"subject": "User account unlocked"
|
||||
},
|
||||
"signature": "Best regards,<br/>The team at PaperMC"
|
||||
},
|
||||
"error": {
|
||||
"channel": {
|
||||
"duplicateColor": "A channel with that color already exists.",
|
||||
"duplicateName": "A channel with that name already exists.",
|
||||
"invalidName": "{0} is an invalid channel name.",
|
||||
"last": "You cannot delete your only channel.",
|
||||
"lastNonEmpty": "You cannot delete your only non-empty channel.",
|
||||
"lastReviewed": "You cannot delete your only reviewed channel.",
|
||||
"maxChannels": "A project may only have up to {0} channels",
|
||||
"minOneReviewed": "There must be at least one reviewed channel."
|
||||
},
|
||||
"invalidFile": "That is an invalid file type.",
|
||||
"invalidUrl": "That URL is invalid.",
|
||||
"loginFailed": "Authentication failed.",
|
||||
"maxLength": "Content too long.",
|
||||
"minLength": "Content too short.",
|
||||
"nameUnavailable": "That name is not available.",
|
||||
"noFile": "No file submitted.",
|
||||
"noLogin": "Login is temporarily unavailable, please try again later.",
|
||||
"notFound": {
|
||||
"message": "The URI \"{0}\" could not be found",
|
||||
"title": "404 Not Found"
|
||||
},
|
||||
"ore": {
|
||||
"timeout": {
|
||||
"_": "Timeout",
|
||||
"body": "Whoops! We couldn't respond to your request in a timely manner, this could be because a service Hangar uses is currently unavailable or the servers are just overloaded. Please try again later.",
|
||||
"title": "Hangar took too long to respond."
|
||||
}
|
||||
},
|
||||
"org": {
|
||||
"cannotCreate": "Unable to create an organization at this time.",
|
||||
"cannotUpdateAvatar": "Unable to update avatar at this time.",
|
||||
"createLimit": "You may only create up to {0} organizations!",
|
||||
"disabled": "Apologies, creation of Organizations is temporarily disabled."
|
||||
},
|
||||
"plugin": {
|
||||
"fileExtension": "Plugin file must be either a JAR or ZIP file.",
|
||||
"fileName": "Filename already in use. Please rename your file and try again.",
|
||||
"incomplete": "Plugin meta file missing required field \"{0}\".",
|
||||
"invalidVersion": "You selected an invalid version for that platform.",
|
||||
"jarNotFound": "Could not find a JAR file in the top level of ZIP file.",
|
||||
"metaNotFound": "No plugin meta file found.",
|
||||
"noConfirmDownload": "Could not verify your confirmation. It might have timed out, already been used, or something might have changed on your end.",
|
||||
"noVersion": "Plugin meta file missing version field.",
|
||||
"notForge": "Project is marked as a Forge Mod and new version contains no dependency to Forge.",
|
||||
"notSponge": "Project is marked as a Paper Plugin and new version contains no dependency to Paper.",
|
||||
"owner": "Uploading under the wrong owner.",
|
||||
"stateChanged": "Something changed when you tried to download the plugin. Please try again.",
|
||||
"timeout": "Version upload timed out, or you have not uploaded a plugin file.",
|
||||
"unexpected": "An unexpected error occurred, please try again later.",
|
||||
"versionExists": "The version you tried to upload already exists."
|
||||
},
|
||||
"project": {
|
||||
"categoryNotFound": "No category with that name found.",
|
||||
"invalidCategory": "Invalid category",
|
||||
"invalidName": "Project name is invalid",
|
||||
"invalidPluginFile": "Invalid plugin file.",
|
||||
"maxKeywords": "Too many keywords! Max is {0}",
|
||||
"nameExists": "A project with that name already exists",
|
||||
"notFound": "No project found for id {0}",
|
||||
"ownerNotFound": "Owner not found",
|
||||
"slugExists": "A project with that slug already exists",
|
||||
"tooManyKeywords": "Too many keywords specified."
|
||||
},
|
||||
"required": {
|
||||
"comment": "A comment is required"
|
||||
},
|
||||
"spongeauth": {
|
||||
"auth": "Couldn't connect to PaperAuth.",
|
||||
"parse": "That name is invalid, please choose a name without spaces or symbols."
|
||||
},
|
||||
"tagline": {
|
||||
"tooLong": "Tagline is too long (max {0})."
|
||||
},
|
||||
"user": {
|
||||
"locked": "Your account is locked."
|
||||
},
|
||||
"version": {
|
||||
"duplicate": "Found a duplicate file in project. Plugin files may only be uploaded once.",
|
||||
"fileIOError": "There was a File IO error.",
|
||||
"illegalVersion": "The name of this version is not allowed.",
|
||||
"noDependency": {
|
||||
"forge": "This project is marked as a Forge mod but your uploaded file contains no dependency to Forge. Please add a dependency to forge and re-upload.",
|
||||
"sponge": "This project is marked as a Paper plugin but your uploaded file contains no dependency to Paper. Please add a dependency to paperapi and re-upload."
|
||||
},
|
||||
"onlyOnePublic": "You only have 1 public version left."
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"about": "About",
|
||||
"any": "Any",
|
||||
"api": "API",
|
||||
"appName": "Hangar",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"code": "Code",
|
||||
"contact": "Support Forum",
|
||||
"continue": "Continue",
|
||||
"create": "Create",
|
||||
"delete": "Delete",
|
||||
"description": "A Paper Plugin Repository",
|
||||
"disclaimer": "Disclaimer",
|
||||
"docs": "Docs",
|
||||
"download": "Download",
|
||||
"downloadExternal": "Download (External)",
|
||||
"edit": "Edit",
|
||||
"featuredSponsor": "Featured Sponsor",
|
||||
"forge": "Forge",
|
||||
"forgeMod": {
|
||||
"_": "Forge Mod",
|
||||
"tooltip": "To mark your project as a Forge mod, add forge as a dependency."
|
||||
},
|
||||
"forums": "Forums",
|
||||
"forumsUnavailable": "Paper Forums are currently unavailable, please try again later.",
|
||||
"getsponge": "Downloads",
|
||||
"harddelete": "Hard delete",
|
||||
"home": "Homepage",
|
||||
"irc": "Community",
|
||||
"issues": "Issues",
|
||||
"javadocs": "Javadocs",
|
||||
"language": "en_US",
|
||||
"learnMore": "Learn more",
|
||||
"linkout": {
|
||||
"title": "External Link Warning",
|
||||
"warning": "You have clicked on an external link to \"{0}\". If you did not intend to visit this link, please go back. Otherwise, click continue."
|
||||
},
|
||||
"login": "Log in",
|
||||
"minecraft": "Minecraft",
|
||||
"more": "More",
|
||||
"name": "Name",
|
||||
"notice": "Notice",
|
||||
"openapi": "Hangar OpenAPI specification",
|
||||
"optional": "optional",
|
||||
"organization": "PaperMC",
|
||||
"platform": "Platform",
|
||||
"plugins": "Plugins",
|
||||
"preview": "Preview",
|
||||
"projectName": "Paper",
|
||||
"remove": "Remove",
|
||||
"restore": "Restore deleted",
|
||||
"save": "Save",
|
||||
"signout": "Sign out",
|
||||
"signup": "Sign up",
|
||||
"source": "Source",
|
||||
"sponge": "Paper",
|
||||
"spongeApi": "PaperAPI",
|
||||
"spongePlugin": {
|
||||
"_": "Paper Plugin",
|
||||
"tooltip": "To mark your project as a Paper plugin, add paperapi as a dependency."
|
||||
},
|
||||
"sponsoredBy": "Sponsored By",
|
||||
"sponsors": "Sponsors",
|
||||
"stagingWarning": "This is a staging server for testing purposes. Data could be deleted at any time. Please use our production server at (TODO: prod url, lol) for uploading your plugins!",
|
||||
"terms": "Terms of Service",
|
||||
"title": "Hangar - A Paper Plugin Repository",
|
||||
"toReply": "to reply to this discussion",
|
||||
"update": "Update",
|
||||
"upload": "Upload",
|
||||
"viewOnForums": "View on Paper Forums",
|
||||
"warning": "Warning"
|
||||
},
|
||||
"licenses": {
|
||||
"apache2": [
|
||||
"Apache 2.0"
|
||||
],
|
||||
"custom": "Custom",
|
||||
"gpl": "GNU General Public License (GPL)",
|
||||
"lgpl": "GNU Lesser General Public License (LGPL)",
|
||||
"mit": "MIT"
|
||||
},
|
||||
"notes": {
|
||||
"_": "Notes",
|
||||
"addmessage": "Add message"
|
||||
},
|
||||
"notification": {
|
||||
"all": "All",
|
||||
"empty": {
|
||||
"all": "You have no notifications.",
|
||||
"read": "You have no read notifications.",
|
||||
"unread": "You have no unread notifications."
|
||||
},
|
||||
"invite": {
|
||||
"_": "You have been invited to join the {0}",
|
||||
"accept": "Accept",
|
||||
"all": "All",
|
||||
"decline": "Decline",
|
||||
"declined": "You have declined an invite from <strong>{0}</strong>.",
|
||||
"joined": "You have joined <strong>{0}</strong>!",
|
||||
"organizations": "Organizations",
|
||||
"projects": "Projects",
|
||||
"undo": "Undo",
|
||||
"visit": "Visit now"
|
||||
},
|
||||
"invites": "Invites",
|
||||
"markAllRead": "Mark all as read",
|
||||
"organization": {
|
||||
"invite": "You have been invited to join the group {0} in the organization {1}."
|
||||
},
|
||||
"plural": "Notifications",
|
||||
"project": {
|
||||
"invite": "You have been invited to join the group {0} on the project {1}.",
|
||||
"newVersion": "A new version has been released for {0}: {1}.",
|
||||
"reviewed": "{0} {1} has been reviewed and is approved."
|
||||
},
|
||||
"read": "Read",
|
||||
"unread": "Unread"
|
||||
},
|
||||
"org": {
|
||||
"create": {
|
||||
"_": "New Organization",
|
||||
"title": "Create a new Organization"
|
||||
},
|
||||
"info": "Organizations allow you group users provide closer collaboration between them within your projects on Hangar.",
|
||||
"name": "Organization name",
|
||||
"plural": "Organizations",
|
||||
"users": {
|
||||
"add": "Add User",
|
||||
"save": "Save Users"
|
||||
},
|
||||
"welcome": {
|
||||
"_": "Welcome to your new organization! Start by changing your avatar by clicking on it.",
|
||||
"confirm": "Got it!"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
"avatarFailed": "Failed to request token for changing the organization avatar."
|
||||
},
|
||||
"page": {
|
||||
"edit": {
|
||||
"cannotDeleteHomepage": "Your project's homepage cannot be deleted",
|
||||
"maxPages": "You have reached the maximum amount of allowed pages."
|
||||
},
|
||||
"new": {
|
||||
"error": "Error creating page",
|
||||
"title": "Create a new page"
|
||||
},
|
||||
"plural": "Pages"
|
||||
},
|
||||
"ph": {
|
||||
"comment": "Comment"
|
||||
},
|
||||
"project": {
|
||||
"author": "Author",
|
||||
"back": "Go back",
|
||||
"category": {
|
||||
"_": "Category",
|
||||
"info": "Category: {0}",
|
||||
"plural": "Categories"
|
||||
},
|
||||
"create": {
|
||||
"_": "New Project",
|
||||
"destination": "Project will be created at {0}",
|
||||
"infoText": {
|
||||
"guidelines": "Before continuing, please review the <a href",
|
||||
"head": "A project contains your downloads and the documentation for your plugin."
|
||||
},
|
||||
"input": {
|
||||
"category": "Project category",
|
||||
"description": "Project description",
|
||||
"name": "Project name"
|
||||
},
|
||||
"issue-input": "Issue tracker URL",
|
||||
"selectFile": "Select plugin file",
|
||||
"source-input": "Source code URL",
|
||||
"title": "Create a new project",
|
||||
"uid": "Unique ID"
|
||||
},
|
||||
"delete": {
|
||||
"info": {
|
||||
"_": "Are you sure you want to delete your Project? This action cannot be undone. Please explain why you want to delete it.",
|
||||
"uniqueid": "WARNING: You or anybody else will not be able to use the plugin ID \"{0}\" in the future if you continue. If you are deleting your project to recreate it, please do not delete your project and contact the Hangar staff for help."
|
||||
},
|
||||
"title": "Delete project"
|
||||
},
|
||||
"deleted": "Project \"{0}\" deleted.",
|
||||
"discuss": {
|
||||
"_": "Discuss",
|
||||
"postAs": "Posting as:"
|
||||
},
|
||||
"docs": "Docs",
|
||||
"download": {
|
||||
"external": "External Download",
|
||||
"recommend": {
|
||||
"_": "Download the latest recommended version",
|
||||
"warn": "This project's recommended version has not been reviewed by our moderation staff and may not be safe for download."
|
||||
},
|
||||
"warn": "This version has not been reviewed by our moderation staff and may not be safe for download."
|
||||
},
|
||||
"downloads": "Downloads",
|
||||
"flag": {
|
||||
"_": "Flag",
|
||||
"plural": "Flags"
|
||||
},
|
||||
"hot": "Hot",
|
||||
"icon": "Project icon",
|
||||
"latest": "Latest",
|
||||
"license": {
|
||||
"link": "Licensed under"
|
||||
},
|
||||
"log": {
|
||||
"logger": {
|
||||
"title": "Project Log"
|
||||
},
|
||||
"visibility": {
|
||||
"title": "Visibility Log"
|
||||
}
|
||||
},
|
||||
"manager": "Project Manager",
|
||||
"members": {
|
||||
"infoText": {
|
||||
"bottom": "Hangar was able to find <strong>{0}</strong> users who are registered with Hangar in your plugin file.",
|
||||
"head": "Project members are other users of Hangar that have special access to your project. Different user \"roles\" define what a user can and cannot do to your project."
|
||||
}
|
||||
},
|
||||
"new": "New",
|
||||
"owner": "Owner",
|
||||
"page": {
|
||||
"name": {
|
||||
"_": "Page name",
|
||||
"info": "Enter a title for your new page."
|
||||
},
|
||||
"parent": {
|
||||
"_": "Parent page",
|
||||
"info": "Select a parent page (optional)"
|
||||
}
|
||||
},
|
||||
"promotedVersions": "Promoted Versions",
|
||||
"publishDate": "Published on {0}",
|
||||
"removeMember": {
|
||||
"_": "Remove member",
|
||||
"confirm": "Are you sure you want to remove this user?"
|
||||
},
|
||||
"rename": {
|
||||
"_": "Rename",
|
||||
"info": "Changing your projects name can have undesired consequences. We will not setup any redirects.",
|
||||
"title": "Rename project"
|
||||
},
|
||||
"search": "Search for projects.",
|
||||
"settings": {
|
||||
"_": "Settings",
|
||||
"deployKey": {
|
||||
"_": "Deployment key",
|
||||
"info": "Generate a unique deployment key to enable build deployment from Gradle"
|
||||
},
|
||||
"forumSync": {
|
||||
"_": "Create posts on the forums",
|
||||
"info": "Sets if events like a new release should automatically create a post on the forums"
|
||||
},
|
||||
"genKey": "Generate key",
|
||||
"license": {
|
||||
"_": "License",
|
||||
"info": "What can people do (and not do) with your project?"
|
||||
},
|
||||
"members": "Members",
|
||||
"revokeKey": "Revoke key"
|
||||
},
|
||||
"starred": "Stars",
|
||||
"top": "Top",
|
||||
"version": {
|
||||
"new": "New Version"
|
||||
},
|
||||
"versions": "Versions",
|
||||
"viewAuthors": "View project creators.",
|
||||
"viewStaff": "View Paper staff.",
|
||||
"views": "Views",
|
||||
"watching": "Watching"
|
||||
},
|
||||
"prompt": {
|
||||
"changeAvatar": {
|
||||
"message": "Welcome to your new organization! Start by changing your avatar by clicking on it.",
|
||||
"title": "Change your avatar!"
|
||||
},
|
||||
"confirm": "Got it!"
|
||||
},
|
||||
"queue": {
|
||||
"review": {
|
||||
"none": "There are no reviews in progress"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"addmessage": "Add message",
|
||||
"log": "Review logs",
|
||||
"start": "Start review",
|
||||
"stop": "Stop review",
|
||||
"takeover": "Takeover review",
|
||||
"title": "Review - {0} {1}",
|
||||
"whystop": "Please explain why you are stopping the review",
|
||||
"whytakeover": "Please explain why you are takingover"
|
||||
},
|
||||
"user": {
|
||||
"apiKeys": {
|
||||
"createKeyBtn": "Create key",
|
||||
"createNew": "Create new key",
|
||||
"error": {
|
||||
"nameAlreadyUsed": "Name already used",
|
||||
"noNameSet": "No name specified",
|
||||
"noPermsSet": "No permissions specified",
|
||||
"tooLongName": "Too long name"
|
||||
},
|
||||
"existingKeys": "Existing keys",
|
||||
"keyDeleteButton": "Delete key",
|
||||
"keyDeleteColumn": "Delete",
|
||||
"keyIdentifier": "Key identifier",
|
||||
"keyName": "Name",
|
||||
"keyPermissions": "Permissions",
|
||||
"keyToken": "Key"
|
||||
},
|
||||
"avatar": {
|
||||
"byFile": "Update by file",
|
||||
"byUrl": "Update by URL"
|
||||
},
|
||||
"editAvatar": "Edit avatar",
|
||||
"enterPassword": "Enter your password to continue:",
|
||||
"flags": {
|
||||
"markAllResolved": "Mark all resolved",
|
||||
"markResolved": "Mark resolved",
|
||||
"messageOwner": "Message project owner",
|
||||
"messageUser": "Message user",
|
||||
"none": "There are no flags to review."
|
||||
},
|
||||
"lock": {
|
||||
"_": "Lock account",
|
||||
"confirm": "Are you sure you want to lock your account? You will not be able to upload files or modify any existing projects."
|
||||
},
|
||||
"memberSince": "A member since {0}",
|
||||
"noOrganizations": "{0} is not part of any organizations. :(",
|
||||
"noStars": "{0} has not starred any projects. :(",
|
||||
"noWatching": "{0} is not watching any projects. :(",
|
||||
"notFound": "Could not find user",
|
||||
"queue": {
|
||||
"_": "Version approvals",
|
||||
"approve": "Approve",
|
||||
"approvePartial": "Approve Partial",
|
||||
"none": "There are no versions to review.",
|
||||
"open": "Approval queue",
|
||||
"progress": "In Review"
|
||||
},
|
||||
"seeAll": "See all",
|
||||
"stats": "Stats",
|
||||
"tagline": {
|
||||
"_": "Tagline",
|
||||
"edit": "Edit tagline",
|
||||
"info": "Add a short tagline to let people know what you're about!"
|
||||
},
|
||||
"unlock": {
|
||||
"_": "Unlock account",
|
||||
"confirm": "Are you sure you want to unlock your account?"
|
||||
},
|
||||
"viewOnForums": "View on forums"
|
||||
},
|
||||
"version": {
|
||||
"_": "Version",
|
||||
"approved": {
|
||||
"_": "Approved",
|
||||
"info": "<strong>{0}</strong> approved this version on <strong>{1}</strong>"
|
||||
},
|
||||
"approvedPartial": "Partially Approved",
|
||||
"create": {
|
||||
"externalUrl": "External download URL",
|
||||
"info": "Release a new version for <strong>{0}</strong>.",
|
||||
"noDescription": "No description given.",
|
||||
"pageTitle": "Release a new version",
|
||||
"publish": "Publish",
|
||||
"selectFile": "Select file",
|
||||
"title": "New project release",
|
||||
"tos": "By clicking \"Publish\" you are agreeing to Hangar's <a href",
|
||||
"unstable": "Mark this version as unstable",
|
||||
"upload": "Upload"
|
||||
},
|
||||
"delete": {
|
||||
"alreadyDeleted": "This version has already been deleted",
|
||||
"cannotLast": "Every project must have at least one version",
|
||||
"info": "Are you sure you want to delete this version? This action cannot be undone. Please explain why you want to delete it.",
|
||||
"title": "Delete version"
|
||||
},
|
||||
"dependency": {
|
||||
"no": "This release has no dependencies",
|
||||
"notOnOre": "This plugin is not available for download on Hangar"
|
||||
},
|
||||
"description": "Description",
|
||||
"download": {
|
||||
"confirm": {
|
||||
"body": {
|
||||
"api": "\nThis version has not been reviewed by our moderation staff and may not be safe for download. \nDisclaimer: We disclaim all responsibility for any harm to your server or system should you choose not to heed this \nwarning."
|
||||
},
|
||||
"curl": {
|
||||
"_": "\nPlease POST to the attached link to acknowledge this disclaimer and continue to the download. \ncurl -O -J -L -d -X \"{0}&csrfToken",
|
||||
"nocsrf": "\nPlease POST to the attached link to acknowledge this disclaimer and continue to the download. \ncurl -O -J -L -d -X \"{0}\""
|
||||
},
|
||||
"disclaimer": "We disclaim all responsibility for any harm to your server or system should you choose not to heed this warning.",
|
||||
"download": "Download it at my own risk",
|
||||
"externalUrl": "This version download is on an external site.",
|
||||
"header": "{1} {2} by {0}",
|
||||
"nonReviewedChannel": "This version will not be reviewed and may not be safe to use.",
|
||||
"reviewedChannel": "This version has not been reviewed by our moderation staff yet and may not be safe to use.",
|
||||
"title": "Download Warning for",
|
||||
"wget": "Sorry, but Hangar does not support the use of wget. \nPlease use the following curl instead: \ncurl -O -J -L \"<url>\""
|
||||
},
|
||||
"confirmPartial": {
|
||||
"api": "\nThis version has only been partially reviewed by our moderation staff and may not be safe for download. \nWhile the core plugin has been reviewed, other resources like shaded libraries have not. \nDisclaimer: We disclaim all responsibility for any harm to your server or system should you choose not to heed this \nwarning.",
|
||||
"reviewedChannel": "\nThis version has only been partially reviewed by our moderation staff and may not be safe for use. \nWhile the core plugin has been reviewed, other resources like shaded libraries have not."
|
||||
}
|
||||
},
|
||||
"externalUrl": "URL",
|
||||
"fileSize": "File size",
|
||||
"filename": "File name",
|
||||
"log": {
|
||||
"logger": {
|
||||
"title": "Project Log"
|
||||
},
|
||||
"visibility": {
|
||||
"title": "Visibility Log"
|
||||
}
|
||||
},
|
||||
"recommended": "Recommended version",
|
||||
"releaseBulletin": {
|
||||
"_": "Release Bulletin",
|
||||
"info": "What''s new in this release?"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"name": {
|
||||
"needsApproval": "Needs approval",
|
||||
"needsChanges": "Needs changes",
|
||||
"new": "New",
|
||||
"public": "Public",
|
||||
"softDelete": "Soft deleted"
|
||||
},
|
||||
"notice": {
|
||||
"author": {
|
||||
"new": "Click ''Publish'' to publish. This project will automatically be moved from the new stage in 24 hours."
|
||||
},
|
||||
"needsApproval": "You have sent the project for review",
|
||||
"needsChanges": "This project requires changes:",
|
||||
"new": "This project is new, and will not be shown to others until a version has been uploaded. If a version is not uploaded over a longer time the project will be deleted.",
|
||||
"public": "None",
|
||||
"softDelete": "Project deleted by {0}"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +1,73 @@
|
||||
<template>
|
||||
<template v-if="enabled">
|
||||
<!-- Edit -->
|
||||
<button type="button" class="btn btn-sm btn-edit btn-page btn-default" title="Edit" @click.stop="pageBtnClick($event.currentTarget)">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-edit btn-page btn-default"
|
||||
:class="{ open: editing && !previewing }"
|
||||
:title="$t('general.edit')"
|
||||
@click.stop="edit"
|
||||
>
|
||||
<i class="fas fa-edit"></i> {{ $t('general.edit') }}
|
||||
</button>
|
||||
|
||||
<!-- Preview -->
|
||||
<div class="btn-edit-container btn-preview-container" title="Preview">
|
||||
<button type="button" class="btn btn-sm btn-preview btn-page btn-default" @click.stop="pageBtnClick($event.currentTarget)">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="saveable" class="btn-edit-container btn-save-container" title="Save">
|
||||
<button form="form-editor-save" type="submit" class="btn btn-sm btn-save btn-page btn-default" @click.stop="pageBtnClick($event.currentTarget)">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="cancellable" class="btn-edit-container btn-cancel-container" title="Cancel">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-cancel btn-page btn-default"
|
||||
@click.stop="
|
||||
btnCancel();
|
||||
pageBtnClick($event.currentTarget);
|
||||
"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template v-if="deletable">
|
||||
<div class="btn-edit-container btn-delete container" title="Delete">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-page-delete btn-page btn-default"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-page-delete"
|
||||
@click.stop="pageBtnClick($event.currentTarget)"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
<transition name="button-hide">
|
||||
<div v-if="editing" class="btn-edit-container btn-preview-container" :class="{ open: previewing }" :title="$t('general.preview')">
|
||||
<button type="button" class="btn btn-sm btn-preview btn-page btn-default" @click.stop="preview">
|
||||
<i v-show="loading.preview" class="fas fa-loading"></i>
|
||||
<i v-show="!loading.preview" class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<div class="modal fade" id="modal-page-delete" tabindex="-1" role="dialog" aria-labelledby="label-page-delete">
|
||||
<transition name="button-hide">
|
||||
<div v-if="saveable && editing" class="btn-edit-container btn-save-container" :title="$t('general.save')">
|
||||
<button form="form-editor-save" type="submit" class="btn btn-sm btn-save btn-page btn-default">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition name="button-hide">
|
||||
<div v-if="cancellable && editing" class="btn-edit-container btn-cancel-container" :title="$t('general.cancel')">
|
||||
<button type="button" class="btn btn-sm btn-cancel btn-page btn-default" @click.stop="cancel">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition name="button-hide">
|
||||
<template v-if="deletable && editing">
|
||||
<div class="btn-edit-container btn-delete-container" :title="$t('general.delete')">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-page-delete btn-page btn-default"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-page-delete"
|
||||
style="color: var(--danger)"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</transition>
|
||||
|
||||
<!-- Edit window -->
|
||||
<div v-show="editing && !previewing" class="page-edit page-content-view">
|
||||
<textarea name="content" class="form-control" :form="targetForm || 'form-editor-save'" v-model="content"></textarea>
|
||||
</div>
|
||||
<div v-show="editing && previewing" class="page-preview page-rendered page-content-view" v-html="previewContent"></div>
|
||||
|
||||
<HangarForm v-if="saveable" method="post" :action="saveCall" id="form-editor-save">
|
||||
<input v-if="extraFormValue" type="hidden" :value="extraFormValue" name="name" />
|
||||
</HangarForm>
|
||||
|
||||
<div v-show="!editing" class="page-content page-rendered page-content-view" v-html="preCooked"></div>
|
||||
|
||||
<teleport to="body">
|
||||
<div v-if="deletable" class="modal fade" id="modal-page-delete" tabindex="-1" role="dialog" aria-labelledby="label-page-delete">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -63,28 +86,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Edit window -->
|
||||
<div class="page-edit version-content-view" style="display: none">
|
||||
<textarea name="content" class="form-control" :form="targetForm || 'form-editor-save'" v-model="content"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Preview window -->
|
||||
<div class="page-preview page-rendered version-content-view" style="display: none"></div>
|
||||
|
||||
<HangarForm v-if="saveable" method="post" :action="saveCall" id="form-editor-save">
|
||||
<input v-if="extraFormValue" type="hidden" :value="extraFormValue" name="name" />
|
||||
</HangarForm>
|
||||
|
||||
<div class="page-content page-rendered">{{ cooked }}</div>
|
||||
</teleport>
|
||||
</template>
|
||||
</template>
|
||||
<script>
|
||||
import { nextTick } from 'vue';
|
||||
import HangarForm from '@/components/HangarForm';
|
||||
import $ from 'jquery';
|
||||
import axios from 'axios';
|
||||
import { toggleSpinner } from '@/utils';
|
||||
|
||||
export default {
|
||||
name: 'Editor',
|
||||
@ -95,23 +103,41 @@ export default {
|
||||
props: {
|
||||
saveCall: String,
|
||||
deleteCall: String,
|
||||
saveable: Boolean,
|
||||
saveable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
deletable: Boolean,
|
||||
enabled: Boolean,
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
raw: String,
|
||||
subject: String,
|
||||
cancellable: Boolean,
|
||||
cancellable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
targetForm: String,
|
||||
extraFormValue: String,
|
||||
contentProp: String,
|
||||
open: Boolean,
|
||||
},
|
||||
computed: {
|
||||
content: {
|
||||
get() {
|
||||
return this.contentProp;
|
||||
if (typeof this.contentProp !== 'undefined') {
|
||||
return this.contentProp;
|
||||
} else {
|
||||
return this.inputContent;
|
||||
}
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:contentProp', val);
|
||||
if (typeof this.contentProp !== 'undefined') {
|
||||
this.$emit('update:contentProp', val);
|
||||
} else {
|
||||
this.inputContent = val;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -119,149 +145,238 @@ export default {
|
||||
return {
|
||||
editing: false,
|
||||
previewing: false,
|
||||
cooked: null,
|
||||
inputContent: null,
|
||||
preCooked: null,
|
||||
previewContent: null,
|
||||
loading: {
|
||||
preview: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showEditBtn(e) {
|
||||
return new Promise((resolve) => {
|
||||
this.animateEditBtn(e, '-34px', function () {
|
||||
e.css('z-index', '1000');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
animateEditBtn(e, marginLeft) {
|
||||
return new Promise((resolve) => {
|
||||
e.animate({ marginLeft: marginLeft }, 100, function () {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
hideEditBtn(e, andThen) {
|
||||
this.animateEditBtn(e, '0', andThen);
|
||||
},
|
||||
async getPreview(raw, target) {
|
||||
toggleSpinner($(target).find('[data-fa-i2svg]').toggleClass('fa-eye'));
|
||||
const res = await axios.post(
|
||||
'/pages/preview',
|
||||
{ raw },
|
||||
{
|
||||
headers: {
|
||||
[window.csrfInfo.headerName]: window.csrfInfo.token,
|
||||
},
|
||||
}
|
||||
);
|
||||
toggleSpinner($(target).find('[data-fa-i2svg]').toggleClass('fa-eye'));
|
||||
return res.data;
|
||||
},
|
||||
btnCancel() {
|
||||
this.editing = false;
|
||||
edit() {
|
||||
this.editing = true;
|
||||
this.previewing = false;
|
||||
|
||||
// hide editor; show content
|
||||
$('.page-edit').hide();
|
||||
$('.page-preview').hide();
|
||||
$('.page-content').show();
|
||||
|
||||
// move buttons behind
|
||||
$('.btn-edit-container').css('z-index', '-1000');
|
||||
|
||||
// hide buttons
|
||||
const fromSave = function () {
|
||||
this.hideEditBtn($('.btn-save-container'), function () {
|
||||
this.hideEditBtn($('.btn-preview-container'));
|
||||
});
|
||||
};
|
||||
|
||||
var btnDelete = $('.btn-delete-container');
|
||||
var btnCancel = $('.btn-cancel-container');
|
||||
if (btnDelete.length) {
|
||||
this.hideEditBtn(btnDelete, function () {
|
||||
this.hideEditBtn(btnCancel, fromSave);
|
||||
});
|
||||
} else {
|
||||
this.hideEditBtn(btnCancel, fromSave);
|
||||
}
|
||||
},
|
||||
async pageBtnClick(target) {
|
||||
if ($(target).hasClass('open')) return;
|
||||
$('button.open').removeClass('open').css('border', '1px solid #ccc');
|
||||
$(target).addClass('open').css('border-right-color', 'white');
|
||||
|
||||
const otherBtns = $('.btn-edit-container');
|
||||
const editor = $('.page-edit');
|
||||
if ($(target).hasClass('btn-edit')) {
|
||||
this.editing = true;
|
||||
this.previewing = false;
|
||||
$(this).css('position', 'absolute').css('top', '');
|
||||
$(otherBtns).css('position', 'absolute').css('top', '');
|
||||
|
||||
// open editor
|
||||
var content = $('.page-rendered');
|
||||
editor.find('textarea').css('height', content.css('height'));
|
||||
content.hide();
|
||||
editor.show();
|
||||
|
||||
// show buttons
|
||||
this.showEditBtn($('.btn-preview-container'))
|
||||
.then(() => {
|
||||
return this.showEditBtn($('.btn-save-container'));
|
||||
})
|
||||
.then(() => {
|
||||
return this.showEditBtn($('.btn-cancel-container'));
|
||||
})
|
||||
.then(() => {
|
||||
return this.showEditBtn($('.btn-delete-container'));
|
||||
});
|
||||
} else if ($(target).hasClass('btn-preview')) {
|
||||
// render markdown
|
||||
const preview = $('.page-preview');
|
||||
const raw = editor.find('textarea').val();
|
||||
editor.hide();
|
||||
preview.show();
|
||||
|
||||
preview.html(await this.getPreview(raw));
|
||||
|
||||
this.editing = false;
|
||||
this.previewing = true;
|
||||
} else if ($(target).hasClass('btn-save')) {
|
||||
// add spinner
|
||||
toggleSpinner($(target).find('[data-fa-i2svg]').toggleClass('fa-save'));
|
||||
async preview() {
|
||||
this.previewing = true;
|
||||
this.previewContent = await this.getPreview(this.content);
|
||||
},
|
||||
async cancel() {
|
||||
this.previewing = false;
|
||||
await nextTick();
|
||||
this.editing = false;
|
||||
},
|
||||
async getPreview(content) {
|
||||
if (content && content.trim().length > 0) {
|
||||
this.loading.preview = true;
|
||||
const res = await axios.post(
|
||||
'/pages/preview',
|
||||
{ raw: content },
|
||||
{
|
||||
headers: {
|
||||
[window.csrfInfo.headerName]: window.csrfInfo.token,
|
||||
},
|
||||
}
|
||||
);
|
||||
this.loading.preview = false;
|
||||
return res.data;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.content = this.raw;
|
||||
this.cooked = await this.getPreview(this.raw);
|
||||
},
|
||||
mounted() {
|
||||
const btnEdit = $('.btn-edit');
|
||||
if (!btnEdit.length) return;
|
||||
|
||||
const otherBtns = $('.btn-edit-container');
|
||||
|
||||
const editText = $('.page-edit').find('textarea');
|
||||
editText
|
||||
.focus(() => {
|
||||
btnEdit
|
||||
.css('border-color', '#66afe9')
|
||||
.css('border-right', '1px solid white')
|
||||
.css('box-shadow', 'inset 0 1px 1px rgba(0,0,0,.075), -3px 0 8px rgba(102, 175, 233, 0.6)');
|
||||
otherBtns.find('.btn').css('border-right-color', '#66afe9');
|
||||
})
|
||||
.blur(() => {
|
||||
$('.btn-page').css('border', '1px solid #ccc').css('box-shadow', 'none');
|
||||
$('button.open').css('border-right', 'white');
|
||||
});
|
||||
this.preCooked = await this.getPreview(this.raw);
|
||||
if (this.open) {
|
||||
this.edit();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.btn-edit,
|
||||
@import '../scss/utils';
|
||||
|
||||
.btn-edit-container {
|
||||
margin-left: -34px;
|
||||
z-index: -1;
|
||||
|
||||
&.open {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&.open .btn-page {
|
||||
border-right-color: white;
|
||||
}
|
||||
|
||||
&:not(.open) .btn-page {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
.btn-edit {
|
||||
@include towardsBottomRight(-50px, 20px);
|
||||
position: absolute;
|
||||
//margin-left: -34px;
|
||||
|
||||
&.open {
|
||||
border-right-color: white;
|
||||
}
|
||||
|
||||
&:not(.open) {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.page-rendered,
|
||||
.page-edit textarea {
|
||||
min-height: 350px;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-edit textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button.open:hover {
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
border-color: $light;
|
||||
}
|
||||
|
||||
.btn-page {
|
||||
@include transition6(border-color, ease-in-out, 0.15s, box-shadow, ease-in-out, 0.15s);
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding: 6px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.btn-page:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-edit-container {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-edit-container .btn {
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.btn-preview-container {
|
||||
margin-top: 58px;
|
||||
}
|
||||
.btn-save-container {
|
||||
margin-top: 96px;
|
||||
}
|
||||
.btn-cancel-container {
|
||||
margin-top: 135px;
|
||||
}
|
||||
.btn-delete-container {
|
||||
margin-top: 175px;
|
||||
}
|
||||
|
||||
.btn-page-delete {
|
||||
color: #c12e2a;
|
||||
}
|
||||
|
||||
.button-hide-enter-active {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.button-hide-leave-active {
|
||||
transition: all 0.3s ease-in;
|
||||
}
|
||||
|
||||
.button-hide-leave-to {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.button-hide-enter-from,
|
||||
.button-hide-leave-to {
|
||||
transform: translateX(34px);
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@import '../scss/utils';
|
||||
.page-rendered {
|
||||
@include basic-border();
|
||||
@include box-shadow4(0, 1px, 1px, rgba(0, 0, 0, 0.05));
|
||||
|
||||
padding: 10px 20px 20px 20px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
max-width: 100%;
|
||||
|
||||
table tr > th {
|
||||
padding: 5px;
|
||||
}
|
||||
table tr:nth-child(2n) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
table tr > td,
|
||||
.page-rendered table tr > th {
|
||||
padding: 10px;
|
||||
@include basic-border();
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f5f5f5;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a code {
|
||||
color: #337ab7;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
border-bottom: 1px solid $lighter;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.headeranchor {
|
||||
display: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1:hover,
|
||||
h2:hover,
|
||||
h3:hover {
|
||||
.headeranchor {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin-left: -18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
h4:hover,
|
||||
h5:hover,
|
||||
h6:hover {
|
||||
.headeranchor {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
margin-left: -12px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { createApp } from 'vue';
|
||||
import CreateVersion from '@/CreateVersion';
|
||||
import { setupI18n } from '@/plugins/i18n';
|
||||
|
||||
const i18n = setupI18n();
|
||||
createApp(CreateVersion, {
|
||||
pendingVersion: window.PENDING_VERSION,
|
||||
ownerName: window.OWNER_NAME,
|
||||
projectSlug: window.PROJECT_SLUG,
|
||||
forumSync: window.FORUM_SYNC,
|
||||
}).mount('#create-version');
|
||||
})
|
||||
.use(i18n)
|
||||
.mount('#create-version');
|
||||
|
6
src/main/frontend/src/entrypoints/project-view.js
Normal file
6
src/main/frontend/src/entrypoints/project-view.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { createApp } from 'vue';
|
||||
import ProjectView from '@/ProjectView';
|
||||
import { setupI18n } from '@/plugins/i18n';
|
||||
|
||||
const i18n = setupI18n();
|
||||
createApp(ProjectView).use(i18n).mount('#project-view');
|
6
src/main/frontend/src/entrypoints/version-view.js
Normal file
6
src/main/frontend/src/entrypoints/version-view.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { createApp } from 'vue';
|
||||
import { setupI18n } from '@/plugins/i18n';
|
||||
import VersionView from '@/VersionView';
|
||||
|
||||
const i18n = setupI18n();
|
||||
createApp(VersionView).use(i18n).mount('#version-view');
|
@ -1,175 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import 'bootstrap/js/dist/popover';
|
||||
|
||||
//=====> EXTERNAL CONSTANTS
|
||||
|
||||
var PROJECT_OWNER = window.PROJECT_OWNER;
|
||||
var PROJECT_SLUG = window.PROJECT_SLUG;
|
||||
const DEFAULT_HEX = window.DEFAULT_HEX;
|
||||
const CHANNEL_CREATE_ROUTE = window.CHANNEL_CREATE_ROUTE;
|
||||
|
||||
//=====> HELPER FUNCTIONS
|
||||
|
||||
function rgbToHex(rgb) {
|
||||
var parts = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
delete parts[0];
|
||||
for (var i = 1; i <= 3; ++i) {
|
||||
parts[i] = parseInt(parts[i]).toString(16);
|
||||
if (parts[i].length === 1) {
|
||||
parts[i] = '0' + parts[i];
|
||||
}
|
||||
}
|
||||
return '#' + parts.join('');
|
||||
}
|
||||
|
||||
function getModal() {
|
||||
return $('#channel-settings');
|
||||
}
|
||||
|
||||
export function initChannelDelete(toggle, channelName, versionCount) {
|
||||
$(toggle).off('click');
|
||||
$(toggle).click(function () {
|
||||
var url = '/' + PROJECT_OWNER + '/' + PROJECT_SLUG + '/channels/' + channelName + '/delete';
|
||||
var modal = $('#modal-delete');
|
||||
modal.find('.modal-footer').find('form').attr('action', url);
|
||||
modal.find('.version-count').text(versionCount);
|
||||
});
|
||||
$('.btn[data-channel-delete]').click(function (e) {
|
||||
e.preventDefault();
|
||||
var id = $(this).data('channel-id');
|
||||
$('#form-delete-' + id)[0].submit();
|
||||
});
|
||||
}
|
||||
window.initChannelDelete = initChannelDelete;
|
||||
|
||||
var onCustomSubmit = function (toggle, channelName, channelHex, title, submit, nonReviewed) {
|
||||
// Called when a channel is being edited before project creation
|
||||
var publishForm = $('#form-publish');
|
||||
$('#channel-name').text(channelName).css('background-color', channelHex);
|
||||
publishForm.find('.channel-input').val(channelName);
|
||||
publishForm.find('.channel-color-input').val(channelHex);
|
||||
getModal().modal('hide');
|
||||
initChannelManager(toggle, channelName, channelHex, title, null, null, submit, nonReviewed);
|
||||
};
|
||||
|
||||
export function initChannelManager(toggle, channelName, channelHex, title, call, method, submit, nonReviewed) {
|
||||
$(toggle).off('click'); // Unbind previous click handlers
|
||||
$(toggle).click(function () {
|
||||
var modal = getModal();
|
||||
var preview = modal.find('.preview');
|
||||
var submitInput = modal.find('input[type="submit"]');
|
||||
|
||||
// Update modal attributes
|
||||
modal.find('.color-picker').css('color', channelHex);
|
||||
modal.find('.modal-title').text(title);
|
||||
modal.find('.non-reviewed').prop('checked', nonReviewed);
|
||||
modal.find('input[type=submit]').attr('disabled', null);
|
||||
preview.css('background-color', channelHex).text(channelName);
|
||||
|
||||
// Set input values
|
||||
modal.find('.channel-color-input').val(channelHex);
|
||||
modal.find('.channel-input').val(channelName);
|
||||
|
||||
// Only show preview when there is input
|
||||
if (channelName.length > 0) {
|
||||
preview.show();
|
||||
} else {
|
||||
preview.hide();
|
||||
}
|
||||
|
||||
submitInput.val(submit);
|
||||
if (call === null && method === null) {
|
||||
// Redirect form submit to client
|
||||
submitInput.off('click'); // Unbind existing click handlers
|
||||
submitInput.click(function (event) {
|
||||
event.preventDefault();
|
||||
submitInput.submit();
|
||||
});
|
||||
|
||||
submitInput.off('submit'); // Unbind existing submit handlers
|
||||
submitInput.submit(function (event) {
|
||||
event.preventDefault();
|
||||
var modal = getModal();
|
||||
onCustomSubmit(toggle, modal.find('.channel-input').val(), modal.find('.channel-color-input').val(), title, submit, nonReviewed);
|
||||
});
|
||||
} else {
|
||||
// Set form action
|
||||
modal.find('form').attr('action', call).attr('method', method);
|
||||
}
|
||||
});
|
||||
}
|
||||
window.initChannelManager = initChannelManager;
|
||||
|
||||
function initModal() {
|
||||
var modal = getModal();
|
||||
// Update the preview within the popover when the name is updated
|
||||
modal.find('.channel-input').on('input', function () {
|
||||
var val = $(this).val();
|
||||
var preview = getModal().find('.preview');
|
||||
var submit = getModal().find('input[type=submit]');
|
||||
var pattern = /^[a-zA-Z0-9]+$/;
|
||||
if (val.length === 0 || !pattern.exec(val)) {
|
||||
preview.hide();
|
||||
submit.attr('disabled', true);
|
||||
} else {
|
||||
preview.show().text(val);
|
||||
submit.attr('disabled', null);
|
||||
}
|
||||
});
|
||||
initColorPicker();
|
||||
}
|
||||
|
||||
function initColorPicker() {
|
||||
var modal = getModal();
|
||||
// Initialize popover to stay opened when hovered over
|
||||
var colorPicker = modal.find('.color-picker');
|
||||
colorPicker
|
||||
.popover({
|
||||
html: true,
|
||||
trigger: 'manual',
|
||||
container: colorPicker.attr('id'),
|
||||
placement: 'right',
|
||||
sanitize: false,
|
||||
content: function () {
|
||||
return getModal().find('.popover-color-picker').html();
|
||||
},
|
||||
})
|
||||
.on('mouseenter', function () {
|
||||
var _this = this;
|
||||
$(this).popover('show');
|
||||
$(this)
|
||||
.siblings('.popover')
|
||||
.on('mouseleave', function () {
|
||||
$(_this).popover('hide');
|
||||
});
|
||||
})
|
||||
.on('mouseleave', function () {
|
||||
var _this = this;
|
||||
setTimeout(function () {
|
||||
if (!$('.popover:hover').length) {
|
||||
$(_this).popover('hide');
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Update colors when new color is selected
|
||||
$(document).on('click', '.channel-id', function () {
|
||||
var color = $(this).css('color');
|
||||
var modal = getModal();
|
||||
modal.find('.channel-color-input').val(rgbToHex(color));
|
||||
modal.find('.color-picker').css('color', color);
|
||||
modal.find('.preview').css('background-color', color);
|
||||
});
|
||||
}
|
||||
|
||||
//=====> DOCUMENT READY
|
||||
$(function () {
|
||||
initModal();
|
||||
if (DEFAULT_HEX && CHANNEL_CREATE_ROUTE) {
|
||||
initChannelManager('#channel-new', '', DEFAULT_HEX, 'New channel', CHANNEL_CREATE_ROUTE, 'post', 'Create channel', false);
|
||||
}
|
||||
|
||||
if (window.loadDeleteManager) {
|
||||
window.loadDeleteManager();
|
||||
}
|
||||
});
|
@ -1,6 +1,8 @@
|
||||
import $ from 'jquery';
|
||||
import { toggleSpinner } from '@/utils';
|
||||
|
||||
$.ajaxSetup(window.ajaxSettings);
|
||||
|
||||
//=====> DOCUMENT READY
|
||||
|
||||
$(function () {
|
||||
|
@ -1,63 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import 'bootstrap/js/dist/modal';
|
||||
import { initChannelManager } from '@/js/channelManage';
|
||||
|
||||
//=====> EXTERNAL CONSTANTS
|
||||
|
||||
var DEFAULT_COLOR = window.DEFAULT_COLOR;
|
||||
|
||||
//=====> HELPER FUNCTIONS
|
||||
|
||||
function initChannelNew(color) {
|
||||
initChannelManager('#channel-new', '', color, 'New channel', null, null, 'Create channel', false);
|
||||
}
|
||||
|
||||
function getForm() {
|
||||
return $('#form-publish');
|
||||
}
|
||||
|
||||
function getSelect() {
|
||||
return $('#select-channel');
|
||||
}
|
||||
|
||||
function setColorInput(val) {
|
||||
getForm().find('.channel-color-input').val(val);
|
||||
}
|
||||
|
||||
//=====> DOCUMENT READY
|
||||
|
||||
$(function () {
|
||||
setTimeout(function () {
|
||||
initChannelNew(DEFAULT_COLOR);
|
||||
}, 200);
|
||||
|
||||
getSelect().change(function () {
|
||||
setColorInput($(this).find(':selected').data('color'));
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var onCustomSubmit = function (
|
||||
toggle,
|
||||
channelName,
|
||||
channelHex,
|
||||
title, // eslint-disable-line no-unused-vars
|
||||
submit, // eslint-disable-line no-unused-vars
|
||||
nonReviewed // eslint-disable-line no-unused-vars
|
||||
) {
|
||||
// Add new name to select
|
||||
var select = getSelect();
|
||||
var exists =
|
||||
select.find('option').find(function () {
|
||||
return $(this).val().toLowerCase() === channelName.toLowerCase();
|
||||
}).length !== 0;
|
||||
|
||||
if (!exists) {
|
||||
setColorInput(channelHex);
|
||||
select.find(':selected').removeAttr('selected');
|
||||
select.append('<option data-color="' + channelHex + '" ' + 'value="' + channelName + '" ' + 'selected>' + channelName + '</option>');
|
||||
}
|
||||
|
||||
$('#channel-settings').modal('hide');
|
||||
initChannelNew(DEFAULT_COLOR);
|
||||
};
|
||||
});
|
21
src/main/frontend/src/plugins/i18n.js
Normal file
21
src/main/frontend/src/plugins/i18n.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import en from '@/assets/locales/en.json';
|
||||
|
||||
// TODO languages
|
||||
|
||||
export function setupI18n(locale = 'en') {
|
||||
const i18n = createI18n({
|
||||
locale,
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en,
|
||||
},
|
||||
});
|
||||
setI18nLanguage(i18n, locale);
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export function setI18nLanguage(i18n, locale) {
|
||||
i18n.global.locale.value = locale;
|
||||
document.querySelector('html').setAttribute('lang', locale);
|
||||
}
|
@ -22,9 +22,13 @@
|
||||
}
|
||||
|
||||
.channel-head {
|
||||
@include towardsBottomRight(15px, 23px);
|
||||
font-size: 20px;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
height: unset;
|
||||
padding: 4px 2px 3px;
|
||||
float: left;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
bottom: 0.9em;
|
||||
}
|
||||
|
||||
.channel-flat {
|
||||
|
@ -1,145 +1,5 @@
|
||||
@import 'utils';
|
||||
|
||||
.page-rendered, .page-edit textarea {
|
||||
min-height: 350px;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-edit textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.page-rendered {
|
||||
@include basic-border();
|
||||
@include box-shadow4(0, 1px, 1px, rgba(0, 0, 0, 0.05));
|
||||
|
||||
padding: 10px 20px 20px 20px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
max-width: 100%;
|
||||
|
||||
table tr > th { padding: 5px; }
|
||||
table tr:nth-child(2n) { background-color: #F5F5F5; }
|
||||
h1, h2, h3, h4, h5, h6 { font-weight: bold; }
|
||||
img { max-width: 100%; }
|
||||
|
||||
table tr > td, .page-rendered table tr > th {
|
||||
padding: 10px;
|
||||
@include basic-border();
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f5f5f5;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a code {
|
||||
color: #337ab7;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
border-bottom: 1px solid $lighter;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.headeranchor {
|
||||
display: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1:hover, h2:hover, h3:hover {
|
||||
.headeranchor {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin-left: -18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
h4:hover, h5:hover, h6:hover {
|
||||
.headeranchor {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
margin-left: -12px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
button.open:hover {
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
border-color: $light;
|
||||
}
|
||||
|
||||
.btn-page {
|
||||
@include transition6(border-color, ease-in-out, .15s, box-shadow, ease-in-out, .15s);
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding: 6px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.btn-page:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
@include towardsBottomRight(-49px, 20px);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.btn-edit-container {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
.btn-edit-container .btn {
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.btn-preview-container { margin-top: 58px; }
|
||||
.btn-save-container { margin-top: 96px; }
|
||||
.btn-cancel-container { margin-top: 135px; }
|
||||
.btn-delete-container { margin-top: 175px; }
|
||||
|
||||
.btn-page-delete {
|
||||
color: #c12e2a;
|
||||
}
|
||||
|
||||
.page-editor-tabs {
|
||||
margin-bottom: 15px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-editor-tabs > li > a:hover {
|
||||
border-bottom: 1px solid $lighter;
|
||||
}
|
||||
|
||||
.page-editor-tabs > li > a:hover,
|
||||
.page-editor-tabs > li.active > a,
|
||||
.page-editor-tabs > li.active > a:hover,
|
||||
.page-editor-tabs > li > a:focus,
|
||||
.page-editor-tabs > li.active > a:focus {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.page-editor-tabs > li.active > a, .page-editor-tabs > li.active > a:hover,
|
||||
.page-editor-tabs > li.active > a, .page-editor-tabs > li.active > a,
|
||||
.page-editor-tabs > li.active > a, .page-editor-tabs > li.active > a:focus {
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.btn-card {
|
||||
background-color: #337ab7;
|
||||
border: 1px solid #1A619E;
|
||||
|
@ -1,3 +1,5 @@
|
||||
@import 'variables';
|
||||
|
||||
.push-down {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
@ -9672,6 +9672,11 @@ vue-hot-reload-api@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
|
||||
|
||||
vue-i18n@^9.0.0-beta.4:
|
||||
version "9.0.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.0.0-beta.4.tgz#a6550f6be56fd617a8ab96e26aa305329f06da02"
|
||||
integrity sha512-Ko1pNflNRwmwjVn2S4Oo9BGHzBRVBvBGdBzAKle713DmmFRnB+NnsJisSmwTqB6BtACCvvJdY+xRTjZaobHtew==
|
||||
|
||||
"vue-loader-v16@npm:vue-loader@^16.0.0-beta.7":
|
||||
version "16.0.0-beta.8"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.0.0-beta.8.tgz#1f523d9fea8e8c6e4f5bb99fd768165af5845879"
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.papermc.hangar.controller;
|
||||
|
||||
import io.papermc.hangar.controller.forms.PagePreview;
|
||||
import io.papermc.hangar.db.customtypes.LoggedActionType;
|
||||
import io.papermc.hangar.db.customtypes.LoggedActionType.ProjectPageContext;
|
||||
import io.papermc.hangar.db.dao.HangarDao;
|
||||
@ -20,8 +21,6 @@ import io.papermc.hangar.service.project.PagesSerivce;
|
||||
import io.papermc.hangar.service.project.ProjectService;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import io.papermc.hangar.util.StringUtils;
|
||||
import org.springframework.boot.configurationprocessor.json.JSONException;
|
||||
import org.springframework.boot.configurationprocessor.json.JSONObject;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -68,15 +67,8 @@ public class PagesController extends HangarController {
|
||||
}
|
||||
|
||||
@PostMapping(value = "/pages/preview", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<String> showPreview(@RequestBody String raw) throws JSONException {
|
||||
JSONObject rawJson;
|
||||
try {
|
||||
rawJson = new JSONObject(raw);
|
||||
rawJson.getString("raw");
|
||||
} catch (JSONException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return new ResponseEntity<>(markdownService.render(rawJson.getString("raw")), HttpStatus.OK);
|
||||
public ResponseEntity<String> showPreview(@RequestBody PagePreview rawObj) {
|
||||
return ResponseEntity.ok(markdownService.render(rawObj.getRaw()));
|
||||
}
|
||||
|
||||
@GetMapping({"/{author}/{slug}/pages/{page}", "/{author}/{slug}/pages/{page}/{subPage}"})
|
||||
|
@ -0,0 +1,28 @@
|
||||
package io.papermc.hangar.controller.forms;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PagePreview {
|
||||
|
||||
@NotNull
|
||||
private final String raw;
|
||||
|
||||
@JsonCreator
|
||||
public PagePreview(@JsonProperty(value = "raw", required = true) @NotNull String raw) {
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRaw() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PagePreview{" +
|
||||
"raw='" + raw + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
package io.papermc.hangar.db.mappers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.model.TagColor;
|
||||
import io.papermc.hangar.model.generated.PromotedVersion;
|
||||
import io.papermc.hangar.model.generated.PromotedVersionTag;
|
||||
import io.papermc.hangar.util.StringUtils;
|
||||
import org.jdbi.v3.core.mapper.ColumnMapper;
|
||||
import org.jdbi.v3.core.statement.StatementContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -20,6 +23,7 @@ public class PromotedVersionMapper implements ColumnMapper<List<PromotedVersion>
|
||||
|
||||
@Override
|
||||
public List<PromotedVersion> map(ResultSet rs, int column, StatementContext ctx) throws SQLException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<PromotedVersion> promotedVersions = new ArrayList<>();
|
||||
JsonNode jsonNode = ((JSONB) rs.getObject(column)).getJson();
|
||||
if (!jsonNode.isArray()) {
|
||||
@ -29,7 +33,14 @@ public class PromotedVersionMapper implements ColumnMapper<List<PromotedVersion>
|
||||
jsons.forEach(json -> {
|
||||
String version = json.get("version_string").asText();
|
||||
String tagName = json.get("tag_name").asText();
|
||||
String data = stringOrNull(json.get("tag_version"));
|
||||
String data = null;
|
||||
if (json.has("tag_version") && json.get("tag_version").isArray()) {
|
||||
try {
|
||||
data = StringUtils.formatVersionNumbers((List<String>) mapper.treeToValue(json.get("tag_version"), List.class));
|
||||
} catch (JsonProcessingException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
TagColor color = TagColor.getValues()[json.get("tag_color").asInt()];
|
||||
// TODO a whole bunch
|
||||
// val displayAndMc = data.map { rawData =>
|
||||
@ -88,6 +99,7 @@ public class PromotedVersionMapper implements ColumnMapper<List<PromotedVersion>
|
||||
new PromotedVersionTag()
|
||||
.name(tagName)
|
||||
.data(data)
|
||||
.displayData(data) // TODO
|
||||
.color(
|
||||
new io.papermc.hangar.model.generated.TagColor()
|
||||
.background(color.getBackground())
|
||||
|
@ -1,5 +1,8 @@
|
||||
package io.papermc.hangar.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.db.model.ProjectsTable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -10,14 +13,17 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class TemplateHelper {
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
private final HangarConfig hangarConfig;
|
||||
|
||||
@Autowired
|
||||
public TemplateHelper(HangarConfig hangarConfig) {
|
||||
public TemplateHelper(ObjectMapper mapper, HangarConfig hangarConfig) {
|
||||
this.mapper = mapper;
|
||||
this.hangarConfig = hangarConfig;
|
||||
}
|
||||
|
||||
@ -55,4 +61,15 @@ public class TemplateHelper {
|
||||
public String prettifyDate(OffsetDateTime dateTime) {
|
||||
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(dateTime.toLocalDate());
|
||||
}
|
||||
|
||||
public ArrayNode serializeMap(Map<?, ?> map) {
|
||||
ArrayNode arrayNode = mapper.createArrayNode();
|
||||
map.forEach((o, o2) -> {
|
||||
ObjectNode objectNode = mapper.createObjectNode();
|
||||
objectNode.set("key", mapper.valueToTree(o));
|
||||
objectNode.set("value", mapper.valueToTree(o2));
|
||||
arrayNode.add(objectNode);
|
||||
});
|
||||
return arrayNode;
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,6 @@
|
||||
<#import "*/utils/hangar.ftlh" as hangar />
|
||||
<#import "*/utils/csrf.ftlh" as csrf />
|
||||
|
||||
<#--@import ore.OreConfig-->
|
||||
<#--@import ore.db.Model-->
|
||||
<#--@import ore.models.project.{Page, Project}-->
|
||||
<#--@import util.syntax._-->
|
||||
<#--@import views.html.helper.CSPNonce-->
|
||||
|
||||
<#--@(model: Project, rootPages: Seq[Model[Page]])(implicit messages: Messages, request: RequestHeader, config: OreConfig)-->
|
||||
|
||||
<#macro pageCreate project, rootPages>
|
||||
<script <#--@CSPNonce.attr-->>
|
||||
PROJECT_OWNER = '${project.ownerName}';
|
||||
|
@ -13,16 +13,21 @@ Documentation page within Project overview.
|
||||
-->
|
||||
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"]>
|
||||
<#function canEditPages>
|
||||
<#return sp.perms(Permission.EditPage)>
|
||||
</#function>
|
||||
|
||||
<#assign scriptsVar>
|
||||
<script> <#--@CSPNonce.attr-->
|
||||
<script>
|
||||
window.PAGE = ${mapper.valueToTree(projectPage)};
|
||||
window.ROOT_PAGES = ${utils.serializeMap(rootPages)};
|
||||
window.PAGE_COUNT = ${pageCount};
|
||||
<#--window.OWNER_NAME = '${p.project.ownerName}';-->
|
||||
<#--window.PROJECT_SLUG = '${p.project.slug}';-->
|
||||
<#--window.PROJECT_PAGE = '${projectPage.slug}';-->
|
||||
</script>
|
||||
<#-- <script> <#–@CSPNonce.attr–>
|
||||
window.NAMESPACE = '${p.getFullSlug()}';
|
||||
</script>
|
||||
<#if editorOpen>
|
||||
<script <#--@CSPNonce.attr-->>
|
||||
<script <#–@CSPNonce.attr–>>
|
||||
window.buttonClick = function() {
|
||||
document.getElementsByClassName('btn-edit')[0].click();
|
||||
}
|
||||
@ -30,44 +35,41 @@ Documentation page within Project overview.
|
||||
</#if>
|
||||
<script type="text/javascript" src="<@hangar.url "js/pageCollapse.js" />"></script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/userSearch.js" />"></script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/memberList.js" />"></script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/memberList.js" />"></script>-->
|
||||
<script type="text/javascript" src="<@hangar.url "js/project-view.js" />"></script>
|
||||
</#assign>
|
||||
|
||||
<@projectView.view p=p sp=sp active="#docs" additionalScripts=scriptsVar>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<@editor.editor
|
||||
saveCall=Routes.PAGES_SAVE.getRouteUrl(p.project.ownerName, p.project.slug, projectPage.slug)
|
||||
deleteCall=Routes.PAGES_DELETE.getRouteUrl(p.project.ownerName, p.project.slug, projectPage.slug)
|
||||
deletable=projectPage.isDeletable
|
||||
enabled=canEditPages()
|
||||
raw=projectPage.contents
|
||||
cooked=markdownService.render(projectPage.contents)
|
||||
subject="Page"
|
||||
extraFormValue=projectPage.name />
|
||||
</div>
|
||||
</div>
|
||||
<div id="project-view"></div>
|
||||
<#--<div class="row">
|
||||
<div class="col-md-12">
|
||||
<@editor.editor
|
||||
saveCall=Routes.PAGES_SAVE.getRouteUrl(p.project.ownerName, p.project.slug, projectPage.slug)
|
||||
deleteCall=Routes.PAGES_DELETE.getRouteUrl(p.project.ownerName, p.project.slug, projectPage.slug)
|
||||
deletable=projectPage.isDeletable
|
||||
enabled=canEditPages()
|
||||
raw=projectPage.contents
|
||||
cooked=markdownService.render(projectPage.contents)
|
||||
subject="Page"
|
||||
extraFormValue=projectPage.name />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-6">
|
||||
|
||||
<#if p.project.recommendedVersionId??>
|
||||
<div class="btn-group btn-download">
|
||||
<a href="${Routes.VERSIONS_DOWNLOAD_RECOMMENDED.getRouteUrl(p.project.ownerName, p.project.slug, "")}"
|
||||
title="<@spring.message "project.download.recommend" />" data-toggle="tooltip"
|
||||
data-placement="bottom" class="btn btn-primary">
|
||||
<i class="fas fa-download"></i> <@spring.message "general.download" />
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="${Routes.VERSIONS_DOWNLOAD_RECOMMENDED.getRouteUrl(p.project.ownerName, p.project.slug, "")}" class="dropdown-item"><@spring.message "general.download" /></a>
|
||||
<a href="#" class="copy-url dropdown-item" data-clipboard-text="${config.baseUrl}${Routes.VERSIONS_DOWNLOAD_RECOMMENDED.getRouteUrl(p.project.ownerName, p.project.slug, "")}">Copy URL</a>
|
||||
<div class="btn-group btn-download">
|
||||
<a href="${Routes.VERSIONS_DOWNLOAD_RECOMMENDED.getRouteUrl(p.project.ownerName, p.project.slug, "")}"
|
||||
title="<@spring.message "project.download.recommend" />" data-toggle="tooltip"
|
||||
data-placement="bottom" class="btn btn-primary">
|
||||
<i class="fas fa-download"></i> <@spring.message "general.download" />
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="${Routes.VERSIONS_DOWNLOAD_RECOMMENDED.getRouteUrl(p.project.ownerName, p.project.slug, "")}" class="dropdown-item"><@spring.message "general.download" /></a>
|
||||
<a href="#" class="copy-url dropdown-item" data-clipboard-text="${config.baseUrl}${Routes.VERSIONS_DOWNLOAD_RECOMMENDED.getRouteUrl(p.project.ownerName, p.project.slug, "")}">Copy URL</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="stats minor">
|
||||
@ -106,13 +108,6 @@ Documentation page within Project overview.
|
||||
</#if>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<#-- <li class="list-group-item">-->
|
||||
<#-- <a href="${Routes.PAGES_SHOW.getRouteUrl(p.project.ownerName, p.project.slug, config.pages.home.name)}">-->
|
||||
<#-- ${"Home"}-->
|
||||
<#-- </a>-->
|
||||
<#-- </li>-->
|
||||
<#-- @ftlvariable name="pg" type="io.papermc.hangar.model.viewhelpers.ProjectPage" -->
|
||||
<#-- @ftlvariable name="children" type="java.util.List<ProjectPage>" -->
|
||||
<#list rootPages as pg, children>
|
||||
<li class="list-group-item">
|
||||
<#if children?size gt 0>
|
||||
@ -132,7 +127,7 @@ Documentation page within Project overview.
|
||||
</li>
|
||||
<#if projectPage.parentId?? && projectPage.parentId = pg.id>
|
||||
<div class="page-children" data-page-id="${pg.id}">
|
||||
<#-- @ftlvariable name="childPage" type="io.papermc.hangar.model.viewhelpers.ProjectPage" -->
|
||||
<#– @ftlvariable name="childPage" type="io.papermc.hangar.model.viewhelpers.ProjectPage" –>
|
||||
<#list children as childPage>
|
||||
<li class="list-group-item page-item-child">
|
||||
<a href="${Routes.PAGES_SHOW.getRouteUrl(p.project.ownerName, p.project.slug, childPage.slug)}">
|
||||
@ -146,8 +141,8 @@ Documentation page within Project overview.
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Member list -->
|
||||
<!-- Member list –>
|
||||
<@memberList.memberList project=p perms=sp.permissions settingsCall=Routes.PROJECTS_SHOW_SETTINGS.getRouteUrl(p.project.ownerName, p.project.slug) />
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
</@projectView.view>
|
||||
|
@ -15,7 +15,7 @@
|
||||
window.OWNER_NAME = '${ownerName}';
|
||||
window.PROJECT_SLUG = '${projectSlug}';
|
||||
window.PENDING_VERSION = ${mapper.valueToTree(pending)};
|
||||
window.CHANNELS = ${channels};
|
||||
window.CHANNELS = ${mapper.valueToTree(channels)};
|
||||
window.FORUM_SYNC = ${forumSync?c};
|
||||
window.MAX_CHANNEL_NAME_LEN = ${config.channels.maxNameLen};
|
||||
window.COLORS = ${mapper.valueToTree(@helper["io.papermc.hangar.model.Color"].getNonTransparentValues())};
|
||||
|
@ -8,14 +8,22 @@
|
||||
<#assign ReviewState=@helper["io.papermc.hangar.model.generated.ReviewState"] />
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"] />
|
||||
<#assign Visibility=@helper["io.papermc.hangar.model.Visibility"] />
|
||||
<@projects.view p=v.p sp=sp active="#versions" noButtons=true>
|
||||
<#assign scriptsVar>
|
||||
<script>
|
||||
window.VERSION = ${mapper.valueToTree(v.v)};
|
||||
</script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/version-view.js" />"></script>
|
||||
</#assign>
|
||||
<@projects.view p=v.p sp=sp active="#versions" noButtons=true additionalScripts=scriptsVar>
|
||||
<!-- Version header -->
|
||||
<div class="row">
|
||||
<div class="col-md-12 version-header">
|
||||
<!-- Title -->
|
||||
<div class="clearfix">
|
||||
<h1 class="float-left">${v.v.versionString}</h1>
|
||||
<span class="channel channel-head" style="background-color: ${v.c.color.hex};">${v.c.name}</span>
|
||||
<h1 class="float-left position-relative">
|
||||
${v.v.versionString}
|
||||
<div class="channel channel-head" style="background-color: ${v.c.color.hex};">${v.c.name}</div>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- User info -->
|
||||
@ -159,14 +167,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="col-md-12">
|
||||
<@editor.editor
|
||||
saveCall=Routes.VERSIONS_SAVE_DESCRIPTION.getRouteUrl(v.p.project.ownerName, v.p.project.slug, v.v.versionString)
|
||||
enabled=sp.perms(Permission.EditPage)
|
||||
raw=v.v.description!""
|
||||
cooked=markdownService.render(v.v.description)
|
||||
subject="Version"
|
||||
/>
|
||||
<div class="col-md-12" style="z-index: 5">
|
||||
<div id="version-view"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,11 +9,15 @@ Base template for Project overview.
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"]>
|
||||
<#assign FlagReason=@helper["io.papermc.hangar.model.FlagReason"]>
|
||||
<#-- @ftlvariable name="FlagReason" type="io.papermc.hangar.model.FlagReason" -->
|
||||
<#macro view p sp active noButtons=false additionalScripts="" additionalStyling="">
|
||||
<#macro view p sp active noButtons=false additionalScripts="">
|
||||
<#-- @ftlvariable name="p" type="io.papermc.hangar.model.viewhelpers.ProjectData" -->
|
||||
<#-- @ftlvariable name="sp" type="io.papermc.hangar.model.viewhelpers.ScopedProjectData" -->
|
||||
<#assign scriptsVar>
|
||||
<script <#-- @CSPNonce.attr -->>
|
||||
window.PROJECT = ${mapper.valueToTree(p.project)};
|
||||
window.CAN_EDIT_PAGES = ${sp.perms(Permission.EditPage)?c};
|
||||
|
||||
|
||||
window.PROJECT_OWNER = "${p.project.ownerName}";
|
||||
window.PROJECT_SLUG = "${p.project.slug}";
|
||||
window.NAMESPACE = "${p.project.ownerName}/${p.project.slug}";
|
||||
@ -22,8 +26,12 @@ Base template for Project overview.
|
||||
window.ACTIVE_NAV = "${active}";
|
||||
</script>
|
||||
${additionalScripts}
|
||||
<script type="text/javascript" src="<@hangar.url "js/projectDetail.js" />"></script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/pageEdit.js" />"></script>
|
||||
<#--<script type="text/javascript" src="<@hangar.url "js/projectDetail.js" />"></script>-->
|
||||
<#--<script type="text/javascript" src="<@hangar.url "js/pageEdit.js" />"></script>-->
|
||||
</#assign>
|
||||
|
||||
<#assign styleVar>
|
||||
<link rel="stylesheet" href="<@hangar.url "css/project-view.css" />">
|
||||
</#assign>
|
||||
|
||||
<#assign metaVar>
|
||||
@ -35,7 +43,7 @@ Base template for Project overview.
|
||||
<meta property="og:description" content="${p.project.description!}"/>
|
||||
</#assign>
|
||||
|
||||
<@base.base title=(p.project.ownerName + " / " + p.project.name) additionalScripts=scriptsVar additionalStyling=additionalStyling additionalMeta=metaVar>
|
||||
<@base.base title=(p.project.ownerName + " / " + p.project.name) additionalScripts=scriptsVar additionalStyling=styleVar additionalMeta=metaVar>
|
||||
<div class="project-header-container">
|
||||
<#if p.visibility != Visibility.PUBLIC>
|
||||
<div class="row">
|
||||
@ -192,24 +200,6 @@ Base template for Project overview.
|
||||
Owner on forum <i class="fas fa-external-link-alt" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<#--<ul class="dropdown-menu" aria-labelledby="admin-actions">
|
||||
<#if headerData.globalPerm(Permission.ModNotesAndFlags)>
|
||||
<li class="dropdown-item">
|
||||
<a href="${Routes.PROJECTS_SHOW_FLAGS.getRouteUrl(p.project.ownerName, p.project.slug)}">
|
||||
Flag history (${p.flagCount})
|
||||
</a>
|
||||
</li>
|
||||
</#if>
|
||||
<#if headerData.globalPerm(Permission.ModNotesAndFlags)>
|
||||
<li><a href="${Routes.PROJECTS_SHOW_NOTES.getRouteUrl(p.project.ownerName, p.project.slug)}">
|
||||
Staff notes (${p.noteCount}) </a></li>
|
||||
</#if>
|
||||
<#if headerData.globalPerm(Permission.ViewLogs)>
|
||||
<li><a href="${Routes.SHOW_LOG.getRouteUrl("", "", p.project.pluginId, "", "", "", "")}">
|
||||
User Action Logs</a></li>
|
||||
</#if>
|
||||
<li><a href="https://papermc.io/forums/${p.project.ownerName}">Owner on forum <i class="fas fa-external-link-alt" aria-hidden="true"></i></a></li>
|
||||
</ul>-->
|
||||
</#if>
|
||||
</div>
|
||||
@ -224,13 +214,13 @@ Base template for Project overview.
|
||||
<div class="navbar-inner">
|
||||
<ul class="nav navbar-nav">
|
||||
<!-- Tabs -->
|
||||
<li id="docs" class="nav-item">
|
||||
<li id="docs" class="nav-item <#if active='#docs'>active</#if>">
|
||||
<a href="${Routes.PROJECTS_SHOW.getRouteUrl(p.project.ownerName, p.project.slug)}"
|
||||
class="nav-link">
|
||||
<i class="fas fa-book"></i> <@spring.message "project.docs" /></a>
|
||||
</li>
|
||||
|
||||
<li id="versions" class="nav-item">
|
||||
<li id="versions" class="nav-item <#if active='#versions'>active</#if>">
|
||||
<a href="${Routes.VERSIONS_SHOW_LIST.getRouteUrl(p.project.ownerName, p.project.slug)}"
|
||||
class="nav-link">
|
||||
<i class="fas fa-download"></i> <@spring.message "project.versions" />
|
||||
@ -238,7 +228,7 @@ Base template for Project overview.
|
||||
</li>
|
||||
|
||||
<#if p.project.topicId??>
|
||||
<li id="discussion" class="nav-item">
|
||||
<li id="discussion" class="nav-item <#if active='#discussion'>active</#if>">
|
||||
<a href="${Routes.PROJECTS_SHOW_DISCUSSION.getRouteUrl(p.project.ownerName, p.project.slug)}"
|
||||
class="nav-link">
|
||||
<i class="fas fa-users"></i> <@spring.message "project.discuss" />
|
||||
@ -248,7 +238,7 @@ Base template for Project overview.
|
||||
|
||||
<#if sp.perms(Permission.EditProjectSettings)>
|
||||
<#-- Show manager if permitted -->
|
||||
<li id="settings" class="nav-item">
|
||||
<li id="settings" class="nav-item <#if active='#settings'>active</#if>">
|
||||
<a href="${Routes.PROJECTS_SHOW_SETTINGS.getRouteUrl(p.project.ownerName, p.project.slug)}"
|
||||
class="nav-link">
|
||||
<i class="fas fa-cog"></i> <@spring.message "project.settings" />
|
||||
|
@ -14,7 +14,13 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12 header-flags">
|
||||
<div class="clearfix">
|
||||
<h1 class="float-left">${project.project.name} <i>${version.v.versionString}</i></h1>
|
||||
<h1 class="float-left">
|
||||
<a class="btn btn-primary" href="/${utils.urlEncode(project.project.ownerName)}/${utils.urlEncode(project.project.slug)}/versions/${utils.urlEncode(version.v.versionString)}">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
${project.project.name}
|
||||
<i>${version.v.versionString}</i>
|
||||
</h1>
|
||||
</div>
|
||||
<p class="user date float-left">
|
||||
<a href="${Routes.USERS_SHOW_PROJECTS.getRouteUrl(project.project.ownerName)}">
|
||||
|
Loading…
Reference in New Issue
Block a user