├── .editorconfig
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── RJP.MultiUrlPicker.sln
├── build.cmd
├── build.fsx
├── build
├── nuspec
│ └── RJP.UmbracoMultiUrlPicker.nuspec
└── version.txt
└── src
├── RJP.MultiUrlPicker.Web.UI
├── MultiUrlPicker.html
└── MultiUrlPicker.js
└── RJP.MultiUrlPicker
├── Information.cs
├── Models
├── Link.cs
├── LinkDisplay.cs
├── LinkDto.cs
├── LinkType.cs
└── MultiUrls.cs
├── MultiUrlPickerPropertyEditor.cs
├── MultiUrlPickerValueConverter.cs
├── Properties
└── AssemblyInfo.cs
├── RJP.MultiUrlPicker.csproj
├── app.config
└── packages.config
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 4
10 | end_of_line = crlf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [*.{js,css,yml}]
19 | indent_size = 2
20 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .fake/
2 | .nuget/
3 | .vs/
4 | .idea/
5 |
6 | /packages/
7 | /artifacts/
8 |
9 | bin/
10 | obj/
11 | node_modules/
12 |
13 | .DS_Store
14 | *.user
15 | *.suo
16 | *.log
17 | *.swp
18 | *.bak
19 | *.log
20 | *.orig
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | # 2.2.1
6 | ### Bugfixes
7 | * #88 - Can't edit anchor tag links
8 |
9 | # 2.2.0
10 | ### Features
11 |
12 | * #83 - Umbraco version 7.12.0 breaks UI
13 | * #82 - Moved `ng-repeat` to the `umb-node-preview` directive
14 | * Localized validation messages
15 | * Added helper messages for min/max number of items
16 |
17 | # 2.1.0
18 | ### Features
19 |
20 | * #45 - Feature Request : Querystring Parameters. Thanks to @mattbrailsford
21 | * No more "Could not find persisted pre-value for field (minNumberOfItems|maxNumberOfItems)" warnings in the Umbraco log
22 |
23 | ### Bugfixes
24 |
25 | * #60 - Only returns first item on startup
26 |
27 | # 2.0.1
28 | ### Bugfixes
29 |
30 | * #54 - Multi Url Picker cant add nodes
31 |
32 | # 2.0.0
33 | ### Features
34 |
35 | * Updated styling to match Umbraco v7.6
36 | * Added Udi support
37 | * New data types with `max number of items` set to 1 will return a single `Link` or `null`.
38 | Existing data types will continue to return `IEnumerable `
39 |
40 | ### Breaking
41 |
42 | * Upgraded Umbraco to v7.6
43 |
44 | # 1.3.2
45 | ### Bugfixes
46 |
47 | * #48 - Editing a media item breaks the link
48 | * #51 - Picked nodes are not resolved when deleted
49 |
50 | # 1.3.1
51 | ### Bugfixes
52 |
53 | * Fixed bug introduced when cleaning code
54 |
55 | # 1.3.0
56 | ### Features
57 |
58 | * #22 - Usage in macro parameter
59 |
60 | ### Bugfixes
61 |
62 | * #28 - Not working in combination with Doc Type Grid Editor Package
63 |
64 | # 1.2.0
65 | ### Features
66 |
67 | * #14 - Use url as name when Page Title is left empty
68 |
69 | # 1.1.0
70 | ### Features
71 |
72 | * #8 - Hide handle when only one item.
73 |
74 | ### Bugfixes
75 |
76 | * #11 - Validation problem with min/max and external links.
77 | * #13 - Gracefully handle when the recycling bin is chosen
78 |
79 | # 1.0.0
80 | ### Features
81 |
82 | * Added min/max items field
83 | * Added document icons to the editor
84 |
85 | ### Breaking
86 |
87 | * Upgraded to Umbraco v7.1.2
88 | * Removed Pick multiple items field
89 |
90 | # 0.2.0
91 | ### Features
92 |
93 | * Added property value converter
94 |
95 | # 0.1.1
96 | ### Bugfixes
97 |
98 | * Data is now stored in the ntext column
99 |
100 | # 0.1.0
101 |
102 | * Initial release
103 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Rasmus John Pedersen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multi Url Picker for Umbraco 7
2 |
3 | [](https://www.nuget.org/packages/RJP.UmbracoMultiUrlPicker)
4 | [](https://our.umbraco.org/projects/backoffice-extensions/multi-url-picker)
5 |
6 | Allows editors to pick and sort multiple urls, it uses Umbraco's link picker which supports internal and external links and media.
7 |
8 | ## Installation
9 |
10 | Install the NuGet [package](https://www.nuget.org/packages/RJP.UmbracoMultiUrlPicker).
11 |
12 | or
13 |
14 | Install the [package](http://our.umbraco.org/projects/backoffice-extensions/multi-url-picker) from the Umbraco package repository.
15 |
16 | ## Usage
17 |
18 | Add a new property to your document type and select the `Multi Url Picker` property editor in the `pickers` category.
19 |
20 | If you're using the models builder, you can access the property on your model e.g. `Model.Links` if your property alias is `links`.
21 |
22 | ```csharp
23 | @{ var links = Model.Links.ToList(); }
24 |
25 | @if (links.Count > 0)
26 | {
27 |
28 | @foreach (var item in links)
29 | {
30 | @item.Name
31 | }
32 |
33 | }
34 | ```
35 |
36 | If `Max number of items` is configured to 1
37 |
38 | ```csharp
39 | @if(Model.Link != null)
40 | {
41 | @Model.Link.Name
42 | }
43 |
44 | ```
45 |
46 | ## Changelog
47 | See the [changelog here](CHANGELOG.md)
48 |
--------------------------------------------------------------------------------
/RJP.MultiUrlPicker.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{F960CEAB-2C15-4617-9BA9-68E8CEBD9920}"
7 | ProjectSection(SolutionItems) = preProject
8 | CHANGELOG.md = CHANGELOG.md
9 | LICENSE = LICENSE
10 | README.md = README.md
11 | EndProjectSection
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RJP.MultiUrlPicker", "src\RJP.MultiUrlPicker\RJP.MultiUrlPicker.csproj", "{2553C499-3593-43ED-95D6-39B03CC61ED1}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {2553C499-3593-43ED-95D6-39B03CC61ED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {2553C499-3593-43ED-95D6-39B03CC61ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {2553C499-3593-43ED-95D6-39B03CC61ED1}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {2553C499-3593-43ED-95D6-39B03CC61ED1}.Release|Any CPU.Build.0 = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(SolutionProperties) = preSolution
27 | HideSolutionNode = FALSE
28 | EndGlobalSection
29 | EndGlobal
30 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | cls
3 | if not exist ".\.nuget" (
4 | mkdir ".\.nuget"
5 | attrib +h ".\.nuget" /s /d
6 | )
7 | SET "release="
8 | SET "buildVersion="
9 | FOR /F %%i IN (build/version.txt) DO IF NOT DEFINED release SET "release=%%i"
10 | if defined APPVEYOR_BUILD_NUMBER (
11 | SET "buildVersion=build%APPVEYOR_BUILD_NUMBER%"
12 | ) else (
13 | FOR /F "skip=1" %%i IN (build/version.txt) DO IF NOT DEFINED buildVersion SET "buildVersion=%%i"
14 | )
15 |
16 | if not exist ".\.nuget\nuget.exe" powershell -Command "Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile .\.nuget\nuget.exe"
17 | if not exist ".\packages\Fake" ".nuget\NuGet.exe" "Install" "FAKE" "-OutputDirectory" "packages" "-ExcludeVersion"
18 | "packages\FAKE\tools\Fake.exe" build.fsx "%*" -ev release=%release% -ev buildVersion=%buildVersion%
19 | exit /b %errorlevel%
20 |
--------------------------------------------------------------------------------
/build.fsx:
--------------------------------------------------------------------------------
1 | // include Fake lib
2 | #r @"packages/FAKE/tools/FakeLib.dll"
3 | open Fake
4 |
5 | // Properties
6 | let solutionFile = FindFirstMatchingFile "*.sln" currentDirectory
7 | let csprojFiles = filesInDirMatchingRecursive "*.csproj" (directoryInfo "./src/")
8 | let nuspecFiles = filesInDirMatching "*.nuspec" (directoryInfo "./build/nuspec/")
9 | let packageFiles =
10 | subDirectories (directoryInfo "./src")
11 | |> Seq.map (filesInDirMatching "package.json")
12 | |> Seq.concat
13 |
14 | let version = environVarOrFail "release"
15 | let informationalVersion =
16 | if hasBuildParam "buildVersion" then sprintf "%s-%s" version (environVar "buildVersion")
17 | else version
18 |
19 | let nugetPath = FullName "./.nuget/nuget.exe"
20 | let assemblyInfos = !! "./src/**/AssemblyInfo.cs"
21 |
22 | let artifactsDir = FullName "./artifacts/"
23 | let appPluginsDir = artifactsDir @@ "App_Plugins" @@ (fileNameWithoutExt solutionFile)
24 |
25 | let Exec command args workingDir =
26 | let result = Shell.Exec(command, args, workingDir)
27 | if result <> 0 then failwithf "%s exited with error %d" command result
28 |
29 | let yarnOrNpm =
30 | match tryFindFileOnPath "yarnpkg.cmd" with
31 | | Some path -> path
32 | | None ->
33 | match tryFindFileOnPath "npm.cmd" with
34 | | Some path -> path
35 | | None -> failwith "yarn or npm could not be found"
36 |
37 | // Targets
38 | Target "Clean" (fun _ ->
39 | CleanDirs [artifactsDir]
40 | )
41 |
42 | Target "RestorePackages" (fun _ ->
43 | solutionFile
44 | |> RestoreMSSolutionPackages(fun p ->
45 | { p with
46 | Retries = 4 })
47 | )
48 |
49 | Target "RestoreUiPackages" (fun _ ->
50 | packageFiles
51 | |> Seq.iter(fun f -> Exec yarnOrNpm "install" f.DirectoryName)
52 | )
53 |
54 | Target "AssemblyInfo" (fun _ ->
55 | ReplaceAssemblyInfoVersionsBulk assemblyInfos (fun f ->
56 | { f with
57 | AssemblyVersion = sprintf "%s.*" version
58 | AssemblyInformationalVersion = informationalVersion })
59 | )
60 |
61 | Target "Build" (fun _ ->
62 | csprojFiles
63 | |> Seq.iter (fun path ->
64 | let buildDir = artifactsDir @@ path.Directory.Name
65 | !! path.FullName
66 | |> MSBuildRelease buildDir "Build"
67 | |> ignore
68 | )
69 | )
70 |
71 | Target "BuildUI" (fun _ ->
72 | packageFiles
73 | |> Seq.iter(fun f -> Exec yarnOrNpm (sprintf "run build -- --output-path %s" appPluginsDir) f.DirectoryName)
74 | )
75 |
76 | Target "Package" (fun _ ->
77 | nuspecFiles
78 | |> Seq.iter (fun path ->
79 | path.FullName
80 | |> NuGetPackDirectly (fun p ->
81 | { p with
82 | ToolPath = nugetPath
83 | WorkingDir = currentDirectory
84 | OutputPath = artifactsDir
85 | Version = informationalVersion }))
86 | )
87 |
88 | Target "Default" DoNothing
89 |
90 | // Dependencies
91 | "Clean"
92 | ==> "RestorePackages" <=> "RestoreUiPackages" <=> "AssemblyInfo"
93 | ==> "Build" <=> "BuildUI"
94 | ==> "Default"
95 |
96 | "Build" <=> "BuildUi"
97 | ==> "Package"
98 |
99 | // start build
100 | RunTargetOrDefault "Default"
101 |
--------------------------------------------------------------------------------
/build/nuspec/RJP.UmbracoMultiUrlPicker.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RJP.UmbracoMultiUrlPicker
5 | $version$
6 | Umbraco Multi Url Picker
7 | Rasmus John Pedersen
8 | Rasmus John Pedersen
9 | https://github.com/rasmusjp/umbraco-multi-url-picker/raw/master/LICENSE
10 | https://github.com/rasmusjp/umbraco-multi-url-picker
11 | false
12 | Allows editors to pick and sort multiple urls, it uses Umbraco's link picker which supports internal and external links and media.
13 |
14 | Copyright 2014 Rasmus John Pedersen
15 | umbraco, urlpicker, multiurlpicker
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/build/version.txt:
--------------------------------------------------------------------------------
1 | 2.2.1
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker.Web.UI/MultiUrlPicker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 |
22 | Add
23 |
24 |
25 |
26 |
27 |
28 |
29 | Add between {{model.config.minNumberOfItems}} and {{model.config.maxNumberOfItems}} items
30 |
31 | You can only have {{model.config.maxNumberOfItems}} items selected
32 |
33 |
34 |
35 |
36 |
37 | Add {{model.config.minNumberOfItems - ctrl.renderModel.length}} item(s)
38 |
39 | You can only have {{model.config.maxNumberOfItems}} items selected
40 |
41 |
42 |
43 |
44 |
45 | Add up to {{model.config.maxNumberOfItems}} items
46 |
47 | You can only have {{model.config.maxNumberOfItems}} items selected
48 |
49 |
50 |
51 |
52 |
53 | Add at least {{model.config.minNumberOfItems}} item(s)
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | You need to add at least {{model.config.minNumberOfItems}} items
64 |
65 |
66 | You can only have {{model.config.maxNumberOfItems}} items selected
67 |
68 |
69 |
70 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker.Web.UI/MultiUrlPicker.js:
--------------------------------------------------------------------------------
1 | ; (function () {
2 | 'use strict'
3 |
4 | var MultiUrlPickerController = function ($scope, angularHelper, entityResource, iconHelper) {
5 | this.renderModel = []
6 |
7 | if ($scope.preview) {
8 | return
9 | }
10 |
11 | if (!Array.isArray($scope.model.value)) {
12 | $scope.model.value = []
13 | }
14 |
15 | $scope.model.value.forEach(function (link) {
16 | link.icon = iconHelper.convertFromLegacyIcon(link.icon)
17 | this.renderModel.push(link)
18 | }.bind(this))
19 |
20 | $scope.$on('formSubmitting', function () {
21 | $scope.model.value = this.renderModel
22 | }.bind(this))
23 |
24 | $scope.$watch(function () {
25 | return this.renderModel.length
26 | }.bind(this), function () {
27 | if ($scope.model.config && $scope.model.config.minNumberOfItems) {
28 | $scope.multiUrlPickerForm.minCount.$setValidity('minCount', +$scope.model.config.minNumberOfItems <= this.renderModel.length)
29 | }
30 | if ($scope.model.config && $scope.model.config.maxNumberOfItems) {
31 | $scope.multiUrlPickerForm.maxCount.$setValidity('maxCount', +$scope.model.config.maxNumberOfItems >= this.renderModel.length)
32 | }
33 | this.sortableOptions.disabled = this.renderModel.length === 1
34 | }.bind(this))
35 |
36 | this.sortableOptions = {
37 | distance: 10,
38 | tolerance: 'pointer',
39 | scroll: true,
40 | zIndex: 6000,
41 | update: function () {
42 | angularHelper.getCurrentForm($scope).$setDirty()
43 | }
44 | }
45 |
46 | this.remove = function ($index) {
47 | this.renderModel.splice($index, 1)
48 |
49 | angularHelper.getCurrentForm($scope).$setDirty()
50 | }
51 |
52 | this.openLinkPicker = function (link, $index) {
53 | var target = link ? {
54 | name: link.name,
55 | // the linkPicker breaks if it get an id or udi for media
56 | id: link.isMedia ? null : link.id,
57 | udi: link.isMedia ? null : link.udi,
58 | url: link.url || '',
59 | anchor: link.querystring,
60 | target: link.target
61 | } : null
62 |
63 | // 7.12.0 and 7.12.1 removes the url in the linkPicker if there's no # or ? at the end
64 | // Hopefully this PR https://github.com/umbraco/Umbraco-CMS/pull/2868 will be merged in 7.12.2
65 | if (link && link.url && (Umbraco.Sys.ServerVariables.application.version == '7.12.0' || Umbraco.Sys.ServerVariables.application.version == '7.12.1')) {
66 | target.url = target.url + '#'
67 | }
68 |
69 | this.linkPickerOverlay = {
70 | view: 'linkpicker',
71 | currentTarget: target,
72 | show: true,
73 | hideQuerystring: $scope.model.config.hideQuerystring === '1',
74 | hideTarget: $scope.model.config.hideTarget === '1',
75 | submit: function (model) {
76 | // Parse query string: add missing ? or truncate to null
77 | var querystring = model.target.anchor
78 | if (querystring) {
79 | if (querystring[0] !== '?' && querystring[0] !== '#') querystring = '?' + querystring
80 | if (querystring.length === 1) querystring = null
81 | }
82 |
83 | if (model.target.url || querystring) {
84 | if (link) {
85 | if (link.isMedia && link.url === model.target.url) {
86 | // we can assume the existing media item is changed and no new file has been selected
87 | // so we don't need to update the id, udi and isMedia fields
88 | } else {
89 | link.id = model.target.id
90 | link.udi = model.target.udi
91 | link.isMedia = model.target.isMedia
92 | }
93 |
94 | link.name = model.target.name || model.target.url || querystring
95 | link.target = model.target.target
96 | link.url = model.target.url || ''
97 | link.querystring = querystring
98 | } else {
99 | link = {
100 | id: model.target.id,
101 | isMedia: model.target.isMedia,
102 | name: model.target.name || model.target.url || querystring,
103 | target: model.target.target,
104 | udi: model.target.udi,
105 | url: model.target.url,
106 | querystring: querystring
107 | }
108 | this.renderModel.push(link)
109 | }
110 |
111 | if (link.udi) {
112 | var entityType = link.isMedia ? 'media' : 'document'
113 |
114 | entityResource.getById(link.udi, entityType)
115 | .then(function (data) {
116 | link.icon = iconHelper.convertFromLegacyIcon(data.icon)
117 | link.published = !(data.metaData && data.metaData.IsPublished === false && entityType === 'document')
118 | })
119 | } else {
120 | link.published = true
121 | link.icon = 'icon-link'
122 | }
123 |
124 | angularHelper.getCurrentForm($scope).$setDirty()
125 | }
126 |
127 | this.linkPickerOverlay.show = false
128 | this.linkPickerOverlay = null
129 | }.bind(this)
130 | }
131 | }
132 | }
133 |
134 | var mupHttpProvider = function ($httpProvider) {
135 | $httpProvider.interceptors.push(function ($q) {
136 | return {
137 | response: function (response) {
138 | if (response.config.url.indexOf('views/common/overlays/linkpicker/linkpicker.html') !== -1) {
139 | // Inject the querystring field
140 | var $markup = $(response.data)
141 | var $anchorField = $markup.find('[label="@defaultdialogs_anchorLinkPicker"]')
142 | // Umbraco 7.12 added it's own anchor/querystring field, so only inject if it doesn't exist
143 | if (!$anchorField.length) {
144 | var $urlField = $markup.find('[label="@defaultdialogs_urlLinkPicker"],[label="@content_urls"]')
145 | $urlField.after(' ')
146 | response.data = $markup[0]
147 | }
148 | }
149 | return response
150 | }
151 | }
152 | })
153 | }
154 |
155 | angular.module('umbraco').controller('RJP.MultiUrlPickerController', MultiUrlPickerController)
156 | .config(['$httpProvider', mupHttpProvider])
157 | })()
158 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Information.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker
2 | {
3 | using System;
4 | using System.Reflection;
5 |
6 | internal static class Information
7 | {
8 | private static readonly Lazy _version;
9 |
10 | static Information()
11 | {
12 | _version = new Lazy(() => Assembly.GetExecutingAssembly().GetName().Version);
13 | }
14 |
15 | public static Version Version
16 | {
17 | get
18 | {
19 | return _version.Value;
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Models/Link.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker.Models
2 | {
3 | using System;
4 |
5 | using Newtonsoft.Json.Linq;
6 |
7 | using Umbraco.Core.Models;
8 | using Umbraco.Core;
9 |
10 | using Umbraco.Web;
11 | using Umbraco.Web.Extensions;
12 | using Umbraco.Core.Models.PublishedContent;
13 |
14 | public class Link
15 | {
16 | private readonly JToken _linkItem;
17 | private bool _publishedContentInitialized = false;
18 | private string _name;
19 | private string _url;
20 | private string _target;
21 | private bool? _deleted;
22 | private LinkType? _linkType;
23 | private IPublishedContent _content;
24 | private Udi _udi;
25 | private int? _id;
26 |
27 | public Link(JToken linkItem)
28 | {
29 | _linkItem = linkItem;
30 | }
31 |
32 | private IPublishedContent PublishedContent
33 | {
34 | get
35 | {
36 | InitPublishedContent();
37 | return _content;
38 | }
39 | }
40 |
41 | [Obsolete("Use Udi instead")]
42 | public int? Id
43 | {
44 | get
45 | {
46 | if (_id == null)
47 | {
48 | _id = _linkItem.Value("id");
49 | if (!_id.HasValue)
50 | {
51 | InitPublishedContent();
52 | }
53 | }
54 | return _id;
55 | }
56 |
57 | }
58 |
59 | public Udi Udi
60 | {
61 | get
62 | {
63 | if (_udi == null)
64 | {
65 | if (!Udi.TryParse(_linkItem.Value("udi"), out _udi))
66 | {
67 | InitPublishedContent();
68 | }
69 | }
70 | return _udi;
71 | }
72 | }
73 |
74 | public string Name
75 | {
76 | get
77 | {
78 | if (string.IsNullOrEmpty(_name))
79 | {
80 | _name = _linkItem.Value("name");
81 | }
82 | return _name;
83 | }
84 | }
85 |
86 | internal bool Deleted
87 | {
88 | get
89 | {
90 | if (_deleted == null)
91 | {
92 | if (Id.HasValue || Udi != null)
93 | {
94 | _deleted = PublishedContent == null;
95 | }
96 | else
97 | {
98 | _deleted = false;
99 | }
100 | }
101 | return (bool)_deleted;
102 | }
103 | }
104 |
105 | public string Url
106 | {
107 | get
108 | {
109 | if (string.IsNullOrEmpty(_url))
110 | {
111 | _url = PublishedContent?.Url ?? _linkItem.Value("url");
112 |
113 | var qs = _linkItem.Value("querystring");
114 | if (!string.IsNullOrWhiteSpace(qs))
115 | {
116 | _url += qs;
117 | }
118 | }
119 | return _url;
120 | }
121 | }
122 |
123 | public string Target
124 | {
125 | get
126 | {
127 | if (string.IsNullOrEmpty(_target))
128 | {
129 | _target = _linkItem.Value("target");
130 | }
131 | return _target == string.Empty ? null : _target;
132 | }
133 | }
134 |
135 | public LinkType Type
136 | {
137 | get
138 | {
139 | if (_linkType == null)
140 | {
141 | if (Udi != null)
142 | {
143 | if (Udi.EntityType == Constants.UdiEntityType.Media)
144 | {
145 | _linkType = LinkType.Media;
146 | }
147 | else
148 | {
149 | _linkType = LinkType.Content;
150 | }
151 | }
152 | else
153 | {
154 | _linkType = LinkType.External;
155 | }
156 | }
157 | return _linkType.Value;
158 | }
159 | }
160 |
161 |
162 | private void InitPublishedContent()
163 | {
164 | if (!_publishedContentInitialized)
165 | {
166 | _publishedContentInitialized = true;
167 |
168 | if (UmbracoContext.Current == null)
169 | {
170 | return;
171 | }
172 |
173 | if (Udi.TryParse(_linkItem.Value("udi"), out _udi))
174 | {
175 | _content = _udi.ToPublishedContent();
176 | _id = _content?.Id;
177 | }
178 | else
179 | {
180 | // there were no Udi so let's try the legacy way
181 | _id = _linkItem.Value("id");
182 | if (_id.HasValue)
183 | {
184 | var helper = new UmbracoHelper(UmbracoContext.Current);
185 |
186 | if (_linkItem.Value("isMedia"))
187 | {
188 | _content = helper.TypedMedia(_id.Value);
189 | }
190 | else
191 | {
192 | _content = helper.TypedContent(_id.Value);
193 | }
194 |
195 | SetUdi();
196 | }
197 | }
198 | }
199 | }
200 |
201 | private void SetUdi()
202 | {
203 | if (_content != null && _udi == null)
204 | {
205 | Guid? key = _content.GetKey();
206 | if (key == Guid.Empty)
207 | {
208 | // if the key is Guid.Empty the model might be created by the ModelsBuilder,
209 | // if so it, by default, derives from PublishedContentModel.
210 | // By calling UnWrap() we get the original content, which probably implements
211 | // IPublishedContentWithKey, so we can get the key
212 | key = (_content as PublishedContentWrapped)?.Unwrap().GetKey();
213 | }
214 |
215 | if (key.HasValue && key != Guid.Empty)
216 | {
217 | string udiType = _content.ItemType == PublishedItemType.Media ?
218 | Constants.UdiEntityType.Media :
219 | Constants.UdiEntityType.Document;
220 |
221 | _udi = Udi.Create(udiType, key.Value);
222 | }
223 | }
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Models/LinkDisplay.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker.Models
2 | {
3 | using System.Runtime.Serialization;
4 |
5 | using Umbraco.Core;
6 |
7 | [DataContract]
8 | internal class LinkDisplay
9 | {
10 | [DataMember(Name = "icon")]
11 | public string Icon { get; set; }
12 |
13 | [DataMember(Name = "id")]
14 | public int? Id { get; set; }
15 |
16 | [DataMember(Name = "isMedia")]
17 | public bool IsMedia { get; set; }
18 |
19 | [DataMember(Name = "name")]
20 | public string Name { get; set; }
21 |
22 | [DataMember(Name = "published")]
23 | public bool Published { get; set; }
24 |
25 | [DataMember(Name = "target")]
26 | public string Target { get; set; }
27 |
28 | [DataMember(Name = "udi")]
29 | public GuidUdi Udi { get; set; }
30 |
31 | [DataMember(Name = "url")]
32 | public string Url { get; set; }
33 |
34 | [DataMember(Name = "querystring")]
35 | public string Querystring { get; set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Models/LinkDto.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker.Models
2 | {
3 | using System.Runtime.Serialization;
4 |
5 | using Umbraco.Core;
6 |
7 | [DataContract]
8 | internal class LinkDto
9 | {
10 | [DataMember(Name = "icon")]
11 | public string Icon { get; set; }
12 |
13 | [DataMember(Name = "id")]
14 | public int? Id { get; set; }
15 |
16 | [DataMember(Name = "isMedia")]
17 | public bool? IsMedia { get; set; }
18 |
19 | [DataMember(Name = "name")]
20 | public string Name { get; set; }
21 |
22 | [DataMember(Name = "target")]
23 | public string Target { get; set; }
24 |
25 | [DataMember(Name = "udi")]
26 | public GuidUdi Udi { get; set; }
27 |
28 | [DataMember(Name = "url")]
29 | public string Url { get; set; }
30 |
31 | [DataMember(Name = "querystring")]
32 | public string Querystring { get; set; }
33 |
34 | internal bool Published { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Models/LinkType.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker.Models
2 | {
3 | public enum LinkType
4 | {
5 | Content,
6 | Media,
7 | External
8 | }
9 | }
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Models/MultiUrls.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker.Models
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | using Newtonsoft.Json;
8 | using Newtonsoft.Json.Linq;
9 |
10 | using Umbraco.Core.Logging;
11 |
12 | [Obsolete("Use IEnumerable instead")]
13 | public class MultiUrls : IEnumerable
14 | {
15 | private readonly string _propertyData;
16 | private readonly List _multiUrls = new List ();
17 |
18 | internal MultiUrls()
19 | {
20 | }
21 |
22 | internal MultiUrls(JArray propertyData)
23 | {
24 | _propertyData = propertyData.ToString();
25 |
26 | Initialize(propertyData);
27 | }
28 |
29 | public MultiUrls(string propertyData)
30 | {
31 | _propertyData = propertyData;
32 |
33 | if (!string.IsNullOrEmpty(propertyData))
34 | {
35 | var relatedLinks = JsonConvert.DeserializeObject(propertyData);
36 | Initialize(relatedLinks);
37 | }
38 | }
39 |
40 | private void Initialize(JArray data)
41 | {
42 | foreach (var item in data)
43 | {
44 | var newLink = new Link(item);
45 | if (!newLink.Deleted)
46 | {
47 | _multiUrls.Add(new Link(item));
48 | }
49 | else
50 | {
51 | LogHelper.Warn(
52 | string.Format("MultiUrlPicker value converter skipped a link as the node has been unpublished/deleted (Id: {0}), ", newLink.Id));
53 | }
54 | }
55 | }
56 |
57 | public string PropertyData
58 | {
59 | get
60 | {
61 | return _propertyData;
62 | }
63 | }
64 |
65 | // Although this method seems unnecessary it makes .Any() available in Dynamics
66 | public bool Any()
67 | {
68 | return Enumerable.Any(this);
69 | }
70 |
71 | public IEnumerator GetEnumerator()
72 | {
73 | return _multiUrls.GetEnumerator();
74 | }
75 |
76 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
77 | {
78 | return GetEnumerator();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/MultiUrlPickerPropertyEditor.cs:
--------------------------------------------------------------------------------
1 | namespace RJP.MultiUrlPicker
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | using ClientDependency.Core;
8 |
9 | using Newtonsoft.Json;
10 |
11 | using Umbraco.Core;
12 | using Umbraco.Core.Logging;
13 | using Umbraco.Core.Models;
14 | using Umbraco.Core.Models.Editors;
15 | using Umbraco.Core.Models.EntityBase;
16 | using Umbraco.Core.PropertyEditors;
17 | using Umbraco.Core.Services;
18 | using Umbraco.Web;
19 | using Umbraco.Web.PropertyEditors;
20 |
21 | using Models;
22 |
23 | using Constants = Umbraco.Core.Constants;
24 | using Umbraco.Core.Configuration;
25 |
26 | [PropertyEditorAsset(ClientDependencyType.Javascript, "~/App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.js")]
27 | [PropertyEditor("RJP.MultiUrlPicker", "Multi Url Picker", "JSON",
28 | "~/App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.html",
29 | Group = "pickers", Icon = "icon-link", IsParameterEditor = true)]
30 | public class MultiUrlPickerPropertyEditor : PropertyEditor
31 | {
32 | private IDictionary _defaultPreValues;
33 |
34 | public MultiUrlPickerPropertyEditor()
35 | {
36 | _defaultPreValues = new Dictionary
37 | {
38 | {"minNumberOfItems", null},
39 | {"maxNumberOfItems", null},
40 | {"hideQuerystring", "0"},
41 | {"hideTarget", "0"},
42 | {"version", Information.Version.ToString(3)},
43 | };
44 | }
45 |
46 | public override IDictionary DefaultPreValues
47 | {
48 | get { return _defaultPreValues; }
49 | set { _defaultPreValues = value; }
50 | }
51 |
52 | protected override PreValueEditor CreatePreValueEditor()
53 | {
54 | return new MultiUrlPickerPreValueEditor();
55 | }
56 |
57 | protected override PropertyValueEditor CreateValueEditor()
58 | {
59 | return new MultiUrlPickerPropertyValueEditor(base.CreateValueEditor());
60 | }
61 |
62 | private class MultiUrlPickerPreValueEditor : PreValueEditor
63 | {
64 | public MultiUrlPickerPreValueEditor()
65 | {
66 | Fields.AddRange(new[]
67 | {
68 | new PreValueField
69 | {
70 | Key = "minNumberOfItems",
71 | Name = "Min number of items",
72 | View = "number"
73 | },
74 | new PreValueField
75 | {
76 | Key = "maxNumberOfItems",
77 | Name = "Max number of items",
78 | View = "number"
79 | },
80 | new PreValueField
81 | {
82 | Key = "hideTarget",
83 | Name = "Hide target/open in new window or tab checkbox",
84 | View = "boolean"
85 | },
86 | new PreValueField
87 | {
88 | Key = "version",
89 | Name = "Multi Url Picker version",
90 | View = "hidden",
91 | HideLabel = true
92 | }
93 | });
94 |
95 | if (UmbracoVersion.Current < new Version(7, 12, 0))
96 | {
97 | // Umbraco 7.12 has it's own query string field which is not possible to hide at the time of writing
98 | // so only add the field in older versions
99 | Fields.Insert(2, new PreValueField
100 | {
101 | Key = "hideQuerystring",
102 | Name = "Hide query string input",
103 | View = "boolean"
104 | });
105 | }
106 | }
107 |
108 | public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals)
109 | {
110 | // if there isn't a version stored set it to 0 for backwards compatibility
111 | if (!persistedPreVals.PreValuesAsDictionary.ContainsKey("version"))
112 | {
113 | persistedPreVals.PreValuesAsDictionary["version"] = new PreValue("0");
114 | }
115 |
116 | return base.ConvertDbToEditor(defaultPreVals, persistedPreVals);
117 | }
118 | }
119 |
120 | private class MultiUrlPickerPropertyValueEditor : PropertyValueEditorWrapper
121 | {
122 | public MultiUrlPickerPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped)
123 | {
124 | }
125 |
126 | public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
127 | {
128 | string value = property.Value?.ToString();
129 |
130 | if (string.IsNullOrEmpty(value))
131 | {
132 | return Enumerable.Empty();
133 | }
134 |
135 | try
136 | {
137 | ServiceContext services = ApplicationContext.Current.Services;
138 | IEntityService entityService = services.EntityService;
139 | IContentTypeService contentTypeService = services.ContentTypeService;
140 |
141 | var links = JsonConvert.DeserializeObject>(value);
142 |
143 | var documentLinks = links.FindAll(link =>
144 | link.Id.HasValue && false == link.IsMedia.GetValueOrDefault() ||
145 | link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Document
146 | );
147 |
148 | var mediaLinks = links.FindAll(link =>
149 | link.Id.HasValue && true == link.IsMedia.GetValueOrDefault() ||
150 | link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Media
151 | );
152 |
153 | List entities = new List();
154 | if (documentLinks.Count > 0)
155 | {
156 | if (documentLinks[0].Id.HasValue)
157 | {
158 | entities.AddRange(
159 | entityService.GetAll(UmbracoObjectTypes.Document, documentLinks.Select(link => link.Id.Value).ToArray())
160 | );
161 | }
162 | else
163 | {
164 | entities.AddRange(
165 | entityService.GetAll(UmbracoObjectTypes.Document, documentLinks.Select(link => link.Udi.Guid).ToArray())
166 | );
167 | }
168 | }
169 |
170 | if (mediaLinks.Count > 0)
171 | {
172 | if (mediaLinks[0].Id.HasValue)
173 | {
174 | entities.AddRange(
175 | entityService.GetAll(UmbracoObjectTypes.Media, mediaLinks.Select(link => link.Id.Value).ToArray())
176 | );
177 | }
178 | else
179 | {
180 | entities.AddRange(
181 | entityService.GetAll(UmbracoObjectTypes.Media, mediaLinks.Select(link => link.Udi.Guid).ToArray())
182 | );
183 | }
184 | }
185 |
186 | var result = new List();
187 | foreach (LinkDto dto in links)
188 | {
189 | if (dto.Id.HasValue || dto.Udi != null)
190 | {
191 | IUmbracoEntity entity = entities.Find(e => e.Key == dto.Udi?.Guid || e.Id == dto.Id);
192 | if (entity == null)
193 | {
194 | continue;
195 | }
196 |
197 | string entityType = Equals(entity.AdditionalData["NodeObjectTypeId"], Constants.ObjectTypes.MediaGuid) ?
198 | Constants.UdiEntityType.Media :
199 | Constants.UdiEntityType.Document;
200 |
201 | var udi = new GuidUdi(entityType, entity.Key);
202 |
203 | string contentTypeAlias = entity.AdditionalData["ContentTypeAlias"] as string;
204 | string icon;
205 | bool isMedia = false;
206 | bool published = Equals(entity.AdditionalData["IsPublished"], true);
207 | string url = dto.Url;
208 |
209 | if (string.IsNullOrEmpty(contentTypeAlias))
210 | {
211 | continue;
212 | }
213 |
214 | if (udi.EntityType == Constants.UdiEntityType.Document)
215 | {
216 | IContentType contentType = contentTypeService.GetContentType(contentTypeAlias);
217 |
218 | if (contentType == null)
219 | {
220 | continue;
221 | }
222 |
223 | icon = contentType.Icon;
224 |
225 | if (UmbracoContext.Current != null)
226 | {
227 | url = UmbracoContext.Current.UrlProvider.GetUrl(entity.Id);
228 | }
229 | }
230 | else
231 | {
232 | IMediaType mediaType = contentTypeService.GetMediaType(contentTypeAlias);
233 |
234 | if (mediaType == null)
235 | {
236 | continue;
237 | }
238 |
239 | icon = mediaType.Icon;
240 | isMedia = true;
241 | published = true;
242 |
243 | if (UmbracoContext.Current != null)
244 | {
245 | url = UmbracoContext.Current.MediaCache.GetById(entity.Id)?.Url;
246 | }
247 | }
248 |
249 | result.Add(new LinkDisplay
250 | {
251 | Icon = icon,
252 | Id = entity.Id,
253 | IsMedia = isMedia,
254 | Name = dto.Name,
255 | Target = dto.Target,
256 | Published = published,
257 | Udi = udi,
258 | Url = url,
259 | Querystring = dto.Querystring
260 | });
261 | }
262 | else
263 | {
264 | result.Add(new LinkDisplay
265 | {
266 | Icon = "icon-link",
267 | Name = dto.Name,
268 | Published = true,
269 | Target = dto.Target,
270 | Url = dto.Url,
271 | Querystring = dto.Querystring
272 | });
273 | }
274 | }
275 | return result;
276 | }
277 | catch (Exception ex)
278 | {
279 | ApplicationContext.Current.ProfilingLogger.Logger.Error("Error getting links", ex);
280 | }
281 |
282 | return base.ConvertDbToEditor(property, propertyType, dataTypeService);
283 | }
284 |
285 | public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
286 | {
287 | string value = editorValue.Value?.ToString();
288 |
289 | if (string.IsNullOrEmpty(value))
290 | {
291 | return string.Empty;
292 | }
293 |
294 | try
295 | {
296 | return JsonConvert.SerializeObject(
297 | from link in JsonConvert.DeserializeObject>(value)
298 | select new LinkDto
299 | {
300 | Name = link.Name,
301 | Target = link.Target,
302 | Udi = link.Udi,
303 | Url = link.Udi == null ? link.Url : null, // only save the url for external links
304 | Querystring = link.Querystring
305 | },
306 | new JsonSerializerSettings
307 | {
308 | NullValueHandling = NullValueHandling.Ignore
309 | });
310 | }
311 | catch (Exception ex)
312 | {
313 | ApplicationContext.Current.ProfilingLogger.Logger.Error("Error saving links", ex);
314 | }
315 | return base.ConvertEditorToDb(editorValue, currentValue);
316 | }
317 | }
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/MultiUrlPickerValueConverter.cs:
--------------------------------------------------------------------------------
1 | using Umbraco.Core.Logging;
2 | using Umbraco.Core.PropertyEditors.ValueConverters;
3 |
4 | namespace RJP.MultiUrlPicker
5 | {
6 | using System;
7 | using System.Linq;
8 | using System.Collections.Generic;
9 |
10 | using Newtonsoft.Json.Linq;
11 |
12 | using Umbraco.Core;
13 | using Umbraco.Core.Models;
14 | using Umbraco.Core.Models.PublishedContent;
15 | using Umbraco.Core.PropertyEditors;
16 | using Umbraco.Core.Services;
17 |
18 | using Models;
19 |
20 | public class MultiUrlPickerValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta
21 | {
22 | private readonly IDataTypeService _dataTypeService;
23 |
24 | public MultiUrlPickerValueConverter(IDataTypeService dataTypeService)
25 | {
26 | _dataTypeService = dataTypeService;
27 | }
28 |
29 | public MultiUrlPickerValueConverter() : this(ApplicationContext.Current.Services.DataTypeService)
30 | {
31 | }
32 |
33 | public override bool IsConverter(PublishedPropertyType propertyType)
34 | {
35 | return propertyType.PropertyEditorAlias.Equals("RJP.MultiUrlPicker");
36 | }
37 |
38 | public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
39 | {
40 | if (string.IsNullOrWhiteSpace(source?.ToString()))
41 | {
42 | return null;
43 | }
44 |
45 | if (source.ToString().Trim().StartsWith("["))
46 | {
47 | try
48 | {
49 | return JArray.Parse(source.ToString());
50 | }
51 | catch (Exception ex)
52 | {
53 | LogHelper.Error("Error parsing JSON", ex);
54 | }
55 | }
56 | return null;
57 | }
58 |
59 | public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview)
60 | {
61 | bool isMultiple = IsMultipleDataType(propertyType.DataTypeId, out int maxNumberOfItems);
62 | if (source == null)
63 | {
64 | return isMultiple ? new MultiUrls() : null;
65 | }
66 |
67 | var urls = new MultiUrls((JArray)source);
68 | if(isMultiple)
69 | {
70 | if(maxNumberOfItems > 0)
71 | {
72 | return urls.Take(maxNumberOfItems);
73 | }
74 | return urls;
75 | }
76 | return urls.FirstOrDefault();
77 | }
78 |
79 | public Type GetPropertyValueType(PublishedPropertyType propertyType)
80 | {
81 | if (IsMultipleDataType(propertyType.DataTypeId, out int maxNumberOfItems))
82 | {
83 | return typeof(IEnumerable );
84 | }
85 | return typeof(Link);
86 | }
87 |
88 | public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue)
89 | {
90 | switch (cacheValue)
91 | {
92 | case PropertyCacheValue.Source:
93 | return PropertyCacheLevel.Content;
94 | case PropertyCacheValue.Object:
95 | case PropertyCacheValue.XPath:
96 | return PropertyCacheLevel.ContentCache;
97 | }
98 |
99 | return PropertyCacheLevel.None;
100 | }
101 |
102 | private bool IsMultipleDataType(int dataTypeId, out int maxNumberOfItems)
103 | {
104 | IDictionary preValues = _dataTypeService
105 | .GetPreValuesCollectionByDataTypeId(dataTypeId)
106 | .PreValuesAsDictionary;
107 |
108 | if (preValues.TryGetValue("maxNumberOfItems", out PreValue maxNumberOfItemsPreValue) &&
109 | int.TryParse(maxNumberOfItemsPreValue.Value, out maxNumberOfItems))
110 | {
111 | PreValue versionPreValue;
112 | Version version;
113 | // for backwards compatibility, always return true if version
114 | // is less than 2.0.0
115 | if (preValues.TryGetValue("version", out versionPreValue) &&
116 | Version.TryParse(versionPreValue.Value, out version)
117 | && version >= new Version(2, 0, 0))
118 | {
119 | return maxNumberOfItems != 1;
120 | }
121 | }
122 |
123 | maxNumberOfItems = 0;
124 | return true;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("RJP.MultiUrlPicker")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("RJP.MultiUrlPicker")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("d6b0bd2f-b2da-4b8f-a33d-d2a13161b766")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | [assembly: AssemblyVersion("2.2.0.*")]
35 | [assembly: AssemblyInformationalVersion("2.2.0")]
36 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/RJP.MultiUrlPicker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {2553C499-3593-43ED-95D6-39B03CC61ED1}
8 | Library
9 | Properties
10 | RJP.MultiUrlPicker
11 | RJP.MultiUrlPicker
12 | v4.5
13 | 512
14 |
15 | ..\
16 | true
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 | true
35 |
36 |
37 |
38 | ..\..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll
39 | True
40 |
41 |
42 | ..\..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll
43 | True
44 |
45 |
46 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\businesslogic.dll
47 | True
48 |
49 |
50 | ..\..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll
51 | True
52 |
53 |
54 | ..\..\packages\ClientDependency-Mvc5.1.8.0.0\lib\net45\ClientDependency.Core.Mvc.dll
55 | True
56 |
57 |
58 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\cms.dll
59 | True
60 |
61 |
62 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\controls.dll
63 | True
64 |
65 |
66 | ..\..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll
67 | False
68 |
69 |
70 | ..\..\packages\Examine.0.1.82\lib\net45\Examine.dll
71 | True
72 |
73 |
74 | ..\..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll
75 | True
76 |
77 |
78 | ..\..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll
79 | False
80 |
81 |
82 | ..\..\packages\ImageProcessor.2.5.3\lib\net45\ImageProcessor.dll
83 | True
84 |
85 |
86 | ..\..\packages\ImageProcessor.Web.4.8.3\lib\net45\ImageProcessor.Web.dll
87 | True
88 |
89 |
90 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\interfaces.dll
91 | True
92 |
93 |
94 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\log4net.dll
95 | True
96 |
97 |
98 | ..\..\packages\Log4Net.Async.2.0.4\lib\net40\Log4Net.Async.dll
99 | True
100 |
101 |
102 | False
103 | ..\..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll
104 | False
105 |
106 |
107 | ..\..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll
108 | True
109 |
110 |
111 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\Microsoft.ApplicationBlocks.Data.dll
112 | True
113 |
114 |
115 | ..\..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll
116 | True
117 |
118 |
119 | ..\..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll
120 | True
121 |
122 |
123 | ..\..\packages\Microsoft.AspNet.SignalR.Core.2.2.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll
124 | True
125 |
126 |
127 | ..\..\packages\Microsoft.IO.RecyclableMemoryStream.1.2.1\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll
128 | True
129 |
130 |
131 | ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
132 | True
133 |
134 |
135 | ..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll
136 | True
137 |
138 |
139 | ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll
140 | True
141 |
142 |
143 | ..\..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll
144 | True
145 |
146 |
147 | ..\..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll
148 | True
149 |
150 |
151 | False
152 | ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
153 |
154 |
155 | ..\..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll
156 | False
157 |
158 |
159 | ..\..\packages\MySql.Data.6.9.9\lib\net45\MySql.Data.dll
160 | True
161 |
162 |
163 | ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll
164 | True
165 |
166 |
167 | ..\..\packages\Owin.1.0\lib\net40\Owin.dll
168 | True
169 |
170 |
171 | ..\..\packages\Semver.2.0.4\lib\netstandard1.1\Semver.dll
172 | True
173 |
174 |
175 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\SQLCE4Umbraco.dll
176 | True
177 |
178 |
179 |
180 |
181 |
182 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\System.Data.SqlServerCe.dll
183 | True
184 |
185 |
186 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\System.Data.SqlServerCe.Entity.dll
187 | True
188 |
189 |
190 |
191 | ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
192 | True
193 |
194 |
195 |
196 | ..\..\packages\System.Threading.Tasks.Dataflow.4.7.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll
197 | True
198 |
199 |
200 |
201 | ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll
202 | True
203 |
204 |
205 | ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll
206 | True
207 |
208 |
209 | ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll
210 | True
211 |
212 |
213 | ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
214 | True
215 |
216 |
217 | ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
218 | True
219 |
220 |
221 | ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
222 | True
223 |
224 |
225 | ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll
226 | True
227 |
228 |
229 | ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll
230 | True
231 |
232 |
233 |
234 |
235 | False
236 |
237 |
238 |
239 |
240 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\TidyNet.dll
241 | True
242 |
243 |
244 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\umbraco.dll
245 | True
246 |
247 |
248 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\Umbraco.Core.dll
249 | True
250 |
251 |
252 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\umbraco.DataLayer.dll
253 | True
254 |
255 |
256 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\umbraco.editorControls.dll
257 | True
258 |
259 |
260 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\umbraco.MacroEngines.dll
261 | True
262 |
263 |
264 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\umbraco.providers.dll
265 | True
266 |
267 |
268 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\Umbraco.Web.UI.dll
269 | True
270 |
271 |
272 | ..\..\packages\UmbracoCms.Core.7.6.0\lib\net45\UmbracoExamine.dll
273 | True
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
303 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/RJP.MultiUrlPicker/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------