├── .gitattributes ├── .gitignore ├── app ├── commands │ └── .gitkeep ├── config │ ├── app.php │ ├── auth.php │ ├── cache.php │ ├── compile.php │ ├── database.php │ ├── mail.php │ ├── packages │ │ └── .gitkeep │ ├── queue.php │ ├── remote.php │ ├── session.php │ ├── testing │ │ ├── cache.php │ │ └── session.php │ ├── view.php │ └── workbench.php ├── controllers │ ├── .gitkeep │ ├── BaseController.php │ ├── HomeController.php │ └── mrc │ │ └── CategoryController.php ├── database │ ├── migrations │ │ ├── .gitkeep │ │ └── 2014_01_17_003928_create_category_table.php │ ├── production.sqlite │ └── seeds │ │ ├── .gitkeep │ │ └── DatabaseSeeder.php ├── filters.php ├── lang │ └── en │ │ ├── pagination.php │ │ ├── reminders.php │ │ └── validation.php ├── models │ ├── User.php │ └── mrc │ │ └── Category.php ├── routes.php ├── start │ ├── artisan.php │ ├── global.php │ └── local.php ├── storage │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── logs │ │ └── .gitignore │ ├── meta │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore ├── tests │ ├── ExampleTest.php │ └── TestCase.php └── views │ ├── emails │ └── auth │ │ └── reminder.blade.php │ ├── hello.php │ └── mrc │ └── category.blade.php ├── artisan ├── bootstrap ├── autoload.php ├── paths.php └── start.php ├── composer.json ├── demo.gif ├── phpunit.xml ├── public ├── .htaccess ├── assets-dev │ ├── css │ │ ├── alertify │ │ │ ├── alertify.core.css │ │ │ └── alertify.default.css │ │ ├── editable │ │ │ ├── img │ │ │ │ ├── clear.png │ │ │ │ └── loading.gif │ │ │ └── jquery-editable.css │ │ ├── jqtree │ │ │ ├── img │ │ │ │ └── jqtree-circle.png │ │ │ └── jqtree.css │ │ └── style.css │ └── js │ │ ├── 1.jquery │ │ └── jquery-2.0.3.js │ │ ├── 2.alertify │ │ └── alertify.js │ │ ├── 3.jqtree │ │ └── tree.jquery.js │ │ ├── 4.editable │ │ └── jquery-editable-poshytip.js │ │ └── site.js ├── assets │ ├── img │ │ ├── clear.png │ │ ├── jqtree-circle.png │ │ └── loading.gif │ ├── site.css │ ├── site.min.css │ └── site.min.js ├── favicon.ico ├── gruntfile.js ├── index.php ├── package.json ├── packages │ └── .gitkeep └── robots.txt ├── readme.md └── server.php /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap/compiled.php 2 | /vendor 3 | composer.phar 4 | composer.lock 5 | .DS_Store 6 | Thumbs.db -------------------------------------------------------------------------------- /app/commands/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/app/commands/.gitkeep -------------------------------------------------------------------------------- /app/config/app.php: -------------------------------------------------------------------------------- 1 | true, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application URL 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This URL is used by the console to properly generate URLs when using 24 | | the Artisan command line tool. You should set this to the root of 25 | | your application so that it is used when running Artisan tasks. 26 | | 27 | */ 28 | 29 | 'url' => 'http://localhost', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application Timezone 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may specify the default timezone for your application, which 37 | | will be used by the PHP date and date-time functions. We have gone 38 | | ahead and set this to a sensible default for you out of the box. 39 | | 40 | */ 41 | 42 | 'timezone' => 'UTC', 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Application Locale Configuration 47 | |-------------------------------------------------------------------------- 48 | | 49 | | The application locale determines the default locale that will be used 50 | | by the translation service provider. You are free to set this value 51 | | to any of the locales which will be supported by the application. 52 | | 53 | */ 54 | 55 | 'locale' => 'en', 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Encryption Key 60 | |-------------------------------------------------------------------------- 61 | | 62 | | This key is used by the Illuminate encrypter service and should be set 63 | | to a random, 32 character string, otherwise these encrypted strings 64 | | will not be safe. Please do this before deploying an application! 65 | | 66 | */ 67 | 68 | 'key' => 'YourSecretKey!!!', 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Autoloaded Service Providers 73 | |-------------------------------------------------------------------------- 74 | | 75 | | The service providers listed here will be automatically loaded on the 76 | | request to your application. Feel free to add your own services to 77 | | this array to grant expanded functionality to your applications. 78 | | 79 | */ 80 | 81 | 'providers' => array( 82 | 83 | 'Illuminate\Foundation\Providers\ArtisanServiceProvider', 84 | 'Illuminate\Auth\AuthServiceProvider', 85 | 'Illuminate\Cache\CacheServiceProvider', 86 | 'Illuminate\Session\CommandsServiceProvider', 87 | 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider', 88 | 'Illuminate\Routing\ControllerServiceProvider', 89 | 'Illuminate\Cookie\CookieServiceProvider', 90 | 'Illuminate\Database\DatabaseServiceProvider', 91 | 'Illuminate\Encryption\EncryptionServiceProvider', 92 | 'Illuminate\Filesystem\FilesystemServiceProvider', 93 | 'Illuminate\Hashing\HashServiceProvider', 94 | 'Illuminate\Html\HtmlServiceProvider', 95 | 'Illuminate\Log\LogServiceProvider', 96 | 'Illuminate\Mail\MailServiceProvider', 97 | 'Illuminate\Database\MigrationServiceProvider', 98 | 'Illuminate\Pagination\PaginationServiceProvider', 99 | 'Illuminate\Queue\QueueServiceProvider', 100 | 'Illuminate\Redis\RedisServiceProvider', 101 | 'Illuminate\Remote\RemoteServiceProvider', 102 | 'Illuminate\Auth\Reminders\ReminderServiceProvider', 103 | 'Illuminate\Database\SeedServiceProvider', 104 | 'Illuminate\Session\SessionServiceProvider', 105 | 'Illuminate\Translation\TranslationServiceProvider', 106 | 'Illuminate\Validation\ValidationServiceProvider', 107 | 'Illuminate\View\ViewServiceProvider', 108 | 'Illuminate\Workbench\WorkbenchServiceProvider', 109 | 110 | ), 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Service Provider Manifest 115 | |-------------------------------------------------------------------------- 116 | | 117 | | The service provider manifest is used by Laravel to lazy load service 118 | | providers which are not needed for each request, as well to keep a 119 | | list of all of the services. Here, you may set its storage spot. 120 | | 121 | */ 122 | 123 | 'manifest' => storage_path().'/meta', 124 | 125 | /* 126 | |-------------------------------------------------------------------------- 127 | | Class Aliases 128 | |-------------------------------------------------------------------------- 129 | | 130 | | This array of class aliases will be registered when this application 131 | | is started. However, feel free to register as many as you wish as 132 | | the aliases are "lazy" loaded so they don't hinder performance. 133 | | 134 | */ 135 | 136 | 'aliases' => array( 137 | 138 | 'App' => 'Illuminate\Support\Facades\App', 139 | 'Artisan' => 'Illuminate\Support\Facades\Artisan', 140 | 'Auth' => 'Illuminate\Support\Facades\Auth', 141 | 'Blade' => 'Illuminate\Support\Facades\Blade', 142 | 'Cache' => 'Illuminate\Support\Facades\Cache', 143 | 'ClassLoader' => 'Illuminate\Support\ClassLoader', 144 | 'Config' => 'Illuminate\Support\Facades\Config', 145 | 'Controller' => 'Illuminate\Routing\Controller', 146 | 'Cookie' => 'Illuminate\Support\Facades\Cookie', 147 | 'Crypt' => 'Illuminate\Support\Facades\Crypt', 148 | 'DB' => 'Illuminate\Support\Facades\DB', 149 | 'Eloquent' => 'Illuminate\Database\Eloquent\Model', 150 | 'Event' => 'Illuminate\Support\Facades\Event', 151 | 'File' => 'Illuminate\Support\Facades\File', 152 | 'Form' => 'Illuminate\Support\Facades\Form', 153 | 'Hash' => 'Illuminate\Support\Facades\Hash', 154 | 'HTML' => 'Illuminate\Support\Facades\HTML', 155 | 'Input' => 'Illuminate\Support\Facades\Input', 156 | 'Lang' => 'Illuminate\Support\Facades\Lang', 157 | 'Log' => 'Illuminate\Support\Facades\Log', 158 | 'Mail' => 'Illuminate\Support\Facades\Mail', 159 | 'Paginator' => 'Illuminate\Support\Facades\Paginator', 160 | 'Password' => 'Illuminate\Support\Facades\Password', 161 | 'Queue' => 'Illuminate\Support\Facades\Queue', 162 | 'Redirect' => 'Illuminate\Support\Facades\Redirect', 163 | 'Redis' => 'Illuminate\Support\Facades\Redis', 164 | 'Request' => 'Illuminate\Support\Facades\Request', 165 | 'Response' => 'Illuminate\Support\Facades\Response', 166 | 'Route' => 'Illuminate\Support\Facades\Route', 167 | 'Schema' => 'Illuminate\Support\Facades\Schema', 168 | 'Seeder' => 'Illuminate\Database\Seeder', 169 | 'Session' => 'Illuminate\Support\Facades\Session', 170 | 'SSH' => 'Illuminate\Support\Facades\SSH', 171 | 'Str' => 'Illuminate\Support\Str', 172 | 'URL' => 'Illuminate\Support\Facades\URL', 173 | 'Validator' => 'Illuminate\Support\Facades\Validator', 174 | 'View' => 'Illuminate\Support\Facades\View', 175 | 176 | ), 177 | 178 | ); 179 | -------------------------------------------------------------------------------- /app/config/auth.php: -------------------------------------------------------------------------------- 1 | 'eloquent', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Authentication Model 23 | |-------------------------------------------------------------------------- 24 | | 25 | | When using the "Eloquent" authentication driver, we need to know which 26 | | Eloquent model should be used to retrieve your users. Of course, it 27 | | is often just the "User" model but you may use whatever you like. 28 | | 29 | */ 30 | 31 | 'model' => 'User', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Authentication Table 36 | |-------------------------------------------------------------------------- 37 | | 38 | | When using the "Database" authentication driver, we need to know which 39 | | table should be used to retrieve your users. We have chosen a basic 40 | | default value but you may easily change it to any table you like. 41 | | 42 | */ 43 | 44 | 'table' => 'users', 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Password Reminder Settings 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Here you may set the settings for password reminders, including a view 52 | | that should be used as your password reminder e-mail. You will also 53 | | be able to set the name of the table that holds the reset tokens. 54 | | 55 | | The "expire" time is the number of minutes that the reminder should be 56 | | considered valid. This security feature keeps tokens short-lived so 57 | | they have less time to be guessed. You may change this as needed. 58 | | 59 | */ 60 | 61 | 'reminder' => array( 62 | 63 | 'email' => 'emails.auth.reminder', 64 | 65 | 'table' => 'password_reminders', 66 | 67 | 'expire' => 60, 68 | 69 | ), 70 | 71 | ); 72 | -------------------------------------------------------------------------------- /app/config/cache.php: -------------------------------------------------------------------------------- 1 | 'file', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | File Cache Location 23 | |-------------------------------------------------------------------------- 24 | | 25 | | When using the "file" cache driver, we need a location where the cache 26 | | files may be stored. A sensible default has been specified, but you 27 | | are free to change it to any other place on disk that you desire. 28 | | 29 | */ 30 | 31 | 'path' => storage_path().'/cache', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Database Cache Connection 36 | |-------------------------------------------------------------------------- 37 | | 38 | | When using the "database" cache driver you may specify the connection 39 | | that should be used to store the cached items. When this option is 40 | | null the default database connection will be utilized for cache. 41 | | 42 | */ 43 | 44 | 'connection' => null, 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Database Cache Table 49 | |-------------------------------------------------------------------------- 50 | | 51 | | When using the "database" cache driver we need to know the table that 52 | | should be used to store the cached items. A default table name has 53 | | been provided but you're free to change it however you deem fit. 54 | | 55 | */ 56 | 57 | 'table' => 'cache', 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Memcached Servers 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Now you may specify an array of your Memcached servers that should be 65 | | used when utilizing the Memcached cache driver. All of the servers 66 | | should contain a value for "host", "port", and "weight" options. 67 | | 68 | */ 69 | 70 | 'memcached' => array( 71 | 72 | array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100), 73 | 74 | ), 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Cache Key Prefix 79 | |-------------------------------------------------------------------------- 80 | | 81 | | When utilizing a RAM based store such as APC or Memcached, there might 82 | | be other applications utilizing the same cache. So, we'll specify a 83 | | value to get prefixed to all our keys so we can avoid collisions. 84 | | 85 | */ 86 | 87 | 'prefix' => 'laravel', 88 | 89 | ); 90 | -------------------------------------------------------------------------------- /app/config/compile.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Database Connection Name 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify which of the database connections below you wish 24 | | to use as your default connection for all database work. Of course 25 | | you may use many connections at once using the Database library. 26 | | 27 | */ 28 | 29 | 'default' => 'mysql', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Database Connections 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here are each of the database connections setup for your application. 37 | | Of course, examples of configuring each database platform that is 38 | | supported by Laravel is shown below to make development simple. 39 | | 40 | | 41 | | All database work in Laravel is done through the PHP PDO facilities 42 | | so make sure you have the driver for your particular database of 43 | | choice installed on your machine before you begin development. 44 | | 45 | */ 46 | 47 | 'connections' => array( 48 | 49 | 'sqlite' => array( 50 | 'driver' => 'sqlite', 51 | 'database' => __DIR__.'/../database/production.sqlite', 52 | 'prefix' => '', 53 | ), 54 | 55 | 'mysql' => array( 56 | 'driver' => 'mysql', 57 | 'host' => 'localhost', 58 | 'database' => 'database', 59 | 'username' => 'root', 60 | 'password' => '', 61 | 'charset' => 'utf8', 62 | 'collation' => 'utf8_unicode_ci', 63 | 'prefix' => '', 64 | ), 65 | 66 | 'pgsql' => array( 67 | 'driver' => 'pgsql', 68 | 'host' => 'localhost', 69 | 'database' => 'database', 70 | 'username' => 'root', 71 | 'password' => '', 72 | 'charset' => 'utf8', 73 | 'prefix' => '', 74 | 'schema' => 'public', 75 | ), 76 | 77 | 'sqlsrv' => array( 78 | 'driver' => 'sqlsrv', 79 | 'host' => 'localhost', 80 | 'database' => 'database', 81 | 'username' => 'root', 82 | 'password' => '', 83 | 'prefix' => '', 84 | ), 85 | 86 | ), 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Migration Repository Table 91 | |-------------------------------------------------------------------------- 92 | | 93 | | This table keeps track of all the migrations that have already run for 94 | | your application. Using this information, we can determine which of 95 | | the migrations on disk haven't actually been run in the database. 96 | | 97 | */ 98 | 99 | 'migrations' => 'migrations', 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Redis Databases 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Redis is an open source, fast, and advanced key-value store that also 107 | | provides a richer set of commands than a typical key-value systems 108 | | such as APC or Memcached. Laravel makes it easy to dig right in. 109 | | 110 | */ 111 | 112 | 'redis' => array( 113 | 114 | 'cluster' => false, 115 | 116 | 'default' => array( 117 | 'host' => '127.0.0.1', 118 | 'port' => 6379, 119 | 'database' => 0, 120 | ), 121 | 122 | ), 123 | 124 | ); 125 | -------------------------------------------------------------------------------- /app/config/mail.php: -------------------------------------------------------------------------------- 1 | 'smtp', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | SMTP Host Address 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may provide the host address of the SMTP server used by your 26 | | applications. A default option is provided that is compatible with 27 | | the Postmark mail service, which will provide reliable delivery. 28 | | 29 | */ 30 | 31 | 'host' => 'smtp.mailgun.org', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | SMTP Host Port 36 | |-------------------------------------------------------------------------- 37 | | 38 | | This is the SMTP port used by your application to delivery e-mails to 39 | | users of your application. Like the host we have set this value to 40 | | stay compatible with the Postmark e-mail application by default. 41 | | 42 | */ 43 | 44 | 'port' => 587, 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Global "From" Address 49 | |-------------------------------------------------------------------------- 50 | | 51 | | You may wish for all e-mails sent by your application to be sent from 52 | | the same address. Here, you may specify a name and address that is 53 | | used globally for all e-mails that are sent by your application. 54 | | 55 | */ 56 | 57 | 'from' => array('address' => null, 'name' => null), 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | E-Mail Encryption Protocol 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Here you may specify the encryption protocol that should be used when 65 | | the application send e-mail messages. A sensible default using the 66 | | transport layer security protocol should provide great security. 67 | | 68 | */ 69 | 70 | 'encryption' => 'tls', 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | SMTP Server Username 75 | |-------------------------------------------------------------------------- 76 | | 77 | | If your SMTP server requires a username for authentication, you should 78 | | set it here. This will get used to authenticate with your server on 79 | | connection. You may also set the "password" value below this one. 80 | | 81 | */ 82 | 83 | 'username' => null, 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | SMTP Server Password 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Here you may set the password required by your SMTP server to send out 91 | | messages from your application. This will be given to the server on 92 | | connection so that the application will be able to send messages. 93 | | 94 | */ 95 | 96 | 'password' => null, 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Sendmail System Path 101 | |-------------------------------------------------------------------------- 102 | | 103 | | When using the "sendmail" driver to send e-mails, we will need to know 104 | | the path to where Sendmail lives on this server. A default path has 105 | | been provided here, which will work well on most of your systems. 106 | | 107 | */ 108 | 109 | 'sendmail' => '/usr/sbin/sendmail -bs', 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Mail "Pretend" 114 | |-------------------------------------------------------------------------- 115 | | 116 | | When this option is enabled, e-mail will not actually be sent over the 117 | | web and will instead be written to your application's logs files so 118 | | you may inspect the message. This is great for local development. 119 | | 120 | */ 121 | 122 | 'pretend' => false, 123 | 124 | ); -------------------------------------------------------------------------------- /app/config/packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/app/config/packages/.gitkeep -------------------------------------------------------------------------------- /app/config/queue.php: -------------------------------------------------------------------------------- 1 | 'sync', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => array( 32 | 33 | 'sync' => array( 34 | 'driver' => 'sync', 35 | ), 36 | 37 | 'beanstalkd' => array( 38 | 'driver' => 'beanstalkd', 39 | 'host' => 'localhost', 40 | 'queue' => 'default', 41 | ), 42 | 43 | 'sqs' => array( 44 | 'driver' => 'sqs', 45 | 'key' => 'your-public-key', 46 | 'secret' => 'your-secret-key', 47 | 'queue' => 'your-queue-url', 48 | 'region' => 'us-east-1', 49 | ), 50 | 51 | 'iron' => array( 52 | 'driver' => 'iron', 53 | 'project' => 'your-project-id', 54 | 'token' => 'your-token', 55 | 'queue' => 'your-queue-name', 56 | ), 57 | 58 | 'redis' => array( 59 | 'driver' => 'redis', 60 | 'queue' => 'default', 61 | ), 62 | 63 | ), 64 | 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | Failed Queue Jobs 68 | |-------------------------------------------------------------------------- 69 | | 70 | | These options configure the behavior of failed queue job logging so you 71 | | can control which database and table are used to store the jobs that 72 | | have failed. You may change them to any database / table you wish. 73 | | 74 | */ 75 | 76 | 'failed' => array( 77 | 78 | 'database' => 'mysql', 'table' => 'failed_jobs', 79 | 80 | ), 81 | 82 | ); 83 | -------------------------------------------------------------------------------- /app/config/remote.php: -------------------------------------------------------------------------------- 1 | 'production', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Remote Server Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | These are the servers that will be accessible via the SSH task runner 24 | | facilities of Laravel. This feature radically simplifies executing 25 | | tasks on your servers, such as deploying out these applications. 26 | | 27 | */ 28 | 29 | 'connections' => array( 30 | 31 | 'production' => array( 32 | 'host' => '', 33 | 'username' => '', 34 | 'password' => '', 35 | 'key' => '', 36 | 'keyphrase' => '', 37 | 'root' => '/var/www', 38 | ), 39 | 40 | ), 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Remote Server Groups 45 | |-------------------------------------------------------------------------- 46 | | 47 | | Here you may list connections under a single group name, which allows 48 | | you to easily access all of the servers at once using a short name 49 | | that is extremely easy to remember, such as "web" or "database". 50 | | 51 | */ 52 | 53 | 'groups' => array( 54 | 55 | 'web' => array('production') 56 | 57 | ), 58 | 59 | ); -------------------------------------------------------------------------------- /app/config/session.php: -------------------------------------------------------------------------------- 1 | 'file', 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session Lifetime 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify the number of minutes that you wish the session 27 | | to be allowed to remain idle before it expires. If you want them 28 | | to immediately expire on the browser closing, set that option. 29 | | 30 | */ 31 | 32 | 'lifetime' => 120, 33 | 34 | 'expire_on_close' => false, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Session File Location 39 | |-------------------------------------------------------------------------- 40 | | 41 | | When using the native session driver, we need a location where session 42 | | files may be stored. A default has been set for you but a different 43 | | location may be specified. This is only needed for file sessions. 44 | | 45 | */ 46 | 47 | 'files' => storage_path().'/sessions', 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Session Database Connection 52 | |-------------------------------------------------------------------------- 53 | | 54 | | When using the "database" or "redis" session drivers, you may specify a 55 | | connection that should be used to manage these sessions. This should 56 | | correspond to a connection in your database configuration options. 57 | | 58 | */ 59 | 60 | 'connection' => null, 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Session Database Table 65 | |-------------------------------------------------------------------------- 66 | | 67 | | When using the "database" session driver, you may specify the table we 68 | | should use to manage the sessions. Of course, a sensible default is 69 | | provided for you; however, you are free to change this as needed. 70 | | 71 | */ 72 | 73 | 'table' => 'sessions', 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Session Sweeping Lottery 78 | |-------------------------------------------------------------------------- 79 | | 80 | | Some session drivers must manually sweep their storage location to get 81 | | rid of old sessions from storage. Here are the chances that it will 82 | | happen on a given request. By default, the odds are 2 out of 100. 83 | | 84 | */ 85 | 86 | 'lottery' => array(2, 100), 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Session Cookie Name 91 | |-------------------------------------------------------------------------- 92 | | 93 | | Here you may change the name of the cookie used to identify a session 94 | | instance by ID. The name specified here will get used every time a 95 | | new session cookie is created by the framework for every driver. 96 | | 97 | */ 98 | 99 | 'cookie' => 'laravel_session', 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Session Cookie Path 104 | |-------------------------------------------------------------------------- 105 | | 106 | | The session cookie path determines the path for which the cookie will 107 | | be regarded as available. Typically, this will be the root path of 108 | | your application but you are free to change this when necessary. 109 | | 110 | */ 111 | 112 | 'path' => '/', 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Session Cookie Domain 117 | |-------------------------------------------------------------------------- 118 | | 119 | | Here you may change the domain of the cookie used to identify a session 120 | | in your application. This will determine which domains the cookie is 121 | | available to in your application. A sensible default has been set. 122 | | 123 | */ 124 | 125 | 'domain' => null, 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | HTTPS Only Cookies 130 | |-------------------------------------------------------------------------- 131 | | 132 | | By setting this option to true, session cookies will only be sent back 133 | | to the server if the browser has a HTTPS connection. This will keep 134 | | the cookie from being sent to you if it can not be done securely. 135 | | 136 | */ 137 | 138 | 'secure' => false, 139 | 140 | ); 141 | -------------------------------------------------------------------------------- /app/config/testing/cache.php: -------------------------------------------------------------------------------- 1 | 'array', 19 | 20 | ); -------------------------------------------------------------------------------- /app/config/testing/session.php: -------------------------------------------------------------------------------- 1 | 'array', 20 | 21 | ); -------------------------------------------------------------------------------- /app/config/view.php: -------------------------------------------------------------------------------- 1 | array(__DIR__.'/../views'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Pagination View 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This view will be used to render the pagination link output, and can 24 | | be easily customized here to show any view you like. A clean view 25 | | compatible with Twitter's Bootstrap is given to you by default. 26 | | 27 | */ 28 | 29 | 'pagination' => 'pagination::slider-3', 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /app/config/workbench.php: -------------------------------------------------------------------------------- 1 | '', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Workbench Author E-Mail Address 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Like the option above, your e-mail address is used when generating new 24 | | workbench packages. The e-mail is placed in your composer.json file 25 | | automatically after the package is created by the workbench tool. 26 | | 27 | */ 28 | 29 | 'email' => '', 30 | 31 | ); -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | layout)) 13 | { 14 | $this->layout = View::make($this->layout); 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /app/controllers/HomeController.php: -------------------------------------------------------------------------------- 1 | beforeFilter(function() 12 | { 13 | $this->request = (object) Input::all(); 14 | if ( ! Request::ajax() || 15 | ! in_array($this->request->action, ["addCategory", "deleteCategory", "renameCategory", "moveCategory"]) || 16 | (\Validator::make(['name' => $this->request->name], ["name" => ["required", "regex:/^[\w\p{Cyrillic}\040,.-_']+$/u"]])->fails()) 17 | ) App::abort(405); 18 | 19 | }, ["on" => "post"]); 20 | } 21 | 22 | public function getIndex() 23 | { 24 | $categories = Category::withoutRoot()->get(['id', 'title as label', '_lft', '_rgt', 'parent_id'])->toTree(); 25 | return View::make("mrc.category")->with("categoriesData", $categories); 26 | } 27 | 28 | public function postIndex() 29 | { 30 | // start transaction 31 | DB::beginTransaction(); 32 | 33 | switch($this->request->action) { 34 | 35 | case "renameCategory": 36 | $status = Category::where("title", $this->request->originalname) 37 | ->where("id", $this->request->id) 38 | ->update(["title" => $this->request->name]); 39 | break; 40 | 41 | case "addCategory": 42 | $sourceCategory = new Category(['title' => $this->request->name]); 43 | $targetCategory = Category::root(); 44 | 45 | // append category to root 46 | if ( $status = $sourceCategory->appendTo($targetCategory)->save() ) { 47 | DB::commit(); 48 | return ["id" => $sourceCategory->id, "parent_id" => $sourceCategory->parent_id]; 49 | } 50 | break; 51 | 52 | case "deleteCategory": 53 | try { 54 | $category = Category::where("title", $this->request->name) 55 | ->where("id", $this->request->id) 56 | ->firstOrFail(); 57 | $status = $category->delete(); 58 | } catch (\Exception $e) { 59 | $status = false; 60 | } 61 | break; 62 | 63 | case "moveCategory": 64 | // get source/target categories from DB 65 | $sourceCategory = Category::find($this->request->id); 66 | $targetCategory = Category::find($this->request->to); 67 | 68 | // check for data consistency (can also do a try&catch instead) 69 | if ($sourceCategory && $targetCategory && ($sourceCategory->parent_id == $this->request->parent_id)) { 70 | switch ($this->request->direction) { 71 | case "inside" : 72 | $status = $sourceCategory->prependTo($targetCategory)->save(); 73 | break; 74 | case "before" : 75 | $status = $sourceCategory->before($targetCategory)->save(); 76 | break; 77 | case "after" : 78 | $status = $sourceCategory->after($targetCategory)->save(); 79 | break; 80 | } 81 | } 82 | break; 83 | } 84 | if (!isset($status) || $status == null) { DB::rollback(); App::abort(400); } 85 | DB::commit(); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /app/database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/app/database/migrations/.gitkeep -------------------------------------------------------------------------------- /app/database/migrations/2014_01_17_003928_create_category_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('title'); 19 | $table->timestamps(); 20 | NestedSet::columns($table); 21 | }); 22 | 23 | // The root node is required 24 | NestedSet::createRoot('mrc_category', array( 25 | 'title' => 'Root', 26 | )); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::drop('mrc_category'); 37 | } 38 | } -------------------------------------------------------------------------------- /app/database/production.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/app/database/production.sqlite -------------------------------------------------------------------------------- /app/database/seeds/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/app/database/seeds/.gitkeep -------------------------------------------------------------------------------- /app/database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call('UserTableSeeder'); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/filters.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 18 | 'next' => 'Next »', 19 | 20 | ); -------------------------------------------------------------------------------- /app/lang/en/reminders.php: -------------------------------------------------------------------------------- 1 | "Passwords must be at least six characters and match the confirmation.", 17 | 18 | "user" => "We can't find a user with that e-mail address.", 19 | 20 | "token" => "This password reset token is invalid.", 21 | 22 | "sent" => "Password reminder sent!", 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /app/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | "The :attribute must be accepted.", 17 | "active_url" => "The :attribute is not a valid URL.", 18 | "after" => "The :attribute must be a date after :date.", 19 | "alpha" => "The :attribute may only contain letters.", 20 | "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", 21 | "alpha_num" => "The :attribute may only contain letters and numbers.", 22 | "array" => "The :attribute must be an array.", 23 | "before" => "The :attribute must be a date before :date.", 24 | "between" => array( 25 | "numeric" => "The :attribute must be between :min and :max.", 26 | "file" => "The :attribute must be between :min and :max kilobytes.", 27 | "string" => "The :attribute must be between :min and :max characters.", 28 | "array" => "The :attribute must have between :min and :max items.", 29 | ), 30 | "confirmed" => "The :attribute confirmation does not match.", 31 | "date" => "The :attribute is not a valid date.", 32 | "date_format" => "The :attribute does not match the format :format.", 33 | "different" => "The :attribute and :other must be different.", 34 | "digits" => "The :attribute must be :digits digits.", 35 | "digits_between" => "The :attribute must be between :min and :max digits.", 36 | "email" => "The :attribute format is invalid.", 37 | "exists" => "The selected :attribute is invalid.", 38 | "image" => "The :attribute must be an image.", 39 | "in" => "The selected :attribute is invalid.", 40 | "integer" => "The :attribute must be an integer.", 41 | "ip" => "The :attribute must be a valid IP address.", 42 | "max" => array( 43 | "numeric" => "The :attribute may not be greater than :max.", 44 | "file" => "The :attribute may not be greater than :max kilobytes.", 45 | "string" => "The :attribute may not be greater than :max characters.", 46 | "array" => "The :attribute may not have more than :max items.", 47 | ), 48 | "mimes" => "The :attribute must be a file of type: :values.", 49 | "min" => array( 50 | "numeric" => "The :attribute must be at least :min.", 51 | "file" => "The :attribute must be at least :min kilobytes.", 52 | "string" => "The :attribute must be at least :min characters.", 53 | "array" => "The :attribute must have at least :min items.", 54 | ), 55 | "not_in" => "The selected :attribute is invalid.", 56 | "numeric" => "The :attribute must be a number.", 57 | "regex" => "The :attribute format is invalid.", 58 | "required" => "The :attribute field is required.", 59 | "required_if" => "The :attribute field is required when :other is :value.", 60 | "required_with" => "The :attribute field is required when :values is present.", 61 | "required_without" => "The :attribute field is required when :values is not present.", 62 | "same" => "The :attribute and :other must match.", 63 | "size" => array( 64 | "numeric" => "The :attribute must be :size.", 65 | "file" => "The :attribute must be :size kilobytes.", 66 | "string" => "The :attribute must be :size characters.", 67 | "array" => "The :attribute must contain :size items.", 68 | ), 69 | "unique" => "The :attribute has already been taken.", 70 | "url" => "The :attribute format is invalid.", 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Custom Validation Language Lines 75 | |-------------------------------------------------------------------------- 76 | | 77 | | Here you may specify custom validation messages for attributes using the 78 | | convention "attribute.rule" to name the lines. This makes it quick to 79 | | specify a specific custom language line for a given attribute rule. 80 | | 81 | */ 82 | 83 | 'custom' => array(), 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Custom Validation Attributes 88 | |-------------------------------------------------------------------------- 89 | | 90 | | The following language lines are used to swap attribute place-holders 91 | | with something more reader friendly such as E-Mail Address instead 92 | | of "email". This simply helps us make messages a little cleaner. 93 | | 94 | */ 95 | 96 | 'attributes' => array(), 97 | 98 | ); 99 | -------------------------------------------------------------------------------- /app/models/User.php: -------------------------------------------------------------------------------- 1 | getKey(); 30 | } 31 | 32 | /** 33 | * Get the password for the user. 34 | * 35 | * @return string 36 | */ 37 | public function getAuthPassword() 38 | { 39 | return $this->password; 40 | } 41 | 42 | /** 43 | * Get the e-mail address where password reminders are sent. 44 | * 45 | * @return string 46 | */ 47 | public function getReminderEmail() 48 | { 49 | return $this->email; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/models/mrc/Category.php: -------------------------------------------------------------------------------- 1 | client->request('GET', '/'); 13 | 14 | $this->assertTrue($this->client->getResponse()->isOk()); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Password Reset

