diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index 148fa76a4..9e6d96340 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -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": { diff --git a/src/main/frontend/src/CreateVersion.vue b/src/main/frontend/src/CreateVersion.vue index d55efa5af..059071888 100644 --- a/src/main/frontend/src/CreateVersion.vue +++ b/src/main/frontend/src/CreateVersion.vue @@ -120,11 +120,19 @@
-
+

Release Bulletin

What's new in this release?

- +
@@ -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(); - }, }; diff --git a/src/main/frontend/src/VersionView.vue b/src/main/frontend/src/VersionView.vue new file mode 100644 index 000000000..9033f8b0c --- /dev/null +++ b/src/main/frontend/src/VersionView.vue @@ -0,0 +1,26 @@ + + diff --git a/src/main/frontend/src/assets/locales/en.json b/src/main/frontend/src/assets/locales/en.json new file mode 100644 index 000000000..f0349077f --- /dev/null +++ b/src/main/frontend/src/assets/locales/en.json @@ -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,
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 {0}.", + "joined": "You have joined {0}!", + "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 {0} 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": "{0} approved this version on {1}" + }, + "approvedPartial": "Partially Approved", + "create": { + "externalUrl": "External download URL", + "info": "Release a new version for {0}.", + "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 \"" + }, + "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}" + } + } +} \ No newline at end of file diff --git a/src/main/frontend/src/components/Editor.vue b/src/main/frontend/src/components/Editor.vue index e6c0ffa3e..452659396 100644 --- a/src/main/frontend/src/components/Editor.vue +++ b/src/main/frontend/src/components/Editor.vue @@ -1,50 +1,73 @@ + diff --git a/src/main/frontend/src/entrypoints/create-version.js b/src/main/frontend/src/entrypoints/create-version.js index 9d66abd73..734b9170a 100644 --- a/src/main/frontend/src/entrypoints/create-version.js +++ b/src/main/frontend/src/entrypoints/create-version.js @@ -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'); diff --git a/src/main/frontend/src/entrypoints/project-view.js b/src/main/frontend/src/entrypoints/project-view.js new file mode 100644 index 000000000..08e579e9e --- /dev/null +++ b/src/main/frontend/src/entrypoints/project-view.js @@ -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'); diff --git a/src/main/frontend/src/entrypoints/version-view.js b/src/main/frontend/src/entrypoints/version-view.js new file mode 100644 index 000000000..903407837 --- /dev/null +++ b/src/main/frontend/src/entrypoints/version-view.js @@ -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'); diff --git a/src/main/frontend/src/js/channelManage.js b/src/main/frontend/src/js/channelManage.js deleted file mode 100644 index cbe3f34aa..000000000 --- a/src/main/frontend/src/js/channelManage.js +++ /dev/null @@ -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(); - } -}); diff --git a/src/main/frontend/src/js/review.js b/src/main/frontend/src/js/review.js index 916044f0c..fb401b5ce 100644 --- a/src/main/frontend/src/js/review.js +++ b/src/main/frontend/src/js/review.js @@ -1,6 +1,8 @@ import $ from 'jquery'; import { toggleSpinner } from '@/utils'; +$.ajaxSetup(window.ajaxSettings); + //=====> DOCUMENT READY $(function () { diff --git a/src/main/frontend/src/js/versionCreateChannelNew.js b/src/main/frontend/src/js/versionCreateChannelNew.js deleted file mode 100644 index 7a18a658e..000000000 --- a/src/main/frontend/src/js/versionCreateChannelNew.js +++ /dev/null @@ -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(''); - } - - $('#channel-settings').modal('hide'); - initChannelNew(DEFAULT_COLOR); - }; -}); diff --git a/src/main/frontend/src/plugins/i18n.js b/src/main/frontend/src/plugins/i18n.js new file mode 100644 index 000000000..9120578ca --- /dev/null +++ b/src/main/frontend/src/plugins/i18n.js @@ -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); +} diff --git a/src/main/frontend/src/scss/_channel.scss b/src/main/frontend/src/scss/_channel.scss index 337a03737..80c02c968 100644 --- a/src/main/frontend/src/scss/_channel.scss +++ b/src/main/frontend/src/scss/_channel.scss @@ -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 { diff --git a/src/main/frontend/src/scss/_editor.scss b/src/main/frontend/src/scss/_editor.scss index d5a33f70b..f3461aa68 100644 --- a/src/main/frontend/src/scss/_editor.scss +++ b/src/main/frontend/src/scss/_editor.scss @@ -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; diff --git a/src/main/frontend/src/scss/_utils.scss b/src/main/frontend/src/scss/_utils.scss index ba799c41f..4d7804a85 100644 --- a/src/main/frontend/src/scss/_utils.scss +++ b/src/main/frontend/src/scss/_utils.scss @@ -1,3 +1,5 @@ +@import 'variables'; + .push-down { margin-top: 10px; } diff --git a/src/main/frontend/yarn.lock b/src/main/frontend/yarn.lock index a5ba2cc20..c1b7ef1c7 100644 --- a/src/main/frontend/yarn.lock +++ b/src/main/frontend/yarn.lock @@ -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" diff --git a/src/main/java/io/papermc/hangar/controller/PagesController.java b/src/main/java/io/papermc/hangar/controller/PagesController.java index cd8e5262c..a0db54bef 100644 --- a/src/main/java/io/papermc/hangar/controller/PagesController.java +++ b/src/main/java/io/papermc/hangar/controller/PagesController.java @@ -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 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 showPreview(@RequestBody PagePreview rawObj) { + return ResponseEntity.ok(markdownService.render(rawObj.getRaw())); } @GetMapping({"/{author}/{slug}/pages/{page}", "/{author}/{slug}/pages/{page}/{subPage}"}) diff --git a/src/main/java/io/papermc/hangar/controller/forms/PagePreview.java b/src/main/java/io/papermc/hangar/controller/forms/PagePreview.java new file mode 100644 index 000000000..f1fbeff91 --- /dev/null +++ b/src/main/java/io/papermc/hangar/controller/forms/PagePreview.java @@ -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 + '\'' + + '}'; + } +} diff --git a/src/main/java/io/papermc/hangar/db/mappers/PromotedVersionMapper.java b/src/main/java/io/papermc/hangar/db/mappers/PromotedVersionMapper.java index 465dd06be..43d4cd21f 100644 --- a/src/main/java/io/papermc/hangar/db/mappers/PromotedVersionMapper.java +++ b/src/main/java/io/papermc/hangar/db/mappers/PromotedVersionMapper.java @@ -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 @Override public List map(ResultSet rs, int column, StatementContext ctx) throws SQLException { + ObjectMapper mapper = new ObjectMapper(); List promotedVersions = new ArrayList<>(); JsonNode jsonNode = ((JSONB) rs.getObject(column)).getJson(); if (!jsonNode.isArray()) { @@ -29,7 +33,14 @@ public class PromotedVersionMapper implements ColumnMapper 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) 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 new PromotedVersionTag() .name(tagName) .data(data) + .displayData(data) // TODO .color( new io.papermc.hangar.model.generated.TagColor() .background(color.getBackground()) diff --git a/src/main/java/io/papermc/hangar/util/TemplateHelper.java b/src/main/java/io/papermc/hangar/util/TemplateHelper.java index 56fa54647..c7f9c04aa 100644 --- a/src/main/java/io/papermc/hangar/util/TemplateHelper.java +++ b/src/main/java/io/papermc/hangar/util/TemplateHelper.java @@ -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; + } } diff --git a/src/main/resources/templates/projects/pages/modalPageCreate.ftlh b/src/main/resources/templates/projects/pages/modalPageCreate.ftlh index c0e4dbc85..192951309 100644 --- a/src/main/resources/templates/projects/pages/modalPageCreate.ftlh +++ b/src/main/resources/templates/projects/pages/modalPageCreate.ftlh @@ -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> +<#-- <#if editorOpen> - - + --> + <@projectView.view p=p sp=sp active="#docs" additionalScripts=scriptsVar> -
-
-
-
- <@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 /> -
-
+
+<#--
+
+ <@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 />
-
+
<#if p.project.recommendedVersionId??> -
- " data-toggle="tooltip" - data-placement="bottom" class="btn btn-primary"> - <@spring.message "general.download" /> - - -
@@ -106,13 +108,6 @@ Documentation page within Project overview.
    -<#--
  • --> -<#-- --> -<#-- ${"Home"}--> -<#-- --> -<#--
  • --> - <#-- @ftlvariable name="pg" type="io.papermc.hangar.model.viewhelpers.ProjectPage" --> - <#-- @ftlvariable name="children" type="java.util.List" --> <#list rootPages as pg, children>
  • <#if children?size gt 0> @@ -132,7 +127,7 @@ Documentation page within Project overview.
  • <#if projectPage.parentId?? && projectPage.parentId = 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>
  • @@ -146,8 +141,8 @@ Documentation page within Project overview.
