├── .gitignore ├── AssetBundle.php ├── Module.php ├── README.md ├── assets ├── css │ ├── style.css │ └── style.less ├── images │ └── preload.gif └── js │ ├── fancybox │ ├── .gitattributes │ ├── CHANGELOG.md │ ├── README.md │ ├── demo │ │ ├── 1_b.jpg │ │ ├── 1_s.jpg │ │ ├── 2_b.jpg │ │ ├── 2_s.jpg │ │ ├── 3_b.jpg │ │ ├── 3_s.jpg │ │ ├── 4_b.jpg │ │ ├── 4_s.jpg │ │ ├── 5_b.jpg │ │ ├── 5_s.jpg │ │ ├── ajax.txt │ │ ├── iframe.html │ │ └── index.html │ ├── lib │ │ ├── jquery-1.10.1.min.js │ │ ├── jquery-1.9.0.min.js │ │ └── jquery.mousewheel-3.0.6.pack.js │ ├── source │ │ ├── blank.gif │ │ ├── fancybox_loading.gif │ │ ├── fancybox_loading@2x.gif │ │ ├── fancybox_overlay.png │ │ ├── fancybox_sprite.png │ │ ├── fancybox_sprite@2x.png │ │ ├── helpers │ │ │ ├── fancybox_buttons.png │ │ │ ├── jquery.fancybox-buttons.css │ │ │ ├── jquery.fancybox-buttons.js │ │ │ ├── jquery.fancybox-media.js │ │ │ ├── jquery.fancybox-thumbs.css │ │ │ └── jquery.fancybox-thumbs.js │ │ ├── jquery.fancybox.css │ │ ├── jquery.fancybox.js │ │ └── jquery.fancybox.pack.js │ └── sprite.psd │ └── sortable │ ├── .editorconfig │ ├── .gitignore │ ├── .jshintrc │ ├── CONTRIBUTING.md │ ├── Gruntfile.js │ ├── README.md │ ├── Sortable.js │ ├── Sortable.min.js │ ├── bower.json │ ├── component.json │ ├── index.html │ ├── jquery.binding.js │ ├── knockout-sortable.js │ ├── meteor │ ├── README.md │ ├── example │ │ ├── .meteor │ │ │ ├── .gitignore │ │ │ ├── packages │ │ │ ├── release │ │ │ └── versions │ │ ├── README.md │ │ ├── client │ │ │ ├── define-object-type.css │ │ │ ├── define-object-type.html │ │ │ └── define-object-type.js │ │ ├── model.js │ │ ├── package.json │ │ ├── packages │ │ │ └── Sortable │ │ ├── run.bat │ │ ├── run.sh │ │ └── server │ │ │ └── fixtures.js │ ├── methods.js │ ├── package.js │ ├── publish.sh │ ├── reactivize.js │ ├── runtests.sh │ ├── template.html │ └── test.js │ ├── ng-sortable.js │ ├── package.json │ ├── react-sortable-mixin.js │ └── st │ ├── app.css │ ├── app.js │ ├── face-01.jpg │ ├── face-02.jpg │ ├── face-03.jpg │ ├── face-04.jpg │ ├── face-05.jpg │ ├── face-06.jpg │ ├── face-07.jpg │ ├── face-08.jpg │ ├── face-09.jpg │ ├── iframe │ ├── frame.html │ └── index.html │ ├── logo.png │ └── og-image.png ├── composer.json ├── controllers └── GalleryController.php ├── messages ├── en │ └── default.php ├── ru │ └── default.php └── uk │ └── default.php ├── migrations └── m150407_084217_gallery.php ├── models ├── Gallery.php ├── GalleryFile.php ├── GallerySearch.php └── Query.php ├── views └── gallery │ ├── _form.php │ ├── _image.php │ ├── create.php │ ├── index.php │ ├── update.php │ └── view.php └── widgets └── Gallery.php /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /AssetBundle.php: -------------------------------------------------------------------------------- 1 | sourcePath = __DIR__ . '/assets'; 37 | 38 | parent::init(); 39 | } 40 | } -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | registerTranslations(); 41 | } 42 | 43 | public function registerTranslations() 44 | { 45 | Yii::$app->i18n->translations['sadovojav/gallery/*'] = [ 46 | 'class' => 'yii\i18n\PhpMessageSource', 47 | 'sourceLanguage' => 'en-US', 48 | 'basePath' => '@vendor/sadovojav/yii2-gallery-module/messages', 49 | 'fileMap' => [ 50 | 'sadovojav/gallery/default' => 'default.php', 51 | ], 52 | ]; 53 | } 54 | 55 | public static function t($category, $message, $params = [], $language = null) 56 | { 57 | return Yii::t('sadovojav/gallery/' . $category, $message, $params, $language); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii2 gallery 2 | 3 | #### Features: 4 | - Upload image 5 | - Drag image and change position 6 | - Make gallery template 7 | - Use inline widget 8 | 9 | ![gallery](https://cloud.githubusercontent.com/assets/9282021/10119704/7a006796-64a5-11e5-8b3c-51046cb05f7b.jpg) 10 | 11 | ## Installation 12 | 13 | ### Composer 14 | 15 | The preferred way to install this extension is through [Composer](http://getcomposer.org/). 16 | 17 | Either run ```php composer.phar require sadovojav/yii2-gallery-module "dev-master"``` 18 | 19 | or add ```"sadovojav/yii2-gallery-module": "dev-master"``` to the require section of your ```composer.json``` 20 | 21 | ### Migration 22 | 23 | yii migrate --migrationPath=@vendor/sadovojav/yii2-gallery-module/migrations 24 | 25 | ## Config 26 | 27 | 1. Attach the module in your config file: 28 | 29 | ```php 30 | 'modules' => [ 31 | 'gallery' => [ 32 | 'class' => 'sadovojav\gallery\Module', 33 | ], 34 | ], 35 | ``` 36 | - string `basePath` = `@webroot/galleries` - Base path 37 | - integer `queryCacheDuration` = `86400` - Query cache duration 38 | - bool `uniqueName` = `false` - Generate unique name 39 | 40 | 2. If you want use custom template, you can set path map 41 | 42 | ```php 43 | 'view' => [ 44 | 'theme' => [ 45 | 'pathMap' => [ 46 | '@sadovojav/gallery/widgets/views' => '@app/web/views/gallery' 47 | ], 48 | 'baseUrl' => '@web/web' 49 | ], 50 | ], 51 | ``` 52 | 53 | ## Administration 54 | 55 | Galleries manager - **/gallery/gallery/index** 56 | 57 | ## Using 58 | 59 | #### 1. Widget 60 | 61 | ~~~ 62 | $model->galleryId 64 | ]); ?> 65 | ~~~ 66 | 67 | - integer `galleryId` required - Gallery Id 68 | - bool `caption` = `false` - Show caption in default template 69 | - string `template` = `null` - Your custom widget template 70 | 71 | 72 | #### 2. Inline Widget 73 | 74 | See here -> [https://github.com/sadovojav/yii2-inline-widgets-behavior](https://github.com/sadovojav/yii2-inline-widgets-behavior) 75 | -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | .modal-open { 2 | padding: 0 !important; 3 | overflow: inherit !important; 4 | } 5 | .image { 6 | margin-top: 15px; 7 | margin-bottom: 15px; 8 | } 9 | .image.sortable-ghost { 10 | opacity: 0.6; 11 | } 12 | .image.preload { 13 | background-image: url("../images/preload.gif"); 14 | background-position: center bottom 10px; 15 | background-repeat: no-repeat; 16 | opacity: 0.6; 17 | } 18 | .image .wrapper { 19 | border: 1px solid #ddd; 20 | } 21 | .image .wrapper .handle { 22 | height: 175px; 23 | overflow: hidden; 24 | position: relative; 25 | vertical-align: middle; 26 | cursor: move; 27 | } 28 | .image .wrapper .handle img { 29 | position: absolute; 30 | left: 50%; 31 | top: 50%; 32 | height: 100%; 33 | width: auto; 34 | -webkit-transform: translate(-50%, -50%); 35 | -ms-transform: translate(-50%, -50%); 36 | transform: translate(-50%, -50%); 37 | } 38 | .image .wrapper .handle img.portrait { 39 | width: 100%; 40 | height: auto; 41 | } 42 | .image .wrapper .bottom { 43 | padding: 5px; 44 | } 45 | .image .wrapper .bottom .actions { 46 | text-align: right; 47 | padding-top: 3px; 48 | } 49 | .image .wrapper .bottom .actions > button { 50 | background: none; 51 | border: 0; 52 | } 53 | .image .wrapper .bottom .actions i { 54 | cursor: pointer; 55 | padding: 1px 5px; 56 | font-size: 12px; 57 | line-height: 1.5; 58 | border-radius: 3px; 59 | border: 1px solid #ccc; 60 | } 61 | .image .wrapper .bottom .actions i:hover { 62 | background: #e6e6e6; 63 | } 64 | .image .wrapper .bottom .actions .watch { 65 | color: #000; 66 | } 67 | .image .wrapper .bottom .actions .edit { 68 | color: #31708f; 69 | } 70 | .image .wrapper .bottom .actions .remove { 71 | color: #a94442; 72 | } 73 | .file-preview-frame { 74 | padding: 0 !important; 75 | box-shadow: none !important; 76 | } 77 | .file-preview-frame:hover { 78 | box-shadow: none !important; 79 | } 80 | .file-preview-frame .file-upload-indicator { 81 | height: 23px; 82 | } 83 | .file-preview-frame .file-upload-indicator:hover { 84 | padding-top: 2px !important; 85 | font-size: 1em !important; 86 | } 87 | .file-preview-frame .file-caption-name { 88 | display: none; 89 | } 90 | .file-preview-frame .file-actions { 91 | padding: 5px; 92 | } 93 | -------------------------------------------------------------------------------- /assets/css/style.less: -------------------------------------------------------------------------------- 1 | .modal-open { 2 | padding: 0 !important; 3 | overflow: inherit !important; 4 | } 5 | 6 | .image { 7 | margin-top: 15px; 8 | margin-bottom: 15px; 9 | &.sortable-ghost { 10 | opacity: 0.6; 11 | } 12 | &.preload { 13 | background-image: url("../images/preload.gif"); 14 | background-position: center bottom 10px; 15 | background-repeat: no-repeat; 16 | opacity: 0.6; 17 | } 18 | .wrapper { 19 | border: 1px solid #ddd; 20 | .handle { 21 | height: 175px; 22 | overflow: hidden; 23 | position: relative; 24 | vertical-align: middle; 25 | cursor: move; 26 | img { 27 | position: absolute; 28 | left: 50%; 29 | top: 50%; 30 | height: 100%; 31 | width: auto; 32 | -webkit-transform: translate(-50%, -50%); 33 | -ms-transform: translate(-50%, -50%); 34 | transform: translate(-50%, -50%); 35 | &.portrait { 36 | width: 100%; 37 | height: auto; 38 | } 39 | } 40 | } 41 | .bottom { 42 | padding: 5px; 43 | .actions { 44 | text-align: right; 45 | padding-top: 3px; 46 | > button { 47 | background: none; 48 | border: 0; 49 | } 50 | i { 51 | cursor: pointer; 52 | padding: 1px 5px; 53 | font-size: 12px; 54 | line-height: 1.5; 55 | border-radius: 3px; 56 | border: 1px solid #ccc; 57 | &:hover { 58 | background: #e6e6e6; 59 | } 60 | } 61 | .watch { 62 | color: #000; 63 | } 64 | .edit { 65 | color: #31708f; 66 | } 67 | .remove { 68 | color: #a94442; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | .file-preview-frame { 76 | padding: 0 !important; 77 | box-shadow: none !important; 78 | &:hover { 79 | box-shadow: none !important; 80 | } 81 | .file-upload-indicator { 82 | height: 23px; 83 | &:hover { 84 | padding-top: 2px !important; 85 | font-size: 1em !important; 86 | } 87 | } 88 | .file-caption-name { 89 | display: none; 90 | } 91 | .file-actions { 92 | padding: 5px; 93 | } 94 | } -------------------------------------------------------------------------------- /assets/images/preload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/images/preload.gif -------------------------------------------------------------------------------- /assets/js/fancybox/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Denote all files that are truly binary and should not be modified. 5 | *.png binary 6 | *.jpg binary 7 | *.gif binary -------------------------------------------------------------------------------- /assets/js/fancybox/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | fancyBox - Changelog 2 | ========= 3 | 4 | ### Version 2.1.5 - June 14, 2013 5 | * Fixed #493 - Broken slideshow 6 | * Fixed #556 - Parent option 7 | * Retina graphics (#564) and retina display support (#420) 8 | * Improved "lock" feature 9 | 10 | ### Version 2.1.4 - January 10, 2013 11 | * Update to be compatible with jQuery v1.9 12 | * Small changes that should fix usability issues for certain users 13 | 14 | ### Version 2.1.3 - October 23, 2012 15 | 16 | * Fixed #426 - Broken IE7 17 | * Fixed #423 - Background flickering on iOS 18 | * Fixed #418 - Automatically Grow/Shrink and Center 19 | * Updated the script to work with jQuery 1.6 20 | * Media helper supports YouTube video series 21 | 22 | ### Version 2.1.2 - October 15, 2012 23 | 24 | * Fixed #414 - Don't allow nextClick if there is only one item 25 | * Fixed #397 - Button helper 'Menu' not visible in IE7 26 | * Overlay can be opened/closed manually: 27 | * $.fancybox.helpers.overlay.open(); 28 | * $.fancybox.helpers.overlay.open({closeClick : false}); 29 | * $.fancybox.helpers.overlay.close(); 30 | * Optimized for Internet Explorer 10 (Windows 8) 31 | 32 | ### Version 2.1.1 - October 01, 2012 33 | 34 | * Fixed #357 - Converting values like 'auto' in getScalar() 35 | * Fixed #358 - Updated overlay background image 36 | * New "fancybox-href" and "fancybox-title" HTML5 data-attributes (#317) 37 | * Improved helpers: 38 | * - now they can have a property 'defaults' that contains default settings 39 | * - updated vimeo and youtube parsers for media helper 40 | * Content locking now can be turned off 41 | 42 | ### Version 2.1.0 - August 20, 2012 43 | 44 | * Fixed #103 - DOM element re-injection after closing 45 | * Fixed #188 - navigation keys inside editable content 46 | * New animation directions (see https://github.com/fancyapps/fancyBox/issues/233#issuecomment-5512453) 47 | * New option "iframe" - it is now possible to separate scrolling for iframe and wrapping element; choose to preload 48 | * New option "swf" - brings back functionality from fancyBox v1 49 | * Improved media helper - better support for vimeo and youtube; links are now configurable 50 | * Rewritten overlay helper: 51 | * - new option "showEarly" - toggles if should be open before of after content is loaded 52 | * - Facebook-style (https://github.com/fancyapps/fancyBox/issues/24) and therefore uses image for background 53 | * Option "padding" accepts array (e.g., padding: [15, 50, 10, 5]) 54 | * One of dimensions (width or height) can now be set to "auto" (option "autoSize" needs to be "false") 55 | * Updated callbacks: 56 | * - "beforeClose" is now called only once 57 | * - "afterLoad" receives current and previous object as arguments 58 | * Method "$.fancybox.update();" recalculates content width/height 59 | * Updated to work with jQuery v1.8 60 | 61 | ### Version 2.0.6 - April 16, 2012 62 | 63 | * Fixed #188 - keystrokes in contenteditable 64 | * Fixed #171 - non-images should not be preloaded 65 | * Fixed #158 - 'closeClick: true' breaks gallery navigation 66 | * New "media" helper - detects and displays various media types 67 | * New option "groupAttr" - name of group selector attribute, default is "data-fancybox-group" 68 | * New feature - selector expressions in URLs, see #170 69 | * Improved 'overlay' helper to use "position: fixed" 70 | * Improved autoSize, fixed wrong height in some cases 71 | * Improved centering and iframe scrolling for iOS 72 | * Updated markup, new element '.fancybox-skin' is now used for styling 73 | 74 | ### Version 2.0.5 - February 21, 2012 75 | 76 | * Fixed #155 - easing for prev/next animations 77 | * Fixed #153 - overriding "keys" options 78 | * Fixed #147 - IE7 problem with #hash links 79 | * Fixed #130 - changing dynamically data-fancybox-group 80 | * Fixed #126 - obey minWidth/minHeight 81 | * Fixed #118 - placement of loading icon and navigation arrows 82 | * Fixed #101 - "index" option not working 83 | * Fixed #94 - "orig" option not working 84 | * Fixed #80 - does not work on IE6 85 | * Fixed #72 - can't set overlay opacity to 0 86 | * Fixed #63 - properly set gallery index 87 | * New option "autoCenter" - toggles centering on window resize or scroll, disabled for mobile devices by default 88 | * New option "autoResize" - toggles responsivity, disabled for mobile devices by default 89 | * New option "preload" - number of images to preload 90 | * New feature to target mobile/desktop browsers using CSS, see #108 91 | * Changed ajax option defaults to "{ dataType: 'html', headers: { 'X-fancyBox': true } }", see #150 and #128 92 | * Updated loading icon for IE7, IE8 93 | * Calculates height of the iframe if 'autoSize' is set to 'true' and the iframe is on the same domain as the main page 94 | 95 | ### Version 2.0.4 - December 12, 2011 96 | 97 | * Fixed #47 - fix overriding properties 98 | * New option "position" to thumbnail and button helpers 99 | 100 | 101 | ### Version 2.0.3 - November 29, 2011 102 | 103 | * Fixed #29 - broken elastic transitions 104 | 105 | 106 | ### Version 2.0.2 - November 28, 2011 107 | 108 | * Fixed slideshow 109 | * Fixed scrollbars issue when displayed a very tall image 110 | * New option "nextClick" - navigate to next gallery item when user clicks the content 111 | * New option "modal" - to disable navigation and closing 112 | * Add 'metadata' plugin support 113 | * Add ability to create groups using 'data-fancybox-group' attribute 114 | * Updated manual usage to match earlier releases 115 | 116 | 117 | ### Version 2.0.1 - November 23, 2011 118 | 119 | * Fixed keyboard events inside form elements 120 | * Fixed manual usage 121 | 122 | 123 | ### Version 2.0.0 - November 21, 2011 124 | 125 | First release - completely rewritten, many new features and updated graphics. -------------------------------------------------------------------------------- /assets/js/fancybox/README.md: -------------------------------------------------------------------------------- 1 | fancyBox 2 | ======== 3 | 4 | fancyBox is a tool that offers a nice and elegant way to add zooming functionality for images, html content and multi-media on your webpages. 5 | 6 | More information and examples: http://www.fancyapps.com/fancybox/ 7 | 8 | License: http://www.fancyapps.com/fancybox/#license 9 | 10 | Copyright (c) 2012 Janis Skarnelis - janis@fancyapps.com 11 | 12 | 13 | How to use 14 | ---------- 15 | 16 | To get started, download the plugin, unzip it and copy files to your website/application directory. 17 | Load files in the section of your HTML document. Make sure you also add the jQuery library. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Create your links with a `title` if you want a title to be shown, and add a class: 26 | 27 | 28 | 29 | If you have a set of related items that you would like to group, 30 | additionally include a group name in the `rel` (or `data-fancybox-group`) attribute: 31 | 32 | 33 | 34 | 35 | Initialise the script like this: 36 | 37 | 42 | 43 | May also be passed an optional options object which will extend the default values. Example: 44 | 45 | 53 | 54 | Tip: Automatically group and apply fancyBox to all images: 55 | 56 | $("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.png'],a[href$='.gif']").attr('rel', 'gallery').fancybox(); 57 | 58 | Script uses the `href` attribute of the matched elements to obtain the location of the content and to figure out content type you want to display. 59 | You can specify type directly by adding classname (fancybox.image, fancybox.iframe, etc) or `data-fancybox-type` attribute: 60 | 61 | //Ajax: 62 | Example 63 | //or 64 | Example 65 | 66 | //Iframe: 67 | Example 68 | 69 | //Inline (will display an element with `id="example"`) 70 | Example 71 | 72 | //SWF: 73 | Example 74 | 75 | //Image: 76 | Example 77 | 78 | Note, ajax requests are subject to the [same origin policy](http://en.wikipedia.org/wiki/Same_origin_policy). 79 | If fancyBox will not be able to get content type, it will try to guess based on 'href' and will quit silently if would not succeed. 80 | (this is different from previsous versions where 'ajax' was used as default type or an error message was displayed). 81 | 82 | Advanced 83 | -------- 84 | 85 | ### Helpers 86 | 87 | Helpers provide a simple mechanism to extend the capabilities of fancyBox. There are two built-in helpers - 'overlay' and 'title'. 88 | You can disable them, set custom options or enable other helpers. Examples: 89 | 90 | //Disable title helper 91 | $(".fancybox").fancybox({ 92 | helpers: { 93 | title: null 94 | } 95 | }); 96 | 97 | //Disable overlay helper 98 | $(".fancybox").fancybox({ 99 | helpers: { 100 | overlay : null 101 | } 102 | }); 103 | 104 | //Change title position and overlay color 105 | $(".fancybox").fancybox({ 106 | helpers: { 107 | title : { 108 | type : 'inside' 109 | }, 110 | overlay : { 111 | css : { 112 | 'background' : 'rgba(255,255,255,0.5)' 113 | } 114 | } 115 | } 116 | }); 117 | 118 | //Enable thumbnail helper and set custom options 119 | $(".fancybox").fancybox({ 120 | helpers: { 121 | thumbs : { 122 | width: 50, 123 | height: 50 124 | } 125 | } 126 | }); 127 | 128 | 129 | ### API 130 | 131 | Also available are event driven callback methods. The `this` keyword refers to the current or upcoming object (depends on callback method). Here is how you can change title: 132 | 133 | $(".fancybox").fancybox({ 134 | beforeLoad : function() { 135 | this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : ''); 136 | 137 | /* 138 | "this.element" refers to current element, so you can, for example, use the "alt" attribute of the image to store the title: 139 | this.title = $(this.element).find('img').attr('alt'); 140 | */ 141 | } 142 | }); 143 | 144 | It`s possible to open fancyBox programmatically in various ways: 145 | 146 | //HTML content: 147 | $.fancybox( '

Lorem Lipsum

Lorem lipsum

', { 148 | title : 'Custom Title' 149 | }); 150 | 151 | //DOM element: 152 | $.fancybox( $("#inline"), { 153 | title : 'Custom Title' 154 | }); 155 | 156 | //Custom object: 157 | $.fancybox({ 158 | href: 'example.jpg', 159 | title : 'Custom Title' 160 | }); 161 | 162 | //Array of objects: 163 | $.fancybox([ 164 | { 165 | href: 'example1.jpg', 166 | title : 'Custom Title 1' 167 | }, 168 | { 169 | href: 'example2.jpg', 170 | title : 'Custom Title 2' 171 | } 172 | ], { 173 | padding: 0 174 | }); 175 | 176 | There are several methods that allow you to interact with and manipulate fancyBox, example: 177 | 178 | //Close fancybox: 179 | $.fancybox.close(); 180 | 181 | There is a simply way to access wrapping elements using JS: 182 | 183 | $.fancybox.wrap 184 | $.fancybox.skin 185 | $.fancybox.outer 186 | $.fancybox.inner 187 | 188 | You can override CSS to customize the look. For example, make navigation arrows always visible, 189 | change width and move them outside of area (use this snippet after including fancybox.css): 190 | 191 | .fancybox-nav span { 192 | visibility: visible; 193 | } 194 | 195 | .fancybox-nav { 196 | width: 80px; 197 | } 198 | 199 | .fancybox-prev { 200 | left: -80px; 201 | } 202 | 203 | .fancybox-next { 204 | right: -80px; 205 | } 206 | 207 | In that case, you might want to increase space around box: 208 | 209 | $(".fancybox").fancybox({ 210 | margin : [20, 60, 20, 60] 211 | }); 212 | 213 | 214 | Bug tracker 215 | ----------- 216 | 217 | Have a bug? Please create an issue on GitHub at https://github.com/fancyapps/fancyBox/issues -------------------------------------------------------------------------------- /assets/js/fancybox/demo/1_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/1_b.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/1_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/1_s.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/2_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/2_b.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/2_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/2_s.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/3_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/3_b.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/3_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/3_s.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/4_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/4_b.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/4_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/4_s.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/5_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/5_b.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/5_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/5_s.jpg -------------------------------------------------------------------------------- /assets/js/fancybox/demo/ajax.txt: -------------------------------------------------------------------------------- 1 |
2 |

Lorem ipsum dolor sit amet3

3 |

4 | Close me 5 |

6 |

7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas fermentum ante et sapien dignissim in viverra magna feugiat. Donec tempus ipsum nec neque dignissim quis eleifend eros gravida. Praesent nisi massa, sodales quis tincidunt ac, semper quis risus. In suscipit nisl sed leo aliquet consequat. Integer vitae augue in risus porttitor pellentesque eu eget odio. Fusce ut sagittis quam. Morbi aliquam interdum blandit. Integer pharetra tempor velit, aliquam dictum justo tempus sed. Morbi congue fringilla justo a feugiat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent quis metus et nisl consectetur pharetra. Nam bibendum turpis eu metus luctus eu volutpat sem molestie. Nam sollicitudin porttitor lorem, ac ultricies est venenatis eu. Ut dignissim elit et orci feugiat ac placerat purus euismod. Ut mi lorem, cursus et sagittis elementum, luctus ac massa. 8 |

9 |

10 | Phasellus et ligula vel diam ullamcorper volutpat. Integer rhoncus rhoncus aliquam. Aliquam erat volutpat. Aenean luctus vestibulum placerat. Quisque quam neque, lacinia aliquet eleifend ac, aliquet blandit felis. Curabitur porta ultricies dui, sit amet mattis quam euismod a. Ut eleifend scelerisque neque, sit amet accumsan odio consequat ut. Proin facilisis auctor elit sed accumsan. Cras dapibus nisl in nisi rhoncus laoreet. Nullam pellentesque tortor libero, eget facilisis ipsum. Donec ultricies tellus tellus, in tincidunt purus. Nullam in est aliquam velit scelerisque blandit. In tincidunt, magna a dapibus imperdiet, quam urna elementum leo, vitae rhoncus nisl velit cursus velit. In dignissim sem ac mauris rhoncus ornare. 11 |

12 |

13 | Duis imperdiet velit vel quam malesuada suscipit imperdiet tellus hendrerit. Mauris vestibulum odio mauris, ut placerat leo. Mauris quis neque at tellus feugiat congue id non enim. Nam vehicula posuere nulla eget vehicula. Donec pretium purus nec ligula porta eu laoreet sapien venenatis. Nulla facilisi. Phasellus eget mi enim. Phasellus molestie tincidunt ultrices. Aenean id sem a tellus lobortis tincidunt. Nam laoreet nulla vel velit tincidunt ac rutrum libero malesuada. Nulla consequat dolor quis nisl tempor fermentum. Integer sodales pretium varius. Aenean a leo vitae odio dictum dignissim malesuada nec dolor. Phasellus adipiscing viverra est, ac sagittis libero sagittis quis. Sed interdum dapibus nunc et fringilla. Nunc vel velit et urna laoreet bibendum. 14 |

15 |
-------------------------------------------------------------------------------- /assets/js/fancybox/demo/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fancyBox - iframe demo 5 | 6 | 7 | 8 |

fancyBox - iframe demo

9 | 10 |

11 | Close iframe parent 12 | 13 | | 14 | 15 | Change content 16 |

17 | 18 |

19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam scelerisque justo ac eros consectetur bibendum. In hac habitasse platea dictumst. Nulla aliquam turpis et tellus elementum luctus. Duis sit amet rhoncus velit. Duis nisl ligula, mattis interdum blandit laoreet, mattis id ante. Cras pulvinar lacus vitae nisi egestas non euismod neque bibendum. Vestibulum faucibus libero id ante molestie ultricies. Vestibulum quis nibh felis. Vestibulum libero nisl, vehicula vel ullamcorper sit amet, tristique sit amet augue. Etiam urna neque, porttitor sed sodales lacinia, posuere a nisl. Vestibulum blandit neque in sapien volutpat ac condimentum sapien auctor. Ut imperdiet venenatis ultricies. Phasellus accumsan, sem eu placerat commodo, felis purus commodo ipsum, sit amet vulputate orci est viverra est. 20 |

21 | 22 |

23 | Aenean velit est, condimentum ut iaculis ut, accumsan at mi. Maecenas velit mi, venenatis ut condimentum at, ultrices vel tortor. Curabitur pharetra ornare dapibus. Ut volutpat cursus semper. In hac habitasse platea dictumst. Donec eu iaculis ipsum. Morbi eu dolor velit, a semper nunc. 24 |

25 | 26 | -------------------------------------------------------------------------------- /assets/js/fancybox/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fancyBox - Fancy jQuery Lightbox Alternative | Demonstration 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 204 | 214 | 215 | 216 |

fancyBox

217 | 218 |

This is a demonstration. More information and examples: www.fancyapps.com/fancybox/

219 | 220 |

Simple image gallery

221 |

222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 |

230 | 231 |

Different effects

232 |

233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 |

241 | 242 |

Various types

243 |

244 | fancyBox will try to guess content type from href attribute but you can specify it directly by adding classname (fancybox.image, fancybox.iframe, etc). 245 |

246 | 252 | 253 | 259 | 260 |

261 | Ajax example will not run from your local computer and requires a server to run. 262 |

263 | 264 |

Button helper

265 |

266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 |

274 | 275 |

Thumbnail helper

276 |

277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 |

285 | 286 |

Media helper

287 |

288 | Will not run from your local computer, requires a server to run. 289 |

290 | 291 | 300 | 301 |

Open manually

302 | 307 | 308 |

309 | Photo Credit: Instagrammer @whitjohns 310 |

311 | 312 | -------------------------------------------------------------------------------- /assets/js/fancybox/lib/jquery.mousewheel-3.0.6.pack.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY 7 | * 8 | * Version: 3.0.6 9 | * 10 | * Requires: 1.2.2+ 11 | */ 12 | (function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;b.axis!==void 0&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);b.wheelDeltaY!==void 0&&(g=b.wheelDeltaY/120);b.wheelDeltaX!==void 0&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]= 13 | d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,false);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); -------------------------------------------------------------------------------- /assets/js/fancybox/source/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/blank.gif -------------------------------------------------------------------------------- /assets/js/fancybox/source/fancybox_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/fancybox_loading.gif -------------------------------------------------------------------------------- /assets/js/fancybox/source/fancybox_loading@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/fancybox_loading@2x.gif -------------------------------------------------------------------------------- /assets/js/fancybox/source/fancybox_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/fancybox_overlay.png -------------------------------------------------------------------------------- /assets/js/fancybox/source/fancybox_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/fancybox_sprite.png -------------------------------------------------------------------------------- /assets/js/fancybox/source/fancybox_sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/fancybox_sprite@2x.png -------------------------------------------------------------------------------- /assets/js/fancybox/source/helpers/fancybox_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/source/helpers/fancybox_buttons.png -------------------------------------------------------------------------------- /assets/js/fancybox/source/helpers/jquery.fancybox-buttons.css: -------------------------------------------------------------------------------- 1 | #fancybox-buttons { 2 | position: fixed; 3 | left: 0; 4 | width: 100%; 5 | z-index: 8050; 6 | } 7 | 8 | #fancybox-buttons.top { 9 | top: 10px; 10 | } 11 | 12 | #fancybox-buttons.bottom { 13 | bottom: 10px; 14 | } 15 | 16 | #fancybox-buttons ul { 17 | display: block; 18 | width: 166px; 19 | height: 30px; 20 | margin: 0 auto; 21 | padding: 0; 22 | list-style: none; 23 | border: 1px solid #111; 24 | border-radius: 3px; 25 | -webkit-box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); 26 | -moz-box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); 27 | box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); 28 | background: rgb(50,50,50); 29 | background: -moz-linear-gradient(top, rgb(68,68,68) 0%, rgb(52,52,52) 50%, rgb(41,41,41) 50%, rgb(51,51,51) 100%); 30 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(68,68,68)), color-stop(50%,rgb(52,52,52)), color-stop(50%,rgb(41,41,41)), color-stop(100%,rgb(51,51,51))); 31 | background: -webkit-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 32 | background: -o-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 33 | background: -ms-linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 34 | background: linear-gradient(top, rgb(68,68,68) 0%,rgb(52,52,52) 50%,rgb(41,41,41) 50%,rgb(51,51,51) 100%); 35 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#444444', endColorstr='#222222',GradientType=0 ); 36 | } 37 | 38 | #fancybox-buttons ul li { 39 | float: left; 40 | margin: 0; 41 | padding: 0; 42 | } 43 | 44 | #fancybox-buttons a { 45 | display: block; 46 | width: 30px; 47 | height: 30px; 48 | text-indent: -9999px; 49 | background-color: transparent; 50 | background-image: url('fancybox_buttons.png'); 51 | background-repeat: no-repeat; 52 | outline: none; 53 | opacity: 0.8; 54 | } 55 | 56 | #fancybox-buttons a:hover { 57 | opacity: 1; 58 | } 59 | 60 | #fancybox-buttons a.btnPrev { 61 | background-position: 5px 0; 62 | } 63 | 64 | #fancybox-buttons a.btnNext { 65 | background-position: -33px 0; 66 | border-right: 1px solid #3e3e3e; 67 | } 68 | 69 | #fancybox-buttons a.btnPlay { 70 | background-position: 0 -30px; 71 | } 72 | 73 | #fancybox-buttons a.btnPlayOn { 74 | background-position: -30px -30px; 75 | } 76 | 77 | #fancybox-buttons a.btnToggle { 78 | background-position: 3px -60px; 79 | border-left: 1px solid #111; 80 | border-right: 1px solid #3e3e3e; 81 | width: 35px 82 | } 83 | 84 | #fancybox-buttons a.btnToggleOn { 85 | background-position: -27px -60px; 86 | } 87 | 88 | #fancybox-buttons a.btnClose { 89 | border-left: 1px solid #111; 90 | width: 35px; 91 | background-position: -56px 0px; 92 | } 93 | 94 | #fancybox-buttons a.btnDisabled { 95 | opacity : 0.4; 96 | cursor: default; 97 | } -------------------------------------------------------------------------------- /assets/js/fancybox/source/helpers/jquery.fancybox-buttons.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Buttons helper for fancyBox 3 | * version: 1.0.5 (Mon, 15 Oct 2012) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * buttons: { 10 | * position : 'top' 11 | * } 12 | * } 13 | * }); 14 | * 15 | */ 16 | (function ($) { 17 | //Shortcut for fancyBox object 18 | var F = $.fancybox; 19 | 20 | //Add helper object 21 | F.helpers.buttons = { 22 | defaults : { 23 | skipSingle : false, // disables if gallery contains single image 24 | position : 'top', // 'top' or 'bottom' 25 | tpl : '
' 26 | }, 27 | 28 | list : null, 29 | buttons: null, 30 | 31 | beforeLoad: function (opts, obj) { 32 | //Remove self if gallery do not have at least two items 33 | 34 | if (opts.skipSingle && obj.group.length < 2) { 35 | obj.helpers.buttons = false; 36 | obj.closeBtn = true; 37 | 38 | return; 39 | } 40 | 41 | //Increase top margin to give space for buttons 42 | obj.margin[ opts.position === 'bottom' ? 2 : 0 ] += 30; 43 | }, 44 | 45 | onPlayStart: function () { 46 | if (this.buttons) { 47 | this.buttons.play.attr('title', 'Pause slideshow').addClass('btnPlayOn'); 48 | } 49 | }, 50 | 51 | onPlayEnd: function () { 52 | if (this.buttons) { 53 | this.buttons.play.attr('title', 'Start slideshow').removeClass('btnPlayOn'); 54 | } 55 | }, 56 | 57 | afterShow: function (opts, obj) { 58 | var buttons = this.buttons; 59 | 60 | if (!buttons) { 61 | this.list = $(opts.tpl).addClass(opts.position).appendTo('body'); 62 | 63 | buttons = { 64 | prev : this.list.find('.btnPrev').click( F.prev ), 65 | next : this.list.find('.btnNext').click( F.next ), 66 | play : this.list.find('.btnPlay').click( F.play ), 67 | toggle : this.list.find('.btnToggle').click( F.toggle ), 68 | close : this.list.find('.btnClose').click( F.close ) 69 | } 70 | } 71 | 72 | //Prev 73 | if (obj.index > 0 || obj.loop) { 74 | buttons.prev.removeClass('btnDisabled'); 75 | } else { 76 | buttons.prev.addClass('btnDisabled'); 77 | } 78 | 79 | //Next / Play 80 | if (obj.loop || obj.index < obj.group.length - 1) { 81 | buttons.next.removeClass('btnDisabled'); 82 | buttons.play.removeClass('btnDisabled'); 83 | 84 | } else { 85 | buttons.next.addClass('btnDisabled'); 86 | buttons.play.addClass('btnDisabled'); 87 | } 88 | 89 | this.buttons = buttons; 90 | 91 | this.onUpdate(opts, obj); 92 | }, 93 | 94 | onUpdate: function (opts, obj) { 95 | var toggle; 96 | 97 | if (!this.buttons) { 98 | return; 99 | } 100 | 101 | toggle = this.buttons.toggle.removeClass('btnDisabled btnToggleOn'); 102 | 103 | //Size toggle button 104 | if (obj.canShrink) { 105 | toggle.addClass('btnToggleOn'); 106 | 107 | } else if (!obj.canExpand) { 108 | toggle.addClass('btnDisabled'); 109 | } 110 | }, 111 | 112 | beforeClose: function () { 113 | if (this.list) { 114 | this.list.remove(); 115 | } 116 | 117 | this.list = null; 118 | this.buttons = null; 119 | } 120 | }; 121 | 122 | }(jQuery)); 123 | -------------------------------------------------------------------------------- /assets/js/fancybox/source/helpers/jquery.fancybox-media.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Media helper for fancyBox 3 | * version: 1.0.6 (Fri, 14 Jun 2013) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * media: true 10 | * } 11 | * }); 12 | * 13 | * Set custom URL parameters: 14 | * $(".fancybox").fancybox({ 15 | * helpers : { 16 | * media: { 17 | * youtube : { 18 | * params : { 19 | * autoplay : 0 20 | * } 21 | * } 22 | * } 23 | * } 24 | * }); 25 | * 26 | * Or: 27 | * $(".fancybox").fancybox({, 28 | * helpers : { 29 | * media: true 30 | * }, 31 | * youtube : { 32 | * autoplay: 0 33 | * } 34 | * }); 35 | * 36 | * Supports: 37 | * 38 | * Youtube 39 | * http://www.youtube.com/watch?v=opj24KnzrWo 40 | * http://www.youtube.com/embed/opj24KnzrWo 41 | * http://youtu.be/opj24KnzrWo 42 | * http://www.youtube-nocookie.com/embed/opj24KnzrWo 43 | * Vimeo 44 | * http://vimeo.com/40648169 45 | * http://vimeo.com/channels/staffpicks/38843628 46 | * http://vimeo.com/groups/surrealism/videos/36516384 47 | * http://player.vimeo.com/video/45074303 48 | * Metacafe 49 | * http://www.metacafe.com/watch/7635964/dr_seuss_the_lorax_movie_trailer/ 50 | * http://www.metacafe.com/watch/7635964/ 51 | * Dailymotion 52 | * http://www.dailymotion.com/video/xoytqh_dr-seuss-the-lorax-premiere_people 53 | * Twitvid 54 | * http://twitvid.com/QY7MD 55 | * Twitpic 56 | * http://twitpic.com/7p93st 57 | * Instagram 58 | * http://instagr.am/p/IejkuUGxQn/ 59 | * http://instagram.com/p/IejkuUGxQn/ 60 | * Google maps 61 | * http://maps.google.com/maps?q=Eiffel+Tower,+Avenue+Gustave+Eiffel,+Paris,+France&t=h&z=17 62 | * http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 63 | * http://maps.google.com/?ll=48.859463,2.292626&spn=0.000965,0.002642&t=m&z=19&layer=c&cbll=48.859524,2.292532&panoid=YJ0lq28OOy3VT2IqIuVY0g&cbp=12,151.58,,0,-15.56 64 | */ 65 | (function ($) { 66 | "use strict"; 67 | 68 | //Shortcut for fancyBox object 69 | var F = $.fancybox, 70 | format = function( url, rez, params ) { 71 | params = params || ''; 72 | 73 | if ( $.type( params ) === "object" ) { 74 | params = $.param(params, true); 75 | } 76 | 77 | $.each(rez, function(key, value) { 78 | url = url.replace( '$' + key, value || '' ); 79 | }); 80 | 81 | if (params.length) { 82 | url += ( url.indexOf('?') > 0 ? '&' : '?' ) + params; 83 | } 84 | 85 | return url; 86 | }; 87 | 88 | //Add helper object 89 | F.helpers.media = { 90 | defaults : { 91 | youtube : { 92 | matcher : /(youtube\.com|youtu\.be|youtube-nocookie\.com)\/(watch\?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*)).*/i, 93 | params : { 94 | autoplay : 1, 95 | autohide : 1, 96 | fs : 1, 97 | rel : 0, 98 | hd : 1, 99 | wmode : 'opaque', 100 | enablejsapi : 1 101 | }, 102 | type : 'iframe', 103 | url : '//www.youtube.com/embed/$3' 104 | }, 105 | vimeo : { 106 | matcher : /(?:vimeo(?:pro)?.com)\/(?:[^\d]+)?(\d+)(?:.*)/, 107 | params : { 108 | autoplay : 1, 109 | hd : 1, 110 | show_title : 1, 111 | show_byline : 1, 112 | show_portrait : 0, 113 | fullscreen : 1 114 | }, 115 | type : 'iframe', 116 | url : '//player.vimeo.com/video/$1' 117 | }, 118 | metacafe : { 119 | matcher : /metacafe.com\/(?:watch|fplayer)\/([\w\-]{1,10})/, 120 | params : { 121 | autoPlay : 'yes' 122 | }, 123 | type : 'swf', 124 | url : function( rez, params, obj ) { 125 | obj.swf.flashVars = 'playerVars=' + $.param( params, true ); 126 | 127 | return '//www.metacafe.com/fplayer/' + rez[1] + '/.swf'; 128 | } 129 | }, 130 | dailymotion : { 131 | matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, 132 | params : { 133 | additionalInfos : 0, 134 | autoStart : 1 135 | }, 136 | type : 'swf', 137 | url : '//www.dailymotion.com/swf/video/$1' 138 | }, 139 | twitvid : { 140 | matcher : /twitvid\.com\/([a-zA-Z0-9_\-\?\=]+)/i, 141 | params : { 142 | autoplay : 0 143 | }, 144 | type : 'iframe', 145 | url : '//www.twitvid.com/embed.php?guid=$1' 146 | }, 147 | twitpic : { 148 | matcher : /twitpic\.com\/(?!(?:place|photos|events)\/)([a-zA-Z0-9\?\=\-]+)/i, 149 | type : 'image', 150 | url : '//twitpic.com/show/full/$1/' 151 | }, 152 | instagram : { 153 | matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, 154 | type : 'image', 155 | url : '//$1/p/$2/media/?size=l' 156 | }, 157 | google_maps : { 158 | matcher : /maps\.google\.([a-z]{2,3}(\.[a-z]{2})?)\/(\?ll=|maps\?)(.*)/i, 159 | type : 'iframe', 160 | url : function( rez ) { 161 | return '//maps.google.' + rez[1] + '/' + rez[3] + '' + rez[4] + '&output=' + (rez[4].indexOf('layer=c') > 0 ? 'svembed' : 'embed'); 162 | } 163 | } 164 | }, 165 | 166 | beforeLoad : function(opts, obj) { 167 | var url = obj.href || '', 168 | type = false, 169 | what, 170 | item, 171 | rez, 172 | params; 173 | 174 | for (what in opts) { 175 | if (opts.hasOwnProperty(what)) { 176 | item = opts[ what ]; 177 | rez = url.match( item.matcher ); 178 | 179 | if (rez) { 180 | type = item.type; 181 | params = $.extend(true, {}, item.params, obj[ what ] || ($.isPlainObject(opts[ what ]) ? opts[ what ].params : null)); 182 | 183 | url = $.type( item.url ) === "function" ? item.url.call( this, rez, params, obj ) : format( item.url, rez, params ); 184 | 185 | break; 186 | } 187 | } 188 | } 189 | 190 | if (type) { 191 | obj.href = url; 192 | obj.type = type; 193 | 194 | obj.autoHeight = false; 195 | } 196 | } 197 | }; 198 | 199 | }(jQuery)); -------------------------------------------------------------------------------- /assets/js/fancybox/source/helpers/jquery.fancybox-thumbs.css: -------------------------------------------------------------------------------- 1 | #fancybox-thumbs { 2 | position: fixed; 3 | left: 0; 4 | width: 100%; 5 | overflow: hidden; 6 | z-index: 8050; 7 | } 8 | 9 | #fancybox-thumbs.bottom { 10 | bottom: 2px; 11 | } 12 | 13 | #fancybox-thumbs.top { 14 | top: 2px; 15 | } 16 | 17 | #fancybox-thumbs ul { 18 | position: relative; 19 | list-style: none; 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | #fancybox-thumbs ul li { 25 | float: left; 26 | padding: 1px; 27 | opacity: 0.5; 28 | } 29 | 30 | #fancybox-thumbs ul li.active { 31 | opacity: 0.75; 32 | padding: 0; 33 | border: 1px solid #fff; 34 | } 35 | 36 | #fancybox-thumbs ul li:hover { 37 | opacity: 1; 38 | } 39 | 40 | #fancybox-thumbs ul li a { 41 | display: block; 42 | position: relative; 43 | overflow: hidden; 44 | border: 1px solid #222; 45 | background: #111; 46 | outline: none; 47 | } 48 | 49 | #fancybox-thumbs ul li img { 50 | display: block; 51 | position: relative; 52 | border: 0; 53 | padding: 0; 54 | max-width: none; 55 | } -------------------------------------------------------------------------------- /assets/js/fancybox/source/helpers/jquery.fancybox-thumbs.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Thumbnail helper for fancyBox 3 | * version: 1.0.7 (Mon, 01 Oct 2012) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * thumbs: { 10 | * width : 50, 11 | * height : 50 12 | * } 13 | * } 14 | * }); 15 | * 16 | */ 17 | (function ($) { 18 | //Shortcut for fancyBox object 19 | var F = $.fancybox; 20 | 21 | //Add helper object 22 | F.helpers.thumbs = { 23 | defaults : { 24 | width : 50, // thumbnail width 25 | height : 50, // thumbnail height 26 | position : 'bottom', // 'top' or 'bottom' 27 | source : function ( item ) { // function to obtain the URL of the thumbnail image 28 | var href; 29 | 30 | if (item.element) { 31 | href = $(item.element).find('img').attr('src'); 32 | } 33 | 34 | if (!href && item.type === 'image' && item.href) { 35 | href = item.href; 36 | } 37 | 38 | return href; 39 | } 40 | }, 41 | 42 | wrap : null, 43 | list : null, 44 | width : 0, 45 | 46 | init: function (opts, obj) { 47 | var that = this, 48 | list, 49 | thumbWidth = opts.width, 50 | thumbHeight = opts.height, 51 | thumbSource = opts.source; 52 | 53 | //Build list structure 54 | list = ''; 55 | 56 | for (var n = 0; n < obj.group.length; n++) { 57 | list += '
  • '; 58 | } 59 | 60 | this.wrap = $('
    ').addClass(opts.position).appendTo('body'); 61 | this.list = $('').appendTo(this.wrap); 62 | 63 | //Load each thumbnail 64 | $.each(obj.group, function (i) { 65 | var href = thumbSource( obj.group[ i ] ); 66 | 67 | if (!href) { 68 | return; 69 | } 70 | 71 | $("").load(function () { 72 | var width = this.width, 73 | height = this.height, 74 | widthRatio, heightRatio, parent; 75 | 76 | if (!that.list || !width || !height) { 77 | return; 78 | } 79 | 80 | //Calculate thumbnail width/height and center it 81 | widthRatio = width / thumbWidth; 82 | heightRatio = height / thumbHeight; 83 | 84 | parent = that.list.children().eq(i).find('a'); 85 | 86 | if (widthRatio >= 1 && heightRatio >= 1) { 87 | if (widthRatio > heightRatio) { 88 | width = Math.floor(width / heightRatio); 89 | height = thumbHeight; 90 | 91 | } else { 92 | width = thumbWidth; 93 | height = Math.floor(height / widthRatio); 94 | } 95 | } 96 | 97 | $(this).css({ 98 | width : width, 99 | height : height, 100 | top : Math.floor(thumbHeight / 2 - height / 2), 101 | left : Math.floor(thumbWidth / 2 - width / 2) 102 | }); 103 | 104 | parent.width(thumbWidth).height(thumbHeight); 105 | 106 | $(this).hide().appendTo(parent).fadeIn(300); 107 | 108 | }).attr('src', href); 109 | }); 110 | 111 | //Set initial width 112 | this.width = this.list.children().eq(0).outerWidth(true); 113 | 114 | this.list.width(this.width * (obj.group.length + 1)).css('left', Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5))); 115 | }, 116 | 117 | beforeLoad: function (opts, obj) { 118 | //Remove self if gallery do not have at least two items 119 | if (obj.group.length < 2) { 120 | obj.helpers.thumbs = false; 121 | 122 | return; 123 | } 124 | 125 | //Increase bottom margin to give space for thumbs 126 | obj.margin[ opts.position === 'top' ? 0 : 2 ] += ((opts.height) + 15); 127 | }, 128 | 129 | afterShow: function (opts, obj) { 130 | //Check if exists and create or update list 131 | if (this.list) { 132 | this.onUpdate(opts, obj); 133 | 134 | } else { 135 | this.init(opts, obj); 136 | } 137 | 138 | //Set active element 139 | this.list.children().removeClass('active').eq(obj.index).addClass('active'); 140 | }, 141 | 142 | //Center list 143 | onUpdate: function (opts, obj) { 144 | if (this.list) { 145 | this.list.stop(true).animate({ 146 | 'left': Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5)) 147 | }, 150); 148 | } 149 | }, 150 | 151 | beforeClose: function () { 152 | if (this.wrap) { 153 | this.wrap.remove(); 154 | } 155 | 156 | this.wrap = null; 157 | this.list = null; 158 | this.width = 0; 159 | } 160 | } 161 | 162 | }(jQuery)); -------------------------------------------------------------------------------- /assets/js/fancybox/source/jquery.fancybox.css: -------------------------------------------------------------------------------- 1 | /*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */ 2 | .fancybox-wrap, 3 | .fancybox-skin, 4 | .fancybox-outer, 5 | .fancybox-inner, 6 | .fancybox-image, 7 | .fancybox-wrap iframe, 8 | .fancybox-wrap object, 9 | .fancybox-nav, 10 | .fancybox-nav span, 11 | .fancybox-tmp 12 | { 13 | padding: 0; 14 | margin: 0; 15 | border: 0; 16 | outline: none; 17 | vertical-align: top; 18 | } 19 | 20 | .fancybox-wrap { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | z-index: 8020; 25 | } 26 | 27 | .fancybox-skin { 28 | position: relative; 29 | background: #f9f9f9; 30 | color: #444; 31 | text-shadow: none; 32 | -webkit-border-radius: 4px; 33 | -moz-border-radius: 4px; 34 | border-radius: 4px; 35 | } 36 | 37 | .fancybox-opened { 38 | z-index: 8030; 39 | } 40 | 41 | .fancybox-opened .fancybox-skin { 42 | -webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 43 | -moz-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 44 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 45 | } 46 | 47 | .fancybox-outer, .fancybox-inner { 48 | position: relative; 49 | } 50 | 51 | .fancybox-inner { 52 | overflow: hidden; 53 | } 54 | 55 | .fancybox-type-iframe .fancybox-inner { 56 | -webkit-overflow-scrolling: touch; 57 | } 58 | 59 | .fancybox-error { 60 | color: #444; 61 | font: 14px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 62 | margin: 0; 63 | padding: 15px; 64 | white-space: nowrap; 65 | } 66 | 67 | .fancybox-image, .fancybox-iframe { 68 | display: block; 69 | width: 100%; 70 | height: 100%; 71 | } 72 | 73 | .fancybox-image { 74 | max-width: 100%; 75 | max-height: 100%; 76 | } 77 | 78 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 79 | background-image: url('fancybox_sprite.png'); 80 | } 81 | 82 | #fancybox-loading { 83 | position: fixed; 84 | top: 50%; 85 | left: 50%; 86 | margin-top: -22px; 87 | margin-left: -22px; 88 | background-position: 0 -108px; 89 | opacity: 0.8; 90 | cursor: pointer; 91 | z-index: 8060; 92 | } 93 | 94 | #fancybox-loading div { 95 | width: 44px; 96 | height: 44px; 97 | background: url('fancybox_loading.gif') center center no-repeat; 98 | } 99 | 100 | .fancybox-close { 101 | position: absolute; 102 | top: -18px; 103 | right: -18px; 104 | width: 36px; 105 | height: 36px; 106 | cursor: pointer; 107 | z-index: 8040; 108 | } 109 | 110 | .fancybox-nav { 111 | position: absolute; 112 | top: 0; 113 | width: 40%; 114 | height: 100%; 115 | cursor: pointer; 116 | text-decoration: none; 117 | background: transparent url('blank.gif'); /* helps IE */ 118 | -webkit-tap-highlight-color: rgba(0,0,0,0); 119 | z-index: 8040; 120 | } 121 | 122 | .fancybox-prev { 123 | left: 0; 124 | } 125 | 126 | .fancybox-next { 127 | right: 0; 128 | } 129 | 130 | .fancybox-nav span { 131 | position: absolute; 132 | top: 50%; 133 | width: 36px; 134 | height: 34px; 135 | margin-top: -18px; 136 | cursor: pointer; 137 | z-index: 8040; 138 | visibility: hidden; 139 | } 140 | 141 | .fancybox-prev span { 142 | left: 10px; 143 | background-position: 0 -36px; 144 | } 145 | 146 | .fancybox-next span { 147 | right: 10px; 148 | background-position: 0 -72px; 149 | } 150 | 151 | .fancybox-nav:hover span { 152 | visibility: visible; 153 | } 154 | 155 | .fancybox-tmp { 156 | position: absolute; 157 | top: -99999px; 158 | left: -99999px; 159 | visibility: hidden; 160 | max-width: 99999px; 161 | max-height: 99999px; 162 | overflow: visible !important; 163 | } 164 | 165 | /* Overlay helper */ 166 | 167 | .fancybox-lock { 168 | overflow: hidden !important; 169 | width: auto; 170 | } 171 | 172 | .fancybox-lock body { 173 | overflow: hidden !important; 174 | } 175 | 176 | .fancybox-lock-test { 177 | overflow-y: hidden !important; 178 | } 179 | 180 | .fancybox-overlay { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | overflow: hidden; 185 | display: none; 186 | z-index: 8010; 187 | background: url('fancybox_overlay.png'); 188 | } 189 | 190 | .fancybox-overlay-fixed { 191 | position: fixed; 192 | bottom: 0; 193 | right: 0; 194 | } 195 | 196 | .fancybox-lock .fancybox-overlay { 197 | overflow: auto; 198 | overflow-y: scroll; 199 | } 200 | 201 | /* Title helper */ 202 | 203 | .fancybox-title { 204 | visibility: hidden; 205 | font: normal 13px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 206 | position: relative; 207 | text-shadow: none; 208 | z-index: 8050; 209 | } 210 | 211 | .fancybox-opened .fancybox-title { 212 | visibility: visible; 213 | } 214 | 215 | .fancybox-title-float-wrap { 216 | position: absolute; 217 | bottom: 0; 218 | right: 50%; 219 | margin-bottom: -35px; 220 | z-index: 8050; 221 | text-align: center; 222 | } 223 | 224 | .fancybox-title-float-wrap .child { 225 | display: inline-block; 226 | margin-right: -100%; 227 | padding: 2px 20px; 228 | background: transparent; /* Fallback for web browsers that doesn't support RGBa */ 229 | background: rgba(0, 0, 0, 0.8); 230 | -webkit-border-radius: 15px; 231 | -moz-border-radius: 15px; 232 | border-radius: 15px; 233 | text-shadow: 0 1px 2px #222; 234 | color: #FFF; 235 | font-weight: bold; 236 | line-height: 24px; 237 | white-space: nowrap; 238 | } 239 | 240 | .fancybox-title-outside-wrap { 241 | position: relative; 242 | margin-top: 10px; 243 | color: #fff; 244 | } 245 | 246 | .fancybox-title-inside-wrap { 247 | padding-top: 10px; 248 | } 249 | 250 | .fancybox-title-over-wrap { 251 | position: absolute; 252 | bottom: 0; 253 | left: 0; 254 | color: #fff; 255 | padding: 10px; 256 | background: #000; 257 | background: rgba(0, 0, 0, .8); 258 | } 259 | 260 | /*Retina graphics!*/ 261 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 262 | only screen and (min--moz-device-pixel-ratio: 1.5), 263 | only screen and (min-device-pixel-ratio: 1.5){ 264 | 265 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 266 | background-image: url('fancybox_sprite@2x.png'); 267 | background-size: 44px 152px; /*The size of the normal image, half the size of the hi-res image*/ 268 | } 269 | 270 | #fancybox-loading div { 271 | background-image: url('fancybox_loading@2x.gif'); 272 | background-size: 24px 24px; /*The size of the normal image, half the size of the hi-res image*/ 273 | } 274 | } -------------------------------------------------------------------------------- /assets/js/fancybox/sprite.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/sprite.psd -------------------------------------------------------------------------------- /assets/js/sortable/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /assets/js/sortable/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | mock.png 3 | .*.sw* 4 | .build* 5 | jquery.fn.* 6 | -------------------------------------------------------------------------------- /assets/js/sortable/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "strict": true, 3 | "newcap": false, 4 | "node": true, 5 | "expr": true, 6 | "supernew": true, 7 | "laxbreak": true, 8 | "white": true, 9 | "globals": { 10 | "define": true, 11 | "test": true, 12 | "expect": true, 13 | "module": true, 14 | "asyncTest": true, 15 | "start": true, 16 | "ok": true, 17 | "equal": true, 18 | "notEqual": true, 19 | "deepEqual": true, 20 | "window": true, 21 | "document": true, 22 | "performance": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /assets/js/sortable/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | 4 | ### Issue 5 | 6 | 1. Try [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch, perhaps the problem has been solved; 7 | 2. [Use the search](https://github.com/RubaXa/Sortable/search?q=problem), maybe already have an answer; 8 | 3. If not found, create example on [jsbin.com](http://jsbin.com/zunibaxada/1/edit?html,js,output) and describe the problem. 9 | 10 | 11 | --- 12 | 13 | 14 | ### Pull Request 15 | 16 | 1. Before PR run `grunt`; 17 | 2. Only into [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch. 18 | 19 | -------------------------------------------------------------------------------- /assets/js/sortable/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 'use strict'; 3 | 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | version: { 8 | js: { 9 | src: ['<%= pkg.exportName %>.js', '*.json'] 10 | }, 11 | cdn: { 12 | options: { 13 | prefix: '(cdnjs\\.cloudflare\\.com\\/ajax\\/libs\\/Sortable|cdn\\.jsdelivr\\.net\\/sortable)\\/', 14 | replace: '[0-9\\.]+' 15 | }, 16 | src: ['README.md'] 17 | } 18 | }, 19 | 20 | jshint: { 21 | all: ['*.js', '!*.min.js'], 22 | 23 | options: { 24 | jshintrc: true 25 | } 26 | }, 27 | 28 | uglify: { 29 | options: { 30 | banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n' 31 | }, 32 | dist: { 33 | files: { 34 | '<%= pkg.exportName %>.min.js': ['<%= pkg.exportName %>.js'] 35 | } 36 | }, 37 | jquery: { 38 | files: { 39 | 'jquery.fn.sortable.min.js': 'jquery.fn.sortable.js' 40 | } 41 | } 42 | }, 43 | 44 | exec: { 45 | 'meteor-test': { 46 | command: 'meteor/runtests.sh' 47 | }, 48 | 'meteor-publish': { 49 | command: 'meteor/publish.sh' 50 | } 51 | }, 52 | 53 | jquery: {} 54 | }); 55 | 56 | 57 | grunt.registerTask('jquery', function (arg) { 58 | var fs = require('fs'), 59 | filename = 'jquery.fn.sortable.js'; 60 | 61 | grunt.log.oklns(filename); 62 | 63 | fs.writeFileSync( 64 | filename, 65 | (fs.readFileSync('jquery.binding.js') + '') 66 | .replace('/* CODE */', 67 | (fs.readFileSync('Sortable.js') + '') 68 | .replace(/^[\s\S]*?function[\s\S]*?(var[\s\S]+)\/\/\s+Export[\s\S]+/, '$1') 69 | ) 70 | ); 71 | 72 | if (arg === 'min') { 73 | grunt.task.run('uglify:jquery'); 74 | } 75 | }); 76 | 77 | 78 | grunt.loadNpmTasks('grunt-version'); 79 | grunt.loadNpmTasks('grunt-contrib-jshint'); 80 | grunt.loadNpmTasks('grunt-contrib-uglify'); 81 | grunt.loadNpmTasks('grunt-exec'); 82 | 83 | // Meteor tasks 84 | grunt.registerTask('meteor-test', 'exec:meteor-test'); 85 | grunt.registerTask('meteor-publish', 'exec:meteor-publish'); 86 | grunt.registerTask('meteor', ['meteor-test', 'meteor-publish']); 87 | 88 | grunt.registerTask('tests', ['jshint']); 89 | grunt.registerTask('default', ['tests', 'version', 'uglify:dist']); 90 | }; 91 | -------------------------------------------------------------------------------- /assets/js/sortable/Sortable.min.js: -------------------------------------------------------------------------------- 1 | /*! Sortable 1.2.0 - MIT | git://github.com/rubaxa/Sortable.git */ 2 | !function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():"undefined"!=typeof Package?Sortable=a():window.Sortable=a()}(function(){"use strict";function a(a,b){this.el=a,this.options=b=q({},b),a[H]=this;var d={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",ignore:"a, img",filter:null,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0};for(var e in d)!(e in b)&&(b[e]=d[e]);var g=b.group;g&&"object"==typeof g||(g=b.group={name:g}),["pull","put"].forEach(function(a){a in g||(g[a]=!0)}),b.groups=" "+g.name+(g.put.join?" "+g.put.join(" "):"")+" ";for(var h in this)"_"===h.charAt(0)&&(this[h]=c(this,this[h]));f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"dragover",this),f(a,"dragenter",this),Q.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a){t&&t.state!==a&&(i(t,"display",a?"none":""),!a&&t.state&&u.insertBefore(t,r),t.state=a)}function c(a,b){var c=P.call(arguments,2);return b.bind?b.bind.apply(b,[a].concat(c)):function(){return b.apply(a,c.concat(P.call(arguments)))}}function d(a,b,c){if(a){c=c||J,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")\\s","g");do if(">*"===d&&a.parentNode===c||(""===d||a.nodeName.toUpperCase()==d)&&(!b.length||((" "+a.className+" ").match(e)||[]).length==b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function e(a){a.dataTransfer.dropEffect="move",a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,!1)}function g(a,b,c){a.removeEventListener(b,c,!1)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(G," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(G," ")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return J.defaultView&&J.defaultView.getComputedStyle?c=J.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function k(a){a.draggable=!1}function l(){M=!1}function m(a,b){var c=a.lastElementChild,d=c.getBoundingClientRect();return b.clientY-(d.top+d.height)>5&&c}function n(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function o(a){for(var b=0;a&&(a=a.previousElementSibling);)"TEMPLATE"!==a.nodeName.toUpperCase()&&b++;return b}function p(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function q(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}var r,s,t,u,v,w,x,y,z,A,B,C,D,E,F={},G=/\s+/g,H="Sortable"+(new Date).getTime(),I=window,J=I.document,K=I.parseInt,L=!!("draggable"in J.createElement("div")),M=!1,N=function(a,b,c,d,e,f,g){var h=J.createEvent("Event"),i=(a||b[H]).options,j="on"+c.charAt(0).toUpperCase()+c.substr(1);h.initEvent(c,!0,!0),h.item=d||b,h.from=e||b,h.clone=t,h.oldIndex=f,h.newIndex=g,i[j]&&i[j].call(a,h),b.dispatchEvent(h)},O=Math.abs,P=[].slice,Q=[],R=p(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h=b.scrollSensitivity,i=b.scrollSpeed,j=a.clientX,k=a.clientY,l=window.innerWidth,m=window.innerHeight;if(x!==c&&(w=b.scroll,x=c,w===!0)){w=c;do if(w.offsetWidth=l-j)-(h>=j),g=(h>=m-k)-(h>=k),(f||g)&&(d=I)),(F.vx!==f||F.vy!==g||F.el!==d)&&(F.el=d,F.vx=f,F.vy=g,clearInterval(F.pid),d&&(F.pid=setInterval(function(){d===I?I.scrollTo(I.pageXOffset+f*i,I.pageYOffset+g*i):(g&&(d.scrollTop+=g*i),f&&(d.scrollLeft+=f*i))},24)))}},30);return a.prototype={constructor:a,_onTapStart:function(a){var b=this,c=this.el,e=this.options,f=a.type,g=a.touches&&a.touches[0],h=(g||a).target,i=h,j=e.filter;if(!("mousedown"===f&&0!==a.button||e.disabled)&&(h=d(h,e.draggable,c))){if(A=o(h),"function"==typeof j){if(j.call(this,a,h,this))return N(b,i,"filter",h,c,A),void a.preventDefault()}else if(j&&(j=j.split(",").some(function(a){return a=d(i,a.trim(),c),a?(N(b,a,"filter",h,c,A),!0):void 0})))return void a.preventDefault();(!e.handle||d(i,e.handle,c))&&this._prepareDragStart(a,g,h)}},_prepareDragStart:function(a,b,c){var d,e=this,g=e.el,h=e.options,i=g.ownerDocument;c&&!r&&c.parentNode===g&&(D=a,u=g,r=c,v=r.nextSibling,C=h.group,d=function(){e._disableDelayedDrag(),r.draggable=!0,h.ignore.split(",").forEach(function(a){j(r,a.trim(),k)}),e._triggerDragStart(b)},f(i,"mouseup",e._onDrop),f(i,"touchend",e._onDrop),f(i,"touchcancel",e._onDrop),h.delay?(f(i,"mousemove",e._disableDelayedDrag),f(i,"touchmove",e._disableDelayedDrag),e._dragStartTimer=setTimeout(d,h.delay)):d())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),g(a,"mousemove",this._disableDelayedDrag),g(a,"touchmove",this._disableDelayedDrag)},_triggerDragStart:function(a){a?(D={target:r,clientX:a.clientX,clientY:a.clientY},this._onDragStart(D,"touch")):L?(f(r,"dragend",this),f(u,"dragstart",this._onDragStart)):this._onDragStart(D,!0);try{J.selection?J.selection.empty():window.getSelection().removeAllRanges()}catch(b){}},_dragStarted:function(){u&&r&&(h(r,this.options.ghostClass,!0),a.active=this,N(this,u,"start",r,u,A))},_emulateDragOver:function(){if(E){i(s,"display","none");var a=J.elementFromPoint(E.clientX,E.clientY),b=a,c=" "+this.options.group.name,d=Q.length;if(b)do{if(b[H]&&b[H].options.groups.indexOf(c)>-1){for(;d--;)Q[d]({clientX:E.clientX,clientY:E.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);i(s,"display","")}},_onTouchMove:function(a){if(D){var b=a.touches?a.touches[0]:a,c=b.clientX-D.clientX,d=b.clientY-D.clientY,e=a.touches?"translate3d("+c+"px,"+d+"px,0)":"translate("+c+"px,"+d+"px)";E=b,i(s,"webkitTransform",e),i(s,"mozTransform",e),i(s,"msTransform",e),i(s,"transform",e),a.preventDefault()}},_onDragStart:function(a,b){var c=a.dataTransfer,d=this.options;if(this._offUpEvents(),"clone"==C.pull&&(t=r.cloneNode(!0),i(t,"display","none"),u.insertBefore(t,r)),b){var e,g=r.getBoundingClientRect(),h=i(r);s=r.cloneNode(!0),i(s,"top",g.top-K(h.marginTop,10)),i(s,"left",g.left-K(h.marginLeft,10)),i(s,"width",g.width),i(s,"height",g.height),i(s,"opacity","0.8"),i(s,"position","fixed"),i(s,"zIndex","100000"),u.appendChild(s),e=s.getBoundingClientRect(),i(s,"width",2*g.width-e.width),i(s,"height",2*g.height-e.height),"touch"===b?(f(J,"touchmove",this._onTouchMove),f(J,"touchend",this._onDrop),f(J,"touchcancel",this._onDrop)):(f(J,"mousemove",this._onTouchMove),f(J,"mouseup",this._onDrop)),this._loopId=setInterval(this._emulateDragOver,150)}else c&&(c.effectAllowed="move",d.setData&&d.setData.call(this,c,r)),f(J,"drop",this);setTimeout(this._dragStarted,0)},_onDragOver:function(a){var c,e,f,g=this.el,h=this.options,j=h.group,k=j.put,n=C===j,o=h.sort;if(void 0!==a.preventDefault&&(a.preventDefault(),!h.dragoverBubble&&a.stopPropagation()),C&&!h.disabled&&(n?o||(f=!u.contains(r)):C.pull&&k&&(C.name===j.name||k.indexOf&&~k.indexOf(C.name)))&&(void 0===a.rootEl||a.rootEl===this.el)){if(R(a,h,this.el),M)return;if(c=d(a.target,h.draggable,g),e=r.getBoundingClientRect(),f)return b(!0),void(t||v?u.insertBefore(r,t||v):o||u.appendChild(r));if(0===g.children.length||g.children[0]===s||g===a.target&&(c=m(g,a))){if(c){if(c.animated)return;q=c.getBoundingClientRect()}b(n),g.appendChild(r),this._animate(e,r),c&&this._animate(q,c)}else if(c&&!c.animated&&c!==r&&void 0!==c.parentNode[H]){y!==c&&(y=c,z=i(c));var p,q=c.getBoundingClientRect(),w=q.right-q.left,x=q.bottom-q.top,A=/left|right|inline/.test(z.cssFloat+z.display),B=c.offsetWidth>r.offsetWidth,D=c.offsetHeight>r.offsetHeight,E=(A?(a.clientX-q.left)/w:(a.clientY-q.top)/x)>.5,F=c.nextElementSibling;M=!0,setTimeout(l,30),b(n),p=A?c.previousElementSibling===r&&!B||E&&B:F!==r&&!D||E&&D,p&&!F?g.appendChild(r):c.parentNode.insertBefore(r,p?F:c),this._animate(e,r),this._animate(q,c)}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;g(J,"touchmove",this._onTouchMove),g(a,"mouseup",this._onDrop),g(a,"touchend",this._onDrop),g(a,"touchcancel",this._onDrop)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(F.pid),clearTimeout(this.dragStartTimer),g(J,"drop",this),g(J,"mousemove",this._onTouchMove),g(c,"dragstart",this._onDragStart),this._offUpEvents(),b&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation(),s&&s.parentNode.removeChild(s),r&&(g(r,"dragend",this),k(r),h(r,this.options.ghostClass,!1),u!==r.parentNode?(B=o(r),N(null,r.parentNode,"sort",r,u,A,B),N(this,u,"sort",r,u,A,B),N(null,r.parentNode,"add",r,u,A,B),N(this,u,"remove",r,u,A,B)):(t&&t.parentNode.removeChild(t),r.nextSibling!==v&&(B=o(r),N(this,u,"update",r,u,A,B),N(this,u,"sort",r,u,A,B))),a.active&&N(this,u,"end",r,u,A,B)),u=r=s=v=t=w=x=D=E=y=z=C=a.active=null,this.save())},handleEvent:function(a){var b=a.type;"dragover"===b||"dragenter"===b?r&&(this._onDragOver(a),e(a)):("drop"===b||"dragend"===b)&&this._onDrop(a)},toArray:function(){for(var a,b=[],c=this.el.children,e=0,f=c.length,g=this.options;f>e;e++)a=c[e],d(a,g.draggable,this.el)&&b.push(a.getAttribute(g.dataIdAttr)||n(a));return b},sort:function(a){var b={},c=this.el;this.toArray().forEach(function(a,e){var f=c.children[e];d(f,this.options.draggable,c)&&(b[a]=f)},this),a.forEach(function(a){b[a]&&(c.removeChild(b[a]),c.appendChild(b[a]))})},save:function(){var a=this.options.store;a&&a.set(this)},closest:function(a,b){return d(a,b||this.options.draggable,this.el)},option:function(a,b){var c=this.options;return void 0===b?c[a]:void(c[a]=b)},destroy:function(){var a=this.el;a[H]=null,g(a,"mousedown",this._onTapStart),g(a,"touchstart",this._onTapStart),g(a,"dragover",this),g(a,"dragenter",this),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),Q.splice(Q.indexOf(this._onDragOver),1),this._onDrop(),this.el=a=null}},a.utils={on:f,off:g,css:i,find:j,bind:c,is:function(a,b){return!!d(a,b,a)},extend:q,throttle:p,closest:d,toggleClass:h,index:o},a.version="1.2.0",a.create=function(b,c){return new a(b,c)},a}); -------------------------------------------------------------------------------- /assets/js/sortable/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sortable", 3 | "main": "Sortable.js", 4 | "version": "1.2.0", 5 | "homepage": "http://rubaxa.github.io/Sortable/", 6 | "authors": [ 7 | "RubaXa " 8 | ], 9 | "description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.", 10 | "keywords": [ 11 | "sortable", 12 | "reorder", 13 | "list", 14 | "html5", 15 | "drag", 16 | "and", 17 | "drop", 18 | "dnd" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /assets/js/sortable/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sortable", 3 | "main": "Sortable.js", 4 | "version": "1.2.0", 5 | "homepage": "http://rubaxa.github.io/Sortable/", 6 | "repo": "RubaXa/Sortable", 7 | "authors": [ 8 | "RubaXa " 9 | ], 10 | "description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.", 11 | "keywords": [ 12 | "sortable", 13 | "reorder", 14 | "list", 15 | "html5", 16 | "drag", 17 | "and", 18 | "drop", 19 | "dnd" 20 | ], 21 | "license": "MIT", 22 | "ignore": [ 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | 29 | "scripts": [ 30 | "Sortable.js" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /assets/js/sortable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Sortable. No jQuery. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Fork me on GitHub 22 | 23 |
    24 |
    25 | 26 |

    The JavaScript library for modern browsers and touch devices. No jQuery.

    27 |
    28 |
    29 | 30 | 31 | 32 |
    33 |
    34 |
    List A
    35 |
      36 |
    • бегемот
    • 37 |
    • корм
    • 38 |
    • антон
    • 39 |
    • сало
    • 40 |
    • железосталь
    • 41 |
    • валик
    • 42 |
    • кровать
    • 43 |
    • краб
    • 44 |
    45 |
    46 | 47 |
    48 |
    List B
    49 |
      50 |
    • казнить
    • 51 |
    • ,
    • 52 |
    • нельзя
    • 53 |
    • помиловать
    • 54 |
    55 |
    56 |
    57 | 58 | 59 | 60 | 61 |
    62 |
    63 |
    Multi
    64 | 65 |
    66 |
    Group A
    67 |
    68 | 72 |
    73 |
    74 | 75 |
    76 |
    Group B
    77 |
    78 | 81 |
    82 |
    83 | 84 |
    85 |
    Group C
    86 |
    87 | 89 |
    90 |
    91 | 92 |
    93 |
    94 | 95 | 96 | 97 | 98 |
    99 |
    100 |
    Editable list
    101 | 102 |
    103 |
      104 |
    • Оля
    • 105 |
    • Владимир
    • 106 |
    • Алина
    • 107 |
    108 | 109 | 110 |
    111 |
    112 |
    113 | 114 | 115 | 116 | 117 |
    118 |
    119 |
    Advanced groups
    120 | 121 |
    122 |
    pull & put
    123 |
      124 |
    • Meat
    • 125 |
    • Potato
    • 126 |
    • Tea
    • 127 |
    128 |
    129 | 130 |
    131 |
    only pull (clone) no reordering
    132 |
      133 |
    • Sex
    • 134 |
    • Drugs
    • 135 |
    • Rock'n'roll
    • 136 |
    137 |
    138 | 139 |
    140 |
    only put
    141 |
      142 |
    • Money
    • 143 |
    • Force
    • 144 |
    • Agility
    • 145 |
    146 |
    147 | 148 |
    149 |
    150 |
    151 | 152 | 153 | 154 | 155 |
    156 |
    157 |
    Drag handle and selectable text
    158 | 159 |
    160 |
      161 |
    • Select text freely
    • 162 |
    • Drag my handle
    • 163 |
    • Best of both worlds
    • 164 |
    165 |
    166 | 167 |
    168 |
    169 |
    170 | 171 | 172 | 173 | 174 |
    175 |
    176 |
    AngluarJS / ng-sortable
    177 | 178 |
    179 |
    180 | {{remaining()}} of {{todos.length}} remaining 181 | [ archive ] 182 |
      183 |
    • 184 | 185 | {{todo.text}} 186 |
    • 187 |
    188 |
    189 | 191 |
    192 |
    193 |
    194 | 195 | 196 |
    197 |
    198 | {{remaining()}} of {{todos.length}} remaining 199 |
      200 |
    • 201 | 202 | {{todo.text}} 203 |
    • 204 |
    205 |
    206 |
    207 | 208 |
    209 |
    210 |
    211 | 212 | 213 | 214 | 215 |
    216 |
    217 |
    Code example
    218 |
    // Simple list
    219 | var list = document.getElementById("my-ui-list");
    220 | Sortable.create(list); // That's all.
    221 | 
    222 | 
    223 | // Grouping
    224 | var foo = document.getElementById("foo");
    225 | Sortable.create(foo, { group: "omega" });
    226 | 
    227 | var bar = document.getElementById("bar");
    228 | Sortable.create(bar, { group: "omega" });
    229 | 
    230 | 
    231 | // Or
    232 | var container = document.getElementById("multi");
    233 | var sort = Sortable.create(container, {
    234 |   animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
    235 |   handle: ".tile__title", // Restricts sort start click/touch to the specified element
    236 |   draggable: ".tile", // Specifies which items inside the element should be sortable
    237 |   onUpdate: function (evt/**Event*/){
    238 |      var item = evt.item; // the current dragged HTMLElement
    239 |   }
    240 | });
    241 | 
    242 | // ..
    243 | sort.destroy();
    244 | 
    245 | 
    246 | // Editable list
    247 | var editableList = Sortable.create(editable, {
    248 |   filter: '.js-remove',
    249 |   onFilter: function (evt) {
    250 |     var el = editableList.closest(evt.item); // get dragged item
    251 |     el && el.parentNode.removeChild(el);
    252 |   }
    253 | });
    254 | 
    255 |
    256 | 257 |
    258 |
    259 |
    See also
    260 |
    Loading…
    261 | 262 |
    263 |
    264 | 265 |
    266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 330 | 331 | 332 | 333 | 334 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /assets/js/sortable/jquery.binding.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery plugin for Sortable 3 | * @author RubaXa 4 | * @license MIT 5 | */ 6 | (function (factory) { 7 | "use strict"; 8 | 9 | if (typeof define === "function" && define.amd) { 10 | define(["jquery"], factory); 11 | } 12 | else { 13 | /* jshint sub:true */ 14 | factory(jQuery); 15 | } 16 | })(function ($) { 17 | "use strict"; 18 | 19 | 20 | /* CODE */ 21 | 22 | 23 | /** 24 | * jQuery plugin for Sortable 25 | * @param {Object|String} options 26 | * @param {..*} [args] 27 | * @returns {jQuery|*} 28 | */ 29 | $.fn.sortable = function (options) { 30 | var retVal; 31 | 32 | this.each(function () { 33 | var $el = $(this), 34 | sortable = $el.data('sortable'); 35 | 36 | if (!sortable && (options instanceof Object || !options)) { 37 | sortable = new Sortable(this, options); 38 | $el.data('sortable', sortable); 39 | } 40 | 41 | if (sortable) { 42 | if (options === 'widget') { 43 | return sortable; 44 | } 45 | else if (options === 'destroy') { 46 | sortable.destroy(); 47 | $el.removeData('sortable'); 48 | } 49 | else if (options in sortable) { 50 | retVal = sortable[sortable].apply(sortable, [].slice.call(arguments, 1)); 51 | } 52 | } 53 | }); 54 | 55 | return (retVal === void 0) ? this : retVal; 56 | }; 57 | }); 58 | -------------------------------------------------------------------------------- /assets/js/sortable/knockout-sortable.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | var init = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) { 5 | 6 | var options = buildOptions(valueAccessor, sortableOptions); 7 | 8 | //It's seems that we cannot update the eventhandlers after we've created the sortable, so define them in init instead of update 9 | ['onStart', 'onEnd', 'onRemove', 'onAdd', 'onUpdate', 'onSort', 'onFilter'].forEach(function (e) { 10 | if (options[e] || eventHandlers[e]) 11 | options[e] = function (eventType, parentVM, parentBindings, handler, e) { 12 | var itemVM = ko.dataFor(e.item), 13 | //All of the bindings on the parent element 14 | bindings = ko.utils.peekObservable(parentBindings()), 15 | //The binding options for the draggable/sortable binding of the parent element 16 | bindingHandlerBinding = bindings.sortable || bindings.draggable, 17 | //The collection that we should modify 18 | collection = bindingHandlerBinding.collection || bindingHandlerBinding.foreach; 19 | if (handler) 20 | handler(e, itemVM, parentVM, collection, bindings); 21 | if (eventHandlers[eventType]) 22 | eventHandlers[eventType](e, itemVM, parentVM, collection, bindings); 23 | }.bind(undefined, e, viewModel, allBindings, options[e]); 24 | }); 25 | 26 | viewModel._sortable = Sortable.create(element, options); 27 | 28 | //Destroy the sortable if knockout disposes the element it's connected to 29 | ko.utils.domNodeDisposal.addDisposeCallback(element, function () { 30 | viewModel._sortable.destroy(); 31 | }); 32 | return ko.bindingHandlers.template.init(element, valueAccessor); 33 | }, 34 | update = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) { 35 | 36 | //There seems to be some problems with updating the options of a sortable 37 | //Tested to change eventhandlers and the group options without any luck 38 | 39 | return ko.bindingHandlers.template.update(element, valueAccessor, allBindings, viewModel, bindingContext); 40 | }, 41 | eventHandlers = (function (handlers) { 42 | 43 | var moveOperations = [], 44 | tryMoveOperation = function (e, itemVM, parentVM, collection, parentBindings) { 45 | //A move operation is the combination of a add and remove event, this is to make sure that we have both the target and origin collections 46 | var currentOperation = { event: e, itemVM: itemVM, parentVM: parentVM, collection: collection, parentBindings: parentBindings }, 47 | existingOperation = moveOperations.filter(function (op) { 48 | return op.itemVM === currentOperation.itemVM; 49 | })[0]; 50 | 51 | if (!existingOperation) { 52 | moveOperations.push(currentOperation); 53 | } 54 | else { 55 | //We're finishing the operation and already have a handle on the operation item meaning that it's safe to remove it 56 | moveOperations.splice(moveOperations.indexOf(existingOperation), 1); 57 | 58 | var removeOperation = currentOperation.event.type === 'remove' ? currentOperation : existingOperation, 59 | addOperation = currentOperation.event.type === 'add' ? currentOperation : existingOperation; 60 | 61 | moveItem(itemVM, removeOperation.collection, addOperation.collection, addOperation.event.clone, addOperation.event); 62 | } 63 | }, 64 | //Moves an item from the to (collection to from (collection), these can be references to the same collection which means it's a sort, 65 | //clone indicates if we should move or copy the item into the new collection 66 | moveItem = function (itemVM, from, to, clone, e) { 67 | //Unwrapping this allows us to manipulate the actual array 68 | var fromArray = from(), 69 | //It's not certain that the items actual index is the same as the index reported by sortable due to filtering etc. 70 | originalIndex = fromArray.indexOf(itemVM); 71 | 72 | //Remove sortables "unbound" element 73 | e.item.parentNode.removeChild(e.item); 74 | 75 | //This splice is necessary for both clone and move/sort 76 | //In sort/move since it shouldn't be at this index/in this array anymore 77 | //In clone since we have to work around knockouts valuHasMutated when manipulating arrays and avoid a "unbound" item added by sortable 78 | fromArray.splice(originalIndex, 1); 79 | //Update the array, this will also remove sortables "unbound" clone 80 | from.valueHasMutated(); 81 | if (clone && from !== to) { 82 | //Readd the item 83 | fromArray.splice(originalIndex, 0, itemVM); 84 | //Force knockout to update 85 | from.valueHasMutated(); 86 | } 87 | //Insert the item on its new position 88 | to().splice(e.newIndex, 0, itemVM); 89 | //Make sure to tell knockout that we've modified the actual array. 90 | to.valueHasMutated(); 91 | }; 92 | 93 | handlers.onRemove = tryMoveOperation; 94 | handlers.onAdd = tryMoveOperation; 95 | handlers.onUpdate = function (e, itemVM, parentVM, collection, parentBindings) { 96 | //This will be performed as a sort since the to/from collections reference the same collection and clone is set to false 97 | moveItem(itemVM, collection, collection, false, e); 98 | }; 99 | 100 | return handlers; 101 | })({}), 102 | //bindingOptions are the options set in the "data-bind" attribute in the ui. 103 | //options are custom options, for instance draggable/sortable specific options 104 | buildOptions = function (bindingOptions, options) { 105 | //deep clone/copy of properties from the "from" argument onto the "into" argument and returns the modified "into" 106 | var merge = function (into, from) { 107 | for (var prop in from) { 108 | if (Object.prototype.toString.call(from[prop]) === '[object Object]') { 109 | if (Object.prototype.toString.call(into[prop]) !== '[object Object]') { 110 | into[prop] = {}; 111 | } 112 | into[prop] = merge(into[prop], from[prop]); 113 | } 114 | else 115 | into[prop] = from[prop]; 116 | } 117 | 118 | return into; 119 | }, 120 | //unwrap the supplied options 121 | unwrappedOptions = ko.utils.peekObservable(bindingOptions()).options || {}; 122 | 123 | //Make sure that we don't modify the provided settings object 124 | options = merge({}, options); 125 | 126 | //group is handled differently since we should both allow to change a draggable to a sortable (and vice versa), 127 | //but still be able to set a name on a draggable without it becoming a drop target. 128 | if (unwrappedOptions.group && Object.prototype.toString.call(unwrappedOptions.group) !== '[object Object]') { 129 | //group property is a name string declaration, convert to object. 130 | unwrappedOptions.group = { name: unwrappedOptions.group }; 131 | } 132 | 133 | return merge(options, unwrappedOptions); 134 | }; 135 | 136 | ko.bindingHandlers.draggable = { 137 | sortableOptions: { 138 | group: { pull: 'clone', put: false }, 139 | sort: false 140 | }, 141 | init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { 142 | return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions); 143 | }, 144 | update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { 145 | return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions); 146 | } 147 | }; 148 | 149 | ko.bindingHandlers.sortable = { 150 | sortableOptions: { 151 | group: { pull: true, put: true } 152 | }, 153 | init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { 154 | return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions); 155 | }, 156 | update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { 157 | return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions); 158 | } 159 | }; 160 | 161 | })(); -------------------------------------------------------------------------------- /assets/js/sortable/meteor/README.md: -------------------------------------------------------------------------------- 1 | Reactive reorderable lists with [Sortable](http://rubaxa.github.io/Sortable/), 2 | backed by [Meteor.js](http://meteor.com) collections: 3 | 4 | * new elements arriving in the collection will update the list as you expect 5 | * elements removed from the collection will be removed from the list 6 | * drag and drop between lists updates collections accordingly 7 | 8 | Demo: http://rubaxa-sortable.meteor.com 9 | 10 | # Meteor 11 | 12 | If you're new to Meteor, here's what the excitement is all about - 13 | [watch the first two minutes](https://www.youtube.com/watch?v=fsi0aJ9yr2o); you'll be hooked by 1:28. 14 | That screencast is from 2012. In the meantime, Meteor has become a mature JavaScript-everywhere web 15 | development framework. Read more at [Why Meteor](http://www.meteorpedia.com/read/Why_Meteor). 16 | 17 | 18 | # Usage 19 | 20 | Simplest invocation - order will be lost when the page is refreshed: 21 | 22 | ```handlebars 23 | {{sortable }} 24 | ``` 25 | 26 | Persist the sort order in the 'order' field of each document in the collection: 27 | 28 | ```handlebars 29 | {{sortable items= sortField="order"}} 30 | ``` 31 | 32 | Along with `items`, `sortField` is the only Meteor-specific option. If it's missing, the package will 33 | assume there is a field called "order" in the collection, holding unique `Number`s such that every 34 | `order` differs from that before and after it by at least 1. Basically, keep to 0, 1, 2, ... . 35 | Try not to depend on a particular format for this field; it *is* though guaranteed that a `sort` will 36 | produce lexicographical order, and that the order will be maintained after an arbitrary number of 37 | reorderings, unlike with [naive solutions](http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible). 38 | 39 | 40 | ## Passing options to the Sortable library 41 | 42 | {{sortable items= option1=value1 option2=value2...}} 43 | {{sortable items= options=myOptions}} 44 | 45 | For available options, please refer to [the main README](../README.md#options). You can pass them directly 46 | or under the `options` object. Direct options (`key=value`) override those in `options`. It is best 47 | to pass presentation-related options directly, and functionality-related settings in an `options` 48 | object, as this will enable designers to work without needing to inspect the JavaScript code: 49 | 50 | 54 | 55 | Define the options in a helper for the template that calls Sortable: 56 | 57 | ```js 58 | Template.myTemplate.helpers({ 59 | playerOptions: function () { 60 | return { 61 | group: { 62 | name: "league", 63 | pull: true, 64 | put: false 65 | }, 66 | sort: false 67 | }; 68 | } 69 | }); 70 | ``` 71 | 72 | 73 | ## Events 74 | 75 | All the original Sortable events are supported. In addition, they will receive 76 | the data context in `event.data`. You can access `event.data.order` this way: 77 | 78 | ```handlebars 79 | {{sortable items=players options=playersOptions}} 80 | ``` 81 | 82 | ```js 83 | Template.myTemplate.helpers({ 84 | playersOptions: function () { 85 | return { 86 | onSort: function(/**Event*/event) { 87 | console.log('Moved player #%d from %d to %d', 88 | event.data.order, event.oldIndex, event.newIndex 89 | ); 90 | } 91 | }; 92 | } 93 | }); 94 | ``` 95 | 96 | 97 | # Issues 98 | 99 | If you encounter an issue while using this package, please CC @dandv when you file it in this repo. 100 | 101 | 102 | # TODO 103 | 104 | * Array support 105 | * Tests 106 | * Misc. - see reactivize.js 107 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | meteor-platform 7 | autopublish 8 | insecure 9 | rubaxa:sortable 10 | dburles:mongo-collection-instances 11 | fezvrasta:bootstrap-material-design 12 | # twbs:bootstrap 13 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.0.1 2 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/.meteor/versions: -------------------------------------------------------------------------------- 1 | application-configuration@1.0.3 2 | autopublish@1.0.1 3 | autoupdate@1.1.3 4 | base64@1.0.1 5 | binary-heap@1.0.1 6 | blaze-tools@1.0.1 7 | blaze@2.0.3 8 | boilerplate-generator@1.0.1 9 | callback-hook@1.0.1 10 | check@1.0.2 11 | ctl-helper@1.0.4 12 | ctl@1.0.2 13 | dburles:mongo-collection-instances@0.2.5 14 | ddp@1.0.12 15 | deps@1.0.5 16 | ejson@1.0.4 17 | fastclick@1.0.1 18 | fezvrasta:bootstrap-material-design@0.2.1 19 | follower-livedata@1.0.2 20 | geojson-utils@1.0.1 21 | html-tools@1.0.2 22 | htmljs@1.0.2 23 | http@1.0.8 24 | id-map@1.0.1 25 | insecure@1.0.1 26 | jquery@1.0.1 27 | json@1.0.1 28 | launch-screen@1.0.0 29 | livedata@1.0.11 30 | logging@1.0.5 31 | meteor-platform@1.2.0 32 | meteor@1.1.3 33 | minifiers@1.1.2 34 | minimongo@1.0.5 35 | mobile-status-bar@1.0.1 36 | mongo@1.0.9 37 | observe-sequence@1.0.3 38 | ordered-dict@1.0.1 39 | random@1.0.1 40 | reactive-dict@1.0.4 41 | reactive-var@1.0.3 42 | reload@1.1.1 43 | retry@1.0.1 44 | routepolicy@1.0.2 45 | rubaxa:sortable@1.0.0 46 | session@1.0.4 47 | spacebars-compiler@1.0.3 48 | spacebars@1.0.3 49 | templating@1.0.9 50 | tracker@1.0.3 51 | twbs:bootstrap@3.3.1 52 | ui@1.0.4 53 | underscore@1.0.1 54 | url@1.0.2 55 | webapp-hashing@1.0.1 56 | webapp@1.1.4 57 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/README.md: -------------------------------------------------------------------------------- 1 | # RubaXa:Sortable Meteor demo 2 | 3 | This demo showcases the two-way integration between the reorderable list 4 | widget [Sortable](https://github.com/RubaXa/Sortable/) and Meteor.js. Meteor 5 | Mongo collections are updated when items are added, removed or reordered, and 6 | the order is persisted. 7 | 8 | It also shows list grouping and control over what lists can give or receive 9 | elements. You can only drag elements from the list to the left onto the list 10 | to the right. 11 | 12 | ## Usage 13 | 14 | The example uses the local package from the checkout, so it needs to wire 15 | up some files (`package.js` and `package.json`). This is done by the handy 16 | run script: 17 | 18 | ### Windows 19 | 20 | git clone https://github.com/RubaXa/Sortable.git 21 | cd Sortable 22 | git checkout dev 23 | cd meteor\example 24 | run.bat 25 | 26 | ### Elsewhere 27 | 28 | git clone https://github.com/RubaXa/Sortable.git 29 | cd Sortable 30 | git checkout dev 31 | meteor/example./run.sh 32 | 33 | ## Prior art 34 | 35 | ### Differential 36 | 37 | Differential wrote [a blog post on reorderable lists with 38 | Meteor](differential.com/blog/sortable-lists-in-meteor-using-jquery-ui) and 39 | [jQuery UI Sortable](http://jqueryui.com/sortable/). It served as inspiration 40 | for integrating [rubaxa:sortable](rubaxa.github.io/Sortable/), 41 | which uses the HTML5 native drag&drop API (not without [its 42 | limitations](https://github.com/RubaXa/Sortable/issues/106)). 43 | The reordering method used by the Differential example can lead to data loss 44 | though, because it calculates the new order of a dropped element as the 45 | arithmetic mean of the elements before and after it. This [runs into limitations 46 | of floating point precision](http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible) 47 | in JavaScript after <50 reorderings. 48 | 49 | ### Todos animated 50 | 51 | http://todos-dnd-animated.meteor.com/ ([source](https://github.com/nleush/meteor-todos-sortable-animation)) 52 | is based on old Meteor Blaze (back then Spark) API, and won't work with current versions. 53 | It does showcase some neat features, such as animation when collection elements 54 | are reordered by another client. It uses jQuery UI Sortable as well, which lacks 55 | some features vs. rubaxa:Sortable, e.g. text selection within the item. 56 | 57 | ## TODO 58 | 59 | * Animation 60 | * Indication that an item is being edited 61 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/client/define-object-type.css: -------------------------------------------------------------------------------- 1 | .glyphicon { 2 | vertical-align: baseline; 3 | font-size: 80%; 4 | margin-right: 0.5em; 5 | } 6 | 7 | [class^="mdi-"], [class*=" mdi-"] { 8 | vertical-align: baseline; 9 | font-size: 90%; 10 | margin-right: 0.4em; 11 | } 12 | 13 | .list-pair { 14 | display: flex; /* use the flexbox model */ 15 | flex-direction: row; 16 | } 17 | .sortable { 18 | /* font-size: 2em;*/ 19 | } 20 | 21 | .sortable.source { 22 | /*background: #9FA8DA;*/ 23 | flex: 0 0 auto; 24 | margin-right: 1em; 25 | cursor: move; 26 | cursor: -webkit-grabbing; 27 | } 28 | 29 | .sortable.target { 30 | /*background: #3F51B5;*/ 31 | flex: 1 1 auto; 32 | margin-left: 1em; 33 | } 34 | 35 | .target .well { 36 | 37 | } 38 | 39 | .sortable-handle { 40 | cursor: move; 41 | cursor: -webkit-grabbing; 42 | } 43 | .sortable-handle.pull-right { 44 | margin-top: 0.3em; 45 | } 46 | 47 | .sortable-ghost { 48 | opacity: 0.6; 49 | } 50 | 51 | /* show the remove button on hover */ 52 | .removable .close { 53 | display: none; 54 | } 55 | .removable:hover .close { 56 | display: block; 57 | } 58 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/client/define-object-type.html: -------------------------------------------------------------------------------- 1 | 2 | Reactive RubaXa:Sortable for Meteor 3 | 4 | 5 | 6 | {{> navbar}} 7 | 8 |
    9 | 14 | 15 | {{> typeDefinition}} 16 |
    17 | 18 | 19 | 42 | 43 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/client/define-object-type.js: -------------------------------------------------------------------------------- 1 | // Define an object type by dragging together attributes 2 | 3 | Template.typeDefinition.helpers({ 4 | types: function () { 5 | return Types.find({}, { sort: { order: 1 } }); 6 | }, 7 | typesOptions: { 8 | sortField: 'order', // defaults to 'order' anyway 9 | group: { 10 | name: 'typeDefinition', 11 | pull: 'clone', 12 | put: false 13 | }, 14 | sort: false // don't allow reordering the types, just the attributes below 15 | }, 16 | 17 | attributes: function () { 18 | return Attributes.find({}, { 19 | sort: { order: 1 }, 20 | transform: function (doc) { 21 | doc.icon = Types.findOne({name: doc.type}).icon; 22 | return doc; 23 | } 24 | }); 25 | }, 26 | attributesOptions: { 27 | group: { 28 | name: 'typeDefinition', 29 | put: true 30 | }, 31 | onAdd: function (event) { 32 | delete event.data._id; // Generate a new id when inserting in the Attributes collection. Otherwise, if we add the same type twice, we'll get an error that the ids are not unique. 33 | delete event.data.icon; 34 | event.data.type = event.data.name; 35 | event.data.name = 'Rename me (double click)' 36 | }, 37 | // event handler for reordering attributes 38 | onSort: function (event) { 39 | console.log('Item %s went from #%d to #%d', 40 | event.data.name, event.oldIndex, event.newIndex 41 | ); 42 | } 43 | } 44 | }); 45 | 46 | Template.sortableItemTarget.events({ 47 | 'dblclick .name': function (event, template) { 48 | // Make the name editable. We should use an existing component, but it's 49 | // in a sorry state - https://github.com/arillo/meteor-x-editable/issues/1 50 | var name = template.$('.name'); 51 | var input = template.$('input'); 52 | if (input.length) { // jQuery never returns null - http://stackoverflow.com/questions/920236/how-can-i-detect-if-a-selector-returns-null 53 | input.show(); 54 | } else { 55 | input = $(''); 56 | name.after(input); 57 | } 58 | name.hide(); 59 | input.focus(); 60 | }, 61 | 'blur input[type=text]': function (event, template) { 62 | // commit the change to the name, if any 63 | var input = template.$('input'); 64 | input.hide(); 65 | template.$('.name').show(); 66 | // TODO - what is the collection here? We'll hard-code for now. 67 | // https://github.com/meteor/meteor/issues/3303 68 | if (this.name !== input.val() && this.name !== '') 69 | Attributes.update(this._id, {$set: {name: input.val()}}); 70 | }, 71 | 'keydown input[type=text]': function (event, template) { 72 | if (event.which === 27) { 73 | // ESC - discard edits and keep existing value 74 | template.$('input').val(this.name); 75 | event.preventDefault(); 76 | event.target.blur(); 77 | } else if (event.which === 13) { 78 | // ENTER 79 | event.preventDefault(); 80 | event.target.blur(); 81 | } 82 | } 83 | }); 84 | 85 | // you can add events to all Sortable template instances 86 | Template.sortable.events({ 87 | 'click .close': function (event, template) { 88 | // `this` is the data context set by the enclosing block helper (#each, here) 89 | template.collection.remove(this._id); 90 | // custom code, working on a specific collection 91 | if (Attributes.find().count() === 0) { 92 | Meteor.setTimeout(function () { 93 | Attributes.insert({ 94 | name: 'Not nice to delete the entire list! Add some attributes instead.', 95 | type: 'String', 96 | order: 0 97 | }) 98 | }, 1000); 99 | } 100 | } 101 | }); 102 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/model.js: -------------------------------------------------------------------------------- 1 | Types = new Mongo.Collection('types'); 2 | Attributes = new Mongo.Collection('attributes'); 3 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/package.json: -------------------------------------------------------------------------------- 1 | ../../package.json -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/packages/Sortable: -------------------------------------------------------------------------------- 1 | ../../../ -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/run.bat: -------------------------------------------------------------------------------- 1 | mklink ..\..\package.js "meteor/package.js" 2 | mklink package.json "../../package.json" 3 | meteor run 4 | del ..\..\package.js package.json 5 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/run.sh: -------------------------------------------------------------------------------- 1 | # sanity check: make sure we're in the root directory of the example 2 | cd "$( dirname "$0" )" 3 | 4 | # delete temp files even if Ctrl+C is pressed 5 | int_trap() { 6 | echo "Cleaning up..." 7 | } 8 | trap int_trap INT 9 | 10 | ln -s "meteor/package.js" ../../package.js 2>/dev/null 11 | ln -s "../../package.json" package.json 2>/dev/null 12 | 13 | meteor run "$@" 14 | 15 | rm ../../package.js package.json 16 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/example/server/fixtures.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function () { 2 | if (Types.find().count() === 0) { 3 | [ 4 | { 5 | name: 'String', 6 | icon: '' 7 | }, 8 | { 9 | name: 'Text, multi-line', 10 | icon: '' 11 | }, 12 | { 13 | name: 'Category', 14 | icon: '' 15 | }, 16 | { 17 | name: 'Number', 18 | icon: '' 19 | }, 20 | { 21 | name: 'Date', 22 | icon: '' 23 | }, 24 | { 25 | name: 'Hyperlink', 26 | icon: '' 27 | }, 28 | { 29 | name: 'Image', 30 | icon: '' 31 | }, 32 | { 33 | name: 'Progress', 34 | icon: '' 35 | }, 36 | { 37 | name: 'Duration', 38 | icon: '' 39 | }, 40 | { 41 | name: 'Map address', 42 | icon: '' 43 | }, 44 | { 45 | name: 'Relationship', 46 | icon: '' 47 | } 48 | ].forEach(function (type, i) { 49 | Types.insert({ 50 | name: type.name, 51 | icon: type.icon, 52 | order: i 53 | }); 54 | } 55 | ); 56 | console.log('Initialized attribute types.'); 57 | } 58 | 59 | if (Attributes.find().count() === 0) { 60 | [ 61 | { name: 'Name', type: 'String' }, 62 | { name: 'Created at', type: 'Date' }, 63 | { name: 'Link', type: 'Hyperlink' }, 64 | { name: 'Owner', type: 'Relationship' } 65 | ].forEach(function (attribute, i) { 66 | Attributes.insert({ 67 | name: attribute.name, 68 | type: attribute.type, 69 | order: i 70 | }); 71 | } 72 | ); 73 | console.log('Created sample object type.'); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Meteor.methods({ 4 | /** 5 | * Update the orderField of documents with given ids in a collection, incrementing it by incDec 6 | * @param {String} collectionName - name of the collection to update 7 | * @param {String[]} ids - array of document ids 8 | * @param {String} orderField - the name of the order field, usually "order" 9 | * @param {Number} incDec - pass 1 or -1 10 | */ 11 | 'rubaxa:sortable/collection-update': function (collectionName, ids, orderField, incDec) { 12 | check(collectionName, String); 13 | check(ids, [String]); 14 | check(orderField, String); 15 | check(incDec, Number); 16 | var selector = {_id: {$in: ids}}, modifier = {$inc: {}}; 17 | modifier.$inc[orderField] = incDec; 18 | Mongo.Collection.get(collectionName).update(selector, modifier, {multi: true}); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/package.js: -------------------------------------------------------------------------------- 1 | // package metadata file for Meteor.js 2 | 'use strict'; 3 | 4 | var packageName = 'rubaxa:sortable'; // http://atmospherejs.com/rubaxa/sortable 5 | 6 | var packageJson = JSON.parse(Npm.require("fs").readFileSync('package.json')); 7 | 8 | Package.describe({ 9 | name: packageName, 10 | summary: 'Sortable: reactive minimalist reorderable drag-and-drop lists on modern browsers and touch devices', 11 | version: packageJson.version, 12 | git: 'https://github.com/RubaXa/Sortable.git', 13 | readme: 'https://github.com/RubaXa/Sortable/blob/master/meteor/README.md' 14 | }); 15 | 16 | Package.onUse(function (api) { 17 | api.versionsFrom(['METEOR@0.9.0', 'METEOR@1.0']); 18 | api.use('templating', 'client'); 19 | api.use('dburles:mongo-collection-instances@0.2.6'); // to watch collections getting created 20 | api.export('Sortable'); 21 | api.addFiles([ 22 | 'Sortable.js', 23 | 'meteor/template.html', // the HTML comes first, so reactivize.js can refer to the template in it 24 | 'meteor/reactivize.js' 25 | ], 'client'); 26 | api.addFiles('meteor/methods.js'); // add to both client and server 27 | }); 28 | 29 | Package.onTest(function (api) { 30 | api.use(packageName, 'client'); 31 | api.use('tinytest', 'client'); 32 | 33 | api.addFiles('meteor/test.js', 'client'); 34 | }); 35 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Publish package to Meteor's repository, Atmospherejs.com 3 | 4 | # Make sure Meteor is installed, per https://www.meteor.com/install. 5 | # The curl'ed script is totally safe; takes 2 minutes to read its source and check. 6 | type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; } 7 | 8 | # sanity check: make sure we're in the root directory of the checkout 9 | cd "$( dirname "$0" )/.." 10 | 11 | ALL_EXIT_CODE=0 12 | 13 | # test any package*.js packages we may have, e.g. package.js, package-compat.js 14 | for PACKAGE_FILE in meteor/package*.js; do 15 | 16 | # Meteor expects package.js to be in the root directory of the checkout, so copy there our package file under that name, temporarily 17 | cp $PACKAGE_FILE ./package.js 18 | 19 | # publish package, creating it if it's the first time we're publishing 20 | PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2) 21 | 22 | echo "Publishing $PACKAGE_NAME..." 23 | 24 | # Attempt to re-publish the package - the most common operation once the initial release has 25 | # been made. If the package name was changed (rare), you'll have to pass the --create flag. 26 | meteor publish "$@"; EXIT_CODE=$? 27 | ALL_EXIT_CODE=$(( $ALL_EXIT_CODE + $EXIT_CODE )) 28 | if (( $EXIT_CODE == 0 )); then 29 | echo "Thanks for releasing a new version. You can see it at" 30 | echo "https://atmospherejs.com/${PACKAGE_NAME/://}" 31 | else 32 | echo "We got an error. Please post it at https://github.com/raix/Meteor-community-discussions/issues/14" 33 | fi 34 | 35 | # rm the temporary build files and package.js 36 | rm -rf ".build.$PACKAGE_NAME" versions.json package.js 37 | 38 | done 39 | 40 | exit $ALL_EXIT_CODE 41 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/reactivize.js: -------------------------------------------------------------------------------- 1 | /* 2 | Make a Sortable reactive by binding it to a Mongo.Collection. 3 | Calls `rubaxa:sortable/collection-update` on the server to update the sortField or affected records. 4 | 5 | TODO: 6 | * supply consecutive values if the `order` field doesn't have any 7 | * .get(DOMElement) - return the Sortable object of a DOMElement 8 | * create a new _id automatically onAdd if the event.from list had pull: 'clone' 9 | * support arrays 10 | * sparse arrays 11 | * tests 12 | * drop onto existing empty lists 13 | * insert back into lists emptied by dropping 14 | * performance on dragging into long list at the beginning 15 | * handle failures on Collection operations, e.g. add callback to .insert 16 | * when adding elements, update ranks just for the half closer to the start/end of the list 17 | * revisit http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible 18 | * reproduce the insidious bug where the list isn't always sorted (fiddle with dragging #1 over #2, then back, then #N before #1) 19 | 20 | */ 21 | 22 | 'use strict'; 23 | 24 | Template.sortable.created = function () { 25 | var templateInstance = this; 26 | // `this` is a template instance that can store properties of our choice - http://docs.meteor.com/#/full/template_inst 27 | if (templateInstance.setupDone) return; // paranoid: only run setup once 28 | // this.data is the data context - http://docs.meteor.com/#/full/template_data 29 | // normalize all options into templateInstance.options, and remove them from .data 30 | templateInstance.options = templateInstance.data.options || {}; 31 | Object.keys(templateInstance.data).forEach(function (key) { 32 | if (key === 'options' || key === 'items') return; 33 | templateInstance.options[key] = templateInstance.data[key]; 34 | delete templateInstance.data[key]; 35 | }); 36 | templateInstance.options.sortField = templateInstance.options.sortField || 'order'; 37 | // We can get the collection via the .collection property of the cursor, but changes made that way 38 | // will NOT be sent to the server - https://github.com/meteor/meteor/issues/3271#issuecomment-66656257 39 | // Thus we need to use dburles:mongo-collection-instances to get a *real* collection 40 | if (templateInstance.data.items && templateInstance.data.items.collection) { 41 | // cursor passed via items=; its .collection works client-only and has a .name property 42 | templateInstance.collectionName = templateInstance.data.items.collection.name; 43 | templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName); 44 | } else if (templateInstance.data.items) { 45 | // collection passed via items=; does NOT have a .name property, but _name 46 | templateInstance.collection = templateInstance.data.items; 47 | templateInstance.collectionName = templateInstance.collection._name; 48 | } else if (templateInstance.data.collection) { 49 | // cursor passed directly 50 | templateInstance.collectionName = templateInstance.data.collection.name; 51 | templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName); 52 | } else { 53 | templateInstance.collection = templateInstance.data; // collection passed directly 54 | templateInstance.collectionName = templateInstance.collection._name; 55 | } 56 | 57 | // TODO if (Array.isArray(templateInstance.collection)) 58 | 59 | // What if user filters some of the items in the cursor, instead of ordering the entire collection? 60 | // Use case: reorder by preference movies of a given genre, a filter within all movies. 61 | // A: Modify all intervening items **that are on the client**, to preserve the overall order 62 | // TODO: update *all* orders via a server method that takes not ids, but start & end elements - mild security risk 63 | delete templateInstance.data.options; 64 | 65 | /** 66 | * When an element was moved, adjust its orders and possibly the order of 67 | * other elements, so as to maintain a consistent and correct order. 68 | * 69 | * There are three approaches to this: 70 | * 1) Using arbitrary precision arithmetic and setting only the order of the moved 71 | * element to the average of the orders of the elements around it - 72 | * http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible 73 | * The downside is that the order field in the DB will increase by one byte every 74 | * time an element is reordered. 75 | * 2) Adjust the orders of the intervening items. This keeps the orders sane (integers) 76 | * but is slower because we have to modify multiple documents. 77 | * TODO: we may be able to update fewer records by only altering the 78 | * order of the records between the newIndex/oldIndex and the start/end of the list. 79 | * 3) Use regular precision arithmetic, but when the difference between the orders of the 80 | * moved item and the one before/after it falls below a certain threshold, adjust 81 | * the order of that other item, and cascade doing so up or down the list. 82 | * This will keep the `order` field constant in size, and will only occasionally 83 | * require updating the `order` of other records. 84 | * 85 | * For now, we use approach #2. 86 | * 87 | * @param {String} itemId - the _id of the item that was moved 88 | * @param {Number} orderPrevItem - the order of the item before it, or null 89 | * @param {Number} orderNextItem - the order of the item after it, or null 90 | */ 91 | templateInstance.adjustOrders = function adjustOrders(itemId, orderPrevItem, orderNextItem) { 92 | var orderField = templateInstance.options.sortField; 93 | var selector = {}, modifier = {$set: {}}; 94 | var ids = []; 95 | var startOrder = templateInstance.collection.findOne(itemId)[orderField]; 96 | if (orderPrevItem !== null) { 97 | // Element has a previous sibling, therefore it was moved down in the list. 98 | // Decrease the order of intervening elements. 99 | selector[orderField] = {$lte: orderPrevItem, $gt: startOrder}; 100 | ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id'); 101 | Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, -1); 102 | 103 | // Set the order of the dropped element to the order of its predecessor, whose order was decreased 104 | modifier.$set[orderField] = orderPrevItem; 105 | } else { 106 | // element moved up the list, increase order of intervening elements 107 | selector[orderField] = {$gte: orderNextItem, $lt: startOrder}; 108 | ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id'); 109 | Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, 1); 110 | 111 | // Set the order of the dropped element to the order of its successor, whose order was increased 112 | modifier.$set[orderField] = orderNextItem; 113 | } 114 | templateInstance.collection.update(itemId, modifier); 115 | }; 116 | 117 | templateInstance.setupDone = true; 118 | }; 119 | 120 | 121 | Template.sortable.rendered = function () { 122 | var templateInstance = this; 123 | var orderField = templateInstance.options.sortField; 124 | 125 | // sorting was changed within the list 126 | var optionsOnUpdate = templateInstance.options.onUpdate; 127 | templateInstance.options.onUpdate = function sortableUpdate(/**Event*/event) { 128 | var itemEl = event.item; // dragged HTMLElement 129 | event.data = Blaze.getData(itemEl); 130 | if (event.newIndex < event.oldIndex) { 131 | // Element moved up in the list. The dropped element has a next sibling for sure. 132 | var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField]; 133 | templateInstance.adjustOrders(event.data._id, null, orderNextItem); 134 | } else if (event.newIndex > event.oldIndex) { 135 | // Element moved down in the list. The dropped element has a previous sibling for sure. 136 | var orderPrevItem = Blaze.getData(itemEl.previousElementSibling)[orderField]; 137 | templateInstance.adjustOrders(event.data._id, orderPrevItem, null); 138 | } else { 139 | // do nothing - drag and drop in the same location 140 | } 141 | if (optionsOnUpdate) optionsOnUpdate(event); 142 | }; 143 | 144 | // element was added from another list 145 | var optionsOnAdd = templateInstance.options.onAdd; 146 | templateInstance.options.onAdd = function sortableAdd(/**Event*/event) { 147 | var itemEl = event.item; // dragged HTMLElement 148 | event.data = Blaze.getData(itemEl); 149 | // let the user decorate the object with additional properties before insertion 150 | if (optionsOnAdd) optionsOnAdd(event); 151 | 152 | // Insert the new element at the end of the list and move it where it was dropped. 153 | // We could insert it at the beginning, but that would lead to negative orders. 154 | var sortSpecifier = {}; sortSpecifier[orderField] = -1; 155 | event.data.order = templateInstance.collection.findOne({}, { sort: sortSpecifier, limit: 1 }).order + 1; 156 | // TODO: this can obviously be optimized by setting the order directly as the arithmetic average, with the caveats described above 157 | var newElementId = templateInstance.collection.insert(event.data); 158 | event.data._id = newElementId; 159 | if (itemEl.nextElementSibling) { 160 | var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField]; 161 | templateInstance.adjustOrders(newElementId, null, orderNextItem); 162 | } else { 163 | // do nothing - inserted after the last element 164 | } 165 | // remove the dropped HTMLElement from the list because we have inserted it in the collection, which will update the template 166 | itemEl.parentElement.removeChild(itemEl); 167 | }; 168 | 169 | // element was removed by dragging into another list 170 | var optionsOnRemove = templateInstance.options.onRemove; 171 | templateInstance.options.onRemove = function sortableRemove(/**Event*/event) { 172 | var itemEl = event.item; // dragged HTMLElement 173 | event.data = Blaze.getData(itemEl); 174 | // don't remove from the collection if group.pull is clone or false 175 | if (typeof templateInstance.options.group === 'undefined' 176 | || typeof templateInstance.options.group.pull === 'undefined' 177 | || templateInstance.options.group.pull === true 178 | ) templateInstance.collection.remove(event.data._id); 179 | if (optionsOnRemove) optionsOnRemove(event); 180 | }; 181 | 182 | // just compute the `data` context 183 | ['onStart', 'onEnd', 'onSort', 'onFilter'].forEach(function (eventHandler) { 184 | if (templateInstance.options[eventHandler]) { 185 | var userEventHandler = templateInstance.options[eventHandler]; 186 | templateInstance.options[eventHandler] = function (/**Event*/event) { 187 | var itemEl = event.item; // dragged HTMLElement 188 | event.data = Blaze.getData(itemEl); 189 | userEventHandler(event); 190 | }; 191 | } 192 | }); 193 | 194 | templateInstance.sortable = Sortable.create(templateInstance.firstNode.parentElement, templateInstance.options); 195 | // TODO make the object accessible, e.g. via Sortable.getSortableById() or some such 196 | }; 197 | 198 | 199 | Template.sortable.destroyed = function () { 200 | this.sortable.destroy(); 201 | }; 202 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Test Meteor package before publishing to Atmospherejs.com 3 | 4 | # Make sure Meteor is installed, per https://www.meteor.com/install. 5 | # The curl'ed script is totally safe; takes 2 minutes to read its source and check. 6 | type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; } 7 | 8 | # sanity check: make sure we're in the root directory of the checkout 9 | cd "$( dirname "$0" )/.." 10 | 11 | 12 | # delete the temporary files even if Ctrl+C is pressed 13 | int_trap() { 14 | printf "\nTests interrupted. Cleaning up...\n\n" 15 | } 16 | trap int_trap INT 17 | 18 | 19 | ALL_EXIT_CODE=0 20 | 21 | # test any package*.js packages we may have, e.g. package.js, package-standalone.js 22 | for PACKAGE_FILE in meteor/package*.js; do 23 | 24 | # Meteor expects package.js in the root dir of the checkout, so copy there our package file under that name, temporarily 25 | cp $PACKAGE_FILE ./package.js 26 | 27 | PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2) 28 | 29 | echo "### Testing $PACKAGE_NAME..." 30 | 31 | # provide an invalid MONGO_URL so Meteor doesn't bog us down with an empty Mongo database 32 | if [ $# -gt 0 ]; then 33 | # interpret any parameter to mean we want an interactive test 34 | MONGO_URL=mongodb:// meteor test-packages ./ 35 | else 36 | # automated/CI test with phantomjs 37 | ./node_modules/.bin/spacejam --mongo-url mongodb:// test-packages ./ 38 | ALL_EXIT_CODES=$(( $ALL_EXIT_CODES + $? )) 39 | fi 40 | 41 | # delete temporary build files and package.js 42 | rm -rf .build.* versions.json package.js 43 | 44 | done 45 | 46 | exit $ALL_EXIT_CODES 47 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/template.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /assets/js/sortable/meteor/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Tinytest.add('Sortable.is', function (test) { 4 | var items = document.createElement('ul'); 5 | items.innerHTML = '
  • item 1
  • item 2
  • item 3
  • '; 6 | var sortable = new Sortable(items); 7 | test.instanceOf(sortable, Sortable, 'Instantiation OK'); 8 | test.length(sortable.toArray(), 3, 'Three elements'); 9 | }); 10 | -------------------------------------------------------------------------------- /assets/js/sortable/ng-sortable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author RubaXa 3 | * @licence MIT 4 | */ 5 | (function (factory) { 6 | 'use strict'; 7 | 8 | if (window.angular && window.Sortable) { 9 | factory(angular, Sortable); 10 | } 11 | else if (typeof define === 'function' && define.amd) { 12 | define(['angular', './Sortable'], factory); 13 | } 14 | })(function (angular, Sortable) { 15 | 'use strict'; 16 | 17 | 18 | /** 19 | * @typedef {Object} ngSortEvent 20 | * @property {*} model List item 21 | * @property {Object|Array} models List of items 22 | * @property {number} oldIndex before sort 23 | * @property {number} newIndex after sort 24 | */ 25 | 26 | 27 | angular.module('ng-sortable', []) 28 | .constant('version', '0.3.7') 29 | .directive('ngSortable', ['$parse', function ($parse) { 30 | var removed, 31 | nextSibling; 32 | 33 | function getSource(el) { 34 | var scope = angular.element(el).scope(); 35 | var ngRepeat = [].filter.call(el.childNodes, function (node) { 36 | return ( 37 | (node.nodeType === 8) && 38 | (node.nodeValue.indexOf('ngRepeat:') !== -1) 39 | ); 40 | })[0]; 41 | 42 | if (!ngRepeat) { 43 | // Without ng-repeat 44 | return null; 45 | } 46 | 47 | // tests: http://jsbin.com/kosubutilo/1/edit?js,output 48 | ngRepeat = ngRepeat.nodeValue.match(/ngRepeat:\s*(?:\(.*?,\s*)?([^\s)]+)[\s)]+in\s+([^\s|]+)/); 49 | 50 | var itemExpr = $parse(ngRepeat[1]); 51 | var itemsExpr = $parse(ngRepeat[2]); 52 | 53 | return { 54 | item: function (el) { 55 | return itemExpr(angular.element(el).scope()); 56 | }, 57 | items: function () { 58 | return itemsExpr(scope); 59 | } 60 | }; 61 | } 62 | 63 | 64 | // Export 65 | return { 66 | restrict: 'AC', 67 | link: function (scope, $el, attrs) { 68 | var el = $el[0], 69 | ngSortable = attrs.ngSortable, 70 | options = scope.$eval(ngSortable) || {}, 71 | source = getSource(el), 72 | sortable 73 | ; 74 | 75 | 76 | function _emitEvent(/**Event*/evt, /*Mixed*/item) { 77 | var name = 'on' + evt.type.charAt(0).toUpperCase() + evt.type.substr(1); 78 | 79 | /* jshint expr:true */ 80 | options[name] && options[name]({ 81 | model: item || source && source.item(evt.item), 82 | models: source && source.items(), 83 | oldIndex: evt.oldIndex, 84 | newIndex: evt.newIndex 85 | }); 86 | } 87 | 88 | 89 | function _sync(/**Event*/evt) { 90 | if (!source) { 91 | // Without ng-repeat 92 | return; 93 | } 94 | 95 | var oldIndex = evt.oldIndex, 96 | newIndex = evt.newIndex, 97 | items = source.items(); 98 | 99 | if (el !== evt.from) { 100 | var prevSource = getSource(evt.from), 101 | prevItems = prevSource.items(); 102 | 103 | oldIndex = prevItems.indexOf(prevSource.item(evt.item)); 104 | removed = prevItems[oldIndex]; 105 | 106 | if (evt.clone) { 107 | evt.from.removeChild(evt.clone); 108 | removed = angular.copy(removed); 109 | } 110 | else { 111 | prevItems.splice(oldIndex, 1); 112 | } 113 | 114 | items.splice(newIndex, 0, removed); 115 | 116 | evt.from.insertBefore(evt.item, nextSibling); // revert element 117 | } 118 | else { 119 | items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); 120 | } 121 | 122 | scope.$apply(); 123 | } 124 | 125 | 126 | sortable = Sortable.create(el, Object.keys(options).reduce(function (opts, name) { 127 | opts[name] = opts[name] || options[name]; 128 | return opts; 129 | }, { 130 | onStart: function (/**Event*/evt) { 131 | nextSibling = evt.item.nextSibling; 132 | _emitEvent(evt); 133 | scope.$apply(); 134 | }, 135 | onEnd: function (/**Event*/evt) { 136 | _emitEvent(evt, removed); 137 | scope.$apply(); 138 | }, 139 | onAdd: function (/**Event*/evt) { 140 | _sync(evt); 141 | _emitEvent(evt, removed); 142 | scope.$apply(); 143 | }, 144 | onUpdate: function (/**Event*/evt) { 145 | _sync(evt); 146 | _emitEvent(evt); 147 | }, 148 | onRemove: function (/**Event*/evt) { 149 | _emitEvent(evt, removed); 150 | }, 151 | onSort: function (/**Event*/evt) { 152 | _emitEvent(evt); 153 | } 154 | })); 155 | 156 | $el.on('$destroy', function () { 157 | sortable.destroy(); 158 | sortable = null; 159 | nextSibling = null; 160 | }); 161 | 162 | if (ngSortable && !/{|}/.test(ngSortable)) { // todo: ugly 163 | angular.forEach([ 164 | 'sort', 'disabled', 'draggable', 'handle', 'animation', 165 | 'onStart', 'onEnd', 'onAdd', 'onUpdate', 'onRemove', 'onSort' 166 | ], function (name) { 167 | scope.$watch(ngSortable + '.' + name, function (value) { 168 | if (value !== void 0) { 169 | options[name] = value; 170 | 171 | if (!/^on[A-Z]/.test(name)) { 172 | sortable.option(name, value); 173 | } 174 | } 175 | }); 176 | }); 177 | } 178 | } 179 | }; 180 | }]); 181 | }); 182 | -------------------------------------------------------------------------------- /assets/js/sortable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sortablejs", 3 | "exportName": "Sortable", 4 | "version": "1.2.0", 5 | "devDependencies": { 6 | "grunt": "*", 7 | "grunt-version": "*", 8 | "grunt-exec": "*", 9 | "grunt-contrib-jshint": "0.9.2", 10 | "grunt-contrib-uglify": "*", 11 | "spacejam": "*" 12 | }, 13 | "description": "Minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports AngularJS and any CSS library, e.g. Bootstrap.", 14 | "main": "Sortable.js", 15 | "scripts": { 16 | "test": "grunt" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/rubaxa/Sortable.git" 21 | }, 22 | "keywords": [ 23 | "sortable", 24 | "reorder", 25 | "drag", 26 | "meteor", 27 | "angular", 28 | "ng-srotable", 29 | "react", 30 | "mixin" 31 | ], 32 | "author": "Konstantin Lebedev ", 33 | "license": "MIT", 34 | "spm": { 35 | "main": "Sortable.js", 36 | "ignore": [ 37 | "meteor", 38 | "st" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /assets/js/sortable/react-sortable-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author RubaXa 3 | * @licence MIT 4 | */ 5 | 6 | (function (factory) { 7 | 'use strict'; 8 | 9 | if (typeof module != 'undefined' && typeof module.exports != 'undefined') { 10 | module.exports = factory(require('./Sortable')); 11 | } 12 | else if (typeof define === 'function' && define.amd) { 13 | define(['./Sortable'], factory); 14 | } 15 | else { 16 | /* jshint sub:true */ 17 | window['SortableMixin'] = factory(Sortable); 18 | } 19 | })(function (/** Sortable */Sortable) { 20 | 'use strict'; 21 | 22 | var _nextSibling; 23 | 24 | var _activeComponent; 25 | 26 | var _defaultOptions = { 27 | ref: 'list', 28 | model: 'items', 29 | 30 | animation: 100, 31 | onStart: 'handleStart', 32 | onEnd: 'handleEnd', 33 | onAdd: 'handleAdd', 34 | onUpdate: 'handleUpdate', 35 | onRemove: 'handleRemove', 36 | onSort: 'handleSort', 37 | onFilter: 'handleFilter' 38 | }; 39 | 40 | 41 | function _getModelName(component) { 42 | return component.sortableOptions && component.sortableOptions.model || _defaultOptions.model; 43 | } 44 | 45 | 46 | function _getModelItems(component) { 47 | var name = _getModelName(component), 48 | items = component.state && component.state[name] || component.props[name]; 49 | 50 | return items.slice(); 51 | } 52 | 53 | 54 | function _extend(dst, src) { 55 | for (var key in src) { 56 | if (src.hasOwnProperty(key)) { 57 | dst[key] = src[key]; 58 | } 59 | } 60 | 61 | return dst; 62 | } 63 | 64 | 65 | /** 66 | * Simple and easy mixin-wrapper for rubaxa/Sortable library, in order to 67 | * make reorderable drag-and-drop lists on modern browsers and touch devices. 68 | * 69 | * @mixin 70 | */ 71 | var SortableMixin = { 72 | sortableMixinVersion: '0.1.0', 73 | 74 | 75 | /** 76 | * @type {Sortable} 77 | * @private 78 | */ 79 | _sortableInstance: null, 80 | 81 | 82 | componentDidMount: function () { 83 | var options = _extend(_extend({}, _defaultOptions), this.sortableOptions || {}), 84 | copyOptions = _extend({}, options), 85 | 86 | emitEvent = function (/** string */type, /** Event */evt) { 87 | var method = this[options[type]]; 88 | method && method.call(this, evt, this._sortableInstance); 89 | }.bind(this); 90 | 91 | 92 | // Bind callbacks so that "this" refers to the component 93 | 'onStart onEnd onAdd onSort onUpdate onRemove onFilter'.split(' ').forEach(function (/** string */name) { 94 | copyOptions[name] = function (evt) { 95 | if (name === 'onStart') { 96 | _nextSibling = evt.item.nextElementSibling; 97 | _activeComponent = this; 98 | } 99 | else if (name === 'onAdd' || name === 'onUpdate') { 100 | evt.from.insertBefore(evt.item, _nextSibling); 101 | 102 | var newState = {}, 103 | remoteState = {}, 104 | oldIndex = evt.oldIndex, 105 | newIndex = evt.newIndex, 106 | items = _getModelItems(this), 107 | remoteItems, 108 | item; 109 | 110 | if (name === 'onAdd') { 111 | remoteItems = _getModelItems(_activeComponent); 112 | item = remoteItems.splice(oldIndex, 1)[0]; 113 | items.splice(newIndex, 0, item); 114 | 115 | remoteState[_getModelName(_activeComponent)] = remoteItems; 116 | } 117 | else { 118 | items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); 119 | } 120 | 121 | newState[_getModelName(this)] = items; 122 | this.setState(newState); 123 | (this !== _activeComponent) && _activeComponent.setState(remoteState); 124 | } 125 | 126 | setTimeout(function () { 127 | emitEvent(name, evt); 128 | }, 0); 129 | }.bind(this); 130 | }, this); 131 | 132 | 133 | /** @namespace this.refs — http://facebook.github.io/react/docs/more-about-refs.html */ 134 | this._sortableInstance = Sortable.create((this.refs[options.ref] || this).getDOMNode(), copyOptions); 135 | }, 136 | 137 | 138 | componentWillUnmount: function () { 139 | this._sortableInstance.destroy(); 140 | this._sortableInstance = null; 141 | } 142 | }; 143 | 144 | 145 | // Export 146 | return SortableMixin; 147 | }); 148 | -------------------------------------------------------------------------------- /assets/js/sortable/st/app.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-image: -webkit-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%); 3 | background-image: -ms-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%); 4 | background-image: linear-gradient(to bottom, #F4E2C9 20%, #F4D7C9 100%); 5 | } 6 | 7 | html, body { 8 | margin: 0; 9 | padding: 0; 10 | position: relative; 11 | color: #464637; 12 | min-height: 100%; 13 | font-size: 20px; 14 | font-family: 'Roboto', sans-serif; 15 | font-weight: 300; 16 | } 17 | 18 | 19 | h1 { 20 | color: #FF3F00; 21 | font-size: 20px; 22 | font-family: 'Roboto', sans-serif; 23 | font-weight: 300; 24 | text-align: center; 25 | } 26 | 27 | 28 | ul { 29 | margin: 0; 30 | padding: 0; 31 | list-style: none; 32 | } 33 | 34 | .container { 35 | width: 80%; 36 | margin: auto; 37 | min-width: 1100px; 38 | max-width: 1300px; 39 | position: relative; 40 | } 41 | 42 | @media (min-width: 750px) and (max-width: 970px){ 43 | .container { 44 | width: 100%; 45 | min-width: 750px; 46 | } 47 | } 48 | 49 | 50 | .sortable-ghost { 51 | opacity: .2; 52 | } 53 | 54 | 55 | img { 56 | border: 0; 57 | vertical-align: middle; 58 | } 59 | 60 | 61 | .logo { 62 | top: 55px; 63 | left: 30px; 64 | position: absolute; 65 | } 66 | 67 | 68 | .title { 69 | color: #fff; 70 | padding: 3px 10px; 71 | display: inline-block; 72 | position: relative; 73 | background-color: #FF7373; 74 | z-index: 1000; 75 | } 76 | .title_xl { 77 | padding: 3px 15px; 78 | font-size: 40px; 79 | } 80 | 81 | 82 | 83 | .tile { 84 | width: 22%; 85 | min-width: 245px; 86 | color: #FF7270; 87 | padding: 10px 30px; 88 | text-align: center; 89 | margin-top: 15px; 90 | margin-left: 5px; 91 | margin-right: 30px; 92 | background-color: #fff; 93 | display: inline-block; 94 | vertical-align: top; 95 | } 96 | .tile__name { 97 | cursor: move; 98 | padding-bottom: 10px; 99 | border-bottom: 1px solid #FF7373; 100 | } 101 | 102 | .tile__list { 103 | margin-top: 10px; 104 | } 105 | .tile__list:last-child { 106 | margin-right: 0; 107 | min-height: 80px; 108 | } 109 | 110 | .tile__list img { 111 | cursor: move; 112 | margin: 10px; 113 | border-radius: 100%; 114 | } 115 | 116 | 117 | 118 | .block { 119 | opacity: 1; 120 | position: absolute; 121 | } 122 | .block__list { 123 | padding: 20px 0; 124 | max-width: 360px; 125 | margin-top: -8px; 126 | margin-left: 5px; 127 | background-color: #fff; 128 | } 129 | .block__list-title { 130 | margin: -20px 0 0; 131 | padding: 10px; 132 | text-align: center; 133 | background: #5F9EDF; 134 | } 135 | .block__list li { cursor: move; } 136 | 137 | .block__list_words li { 138 | background-color: #fff; 139 | padding: 10px 40px; 140 | } 141 | .block__list_words .sortable-ghost { 142 | opacity: 0.4; 143 | background-color: #F4E2C9; 144 | } 145 | 146 | .block__list_words li:first-letter { 147 | text-transform: uppercase; 148 | } 149 | 150 | .block__list_tags { 151 | padding-left: 30px; 152 | } 153 | 154 | .block__list_tags:after { 155 | clear: both; 156 | content: ''; 157 | display: block; 158 | } 159 | .block__list_tags li { 160 | color: #fff; 161 | float: left; 162 | margin: 8px 20px 10px 0; 163 | padding: 5px 10px; 164 | min-width: 10px; 165 | background-color: #5F9EDF; 166 | text-align: center; 167 | } 168 | .block__list_tags li:first-child:first-letter { 169 | text-transform: uppercase; 170 | } 171 | 172 | 173 | 174 | #editable {} 175 | #editable li { 176 | position: relative; 177 | } 178 | 179 | #editable i { 180 | -webkit-transition: opacity .2s; 181 | transition: opacity .2s; 182 | opacity: 0; 183 | display: block; 184 | cursor: pointer; 185 | color: #c00; 186 | top: 10px; 187 | right: 40px; 188 | position: absolute; 189 | font-style: normal; 190 | } 191 | 192 | #editable li:hover i { 193 | opacity: 1; 194 | } 195 | 196 | 197 | #filter {} 198 | #filter button { 199 | color: #fff; 200 | width: 100%; 201 | border: none; 202 | outline: 0; 203 | opacity: .5; 204 | margin: 10px 0 0; 205 | transition: opacity .1s ease; 206 | cursor: pointer; 207 | background: #5F9EDF; 208 | padding: 10px 0; 209 | font-size: 20px; 210 | } 211 | #filter button:hover { 212 | opacity: 1; 213 | } 214 | 215 | #filter .block__list { 216 | padding-bottom: 0; 217 | } 218 | 219 | .drag-handle { 220 | margin-right: 10px; 221 | font: bold 20px Sans-Serif; 222 | color: #5F9EDF; 223 | display: inline-block; 224 | cursor: move; 225 | cursor: -webkit-grabbing; /* overrides 'move' */ 226 | } 227 | 228 | #todos input { 229 | padding: 5px; 230 | font-size: 14px; 231 | font-family: 'Roboto', sans-serif; 232 | font-weight: 300; 233 | } 234 | 235 | 236 | 237 | #nested ul li { 238 | background-color: rgba(0,0,0,.05); 239 | } 240 | -------------------------------------------------------------------------------- /assets/js/sortable/st/app.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var byId = function (id) { return document.getElementById(id); }, 5 | 6 | loadScripts = function (desc, callback) { 7 | var deps = [], key, idx = 0; 8 | 9 | for (key in desc) { 10 | deps.push(key); 11 | } 12 | 13 | (function _next() { 14 | var pid, 15 | name = deps[idx], 16 | script = document.createElement('script'); 17 | 18 | script.type = 'text/javascript'; 19 | script.src = desc[deps[idx]]; 20 | 21 | pid = setInterval(function () { 22 | if (window[name]) { 23 | clearTimeout(pid); 24 | 25 | deps[idx++] = window[name]; 26 | 27 | if (deps[idx]) { 28 | _next(); 29 | } else { 30 | callback.apply(null, deps); 31 | } 32 | } 33 | }, 30); 34 | 35 | document.getElementsByTagName('head')[0].appendChild(script); 36 | })() 37 | }, 38 | 39 | console = window.console; 40 | 41 | 42 | if (!console.log) { 43 | console.log = function () { 44 | alert([].join.apply(arguments, ' ')); 45 | }; 46 | } 47 | 48 | 49 | Sortable.create(byId('foo'), { 50 | group: "words", 51 | animation: 150, 52 | store: { 53 | get: function (sortable) { 54 | var order = localStorage.getItem(sortable.options.group); 55 | return order ? order.split('|') : []; 56 | }, 57 | set: function (sortable) { 58 | var order = sortable.toArray(); 59 | localStorage.setItem(sortable.options.group, order.join('|')); 60 | } 61 | }, 62 | onAdd: function (evt){ console.log('onAdd.foo:', [evt.item, evt.from]); }, 63 | onUpdate: function (evt){ console.log('onUpdate.foo:', [evt.item, evt.from]); }, 64 | onRemove: function (evt){ console.log('onRemove.foo:', [evt.item, evt.from]); }, 65 | onStart:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);}, 66 | onSort:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);}, 67 | onEnd: function(evt){ console.log('onEnd.foo:', [evt.item, evt.from]);} 68 | }); 69 | 70 | 71 | Sortable.create(byId('bar'), { 72 | group: "words", 73 | animation: 150, 74 | onAdd: function (evt){ console.log('onAdd.bar:', evt.item); }, 75 | onUpdate: function (evt){ console.log('onUpdate.bar:', evt.item); }, 76 | onRemove: function (evt){ console.log('onRemove.bar:', evt.item); }, 77 | onStart:function(evt){ console.log('onStart.foo:', evt.item);}, 78 | onEnd: function(evt){ console.log('onEnd.foo:', evt.item);} 79 | }); 80 | 81 | 82 | // Multi groups 83 | Sortable.create(byId('multi'), { 84 | animation: 150, 85 | draggable: '.tile', 86 | handle: '.tile__name' 87 | }); 88 | 89 | [].forEach.call(byId('multi').getElementsByClassName('tile__list'), function (el){ 90 | Sortable.create(el, { 91 | group: 'photo', 92 | animation: 150 93 | }); 94 | }); 95 | 96 | 97 | // Editable list 98 | var editableList = Sortable.create(byId('editable'), { 99 | animation: 150, 100 | filter: '.js-remove', 101 | onFilter: function (evt) { 102 | evt.item.parentNode.removeChild(evt.item); 103 | } 104 | }); 105 | 106 | 107 | byId('addUser').onclick = function () { 108 | Ply.dialog('prompt', { 109 | title: 'Add', 110 | form: { name: 'name' } 111 | }).done(function (ui) { 112 | var el = document.createElement('li'); 113 | el.innerHTML = ui.data.name + ''; 114 | editableList.el.appendChild(el); 115 | }); 116 | }; 117 | 118 | 119 | // Advanced groups 120 | [{ 121 | name: 'advanced', 122 | pull: true, 123 | put: true 124 | }, 125 | { 126 | name: 'advanced', 127 | pull: 'clone', 128 | put: false 129 | }, { 130 | name: 'advanced', 131 | pull: false, 132 | put: true 133 | }].forEach(function (groupOpts, i) { 134 | Sortable.create(byId('advanced-' + (i + 1)), { 135 | sort: (i != 1), 136 | group: groupOpts, 137 | animation: 150 138 | }); 139 | }); 140 | 141 | 142 | // 'handle' option 143 | Sortable.create(byId('handle-1'), { 144 | handle: '.drag-handle', 145 | animation: 150 146 | }); 147 | 148 | 149 | // Angular example 150 | angular.module('todoApp', ['ng-sortable']) 151 | .controller('TodoController', ['$scope', function ($scope) { 152 | $scope.todos = [ 153 | {text: 'learn angular', done: true}, 154 | {text: 'build an angular app', done: false} 155 | ]; 156 | 157 | $scope.addTodo = function () { 158 | $scope.todos.push({text: $scope.todoText, done: false}); 159 | $scope.todoText = ''; 160 | }; 161 | 162 | $scope.remaining = function () { 163 | var count = 0; 164 | angular.forEach($scope.todos, function (todo) { 165 | count += todo.done ? 0 : 1; 166 | }); 167 | return count; 168 | }; 169 | 170 | $scope.archive = function () { 171 | var oldTodos = $scope.todos; 172 | $scope.todos = []; 173 | angular.forEach(oldTodos, function (todo) { 174 | if (!todo.done) $scope.todos.push(todo); 175 | }); 176 | }; 177 | }]) 178 | .controller('TodoControllerNext', ['$scope', function ($scope) { 179 | $scope.todos = [ 180 | {text: 'learn Sortable', done: true}, 181 | {text: 'use ng-sortable', done: false}, 182 | {text: 'Enjoy', done: false} 183 | ]; 184 | 185 | $scope.remaining = function () { 186 | var count = 0; 187 | angular.forEach($scope.todos, function (todo) { 188 | count += todo.done ? 0 : 1; 189 | }); 190 | return count; 191 | }; 192 | 193 | $scope.sortableConfig = { group: 'todo', animation: 150 }; 194 | 'Start End Add Update Remove Sort'.split(' ').forEach(function (name) { 195 | $scope.sortableConfig['on' + name] = console.log.bind(console, name); 196 | }); 197 | }]); 198 | })(); 199 | 200 | 201 | 202 | // Background 203 | document.addEventListener("DOMContentLoaded", function () { 204 | function setNoiseBackground(el, width, height, opacity) { 205 | var canvas = document.createElement("canvas"); 206 | var context = canvas.getContext("2d"); 207 | 208 | canvas.width = width; 209 | canvas.height = height; 210 | 211 | for (var i = 0; i < width; i++) { 212 | for (var j = 0; j < height; j++) { 213 | var val = Math.floor(Math.random() * 255); 214 | context.fillStyle = "rgba(" + val + "," + val + "," + val + "," + opacity + ")"; 215 | context.fillRect(i, j, 1, 1); 216 | } 217 | } 218 | 219 | el.style.background = "url(" + canvas.toDataURL("image/png") + ")"; 220 | } 221 | 222 | setNoiseBackground(document.getElementsByTagName('body')[0], 50, 50, 0.02); 223 | }, false); 224 | -------------------------------------------------------------------------------- /assets/js/sortable/st/face-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-01.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-02.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-03.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-04.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-05.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-06.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-07.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-08.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/face-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/face-09.jpg -------------------------------------------------------------------------------- /assets/js/sortable/st/iframe/frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 | 14 16 | 17 | Drag me by the handle 18 |
    19 |
    20 | 2 21 | 22 | You can also select text 23 |
    24 |
    25 | 1 26 | 27 | Best of both worlds! 28 |
    29 |
    30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /assets/js/sortable/st/iframe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IFrame playground 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
    20 |
    This is Sortable
    21 |
    It works with Bootstrap...
    22 |
    ...out of the box.
    23 |
    It has support for touch devices.
    24 |
    Just drag some elements around.
    25 |
    26 | 27 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /assets/js/sortable/st/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/logo.png -------------------------------------------------------------------------------- /assets/js/sortable/st/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/sortable/st/og-image.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sadovojav/yii2-gallery-module", 3 | "description": "Yii2 gallery module", 4 | "version": "1.0.0.0", 5 | "type": "yii2-extension", 6 | "keywords": [ 7 | "yii2", 8 | "gallery", 9 | "module" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Aleksandr Sadovoj", 14 | "email": "sadovojav@gmail.com", 15 | "homepage": "https://github.com/sadovojav" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=5.4.0", 20 | "yiisoft/yii2": "*", 21 | "kartik-v/yii2-widgets": "*", 22 | "kartik-v/yii2-grid": "*", 23 | "sadovojav/yii2-inline-widgets-behavior": "dev-master" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "sadovojav\\gallery\\": "" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /controllers/GalleryController.php: -------------------------------------------------------------------------------- 1 | [ 27 | 'class' => AccessControl::className(), 28 | 'only' => ['index', 'create', 'view', 'delete', 'update', 'upload', 'remove', 'caption'], 29 | 'rules' => [ 30 | [ 31 | 'allow' => true, 32 | 'roles' => ['@'], 33 | ], 34 | ], 35 | ], 36 | 'verbs' => [ 37 | 'class' => VerbFilter::className(), 38 | 'actions' => [ 39 | 'delete' => ['post'], 40 | 'upload' => ['post'], 41 | 'remove' => ['post'], 42 | 'caption' => ['post'], 43 | ], 44 | ], 45 | ]; 46 | } 47 | 48 | public function actionUpload() 49 | { 50 | Yii::$app->response->format = Response::FORMAT_JSON; 51 | 52 | if (!Yii::$app->request->isAjax) { 53 | throw new BadRequestHttpException(); 54 | } 55 | 56 | $files = UploadedFile::getInstancesByName('files'); 57 | 58 | $baseDir = Yii::getAlias(Module::getInstance()->basePath); 59 | 60 | if (!is_dir($baseDir)) { 61 | mkdir($baseDir); 62 | } 63 | 64 | $dir = $baseDir . DIRECTORY_SEPARATOR . $_POST['galleryId']; 65 | 66 | if (!is_dir($dir)) { 67 | mkdir($dir); 68 | } 69 | 70 | $response = []; 71 | 72 | foreach ($files as $key => $file) { 73 | if (Module::getInstance()->uniqueName) { 74 | $name = $this->getUniqueName($file); 75 | } else { 76 | $name = $file->name; 77 | } 78 | 79 | $file->saveAs($dir . DIRECTORY_SEPARATOR . $name); 80 | 81 | $model = new GalleryFile(); 82 | $model->galleryId = $_POST['galleryId']; 83 | $model->file = $name; 84 | 85 | if ($model->save()) { 86 | $response = [ 87 | 'status' => true, 88 | 'message' => 'Success', 89 | 'html' => $this->renderAjax('_image', [ 90 | 'model' => $model 91 | ]) 92 | ]; 93 | } 94 | 95 | break; 96 | } 97 | 98 | return $response; 99 | } 100 | 101 | /** 102 | * Deletes an existing gallery fle model. 103 | * @param integer $id 104 | * @return mixed 105 | */ 106 | public function actionRemove() 107 | { 108 | Yii::$app->response->format = Response::FORMAT_JSON; 109 | 110 | $response = false; 111 | 112 | if (!Yii::$app->request->isAjax) { 113 | throw new BadRequestHttpException(); 114 | } 115 | 116 | $model = GalleryFile::findOne(Yii::$app->request->post('id')); 117 | 118 | if (file_exists($model->path)) { 119 | unlink($model->path); 120 | } 121 | 122 | if ($model->delete()) { 123 | $response = [ 124 | 'status' => true, 125 | 'message' => 'Success', 126 | ]; 127 | } 128 | 129 | return $response; 130 | } 131 | 132 | public function actionCaption() 133 | { 134 | Yii::$app->response->format = Response::FORMAT_JSON; 135 | 136 | if (!Yii::$app->request->isAjax) { 137 | throw new BadRequestHttpException(); 138 | } 139 | 140 | $response = false; 141 | 142 | $model = GalleryFile::findOne(Yii::$app->request->post('id')); 143 | $model->caption = Yii::$app->request->post('caption'); 144 | 145 | if ($model->save()) { 146 | $response = [ 147 | 'status' => true, 148 | 'message' => 'Success', 149 | ]; 150 | } 151 | 152 | return $response; 153 | } 154 | 155 | /** 156 | * Get unique name 157 | * @param $file 158 | * @return string 159 | */ 160 | private function getUniqueName($file) 161 | { 162 | $explodeName = explode('.', $file->name); 163 | 164 | $ext = end($explodeName); 165 | 166 | return Yii::$app->security->generateRandomString(16) . ".{$ext}"; 167 | } 168 | 169 | /** 170 | * Lists all gallery models. 171 | * @return mixed 172 | */ 173 | public function actionIndex() 174 | { 175 | $searchModel = new GallerySearch(); 176 | $dataProvider = $searchModel->search(Yii::$app->request->queryParams); 177 | 178 | return $this->render('index', [ 179 | 'dataProvider' => $dataProvider, 180 | 'searchModel' => $searchModel 181 | ]); 182 | } 183 | 184 | /** 185 | * Displays a single gallery model. 186 | * @param integer $id 187 | * @return mixed 188 | */ 189 | public function actionView($id) 190 | { 191 | return $this->render('view', [ 192 | 'model' => $this->findModel($id), 193 | ]); 194 | } 195 | 196 | /** 197 | * Creates a new gallery model. 198 | * If creation is successful, the browser will be redirected to the 'view' page. 199 | * @return mixed 200 | */ 201 | public function actionCreate() 202 | { 203 | $model = new Gallery(); 204 | 205 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 206 | return $this->redirect(['update', 'id' => $model->id]); 207 | } else { 208 | return $this->render('create', [ 209 | 'model' => $model, 210 | ]); 211 | } 212 | } 213 | 214 | /** 215 | * Updates an existing gallery model. 216 | * If update is successful, the browser will be redirected to the 'view' page. 217 | * @param integer $id 218 | * @return mixed 219 | */ 220 | public function actionUpdate($id) 221 | { 222 | $model = $this->findModel($id); 223 | 224 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 225 | return $this->redirect(['view', 'id' => $model->id]); 226 | } else { 227 | return $this->render('update', [ 228 | 'model' => $model, 229 | ]); 230 | } 231 | } 232 | 233 | /** 234 | * Deletes an existing gallery model. 235 | * If deletion is successful, the browser will be redirected to the 'index' page. 236 | * @param integer $id 237 | * @return mixed 238 | */ 239 | public function actionDelete($id) 240 | { 241 | $this->findModel($id)->delete(); 242 | 243 | return $this->redirect(['index']); 244 | } 245 | 246 | /** 247 | * Finds the gallery model based on its primary key value. 248 | * If the model is not found, a 404 HTTP exception will be thrown. 249 | * @param integer $id 250 | * @return gallery the loaded model 251 | * @throws NotFoundHttpException if the model cannot be found 252 | */ 253 | protected function findModel($id) 254 | { 255 | if (($model = Gallery::findOne($id)) !== null) { 256 | return $model; 257 | } else { 258 | throw new NotFoundHttpException('The requested page does not exist.'); 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /messages/en/default.php: -------------------------------------------------------------------------------- 1 | 'Galleries', 5 | 6 | 'GALLERY_MANAGER' => 'Gallery manager', 7 | 8 | 'CREATE' => 'Create', 9 | 'CREATED' => 'Created', 10 | 'UPDATE' => 'Update', 11 | 'UPDATED' => 'Updated', 12 | 'DELETE' => 'Delete', 13 | 'RESET' => 'Reset', 14 | 'VIEW' => 'View', 15 | 16 | 'ID' => 'ID', 17 | 'NAME' => 'Name', 18 | 'STATUS' => 'Status', 19 | 'CAPTION' => 'Caption...', 20 | 'ORIGINAL' => 'Original', 21 | 'UPDATE_CAPTION' => 'Update caption', 22 | 'REMOVE_IMAGE' => 'Removoe image', 23 | 24 | 'ATTENTION_SAVE_MODEL' => 'Attention! Save the model to work with the gallery', 25 | 26 | 'DELETE_ITEM' => 'Are you sure you want to delete this item?' 27 | ]; -------------------------------------------------------------------------------- /messages/ru/default.php: -------------------------------------------------------------------------------- 1 | 'Галереи', 5 | 6 | 'GALLERY_MANAGER' => 'Управление галереями', 7 | 8 | 'CREATE' => 'Создать', 9 | 'CREATED' => 'Создан', 10 | 'UPDATE' => 'Обновить', 11 | 'UPDATED' => 'Обновлён', 12 | 'DELETE' => 'Удалить', 13 | 'RESET' => 'Сброс', 14 | 'VIEW' => 'Обзор', 15 | 16 | 'ID' => 'ID', 17 | 'NAME' => 'Название', 18 | 'STATUS' => 'Статус', 19 | 'CAPTION' => 'Подпись...', 20 | 'ORIGINAL' => 'Оригинал', 21 | 'UPDATE_CAPTION' => 'Обновить подпись', 22 | 'REMOVE_IMAGE' => 'Удалить изображение', 23 | 24 | 'ATTENTION_SAVE_MODEL' => 'Внимание! Сохраните модель для работы с галереей', 25 | 26 | 'DELETE_ITEM' => 'Вы уверены, что хотите удалить эту запись?' 27 | ]; -------------------------------------------------------------------------------- /messages/uk/default.php: -------------------------------------------------------------------------------- 1 | 'Галереї', 5 | 6 | 'GALLERY_MANAGER' => 'Управління галереями', 7 | 8 | 'CREATE' => 'Створити', 9 | 'CREATED' => 'Створено', 10 | 'UPDATE' => 'Оновити', 11 | 'UPDATED' => 'Оновлений', 12 | 'DELETE' => 'Видалити', 13 | 'VIEW' => 'Перегляд', 14 | 15 | 'ID' => 'ID', 16 | 'NAME' => 'Назва', 17 | 'STATUS' => 'Статус', 18 | 'CAPTION' => 'Подпись...', 19 | 'ORIGINAL' => 'Оригінал', 20 | 'UPDATE_CAPTION' => 'Обновить подпись', 21 | 'REMOVE_IMAGE' => 'Удалить изображение', 22 | 23 | 'ATTENTION_SAVE_MODEL' => 'Увага! Збережіть модель для роботи з галереєю', 24 | 25 | 'DELETE_ITEM' => 'Ви впевнені, що хочете видалити цей запис?' 26 | ]; -------------------------------------------------------------------------------- /migrations/m150407_084217_gallery.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { 16 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; 17 | } 18 | 19 | /** 20 | * Create {{%gallery}} table 21 | */ 22 | $this->createTable('{{%gallery}}', [ 23 | 'id' => Schema::TYPE_PK, 24 | 'name' => Schema::TYPE_STRING . '(50) NOT NULL', 25 | 'status' => Schema::TYPE_SMALLINT . '(1) NOT NULL DEFAULT 0', 26 | 'created' => Schema::TYPE_DATETIME . ' NOT NULL', 27 | 'updated' => Schema::TYPE_DATETIME . ' NOT NULL', 28 | ], $tableOptions); 29 | 30 | $this->createIndex('idx_gallery_id_status', '{{%gallery}}', 'id, status'); 31 | 32 | /** 33 | * Create {{%gallery_file}} table 34 | */ 35 | $this->createTable('{{%gallery_file}}', [ 36 | 'id' => Schema::TYPE_PK, 37 | 'galleryId' => Schema::TYPE_INTEGER . ' NOT NULL', 38 | 'file' => Schema::TYPE_STRING . '(255) NOT NULL', 39 | 'caption' => Schema::TYPE_STRING . '(255)', 40 | 'position' => Schema::TYPE_INTEGER . '(1) DEFAULT 0', 41 | ], $tableOptions); 42 | 43 | $this->createIndex('idx_gallery_galleryId', '{{%gallery_file}}', 'galleryId'); 44 | $this->addForeignKey('fk_gallery_file_galleryId', '{{%gallery_file}}', 'galleryId', '{{%gallery}}', 45 | 'id','CASCADE','RESTRICT'); 46 | } 47 | 48 | public function down() 49 | { 50 | echo "m150407_084217_gallery cannot be reverted.\n"; 51 | 52 | $this->dropTable('{{%gallery}}'); 53 | $this->dropTable('{{%gallery_file}}'); 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /models/Gallery.php: -------------------------------------------------------------------------------- 1 | [ 40 | 'class' => TimestampBehavior::className(), 41 | 'createdAtAttribute' => 'created', 42 | 'updatedAtAttribute' => 'updated', 43 | 'value' => new Expression('NOW()'), 44 | ] 45 | ]; 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | */ 51 | public function rules() 52 | { 53 | return [ 54 | ['name', 'required'], 55 | ['status', 'integer'], 56 | [['created', 'updated'], 'safe'], 57 | ['name', 'string', 'max' => 50], 58 | ]; 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function attributeLabels() 65 | { 66 | return [ 67 | 'id' => Module::t('default', 'ID'), 68 | 'name' => Module::t('default', 'NAME'), 69 | 'status' => Module::t('default', 'STATUS'), 70 | 'created' => Module::t('default', 'CREATED'), 71 | 'updated' => Module::t('default', 'UPDATED'), 72 | ]; 73 | } 74 | 75 | public function beforeSave($insert) 76 | { 77 | if (parent::beforeSave($insert)) { 78 | $this->updatePositions(); 79 | 80 | return true; 81 | } else { 82 | return false; 83 | } 84 | } 85 | 86 | public function afterSave($insert, $changedAttributes) 87 | { 88 | $baseDir = Yii::getAlias(Module::getInstance()->basePath); 89 | 90 | if (!is_dir($baseDir)) { 91 | mkdir($baseDir); 92 | } 93 | 94 | $dir = $baseDir . DIRECTORY_SEPARATOR . $this->id; 95 | 96 | if (!is_dir($dir)) { 97 | mkdir($dir); 98 | } 99 | } 100 | 101 | public function afterDelete() 102 | { 103 | $this->removeModelDirectory(); 104 | } 105 | 106 | /** 107 | * @inheritdoc 108 | * @return Query 109 | */ 110 | public static function find() 111 | { 112 | return new Query(get_called_class()); 113 | } 114 | 115 | /** 116 | * Remove model directory with all files 117 | */ 118 | private function removeModelDirectory() 119 | { 120 | $dir = Yii::getAlias(Module::getInstance()->basePath . DIRECTORY_SEPARATOR . $this->id); 121 | $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); 122 | $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); 123 | 124 | foreach($files as $file) { 125 | if ($file->isDir()){ 126 | rmdir($file->getRealPath()); 127 | } else { 128 | unlink($file->getRealPath()); 129 | } 130 | } 131 | 132 | rmdir($dir); 133 | } 134 | 135 | /** 136 | * Update image position 137 | * @return bool|int 138 | * @throws \yii\db\Exception 139 | */ 140 | private function updatePositions() 141 | { 142 | if (!isset($_POST['positions']) || empty($_POST['positions'])) { 143 | return false; 144 | } 145 | 146 | $positions = explode('|', $_POST['positions']); 147 | 148 | if (!count($positions)) { 149 | return false; 150 | } 151 | 152 | $when = ''; 153 | 154 | foreach ($positions as $key => $value) { 155 | $when .= ' WHEN ' . $value . ' THEN ' . $key; 156 | $where[] = $value; 157 | } 158 | 159 | $sql = 'UPDATE {{%gallery_file}} SET position = CASE id' . $when . ' END WHERE id IN (' . implode(', ', $where) . ')'; 160 | 161 | $command = self::getDb()->createCommand($sql); 162 | 163 | return $command->execute(); 164 | } 165 | 166 | /** 167 | * @return \yii\db\ActiveQuery 168 | */ 169 | public function getFiles() 170 | { 171 | return $this->hasMany(GalleryFile::className(), ['galleryId' => 'id']) 172 | ->orderBy([ 173 | 'position' => SORT_ASC 174 | ]); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /models/GalleryFile.php: -------------------------------------------------------------------------------- 1 | 0], 36 | [['caption', 'file'], 'string', 'max' => 255] 37 | ]; 38 | } 39 | 40 | /** 41 | * Get file src 42 | * @return string 43 | */ 44 | public function getSrc() 45 | { 46 | $path = DIRECTORY_SEPARATOR . preg_replace('/^@(\w+)\//i', '', Yii::$app->getModule('gallery')->basePath); 47 | 48 | return FileHelper::normalizePath($path . DIRECTORY_SEPARATOR . $this->galleryId . DIRECTORY_SEPARATOR . $this->file, DIRECTORY_SEPARATOR); 49 | } 50 | 51 | /** 52 | * Get file path 53 | * @return bool|string 54 | */ 55 | public function getPath() 56 | { 57 | return FileHelper::normalizePath(Yii::getAlias(Yii::$app->getModule('gallery')->basePath . DIRECTORY_SEPARATOR 58 | . $this->galleryId . DIRECTORY_SEPARATOR . $this->file)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /models/GallerySearch.php: -------------------------------------------------------------------------------- 1 | $query, 46 | 'sort'=> [ 47 | 'defaultOrder' => [ 48 | 'created' => SORT_DESC 49 | ], 50 | ] 51 | ]); 52 | 53 | if (!($this->load($params) && $this->validate())) { 54 | return $dataProvider; 55 | } 56 | 57 | $query->andFilterWhere([ 58 | 'id' => $this->id, 59 | 'status' => $this->status, 60 | ]); 61 | 62 | $query->andFilterWhere(['like', 'name', $this->name]); 63 | 64 | return $dataProvider; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /models/Query.php: -------------------------------------------------------------------------------- 1 | andWhere(['status' => $state]); 18 | 19 | return $this; 20 | } 21 | } -------------------------------------------------------------------------------- /views/gallery/_form.php: -------------------------------------------------------------------------------- 1 | isNewRecord) { 13 | $this->registerJs(' 14 | var el = document.getElementById("images"); 15 | 16 | var sortable = Sortable.create(el, { 17 | dataIdAttr: "data-id", 18 | handle: ".handle", 19 | onEnd: function (evt) { 20 | calculatePositions(); 21 | }, 22 | }); 23 | 24 | $(document).on("click", ".remove", function(e) { 25 | var parent = $(this).closest(".image"); 26 | var id = parent.attr("data-id"); 27 | 28 | $.ajax({ 29 | type: "POST", 30 | url: "' . Yii::$app->urlManager->createUrl(['/gallery/gallery/remove']) . '", 31 | data: {"id": id}, 32 | beforeSend: function () { 33 | parent.addClass("preload"); 34 | }, 35 | success: function(response) { 36 | parent.removeClass("preload"); 37 | 38 | if (response) { 39 | calculatePositions(); 40 | 41 | parent.fadeOut(500, function() { 42 | $(this).remove() 43 | }); 44 | } 45 | } 46 | }); 47 | }); 48 | 49 | $(document).on("click", ".edit", function(e) { 50 | var parent = $(this).closest(".image"); 51 | var id = parent.attr("data-id"); 52 | var caption = parent.find("input[type=text]").val(); 53 | 54 | $.ajax({ 55 | type: "POST", 56 | url: "' . Yii::$app->urlManager->createUrl(['/gallery/gallery/caption']) . '", 57 | data: {"id": id, "caption": caption}, 58 | beforeSend: function () { 59 | parent.addClass("preload"); 60 | }, 61 | success: function(response) { 62 | parent.removeClass("preload"); 63 | parent.find(".fancy").attr("title", caption); 64 | } 65 | }); 66 | }); 67 | 68 | $(".fancy").fancybox({ 69 | padding: 0, 70 | helpers: { 71 | overlay: { 72 | locked: false 73 | }, 74 | title : { 75 | type : "over" 76 | } 77 | } 78 | }); 79 | 80 | function calculatePositions() { 81 | var order = sortable.toArray(); 82 | var orderJoin = order.join("|"); 83 | 84 | $("#positions").val(orderJoin); 85 | } 86 | '); 87 | } 88 | 89 | ?> 90 | 91 | -------------------------------------------------------------------------------- /views/gallery/_image.php: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 |
    10 |
    11 | 12 |
    13 | 14 |
    15 | caption, [ 16 | 'class' => 'form-control', 17 | 'placeholder' => Module::t('default', 'CAPTION') 18 | ]); ?> 19 | 20 |
    21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    29 |
    30 |
    31 |
    -------------------------------------------------------------------------------- /views/gallery/create.php: -------------------------------------------------------------------------------- 1 | title = Module::t('default', 'CREATE'); 7 | $this->params['breadcrumbs'] = [ 8 | ['label' => Module::t('default', 'GALLERIES'), 'url' => ['index']], 9 | $this->title 10 | ]; 11 | 12 | ?> 13 | 14 | 27 | -------------------------------------------------------------------------------- /views/gallery/index.php: -------------------------------------------------------------------------------- 1 | title = Module::t('default', 'GALLERY_MANAGER'); 8 | $this->params['breadcrumbs'] = [ 9 | Module::t('default', 'GALLERIES') 10 | ]; 11 | 12 | ?> 13 | 14 | 57 | -------------------------------------------------------------------------------- /views/gallery/update.php: -------------------------------------------------------------------------------- 1 | title = Module::t('default', 'UPDATE') . ' ' . Html::encode($model->name); 7 | $this->params['breadcrumbs'] = [ 8 | ['label' => Module::t('default', 'GALLERIES'), 'url' => ['index']], 9 | ['label' => Html::encode($model->name), 'url' => ['view', 'id' => $model->id]], 10 | Module::t('default', 'UPDATE') 11 | ]; 12 | 13 | ?> 14 | 15 | 38 | -------------------------------------------------------------------------------- /views/gallery/view.php: -------------------------------------------------------------------------------- 1 | title = Html::encode($model->name); 8 | $this->params['breadcrumbs'] = [ 9 | ['label' => Module::t('default', 'GALLERIES'), 'url' => ['index']], 10 | $this->title 11 | ]; 12 | 13 | ?> 14 | 15 | 53 | -------------------------------------------------------------------------------- /widgets/Gallery.php: -------------------------------------------------------------------------------- 1 | sql = 'SELECT MAX(updated) FROM {{%gallery}}'; 35 | 36 | $model = BaseGallery::getDb()->cache(function () { 37 | return BaseGallery::find() 38 | ->where('id = :id', [ 39 | ':id' => $this->galleryId 40 | ]) 41 | ->active() 42 | ->one(); 43 | }, Yii::$app->getModule('gallery')->queryCacheDuration, $dependency); 44 | 45 | if (is_null($model) || !count($model->files)) { 46 | return false; 47 | } 48 | 49 | if (!is_null($this->template)) { 50 | return $this->render($this->template, [ 51 | 'model' => $model, 52 | 'models' => $model->files 53 | ]); 54 | } else { 55 | return $this->getDefaultGallery($model); 56 | } 57 | } 58 | 59 | /** 60 | * Get default gallery style image/caption 61 | * @param $model 62 | * @return string 63 | */ 64 | private function getDefaultGallery($model) 65 | { 66 | $html = Html::beginTag('div', [ 67 | 'class' => 'content-gallery default gallery-' . $model->id, 68 | ]); 69 | 70 | foreach ($model->files as $value) { 71 | $html .= Html::beginTag('div'); 72 | $html .= Html::img($value->src, [ 73 | 'alt' => $this->caption ? $value->caption : null, 74 | 'class' => 'img-responsive' 75 | ]); 76 | 77 | if ($this->caption) { 78 | $html .= Html::tag('div', $value->caption, [ 79 | 'class' => 'caption' 80 | ]); 81 | } 82 | 83 | $html .= Html::endTag('div'); 84 | } 85 | 86 | $html .= Html::endTag('div'); 87 | 88 | return $html; 89 | } 90 | } --------------------------------------------------------------------------------