8 | 9 |
10 | To reset your password, complete this form: {{ URL::to('password/reset', array($token)) }}. 11 |
12 | 13 | -------------------------------------------------------------------------------- /app/views/hello.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Laravel PHP Framework 6 | 35 | 36 | 37 |
38 | 39 |

You have arrived.

40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /app/views/mrc/category.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Add new category 19 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setRequestForConsoleEnvironment(); 45 | 46 | $artisan = Illuminate\Console\Application::start($app); 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Run The Artisan Application 51 | |-------------------------------------------------------------------------- 52 | | 53 | | When we run the console application, the current CLI command will be 54 | | executed in this console and the response sent back to a terminal 55 | | or another output device for the developers. Here goes nothing! 56 | | 57 | */ 58 | 59 | $status = $artisan->run(); 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Shutdown The Application 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Once Artisan has finished running. We will fire off the shutdown events 67 | | so that any final work may be done by the application before we shut 68 | | down the process. This is the last thing to happen to the request. 69 | | 70 | */ 71 | 72 | $app->shutdown(); 73 | 74 | exit($status); -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/../app', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Public Path 21 | |-------------------------------------------------------------------------- 22 | | 23 | | The public path contains the assets for your web application, such as 24 | | your JavaScript and CSS files, and also contains the primary entry 25 | | point for web requests into these applications from the outside. 26 | | 27 | */ 28 | 29 | 'public' => __DIR__.'/../public', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Base Path 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The base path is the root of the Laravel installation. Most likely you 37 | | will not need to change this value. But, if for some wild reason it 38 | | is necessary you will do so here, just proceed with some caution. 39 | | 40 | */ 41 | 42 | 'base' => __DIR__.'/..', 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Storage Path 47 | |-------------------------------------------------------------------------- 48 | | 49 | | The storage path is used by Laravel to store cached Blade views, logs 50 | | and other pieces of information. You may modify the path here when 51 | | you want to change the location of this directory for your apps. 52 | | 53 | */ 54 | 55 | 'storage' => __DIR__.'/../app/storage', 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /bootstrap/start.php: -------------------------------------------------------------------------------- 1 | detectEnvironment(array( 28 | 29 | 'local' => array('your-machine-name'), 30 | 31 | )); 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Bind Paths 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Here we are binding the paths configured in paths.php to the app. You 39 | | should not be changing these here. If you need to change these you 40 | | may do so within the paths.php file and they will be bound here. 41 | | 42 | */ 43 | 44 | $app->bindInstallPaths(require __DIR__.'/paths.php'); 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Load The Application 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Here we will load this Illuminate application. We will keep this in a 52 | | separate location so we can isolate the creation of an application 53 | | from the actual running of the application with a given request. 54 | | 55 | */ 56 | 57 | $framework = $app['path.base'].'/vendor/laravel/framework/src'; 58 | 59 | require $framework.'/Illuminate/Foundation/start.php'; 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Return The Application 64 | |-------------------------------------------------------------------------- 65 | | 66 | | This script returns the application instance. The instance is given to 67 | | the calling script so we can separate the building of the instances 68 | | from the actual running of the application and sending responses. 69 | | 70 | */ 71 | 72 | return $app; 73 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "MIT", 3 | "require": { 4 | "laravel/framework": "4.1.*", 5 | "kalnoy/nestedset": "1.0.*" 6 | }, 7 | "autoload": { 8 | "classmap": [ 9 | "app/commands", 10 | "app/controllers", 11 | "app/models", 12 | "app/database/migrations", 13 | "app/database/seeds" 14 | ] 15 | }, 16 | "scripts": { 17 | "post-install-cmd": [ 18 | "php artisan optimize" 19 | ], 20 | "post-update-cmd": [ 21 | "php artisan clear-compiled", 22 | "php artisan optimize" 23 | ], 24 | "post-create-project-cmd": [ 25 | "php artisan key:generate" 26 | ] 27 | }, 28 | "config": { 29 | "preferred-install": "dist" 30 | }, 31 | "minimum-stability": "dev" 32 | } 33 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/demo.gif -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./app/tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes... 9 | RewriteRule ^(.*)/$ /$1 [L,R=301] 10 | 11 | # Handle Front Controller... 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | RewriteCond %{REQUEST_FILENAME} !-f 14 | RewriteRule ^ index.php [L] 15 | 16 | -------------------------------------------------------------------------------- /public/assets-dev/css/alertify/alertify.core.css: -------------------------------------------------------------------------------- 1 | .alertify, 2 | .alertify-show, 3 | .alertify-log { 4 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 5 | -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 6 | -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 7 | -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 8 | transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */ 9 | } 10 | .alertify-hide { 11 | -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 12 | -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 14 | -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 15 | transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 16 | } 17 | .alertify-log-hide { 18 | -webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 19 | -moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 20 | -ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 21 | -o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 22 | transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 23 | } 24 | .alertify-cover { 25 | position: fixed; z-index: 99999; 26 | top: 0; right: 0; bottom: 0; left: 0; 27 | background-color:white; 28 | filter:alpha(opacity=0); 29 | opacity:0; 30 | } 31 | .alertify-cover-hidden { 32 | display: none; 33 | } 34 | .alertify { 35 | position: fixed; z-index: 99999; 36 | top: 50px; left: 50%; 37 | width: 550px; 38 | margin-left: -275px; 39 | opacity: 1; 40 | } 41 | .alertify-hidden { 42 | -webkit-transform: translate(0,-150px); 43 | -moz-transform: translate(0,-150px); 44 | -ms-transform: translate(0,-150px); 45 | -o-transform: translate(0,-150px); 46 | transform: translate(0,-150px); 47 | opacity: 0; 48 | display: none; 49 | } 50 | /* overwrite display: none; for everything except IE6-8 */ 51 | :root *> .alertify-hidden { 52 | display: block; 53 | visibility: hidden; 54 | } 55 | .alertify-logs { 56 | position: fixed; 57 | z-index: 5000; 58 | bottom: 10px; 59 | right: 10px; 60 | width: 300px; 61 | /* show on top: */ 62 | top: 10px; 63 | right: 10px; 64 | } 65 | .alertify-logs-hidden { 66 | display: none; 67 | } 68 | .alertify-log { 69 | display: block; 70 | margin-top: 10px; 71 | position: relative; 72 | right: -300px; 73 | opacity: 0; 74 | } 75 | .alertify-log-show { 76 | right: 0; 77 | opacity: 1; 78 | } 79 | .alertify-log-hide { 80 | -webkit-transform: translate(300px, 0); 81 | -moz-transform: translate(300px, 0); 82 | -ms-transform: translate(300px, 0); 83 | -o-transform: translate(300px, 0); 84 | transform: translate(300px, 0); 85 | opacity: 0; 86 | } 87 | .alertify-dialog { 88 | padding: 25px; 89 | } 90 | .alertify-resetFocus { 91 | border: 0; 92 | clip: rect(0 0 0 0); 93 | height: 1px; 94 | margin: -1px; 95 | overflow: hidden; 96 | padding: 0; 97 | position: absolute; 98 | width: 1px; 99 | } 100 | .alertify-inner { 101 | text-align: center; 102 | } 103 | .alertify-text { 104 | margin-bottom: 15px; 105 | width: 100%; 106 | -webkit-box-sizing: border-box; 107 | -moz-box-sizing: border-box; 108 | box-sizing: border-box; 109 | font-size: 100%; 110 | } 111 | .alertify-buttons { 112 | } 113 | .alertify-button, 114 | .alertify-button:hover, 115 | .alertify-button:active, 116 | .alertify-button:visited { 117 | background: none; 118 | text-decoration: none; 119 | border: none; 120 | /* line-height and font-size for input button */ 121 | line-height: 1.5; 122 | font-size: 100%; 123 | display: inline-block; 124 | cursor: pointer; 125 | margin-left: 5px; 126 | } 127 | 128 | @media only screen and (max-width: 680px) { 129 | .alertify, 130 | .alertify-logs { 131 | width: 90%; 132 | -webkit-box-sizing: border-box; 133 | -moz-box-sizing: border-box; 134 | box-sizing: border-box; 135 | } 136 | .alertify { 137 | left: 5%; 138 | margin: 0; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /public/assets-dev/css/alertify/alertify.default.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Look and Feel 3 | */ 4 | .alertify, 5 | .alertify-log { 6 | font-family: sans-serif; 7 | } 8 | .alertify { 9 | background: #FFF; 10 | border: 10px solid #333; /* browsers that don't support rgba */ 11 | border: 10px solid rgba(0,0,0,.7); 12 | border-radius: 8px; 13 | box-shadow: 0 3px 3px rgba(0,0,0,.3); 14 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 15 | -moz-background-clip: padding; /* Firefox 3.6 */ 16 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */ 17 | } 18 | .alertify-text { 19 | border: 1px solid #CCC; 20 | padding: 10px; 21 | border-radius: 4px; 22 | } 23 | .alertify-button { 24 | border-radius: 4px; 25 | color: #FFF; 26 | font-weight: bold; 27 | padding: 6px 15px; 28 | text-decoration: none; 29 | text-shadow: 1px 1px 0 rgba(0,0,0,.5); 30 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,.5); 31 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 32 | background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 33 | background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 34 | background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 35 | background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 36 | } 37 | .alertify-button:hover, 38 | .alertify-button:focus { 39 | outline: none; 40 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 41 | background-image: -moz-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 42 | background-image: -ms-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 43 | background-image: -o-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 44 | background-image: linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 45 | } 46 | .alertify-button:focus { 47 | box-shadow: 0 0 15px #2B72D5; 48 | } 49 | .alertify-button:active { 50 | position: relative; 51 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 52 | } 53 | .alertify-button-cancel, 54 | .alertify-button-cancel:hover, 55 | .alertify-button-cancel:focus { 56 | background-color: #FE1A00; 57 | border: 1px solid #D83526; 58 | } 59 | .alertify-button-ok, 60 | .alertify-button-ok:hover, 61 | .alertify-button-ok:focus { 62 | background-color: #5CB811; 63 | border: 1px solid #3B7808; 64 | } 65 | 66 | .alertify-log { 67 | background: #1F1F1F; 68 | background: rgba(0,0,0,.9); 69 | padding: 15px; 70 | border-radius: 4px; 71 | color: #FFF; 72 | text-shadow: -1px -1px 0 rgba(0,0,0,.5); 73 | } 74 | .alertify-log-error { 75 | background: #FE1A00; 76 | background: rgba(254,26,0,.9); 77 | } 78 | .alertify-log-success { 79 | background: #5CB811; 80 | background: rgba(92,184,17,.9); 81 | } -------------------------------------------------------------------------------- /public/assets-dev/css/editable/img/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/assets-dev/css/editable/img/clear.png -------------------------------------------------------------------------------- /public/assets-dev/css/editable/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/assets-dev/css/editable/img/loading.gif -------------------------------------------------------------------------------- /public/assets-dev/css/editable/jquery-editable.css: -------------------------------------------------------------------------------- 1 | /*! X-editable - v1.5.0 2 | * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery 3 | * http://github.com/vitalets/x-editable 4 | * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ 5 | .editableform { 6 | margin-bottom: 0; /* overwrites bootstrap margin */ 7 | } 8 | 9 | .editableform .control-group { 10 | margin-bottom: 0; /* overwrites bootstrap margin */ 11 | white-space: nowrap; /* prevent wrapping buttons on new line */ 12 | line-height: 20px; /* overwriting bootstrap line-height. See #133 */ 13 | } 14 | 15 | .editable-buttons { 16 | display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ 17 | vertical-align: top; 18 | margin-left: 7px; 19 | /* inline-block emulation for IE7*/ 20 | zoom: 1; 21 | *display: inline; 22 | } 23 | 24 | .editable-buttons.editable-buttons-bottom { 25 | display: block; 26 | margin-top: 7px; 27 | margin-left: 0; 28 | } 29 | 30 | .editable-input { 31 | vertical-align: top; 32 | display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ 33 | width: auto; /* bootstrap-responsive has width: 100% that breakes layout */ 34 | white-space: normal; /* reset white-space decalred in parent*/ 35 | /* display-inline emulation for IE7*/ 36 | zoom: 1; 37 | *display: inline; 38 | } 39 | 40 | .editable-buttons .editable-cancel { 41 | margin-left: 7px; 42 | } 43 | 44 | /*for jquery-ui buttons need set height to look more pretty*/ 45 | .editable-buttons button.ui-button-icon-only { 46 | height: 24px; 47 | width: 30px; 48 | } 49 | 50 | .editableform-loading { 51 | background: url('img/loading.gif') center center no-repeat; 52 | height: 25px; 53 | width: auto; 54 | min-width: 25px; 55 | } 56 | 57 | .editable-inline .editableform-loading { 58 | background-position: left 5px; 59 | } 60 | 61 | .editable-error-block { 62 | max-width: 300px; 63 | margin: 5px 0 0 0; 64 | width: auto; 65 | white-space: normal; 66 | } 67 | 68 | /*add padding for jquery ui*/ 69 | .editable-error-block.ui-state-error { 70 | padding: 3px; 71 | } 72 | 73 | .editable-error { 74 | color: red; 75 | } 76 | 77 | /* ---- For specific types ---- */ 78 | 79 | .editableform .editable-date { 80 | padding: 0; 81 | margin: 0; 82 | float: left; 83 | } 84 | 85 | /* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */ 86 | .editable-inline .add-on .icon-th { 87 | margin-top: 3px; 88 | margin-left: 1px; 89 | } 90 | 91 | 92 | /* checklist vertical alignment */ 93 | .editable-checklist label input[type="checkbox"], 94 | .editable-checklist label span { 95 | vertical-align: middle; 96 | margin: 0; 97 | } 98 | 99 | .editable-checklist label { 100 | white-space: nowrap; 101 | } 102 | 103 | /* set exact width of textarea to fit buttons toolbar */ 104 | .editable-wysihtml5 { 105 | width: 566px; 106 | height: 250px; 107 | } 108 | 109 | /* clear button shown as link in date inputs */ 110 | .editable-clear { 111 | clear: both; 112 | font-size: 0.9em; 113 | text-decoration: none; 114 | text-align: right; 115 | } 116 | 117 | /* IOS-style clear button for text inputs */ 118 | .editable-clear-x { 119 | background: url('img/clear.png') center center no-repeat; 120 | display: block; 121 | width: 13px; 122 | height: 13px; 123 | position: absolute; 124 | opacity: 0.6; 125 | z-index: 100; 126 | 127 | top: 50%; 128 | right: 6px; 129 | margin-top: -6px; 130 | 131 | } 132 | 133 | .editable-clear-x:hover { 134 | opacity: 1; 135 | } 136 | 137 | .editable-pre-wrapped { 138 | white-space: pre-wrap; 139 | } 140 | .editable-container.editable-popup { 141 | max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ 142 | } 143 | 144 | .editable-container.popover { 145 | width: auto; /* without this rule popover does not stretch */ 146 | } 147 | 148 | .editable-container.editable-inline { 149 | display: inline-block; 150 | vertical-align: middle; 151 | width: auto; 152 | /* inline-block emulation for IE7*/ 153 | zoom: 1; 154 | *display: inline; 155 | } 156 | 157 | .editable-container.ui-widget { 158 | font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */ 159 | z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */ 160 | } 161 | .editable-click, 162 | a.editable-click, 163 | a.editable-click:hover { 164 | text-decoration: none; 165 | border-bottom: dashed 1px #0088cc; 166 | } 167 | 168 | .editable-click.editable-disabled, 169 | a.editable-click.editable-disabled, 170 | a.editable-click.editable-disabled:hover { 171 | color: #585858; 172 | cursor: default; 173 | border-bottom: none; 174 | } 175 | 176 | .editable-empty, .editable-empty:hover, .editable-empty:focus{ 177 | font-style: italic; 178 | color: #DD1144; 179 | /* border-bottom: none; */ 180 | text-decoration: none; 181 | } 182 | 183 | .editable-unsaved { 184 | font-weight: bold; 185 | } 186 | 187 | .editable-unsaved:after { 188 | /* content: '*'*/ 189 | } 190 | 191 | .editable-bg-transition { 192 | -webkit-transition: background-color 1400ms ease-out; 193 | -moz-transition: background-color 1400ms ease-out; 194 | -o-transition: background-color 1400ms ease-out; 195 | -ms-transition: background-color 1400ms ease-out; 196 | transition: background-color 1400ms ease-out; 197 | } 198 | 199 | /*see https://github.com/vitalets/x-editable/issues/139 */ 200 | .form-horizontal .editable 201 | { 202 | padding-top: 5px; 203 | display:inline-block; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /public/assets-dev/css/jqtree/img/jqtree-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/assets-dev/css/jqtree/img/jqtree-circle.png -------------------------------------------------------------------------------- /public/assets-dev/css/jqtree/jqtree.css: -------------------------------------------------------------------------------- 1 | #tree { 2 | margin: 10px; 3 | padding: 10px; 4 | } 5 | 6 | ul.jqtree-tree { 7 | margin-left: 12px; 8 | } 9 | ul.jqtree-tree, ul.jqtree-tree ul.jqtree_common { 10 | list-style: none outside; 11 | margin-bottom: 0; 12 | padding: 0; 13 | } 14 | ul.jqtree-tree ul.jqtree_common { 15 | display: block; 16 | margin-left: 12px; 17 | margin-right: 0; 18 | } 19 | ul.jqtree-tree li.jqtree-closed > ul.jqtree_common { 20 | display: none; 21 | } 22 | ul.jqtree-tree li.jqtree_common { 23 | clear: both; 24 | list-style-type: none; 25 | } 26 | ul.jqtree-tree .jqtree-toggler { 27 | display: block; 28 | position: absolute; 29 | left: -1.5em; 30 | top: 30%; 31 | *top: 0; 32 | /* fix for ie7 */ 33 | font-size: 12px; 34 | line-height: 12px; 35 | font-family: arial; 36 | /* fix for ie9 */ 37 | border-bottom: none; 38 | color: #333; 39 | } 40 | ul.jqtree-tree .jqtree-toggler:hover { 41 | color: #000; 42 | } 43 | ul.jqtree-tree .jqtree-element { 44 | cursor: pointer; 45 | } 46 | ul.jqtree-tree .jqtree-title { 47 | color: #1C4257; 48 | vertical-align: middle; 49 | } 50 | ul.jqtree-tree li.jqtree-folder { 51 | margin-bottom: 4px; 52 | } 53 | ul.jqtree-tree li.jqtree-folder.jqtree-closed { 54 | margin-bottom: 1px; 55 | } 56 | ul.jqtree-tree li.jqtree-folder .jqtree-title { 57 | margin-left: 0; 58 | } 59 | ul.jqtree-tree .jqtree-toggler.jqtree-closed { 60 | background-position: 0 0; 61 | } 62 | span.jqtree-dragging { 63 | color: #fff; 64 | background: #000; 65 | opacity: 0.6; 66 | cursor: pointer; 67 | padding: 2px 8px; 68 | } 69 | ul.jqtree-tree li.jqtree-ghost { 70 | position: relative; 71 | z-index: 10; 72 | margin-right: 10px; 73 | } 74 | ul.jqtree-tree li.jqtree-ghost span { 75 | display: block; 76 | } 77 | ul.jqtree-tree li.jqtree-ghost span.jqtree-circle { 78 | background-image: url(img/jqtree-circle.png); 79 | background-repeat: no-repeat; 80 | height: 8px; 81 | width: 8px; 82 | position: absolute; 83 | top: -4px; 84 | left: 2px; 85 | } 86 | ul.jqtree-tree li.jqtree-ghost span.jqtree-line { 87 | background-color: #0000ff; 88 | height: 2px; 89 | padding: 0; 90 | position: absolute; 91 | top: -1px; 92 | left: 10px; 93 | width: 100%; 94 | } 95 | ul.jqtree-tree li.jqtree-ghost.jqtree-inside { 96 | margin-left: 48px; 97 | } 98 | ul.jqtree-tree span.jqtree-border { 99 | position: absolute; 100 | display: block; 101 | left: -2px; 102 | top: 0; 103 | border: solid 2px #0000ff; 104 | -webkit-border-radius: 6px; 105 | -moz-border-radius: 6px; 106 | border-radius: 6px; 107 | margin: 0; 108 | } 109 | ul.jqtree-tree .jqtree-element { 110 | width: 100%; 111 | /* todo: why is this in here? */ 112 | *width: auto; 113 | /* ie7 fix; issue 41 */ 114 | position: relative; 115 | } 116 | ul.jqtree-tree li.jqtree-selected > .jqtree-element, ul.jqtree-tree li.jqtree-selected > .jqtree-element:hover { 117 | background-color: #97BDD6; 118 | background: -webkit-gradient(linear, left top, left bottom, from(#BEE0F5), to(#89AFCA)); 119 | background: -moz-linear-gradient(top, #BEE0F5, #89AFCA); 120 | background: -ms-linear-gradient(top, #BEE0F5, #89AFCA); 121 | background: -o-linear-gradient(top, #BEE0F5, #89AFCA); 122 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 123 | } 124 | ul.jqtree-tree .jqtree-moving > .jqtree-element .jqtree-title { 125 | outline: dashed 1px #0000ff; 126 | } -------------------------------------------------------------------------------- /public/assets-dev/css/style.css: -------------------------------------------------------------------------------- 1 | /* Animation keyframes - you need to add prefixes */ 2 | @-webkit-keyframes spin { 3 | from { -webkit-transform: rotate(0deg); } 4 | to { -webkit-transform: rotate(360deg); } 5 | } 6 | 7 | @-moz-keyframes spin { 8 | from { -moz-transform: rotate(0deg); } 9 | to { -moz-transform: rotate(360deg); } 10 | } 11 | 12 | @-ms-keyframes spin { 13 | from { -ms-transform: rotate(0deg); } 14 | to { -ms-transform: rotate(360deg); } 15 | } 16 | 17 | .loading { 18 | position: absolute; 19 | width: 28px; 20 | height: 28px; 21 | } 22 | 23 | .loading .spinner { 24 | position: absolute; 25 | left: 1px; 26 | top: 1px; 27 | width: 26px; 28 | height: 26px; 29 | -webkit-animation: spin 1s infinite linear; 30 | -moz-animation: spin 1s infinite linear; 31 | -o-animation: spin 1s infinite linear; 32 | animation: spin 1s infinite linear; 33 | } 34 | 35 | .loading .mask { 36 | width: 12px; 37 | height: 12px; 38 | overflow: hidden; 39 | } 40 | 41 | .loading .maskedCircle { 42 | width: 20px; 43 | height: 20px; 44 | border-radius: 12px; 45 | border: 3px solid #666; 46 | } -------------------------------------------------------------------------------- /public/assets-dev/js/2.alertify/alertify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * alertify 3 | * An unobtrusive customizable JavaScript notification system 4 | * 5 | * @author Fabien Doiron 6 | * @copyright Fabien Doiron 2013 7 | * @license MIT 8 | * @link http://fabien-d.github.com/alertify.js/ 9 | * @module alertify 10 | * @version 0.3.11 11 | */ 12 | (function (global, undefined) { 13 | "use strict"; 14 | 15 | var document = global.document, 16 | Alertify; 17 | 18 | Alertify = function () { 19 | 20 | var _alertify = {}, 21 | dialogs = {}, 22 | isopen = false, 23 | keys = { ENTER: 13, ESC: 27, SPACE: 32 }, 24 | queue = [], 25 | $, btnCancel, btnOK, btnReset, btnResetBack, btnFocus, elCallee, elCover, elDialog, elLog, form, input, getTransitionEvent; 26 | 27 | /** 28 | * Markup pieces 29 | * @type {Object} 30 | */ 31 | dialogs = { 32 | buttons : { 33 | holder : "", 34 | submit : "", 35 | ok : "", 36 | cancel : "" 37 | }, 38 | input : "
", 39 | message : "

{{message}}

", 40 | log : "
{{message}}
" 41 | }; 42 | 43 | /** 44 | * Return the proper transitionend event 45 | * @return {String} Transition type string 46 | */ 47 | getTransitionEvent = function () { 48 | var t, 49 | type, 50 | supported = false, 51 | el = document.createElement("fakeelement"), 52 | transitions = { 53 | "WebkitTransition" : "webkitTransitionEnd", 54 | "MozTransition" : "transitionend", 55 | "OTransition" : "otransitionend", 56 | "transition" : "transitionend" 57 | }; 58 | 59 | for (t in transitions) { 60 | if (el.style[t] !== undefined) { 61 | type = transitions[t]; 62 | supported = true; 63 | break; 64 | } 65 | } 66 | 67 | return { 68 | type : type, 69 | supported : supported 70 | }; 71 | }; 72 | 73 | /** 74 | * Shorthand for document.getElementById() 75 | * 76 | * @param {String} id A specific element ID 77 | * @return {Object} HTML element 78 | */ 79 | $ = function (id) { 80 | return document.getElementById(id); 81 | }; 82 | 83 | /** 84 | * Alertify private object 85 | * @type {Object} 86 | */ 87 | _alertify = { 88 | 89 | /** 90 | * Labels object 91 | * @type {Object} 92 | */ 93 | labels : { 94 | ok : "OK", 95 | cancel : "Cancel" 96 | }, 97 | 98 | /** 99 | * Delay number 100 | * @type {Number} 101 | */ 102 | delay : 5000, 103 | 104 | /** 105 | * Whether buttons are reversed (default is secondary/primary) 106 | * @type {Boolean} 107 | */ 108 | buttonReverse : false, 109 | 110 | /** 111 | * Which button should be focused by default 112 | * @type {String} "ok" (default), "cancel", or "none" 113 | */ 114 | buttonFocus : "ok", 115 | 116 | /** 117 | * Set the transition event on load 118 | * @type {[type]} 119 | */ 120 | transition : undefined, 121 | 122 | /** 123 | * Set the proper button click events 124 | * 125 | * @param {Function} fn [Optional] Callback function 126 | * 127 | * @return {undefined} 128 | */ 129 | addListeners : function (fn) { 130 | var hasOK = (typeof btnOK !== "undefined"), 131 | hasCancel = (typeof btnCancel !== "undefined"), 132 | hasInput = (typeof input !== "undefined"), 133 | val = "", 134 | self = this, 135 | ok, cancel, common, key, reset; 136 | 137 | // ok event handler 138 | ok = function (event) { 139 | if (typeof event.preventDefault !== "undefined") event.preventDefault(); 140 | common(event); 141 | if (typeof input !== "undefined") val = input.value; 142 | if (typeof fn === "function") { 143 | if (typeof input !== "undefined") { 144 | fn(true, val); 145 | } 146 | else fn(true); 147 | } 148 | return false; 149 | }; 150 | 151 | // cancel event handler 152 | cancel = function (event) { 153 | if (typeof event.preventDefault !== "undefined") event.preventDefault(); 154 | common(event); 155 | if (typeof fn === "function") fn(false); 156 | return false; 157 | }; 158 | 159 | // common event handler (keyup, ok and cancel) 160 | common = function (event) { 161 | self.hide(); 162 | self.unbind(document.body, "keyup", key); 163 | self.unbind(btnReset, "focus", reset); 164 | if (hasOK) self.unbind(btnOK, "click", ok); 165 | if (hasCancel) self.unbind(btnCancel, "click", cancel); 166 | }; 167 | 168 | // keyup handler 169 | key = function (event) { 170 | var keyCode = event.keyCode; 171 | if ((keyCode === keys.SPACE && !hasInput) || (hasInput && keyCode === keys.ENTER)) ok(event); 172 | if (keyCode === keys.ESC && hasCancel) cancel(event); 173 | }; 174 | 175 | // reset focus to first item in the dialog 176 | reset = function (event) { 177 | if (hasInput) input.focus(); 178 | else if (!hasCancel || self.buttonReverse) btnOK.focus(); 179 | else btnCancel.focus(); 180 | }; 181 | 182 | // handle reset focus link 183 | // this ensures that the keyboard focus does not 184 | // ever leave the dialog box until an action has 185 | // been taken 186 | this.bind(btnReset, "focus", reset); 187 | this.bind(btnResetBack, "focus", reset); 188 | // handle OK click 189 | if (hasOK) this.bind(btnOK, "click", ok); 190 | // handle Cancel click 191 | if (hasCancel) this.bind(btnCancel, "click", cancel); 192 | // listen for keys, Cancel => ESC 193 | this.bind(document.body, "keyup", key); 194 | if (!this.transition.supported) { 195 | this.setFocus(); 196 | } 197 | }, 198 | 199 | /** 200 | * Bind events to elements 201 | * 202 | * @param {Object} el HTML Object 203 | * @param {Event} event Event to attach to element 204 | * @param {Function} fn Callback function 205 | * 206 | * @return {undefined} 207 | */ 208 | bind : function (el, event, fn) { 209 | if (typeof el.addEventListener === "function") { 210 | el.addEventListener(event, fn, false); 211 | } else if (el.attachEvent) { 212 | el.attachEvent("on" + event, fn); 213 | } 214 | }, 215 | 216 | /** 217 | * Use alertify as the global error handler (using window.onerror) 218 | * 219 | * @return {boolean} success 220 | */ 221 | handleErrors : function () { 222 | if (typeof global.onerror !== "undefined") { 223 | var self = this; 224 | global.onerror = function (msg, url, line) { 225 | self.error("[" + msg + " on line " + line + " of " + url + "]", 0); 226 | }; 227 | return true; 228 | } else { 229 | return false; 230 | } 231 | }, 232 | 233 | /** 234 | * Append button HTML strings 235 | * 236 | * @param {String} secondary The secondary button HTML string 237 | * @param {String} primary The primary button HTML string 238 | * 239 | * @return {String} The appended button HTML strings 240 | */ 241 | appendButtons : function (secondary, primary) { 242 | return this.buttonReverse ? primary + secondary : secondary + primary; 243 | }, 244 | 245 | /** 246 | * Build the proper message box 247 | * 248 | * @param {Object} item Current object in the queue 249 | * 250 | * @return {String} An HTML string of the message box 251 | */ 252 | build : function (item) { 253 | var html = "", 254 | type = item.type, 255 | message = item.message, 256 | css = item.cssClass || ""; 257 | 258 | html += "
"; 259 | html += "Reset Focus"; 260 | 261 | if (_alertify.buttonFocus === "none") html += ""; 262 | 263 | // doens't require an actual form 264 | if (type === "prompt") html += "
"; 265 | 266 | html += "
"; 267 | html += dialogs.message.replace("{{message}}", message); 268 | 269 | if (type === "prompt") html += dialogs.input; 270 | 271 | html += dialogs.buttons.holder; 272 | html += "
"; 273 | 274 | if (type === "prompt") html += "
"; 275 | 276 | html += "Reset Focus"; 277 | html += "
"; 278 | 279 | switch (type) { 280 | case "confirm": 281 | html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.ok)); 282 | html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); 283 | break; 284 | case "prompt": 285 | html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.submit)); 286 | html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); 287 | break; 288 | case "alert": 289 | html = html.replace("{{buttons}}", dialogs.buttons.ok); 290 | html = html.replace("{{ok}}", this.labels.ok); 291 | break; 292 | default: 293 | break; 294 | } 295 | 296 | elDialog.className = "alertify alertify-" + type + " " + css; 297 | elCover.className = "alertify-cover"; 298 | return html; 299 | }, 300 | 301 | /** 302 | * Close the log messages 303 | * 304 | * @param {Object} elem HTML Element of log message to close 305 | * @param {Number} wait [optional] Time (in ms) to wait before automatically hiding the message, if 0 never hide 306 | * 307 | * @return {undefined} 308 | */ 309 | close : function (elem, wait) { 310 | // Unary Plus: +"2" === 2 311 | var timer = (wait && !isNaN(wait)) ? +wait : this.delay, 312 | self = this, 313 | hideElement, transitionDone; 314 | 315 | // set click event on log messages 316 | this.bind(elem, "click", function () { 317 | hideElement(elem); 318 | }); 319 | // Hide the dialog box after transition 320 | // This ensure it doens't block any element from being clicked 321 | transitionDone = function (event) { 322 | event.stopPropagation(); 323 | // unbind event so function only gets called once 324 | self.unbind(this, self.transition.type, transitionDone); 325 | // remove log message 326 | elLog.removeChild(this); 327 | if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden"; 328 | }; 329 | // this sets the hide class to transition out 330 | // or removes the child if css transitions aren't supported 331 | hideElement = function (el) { 332 | // ensure element exists 333 | if (typeof el !== "undefined" && el.parentNode === elLog) { 334 | // whether CSS transition exists 335 | if (self.transition.supported) { 336 | self.bind(el, self.transition.type, transitionDone); 337 | el.className += " alertify-log-hide"; 338 | } else { 339 | elLog.removeChild(el); 340 | if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden"; 341 | } 342 | } 343 | }; 344 | // never close (until click) if wait is set to 0 345 | if (wait === 0) return; 346 | // set timeout to auto close the log message 347 | setTimeout(function () { hideElement(elem); }, timer); 348 | }, 349 | 350 | /** 351 | * Create a dialog box 352 | * 353 | * @param {String} message The message passed from the callee 354 | * @param {String} type Type of dialog to create 355 | * @param {Function} fn [Optional] Callback function 356 | * @param {String} placeholder [Optional] Default value for prompt input field 357 | * @param {String} cssClass [Optional] Class(es) to append to dialog box 358 | * 359 | * @return {Object} 360 | */ 361 | dialog : function (message, type, fn, placeholder, cssClass) { 362 | // set the current active element 363 | // this allows the keyboard focus to be resetted 364 | // after the dialog box is closed 365 | elCallee = document.activeElement; 366 | // check to ensure the alertify dialog element 367 | // has been successfully created 368 | var check = function () { 369 | if ((elLog && elLog.scrollTop !== null) && (elCover && elCover.scrollTop !== null)) return; 370 | else check(); 371 | }; 372 | // error catching 373 | if (typeof message !== "string") throw new Error("message must be a string"); 374 | if (typeof type !== "string") throw new Error("type must be a string"); 375 | if (typeof fn !== "undefined" && typeof fn !== "function") throw new Error("fn must be a function"); 376 | // initialize alertify if it hasn't already been done 377 | this.init(); 378 | check(); 379 | 380 | queue.push({ type: type, message: message, callback: fn, placeholder: placeholder, cssClass: cssClass }); 381 | if (!isopen) this.setup(); 382 | 383 | return this; 384 | }, 385 | 386 | /** 387 | * Extend the log method to create custom methods 388 | * 389 | * @param {String} type Custom method name 390 | * 391 | * @return {Function} 392 | */ 393 | extend : function (type) { 394 | if (typeof type !== "string") throw new Error("extend method must have exactly one paramter"); 395 | return function (message, wait) { 396 | this.log(message, type, wait); 397 | return this; 398 | }; 399 | }, 400 | 401 | /** 402 | * Hide the dialog and rest to defaults 403 | * 404 | * @return {undefined} 405 | */ 406 | hide : function () { 407 | var transitionDone, 408 | self = this; 409 | // remove reference from queue 410 | queue.splice(0,1); 411 | // if items remaining in the queue 412 | if (queue.length > 0) this.setup(true); 413 | else { 414 | isopen = false; 415 | // Hide the dialog box after transition 416 | // This ensure it doens't block any element from being clicked 417 | transitionDone = function (event) { 418 | event.stopPropagation(); 419 | // unbind event so function only gets called once 420 | self.unbind(elDialog, self.transition.type, transitionDone); 421 | }; 422 | // whether CSS transition exists 423 | if (this.transition.supported) { 424 | this.bind(elDialog, this.transition.type, transitionDone); 425 | elDialog.className = "alertify alertify-hide alertify-hidden"; 426 | } else { 427 | elDialog.className = "alertify alertify-hide alertify-hidden alertify-isHidden"; 428 | } 429 | elCover.className = "alertify-cover alertify-cover-hidden"; 430 | // set focus to the last element or body 431 | // after the dialog is closed 432 | elCallee.focus(); 433 | } 434 | }, 435 | 436 | /** 437 | * Initialize Alertify 438 | * Create the 2 main elements 439 | * 440 | * @return {undefined} 441 | */ 442 | init : function () { 443 | // ensure legacy browsers support html5 tags 444 | document.createElement("nav"); 445 | document.createElement("article"); 446 | document.createElement("section"); 447 | // cover 448 | if ($("alertify-cover") == null) { 449 | elCover = document.createElement("div"); 450 | elCover.setAttribute("id", "alertify-cover"); 451 | elCover.className = "alertify-cover alertify-cover-hidden"; 452 | document.body.appendChild(elCover); 453 | } 454 | // main element 455 | if ($("alertify") == null) { 456 | isopen = false; 457 | queue = []; 458 | elDialog = document.createElement("section"); 459 | elDialog.setAttribute("id", "alertify"); 460 | elDialog.className = "alertify alertify-hidden"; 461 | document.body.appendChild(elDialog); 462 | } 463 | // log element 464 | if ($("alertify-logs") == null) { 465 | elLog = document.createElement("section"); 466 | elLog.setAttribute("id", "alertify-logs"); 467 | elLog.className = "alertify-logs alertify-logs-hidden"; 468 | document.body.appendChild(elLog); 469 | } 470 | // set tabindex attribute on body element 471 | // this allows script to give it focus 472 | // after the dialog is closed 473 | document.body.setAttribute("tabindex", "0"); 474 | // set transition type 475 | this.transition = getTransitionEvent(); 476 | }, 477 | 478 | /** 479 | * Show a new log message box 480 | * 481 | * @param {String} message The message passed from the callee 482 | * @param {String} type [Optional] Optional type of log message 483 | * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding the log 484 | * 485 | * @return {Object} 486 | */ 487 | log : function (message, type, wait) { 488 | // check to ensure the alertify dialog element 489 | // has been successfully created 490 | var check = function () { 491 | if (elLog && elLog.scrollTop !== null) return; 492 | else check(); 493 | }; 494 | // initialize alertify if it hasn't already been done 495 | this.init(); 496 | check(); 497 | 498 | elLog.className = "alertify-logs"; 499 | this.notify(message, type, wait); 500 | return this; 501 | }, 502 | 503 | /** 504 | * Add new log message 505 | * If a type is passed, a class name "alertify-log-{type}" will get added. 506 | * This allows for custom look and feel for various types of notifications. 507 | * 508 | * @param {String} message The message passed from the callee 509 | * @param {String} type [Optional] Type of log message 510 | * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding 511 | * 512 | * @return {undefined} 513 | */ 514 | notify : function (message, type, wait) { 515 | var log = document.createElement("article"); 516 | log.className = "alertify-log" + ((typeof type === "string" && type !== "") ? " alertify-log-" + type : ""); 517 | log.innerHTML = message; 518 | // append child 519 | elLog.appendChild(log); 520 | // triggers the CSS animation 521 | setTimeout(function() { log.className = log.className + " alertify-log-show"; }, 50); 522 | this.close(log, wait); 523 | }, 524 | 525 | /** 526 | * Set properties 527 | * 528 | * @param {Object} args Passing parameters 529 | * 530 | * @return {undefined} 531 | */ 532 | set : function (args) { 533 | var k; 534 | // error catching 535 | if (typeof args !== "object" && args instanceof Array) throw new Error("args must be an object"); 536 | // set parameters 537 | for (k in args) { 538 | if (args.hasOwnProperty(k)) { 539 | this[k] = args[k]; 540 | } 541 | } 542 | }, 543 | 544 | /** 545 | * Common place to set focus to proper element 546 | * 547 | * @return {undefined} 548 | */ 549 | setFocus : function () { 550 | if (input) { 551 | input.focus(); 552 | input.select(); 553 | } 554 | else btnFocus.focus(); 555 | }, 556 | 557 | /** 558 | * Initiate all the required pieces for the dialog box 559 | * 560 | * @return {undefined} 561 | */ 562 | setup : function (fromQueue) { 563 | var item = queue[0], 564 | self = this, 565 | transitionDone; 566 | 567 | // dialog is open 568 | isopen = true; 569 | // Set button focus after transition 570 | transitionDone = function (event) { 571 | event.stopPropagation(); 572 | self.setFocus(); 573 | // unbind event so function only gets called once 574 | self.unbind(elDialog, self.transition.type, transitionDone); 575 | }; 576 | // whether CSS transition exists 577 | if (this.transition.supported && !fromQueue) { 578 | this.bind(elDialog, this.transition.type, transitionDone); 579 | } 580 | // build the proper dialog HTML 581 | elDialog.innerHTML = this.build(item); 582 | // assign all the common elements 583 | btnReset = $("alertify-resetFocus"); 584 | btnResetBack = $("alertify-resetFocusBack"); 585 | btnOK = $("alertify-ok") || undefined; 586 | btnCancel = $("alertify-cancel") || undefined; 587 | btnFocus = (_alertify.buttonFocus === "cancel") ? btnCancel : ((_alertify.buttonFocus === "none") ? $("alertify-noneFocus") : btnOK), 588 | input = $("alertify-text") || undefined; 589 | form = $("alertify-form") || undefined; 590 | // add placeholder value to the input field 591 | if (typeof item.placeholder === "string" && item.placeholder !== "") input.value = item.placeholder; 592 | if (fromQueue) this.setFocus(); 593 | this.addListeners(item.callback); 594 | }, 595 | 596 | /** 597 | * Unbind events to elements 598 | * 599 | * @param {Object} el HTML Object 600 | * @param {Event} event Event to detach to element 601 | * @param {Function} fn Callback function 602 | * 603 | * @return {undefined} 604 | */ 605 | unbind : function (el, event, fn) { 606 | if (typeof el.removeEventListener === "function") { 607 | el.removeEventListener(event, fn, false); 608 | } else if (el.detachEvent) { 609 | el.detachEvent("on" + event, fn); 610 | } 611 | } 612 | }; 613 | 614 | return { 615 | alert : function (message, fn, cssClass) { _alertify.dialog(message, "alert", fn, "", cssClass); return this; }, 616 | confirm : function (message, fn, cssClass) { _alertify.dialog(message, "confirm", fn, "", cssClass); return this; }, 617 | extend : _alertify.extend, 618 | init : _alertify.init, 619 | log : function (message, type, wait) { _alertify.log(message, type, wait); return this; }, 620 | prompt : function (message, fn, placeholder, cssClass) { _alertify.dialog(message, "prompt", fn, placeholder, cssClass); return this; }, 621 | success : function (message, wait) { _alertify.log(message, "success", wait); return this; }, 622 | error : function (message, wait) { _alertify.log(message, "error", wait); return this; }, 623 | set : function (args) { _alertify.set(args); }, 624 | labels : _alertify.labels, 625 | debug : _alertify.handleErrors 626 | }; 627 | }; 628 | 629 | // AMD and window support 630 | if (typeof define === "function") { 631 | define([], function () { return new Alertify(); }); 632 | } else if (typeof global.alertify === "undefined") { 633 | global.alertify = new Alertify(); 634 | } 635 | 636 | }(this)); 637 | -------------------------------------------------------------------------------- /public/assets-dev/js/site.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | // configure spinner 4 | $spinner = $(".loading"); 5 | $spinner.toggle(); 6 | 7 | // configure editable 8 | $.fn.editableform.buttons = '' 9 | + '' 10 | + ''; 11 | $.fn.editable.defaults.mode = 'inline'; 12 | 13 | // configure tree 14 | var $tree = $("#tree"); 15 | var opts = { 16 | data: data, 17 | dragAndDrop: true, 18 | autoOpen: true, 19 | selectable: false, 20 | useContextMenu: false, 21 | onCreateLi: function (node, $li) { 22 | var li = $li.find(".jqtree-title"); 23 | li 24 | .attr("data-pk", node.id) 25 | .attr("data-type", "text") 26 | .addClass("editable-click editable-container") 27 | .attr("data-name", node.name) 28 | } 29 | } 30 | function checkData() { if ($tree.find("ul").children().length === 0) $tree.html("Empty :)"); } 31 | $tree.bind("tree.init", checkData) 32 | 33 | // initialize tree 34 | $tree.tree(opts) 35 | 36 | // move category 37 | $tree.bind("tree.move", function (e) { 38 | $spinner.toggle(); 39 | e.preventDefault(); 40 | $.ajax(serverUrl, { 41 | type: "POST", 42 | data: { 43 | "action": "moveCategory", 44 | "id": e.move_info.moved_node.id, 45 | "parent_id": e.move_info.moved_node.parent_id, 46 | "to": e.move_info.target_node.id, 47 | "name": e.move_info.moved_node.name, 48 | "direction": e.move_info.position 49 | }, 50 | success: function () { 51 | $spinner.toggle(); 52 | e.move_info.do_move(); 53 | e.move_info.moved_node["parent_id"] = (e.move_info.position == "inside") ? e.move_info.target_node["id"] : e.move_info.target_node["parent_id"]; 54 | }, 55 | error: function (r) { 56 | $spinner.toggle(); 57 | alertify.error(r.statusText); 58 | } 59 | }); 60 | }) // END move 61 | 62 | // add category 63 | $(".newCategory").click(function (e) { 64 | e.preventDefault(); 65 | alertify.prompt("Category name:", function (e, str) { 66 | if (e) { 67 | $spinner.toggle(); 68 | $.ajax(serverUrl, { 69 | type: "POST", 70 | data: { 71 | "action": "addCategory", 72 | "name": str 73 | }, 74 | success: function (r) { 75 | $spinner.toggle(); 76 | var root = $tree.tree("getTree"); 77 | $tree.tree( 78 | "appendNode", { 79 | name: str, 80 | id: r.id, 81 | parent_id: r.parent_id 82 | }, 83 | root 84 | ); 85 | }, 86 | error: function (r) { 87 | $spinner.toggle(); 88 | alertify.error(r.statusText); 89 | } 90 | }); 91 | } 92 | }); 93 | }) // END add 94 | 95 | // rename category 96 | $tree.editable({ 97 | selector: "span.jqtree-title", 98 | url: serverUrl, 99 | params: function (params) { 100 | var data = {}; 101 | data.action = "renameCategory"; 102 | data.id = params.pk; 103 | data.name = params.value; 104 | data.originalname = params.name; 105 | return data; 106 | }, 107 | success: function (r, v) { 108 | var node = $tree.tree("getNodeById", $(this).attr("data-pk")); 109 | node.name = v; 110 | $(this).editable("option", "name", v) 111 | }, 112 | error: function (r) { 113 | alertify.error(r.statusText); 114 | } 115 | }) // END rename 116 | 117 | // delete category 118 | $(document).on("click", ".editable-delete", function () { 119 | var nodeId = $(this).closest(".jqtree-element").find("span:eq(0)").data("pk"); 120 | var node = $tree.tree("getNodeById", nodeId) 121 | alertify.set({ buttonFocus: "cancel", buttonReverse: true }); 122 | alertify.confirm("Are you sure you want to delete this category?", function (e) { 123 | if (e) { 124 | $spinner.toggle(); 125 | $.ajax(serverUrl, { 126 | type: "POST", 127 | data: { 128 | "action": "deleteCategory", 129 | "id": node.id, 130 | "name": node.name 131 | }, 132 | success: function (d) { 133 | $spinner.toggle(); 134 | $tree.tree("removeNode", node); 135 | checkData(); 136 | }, 137 | error: function (r) { 138 | $spinner.toggle(); 139 | alertify.error(r.statusText); 140 | } 141 | }); 142 | } 143 | }); 144 | }); // END delete 145 | 146 | }); -------------------------------------------------------------------------------- /public/assets/img/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/assets/img/clear.png -------------------------------------------------------------------------------- /public/assets/img/jqtree-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/assets/img/jqtree-circle.png -------------------------------------------------------------------------------- /public/assets/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/assets/img/loading.gif -------------------------------------------------------------------------------- /public/assets/site.css: -------------------------------------------------------------------------------- 1 | .alertify, 2 | .alertify-show, 3 | .alertify-log { 4 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 5 | -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 6 | -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 7 | -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 8 | transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */ 9 | } 10 | .alertify-hide { 11 | -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 12 | -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 14 | -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 15 | transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 16 | } 17 | .alertify-log-hide { 18 | -webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 19 | -moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 20 | -ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 21 | -o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 22 | transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 23 | } 24 | .alertify-cover { 25 | position: fixed; z-index: 99999; 26 | top: 0; right: 0; bottom: 0; left: 0; 27 | background-color:white; 28 | filter:alpha(opacity=0); 29 | opacity:0; 30 | } 31 | .alertify-cover-hidden { 32 | display: none; 33 | } 34 | .alertify { 35 | position: fixed; z-index: 99999; 36 | top: 50px; left: 50%; 37 | width: 550px; 38 | margin-left: -275px; 39 | opacity: 1; 40 | } 41 | .alertify-hidden { 42 | -webkit-transform: translate(0,-150px); 43 | -moz-transform: translate(0,-150px); 44 | -ms-transform: translate(0,-150px); 45 | -o-transform: translate(0,-150px); 46 | transform: translate(0,-150px); 47 | opacity: 0; 48 | display: none; 49 | } 50 | /* overwrite display: none; for everything except IE6-8 */ 51 | :root *> .alertify-hidden { 52 | display: block; 53 | visibility: hidden; 54 | } 55 | .alertify-logs { 56 | position: fixed; 57 | z-index: 5000; 58 | bottom: 10px; 59 | right: 10px; 60 | width: 300px; 61 | /* show on top: */ 62 | top: 10px; 63 | right: 10px; 64 | } 65 | .alertify-logs-hidden { 66 | display: none; 67 | } 68 | .alertify-log { 69 | display: block; 70 | margin-top: 10px; 71 | position: relative; 72 | right: -300px; 73 | opacity: 0; 74 | } 75 | .alertify-log-show { 76 | right: 0; 77 | opacity: 1; 78 | } 79 | .alertify-log-hide { 80 | -webkit-transform: translate(300px, 0); 81 | -moz-transform: translate(300px, 0); 82 | -ms-transform: translate(300px, 0); 83 | -o-transform: translate(300px, 0); 84 | transform: translate(300px, 0); 85 | opacity: 0; 86 | } 87 | .alertify-dialog { 88 | padding: 25px; 89 | } 90 | .alertify-resetFocus { 91 | border: 0; 92 | clip: rect(0 0 0 0); 93 | height: 1px; 94 | margin: -1px; 95 | overflow: hidden; 96 | padding: 0; 97 | position: absolute; 98 | width: 1px; 99 | } 100 | .alertify-inner { 101 | text-align: center; 102 | } 103 | .alertify-text { 104 | margin-bottom: 15px; 105 | width: 100%; 106 | -webkit-box-sizing: border-box; 107 | -moz-box-sizing: border-box; 108 | box-sizing: border-box; 109 | font-size: 100%; 110 | } 111 | .alertify-buttons { 112 | } 113 | .alertify-button, 114 | .alertify-button:hover, 115 | .alertify-button:active, 116 | .alertify-button:visited { 117 | background: none; 118 | text-decoration: none; 119 | border: none; 120 | /* line-height and font-size for input button */ 121 | line-height: 1.5; 122 | font-size: 100%; 123 | display: inline-block; 124 | cursor: pointer; 125 | margin-left: 5px; 126 | } 127 | 128 | @media only screen and (max-width: 680px) { 129 | .alertify, 130 | .alertify-logs { 131 | width: 90%; 132 | -webkit-box-sizing: border-box; 133 | -moz-box-sizing: border-box; 134 | box-sizing: border-box; 135 | } 136 | .alertify { 137 | left: 5%; 138 | margin: 0; 139 | } 140 | } 141 | 142 | /** 143 | * Default Look and Feel 144 | */ 145 | .alertify, 146 | .alertify-log { 147 | font-family: sans-serif; 148 | } 149 | .alertify { 150 | background: #FFF; 151 | border: 10px solid #333; /* browsers that don't support rgba */ 152 | border: 10px solid rgba(0,0,0,.7); 153 | border-radius: 8px; 154 | box-shadow: 0 3px 3px rgba(0,0,0,.3); 155 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 156 | -moz-background-clip: padding; /* Firefox 3.6 */ 157 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */ 158 | } 159 | .alertify-text { 160 | border: 1px solid #CCC; 161 | padding: 10px; 162 | border-radius: 4px; 163 | } 164 | .alertify-button { 165 | border-radius: 4px; 166 | color: #FFF; 167 | font-weight: bold; 168 | padding: 6px 15px; 169 | text-decoration: none; 170 | text-shadow: 1px 1px 0 rgba(0,0,0,.5); 171 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,.5); 172 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 173 | background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 174 | background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 175 | background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 176 | background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 177 | } 178 | .alertify-button:hover, 179 | .alertify-button:focus { 180 | outline: none; 181 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 182 | background-image: -moz-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 183 | background-image: -ms-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 184 | background-image: -o-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 185 | background-image: linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 186 | } 187 | .alertify-button:focus { 188 | box-shadow: 0 0 15px #2B72D5; 189 | } 190 | .alertify-button:active { 191 | position: relative; 192 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 193 | } 194 | .alertify-button-cancel, 195 | .alertify-button-cancel:hover, 196 | .alertify-button-cancel:focus { 197 | background-color: #FE1A00; 198 | border: 1px solid #D83526; 199 | } 200 | .alertify-button-ok, 201 | .alertify-button-ok:hover, 202 | .alertify-button-ok:focus { 203 | background-color: #5CB811; 204 | border: 1px solid #3B7808; 205 | } 206 | 207 | .alertify-log { 208 | background: #1F1F1F; 209 | background: rgba(0,0,0,.9); 210 | padding: 15px; 211 | border-radius: 4px; 212 | color: #FFF; 213 | text-shadow: -1px -1px 0 rgba(0,0,0,.5); 214 | } 215 | .alertify-log-error { 216 | background: #FE1A00; 217 | background: rgba(254,26,0,.9); 218 | } 219 | .alertify-log-success { 220 | background: #5CB811; 221 | background: rgba(92,184,17,.9); 222 | } 223 | /*! X-editable - v1.5.0 224 | * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery 225 | * http://github.com/vitalets/x-editable 226 | * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ 227 | .editableform { 228 | margin-bottom: 0; /* overwrites bootstrap margin */ 229 | } 230 | 231 | .editableform .control-group { 232 | margin-bottom: 0; /* overwrites bootstrap margin */ 233 | white-space: nowrap; /* prevent wrapping buttons on new line */ 234 | line-height: 20px; /* overwriting bootstrap line-height. See #133 */ 235 | } 236 | 237 | .editable-buttons { 238 | display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ 239 | vertical-align: top; 240 | margin-left: 7px; 241 | /* inline-block emulation for IE7*/ 242 | zoom: 1; 243 | *display: inline; 244 | } 245 | 246 | .editable-buttons.editable-buttons-bottom { 247 | display: block; 248 | margin-top: 7px; 249 | margin-left: 0; 250 | } 251 | 252 | .editable-input { 253 | vertical-align: top; 254 | display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ 255 | width: auto; /* bootstrap-responsive has width: 100% that breakes layout */ 256 | white-space: normal; /* reset white-space decalred in parent*/ 257 | /* display-inline emulation for IE7*/ 258 | zoom: 1; 259 | *display: inline; 260 | } 261 | 262 | .editable-buttons .editable-cancel { 263 | margin-left: 7px; 264 | } 265 | 266 | /*for jquery-ui buttons need set height to look more pretty*/ 267 | .editable-buttons button.ui-button-icon-only { 268 | height: 24px; 269 | width: 30px; 270 | } 271 | 272 | .editableform-loading { 273 | background: url('img/loading.gif') center center no-repeat; 274 | height: 25px; 275 | width: auto; 276 | min-width: 25px; 277 | } 278 | 279 | .editable-inline .editableform-loading { 280 | background-position: left 5px; 281 | } 282 | 283 | .editable-error-block { 284 | max-width: 300px; 285 | margin: 5px 0 0 0; 286 | width: auto; 287 | white-space: normal; 288 | } 289 | 290 | /*add padding for jquery ui*/ 291 | .editable-error-block.ui-state-error { 292 | padding: 3px; 293 | } 294 | 295 | .editable-error { 296 | color: red; 297 | } 298 | 299 | /* ---- For specific types ---- */ 300 | 301 | .editableform .editable-date { 302 | padding: 0; 303 | margin: 0; 304 | float: left; 305 | } 306 | 307 | /* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */ 308 | .editable-inline .add-on .icon-th { 309 | margin-top: 3px; 310 | margin-left: 1px; 311 | } 312 | 313 | 314 | /* checklist vertical alignment */ 315 | .editable-checklist label input[type="checkbox"], 316 | .editable-checklist label span { 317 | vertical-align: middle; 318 | margin: 0; 319 | } 320 | 321 | .editable-checklist label { 322 | white-space: nowrap; 323 | } 324 | 325 | /* set exact width of textarea to fit buttons toolbar */ 326 | .editable-wysihtml5 { 327 | width: 566px; 328 | height: 250px; 329 | } 330 | 331 | /* clear button shown as link in date inputs */ 332 | .editable-clear { 333 | clear: both; 334 | font-size: 0.9em; 335 | text-decoration: none; 336 | text-align: right; 337 | } 338 | 339 | /* IOS-style clear button for text inputs */ 340 | .editable-clear-x { 341 | background: url('img/clear.png') center center no-repeat; 342 | display: block; 343 | width: 13px; 344 | height: 13px; 345 | position: absolute; 346 | opacity: 0.6; 347 | z-index: 100; 348 | 349 | top: 50%; 350 | right: 6px; 351 | margin-top: -6px; 352 | 353 | } 354 | 355 | .editable-clear-x:hover { 356 | opacity: 1; 357 | } 358 | 359 | .editable-pre-wrapped { 360 | white-space: pre-wrap; 361 | } 362 | .editable-container.editable-popup { 363 | max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ 364 | } 365 | 366 | .editable-container.popover { 367 | width: auto; /* without this rule popover does not stretch */ 368 | } 369 | 370 | .editable-container.editable-inline { 371 | display: inline-block; 372 | vertical-align: middle; 373 | width: auto; 374 | /* inline-block emulation for IE7*/ 375 | zoom: 1; 376 | *display: inline; 377 | } 378 | 379 | .editable-container.ui-widget { 380 | font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */ 381 | z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */ 382 | } 383 | .editable-click, 384 | a.editable-click, 385 | a.editable-click:hover { 386 | text-decoration: none; 387 | border-bottom: dashed 1px #0088cc; 388 | } 389 | 390 | .editable-click.editable-disabled, 391 | a.editable-click.editable-disabled, 392 | a.editable-click.editable-disabled:hover { 393 | color: #585858; 394 | cursor: default; 395 | border-bottom: none; 396 | } 397 | 398 | .editable-empty, .editable-empty:hover, .editable-empty:focus{ 399 | font-style: italic; 400 | color: #DD1144; 401 | /* border-bottom: none; */ 402 | text-decoration: none; 403 | } 404 | 405 | .editable-unsaved { 406 | font-weight: bold; 407 | } 408 | 409 | .editable-unsaved:after { 410 | /* content: '*'*/ 411 | } 412 | 413 | .editable-bg-transition { 414 | -webkit-transition: background-color 1400ms ease-out; 415 | -moz-transition: background-color 1400ms ease-out; 416 | -o-transition: background-color 1400ms ease-out; 417 | -ms-transition: background-color 1400ms ease-out; 418 | transition: background-color 1400ms ease-out; 419 | } 420 | 421 | /*see https://github.com/vitalets/x-editable/issues/139 */ 422 | .form-horizontal .editable 423 | { 424 | padding-top: 5px; 425 | display:inline-block; 426 | } 427 | 428 | 429 | #tree { 430 | margin: 10px; 431 | padding: 10px; 432 | } 433 | 434 | ul.jqtree-tree { 435 | margin-left: 12px; 436 | } 437 | ul.jqtree-tree, ul.jqtree-tree ul.jqtree_common { 438 | list-style: none outside; 439 | margin-bottom: 0; 440 | padding: 0; 441 | } 442 | ul.jqtree-tree ul.jqtree_common { 443 | display: block; 444 | margin-left: 12px; 445 | margin-right: 0; 446 | } 447 | ul.jqtree-tree li.jqtree-closed > ul.jqtree_common { 448 | display: none; 449 | } 450 | ul.jqtree-tree li.jqtree_common { 451 | clear: both; 452 | list-style-type: none; 453 | } 454 | ul.jqtree-tree .jqtree-toggler { 455 | display: block; 456 | position: absolute; 457 | left: -1.5em; 458 | top: 30%; 459 | *top: 0; 460 | /* fix for ie7 */ 461 | font-size: 12px; 462 | line-height: 12px; 463 | font-family: arial; 464 | /* fix for ie9 */ 465 | border-bottom: none; 466 | color: #333; 467 | } 468 | ul.jqtree-tree .jqtree-toggler:hover { 469 | color: #000; 470 | } 471 | ul.jqtree-tree .jqtree-element { 472 | cursor: pointer; 473 | } 474 | ul.jqtree-tree .jqtree-title { 475 | color: #1C4257; 476 | vertical-align: middle; 477 | } 478 | ul.jqtree-tree li.jqtree-folder { 479 | margin-bottom: 4px; 480 | } 481 | ul.jqtree-tree li.jqtree-folder.jqtree-closed { 482 | margin-bottom: 1px; 483 | } 484 | ul.jqtree-tree li.jqtree-folder .jqtree-title { 485 | margin-left: 0; 486 | } 487 | ul.jqtree-tree .jqtree-toggler.jqtree-closed { 488 | background-position: 0 0; 489 | } 490 | span.jqtree-dragging { 491 | color: #fff; 492 | background: #000; 493 | opacity: 0.6; 494 | cursor: pointer; 495 | padding: 2px 8px; 496 | } 497 | ul.jqtree-tree li.jqtree-ghost { 498 | position: relative; 499 | z-index: 10; 500 | margin-right: 10px; 501 | } 502 | ul.jqtree-tree li.jqtree-ghost span { 503 | display: block; 504 | } 505 | ul.jqtree-tree li.jqtree-ghost span.jqtree-circle { 506 | background-image: url(img/jqtree-circle.png); 507 | background-repeat: no-repeat; 508 | height: 8px; 509 | width: 8px; 510 | position: absolute; 511 | top: -4px; 512 | left: 2px; 513 | } 514 | ul.jqtree-tree li.jqtree-ghost span.jqtree-line { 515 | background-color: #0000ff; 516 | height: 2px; 517 | padding: 0; 518 | position: absolute; 519 | top: -1px; 520 | left: 10px; 521 | width: 100%; 522 | } 523 | ul.jqtree-tree li.jqtree-ghost.jqtree-inside { 524 | margin-left: 48px; 525 | } 526 | ul.jqtree-tree span.jqtree-border { 527 | position: absolute; 528 | display: block; 529 | left: -2px; 530 | top: 0; 531 | border: solid 2px #0000ff; 532 | -webkit-border-radius: 6px; 533 | -moz-border-radius: 6px; 534 | border-radius: 6px; 535 | margin: 0; 536 | } 537 | ul.jqtree-tree .jqtree-element { 538 | width: 100%; 539 | /* todo: why is this in here? */ 540 | *width: auto; 541 | /* ie7 fix; issue 41 */ 542 | position: relative; 543 | } 544 | ul.jqtree-tree li.jqtree-selected > .jqtree-element, ul.jqtree-tree li.jqtree-selected > .jqtree-element:hover { 545 | background-color: #97BDD6; 546 | background: -webkit-gradient(linear, left top, left bottom, from(#BEE0F5), to(#89AFCA)); 547 | background: -moz-linear-gradient(top, #BEE0F5, #89AFCA); 548 | background: -ms-linear-gradient(top, #BEE0F5, #89AFCA); 549 | background: -o-linear-gradient(top, #BEE0F5, #89AFCA); 550 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 551 | } 552 | ul.jqtree-tree .jqtree-moving > .jqtree-element .jqtree-title { 553 | outline: dashed 1px #0000ff; 554 | } 555 | /* Animation keyframes - you need to add prefixes */ 556 | @-webkit-keyframes spin { 557 | from { -webkit-transform: rotate(0deg); } 558 | to { -webkit-transform: rotate(360deg); } 559 | } 560 | 561 | @-moz-keyframes spin { 562 | from { -moz-transform: rotate(0deg); } 563 | to { -moz-transform: rotate(360deg); } 564 | } 565 | 566 | @-ms-keyframes spin { 567 | from { -ms-transform: rotate(0deg); } 568 | to { -ms-transform: rotate(360deg); } 569 | } 570 | 571 | .loading { 572 | position: absolute; 573 | width: 28px; 574 | height: 28px; 575 | } 576 | 577 | .loading .spinner { 578 | position: absolute; 579 | left: 1px; 580 | top: 1px; 581 | width: 26px; 582 | height: 26px; 583 | -webkit-animation: spin 1s infinite linear; 584 | -moz-animation: spin 1s infinite linear; 585 | -o-animation: spin 1s infinite linear; 586 | animation: spin 1s infinite linear; 587 | } 588 | 589 | .loading .mask { 590 | width: 12px; 591 | height: 12px; 592 | overflow: hidden; 593 | } 594 | 595 | .loading .maskedCircle { 596 | width: 20px; 597 | height: 20px; 598 | border-radius: 12px; 599 | border: 3px solid #666; 600 | } -------------------------------------------------------------------------------- /public/assets/site.min.css: -------------------------------------------------------------------------------- 1 | /*! mrc-categories 2014-01-17 */ 2 | 3 | .alertify,.alertify-show,.alertify-log{-webkit-transition:all 500ms cubic-bezier(0.175,.885,.32,1.275);-moz-transition:all 500ms cubic-bezier(0.175,.885,.32,1.275);-ms-transition:all 500ms cubic-bezier(0.175,.885,.32,1.275);-o-transition:all 500ms cubic-bezier(0.175,.885,.32,1.275);transition:all 500ms cubic-bezier(0.175,.885,.32,1.275)}.alertify-hide{-webkit-transition:all 250ms cubic-bezier(0.6,-.28,.735,.045);-moz-transition:all 250ms cubic-bezier(0.6,-.28,.735,.045);-ms-transition:all 250ms cubic-bezier(0.6,-.28,.735,.045);-o-transition:all 250ms cubic-bezier(0.6,-.28,.735,.045);transition:all 250ms cubic-bezier(0.6,-.28,.735,.045)}.alertify-log-hide{-webkit-transition:all 500ms cubic-bezier(0.6,-.28,.735,.045);-moz-transition:all 500ms cubic-bezier(0.6,-.28,.735,.045);-ms-transition:all 500ms cubic-bezier(0.6,-.28,.735,.045);-o-transition:all 500ms cubic-bezier(0.6,-.28,.735,.045);transition:all 500ms cubic-bezier(0.6,-.28,.735,.045)}.alertify-cover{position:fixed;z-index:99999;top:0;right:0;bottom:0;left:0;background-color:#fff;filter:alpha(opacity=0);opacity:0}.alertify-cover-hidden{display:none}.alertify{position:fixed;z-index:99999;top:50px;left:50%;width:550px;margin-left:-275px;opacity:1}.alertify-hidden{-webkit-transform:translate(0,-150px);-moz-transform:translate(0,-150px);-ms-transform:translate(0,-150px);-o-transform:translate(0,-150px);transform:translate(0,-150px);opacity:0;display:none}:root *> .alertify-hidden{display:block;visibility:hidden}.alertify-logs{position:fixed;z-index:5000;bottom:10px;right:10px;width:300px;top:10px;right:10px}.alertify-logs-hidden{display:none}.alertify-log{display:block;margin-top:10px;position:relative;right:-300px;opacity:0}.alertify-log-show{right:0;opacity:1}.alertify-log-hide{-webkit-transform:translate(300px,0);-moz-transform:translate(300px,0);-ms-transform:translate(300px,0);-o-transform:translate(300px,0);transform:translate(300px,0);opacity:0}.alertify-dialog{padding:25px}.alertify-resetFocus{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.alertify-inner{text-align:center}.alertify-text{margin-bottom:15px;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:100%}.alertify-buttons{}.alertify-button,.alertify-button:hover,.alertify-button:active,.alertify-button:visited{background:0;text-decoration:none;border:0;line-height:1.5;font-size:100%;display:inline-block;cursor:pointer;margin-left:5px}@media only screen and (max-width:680px){.alertify,.alertify-logs{width:90%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.alertify{left:5%;margin:0}}.alertify,.alertify-log{font-family:sans-serif}.alertify{background:#FFF;border:10px solid #333;border:10px solid rgba(0,0,0,.7);border-radius:8px;box-shadow:0 3px 3px rgba(0,0,0,.3);-webkit-background-clip:padding;-moz-background-clip:padding;background-clip:padding-box}.alertify-text{border:1px solid #CCC;padding:10px;border-radius:4px}.alertify-button{border-radius:4px;color:#FFF;font-weight:700;padding:6px 15px;text-decoration:none;text-shadow:1px 1px 0 rgba(0,0,0,.5);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.5);background-image:-webkit-linear-gradient(top,rgba(255,255,255,.3),rgba(255,255,255,0));background-image:-moz-linear-gradient(top,rgba(255,255,255,.3),rgba(255,255,255,0));background-image:-ms-linear-gradient(top,rgba(255,255,255,.3),rgba(255,255,255,0));background-image:-o-linear-gradient(top,rgba(255,255,255,.3),rgba(255,255,255,0));background-image:linear-gradient(top,rgba(255,255,255,.3),rgba(255,255,255,0))}.alertify-button:hover,.alertify-button:focus{outline:0;background-image:-webkit-linear-gradient(top,rgba(0,0,0,.1),rgba(0,0,0,0));background-image:-moz-linear-gradient(top,rgba(0,0,0,.1),rgba(0,0,0,0));background-image:-ms-linear-gradient(top,rgba(0,0,0,.1),rgba(0,0,0,0));background-image:-o-linear-gradient(top,rgba(0,0,0,.1),rgba(0,0,0,0));background-image:linear-gradient(top,rgba(0,0,0,.1),rgba(0,0,0,0))}.alertify-button:focus{box-shadow:0 0 15px #2B72D5}.alertify-button:active{position:relative;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.alertify-button-cancel,.alertify-button-cancel:hover,.alertify-button-cancel:focus{background-color:#FE1A00;border:1px solid #D83526}.alertify-button-ok,.alertify-button-ok:hover,.alertify-button-ok:focus{background-color:#5CB811;border:1px solid #3B7808}.alertify-log{background:#1F1F1F;background:rgba(0,0,0,.9);padding:15px;border-radius:4px;color:#FFF;text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-log-error{background:#FE1A00;background:rgba(254,26,0,.9)}.alertify-log-success{background:#5CB811;background:rgba(92,184,17,.9)}.editableform{margin-bottom:0}.editableform .control-group{margin-bottom:0;white-space:nowrap;line-height:20px}.editable-buttons{display:inline-block;vertical-align:top;margin-left:7px;zoom:1;*display:inline}.editable-buttons.editable-buttons-bottom{display:block;margin-top:7px;margin-left:0}.editable-input{vertical-align:top;display:inline-block;width:auto;white-space:normal;zoom:1;*display:inline}.editable-buttons .editable-cancel{margin-left:7px}.editable-buttons button.ui-button-icon-only{height:24px;width:30px}.editableform-loading{background:url(img/loading.gif) center center no-repeat;height:25px;width:auto;min-width:25px}.editable-inline .editableform-loading{background-position:left 5px}.editable-error-block{max-width:300px;margin:5px 0 0;width:auto;white-space:normal}.editable-error-block.ui-state-error{padding:3px}.editable-error{color:red}.editableform .editable-date{padding:0;margin:0;float:left}.editable-inline .add-on .icon-th{margin-top:3px;margin-left:1px}.editable-checklist label input[type=checkbox],.editable-checklist label span{vertical-align:middle;margin:0}.editable-checklist label{white-space:nowrap}.editable-wysihtml5{width:566px;height:250px}.editable-clear{clear:both;font-size:.9em;text-decoration:none;text-align:right}.editable-clear-x{background:url(img/clear.png) center center no-repeat;display:block;width:13px;height:13px;position:absolute;opacity:.6;z-index:100;top:50%;right:6px;margin-top:-6px}.editable-clear-x:hover{opacity:1}.editable-pre-wrapped{white-space:pre-wrap}.editable-container.editable-popup{max-width:none!important}.editable-container.popover{width:auto}.editable-container.editable-inline{display:inline-block;vertical-align:middle;width:auto;zoom:1;*display:inline}.editable-container.ui-widget{font-size:inherit;z-index:9990}.editable-click,a.editable-click,a.editable-click:hover{text-decoration:none;border-bottom:dashed 1px #08c}.editable-click.editable-disabled,a.editable-click.editable-disabled,a.editable-click.editable-disabled:hover{color:#585858;cursor:default;border-bottom:0}.editable-empty,.editable-empty:hover,.editable-empty:focus{font-style:italic;color:#D14;text-decoration:none}.editable-unsaved{font-weight:700}.editable-unsaved:after{}.editable-bg-transition{-webkit-transition:background-color 1400ms ease-out;-moz-transition:background-color 1400ms ease-out;-o-transition:background-color 1400ms ease-out;-ms-transition:background-color 1400ms ease-out;transition:background-color 1400ms ease-out}.form-horizontal .editable{padding-top:5px;display:inline-block}#tree{margin:10px;padding:10px}ul.jqtree-tree{margin-left:12px}ul.jqtree-tree,ul.jqtree-tree ul.jqtree_common{list-style:none outside;margin-bottom:0;padding:0}ul.jqtree-tree ul.jqtree_common{display:block;margin-left:12px;margin-right:0}ul.jqtree-tree li.jqtree-closed>ul.jqtree_common{display:none}ul.jqtree-tree li.jqtree_common{clear:both;list-style-type:none}ul.jqtree-tree .jqtree-toggler{display:block;position:absolute;left:-1.5em;top:30%;*top:0;font-size:12px;line-height:12px;font-family:arial;border-bottom:0;color:#333}ul.jqtree-tree .jqtree-toggler:hover{color:#000}ul.jqtree-tree .jqtree-element{cursor:pointer}ul.jqtree-tree .jqtree-title{color:#1C4257;vertical-align:middle}ul.jqtree-tree li.jqtree-folder{margin-bottom:4px}ul.jqtree-tree li.jqtree-folder.jqtree-closed{margin-bottom:1px}ul.jqtree-tree li.jqtree-folder .jqtree-title{margin-left:0}ul.jqtree-tree .jqtree-toggler.jqtree-closed{background-position:0 0}span.jqtree-dragging{color:#fff;background:#000;opacity:.6;cursor:pointer;padding:2px 8px}ul.jqtree-tree li.jqtree-ghost{position:relative;z-index:10;margin-right:10px}ul.jqtree-tree li.jqtree-ghost span{display:block}ul.jqtree-tree li.jqtree-ghost span.jqtree-circle{background-image:url(img/jqtree-circle.png);background-repeat:no-repeat;height:8px;width:8px;position:absolute;top:-4px;left:2px}ul.jqtree-tree li.jqtree-ghost span.jqtree-line{background-color:#00f;height:2px;padding:0;position:absolute;top:-1px;left:10px;width:100%}ul.jqtree-tree li.jqtree-ghost.jqtree-inside{margin-left:48px}ul.jqtree-tree span.jqtree-border{position:absolute;display:block;left:-2px;top:0;border:solid 2px #00f;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;margin:0}ul.jqtree-tree .jqtree-element{width:100%;*width:auto;position:relative}ul.jqtree-tree li.jqtree-selected>.jqtree-element,ul.jqtree-tree li.jqtree-selected>.jqtree-element:hover{background-color:#97BDD6;background:-webkit-gradient(linear,left top,left bottom,from(#BEE0F5),to(#89AFCA));background:-moz-linear-gradient(top,#BEE0F5,#89AFCA);background:-ms-linear-gradient(top,#BEE0F5,#89AFCA);background:-o-linear-gradient(top,#BEE0F5,#89AFCA);text-shadow:0 1px 0 rgba(255,255,255,.7)}ul.jqtree-tree .jqtree-moving>.jqtree-element .jqtree-title{outline:dashed 1px #00f}@-webkit-keyframes spin{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes spin{from{-moz-transform:rotate(0deg)}to{-moz-transform:rotate(360deg)}}@-ms-keyframes spin{from{-ms-transform:rotate(0deg)}to{-ms-transform:rotate(360deg)}}.loading{position:absolute;width:28px;height:28px}.loading .spinner{position:absolute;left:1px;top:1px;width:26px;height:26px;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;-o-animation:spin 1s infinite linear;animation:spin 1s infinite linear}.loading .mask{width:12px;height:12px;overflow:hidden}.loading .maskedCircle{width:20px;height:20px;border-radius:12px;border:3px solid #666} -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/favicon.ico -------------------------------------------------------------------------------- /public/gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | concat: { 5 | css: { 6 | src: [ 7 | 'assets-dev/css/**/*.css' 8 | ], 9 | dest: 'assets/site.css' 10 | }, 11 | }, 12 | cssmin: { 13 | options: { 14 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n', 15 | keepSpecialComments: 0, 16 | }, 17 | minify: { 18 | files: { 19 | 'assets/site.min.css': 'assets/site.css' 20 | } 21 | } // MINIFY 22 | }, 23 | uglify: { 24 | options: { 25 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n\n' 26 | }, 27 | js: { 28 | files: { 29 | 'assets/site.min.js': ['assets-dev/js/**/*.js'] 30 | } 31 | } // JS 32 | }, 33 | copy: { 34 | main: { 35 | files: [{ 36 | expand: true, 37 | flatten: true, 38 | src: ['assets-dev/css/**/*.{jpg,png}'], 39 | dest: 'assets/img', 40 | filter: 'isFile' 41 | }] 42 | } 43 | }, 44 | imagemin: { 45 | png: { 46 | options: { 47 | optimizationLevel: 7, 48 | pngquant: true 49 | }, 50 | files: [{ 51 | expand: true, 52 | cwd: "assets/img/", 53 | src: ["*.png"], 54 | dest: "assets/img/", 55 | ext: ".png" 56 | }] 57 | }, // PNG 58 | jpg: { 59 | options: { 60 | progressive: true 61 | }, 62 | files: [{ 63 | expand: true, 64 | cwd: "assets/img/", 65 | src: ["*.jpg"], 66 | dest: "assets/img/", 67 | ext: ".jpg" 68 | }] 69 | } // JPG (has to be separate due to a bug on Windows) 70 | }, 71 | watch: { 72 | grunt: { 73 | files: ['Gruntfile.js'] 74 | }, 75 | css: { 76 | files: ['assets/**/*.css'], 77 | tasks: ['concat', 'cssmin'], 78 | }, // CSS 79 | js: { 80 | files: ['assets-dev/**/*.js'], 81 | tasks: ['uglify:js'], 82 | } // JS 83 | } 84 | }); 85 | grunt.loadNpmTasks('grunt-contrib-imagemin'); 86 | grunt.loadNpmTasks('grunt-contrib-concat'); 87 | grunt.loadNpmTasks('grunt-contrib-uglify'); 88 | grunt.loadNpmTasks('grunt-contrib-watch'); 89 | grunt.loadNpmTasks('grunt-contrib-copy'); 90 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 91 | grunt.loadNpmTasks('grunt-sass'); 92 | grunt.registerTask('build', ['concat', 'cssmin', 'uglify:js', 'copy', 'imagemin']); 93 | grunt.registerTask('default', ['build', 'watch']); 94 | }; -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | /* 10 | |-------------------------------------------------------------------------- 11 | | Register The Auto Loader 12 | |-------------------------------------------------------------------------- 13 | | 14 | | Composer provides a convenient, automatically generated class loader 15 | | for our application. We just need to utilize it! We'll require it 16 | | into the script here so that we do not have to worry about the 17 | | loading of any our classes "manually". Feels great to relax. 18 | | 19 | */ 20 | 21 | require __DIR__.'/../bootstrap/autoload.php'; 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Turn On The Lights 26 | |-------------------------------------------------------------------------- 27 | | 28 | | We need to illuminate PHP development, so let's turn on the lights. 29 | | This bootstraps the framework and gets it ready for use, then it 30 | | will load up this application so that we can run it and send 31 | | the responses back to the browser and delight these users. 32 | | 33 | */ 34 | 35 | $app = require_once __DIR__.'/../bootstrap/start.php'; 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Run The Application 40 | |-------------------------------------------------------------------------- 41 | | 42 | | Once we have the application, we can simply call the run method, 43 | | which will execute the request and send the response back to 44 | | the client's browser allowing them to enjoy the creative 45 | | and wonderful application we have whipped up for them. 46 | | 47 | */ 48 | 49 | $app->run(); 50 | -------------------------------------------------------------------------------- /public/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mrc-categories", 3 | "version": "0.0.1", 4 | "author": "Mr. Casual", 5 | "private": true, 6 | "devDependencies": { 7 | "grunt": "~0.4.2", 8 | "node-sass": "~0.7.0", 9 | "grunt-contrib-concat": "0.1.3", 10 | "grunt-contrib-cssmin": "0.6.1", 11 | "grunt-contrib-watch": "0.5.3", 12 | "grunt-contrib-uglify": "0.2.0", 13 | "jpegtran-bin": "0.2.0", 14 | "grunt-contrib-imagemin": "0.4.0", 15 | "grunt-contrib-copy": "*" 16 | } 17 | } -------------------------------------------------------------------------------- /public/packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcasual/nested-sets-laravel-jquery/497b41b425a5508835aed4d2e2fee9842aecfb98/public/packages/.gitkeep -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Nested sets with Laravel and jQuery 2 | 3 | This is a quick implementation of nested sets using Laravel 4 and jQuery. Key features: 4 | 5 | * Drag & drop sorting (using [jqTree](http://mbraak.github.io/jqTree/)) 6 | * In-place editing (using [X-editable](http://vitalets.github.io/x-editable/)) 7 | * Error notification and modal dialogs (using [Alertify](http://fabien-d.github.io/alertify.js/)) 8 | 9 | Under the hood, I rely on Aleksander Kalnoy's [nested set](https://github.com/lazychaser/laravel4-nestedset) package for Laravel that takes care of hierarchal data management. 10 | 11 | # Demo 12 | 13 | All operations are done without page refresh: 14 | 15 | ![implementation demo](https://raw.github.com/mrcasual/nested-sets-laravel-jquery/master/demo.gif) 16 | 17 | # Installation 18 | 19 | - Clone/download this repo 20 | - Configure your MySQL database in ```./app/config/database.php``` 21 | - Run ``composer install`` 22 | - Run ```php artisan migrate``` 23 | * Launch the web server with ```php artisan serve``` and navigate to [localhost:8000](http://localhost:8000) 24 | 25 | You may also wish to install [Node.js](nodejs.org) and [Grunt](http://gruntjs.com/). By default, JS and CSS assets are served concatenated and minified. Enter ```./public``` and run: 26 | 27 | * ```npm install``` to fetch Grunt packages 28 | * ```grunt``` to start the watch task 29 | 30 | # Contact 31 | 32 | Hit me up on Twitter: [@CasualMr](https://twitter.com/CasualMr) 33 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 |