V10: Umbraco Marketplace (#13511)

* v11: Umbraco Marketplace replaces packages repo (#13371)

* add lang keys for marketplace

* remove old 'repo' page and deprecate related services

* add new view for Umbraco Marketplace

* optimise margin/padding for other tabs

* mark Our Repository constants as obsolete

* improve css path to iframe slightly with more aliases and classnames

* remove style qs

* update URL of Marketplace

* add ng-controller with utitlities for future PostMessage API

* rename marketplace loaded function

* remove iframe postmessage logic for time being

* add handling of dynamic querystring params

* assume url does not change

* Added support for additional parameters for marketplace

* Update src/JsonSchema/AppSettings.cs

Fix styling issue

Co-authored-by: Ronald Barendse <ronald@barend.se>

* Update src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs

Fix styling issue

Co-authored-by: Ronald Barendse <ronald@barend.se>

* Update src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs

Make comment more descriptive

Co-authored-by: Ronald Barendse <ronald@barend.se>

* Update src/Umbraco.Core/Constants-Marketplace.cs

Fix styling issue

Co-authored-by: Ronald Barendse <ronald@barend.se>

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Ronald Barendse <ronald@barend.se>

* Info panes not loading prerequisites (#13486)

* load relations immediately on init and remove call to non-existing function `appTabChange` on destroy

* load relations immediately on init and remove now not-used eventsService

* update marketplace url to its final, live url

* re-add files to prevent breaking change

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Ronald Barendse <ronald@barend.se>
This commit is contained in:
Jacob Overgaard
2022-12-07 10:44:34 +01:00
committed by GitHub
parent f28b4c1279
commit 793272bda1
22 changed files with 834 additions and 687 deletions

View File

@@ -91,6 +91,8 @@ namespace JsonSchema
public InstallDefaultDataSettings? DefaultDataCreation { get; set; }
public DataTypesSettings? DataTypes { get; set; }
public MarketplaceSettings? Marketplace { get; set; }
}
/// <summary>

View File

@@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Configuration.Models;
/// Typed configuration options for active directory settings.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigActiveDirectory)]
[Obsolete("This is not used anymore. Will be removed in Umbraco 12")]public class ActiveDirectorySettings
[Obsolete("This is not used anymore. Will be removed in Umbraco 12")]
public class ActiveDirectorySettings
{
/// <summary>
/// Gets or sets a value for the Active Directory domain.

View File

@@ -0,0 +1,16 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
namespace Umbraco.Cms.Core.Configuration.Models;
/// <summary>
/// Configuration options for the Marketplace.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigMarketplace)]
public class MarketplaceSettings
{
/// <summary>
/// Gets or sets the additional parameters that are sent to the Marketplace.
/// </summary>
public Dictionary<string, string> AdditionalParameters { get; set; } = new ();
}

View File

@@ -27,6 +27,7 @@ public static partial class Constants
public const string ConfigHostingDebug = ConfigHostingPrefix + "Debug";
public const string ConfigCustomErrorsMode = ConfigCustomErrorsPrefix + "Mode";
public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory";
public const string ConfigMarketplace = ConfigPrefix + "Marketplace";
public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration";
public const string ConfigContent = ConfigPrefix + "Content";
public const string ConfigCoreDebug = ConfigCorePrefix + "Debug";

View File

@@ -0,0 +1,12 @@
namespace Umbraco.Cms.Core;
public static partial class Constants
{
/// <summary>
/// Defines the constants used for the Umbraco Marketplace.
/// </summary>
public static class Marketplace
{
public const string Url = "https://marketplace.umbraco.com";
}
}

View File

@@ -5,6 +5,7 @@ public static partial class Constants
/// <summary>
/// Defines the constants used for the Umbraco package repository
/// </summary>
[Obsolete("This is no longer used and will be removed in Umbraco 13")]
public static class PackageRepository
{
public const string RestApiBaseUrl = "https://our.umbraco.com/webapi/packages/v1";

View File

@@ -50,6 +50,7 @@ public static partial class UmbracoBuilderExtensions
builder
.AddUmbracoOptions<ModelsBuilderSettings>()
.AddUmbracoOptions<ActiveDirectorySettings>()
.AddUmbracoOptions<MarketplaceSettings>()
.AddUmbracoOptions<ContentSettings>()
.AddUmbracoOptions<CoreDebugSettings>()
.AddUmbracoOptions<ExceptionFilterSettings>()

View File

@@ -1262,6 +1262,7 @@ Mange hilsner fra Umbraco robotten
<key alias="media">Mediearkiv</key>
<key alias="member">Medlemmer</key>
<key alias="packages">Pakker</key>
<key alias="marketplace">Marketplace</key>
<key alias="newsletters">Nyhedsbreve</key>
<key alias="settings">Indstillinger</key>
<key alias="statistics">Statistik</key>

View File

@@ -1476,6 +1476,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="member">Members</key>
<key alias="newsletters">Newsletters</key>
<key alias="packages">Packages</key>
<key alias="marketplace">Marketplace</key>
<key alias="settings">Settings</key>
<key alias="statistics">Statistics</key>
<key alias="translation">Translation</key>

View File

@@ -1513,6 +1513,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="media">Media</key>
<key alias="member">Members</key>
<key alias="packages">Packages</key>
<key alias="marketplace">Marketplace</key>
<key alias="settings">Settings</key>
<key alias="translation">Translation</key>
<key alias="users">Users</key>

View File

@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
@@ -54,6 +55,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private MemberPasswordConfigurationSettings _memberPasswordConfigurationSettings;
private DataTypesSettings _dataTypesSettings;
private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory;
private MarketplaceSettings _marketplaceSettings;
[Obsolete("Use constructor that takes IOptionsMontior<DataTypeSettings>, scheduled for removal in V12")]
public BackOfficeServerVariables(
@@ -139,6 +141,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
}
[Obsolete("Use constructor that takes IOptionsMonitor<MarketplaceSettings>, scheduled for removal in V13")]
public BackOfficeServerVariables(
LinkGenerator linkGenerator,
IRuntimeState runtimeState,
@@ -159,6 +162,52 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
IOptionsMonitor<MemberPasswordConfigurationSettings> memberPasswordConfigurationSettings,
IOptionsMonitor<DataTypesSettings> dataTypesSettings,
ITempDataDictionaryFactory tempDataDictionaryFactory)
: this(
linkGenerator,
runtimeState,
features,
globalSettings,
umbracoVersion,
contentSettings,
httpContextAccessor,
treeCollection,
hostingEnvironment,
runtimeSettings,
securitySettings,
runtimeMinifier,
externalLogins,
imageUrlGenerator,
previewRoutes,
emailSender,
memberPasswordConfigurationSettings,
dataTypesSettings,
tempDataDictionaryFactory,
StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<MarketplaceSettings>>()
)
{
}
public BackOfficeServerVariables(
LinkGenerator linkGenerator,
IRuntimeState runtimeState,
UmbracoFeatures features,
IOptionsMonitor<GlobalSettings> globalSettings,
IUmbracoVersion umbracoVersion,
IOptionsMonitor<ContentSettings> contentSettings,
IHttpContextAccessor httpContextAccessor,
TreeCollection treeCollection,
IHostingEnvironment hostingEnvironment,
IOptionsMonitor<RuntimeSettings> runtimeSettings,
IOptionsMonitor<SecuritySettings> securitySettings,
IRuntimeMinifier runtimeMinifier,
IBackOfficeExternalLoginProviders externalLogins,
IImageUrlGenerator imageUrlGenerator,
PreviewRoutes previewRoutes,
IEmailSender emailSender,
IOptionsMonitor<MemberPasswordConfigurationSettings> memberPasswordConfigurationSettings,
IOptionsMonitor<DataTypesSettings> dataTypesSettings,
ITempDataDictionaryFactory tempDataDictionaryFactory,
IOptionsMonitor<MarketplaceSettings> marketplaceSettings)
{
_linkGenerator = linkGenerator;
_runtimeState = runtimeState;
@@ -179,6 +228,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
_tempDataDictionaryFactory = tempDataDictionaryFactory;
_memberPasswordConfigurationSettings = memberPasswordConfigurationSettings.CurrentValue;
_dataTypesSettings = dataTypesSettings.CurrentValue;
_marketplaceSettings = marketplaceSettings.CurrentValue;
globalSettings.OnChange(x => _globalSettings = x);
contentSettings.OnChange(x => _contentSettings = x);
@@ -186,6 +236,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
securitySettings.OnChange(x => _securitySettings = x);
dataTypesSettings.OnChange(x => _dataTypesSettings = x);
memberPasswordConfigurationSettings.OnChange(x => _memberPasswordConfigurationSettings = x);
marketplaceSettings.OnChange(x => _marketplaceSettings = x);
}
/// <summary>
@@ -298,6 +349,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{"gridConfig", _linkGenerator.GetPathByAction(nameof(BackOfficeController.GetGridConfig), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
// TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address
{"serverVarsJs", _linkGenerator.GetPathByAction(nameof(BackOfficeController.Application), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
{"marketplaceUrl", GetMarketplaceUrl()},
//API URLs
{
"packagesRestApiBaseUrl", Constants.PackageRepository.RestApiBaseUrl
@@ -625,6 +677,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return defaultVals;
}
private string GetMarketplaceUrl()
{
var uriBuilder = new UriBuilder(Constants.Marketplace.Url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["umbversion"] = _runtimeState.SemanticVersion.ToSemanticStringWithoutBuild();
query["style"] = "backoffice";
foreach (var kvp in _marketplaceSettings.AdditionalParameters)
{
query[kvp.Key] = kvp.Value;
}
uriBuilder.Query = query.ToString();
return uriBuilder.ToString();
}
[DataContract]
private class PluginTree
{

View File

@@ -2,8 +2,9 @@
* @ngdoc service
* @name umbraco.resources.ourPackageRepositoryResource
* @description handles data for package installations
* @deprecated This resource is deprecated and will be removed in future versions. Umbraco no longer supports the Our Umbraco repository.
**/
function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) {
function ourPackageRepositoryResource($http, umbRequestHelper) {
var baseurl = Umbraco.Sys.ServerVariables.umbracoUrls.packagesRestApiBaseUrl;

View File

@@ -1,3 +1,41 @@
[data-element="editor-packages"] {
.umb-pane {
height: 100%;
margin: 0;
.umb-pane-content,
.umb-editor-sub-views {
height: 100%;
.umb-editor-sub-view {
padding: 20px;
}
.sub-view-Marketplace {
height: 100%;
margin: 0;
padding: 0;
.umb-editor-sub-view__content {
height: 100%;
}
}
}
}
}
.umb-marketplace-view-wrapper {
height: 100%;
display: flex;
align-items: stretch;
}
.umb-marketplace-view {
width: 100%;
height: 100%;
overflow: hidden;
}
.umb-packages-view-title {
font-size: 20px;
font-weight: bold;

View File

@@ -6,6 +6,6 @@
</div>
<div class="umb-pane">
<div ng-transclude></div>
<div class="umb-pane-content" ng-transclude></div>
</div>
</div>

View File

@@ -1,17 +1,12 @@
<div class="umb-editor-sub-views">
<div
id="sub-view-{{$index}}"
class="umb-editor-sub-view"
ng-repeat="subView in subViews track by subView.alias"
ng-class="'sub-view-' + subView.name"
val-sub-view="subView">
<div class="umb-editor-sub-view__content"
ng-show="subView.active === true"
ng-include="subView.view">
</div>
</div>
<div
id="sub-view-{{$index}}"
class="umb-editor-sub-view"
ng-repeat="subView in subViews track by subView.alias"
ng-class="'sub-view-' + subView.name"
val-sub-view="subView"
ng-if="subView.active"
>
<div class="umb-editor-sub-view__content" ng-include="subView.view"></div>
</div>
</div>

View File

@@ -6,10 +6,9 @@
* @description
* The controller for the info view of the datatype editor
*/
function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout, editorService) {
function DataTypeInfoController($scope, $routeParams, dataTypeResource, $timeout, editorService) {
var vm = this;
var evts = [];
var referencesLoaded = false;
vm.references = {};
@@ -48,7 +47,7 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsSe
function open(id, event, type) {
// targeting a new tab/window?
if (event.ctrlKey ||
if (event.ctrlKey ||
event.shiftKey ||
event.metaKey || // apple
(event.button && event.button === 1) // middle click, >IE9 + everyone else
@@ -85,25 +84,7 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsSe
}
}
// load data type references when the references tab is activated
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
if (args.alias === "info") {
loadRelations();
}
});
}));
//ensure to unregister from all events!
$scope.$on('$destroy', function () {
for (var e in evts) {
eventsService.unsubscribe(evts[e]);
}
});
loadRelations();
}
angular.module("umbraco").controller("Umbraco.Editors.DataType.InfoController", DataTypeInfoController);

View File

@@ -1,99 +1,99 @@
(function () {
"use strict";
"use strict";
function PackagesOverviewController($scope, $location, $routeParams, localizationService, localStorageService) {
function PackagesOverviewController($location, $routeParams, localizationService, localStorageService) {
//Hack!
// if there is a local storage value for packageInstallData then we need to redirect there,
// the issue is that we still have webforms and we cannot go to a hash location and then window.reload
// because it will double load it.
// we will refresh and then navigate there.
//Hack!
// if there is a local storage value for packageInstallData then we need to redirect there,
// the issue is that we still have webforms and we cannot go to a hash location and then window.reload
// because it will double load it.
// we will refresh and then navigate there.
let packageInstallData = localStorageService.get("packageInstallData");
let packageUri = $routeParams.method;
let packageInstallData = localStorageService.get("packageInstallData");
let packageUri = $routeParams.method;
if (packageInstallData) {
localStorageService.remove("packageInstallData");
if (packageInstallData) {
localStorageService.remove("packageInstallData");
if (packageInstallData.postInstallationPath) {
//navigate to the custom installer screen if set
$location.path(packageInstallData.postInstallationPath).search("packageId", packageInstallData.id);
return;
}
if (packageInstallData.postInstallationPath) {
//navigate to the custom installer screen if set
$location.path(packageInstallData.postInstallationPath).search("packageId", packageInstallData.id);
return;
}
//if it is "installed" then set the uri/path to that
if (packageInstallData === "installed") {
packageUri = "installed";
}
}
var vm = this;
vm.page = {};
vm.page.labels = {};
vm.page.name = "";
vm.page.navigation = [];
onInit();
function onInit() {
loadNavigation();
setPageName();
}
function loadNavigation() {
var labels = ["sections_packages", "packager_installed", "packager_installLocal", "packager_created"];
localizationService.localizeMany(labels).then(function (data) {
vm.page.labels.packages = data[0];
vm.page.labels.installed = data[1];
vm.page.labels.install = data[2];
vm.page.labels.created = data[3];
vm.page.navigation = [
{
"name": vm.page.labels.packages,
"icon": "icon-cloud",
"view": "views/packages/views/repo.html",
"active": !packageUri || packageUri === "repo",
"alias": "umbPackages",
"action": function () {
$location.path("/packages/packages/repo");
}
},
{
"name": vm.page.labels.installed,
"icon": "icon-box",
"view": "views/packages/views/installed.html",
"active": packageUri === "installed",
"alias": "umbInstalled",
"action": function () {
$location.path("/packages/packages/installed");
}
},
{
"name": vm.page.labels.created,
"icon": "icon-files",
"view": "views/packages/views/created.html",
"active": packageUri === "created",
"alias": "umbCreatedPackages",
"action": function () {
$location.path("/packages/packages/created");
}
}
];
});
}
function setPageName() {
localizationService.localize("sections_packages").then(function (data) {
vm.page.name = data;
})
}
//if it is "installed" then set the uri/path to that
if (packageInstallData === "installed") {
packageUri = "installed";
}
}
angular.module("umbraco").controller("Umbraco.Editors.Packages.OverviewController", PackagesOverviewController);
var vm = this;
vm.page = {};
vm.page.labels = {};
vm.page.name = "";
vm.page.navigation = [];
onInit();
function onInit() {
loadNavigation();
setPageName();
}
function loadNavigation() {
var labels = ["sections_marketplace", "packager_installed", "packager_installLocal", "packager_created"];
localizationService.localizeMany(labels).then(function (data) {
vm.page.labels.marketplace = data[0];
vm.page.labels.installed = data[1];
vm.page.labels.install = data[2];
vm.page.labels.created = data[3];
vm.page.navigation = [
{
"name": vm.page.labels.marketplace,
"icon": "icon-cloud",
"view": "views/packages/views/marketplace.html",
"active": !packageUri || packageUri === "repo",
"alias": "umbMarketplace",
"action": function () {
$location.path("/packages/packages/repo");
}
},
{
"name": vm.page.labels.installed,
"icon": "icon-box",
"view": "views/packages/views/installed.html",
"active": packageUri === "installed",
"alias": "umbInstalled",
"action": function () {
$location.path("/packages/packages/installed");
}
},
{
"name": vm.page.labels.created,
"icon": "icon-files",
"view": "views/packages/views/created.html",
"active": packageUri === "created",
"alias": "umbCreatedPackages",
"action": function () {
$location.path("/packages/packages/created");
}
}
];
});
}
function setPageName() {
localizationService.localize("sections_marketplace").then(function (data) {
vm.page.name = data;
})
}
}
angular.module("umbraco").controller("Umbraco.Editors.Packages.OverviewController", PackagesOverviewController);
})();

View File

@@ -0,0 +1,18 @@
(function () {
"use strict";
function MarketplaceController($sce) {
var vm = this;
var marketplaceUrl = new URL(Umbraco.Sys.ServerVariables.umbracoUrls.marketplaceUrl);
function init() {
vm.marketplaceUrl = $sce.trustAsResourceUrl(marketplaceUrl.toString());
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Packages.MarketplaceController", MarketplaceController);
})();

View File

@@ -0,0 +1,13 @@
<div
class="umb-marketplace-view-wrapper clearfix"
ng-controller="Umbraco.Editors.Packages.MarketplaceController as vm"
>
<iframe
ng-if="::vm.marketplaceUrl"
ng-src="{{ ::vm.marketplaceUrl }}"
class="umb-marketplace-view"
title="Umbraco Marketplace"
allowfullscreen
allow="geolocation; autoplay; clipboard-write; encrypted-media"
></iframe>
</div>

View File

@@ -1,265 +1,265 @@
(function () {
"use strict";
"use strict";
function PackagesRepoController($scope, $timeout, ourPackageRepositoryResource, $q, localizationService, notificationsService) {
function PackagesRepoController($scope, $timeout, ourPackageRepositoryResource, $q, localizationService, notificationsService) {
var vm = this;
var vm = this;
vm.packageViewState = "packageList";
vm.categories = [];
vm.loading = true;
vm.pagination = {
pageNumber: 1,
totalPages: 10,
pageSize: 24
};
vm.searchQuery = "";
vm.selectCategory = selectCategory;
vm.showPackageDetails = showPackageDetails;
vm.setPackageViewState = setPackageViewState;
vm.nextPage = nextPage;
vm.prevPage = prevPage;
vm.goToPage = goToPage;
vm.openLightbox = openLightbox;
vm.closeLightbox = closeLightbox;
vm.search = search;
vm.installCompleted = false;
vm.highlightedPackageCollections = [];
vm.labels = {};
vm.packageViewState = "packageList";
vm.categories = [];
vm.loading = true;
vm.pagination = {
pageNumber: 1,
totalPages: 10,
pageSize: 24
};
vm.searchQuery = "";
vm.selectCategory = selectCategory;
vm.showPackageDetails = showPackageDetails;
vm.setPackageViewState = setPackageViewState;
vm.nextPage = nextPage;
vm.prevPage = prevPage;
vm.goToPage = goToPage;
vm.openLightbox = openLightbox;
vm.closeLightbox = closeLightbox;
vm.search = search;
vm.installCompleted = false;
vm.highlightedPackageCollections = [];
vm.labels = {};
var defaultSort = "Latest";
var currSort = defaultSort;
var defaultSort = "Latest";
var currSort = defaultSort;
//used to cancel any request in progress if another one needs to take it's place
var canceler = null;
//used to cancel any request in progress if another one needs to take it's place
var canceler = null;
function getActiveCategory() {
if (vm.searchQuery !== "") {
return "";
}
for (var i = 0; i < vm.categories.length; i++) {
if (vm.categories[i].active === true) {
return vm.categories[i].name;
}
}
return "";
function getActiveCategory() {
if (vm.searchQuery !== "") {
return "";
}
for (var i = 0; i < vm.categories.length; i++) {
if (vm.categories[i].active === true) {
return vm.categories[i].name;
}
function init() {
vm.loading = true;
localizationService.localizeMany(["packager_packagesPopular", "packager_packagesPromoted"])
.then(function (labels) {
vm.labels.popularPackages = labels[0];
vm.labels.promotedPackages = labels[1];
var popularPackages, promotedPackages;
$q.all([
ourPackageRepositoryResource.getCategories()
.then(function (cats) {
vm.categories = cats.filter(function (cat) {
return cat.name !== "Umbraco Pro";
});
}),
ourPackageRepositoryResource.getPopular(10)
.then(function (pack) {
popularPackages = { title: vm.labels.popularPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.getPromoted(20)
.then(function (pack) {
promotedPackages = { title: vm.labels.promotedPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
})
])
.then(function () {
vm.highlightedPackageCollections = [popularPackages, promotedPackages];
vm.loading = false;
});
});
}
function selectCategory(selectedCategory, categories) {
for (var i = 0; i < categories.length; i++) {
var category = categories[i];
if (category.name === selectedCategory.name) {
//it's already selected, let's unselect to show all again
if (category.active === true) {
category.active = false;
}
else {
category.active = true;
}
}
else {
category.active = false;
}
}
vm.loading = true;
vm.searchQuery = "";
var reset = selectedCategory.active === false;
var searchCategory = reset ? "" : selectedCategory.name;
currSort = defaultSort;
var popularPackages, promotedPackages;
$q.all([
ourPackageRepositoryResource.getPopular(10, searchCategory)
.then(function (pack) {
popularPackages = { title: vm.labels.popularPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.getPromoted(20, searchCategory)
.then(function (pack) {
promotedPackages = { title: vm.labels.promotedPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
vm.pagination.pageNumber = 1;
})
])
.then(function () {
vm.highlightedPackageCollections = [popularPackages, promotedPackages];
vm.loading = false;
});
}
function showPackageDetails(selectedPackage) {
ourPackageRepositoryResource.getDetails(selectedPackage.id)
.then(function (pack) {
vm.package = pack;
vm.packageViewState = "packageDetails";
});
}
function setPackageViewState(state) {
if (state) {
vm.packageViewState = state;
}
}
function nextPage(pageNumber) {
ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
});
}
function prevPage(pageNumber) {
ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
});
}
function goToPage(pageNumber) {
ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
});
}
var previousElement = null;
function openLightbox(itemIndex, items) {
previousElement = ( document.activeElement || document.body );
vm.lightbox = {
show: true,
items: items,
activeIndex: itemIndex,
focus: true
};
}
function closeLightbox() {
vm.lightbox.show = false;
vm.lightbox = null;
if(previousElement){
setTimeout(function(){
previousElement.focus();
previousElement = null;
}, 100)
}
document.activeElement.blur();
}
var searchDebounced = _.debounce(function (e) {
//a canceler exists, so perform the cancelation operation and reset
if (canceler) {
canceler.resolve();
}
canceler = $q.defer();
$scope.$apply(function () {
currSort = vm.searchQuery ? "Default" : "Latest";
ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1,
vm.pagination.pageSize,
currSort,
"",
vm.searchQuery,
canceler.promise)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
vm.pagination.pageNumber = 1;
vm.loading = false;
//set back to null so it can be re-created
canceler = null;
})
.catch(function (err) {
canceler = null;
if (err) {
// If an abort happened, ignore it since it happened because of a new search
if (err.xhrStatus === 'abort') {
return;
}
// Otherwise, show the error
if (err.errorMsg) {
notificationsService.error(err.errorMsg);
return;
}
}
console.error(err);
});
});
}, 200);
function search(searchQuery) {
vm.loading = true;
searchDebounced();
}
vm.reloadPage = function () {
//reload on next digest (after cookie)
$timeout(function () {
window.location.reload(true);
});
}
init();
}
return "";
}
angular.module("umbraco").controller("Umbraco.Editors.Packages.RepoController", PackagesRepoController);
function init() {
vm.loading = true;
localizationService.localizeMany(["packager_packagesPopular", "packager_packagesPromoted"])
.then(function (labels) {
vm.labels.popularPackages = labels[0];
vm.labels.promotedPackages = labels[1];
var popularPackages, promotedPackages;
$q.all([
ourPackageRepositoryResource.getCategories()
.then(function (cats) {
vm.categories = cats.filter(function (cat) {
return cat.name !== "Umbraco Pro";
});
}),
ourPackageRepositoryResource.getPopular(10)
.then(function (pack) {
popularPackages = { title: vm.labels.popularPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.getPromoted(20)
.then(function (pack) {
promotedPackages = { title: vm.labels.promotedPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
})
])
.then(function () {
vm.highlightedPackageCollections = [popularPackages, promotedPackages];
vm.loading = false;
});
});
}
function selectCategory(selectedCategory, categories) {
for (var i = 0; i < categories.length; i++) {
var category = categories[i];
if (category.name === selectedCategory.name) {
//it's already selected, let's unselect to show all again
if (category.active === true) {
category.active = false;
}
else {
category.active = true;
}
}
else {
category.active = false;
}
}
vm.loading = true;
vm.searchQuery = "";
var reset = selectedCategory.active === false;
var searchCategory = reset ? "" : selectedCategory.name;
currSort = defaultSort;
var popularPackages, promotedPackages;
$q.all([
ourPackageRepositoryResource.getPopular(10, searchCategory)
.then(function (pack) {
popularPackages = { title: vm.labels.popularPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.getPromoted(20, searchCategory)
.then(function (pack) {
promotedPackages = { title: vm.labels.promotedPackages, packages: pack.packages };
}),
ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
vm.pagination.pageNumber = 1;
})
])
.then(function () {
vm.highlightedPackageCollections = [popularPackages, promotedPackages];
vm.loading = false;
});
}
function showPackageDetails(selectedPackage) {
ourPackageRepositoryResource.getDetails(selectedPackage.id)
.then(function (pack) {
vm.package = pack;
vm.packageViewState = "packageDetails";
});
}
function setPackageViewState(state) {
if (state) {
vm.packageViewState = state;
}
}
function nextPage(pageNumber) {
ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
});
}
function prevPage(pageNumber) {
ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
});
}
function goToPage(pageNumber) {
ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
});
}
var previousElement = null;
function openLightbox(itemIndex, items) {
previousElement = ( document.activeElement || document.body );
vm.lightbox = {
show: true,
items: items,
activeIndex: itemIndex,
focus: true
};
}
function closeLightbox() {
vm.lightbox.show = false;
vm.lightbox = null;
if(previousElement){
setTimeout(function(){
previousElement.focus();
previousElement = null;
}, 100)
}
document.activeElement.blur();
}
var searchDebounced = _.debounce(function (e) {
//a canceler exists, so perform the cancelation operation and reset
if (canceler) {
canceler.resolve();
}
canceler = $q.defer();
$scope.$apply(function () {
currSort = vm.searchQuery ? "Default" : "Latest";
ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1,
vm.pagination.pageSize,
currSort,
"",
vm.searchQuery,
canceler.promise)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
vm.pagination.pageNumber = 1;
vm.loading = false;
//set back to null so it can be re-created
canceler = null;
})
.catch(function (err) {
canceler = null;
if (err) {
// If an abort happened, ignore it since it happened because of a new search
if (err.xhrStatus === 'abort') {
return;
}
// Otherwise, show the error
if (err.errorMsg) {
notificationsService.error(err.errorMsg);
return;
}
}
console.error(err);
});
});
}, 200);
function search(searchQuery) {
vm.loading = true;
searchDebounced();
}
vm.reloadPage = function () {
//reload on next digest (after cookie)
$timeout(function () {
window.location.reload(true);
});
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Packages.RepoController", PackagesRepoController);
})();

View File

@@ -1,318 +1,318 @@
<div ng-controller="Umbraco.Editors.Packages.RepoController as vm" class="clearfix">
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<!-- LIST -->
<div class="umb-packages-view-wrapper" ng-if="vm.packageViewState === 'packageList'">
<div class="umb-packages-section">
<div class="umb-packages-search">
<label for="package-query" class="sr-only">
<localize key="packager_packageSearch">Search for packages</localize>
</label>
<input type="text"
class="-full-width-input"
name="query"
id="package-query"
localize="placeholder"
placeholder="@packager_packageSearch"
ng-model="vm.searchQuery"
ng-on-input="vm.search()"
no-dirty-check />
</div>
</div>
<div class="umb-packages-section" ng-if="vm.searchQuery == ''">
<div class="umb-packages-categories">
<button type="button"
class="umb-packages-category"
ng-click="vm.selectCategory(category, vm.categories)"
ng-repeat="category in vm.categories"
ng-class="{'-active': category.active, '-first': $first, '-last': $last}">
{{category.name}}
</button>
</div>
</div>
<div ng-show="vm.loading === false">
<div ng-repeat="highlightedPackageCollection in vm.highlightedPackageCollections">
<div class="umb-packages-section" ng-if="vm.searchQuery == '' && highlightedPackageCollection.packages.length > 0">
<h4><strong>{{highlightedPackageCollection.title}}</strong></h4>
<div class="umb-packages">
<div class="umb-package" ng-repeat="package in highlightedPackageCollection.packages">
<button type="button" class="umb-package-link" ng-click="vm.showPackageDetails(package)">
<div class="flex flex-column">
<div class="umb-package-icon">
<img ng-src="{{package.icon}}" alt="" />
</div>
<div class="umb-package-info">
<div class="umb-package-name">{{package.name}}</div>
<div class="umb-package-description">{{package.excerpt | limitTo: 40}}<span ng-if="package.excerpt > (package.excerpt | limitTo: 40)">...</span></div>
<div class="umb-package-numbers">
<small class="umb-package-downloads">
<umb-icon icon="icon-download-alt"></umb-icon>
<strong>{{package.downloads}}</strong>
</small>
<small class="umb-package-likes">
<umb-icon icon="icon-hearts" class="icon-hearts"></umb-icon>
<strong>{{package.likes}}</strong>
</small>
</div>
<div class="umb-package-cloud">
<div ng-if="package.certifiedToWorkOnUmbracoCloud">
<umb-icon icon="icon-cloud" class="icon-cloud"></umb-icon>
<span><localize key="packager_verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco Cloud</localize></span>
</div>
</div>
</div>
</div>
</button>
</div> <!-- end package -->
</div> <!-- end packages -->
</div>
</div>
<div class="umb-packages-section" ng-if="vm.packages.length > 0">
<h4 ng-if="vm.searchQuery === ''"><strong><localize key="packager_packagesNew">New Releases</localize></strong></h4>
<h4 ng-if="vm.searchQuery !== ''"><strong><localize key="packager_packageSearchResults">Results for</localize> '{{ vm.searchQuery }}'</strong></h4>
<div class="umb-packages">
<div class="umb-package" ng-repeat="package in vm.packages">
<button type="button" class="umb-package-link" ng-click="vm.showPackageDetails(package)">
<div class="flex flex-column">
<div class="umb-package-icon">
<img ng-src="{{package.icon}}" alt="" />
</div>
<div class="umb-package-info">
<div class="umb-package-name">{{ package.name }}</div>
<div class="umb-package-description">{{ package.excerpt | limitTo: 40 }}<span ng-if="package.excerpt > (package.excerpt | limitTo: 40)">...</span></div>
<div class="umb-package-numbers">
<small class="umb-package-downloads">
<umb-icon icon="icon-download-alt"></umb-icon>
<strong>{{package.downloads}}</strong>
</small>
<small class="umb-package-likes">
<umb-icon icon="icon-hearts" class="icon-hearts"></umb-icon>
<strong>{{package.likes}}</strong>
</small>
</div>
<div class="umb-package-cloud">
<div ng-if="package.certifiedToWorkOnUmbracoCloud">
<umb-icon icon="icon-cloud" class="icon-cloud"></umb-icon>
<span><localize key="packager_verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco Cloud</localize></span>
</div>
</div>
</div>
</div>
</button>
</div> <!-- end package -->
</div> <!-- end packages -->
</div>
<div class="umb-packages__pagination" ng-if="vm.pagination.totalPages > 1 && vm.loading === false">
<umb-pagination page-number="vm.pagination.pageNumber"
total-pages="vm.pagination.totalPages"
on-next="vm.nextPage"
on-prev="vm.prevPage"
on-go-to-page="vm.goToPage">
</umb-pagination>
</div>
<!-- Empty state -->
<umb-empty-state ng-if="vm.packages.length === 0 && vm.loading === false && vm.searchQuery !== ''"
position="center">
<h4><strong><localize key="packager_packageNoResults">We couldn't find anything for</localize> '{{ vm.searchQuery }}'</strong></h4>
<p class="faded"><localize key="packager_packageNoResultsDescription">Please try searching for another package or browse through the categories</localize>.</p>
</umb-empty-state>
<umb-empty-state ng-if="vm.popular.length === 0 && vm.loading === false && vm.searchQuery === ''"
position="center">
<h4><strong><localize key="general_searchNoResult">Sorry, we can not find what you are looking for.</localize></strong></h4>
<p class="faded"><localize key="packager_packageNoResultsDescription">Please try searching for another package or browse through the categories</localize>.</p>
</umb-empty-state>
</div>
<!-- LIST -->
<div class="umb-packages-view-wrapper" ng-if="vm.packageViewState === 'packageList'">
<div class="umb-packages-section">
<div class="umb-packages-search">
<label for="package-query" class="sr-only">
<localize key="packager_packageSearch">Search for packages</localize>
</label>
<input type="text"
class="-full-width-input"
name="query"
id="package-query"
localize="placeholder"
placeholder="@packager_packageSearch"
ng-model="vm.searchQuery"
ng-on-input="vm.search()"
no-dirty-check />
</div>
</div>
<div class="umb-packages-section" ng-if="vm.searchQuery == ''">
<div class="umb-packages-categories">
<button type="button"
class="umb-packages-category"
ng-click="vm.selectCategory(category, vm.categories)"
ng-repeat="category in vm.categories"
ng-class="{'-active': category.active, '-first': $first, '-last': $last}">
{{category.name}}
</button>
</div>
</div>
<!-- DETAILS -->
<div ng-if="vm.packageViewState === 'packageDetails' && vm.loading === false">
<div ng-show="vm.loading === false">
<umb-editor-sub-header>
<umb-editor-sub-header-content-left>
<button type="button" class="umb-package-details__back-action" ng-click="vm.setPackageViewState('packageList');" prevent-default><span aria-hidden="true">&larr;</span> <localize key="general_back">Back</localize></button>
</umb-editor-sub-header-content-left>
</umb-editor-sub-header>
<div ng-repeat="highlightedPackageCollection in vm.highlightedPackageCollections">
<div class="umb-packages-section" ng-if="vm.searchQuery == '' && highlightedPackageCollection.packages.length > 0">
<h4><strong>{{highlightedPackageCollection.title}}</strong></h4>
<div class="umb-packages">
<div class="umb-packages-view-wrapper">
<div class="umb-package" ng-repeat="package in highlightedPackageCollection.packages">
<button type="button" class="umb-package-link" ng-click="vm.showPackageDetails(package)">
<div class="umb-package-details">
<div class="flex flex-column">
<div class="umb-package-icon">
<img ng-src="{{package.icon}}" alt="" />
</div>
<div class="umb-package-details__main-content">
<div class="umb-package-info">
<div class="umb-package-name">{{package.name}}</div>
<div class="umb-package-description">{{package.excerpt | limitTo: 40}}<span ng-if="package.excerpt > (package.excerpt | limitTo: 40)">...</span></div>
<umb-box>
<umb-box-content>
<div class="umb-packages-view-title">{{ vm.package.name }}</div>
<div class="umb-package-details__description" ng-bind-html="vm.package.description"></div>
<div class="umb-gallery">
<div class="umb-gallery__thumbnails">
<a ng-href="{{ image.source }}" type="button" class="umb-gallery__thumbnail" ng-repeat="image in vm.package.images" ng-click="vm.openLightbox($index, vm.package.images)" prevent-default>
<img ng-src="{{ image.thumbnail }}" alt="" />
</a>
</div>
</div>
</umb-box-content>
</umb-box>
<umb-lightbox
ng-if="vm.lightbox.show"
items="vm.lightbox.items"
active-item-index="vm.lightbox.activeIndex"
on-close="vm.closeLightbox">
</umb-lightbox>
</div>
<div class="umb-package-details__sidebar">
<umb-box>
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_installInstructions">Install Instructions</localize></div>
<div class="umb-package-details__install-instructions">
dotnet add package <span>{{vm.package.nuGetPackageId}}</span>
<div class="umb-package-numbers">
<small class="umb-package-downloads">
<umb-icon icon="icon-download-alt"></umb-icon>
<strong>{{package.downloads}}</strong>
</small>
<small class="umb-package-likes">
<umb-icon icon="icon-hearts" class="icon-hearts"></umb-icon>
<strong>{{package.likes}}</strong>
</small>
</div>
<div class="umb-package-cloud">
<div ng-if="package.certifiedToWorkOnUmbracoCloud">
<umb-icon icon="icon-cloud" class="icon-cloud"></umb-icon>
<span><localize key="packager_verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco Cloud</localize></span>
</div>
</umb-box-content>
</umb-box>
<umb-box>
<umb-box-content>
<div class="umb-package-details__owner-profile">
<div class="umb-package-details__owner-profile-avatar">
<umb-avatar
size="m"
img-src="{{ 'https://our.umbraco.com' + vm.package.ownerInfo.ownerAvatar }}">
</umb-avatar>
</div>
<div>
<div class="umb-package-details__owner-profile-name">{{ vm.package.ownerInfo.owner }}</div>
<div class="umb-package-details__owner-profile-karma">
{{ vm.package.ownerInfo.owner }} <localize key="packager_packageHas">has</localize> <strong>{{ vm.package.ownerInfo.karma }}</strong> <localize key="packager_packageKarmaPoints">karma points</localize>
</div>
</div>
</div>
</umb-box-content>
</umb-box>
<umb-box>
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_packageInfo">Information</localize></div>
<div>
<div class="umb-package-details__information-item" ng-if="vm.package.ownerInfo.owner">
<div class="umb-package-details__information-item-label"><localize key="packager_packageOwner">Owner</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.ownerInfo.owner}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.ownerInfo.contributors">
<div class="umb-package-details__information-item-label"><localize key="packager_packageContrib">Contributors</localize>:</div>
<div class="umb-package-details__information-item-content">
<span ng-repeat="contributor in vm.package.ownerInfo.contributors">{{ contributor }}<span ng-if="!$last">,&nbsp;</span></span>
</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.created">
<div class="umb-package-details__information-item-label"><localize key="packager_packageCreated">Created</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.created | date:'yyyy-MM-dd HH:mm:ss'}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.latestVersion">
<div class="umb-package-details__information-item-label"><localize key="packager_packageCurrentVersion">Current version</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.latestVersion}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.information.netVersion">
<div class="umb-package-details__information-item-label"><localize key="packager_packageNetVersion">.NET Version</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.information.netVersion}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.licenseName">
<div class="umb-package-details__information-item-label"><localize key="packager_packageLicense">License</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.licenseName}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.downloads">
<div class="umb-package-details__information-item-label"><localize key="packager_packageDownloads">Downloads</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.downloads}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.ownerInfo.karma">
<div class="umb-package-details__information-item-label"><localize key="packager_packageLikes">Likes</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.likes}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.certifiedToWorkOnUmbracoCloud">
<div class="umb-package-details__information-item-label"><localize key="packager_verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco CLoud</localize></div>
</div>
</div>
</umb-box-content>
</umb-box>
<umb-box>
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_packageCompatibility">Compatibility</localize></div>
<div class="umb-package-details__section-description"><localize key="packager_packageCompatibilityDescription">This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100%</localize></div>
<div class="umb-package-details__compatability" ng-repeat="compatibility in vm.package.compatibility | filter:percentage > 0">
<div class="umb-package-details__compatability-label">
<span class="umb-package-details__information-item-label">{{compatibility.version}}</span>
<span class="umb-package-details__information-item-label-2">({{compatibility.percentage}}%)</span>
</div>
<umb-progress-bar
percentage="{{compatibility.percentage}}">
</umb-progress-bar>
</div>
</umb-box-content>
</umb-box>
<umb-box ng-if="vm.package.externalSources">
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_packageExternalSources">External sources</localize></div>
<div>
<div class="umb-package-details__information-item" ng-repeat="externalSource in vm.package.externalSources">
<a class="umb-package-details__link" href="{{externalSource.url}}" target="_blank" rel="noopener noreferrer">
<umb-icon icon="icon-out"></umb-icon>
{{externalSource.name}}
</a>
</div>
</div>
</umb-box-content>
</umb-box>
</div>
</div>
</div>
</div>
</button>
</div> <!-- end package -->
</div> <!-- end packages -->
</div>
</div>
<div class="umb-packages-section" ng-if="vm.packages.length > 0">
<h4 ng-if="vm.searchQuery === ''"><strong><localize key="packager_packagesNew">New Releases</localize></strong></h4>
<h4 ng-if="vm.searchQuery !== ''"><strong><localize key="packager_packageSearchResults">Results for</localize> '{{ vm.searchQuery }}'</strong></h4>
<div class="umb-packages">
<div class="umb-package" ng-repeat="package in vm.packages">
<button type="button" class="umb-package-link" ng-click="vm.showPackageDetails(package)">
<div class="flex flex-column">
<div class="umb-package-icon">
<img ng-src="{{package.icon}}" alt="" />
</div>
<div class="umb-package-info">
<div class="umb-package-name">{{ package.name }}</div>
<div class="umb-package-description">{{ package.excerpt | limitTo: 40 }}<span ng-if="package.excerpt > (package.excerpt | limitTo: 40)">...</span></div>
<div class="umb-package-numbers">
<small class="umb-package-downloads">
<umb-icon icon="icon-download-alt"></umb-icon>
<strong>{{package.downloads}}</strong>
</small>
<small class="umb-package-likes">
<umb-icon icon="icon-hearts" class="icon-hearts"></umb-icon>
<strong>{{package.likes}}</strong>
</small>
</div>
<div class="umb-package-cloud">
<div ng-if="package.certifiedToWorkOnUmbracoCloud">
<umb-icon icon="icon-cloud" class="icon-cloud"></umb-icon>
<span><localize key="packager_verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco Cloud</localize></span>
</div>
</div>
</div>
</div>
</button>
</div> <!-- end package -->
</div> <!-- end packages -->
</div>
<div class="umb-packages__pagination" ng-if="vm.pagination.totalPages > 1 && vm.loading === false">
<umb-pagination page-number="vm.pagination.pageNumber"
total-pages="vm.pagination.totalPages"
on-next="vm.nextPage"
on-prev="vm.prevPage"
on-go-to-page="vm.goToPage">
</umb-pagination>
</div>
<!-- Empty state -->
<umb-empty-state ng-if="vm.packages.length === 0 && vm.loading === false && vm.searchQuery !== ''"
position="center">
<h4><strong><localize key="packager_packageNoResults">We couldn't find anything for</localize> '{{ vm.searchQuery }}'</strong></h4>
<p class="faded"><localize key="packager_packageNoResultsDescription">Please try searching for another package or browse through the categories</localize>.</p>
</umb-empty-state>
<umb-empty-state ng-if="vm.popular.length === 0 && vm.loading === false && vm.searchQuery === ''"
position="center">
<h4><strong><localize key="general_searchNoResult">Sorry, we can not find what you are looking for.</localize></strong></h4>
<p class="faded"><localize key="packager_packageNoResultsDescription">Please try searching for another package or browse through the categories</localize>.</p>
</umb-empty-state>
</div>
</div>
<!-- DETAILS -->
<div ng-if="vm.packageViewState === 'packageDetails' && vm.loading === false">
<umb-editor-sub-header>
<umb-editor-sub-header-content-left>
<button type="button" class="umb-package-details__back-action" ng-click="vm.setPackageViewState('packageList');" prevent-default><span aria-hidden="true">&larr;</span> <localize key="general_back">Back</localize></button>
</umb-editor-sub-header-content-left>
</umb-editor-sub-header>
<div class="umb-packages-view-wrapper">
<div class="umb-package-details">
<div class="umb-package-details__main-content">
<umb-box>
<umb-box-content>
<div class="umb-packages-view-title">{{ vm.package.name }}</div>
<div class="umb-package-details__description" ng-bind-html="vm.package.description"></div>
<div class="umb-gallery">
<div class="umb-gallery__thumbnails">
<a ng-href="{{ image.source }}" type="button" class="umb-gallery__thumbnail" ng-repeat="image in vm.package.images" ng-click="vm.openLightbox($index, vm.package.images)" prevent-default>
<img ng-src="{{ image.thumbnail }}" alt="" />
</a>
</div>
</div>
</umb-box-content>
</umb-box>
<umb-lightbox
ng-if="vm.lightbox.show"
items="vm.lightbox.items"
active-item-index="vm.lightbox.activeIndex"
on-close="vm.closeLightbox">
</umb-lightbox>
</div>
<div class="umb-package-details__sidebar">
<umb-box>
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_installInstructions">Install Instructions</localize></div>
<div class="umb-package-details__install-instructions">
dotnet add package <span>{{vm.package.nuGetPackageId}}</span>
</div>
</umb-box-content>
</umb-box>
<umb-box>
<umb-box-content>
<div class="umb-package-details__owner-profile">
<div class="umb-package-details__owner-profile-avatar">
<umb-avatar
size="m"
img-src="{{ 'https://our.umbraco.com' + vm.package.ownerInfo.ownerAvatar }}">
</umb-avatar>
</div>
<div>
<div class="umb-package-details__owner-profile-name">{{ vm.package.ownerInfo.owner }}</div>
<div class="umb-package-details__owner-profile-karma">
{{ vm.package.ownerInfo.owner }} <localize key="packager_packageHas">has</localize> <strong>{{ vm.package.ownerInfo.karma }}</strong> <localize key="packager_packageKarmaPoints">karma points</localize>
</div>
</div>
</div>
</umb-box-content>
</umb-box>
<umb-box>
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_packageInfo">Information</localize></div>
<div>
<div class="umb-package-details__information-item" ng-if="vm.package.ownerInfo.owner">
<div class="umb-package-details__information-item-label"><localize key="packager_packageOwner">Owner</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.ownerInfo.owner}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.ownerInfo.contributors">
<div class="umb-package-details__information-item-label"><localize key="packager_packageContrib">Contributors</localize>:</div>
<div class="umb-package-details__information-item-content">
<span ng-repeat="contributor in vm.package.ownerInfo.contributors">{{ contributor }}<span ng-if="!$last">,&nbsp;</span></span>
</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.created">
<div class="umb-package-details__information-item-label"><localize key="packager_packageCreated">Created</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.created | date:'yyyy-MM-dd HH:mm:ss'}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.latestVersion">
<div class="umb-package-details__information-item-label"><localize key="packager_packageCurrentVersion">Current version</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.latestVersion}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.information.netVersion">
<div class="umb-package-details__information-item-label"><localize key="packager_packageNetVersion">.NET Version</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.information.netVersion}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.licenseName">
<div class="umb-package-details__information-item-label"><localize key="packager_packageLicense">License</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.licenseName}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.downloads">
<div class="umb-package-details__information-item-label"><localize key="packager_packageDownloads">Downloads</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.downloads}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.ownerInfo.karma">
<div class="umb-package-details__information-item-label"><localize key="packager_packageLikes">Likes</localize>:</div>
<div class="umb-package-details__information-item-content">{{vm.package.likes}}</div>
</div>
<div class="umb-package-details__information-item" ng-if="vm.package.certifiedToWorkOnUmbracoCloud">
<div class="umb-package-details__information-item-label"><localize key="packager_verifiedToWorkOnUmbracoCloud">Verified to work on Umbraco CLoud</localize></div>
</div>
</div>
</umb-box-content>
</umb-box>
<umb-box>
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_packageCompatibility">Compatibility</localize></div>
<div class="umb-package-details__section-description"><localize key="packager_packageCompatibilityDescription">This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100%</localize></div>
<div class="umb-package-details__compatability" ng-repeat="compatibility in vm.package.compatibility | filter:percentage > 0">
<div class="umb-package-details__compatability-label">
<span class="umb-package-details__information-item-label">{{compatibility.version}}</span>
<span class="umb-package-details__information-item-label-2">({{compatibility.percentage}}%)</span>
</div>
<umb-progress-bar
percentage="{{compatibility.percentage}}">
</umb-progress-bar>
</div>
</umb-box-content>
</umb-box>
<umb-box ng-if="vm.package.externalSources">
<umb-box-content>
<div class="umb-package-details__section-title"><localize key="packager_packageExternalSources">External sources</localize></div>
<div>
<div class="umb-package-details__information-item" ng-repeat="externalSource in vm.package.externalSources">
<a class="umb-package-details__link" href="{{externalSource.url}}" target="_blank" rel="noopener noreferrer">
<umb-icon icon="icon-out"></umb-icon>
{{externalSource.name}}
</a>
</div>
</div>
</umb-box-content>
</umb-box>
</div>
</div>
</div>
</div>
</div>

View File

@@ -54,14 +54,7 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
});
// load references when the 'relations' tab is first activated/switched to
var appTabChange = eventsService.on("app.tabChange", function (event, args) {
if (args.alias === "relations") {
loadRelations();
}
});
$scope.$on('$destroy', function () {
appTabChange();
});
loadRelations();
// Inital page/overview API call of relation type
relationTypeResource.getById($routeParams.id)