- + diff --git a/src/main/resources/templates/projects/versions/create.ftlh b/src/main/resources/templates/projects/versions/create.ftlh index 8ce9278da..98392341c 100644 --- a/src/main/resources/templates/projects/versions/create.ftlh +++ b/src/main/resources/templates/projects/versions/create.ftlh @@ -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())}; diff --git a/src/main/resources/templates/projects/versions/view.ftlh b/src/main/resources/templates/projects/versions/view.ftlh index a63dc58e7..f363bae33 100644 --- a/src/main/resources/templates/projects/versions/view.ftlh +++ b/src/main/resources/templates/projects/versions/view.ftlh @@ -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> + + + +<@projects.view p=v.p sp=sp active="#versions" noButtons=true additionalScripts=scriptsVar>
-

${v.v.versionString}

- ${v.c.name} +

+ ${v.v.versionString} +
${v.c.name}
+

@@ -159,14 +167,8 @@
-
- <@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" - /> +
+
diff --git a/src/main/resources/templates/projects/view.ftlh b/src/main/resources/templates/projects/view.ftlh index 480edd0db..7ef5f243e 100644 --- a/src/main/resources/templates/projects/view.ftlh +++ b/src/main/resources/templates/projects/view.ftlh @@ -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> ${additionalScripts} - - + <#----> + <#----> + + + <#assign styleVar> + "> <#assign metaVar> @@ -35,7 +43,7 @@ Base template for Project overview. - <@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>
<#if p.visibility != Visibility.PUBLIC> - - <#---->
@@ -224,13 +214,13 @@ Base template for Project overview.