├── examples
└── normas_on_rails
│ ├── log
│ └── .keep
│ ├── tmp
│ └── .keep
│ ├── vendor
│ └── .keep
│ ├── lib
│ ├── assets
│ │ └── .keep
│ └── tasks
│ │ └── .keep
│ ├── public
│ ├── favicon.ico
│ ├── apple-touch-icon.png
│ ├── apple-touch-icon-precomposed.png
│ ├── robots.txt
│ ├── 500.html
│ ├── 422.html
│ └── 404.html
│ ├── .ruby-version
│ ├── app
│ ├── assets
│ │ ├── images
│ │ │ └── .keep
│ │ └── config
│ │ │ └── manifest.js
│ ├── models
│ │ └── concerns
│ │ │ └── .keep
│ ├── controllers
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── application_controller.rb
│ │ └── main_controller.rb
│ ├── views
│ │ ├── layouts
│ │ │ ├── mailer.text.haml
│ │ │ ├── mailer.html.haml
│ │ │ └── application.html.haml
│ │ └── main
│ │ │ ├── index.html.haml
│ │ │ ├── split_field.html.haml
│ │ │ ├── _split_row.html.haml
│ │ │ ├── _react_demo.html.haml
│ │ │ ├── _my_player.html.haml
│ │ │ ├── _random_component.html.haml
│ │ │ ├── _morpheus.html.haml
│ │ │ └── _split_cell.html.haml
│ ├── jobs
│ │ └── application_job.rb
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ └── helpers
│ │ └── application_helper.rb
│ ├── .browserslistrc
│ ├── .postcssrc.yml
│ ├── client
│ ├── images
│ │ └── morpheus.jpg
│ ├── app
│ │ ├── hello.js
│ │ ├── components
│ │ │ ├── index.js
│ │ │ └── ReactDemo.jsx
│ │ ├── player.js
│ │ ├── split-field.js
│ │ └── global
│ │ │ ├── select2.js
│ │ │ └── popover.js
│ ├── css
│ │ ├── reset.scss
│ │ ├── _base.scss
│ │ ├── s-box.scss
│ │ ├── b-my-player.scss
│ │ ├── b-morpheus.scss
│ │ ├── b-split-field.scss
│ │ └── s-popover.scss
│ ├── packs
│ │ └── application.js
│ └── lib
│ │ └── normas.js
│ ├── config
│ ├── webpack
│ │ ├── test.js
│ │ ├── development.js
│ │ ├── production.js
│ │ └── environment.js
│ ├── spring.rb
│ ├── boot.rb
│ ├── environment.rb
│ ├── initializers
│ │ ├── mime_types.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── application_controller_renderer.rb
│ │ ├── cookies_serializer.rb
│ │ ├── wrap_parameters.rb
│ │ ├── backtrace_silencers.rb
│ │ └── inflections.rb
│ ├── routes.rb
│ ├── cable.yml
│ ├── locales
│ │ └── en.yml
│ ├── application.rb
│ ├── secrets.yml
│ ├── webpacker.yml
│ ├── environments
│ │ ├── development.rb
│ │ ├── test.rb
│ │ └── production.rb
│ └── puma.rb
│ ├── bin
│ ├── bundle
│ ├── rake
│ ├── rails
│ ├── yarn
│ ├── webpack
│ ├── webpack-dev-server
│ ├── spring
│ ├── update
│ └── setup
│ ├── config.ru
│ ├── Rakefile
│ ├── .babelrc
│ ├── .gitignore
│ ├── package.json
│ ├── Gemfile
│ ├── README.md
│ └── Gemfile.lock
├── src
├── .browserslistrc
├── js
│ ├── .babelrc
│ ├── mixins
│ │ ├── base.js
│ │ ├── dom.js
│ │ ├── mutations.js
│ │ ├── view.js
│ │ ├── content.js
│ │ ├── logging.js
│ │ ├── views.js
│ │ ├── navigation.js
│ │ ├── events.js
│ │ ├── elements.js
│ │ └── turbolinks.js
│ ├── index.js
│ ├── lib
│ │ ├── url.js
│ │ ├── jqueryAdditions.js
│ │ └── helpers.js
│ └── extensions
│ │ └── react.js
└── scss
│ ├── _font-face.scss
│ ├── _media.scss
│ ├── _primitives.scss
│ ├── _triangle.scss
│ ├── _typography.scss
│ └── _sugar.scss
├── .gitignore
├── .npmignore
├── dist
└── js
│ ├── integrations
│ ├── react.js
│ ├── react.production.js
│ ├── react.js.map
│ ├── react.production.js.map
│ ├── turbolinks.production.js
│ ├── turbolinks.js
│ ├── turbolinks.production.js.map
│ └── turbolinks.js.map
│ └── extensions
│ ├── views.production.js
│ ├── views.js
│ ├── views.production.js.map
│ └── views.js.map
├── LICENSE
├── package.json
└── config
└── rollup.config.js
/examples/normas_on_rails/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/vendor/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.4.0
2 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 2 versions
2 | not IE < 11
3 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | node_modules/
4 |
5 | yarn-error.log
6 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/layouts/mailer.text.haml:
--------------------------------------------------------------------------------
1 | = yield
2 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/index.html.haml:
--------------------------------------------------------------------------------
1 | %h1 Hello!
2 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 2 versions
2 | not IE < 11
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .gitignore
3 |
4 | /node_modules
5 | yarn.lock
6 |
7 | examples/
8 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/.postcssrc.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | postcss-import: {}
3 | postcss-cssnext: {}
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/split_field.html.haml:
--------------------------------------------------------------------------------
1 | .b-split-field
2 | = render 'split_row'
3 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/_split_row.html.haml:
--------------------------------------------------------------------------------
1 | .b-split-field__row
2 | = render 'split_cell'
3 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/_react_demo.html.haml:
--------------------------------------------------------------------------------
1 | = react_component 'ReactDemo', demoText: 'React Demo! Click me!'
2 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/images/morpheus.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evrone/normas/HEAD/examples/normas_on_rails/client/images/morpheus.jpg
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/hello.js:
--------------------------------------------------------------------------------
1 | import normas from 'lib/normas';
2 |
3 | normas.listenEvents('click h1', () => alert('Hello from Normas!'));
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | const environment = require('./environment');
2 |
3 | module.exports = environment.toWebpackConfig();
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 | end
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/reset.scss:
--------------------------------------------------------------------------------
1 | body {
2 |
3 | }
4 |
5 | *,
6 | *::before,
7 | *::after {
8 | position: relative;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/_base.scss:
--------------------------------------------------------------------------------
1 | @import '~normas/src/scss/sugar';
2 | @import '~normas/src/scss/primitives';
3 |
4 | $common-border-radius: 5px;
5 | $bounce-transition: cubic-bezier(0.42, 0.8, 0.58, 1.2);
6 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root 'main#index'
3 | get 'main/split_field'
4 | get 'main/split_field_row'
5 | get 'main/split_field_cell'
6 | get 'main/morpheus'
7 | end
8 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def react_component(component_name, props = nil)
3 | content_tag :div, '', data: { react_component: component_name, props: props }
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 | channel_prefix: normas-example_production
11 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/_my_player.html.haml:
--------------------------------------------------------------------------------
1 | .b-my-player
2 | .b-my-player__full-screen= "><"
3 |
4 | .b-my-player__playback-controls
5 | .b-my-player__play= ">"
6 | .b-my-player__pause= "||"
7 | .b-my-player__stop= "[]"
8 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/layouts/mailer.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html
3 | %head
4 | %meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/
5 | :css
6 | /* Email styles need to be inline */
7 | /* Use premailer[-rails] */
8 | %body
9 | = yield
10 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | const environment = require('./environment');
2 |
3 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4 | // environment.plugins.append('BundleAnalyzer', new BundleAnalyzerPlugin());
5 |
6 | module.exports = environment.toWebpackConfig();
7 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | const environment = require('./environment');
2 |
3 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4 | // environment.plugins.append('BundleAnalyzer', new BundleAnalyzerPlugin());
5 |
6 | module.exports = environment.toWebpackConfig();
7 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/packs/application.js:
--------------------------------------------------------------------------------
1 | import 'css/reset';
2 |
3 | import 'jquery';
4 | import 'jquery-ujs';
5 |
6 | import 'app/hello';
7 | import 'app/split-field';
8 |
9 | import 'css/s-box';
10 |
11 | import 'app/global/select2';
12 | import 'app/global/popover';
13 |
14 | import 'app/player';
15 | import 'app/components';
16 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/_random_component.html.haml:
--------------------------------------------------------------------------------
1 | - case rand(4)
2 | - when 0
3 | = select_tag :sample_select, options_for_select(['Free', 'Basic', 'Advanced', 'Super Platinum'], selected: 'Free'),
4 | id: "random_select_#{rand(100_000_000)}"
5 | - when 1
6 | = render 'morpheus'
7 | - when 2
8 | = render 'my_player'
9 | - when 3
10 | = render 'react_demo'
11 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | VENDOR_PATH = File.expand_path('..', __dir__)
3 | Dir.chdir(VENDOR_PATH) do
4 | begin
5 | exec "yarnpkg #{ARGV.join(" ")}"
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/components/index.js:
--------------------------------------------------------------------------------
1 | import normasReact from 'normas/dist/js/integrations/react';
2 | import normas from 'lib/normas'; // or may be you use global Normas instance
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 |
6 | normasReact.init({ normas, React, ReactDOM });
7 |
8 | import ReactDemo from './ReactDemo';
9 |
10 | normasReact.registerComponents({
11 | ReactDemo,
12 | });
13 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/controllers/main_controller.rb:
--------------------------------------------------------------------------------
1 | class MainController < ApplicationController
2 | def index; end
3 |
4 | def split_field; end
5 |
6 | def split_field_row
7 | render partial: 'main/split_row'
8 | end
9 |
10 | def split_field_cell
11 | render partial: 'main/split_cell'
12 | end
13 |
14 | def morpheus
15 | render partial: 'main/morpheus', locals: { morpheus_part: 'body' }
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/s-box.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 |
3 | .s-box {
4 | & + & {
5 | margin-top: 10px;
6 | }
7 | border-radius: $common-border-radius;
8 | background: #fff;
9 |
10 | &__caption {
11 | padding: 17px 20px;
12 | //@include font(12px, 16px, medium);
13 | text-transform: uppercase;
14 | user-select: none;
15 | }
16 |
17 | &__inner {
18 | display: block;
19 | padding: 20px;
20 | word-wrap: break-word;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "uglify": true
9 | },
10 | "useBuiltIns": true
11 | }
12 | ],
13 | "react"
14 | ],
15 | "plugins": [
16 | "syntax-dynamic-import",
17 | "transform-object-rest-spread",
18 | [
19 | "transform-class-properties",
20 | {
21 | "spec": true
22 | }
23 | ]
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 | #
4 | # This file was generated by Bundler.
5 | #
6 | # The application 'webpack' is installed as part of a gem, and
7 | # this file is here to facilitate running it.
8 | #
9 |
10 | require "pathname"
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12 | Pathname.new(__FILE__).realpath)
13 |
14 | require "rubygems"
15 | require "bundler/setup"
16 |
17 | load Gem.bin_path("webpacker", "webpack")
18 |
--------------------------------------------------------------------------------
/src/js/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "uglify": true
9 | },
10 | "useBuiltIns": true
11 | }
12 | ]
13 | ],
14 | "plugins": [
15 | "external-helpers",
16 | "syntax-dynamic-import",
17 | "transform-export-extensions",
18 | "transform-object-rest-spread",
19 | [
20 | "transform-class-properties",
21 | {
22 | "spec": true
23 | }
24 | ]
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/webpack-dev-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 | #
4 | # This file was generated by Bundler.
5 | #
6 | # The application 'webpack-dev-server' is installed as part of a gem, and
7 | # this file is here to facilitate running it.
8 | #
9 |
10 | require "pathname"
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12 | Pathname.new(__FILE__).realpath)
13 |
14 | require "rubygems"
15 | require "bundler/setup"
16 |
17 | load Gem.bin_path("webpacker", "webpack-dev-server")
18 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/_morpheus.html.haml:
--------------------------------------------------------------------------------
1 | - morpheus_part ||= 'head'
2 | - if morpheus_part == 'body'
3 | .b-morpheus__body
4 | = render 'morpheus', morpheus_part: 'left-hand'
5 | = render 'morpheus', morpheus_part: 'right-hand'
6 | - else
7 | .b-morpheus{ class: morpheus_part }
8 | = link_to '', main_morpheus_path, remote: true,
9 | class: "js-popover-trigger b-morpheus__#{morpheus_part}",
10 | data: { popover_selector_scope: 'next' }
11 | .s-popover.js-popover
12 | .s-box__inner
13 | Loading...
14 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/main/_split_cell.html.haml:
--------------------------------------------------------------------------------
1 | .b-split-field__cell
2 | %nav.b-split-field__controls
3 | = link_to '', main_split_field_cell_path, remote: true,
4 | class: 'b-split-field__control b-split-field__control_split-cell js-split-cell'
5 | = link_to '', main_split_field_row_path, remote: true,
6 | class: 'b-split-field__control b-split-field__control_split-row js-split-row'
7 | .b-split-field__control.b-split-field__control_remove.js-remove-cell
8 |
9 | .b-split-field__row
10 | = render 'random_component'
11 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | /.generators
11 | /.rakeTasks
12 |
13 | # Ignore all logfiles and tempfiles.
14 | /log/*
15 | /tmp/*
16 | !/log/.keep
17 | !/tmp/.keep
18 |
19 | /node_modules
20 | /yarn-error.log
21 |
22 | .byebug_history
23 | /public/packs
24 | /public/packs-test
25 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/app/views/layouts/application.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html
3 | %head
4 | %title Normas.js example
5 | = csrf_meta_tags
6 |
7 | -#= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
8 | -#= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
9 | = javascript_pack_tag 'application'
10 | = stylesheet_pack_tag 'application'
11 |
12 | %body
13 | %header
14 | %nav
15 | = link_to_unless_current 'Root', root_path
16 | = link_to_unless_current 'Split field', main_split_field_path
17 | %hr
18 |
19 | .content
20 | = yield
21 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "normas-example",
3 | "private": true,
4 | "dependencies": {
5 | "@rails/webpacker": "^3.2.0",
6 | "babel-preset-react": "^6.24.1",
7 | "coffeescript": "1.12.7",
8 | "jquery": "^3.2.1",
9 | "jquery-ujs": "^1.2.2",
10 | "lodash": "^4.17.4",
11 | "normas": "file:../..",
12 | "prop-types": "^15.6.0",
13 | "react": "^16.2.0",
14 | "react-dom": "^16.2.0",
15 | "select2": "^4.0.6-rc.1",
16 | "source-map-loader": "^0.2.3",
17 | "turbolinks": "^5.0.3",
18 | "webpack-bundle-analyzer": "^2.9.1"
19 | },
20 | "devDependencies": {
21 | "webpack-dev-server": "^2.9.7"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) do |repo_name|
4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 | "https://github.com/#{repo_name}.git"
6 | end
7 |
8 |
9 | gem 'rails', '~> 5.1.4'
10 | gem 'haml-rails'
11 | gem 'puma', '~> 3.7'
12 | gem 'webpacker'
13 | gem 'jbuilder', '~> 2.5'
14 |
15 | # Use Capistrano for deployment
16 | # gem 'capistrano-rails', group: :development
17 |
18 | group :development, :test do
19 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
20 | end
21 |
22 | group :development do
23 | gem 'web-console', '>= 3.3.0'
24 | gem 'listen', '>= 3.0.5', '< 3.2'
25 | gem 'spring'
26 | gem 'spring-watcher-listen', '~> 2.0.0'
27 | end
28 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/webpack/environment.js:
--------------------------------------------------------------------------------
1 | const { environment } = require('@rails/webpacker');
2 |
3 | // User ES-next source from Normas for development
4 | const babelLoader = environment.loaders.get('babel');
5 | babelLoader.exclude = /node_modules(?!\/normas)/;
6 |
7 | // Globalize jQuery
8 | const oldToWebpackConfig = environment.toWebpackConfig;
9 | environment.toWebpackConfig = () => {
10 | const config = oldToWebpackConfig.call(environment);
11 | config.resolve.alias = {
12 | jquery: 'jquery/src/jquery',
13 | };
14 | return config;
15 | };
16 |
17 | environment.loaders.append('source-map', {
18 | test: /\.js$/,
19 | use: ['source-map-loader'],
20 | enforce: 'pre'
21 | });
22 |
23 | module.exports = environment;
24 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/components/ReactDemo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ReactDemo extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {
7 | demoText: this.props.demoText,
8 | };
9 | this.handleClickDemoText = this.handleClickDemoText.bind(this);
10 | }
11 |
12 | render() {
13 | return (
14 |
19 | {this.state.demoText}
20 |
21 | );
22 | }
23 |
24 | handleClickDemoText() {
25 | const demoWords = this.state.demoText.split(/\s+/);
26 | demoWords.unshift(demoWords.pop());
27 | this.setState({ demoText: demoWords.join(' ') });
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/README.md:
--------------------------------------------------------------------------------
1 | # Normas.js Example
2 |
3 | Example project with Normas.js and server-side rendering on Rails 5.1
4 |
5 | ### Installation
6 |
7 | `bin/setup` — should be enough
8 |
9 | ### Run
10 |
11 | Run two different processes:
12 |
13 | `bin/webpack-dev-server` — start webpack dev server
14 |
15 | `bin/rails server -b 0.0.0.0 -p 3000 -e development` — start rails server
16 |
17 | ### Contributing
18 |
19 | You can link `normas` from local setup for debugging this example with `normas`:
20 |
21 | `cd ../.. && yarn link && cd examples/normas_on_rails && yarn link "normas"`
22 |
23 | and unlink with `yarn unlink "normas"`
24 |
25 | ### Info
26 |
27 | This project bootstrapped with command:
28 |
29 | `rails new normas_on_rails --skip-coffee --skip-sprockets --skip-turbolinks --webpack=react --skip-active-record -T`
30 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Removing old logs and tempfiles =="
22 | system! 'bin/rails log:clear tmp:clear'
23 |
24 | puts "\n== Restarting application server =="
25 | system! 'bin/rails restart'
26 | end
27 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # Install JavaScript dependencies if using Yarn
22 | system('bin/yarn')
23 |
24 |
25 | puts "\n== Removing old logs and tempfiles =="
26 | system! 'bin/rails log:clear tmp:clear'
27 |
28 | puts "\n== Restarting application server =="
29 | system! 'bin/rails restart'
30 | end
31 |
--------------------------------------------------------------------------------
/src/js/mixins/base.js:
--------------------------------------------------------------------------------
1 | import * as importedHelpers from '../lib/helpers';
2 | import dom from './dom';
3 |
4 | const mutableHelpers = Object.assign({}, importedHelpers);
5 |
6 | export default class Base {
7 | static version = '0.4.0-rc2';
8 | static helpers = mutableHelpers;
9 | static dom = dom;
10 | helpers = mutableHelpers;
11 | dom = dom;
12 |
13 | constructor({ el = document, instanceName = 'NormasApp' }) {
14 | this.instanceName = instanceName;
15 | this.el = el;
16 | this.$el = $(el);
17 | }
18 |
19 | $(...args) {
20 | return this.$el.find(...args);
21 | }
22 |
23 | log(...args) {
24 | // nop
25 | }
26 |
27 | error(...args) {
28 | // nop
29 | }
30 |
31 | // protected
32 |
33 | static readOptions(dest, source, defaults) {
34 | Object.keys(defaults).forEach(key => {
35 | dest[key] = source && (key in source) ? source[key] : defaults[key];
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Normas
3 | *
4 | * @see {@link https://github.com/evrone/normas|Github}
5 | * @license MIT
6 | * @copyright Dmitry Karpunin , 2017-2018
7 | */
8 |
9 | import './lib/jqueryAdditions';
10 | import NormasBase from './mixins/base';
11 | import NormasLogging from './mixins/logging';
12 | import normasEvents from './mixins/events';
13 | import normasContent from './mixins/content';
14 | import normasElements from './mixins/elements';
15 | import normasNavigation from './mixins/navigation';
16 | import normasMutations from './mixins/mutations';
17 |
18 | const NormasSubBase = NORMAS_DEBUG ? NormasLogging(NormasBase) : NormasBase;
19 |
20 | const NormasCore = normasEvents(NormasSubBase);
21 |
22 | const Normas =
23 | normasMutations(
24 | normasNavigation(
25 | normasElements(
26 | normasContent(
27 | NormasCore
28 | )
29 | )
30 | )
31 | );
32 |
33 | Normas.NormasCore = NormasCore;
34 |
35 | export default Normas;
36 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/b-my-player.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 |
3 | .b-my-player {
4 | width: 33%;
5 | margin: 0 auto;
6 | background: black;
7 |
8 | @include before {
9 | padding-bottom: 9 / 16 * 100%;
10 | }
11 |
12 | &__full-screen {
13 | @include absolute(10px, 10px);
14 | }
15 |
16 | &__playback-controls {
17 | @include absolute(false, 10px, 10px, 10px);
18 | margin: 0 auto;
19 | padding: 5px;
20 | background-color: rgba(#fff, 0.2);
21 | border-radius: 5px;
22 | display: flex;
23 | }
24 |
25 | &__play {
26 | }
27 |
28 | &__pause {
29 | display: none;
30 |
31 | }
32 |
33 | &__stop {
34 | display: none;
35 | margin-left: 5px;
36 | }
37 |
38 | &__full-screen,
39 | &__play,
40 | &__pause,
41 | &__stop {
42 | text-align: center;
43 | @include size(15px);
44 | border-radius: 3px;
45 | cursor: pointer;
46 | background-color: #394f56;
47 |
48 |
49 | &:hover {
50 | background-color: #296e7b;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/dist/js/integrations/react.js:
--------------------------------------------------------------------------------
1 | "use strict";var t={selector:"[data-react-component]",listenOptions:{}},e={normas:null,React:null,ReactDOM:null,PropTypes:null,components:{},init:function(e){var n=e.normas,o=e.app,s=e.React,r=e.ReactDOM,i=e.PropTypes,a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},m=n.helpers.deepMerge(t,a),p=m.selector,c=m.listenOptions;this.normas=n||o,this.React=s,this.ReactDOM=r,this.PropTypes=i,n.listenToElement(p,this.mountComponentToElement.bind(this),this.unmountComponentFromElement.bind(this),c)},registerComponents:function(t){Object.assign(this.components,t)},mountComponentToElement:function(t){var e=t[0],n=e.getAttribute("data-react-component");if(n){var o=this.components[n];if(o){var s=e.getAttribute("data-props"),r=s?JSON.parse(s):null,i=this.React.createElement(o,r);this.ReactDOM.render(i,e)}else this.normas.error("No registered component class with name",n)}else this.normas.error("No component name in",e)},unmountComponentFromElement:function(t){var e=t[0];this.ReactDOM.unmountComponentAtNode(e)}};module.exports=e;
2 | //# sourceMappingURL=react.js.map
3 |
--------------------------------------------------------------------------------
/dist/js/integrations/react.production.js:
--------------------------------------------------------------------------------
1 | "use strict";var t={selector:"[data-react-component]",listenOptions:{}},e={normas:null,React:null,ReactDOM:null,PropTypes:null,components:{},init:function(e){var n=e.normas,o=e.app,s=e.React,r=e.ReactDOM,i=e.PropTypes,a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},m=n.helpers.deepMerge(t,a),p=m.selector,c=m.listenOptions;this.normas=n||o,this.React=s,this.ReactDOM=r,this.PropTypes=i,n.listenToElement(p,this.mountComponentToElement.bind(this),this.unmountComponentFromElement.bind(this),c)},registerComponents:function(t){Object.assign(this.components,t)},mountComponentToElement:function(t){var e=t[0],n=e.getAttribute("data-react-component");if(n){var o=this.components[n];if(o){var s=e.getAttribute("data-props"),r=s?JSON.parse(s):null,i=this.React.createElement(o,r);this.ReactDOM.render(i,e)}else this.normas.error("No registered component class with name",n)}else this.normas.error("No component name in",e)},unmountComponentFromElement:function(t){var e=t[0];this.ReactDOM.unmountComponentAtNode(e)}};module.exports=e;
2 | //# sourceMappingURL=react.production.js.map
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017-2018 Dmitry Karpunin
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 |
3 | require "rails"
4 | # Pick the frameworks you want:
5 | require "active_model/railtie"
6 | require "active_job/railtie"
7 | # require "active_record/railtie"
8 | require "action_controller/railtie"
9 | require "action_mailer/railtie"
10 | require "action_view/railtie"
11 | require "action_cable/engine"
12 | # require "sprockets/railtie"
13 | # require "rails/test_unit/railtie"
14 |
15 | # Require the gems listed in Gemfile, including any gems
16 | # you've limited to :test, :development, or :production.
17 | Bundler.require(*Rails.groups)
18 |
19 | module NormasExample
20 | class Application < Rails::Application
21 | # Initialize configuration defaults for originally generated Rails version.
22 | config.load_defaults 5.1
23 |
24 | # Settings in config/environments/* take precedence over those specified here.
25 | # Application configuration should go into files in config/initializers
26 | # -- all .rb files in that directory are automatically loaded.
27 |
28 | # Don't generate system test files.
29 | config.generators.system_tests = nil
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/b-morpheus.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 |
3 | .b-morpheus {
4 | margin: 0 auto;
5 | width: 450px;
6 |
7 | &__head,
8 | &__body {
9 | //&__left-hand,
10 | //&__right-hand {
11 | background: no-repeat url('../images/morpheus.jpg') center top;
12 | }
13 |
14 | &__head {
15 | @include size(130px, 160px);
16 | display: block;
17 | margin: 0 auto;
18 | }
19 |
20 | &__body {
21 | height: 430px - 160px;
22 | background-position: center -160px;
23 | }
24 |
25 | //&__body > & {
26 | // width: auto;
27 | //}
28 |
29 | &__left-hand,
30 | &__right-hand {
31 | width: 30%;
32 | display: block;
33 | //position: absolute;
34 | //bottom: 35px;
35 | height: 100%;
36 | margin: 0 auto;
37 | }
38 |
39 | &.left-hand,
40 | &.right-hand {
41 | //width: 30%;
42 | //display: block;
43 | position: absolute;
44 | bottom: 35px;
45 | height: 100px;
46 | }
47 |
48 | &.left-hand {
49 | left: -155px;
50 | }
51 |
52 | &.right-hand {
53 | right: -155px;
54 | }
55 |
56 | //
57 | //&__left-hand {
58 | // left: 0;
59 | //}
60 | //
61 | //&__right-hand {
62 | // right: 0;
63 | //}
64 | }
65 |
--------------------------------------------------------------------------------
/src/scss/_font-face.scss:
--------------------------------------------------------------------------------
1 | // required typography
2 |
3 | @function font-source-declaration($font-family, $file-path, $file-formats, $base64: false) {
4 | $src: ();
5 | $formats-map: (
6 | eot: '#{$file-path}.eot?#iefix' format('embedded-opentype'),
7 | woff2: '#{$file-path}.woff2' format('woff2'),
8 | woff: '#{$file-path}.woff' format('woff'),
9 | ttf: '#{$file-path}.ttf' format('truetype'),
10 | svg: '#{$file-path}.svg##{$font-family}' format('svg')
11 | );
12 | @each $key, $values in $formats-map {
13 | @if contains($file-formats, $key) {
14 | $file-path: nth($values, 1);
15 | $font-format: nth($values, 2);
16 | $url: if($base64, inline($file-path), resolve($file-path));
17 | $src: append($src, $url $font-format, comma);
18 | }
19 | }
20 | @return $src;
21 | }
22 |
23 | @mixin font-face(
24 | $font-name,
25 | $file-path,
26 | $weight: normal,
27 | $style: normal,
28 | $file-formats: eot woff2 woff ttf svg,
29 | $base64: false) {
30 | @font-face {
31 | font-family: $font-name;
32 | font-style: $style;
33 | font-weight: font-weight($weight);
34 | src: font-source-declaration($font-name, $file-path, $file-formats, $base64);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/player.js:
--------------------------------------------------------------------------------
1 | import normas from 'lib/normas';
2 | // import View from 'normas/src/js/view';
3 |
4 |
5 | import 'css/b-my-player';
6 |
7 | class MyPlayer extends normas.View {
8 | static selector = '.b-my-player';
9 |
10 | static events = {
11 | 'click .b-my-player__full-screen': 'gotoFullScreen',
12 | '.b-my-player__playback-controls': {
13 | 'click .b-my-player__play': 'play',
14 | 'click .b-my-player__pause': 'pause',
15 | 'click .b-my-player__stop': 'stop',
16 | },
17 | };
18 |
19 | gotoFullScreen() {
20 | alert('No fullscreen :)');
21 | }
22 |
23 | play($play) {
24 | $play.hide();
25 | this.$('.b-my-player__pause, .b-my-player__stop').show();
26 | }
27 |
28 | pause($pause) {
29 | $pause.hide();
30 | this.$('.b-my-player__play').show();
31 | this.$('.b-my-player__stop').hide();
32 | }
33 |
34 | stop($stop) {
35 | $stop.hide();
36 | this.$('.b-my-player__play').show();
37 | this.$('.b-my-player__pause').hide();
38 | }
39 | }
40 |
41 | // const testPlayer0 = new normas.View({ $el: $('div:last') });
42 | // const testPlayer = new My2Player({ $el: $('div:last') });
43 |
44 | normas.registerView(MyPlayer);
45 |
--------------------------------------------------------------------------------
/src/scss/_media.scss:
--------------------------------------------------------------------------------
1 | @mixin media($query) {
2 | $media-query: 'screen';
3 | $tick: false;
4 | $prop: false;
5 | @each $q in $query {
6 | $tick: is-not($tick);
7 | @if $tick {
8 | $prop: $q;
9 | $media-query: $media-query + " and (#{$q}:";
10 | } @else {
11 | @if $prop == max-width {
12 | $q: $q - 1px;
13 | }
14 | $media-query: $media-query + " #{$q})";
15 | }
16 | }
17 | @media #{$media-query} {
18 | @content;
19 | }
20 | }
21 |
22 | $_media-variant: false;
23 | $_media-args: false;
24 | $_media-first-arg: false;
25 | $_media-last-arg: false;
26 |
27 | @mixin media-variants($media-variants, $media-prop: max-width) {
28 | $_media-first-arg: true !global;
29 | $index: 0;
30 | $length: length($media-variants);
31 | @each $args in $media-variants {
32 | $_media-args: $args !global;
33 | $_media-variant: map-get($_media-args, variant) !global;
34 | $index: $index + 1;
35 | @if $index == $length {
36 | $_media-last-arg: true !global;
37 | }
38 | @if $_media-variant == base {
39 | @content;
40 | } @else {
41 | @include media($media-prop $_media-variant) {
42 | @content;
43 | }
44 | }
45 | @if $_media-first-arg {
46 | $_media-first-arg: false !global;
47 | }
48 | }
49 | $_media-last-arg: false !global;
50 | $_media-variant: false !global;
51 | $_media-args: false !global;
52 | }
53 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: 33f295774989a6c95fab76264c7b135185d012aaf63d4dbc96034f81b46a895aeceabbdef50993373d44a4262e48bbccb026c866dddf83fc78e6d3a6b99ed210
22 |
23 | test:
24 | secret_key_base: 88a530e0656fdab772c2824996d1adb9be9e0b2f8176d66604db812593d99d0b2036cb2b7b25cf330c886d21dafc90023fb5bccf12bd80218e8bb4514cc5989d
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
33 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/split-field.js:
--------------------------------------------------------------------------------
1 | import normas from 'lib/normas';
2 | import 'css/b-split-field';
3 |
4 | normas.listenEvents('.b-split-field', {
5 | 'ajax:send': () => normas.sayAboutPageLoading(true),
6 | 'ajax:complete': () => normas.sayAboutPageLoading(false),
7 | 'ajax:success .js-split-row': ($splitRowControl, event, newRowHtml) => {
8 | $(newRowHtml).hide().appendTo($splitRowControl.closest('.b-split-field__cell')).fadeIn();
9 | },
10 | 'ajax:success .js-split-cell': ($splitCellControl, event, newCellHtml) => {
11 | $(newCellHtml).hide().insertAfter($splitCellControl.closest('.b-split-field__cell')).fadeIn();
12 | },
13 | 'click .js-remove-cell': $removeCellControl => {
14 | let $cell = $removeCellControl.closest('.b-split-field__cell');
15 | if ($cell.siblings().length === 0) {
16 | $cell = $cell.closest('.b-split-field__row');
17 | }
18 | $cell.fadeOut(() => $cell.remove());
19 | },
20 | 'ajax:error': ($target, event, other) => console.log(other),
21 | });
22 |
23 | // normas.listenToContent(
24 | // ($content) => console.log('new content', $content),
25 | // ($content) => console.log('removed content', $content),
26 | // );
27 |
28 | normas.listenToElement(
29 | '.b-split-field__cell',
30 | $cell => {
31 | //console.log('>>> $cell', $cell);
32 | if ($cell.hasClass('init-cell')) {
33 | console.error('$cell.hasClass(\'init-cell\')');
34 | }
35 | $cell.addClass('init-cell');
36 | },
37 | $cell => {
38 | //console.log('--- $cell', $cell);
39 | $cell.removeClass('init-cell');
40 | },
41 | );
42 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/global/select2.js:
--------------------------------------------------------------------------------
1 | import 'select2';
2 | import 'select2/dist/css/select2';
3 | import normas from 'lib/normas';
4 |
5 | export function bindSelect2($element) {
6 | let element = $element[0];
7 | $element.select2();
8 | let select2 = $element.data('select2');
9 | // this and other changes in this commit resolve Select2 and Fastclick.js conflict
10 | // by https://github.com/select2/select2/issues/3222
11 | select2.$container.find('*').addClass('needsclick');
12 | if (element.hasAttribute('multiple')) {
13 | $element.on('change', onChangeMultipleSelect2);
14 | }
15 | let $search = select2.dropdown.$search || select2.selection.$search;
16 | $search.on('keydown', (event) => {
17 | if (event.which === 9 && select2.isOpen()) {
18 | $element.data('tabPressed', true);
19 | }
20 | });
21 | }
22 |
23 | export function unbindSelect2($element) {
24 | normas.log('unbindSelect2');
25 | if ($element.data('select2')) {
26 | $element.select2('destroy');
27 | }
28 | }
29 |
30 | // normas.listenToPage(() => {
31 | // $('.select2.select2-container').remove();
32 | // });
33 | //
34 | normas.listenToElement('select', bindSelect2, unbindSelect2, { delay: 500 });
35 |
36 | normas.listenEvents({
37 | // changeMedia: debounce(resizeSelects, 100 + 10),
38 | '.select2-selection--single': {
39 | focus(event) {
40 | let $select = $(event.currentTarget).closest('.select2-container').prev('select');
41 | // console.log('focus single, ', $select.attr('name'));
42 | if (!$select.data('closing')) {
43 | // console.log(', go open!');
44 | $select.select2('open');
45 | }
46 | },
47 | },
48 | });
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "normas",
3 | "version": "0.4.0-rc2",
4 | "description": "Normal Ligtweight Javascript Framework for server-side render compatible with Turbolinks",
5 | "author": "Dmitry Karpunin ",
6 | "license": "MIT",
7 | "keywords": [
8 | "lightweight",
9 | "framework",
10 | "turbolinks"
11 | ],
12 | "repository": "evrone/normas",
13 | "files": [
14 | "src",
15 | "dist"
16 | ],
17 | "main": "dist/js/normas.js",
18 | "module": "dist/js/normas.js",
19 | "esnext": "src/js/index.js",
20 | "bundlesize": [
21 | {
22 | "path": "dist/js/normas.js",
23 | "maxSize": "7 kB"
24 | },
25 | {
26 | "path": "dist/js/normas.production.js",
27 | "maxSize": "4.5 kB"
28 | }
29 | ],
30 | "scripts": {
31 | "build": "rollup -c config/rollup.config.js",
32 | "test": "bundlesize"
33 | },
34 | "devDependencies": {
35 | "babel-core": "~6.26.0",
36 | "babel-plugin-external-helpers": "~6.22.0",
37 | "babel-plugin-syntax-dynamic-import": "~6.18.0",
38 | "babel-plugin-transform-class-properties": "~6.24.1",
39 | "babel-plugin-transform-export-extensions": "~6.22.0",
40 | "babel-plugin-transform-object-rest-spread": "~6.26.0",
41 | "babel-preset-env": "~1.6.0",
42 | "bundlesize": "~0.16.0",
43 | "rollup": "~0.54.0",
44 | "rollup-config-module": "~2.0.0",
45 | "rollup-plugin-babel": "~3.0.3",
46 | "rollup-plugin-commonjs": "~8.3.0",
47 | "rollup-plugin-filesize": "~1.5.0",
48 | "rollup-plugin-node-resolve": "~3.0.2",
49 | "rollup-plugin-progress": "~0.4.0",
50 | "rollup-plugin-uglify": "~3.0.0"
51 | },
52 | "peerDependencies": {
53 | "jquery": "^3.0.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/webpacker.yml:
--------------------------------------------------------------------------------
1 | # Note: You must restart bin/webpack-dev-server for changes to take effect
2 |
3 | default: &default
4 | source_path: client
5 | source_entry_path: packs
6 | public_output_path: packs
7 | cache_path: tmp/cache/webpacker
8 |
9 | # Additional paths webpack should lookup modules
10 | # ['app/assets', 'engine/foo/app/assets']
11 | resolved_paths: []
12 |
13 | # Reload manifest.json on all requests so we reload latest compiled packs
14 | cache_manifest: false
15 |
16 | extensions:
17 | # - .coffee
18 | # - .erb
19 | - .js
20 | - .jsx
21 | # - .ts
22 | # - .vue
23 | # - .sass
24 | - .scss
25 | - .css
26 | - .png
27 | - .svg
28 | - .gif
29 | - .jpeg
30 | - .jpg
31 |
32 | development:
33 | <<: *default
34 | compile: true
35 |
36 | # Reference: https://webpack.js.org/configuration/dev-server/
37 | dev_server:
38 | https: false
39 | host: localhost
40 | port: 3035
41 | public: localhost:3035
42 | hmr: false
43 | # Inline should be set to true if using HMR
44 | inline: true
45 | overlay: true
46 | compress: true
47 | disable_host_check: true
48 | use_local_ip: false
49 | quiet: false
50 | headers:
51 | 'Access-Control-Allow-Origin': '*'
52 | watch_options:
53 | ignored: /node_modules/
54 |
55 |
56 | test:
57 | <<: *default
58 | compile: true
59 |
60 | # Compile test packs to a separate directory
61 | public_output_path: packs-test
62 |
63 | production:
64 | <<: *default
65 |
66 | # Production depends on precompilation of packs prior to booting for performance.
67 | compile: false
68 |
69 | # Cache manifest.json for performance
70 | cache_manifest: true
71 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/b-split-field.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 |
3 | $c-row: #008037;
4 | $c-cell: #003780;
5 |
6 | .b-split-field {
7 | &__row {
8 | display: flex;
9 |
10 | background-color: #c8f6cf;
11 | //border: 3px solid $c-row;
12 | border-radius: 5px;
13 | padding: 10px;
14 |
15 | & + & {
16 | margin-top: 10px;
17 | }
18 | }
19 |
20 | &__cell {
21 | flex: 0 1 100%;
22 | min-height: 44px;
23 |
24 | background-color: #abd4ed;
25 | //border: 3px solid $c-cell;
26 | border-radius: 5px;
27 | padding: 10px;
28 |
29 | & + & {
30 | margin-left: 10px;
31 | }
32 | }
33 |
34 | $controls-space: 2px;
35 | &__controls {
36 | @include absolute(3px, false, false, 3px);
37 | z-index: 1;
38 | width: $controls-space * 4 + 20px * 3;
39 | border-radius: 3px;
40 | padding: $controls-space;
41 | background: #999999;
42 |
43 | display: flex;
44 | }
45 |
46 | &__control {
47 |
48 | flex: 0 0 20px;
49 | height: 20px;
50 | background: #CCCCCC;
51 | border-radius: 2px;
52 | padding: 2px;
53 | cursor: pointer;
54 |
55 | &:hover {
56 | background: #EEEEEE;
57 | }
58 |
59 | & + & {
60 | margin-left: $controls-space;
61 | }
62 |
63 | display: flex;
64 | justify-content: space-around;
65 | align-items: center;
66 |
67 | &_split-row {
68 | flex-direction: column;
69 |
70 | @include both {
71 | @include size(14px, 6px);
72 | border: 2px solid $c-row;
73 | }
74 | }
75 |
76 | &_split-cell {
77 | @include both {
78 | @include size(6px, 14px);
79 | border: 2px solid $c-cell;
80 | }
81 | }
82 |
83 | &_remove {
84 | @include cross(20px, 18px, #BB5555, false, 2px, true);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Verifies that versions and hashed value of the package contents in the project's package.json
3 | config.webpacker.check_yarn_integrity = true
4 |
5 | # Settings specified here will take precedence over those in config/application.rb.
6 |
7 | # In the development environment your application's code is reloaded on
8 | # every request. This slows down response time but is perfect for development
9 | # since you don't have to restart the web server when you make code changes.
10 | config.cache_classes = false
11 |
12 | # Do not eager load code on boot.
13 | config.eager_load = false
14 |
15 | # Show full error reports.
16 | config.consider_all_requests_local = true
17 |
18 | # Enable/disable caching. By default caching is disabled.
19 | if Rails.root.join('tmp/caching-dev.txt').exist?
20 | config.action_controller.perform_caching = true
21 |
22 | config.cache_store = :memory_store
23 | config.public_file_server.headers = {
24 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
25 | }
26 | else
27 | config.action_controller.perform_caching = false
28 |
29 | config.cache_store = :null_store
30 | end
31 |
32 | # Don't care if the mailer can't send.
33 | config.action_mailer.raise_delivery_errors = false
34 |
35 | config.action_mailer.perform_caching = false
36 |
37 | # Print deprecation notices to the Rails logger.
38 | config.active_support.deprecation = :log
39 |
40 |
41 | # Raises error for missing translations
42 | # config.action_view.raise_on_missing_translations = true
43 |
44 | # Use an evented file watcher to asynchronously detect changes in source code,
45 | # routes, locales, etc. This feature depends on the listen gem.
46 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
47 | end
48 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/js/mixins/dom.js:
--------------------------------------------------------------------------------
1 | import { isArray, isPlainObject, mapValues, each } from '../lib/helpers';
2 |
3 | export default {
4 | memoryData(element, key, ...value) {
5 | if (value.length > 0) {
6 | this.elementsApply(element, el => { el[key] = value[0]; });
7 | } else {
8 | return element[key];
9 | }
10 | },
11 |
12 | removeMemoryData(element, key) {
13 | const value = element[key];
14 | delete element[key];
15 | return value;
16 | },
17 |
18 | data(element, key, ...value) {
19 | if (value.length > 0) {
20 | const stringifiedValue = this.dataStringify(value[0]);
21 | this.elementsApply(element, el => { el.dataset[key] = stringifiedValue; });
22 | } else {
23 | return key
24 | ?
25 | this.dataParse(element.dataset[key])
26 | :
27 | mapValues(element.dataset, dataValue => this.dataParse(dataValue));
28 | }
29 | },
30 |
31 | removeData(element, key) {
32 | const value = element.dataset[key];
33 | delete element.dataset[key];
34 | return value;
35 | },
36 |
37 | dataStringify(data) {
38 | return isArray(data) || isPlainObject(data)
39 | ?
40 | JSON.stringify(data)
41 | :
42 | data;
43 | },
44 |
45 | dataParse(dataValue) {
46 | try {
47 | return JSON.parse(dataValue);
48 | } catch (e) {
49 | this.lastDataParseError = e;
50 | return dataValue;
51 | }
52 | },
53 |
54 | elementsApply(element, iteratee) {
55 | if (this.isElement(element)) {
56 | iteratee(element);
57 | } else {
58 | each(element, iteratee);
59 | }
60 | },
61 |
62 | isElement(element) {
63 | return element instanceof Element;
64 | },
65 |
66 | contains(rootElement, element) {
67 | return (rootElement === document ? document.body : rootElement).contains(element);
68 | },
69 |
70 | remove(element) {
71 | element.parentNode.removeChild(element);
72 | },
73 | };
74 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/js/lib/url.js:
--------------------------------------------------------------------------------
1 | import { compact, map, isArray } from '../lib/helpers';
2 |
3 | export function filterUrl($form, filterNames = null) {
4 | let params = $form.serializeArray();
5 | let action = $form.attr('action');
6 | params = params.filter(param => param.name !== 'utf8');
7 | return changeUrlParams(action, params, filterNames);
8 | }
9 |
10 | export function changeUrlParams(url, setParams, filterNames = null) {
11 | let urlParts = url.split('?');
12 | let params = getUrlParams(url);
13 | if (!isArray(setParams)) {
14 | setParams = serializedArrayFromHash(setParams);
15 | }
16 | params = prepareParams(params.concat(setParams));
17 | if (filterNames) { params = params.filter(({ name }) => filterNames.indexOf(name.replace(/\[\]$/, '')) > -1) }
18 | urlParts[1] = $.param(params);
19 | url = compact(urlParts).join('?');
20 | return url;
21 | }
22 |
23 | export function getUrlParams(url) {
24 | let paramsPart = url.split('?')[1] || '';
25 | if (!paramsPart) {
26 | return [];
27 | }
28 | let params = paramsPart.split('&');
29 | return params.map((param) => {
30 | let [name, value] = param.split('=').map(decodeURIComponent);
31 | return { name, value };
32 | });
33 | }
34 |
35 | function prepareParams(serializedArray) {
36 | // collapsing value by names to last in order
37 | let params = [];
38 | let names = [];
39 | serializedArray.forEach((param) => {
40 | let index = /\[\]$/.test(param.name) ? -1 : names.indexOf(param.name);
41 | if (index < 0) {
42 | names.push(param.name);
43 | params.push(param);
44 | } else {
45 | params[index] = param;
46 | }
47 | });
48 | return params.filter(param => param.value).sort(serializedSort);
49 | }
50 |
51 | function serializedSort(a, b) {
52 | if (a.name === b.name) {
53 | return a.value >= b.value ? 1 : -1;
54 | }
55 | return a.name > b.name ? 1 : -1;
56 | }
57 |
58 | function serializedArrayFromHash(params) {
59 | return map(params, (value, name) => ({ name, value })).sort(serializedSort);
60 | }
61 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/lib/normas.js:
--------------------------------------------------------------------------------
1 | import Normas from 'normas';
2 | import normasTurbolinks from 'normas/dist/js/integrations/turbolinks';
3 | import normasViews from 'normas/dist/js/extensions/views';
4 | // import Turbolinks from 'turbolinks';
5 | import Turbolinks from '../../vendor/turbolinks-debug';
6 |
7 | const NormasWithTurbolinks =
8 | normasViews(
9 | normasTurbolinks(
10 | Normas
11 | )
12 | );
13 |
14 | const normas = new NormasWithTurbolinks({
15 | Turbolinks,
16 | enabling: { // detailed enabling settings, each default `true`
17 | turbolinks: true,
18 | mutations: true,
19 | },
20 | debugMode: process.env.NODE_ENV === 'development', // default `true`
21 | logging: { // detailed logging settings, mostly default `true`
22 | hideInstancesOf: [Element, NormasWithTurbolinks.View],
23 | constructGrouping: 'groupCollapsed',
24 | // content: true,
25 | // Core level options
26 | // hideInstancesOf: [], // list of constructors whose instances will be muted, ex: [Element, $, Normas.View, Normas]
27 | // construct: true, // logs about constructing, default `false`, because noisy
28 | // constructGrouping: true, // group logging about constructing
29 | // events: true, // logs about events listening
30 | // eventsDebounced: true, // events collect in debounced by 20ms batches
31 | // eventsTable: false, // events subscriptions info as table, default `false`, because massive
32 | // App level options
33 | // elements: true, // logs about element enter and leave
34 | // content: false, // logs about content enter and leave, default `false`, because noisy
35 | // contentGrouping: true, // group logging under content lifecycle
36 | // navigation: true, // logs in navigation mixin
37 | // navigationGrouping: true, // group logging under page events
38 | },
39 | viewOptions: {
40 | logging: {
41 | },
42 | },
43 | });
44 |
45 | global.normas = normas;
46 |
47 | export default normas;
48 |
--------------------------------------------------------------------------------
/src/js/extensions/react.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React integration for Normas
3 | *
4 | * @see {@link https://github.com/evrone/normas#reactjs-integration|Docs}
5 | * @see {@link https://github.com/evrone/normas/blob/master/src/js/extensions/react.js|Source}
6 | * @license MIT
7 | * @copyright Dmitry Karpunin , 2017-2018
8 | */
9 |
10 | const defaultOptions = {
11 | selector: '[data-react-component]',
12 | listenOptions: {},
13 | };
14 |
15 | export default {
16 | normas: null,
17 | React: null,
18 | ReactDOM: null,
19 | PropTypes: null,
20 | components: {},
21 |
22 | init({ normas, app, React, ReactDOM, PropTypes }, options = {}) {
23 | const { selector, listenOptions } = normas.helpers.deepMerge(defaultOptions, options);
24 | this.normas = normas || app;
25 | this.React = React;
26 | this.ReactDOM = ReactDOM;
27 | this.PropTypes = PropTypes;
28 |
29 | normas.listenToElement(
30 | selector,
31 | this.mountComponentToElement.bind(this),
32 | this.unmountComponentFromElement.bind(this),
33 | listenOptions,
34 | );
35 | },
36 |
37 | registerComponents(components) {
38 | Object.assign(this.components, components);
39 | },
40 |
41 | // private
42 |
43 | mountComponentToElement($element) {
44 | const domNode = $element[0];
45 | const name = domNode.getAttribute('data-react-component');
46 | if (!name) {
47 | this.normas.error('No component name in', domNode);
48 | return;
49 | }
50 | const componentClass = this.components[name];
51 | if (!componentClass) {
52 | this.normas.error('No registered component class with name', name);
53 | return;
54 | }
55 | const propsString = domNode.getAttribute('data-props');
56 | const props = propsString ? JSON.parse(propsString) : null;
57 | const component = this.React.createElement(componentClass, props);
58 | this.ReactDOM.render(component, domNode);
59 | },
60 |
61 | unmountComponentFromElement($element) {
62 | const domNode = $element[0];
63 | this.ReactDOM.unmountComponentAtNode(domNode);
64 | },
65 | }
66 |
--------------------------------------------------------------------------------
/src/js/mixins/mutations.js:
--------------------------------------------------------------------------------
1 | // require navigation mixin
2 | export default Base => (class extends Base {
3 | constructor(options) {
4 | super(options);
5 | if (!this.enablings) this.enablings = {};
6 | this.constructor.readOptions(this.enablings, options.enablings, { mutations: true });
7 | if (NORMAS_DEBUG) {
8 | this.log('info', 'construct',
9 | ...this.constructor.logColor(`🤖 "${this.instanceName}" MutationObserver %REPLACE%.`,
10 | this.enablings.mutations ? 'enabled' : 'disabled',
11 | this.enablings.mutations ? 'green' : 'blue'));
12 | }
13 | if (this.enablings.mutations) {
14 | if (MutationObserver) {
15 | this.observeMutations();
16 | } else if (NORMAS_DEBUG) {
17 | this.log('warn', 'construct', `🤖 "${this.instanceName}" mutation observer NOT SUPPORTED!`,
18 | this.constructor.readmeLink('-content-broadcasting'));
19 | }
20 | }
21 | }
22 |
23 | observeMutations() {
24 | // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
25 | this.mutationObserver = new MutationObserver(mutations => mutations.forEach(this.checkMutations));
26 | this.mutationObserver.observe(this.$el[0], { childList: true, subtree: true });
27 | }
28 |
29 | checkMutations = (mutation) => {
30 | if (!this.navigationStarted || mutation.type !== 'childList') {
31 | return;
32 | }
33 | const removedNodes = this.constructor.filterMutationNodes(mutation.removedNodes);
34 | const addedNodes = this.constructor.filterMutationNodes(mutation.addedNodes, true);
35 |
36 | if (removedNodes.length > 0) {
37 | this.sayAboutContentLeave($(removedNodes));
38 | }
39 | if (addedNodes.length > 0) {
40 | this.sayAboutContentEnter($(addedNodes));
41 | }
42 | };
43 |
44 | static filterMutationNodes(nodes, checkParentElement = false) {
45 | return Array.prototype.filter.call(nodes, node => (
46 | node.nodeType === 1 &&
47 | !node.isPreview &&
48 | !['TITLE', 'META'].includes(node.tagName) &&
49 | node.className !== 'turbolinks-progress-bar' &&
50 | !(checkParentElement && !node.parentElement) &&
51 | !(node.parentElement && node.parentElement.tagName === 'HEAD')
52 | ));
53 | }
54 | });
55 |
--------------------------------------------------------------------------------
/src/js/mixins/view.js:
--------------------------------------------------------------------------------
1 | export default Base => (class extends Base {
2 | // Override it with your own initialization logic (like componentDidUnmount in react).
3 | initialize(options) {
4 | }
5 |
6 | // Override it with your own unmount logic (like componentWillUnmount in react).
7 | terminate() {
8 | }
9 |
10 | // protected
11 |
12 | constructor(options) {
13 | Object.assign(options, Base.dom.data(options.el));
14 | super(options);
15 | this.reflectOptions(options);
16 | this.initializeEvents(options);
17 | this.initialize(options);
18 | if (NORMAS_DEBUG && this.logging.constructGrouping) {
19 | this.log('groupEnd', 'construct');
20 | }
21 | }
22 |
23 | destructor() {
24 | if (NORMAS_DEBUG) {
25 | const [destructText, ...destructStyles] = this.constructor.logColor('destructing', 'red');
26 | this.log(this.constructor.groupingMethod(this.logging.constructGrouping), 'construct',
27 | ...this.constructor.logBold(`${this.logging.constructPrefix} "%REPLACE%" ${destructText}`, this.instanceName),
28 | ...destructStyles,
29 | this);
30 | }
31 | this.terminate();
32 | if (this.listenedEvents) {
33 | this.forgetEvents(this.listenedEvents);
34 | this.listenedEvents = null;
35 | }
36 | if (NORMAS_DEBUG && this.logging.constructGrouping) {
37 | this.log('groupEnd', 'construct');
38 | }
39 | }
40 |
41 | reflectOptions(options) {
42 | if (!this.constructor.reflectOptions) {
43 | return;
44 | }
45 | Object.keys(options).forEach(attr => {
46 | if (this.constructor.reflectOptions.includes(attr)) {
47 | this[attr] = options[attr];
48 | }
49 | });
50 | }
51 |
52 | initializeEvents(_options) {
53 | const { events } = this.constructor;
54 | if (events) {
55 | if (!this.linkedEvents) {
56 | this.linkedEvents = this.linkEvents(this.helpers.isFunction(events) ? events() : events);
57 | }
58 | this.listenedEvents = this.listenEvents(this.linkedEvents);
59 | }
60 | }
61 |
62 | linkEvents(events) {
63 | return this.helpers.mapValues(events, handle => this.helpers.isString(handle) ?
64 | this[handle].bind(this)
65 | :
66 | (typeof this.helpers.isPlainObject(handle) ? this.linkEvents(handle) : handle)
67 | );
68 | }
69 |
70 | data(key, ...value) {
71 | this.dom.data(this.el, key, ...value);
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/src/js/lib/jqueryAdditions.js:
--------------------------------------------------------------------------------
1 | $.fn.each$ = function (handle) {
2 | return this.each((index, element) => {
3 | handle($(element), index);
4 | });
5 | };
6 |
7 | $.fn.filter$ = function (handle) {
8 | return this.filter((index, element) => handle($(element), index));
9 | };
10 |
11 | $.fn.map$ = function (handle) {
12 | return this.map((index, element) => handle($(element), index));
13 | };
14 |
15 | // [showOrHide[, duration[, callback]]]
16 | $.fn.slideToggleByState = function slideToggleByState(...a) {
17 | if (this.length > 0) {
18 | if (a.length > 0) {
19 | if (a.shift()) {
20 | this.slideDown(...a);
21 | } else {
22 | this.slideUp(...a);
23 | }
24 | } else {
25 | this.slideToggle();
26 | }
27 | }
28 | return this;
29 | };
30 |
31 | // http://css-tricks.com/snippets/jquery/mover-cursor-to-end-of-textarea/
32 | $.fn.focusToEnd = function focusToEnd() {
33 | let $this = this.first();
34 | if ($this.is('select, :checkbox, :radio')) {
35 | $this.focus();
36 | } else {
37 | let val = $this.val();
38 | $this.focus().val('').val(val);
39 | }
40 | return this;
41 | };
42 |
43 | $.fn.focusTo = function focusTo(caretPos) {
44 | return this.each((index, element) => {
45 | if (element.createTextRange) {
46 | let range = element.createTextRange();
47 | range.move('character', caretPos);
48 | range.select();
49 | } else if (element.selectionStart) {
50 | element.focus();
51 | element.setSelectionRange(caretPos, caretPos);
52 | } else {
53 | element.focus();
54 | }
55 | });
56 | };
57 |
58 | /*
59 | ** Returns the caret (cursor) position of the specified text field.
60 | ** Return value range is 0-oField.value.length.
61 | */
62 | $.fn.caretPosition = function caretPosition() {
63 | // Initialize
64 | let iCaretPos = 0;
65 | let oField = this[0];
66 |
67 | // IE Support
68 | if (document.selection) {
69 | // Set focus on the element
70 | oField.focus();
71 | // To get cursor position, get empty selection range
72 | let oSel = document.selection.createRange();
73 | // Move selection start to 0 position
74 | oSel.moveStart('character', -oField.value.length);
75 | // The caret position is selection length
76 | iCaretPos = oSel.text.length;
77 | } else if (oField.selectionStart != null) {
78 | iCaretPos = oField.selectionStart;
79 | }
80 |
81 | // Return results
82 | return iCaretPos;
83 | };
84 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # If you are preloading your application and using Active Record, it's
36 | # recommended that you close any connections to the database before workers
37 | # are forked to prevent connection leakage.
38 | #
39 | # before_fork do
40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
41 | # end
42 |
43 | # The code in the `on_worker_boot` will be called if you are using
44 | # clustered mode by specifying a number of `workers`. After each worker
45 | # process is booted, this block will be run. If you are using the `preload_app!`
46 | # option, you will want to use this block to reconnect to any threads
47 | # or connections that may have been created at application boot, as Ruby
48 | # cannot share connections between processes.
49 | #
50 | # on_worker_boot do
51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
52 | # end
53 | #
54 |
55 | # Allow puma to be restarted by `rails restart` command.
56 | plugin :tmp_restart
57 |
--------------------------------------------------------------------------------
/config/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs';
2 | import nodeResolve from 'rollup-plugin-node-resolve';
3 | import babel from 'rollup-plugin-babel';
4 | import uglify from 'rollup-plugin-uglify';
5 | import progress from 'rollup-plugin-progress';
6 | import filesize from 'rollup-plugin-filesize';
7 | import colors from 'colors'; // dependency from 'rollup-plugin-filesize'
8 |
9 | const bundles = [
10 | {
11 | name: 'normas',
12 | input: 'index.js',
13 | },
14 | {
15 | name: 'integrations/turbolinks',
16 | input: 'mixins/turbolinks.js',
17 | },
18 | {
19 | name: 'integrations/react',
20 | input: 'extensions/react.js',
21 | },
22 | {
23 | name: 'extensions/views',
24 | input: 'mixins/views.js',
25 | },
26 | ];
27 |
28 | bundles.forEach(b => b.debug = true);
29 |
30 | bundles.push(...bundles.map(({ name, input }) => ({
31 | name: `${name}.production`,
32 | input,
33 | debug: false,
34 | })));
35 |
36 | export default bundles.map(({ name, input, debug }) => ({
37 | input: `src/js/${input}`,
38 | output: {
39 | strict: true,
40 | file: `dist/js/${name}.js`,
41 | name,
42 | format: 'cjs',
43 | sourcemap: true,
44 | },
45 | plugins: [
46 | nodeResolve({
47 | // module: true,
48 | // jsnext: true,
49 | // browser: true,
50 | // main: true,
51 | // jail: '/src/js',
52 | // modulesOnly: true,
53 | }),
54 | babel({
55 | sourceMap: true,
56 | exclude: 'node_modules/**',
57 | }),
58 | commonjs(),
59 | uglify({
60 | toplevel: true,
61 | compress: {
62 | global_defs: {
63 | NORMAS_DEBUG: debug,
64 | },
65 | },
66 | // output: {
67 | // // comments: 'all',
68 | // comments: function(node, comment) {
69 | // // multiline comment
70 | // // if (comment.type === 'comment2') {
71 | // return /@preserve|@license|@cc_on/i.test(comment.value);
72 | // // }
73 | // },
74 | // },
75 | }),
76 | progress(),
77 | filesize({
78 | render: function(opt, size, gzip, _bundle) {
79 | const primaryColor = opt.theme === 'dark' ? 'green' : 'black';
80 | const secondaryColor = opt.theme === 'dark' ? 'blue' : 'blue';
81 | return (
82 | (colors[primaryColor].bold('Bundle size: ') + colors[secondaryColor](size)) +
83 | (opt.showGzippedSize ? ', ' + colors[primaryColor].bold('Gzipped size: ') + colors[secondaryColor](gzip) : "")
84 | );
85 | },
86 | }),
87 | ],
88 | }));
89 |
--------------------------------------------------------------------------------
/src/scss/_primitives.scss:
--------------------------------------------------------------------------------
1 | @mixin scrollable {
2 | overflow-x: hidden;
3 | overflow-y: auto;
4 |
5 | // Edge-tech property for smooth scrolling on touch devices
6 | // scss-lint:disable PropertySpelling
7 | -webkit-overflow-scrolling: touch;
8 | overflow-scrolling: touch;
9 | // scss-lint:enable PropertySpelling
10 | }
11 |
12 | @mixin chevron($size, $color, $direction: false, $border-width: 1px) {
13 | @if $size {
14 | @include size($size);
15 | }
16 | border-width: $border-width $border-width 0 0;
17 | border-style: solid;
18 | border-color: $color;
19 | @if $direction {
20 | @include chevron-direction($direction);
21 | }
22 | @content;
23 | }
24 |
25 | @mixin chevron-direction($direction) {
26 | // up || right || down || left || up-right || up-left || down-left || down-right
27 | @if $direction == up {
28 | transform: rotate(-45deg);
29 | } @else if $direction == right {
30 | transform: rotate(45deg);
31 | } @else if $direction == down {
32 | transform: rotate(135deg);
33 | } @else if $direction == left {
34 | transform: rotate(-135deg);
35 | } @else if $direction == up-right {
36 | transform: rotate(0deg);
37 | } @else if $direction == up-left {
38 | transform: rotate(-90deg);
39 | } @else if $direction == down-left {
40 | transform: rotate(-180deg);
41 | } @else if $direction == down-right {
42 | transform: rotate(-270deg);
43 | } @else {
44 | @error 'Unknown argument in chevron-direction($direction: #{$direction})';
45 | }
46 | }
47 |
48 | @mixin cross(
49 | $element-size, $line-size, $line-color,
50 | $set-element-size: true, $line-width: 1px,
51 | $rotate: false, $hover-color: false) {
52 | @if $set-element-size {
53 | @include size($element-size);
54 | }
55 | @include both {
56 | @include size(0, $line-size);
57 | @include absolute(($element-size - $line-size) / 2, false, false, ($element-size - $line-width) / 2);
58 | border-right: $line-width solid $line-color;
59 | @content;
60 | }
61 | @if $rotate {
62 | &::before {
63 | transform: rotate(-45deg);
64 | }
65 | &::after {
66 | transform: rotate(45deg);
67 | }
68 | } @else {
69 | &::after {
70 | transform: rotate(90deg);
71 | }
72 | }
73 | @if $hover-color {
74 | &:hover {
75 | &::before,
76 | &::after {
77 | border-color: $hover-color;
78 | }
79 | }
80 | }
81 | }
82 |
83 | @mixin cross-size($element-size, $line-size) {
84 | $padding: ($element-size - $line-size) / 2;
85 | @include size($element-size);
86 | &::before,
87 | &::after {
88 | height: $line-size;
89 | @if $padding > 0 {
90 | top: $padding;
91 | }
92 | left: $element-size / 2;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/scss/_triangle.scss:
--------------------------------------------------------------------------------
1 | @mixin triangle($size, $color, $direction) {
2 | $width: nth($size, 1);
3 | $height: nth($size, length($size));
4 | $foreground-color: nth($color, 1);
5 | $background-color: if(length($color) == 2, nth($color, 2), transparent);
6 | height: 0;
7 | width: 0;
8 |
9 | @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
10 | $width: $width / 2;
11 | $height: if(length($size) > 1, $height, $height / 2);
12 |
13 | @if $direction == up {
14 | border-bottom: $height solid $foreground-color;
15 | border-left: $width solid $background-color;
16 | border-right: $width solid $background-color;
17 | } @else if $direction == right {
18 | border-bottom: $width solid $background-color;
19 | border-left: $height solid $foreground-color;
20 | border-top: $width solid $background-color;
21 | } @else if $direction == down {
22 | border-left: $width solid $background-color;
23 | border-right: $width solid $background-color;
24 | border-top: $height solid $foreground-color;
25 | } @else if $direction == left {
26 | border-bottom: $width solid $background-color;
27 | border-right: $height solid $foreground-color;
28 | border-top: $width solid $background-color;
29 | }
30 | } @else if ($direction == up-right) or ($direction == up-left) {
31 | border-top: $height solid $foreground-color;
32 |
33 | @if $direction == up-right {
34 | border-left: $width solid $background-color;
35 | } @else if $direction == up-left {
36 | border-right: $width solid $background-color;
37 | }
38 | } @else if ($direction == down-right) or ($direction == down-left) {
39 | border-bottom: $height solid $foreground-color;
40 |
41 | @if $direction == down-right {
42 | border-left: $width solid $background-color;
43 | } @else if $direction == down-left {
44 | border-right: $width solid $background-color;
45 | }
46 | } @else if ($direction == inset-up) {
47 | border-color: $background-color $background-color $foreground-color;
48 | border-style: solid;
49 | border-width: $height $width;
50 | } @else if ($direction == inset-down) {
51 | border-color: $foreground-color $background-color $background-color;
52 | border-style: solid;
53 | border-width: $height $width;
54 | } @else if ($direction == inset-right) {
55 | border-color: $background-color $background-color $background-color $foreground-color;
56 | border-style: solid;
57 | border-width: $width $height;
58 | } @else if ($direction == inset-left) {
59 | border-color: $background-color $foreground-color $background-color $background-color;
60 | border-style: solid;
61 | border-width: $width $height;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/scss/_typography.scss:
--------------------------------------------------------------------------------
1 | @mixin font($font-size, $line-height: false, $args...) {
2 | @include _primary-font-prop(font-size, $font-size);
3 | @include _primary-font-prop(line-height, $line-height);
4 | @each $arg in $args {
5 | @include _font-prop($arg);
6 | }
7 | }
8 |
9 | @mixin _primary-font-prop($name, $arg) {
10 | @if $arg {
11 | @if type-of($arg) == number or index(inherit initial, $arg) {
12 | #{$name}: $arg;
13 | } @else {
14 | @include _font-prop($arg);
15 | }
16 | }
17 | }
18 |
19 | @mixin _font-prop($arg) {
20 | $arg-part: nth($arg, 1);
21 | @if type-of($arg-part) == color {
22 | color: $arg;
23 | } @else if index(capitalize lowercase uppercase, $arg-part) {
24 | text-transform: $arg;
25 | } @else if index(center left right, $arg-part) {
26 | text-align: $arg;
27 | } @else if index(blink line-through overline underline, $arg-part) {
28 | text-decoration: $arg;
29 | } @else if index(pre pre-line pre-wrap nowrap, $arg-part) {
30 | white-space: $arg;
31 | } @else {
32 | $normalized-font-weight: font-weight($arg-part);
33 | @if $normalized-font-weight {
34 | $arg: set-nth($arg, 1, $normalized-font-weight);
35 | font-weight: $arg;
36 | } @else {
37 | @error 'Unknown argument in @mixin font(..., #{$arg-part}:#{type-of($arg-part)}, ...)';
38 | }
39 | }
40 | }
41 |
42 | @mixin font-weight($font-weight) {
43 | $normalized-font-weight: font-weight($font-weight);
44 | @if $normalized-font-weight {
45 | font-weight: $normalized-font-weight;
46 | } @else {
47 | @error 'Unknown argument in @function font-weight($font-weight: #{$font-weight})';
48 | }
49 | }
50 |
51 | @function font-weight($font-weight) {
52 | @if ($font-weight == 100 or $font-weight == thin or $font-weight == hairline) {
53 | @return 100;
54 | } @else if($font-weight == 200 or $font-weight == extralight or $font-weight == ultralight) {
55 | @return 200;
56 | } @else if($font-weight == 300 or $font-weight == light) {
57 | @return 300;
58 | } @else if($font-weight == 400 or $font-weight == normal or $font-weight == regular) {
59 | @return 400;
60 | } @else if($font-weight == 500 or $font-weight == medium) {
61 | @return 500;
62 | } @else if($font-weight == 600 or $font-weight == semibold or $font-weight == demibold) {
63 | @return 600;
64 | } @else if($font-weight == 700 or $font-weight == bold) {
65 | @return 700;
66 | } @else if($font-weight == 800 or $font-weight == extrabold or $font-weight == ultrabold) {
67 | @return 800;
68 | } @else if($font-weight == 900 or $font-weight == 'black' or $font-weight == heavy) {
69 | @return 900;
70 | }
71 | @return false;
72 | }
73 |
74 | @mixin ellipsis($display: inline-block, $width: 100%) {
75 | @if $display {
76 | display: $display;
77 | }
78 | @if $width {
79 | max-width: $width;
80 | }
81 | overflow: hidden;
82 | text-overflow: ellipsis;
83 | white-space: nowrap;
84 | word-wrap: normal;
85 | }
86 |
--------------------------------------------------------------------------------
/dist/js/integrations/react.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"react.js","sources":["../../../src/js/extensions/react.js"],"sourcesContent":["/**\n * React integration for Normas\n *\n * @see {@link https://github.com/evrone/normas#reactjs-integration|Docs}\n * @see {@link https://github.com/evrone/normas/blob/master/src/js/extensions/react.js|Source}\n * @license MIT\n * @copyright Dmitry Karpunin , 2017-2018\n */\n\nconst defaultOptions = {\n selector: '[data-react-component]',\n listenOptions: {},\n};\n\nexport default {\n normas: null,\n React: null,\n ReactDOM: null,\n PropTypes: null,\n components: {},\n\n init({ normas, app, React, ReactDOM, PropTypes }, options = {}) {\n const { selector, listenOptions } = normas.helpers.deepMerge(defaultOptions, options);\n this.normas = normas || app;\n this.React = React;\n this.ReactDOM = ReactDOM;\n this.PropTypes = PropTypes;\n\n normas.listenToElement(\n selector,\n this.mountComponentToElement.bind(this),\n this.unmountComponentFromElement.bind(this),\n listenOptions,\n );\n },\n\n registerComponents(components) {\n Object.assign(this.components, components);\n },\n\n // private\n\n mountComponentToElement($element) {\n const domNode = $element[0];\n const name = domNode.getAttribute('data-react-component');\n if (!name) {\n this.normas.error('No component name in', domNode);\n return;\n }\n const componentClass = this.components[name];\n if (!componentClass) {\n this.normas.error('No registered component class with name', name);\n return;\n }\n const propsString = domNode.getAttribute('data-props');\n const props = propsString ? JSON.parse(propsString) : null;\n const component = this.React.createElement(componentClass, props);\n this.ReactDOM.render(component, domNode);\n },\n\n unmountComponentFromElement($element) {\n const domNode = $element[0];\n this.ReactDOM.unmountComponentAtNode(domNode);\n },\n}\n"],"names":["defaultOptions","normas","app","React","ReactDOM","PropTypes","options","helpers","deepMerge","selector","listenOptions","listenToElement","this","mountComponentToElement","bind","unmountComponentFromElement","components","assign","$element","domNode","name","getAttribute","componentClass","propsString","props","JSON","parse","component","createElement","render","error","unmountComponentAtNode"],"mappings":"aASA,IAAMA,YACM,qDAKF,WACD,cACG,eACC,wCAGJC,IAAAA,OAAQC,IAAAA,IAAKC,IAAAA,MAAOC,IAAAA,SAAUC,IAAAA,UAAaC,8DACZL,EAAOM,QAAQC,UAAUR,EAAgBM,GAArEG,IAAAA,SAAUC,IAAAA,mBACbT,OAASA,GAAUC,OACnBC,MAAQA,OACRC,SAAWA,OACXC,UAAYA,IAEVM,gBACLF,EACAG,KAAKC,wBAAwBC,KAAKF,MAClCA,KAAKG,4BAA4BD,KAAKF,MACtCF,gCAIeM,UACVC,OAAOL,KAAKI,WAAYA,qCAKTE,OAChBC,EAAUD,EAAS,GACnBE,EAAOD,EAAQE,aAAa,2BAC7BD,OAICE,EAAiBV,KAAKI,WAAWI,MAClCE,OAICC,EAAcJ,EAAQE,aAAa,cACnCG,EAAQD,EAAcE,KAAKC,MAAMH,GAAe,KAChDI,EAAYf,KAAKT,MAAMyB,cAAcN,EAAgBE,QACtDpB,SAASyB,OAAOF,EAAWR,aANzBlB,OAAO6B,MAAM,0CAA2CV,aALxDnB,OAAO6B,MAAM,uBAAwBX,yCAclBD,OACpBC,EAAUD,EAAS,QACpBd,SAAS2B,uBAAuBZ"}
--------------------------------------------------------------------------------
/dist/js/integrations/react.production.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"react.production.js","sources":["../../../src/js/extensions/react.js"],"sourcesContent":["/**\n * React integration for Normas\n *\n * @see {@link https://github.com/evrone/normas#reactjs-integration|Docs}\n * @see {@link https://github.com/evrone/normas/blob/master/src/js/extensions/react.js|Source}\n * @license MIT\n * @copyright Dmitry Karpunin , 2017-2018\n */\n\nconst defaultOptions = {\n selector: '[data-react-component]',\n listenOptions: {},\n};\n\nexport default {\n normas: null,\n React: null,\n ReactDOM: null,\n PropTypes: null,\n components: {},\n\n init({ normas, app, React, ReactDOM, PropTypes }, options = {}) {\n const { selector, listenOptions } = normas.helpers.deepMerge(defaultOptions, options);\n this.normas = normas || app;\n this.React = React;\n this.ReactDOM = ReactDOM;\n this.PropTypes = PropTypes;\n\n normas.listenToElement(\n selector,\n this.mountComponentToElement.bind(this),\n this.unmountComponentFromElement.bind(this),\n listenOptions,\n );\n },\n\n registerComponents(components) {\n Object.assign(this.components, components);\n },\n\n // private\n\n mountComponentToElement($element) {\n const domNode = $element[0];\n const name = domNode.getAttribute('data-react-component');\n if (!name) {\n this.normas.error('No component name in', domNode);\n return;\n }\n const componentClass = this.components[name];\n if (!componentClass) {\n this.normas.error('No registered component class with name', name);\n return;\n }\n const propsString = domNode.getAttribute('data-props');\n const props = propsString ? JSON.parse(propsString) : null;\n const component = this.React.createElement(componentClass, props);\n this.ReactDOM.render(component, domNode);\n },\n\n unmountComponentFromElement($element) {\n const domNode = $element[0];\n this.ReactDOM.unmountComponentAtNode(domNode);\n },\n}\n"],"names":["defaultOptions","normas","app","React","ReactDOM","PropTypes","options","helpers","deepMerge","selector","listenOptions","listenToElement","this","mountComponentToElement","bind","unmountComponentFromElement","components","assign","$element","domNode","name","getAttribute","componentClass","propsString","props","JSON","parse","component","createElement","render","error","unmountComponentAtNode"],"mappings":"aASA,IAAMA,YACM,qDAKF,WACD,cACG,eACC,wCAGJC,IAAAA,OAAQC,IAAAA,IAAKC,IAAAA,MAAOC,IAAAA,SAAUC,IAAAA,UAAaC,8DACZL,EAAOM,QAAQC,UAAUR,EAAgBM,GAArEG,IAAAA,SAAUC,IAAAA,mBACbT,OAASA,GAAUC,OACnBC,MAAQA,OACRC,SAAWA,OACXC,UAAYA,IAEVM,gBACLF,EACAG,KAAKC,wBAAwBC,KAAKF,MAClCA,KAAKG,4BAA4BD,KAAKF,MACtCF,gCAIeM,UACVC,OAAOL,KAAKI,WAAYA,qCAKTE,OAChBC,EAAUD,EAAS,GACnBE,EAAOD,EAAQE,aAAa,2BAC7BD,OAICE,EAAiBV,KAAKI,WAAWI,MAClCE,OAICC,EAAcJ,EAAQE,aAAa,cACnCG,EAAQD,EAAcE,KAAKC,MAAMH,GAAe,KAChDI,EAAYf,KAAKT,MAAMyB,cAAcN,EAAgBE,QACtDpB,SAASyB,OAAOF,EAAWR,aANzBlB,OAAO6B,MAAM,0CAA2CV,aALxDnB,OAAO6B,MAAM,uBAAwBX,yCAclBD,OACpBC,EAAUD,EAAS,QACpBd,SAAS2B,uBAAuBZ"}
--------------------------------------------------------------------------------
/src/js/mixins/content.js:
--------------------------------------------------------------------------------
1 | // require events mixin
2 | export default Base => (class extends Base {
3 | static contentEnterEventName = 'content:enter';
4 | static contentLeaveEventName = 'content:leave';
5 |
6 | constructor(options) {
7 | super(options);
8 | if (NORMAS_DEBUG) {
9 | this.constructor.readOptions(this.logging, options.logging, {
10 | content: false,
11 | contentGrouping: true,
12 | });
13 | this.log('info', 'construct',
14 | `📰 "${this.instanceName}" content mixin activated.`,
15 | 'logging.content =', this.logging.content);
16 | }
17 | }
18 |
19 | // subscription to content lifecycle
20 |
21 | listenToContent(enter, leave = null) {
22 | if (enter) {
23 | this.$el.on(this.constructor.contentEnterEventName, (event, $content) => enter($content, event));
24 | }
25 | if (leave) {
26 | this.$el.on(this.constructor.contentLeaveEventName, (event, $content) => leave($content, event));
27 | }
28 | }
29 |
30 | // manual content broadcasting
31 |
32 | sayAboutContentEnter($content) {
33 | return this.sayAboutContentMove('enter', this.constructor.contentEnterEventName, $content);
34 | }
35 |
36 | sayAboutContentLeave($content) {
37 | return this.sayAboutContentMove('leave', this.constructor.contentLeaveEventName, $content);
38 | }
39 |
40 | // private
41 | sayAboutContentMove(move, eventName, $content) {
42 | const enter = move === 'enter';
43 | $content = this.constructor.filterContent($content, (enter ? 'normasEntered' : 'normasLeft'), enter);
44 | if ($content.length > 0) {
45 | if (NORMAS_DEBUG) {
46 | this.logContent(move, $content);
47 | }
48 | this.trigger(eventName, $content);
49 | if (NORMAS_DEBUG && this.logging.contentGrouping) {
50 | this.log('groupEnd', 'content');
51 | }
52 | }
53 | return $content;
54 | }
55 |
56 | // helpers
57 |
58 | replaceContentInner($container, content) {
59 | this.sayAboutContentLeave($container);
60 | $container.html(content);
61 | this.sayAboutContentEnter($container);
62 | }
63 |
64 | replaceContent($content, $newContent) {
65 | if ($content.length > 1) {
66 | $content = $content.first();
67 | }
68 | this.sayAboutContentLeave($content);
69 | $content.replaceWith($newContent);
70 | this.sayAboutContentEnter($newContent);
71 | }
72 |
73 | // private
74 |
75 | logContent(logEvent, $content) {
76 | if (!NORMAS_DEBUG) {
77 | return;
78 | }
79 | const [eventName, ...eventStyles] = this.constructor.logCycle(logEvent, logEvent === 'enter', 5);
80 | const [contentName, ...contentStyles] = this.constructor.logBold(this.constructor.contentName($content));
81 | this.log(this.constructor.groupingMethod(this.logging.contentGrouping), 'content',
82 | `📰 content ${eventName} "${contentName}"`,
83 | ...eventStyles, ...contentStyles,
84 | $content);
85 | }
86 |
87 | static filterContent($content, elementFlagName, checkParentElement = false) {
88 | return $content.filter((_index, element) => {
89 | if (element[elementFlagName] || (checkParentElement && !element.parentElement)) {
90 | return false;
91 | }
92 | element[elementFlagName] = true;
93 | return true;
94 | });
95 | }
96 | });
97 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/css/s-popover.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 |
3 | .js-popover-trigger {
4 | //@include cursor-pointer;
5 | //> .s-popover span {
6 | // cursor: default;
7 | //}
8 | }
9 |
10 | @mixin s-popover-style {
11 | background-color: #fff;
12 | border-radius: $common-border-radius;
13 | box-shadow:
14 | 0 0 13px rgba(#296e7b, 0.19),
15 | 0 8px 25px rgba(#394f56, 0.13);
16 | }
17 |
18 | @mixin s-popover-show-transition($show,
19 | $shift: -30px,
20 | $duration: $common-ui-transition-duration,
21 | $enter-transition: $bounce-transition) {
22 | @if (not $show) {
23 | @include transition-props((
24 | visibility: hidden,
25 | opacity: 0,
26 | transform: scale(0.9) translate(0, $shift)
27 | ), $duration, $will-change: true);
28 | } @else {
29 | @include transition-props((
30 | visibility: visible,
31 | opacity: 1,
32 | transform: scale(1) translate(0, 0)
33 | ), $duration, $enter-transition);
34 | }
35 | }
36 |
37 | .s-popover {
38 | //@include font($font-size-base, $line-height-base, regular, $c-base);
39 | text-align: left;
40 | white-space: normal;
41 |
42 | //@include gpu;
43 | @include absolute(100%, false, false, 0);
44 |
45 | min-width: 100%;
46 | min-height: 35px;
47 | max-height: 70vh;
48 | display: flex;
49 | flex-direction: column;
50 | z-index: 101;
51 | @include s-popover-style;
52 | @include s-popover-show-transition(false);
53 |
54 | .s-popover-hover-trigger:hover + &,
55 | .s-popover-hover-trigger:hover > &,
56 | &.show {
57 | @include s-popover-show-transition(true);
58 | }
59 | cursor: default;
60 |
61 | &.with-corner {
62 | @include before {
63 | @include s-popover-style;
64 | @include size(21px);
65 | transform: rotate(45deg);
66 | @include absolute(-2px, 0, false, 0);
67 | margin: auto;
68 | }
69 | }
70 |
71 | > .s-box__inner {
72 | background: #fff;
73 | border-radius: $common-border-radius;
74 | }
75 |
76 | &.right {
77 | right: 0;
78 | left: auto;
79 | }
80 | //@include desktop-tablet {
81 | // &.desktop-right {
82 | // right: 0;
83 | // left: auto;
84 | // }
85 | //}
86 |
87 | &__caption {
88 | @include height-with-line(50px);
89 | //border-bottom: 1px solid $c-form-border;
90 | //@include font(12px, false, medium);
91 | text-align: center;
92 | text-transform: uppercase;
93 | user-select: none;
94 | background: #fff;
95 | border-radius: $common-border-radius $common-border-radius 0 0;
96 | }
97 |
98 | &.select {
99 | margin-top: 2px;
100 | padding: 8px 0;
101 | text-align: left;
102 | > .sort_link,
103 | > .option {
104 | + .sort_link,
105 | + .option {
106 | margin-top: 4px;
107 | }
108 | padding: 9px 15px;
109 | //@include ellipsis(block);
110 | //@include font(14px, 15px, regular, $c-action-gray);
111 | @include cursor-pointer;
112 | &:hover {
113 | //color: $c-action-gray_hover;
114 | //background-color: $c-select-option_hover;
115 | }
116 | &.desc,
117 | &.asc,
118 | &.selected {
119 | color: #fff;
120 | //background-color: $c-brand;
121 | }
122 | }
123 | }
124 | }
125 |
126 | //.popover-links {
127 | // padding: 7px 0 5px;
128 | // > a {
129 | // display: block;
130 | // padding: 13px 15px;
131 | // //@include font(14px, 15px, regular);
132 | // &.with-border-top {
133 | // margin-top: 5px;
134 | // border-top: 1px solid $c-form-border;
135 | // padding-top: 17px;
136 | // }
137 | // }
138 | //}
139 |
--------------------------------------------------------------------------------
/dist/js/integrations/turbolinks.production.js:
--------------------------------------------------------------------------------
1 | "use strict";var t=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},e=function(){function t(t,e){for(var o=0;o (class extends Base {
2 | constructor(options) {
3 | super(options);
4 | this.constructor.readOptions(this, options, { debugMode: true });
5 | this.logging = {};
6 | this.constructor.readOptions(this.logging, options.logging, {
7 | construct: true,
8 | constructGrouping: true,
9 | constructPrefix: '🏗️',
10 | hideInstancesOf: [],
11 | });
12 | const [constructText, ...constructStyles] = this.constructor.logColor('construct', 'green');
13 | this.log(this.constructor.groupingMethod(this.logging.constructGrouping), 'construct',
14 | ...this.constructor.logBold(`${this.logging.constructPrefix} "%REPLACE%" ${constructText}`, this.instanceName),
15 | ...constructStyles,
16 | this);
17 | }
18 |
19 | static consoleMethods = ['log', 'group', 'groupEnd', 'groupCollapsed', 'info', 'table', 'warn'];
20 |
21 | log(...args) {
22 | if (!this.debugMode) return;
23 | const method = this.helpers.includes(this.constructor.consoleMethods, args[0]) ? args.shift() : 'log';
24 | if (this.helpers.intersection(Object.keys(this.logging), args[0]).length > 0 && !this.logging[args.shift()]) {
25 | return;
26 | }
27 | this.constructor.log(method, ...this.filterLog(args));
28 | }
29 |
30 | error(...args) {
31 | this.constructor.log('error', ...args);
32 | }
33 |
34 | // private
35 |
36 | filterLog(args) {
37 | return this.logging.hideInstancesOf.length === 0 ? args :
38 | this.helpers.filter(args, a => !this.helpers.find(this.logging.hideInstancesOf, c => a instanceof c));
39 | }
40 |
41 | static groupingMethod(grouping) {
42 | return (typeof grouping === 'string') ? grouping : (grouping ? 'group' : 'log');
43 | }
44 |
45 | static log(method, ...args) {
46 | if (console && console[method]) {
47 | console[method](...args); // eslint-disable-line no-console
48 | }
49 | }
50 |
51 | static logPlur(message, count, omitOne = true) {
52 | return message.replace('%COUNT%', omitOne && count === 1 ? '' : count).replace('%S%', count === 1 ? '' : 's');
53 | }
54 |
55 | static logCycle(moveName, enter, intensity = 2) {
56 | return this.logColor(
57 | `${moveName} ${(enter ? '>' : '<').repeat(intensity)}`,
58 | enter ? 'green' : 'red',
59 | );
60 | }
61 |
62 | static logColor(template, colorText, color) {
63 | if (!color) {
64 | color = colorText;
65 | colorText = null;
66 | }
67 | return this.logStyle(template, colorText, { color });
68 | }
69 |
70 | static logBold(template, boldText) {
71 | return this.logStyle(template, boldText, { 'font-weight': 'bold' });
72 | }
73 |
74 | static logStyle(template, stylingReplace, style) {
75 | if (this.helpers.isPlainObject(stylingReplace)) {
76 | style = stylingReplace;
77 | stylingReplace = null;
78 | }
79 | if (!stylingReplace) {
80 | stylingReplace = template;
81 | template = null;
82 | }
83 | const stylePairs = Object.entries(style);
84 | const beginStyle = stylePairs.map(p => p.join(': ')).join('; ');
85 | const endStyle = stylePairs.map(([k]) => [k, 'inherit'].join(': ')).join('; ');
86 | stylingReplace = `%c${stylingReplace}%c`;
87 | if (template) {
88 | stylingReplace = template.replace('%REPLACE%', stylingReplace);
89 | }
90 | return [stylingReplace, beginStyle, endStyle];
91 | }
92 |
93 | static contentName($content) {
94 | const contentCounts = this.helpers.countBy($content, content => {
95 | const classList = content.classList ? this.helpers.map(content.classList, className => className) : [];
96 | return [content.tagName].concat(classList).join('.');
97 | });
98 | return Object.keys(contentCounts).map(name => {
99 | const count = contentCounts[name];
100 | return `${count > 1 ? count + ' ' : ''}${name}`;
101 | }).join(' + ');
102 | }
103 |
104 | static readmeLink(point) {
105 | return `Read https://github.com/evrone/normas#${point}`;
106 | }
107 | });
108 |
--------------------------------------------------------------------------------
/src/js/mixins/views.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Views system for Normas
3 | *
4 | * @see {@link https://github.com/evrone/normas#-views|Docs}
5 | * @see {@link https://github.com/evrone/normas/blob/master/src/js/mixins/views.js|Source}
6 | * @license MIT
7 | * @copyright Dmitry Karpunin , 2017-2018
8 | */
9 |
10 | // TODO: may be rename Views, views, View, view
11 | import normasView from './view';
12 |
13 | export default Base => normasViews(Base, normasView(Base.NormasCore));
14 |
15 | // require content mixin
16 | // require events mixin
17 | const normasViews = (Base, View) => (class extends Base {
18 | static View = View;
19 | View = View;
20 | viewClasses = {};
21 | viewInstances = [];
22 |
23 | constructor(options) {
24 | super(options);
25 | this.viewOptions = {
26 | debugMode: this.debugMode,
27 | ...options.viewOptions,
28 | };
29 | if (NORMAS_DEBUG) {
30 | this.viewOptions.logging = {
31 | ...this.logging,
32 | constructGrouping: 'groupCollapsed',
33 | constructPrefix: '🏭', // private
34 | eventsDebounced: false,
35 | ...(options.viewOptions && options.viewOptions.logging),
36 | };
37 | this.log('info', 'construct', `🏭 "${this.instanceName}" views mixin activated.`);
38 | }
39 | }
40 |
41 | registerView(viewClass, options = {}) {
42 | if (this.viewClasses[viewClass.selector]) {
43 | if (NORMAS_DEBUG) {
44 | this.error(`🏭 View class for selector \`${viewClass.selector}\` already registered`,
45 | this.viewClasses[viewClass.selector]);
46 | }
47 | return;
48 | }
49 | this.viewClasses[viewClass.selector] = viewClass;
50 | this.listenToElement(
51 | viewClass.selector,
52 | $el => this.bindView($el, viewClass, options),
53 | $el => this.unbindView($el, viewClass),
54 | {
55 | delay: viewClass.delay,
56 | silent: true,
57 | },
58 | );
59 | }
60 |
61 | bindView($el, viewClass, options) {
62 | if (!this.canBind($el, viewClass)) {
63 | return null;
64 | }
65 | if (viewClass.instanceIndex) {
66 | viewClass.instanceIndex += 1;
67 | } else {
68 | viewClass.instanceIndex = 1;
69 | }
70 | const view = new viewClass({
71 | ...this.helpers.deepMerge(this.viewOptions, options),
72 | instanceName: `${viewClass.selector}_${viewClass.instanceIndex}`,
73 | el: $el[0],
74 | });
75 | this.viewInstances.push(view);
76 | return view;
77 | }
78 |
79 | canBind($element, viewClass) {
80 | const view = this.getViewsOnElement($element, viewClass)[0];
81 | if (view) {
82 | if (NORMAS_DEBUG) {
83 | this.log('warn', '🏭 Element already has bound view', $element, viewClass, view);
84 | }
85 | return false;
86 | }
87 | return true;
88 | }
89 |
90 | unbindView($element, viewClass) {
91 | const view = this.getViewsOnElement($element, viewClass)[0];
92 | if (view) {
93 | view.destructor();
94 | this.viewInstances = this.helpers.without(this.viewInstances, view);
95 | }
96 | }
97 |
98 | getViewsOnElement($element, viewClass = null) {
99 | const el = $element instanceof $ ? $element[0] : $element;
100 | const filterOptions = { el };
101 | if (viewClass) {
102 | filterOptions.constructor = viewClass;
103 | }
104 | return this.helpers.filter(this.viewInstances, filterOptions);
105 | }
106 |
107 | getViewsInContainer($container, checkRoot = true) {
108 | return this.helpers.filter(this.viewInstances, view =>
109 | view.$el.closest($container).length > 0 && (checkRoot || view.el !== $container[0])
110 | );
111 | }
112 |
113 | getAllViews(viewClass) {
114 | return this.helpers.filter(this.viewInstances, { constructor: viewClass });
115 | }
116 |
117 | getFirstView(viewClass) {
118 | return this.helpers.find(this.viewInstances, { constructor: viewClass });
119 | }
120 |
121 | getFirstChildView(viewClass) {
122 | return this.helpers.find(this.viewInstances, view => view instanceof viewClass);
123 | }
124 | });
125 |
--------------------------------------------------------------------------------
/src/scss/_sugar.scss:
--------------------------------------------------------------------------------
1 | // borrowed from https://github.com/thoughtbot/neat
2 | @function is-not($value) {
3 | @return if($value, false, true);
4 | }
5 |
6 | @function is-even($value) {
7 | @return if(round($value / 2) * 2 == $value, true, false);
8 | }
9 |
10 | @function contains($list, $values...) {
11 | @each $value in $values {
12 | @if type-of(index($list, $value)) != 'number' {
13 | @return false;
14 | }
15 | }
16 | @return true;
17 | }
18 |
19 | @function fix-line-height($line-height) {
20 | //@return if(is-even($line-height), $line-height, $line-height - 1px);
21 | @return $line-height;
22 | }
23 |
24 | @mixin height-with-line($height) {
25 | height: $height;
26 | line-height: fix-line-height($height);
27 | }
28 |
29 | @mixin presence-props($props) {
30 | @each $prop, $value in $props {
31 | @if $value {
32 | #{$prop}: #{$value};
33 | }
34 | }
35 | }
36 |
37 | @mixin size($width, $height: $width) {
38 | @include presence-props((width: $width, height: $height));
39 | }
40 |
41 | @mixin position($position, $top: false, $right: false, $bottom: false, $left: false) {
42 | @include presence-props((position: $position, top: $top, right: $right, bottom: $bottom, left: $left));
43 | }
44 |
45 | @mixin absolute($top: false, $right: false, $bottom: false, $left: false) {
46 | @include position(absolute, $top, $right, $bottom, $left);
47 | }
48 |
49 | @mixin fixed($top: false, $right: false, $bottom: false, $left: false) {
50 | @include position(fixed, $top, $right, $bottom, $left);
51 | }
52 |
53 | @mixin before($display: block, $content: '') {
54 | @include pseudo('&::before', $display, $content) { @content; }
55 | }
56 |
57 | @mixin after($display: block, $content: '') {
58 | @include pseudo('&::after', $display, $content) { @content; }
59 | }
60 |
61 | @mixin both($display: block, $content: '') {
62 | @include pseudo('&::before, &::after', $display, $content) { @content; }
63 | }
64 |
65 | @mixin pseudo($selector, $display: block, $content: '') {
66 | #{$selector} {
67 | @if $display {
68 | display: $display;
69 | }
70 | content: $content;
71 | @content;
72 | }
73 | }
74 |
75 | @mixin disabled {
76 | cursor: default;
77 | pointer-events: none;
78 | }
79 |
80 | @mixin cursor-pointer($simple: false) {
81 | user-select: none;
82 | @if $simple {
83 | cursor: pointer;
84 | } @else {
85 | &,
86 | & span {
87 | cursor: pointer;
88 | }
89 | }
90 | }
91 |
92 | @mixin cursor-default($simple: false) {
93 | @if $simple {
94 | cursor: default;
95 | } @else {
96 | &,
97 | & span {
98 | cursor: default;
99 | }
100 | }
101 | }
102 |
103 | @mixin gpu {
104 | transform: translateZ(0);
105 | }
106 |
107 | @mixin ios-smooth-scroll {
108 | -webkit-overflow-scrolling: touch;
109 | }
110 |
111 | $common-ui-transition-duration: 300ms !default;
112 |
113 | @mixin transition-props($props, $duration: $common-ui-transition-duration, $transition-function: ease, $will-change: false) {
114 | $transitions: ();
115 | $will-change-props: ();
116 | @each $prop, $value in $props {
117 | #{$prop}: $value;
118 | $tf: if($prop == visibility, if($value == hidden, step-end, step-start), $transition-function);
119 | $transitions: append($transitions, $prop $duration $tf, comma);
120 | @if $will-change {
121 | $will-change-props: append($will-change-props, $prop, comma);
122 | }
123 | }
124 | transition: $transitions;
125 | @if $will-change {
126 | will-change: $will-change-props;
127 | }
128 | }
129 |
130 | @function vertical-paddings($outer-height, $inner-height) {
131 | $space: $outer-height - $inner-height;
132 | $space-bottom: round($space / 2);
133 | $space-top: $space - $space-bottom;
134 | @return $space-top $space-bottom $space;
135 | }
136 |
137 | @function list-to-string($list, $separator: '') {
138 | $result: '';
139 | $first: true;
140 | @each $cut in $list {
141 | @if $first {
142 | $first: false;
143 | $result: $cut;
144 | } @else {
145 | $result: "#{$result}#{$separator}#{$cut}";
146 | }
147 | }
148 | @return $result;
149 | }
150 |
--------------------------------------------------------------------------------
/examples/normas_on_rails/client/app/global/popover.js:
--------------------------------------------------------------------------------
1 | import 'css/s-popover';
2 | import 'css/b-morpheus';
3 |
4 | import normas from 'lib/normas';
5 |
6 | const popoverTriggerSelector = '.js-popover-trigger';
7 | const popoverSelector = '.js-popover';
8 | let $activePopovers = $([]);
9 |
10 | normas.listenEvents(popoverTriggerSelector, {
11 | click: clickOnTrigger,
12 | 'ajax:before': ajaxBeforeOnTrigger,
13 | 'ajax:success': ajaxSuccessOnTrigger,
14 | });
15 |
16 | normas.listenEvents(popoverSelector, {
17 | click: clickOnPopover,
18 | 'click > .option': clickOnPopoverOption,
19 | });
20 |
21 | normas.listenEvents('click', () => { closeActivePopovers(normas.$el) });
22 |
23 | function clickOnTrigger($trigger, event) {
24 | const $popover = getPopoverForTrigger($trigger);
25 | const isActive = $popover.is($activePopovers);
26 | closeActivePopovers($trigger.closest($activePopovers));
27 | if (!isActive) {
28 | $trigger.addClass('active');
29 | $popover.addClass('show');
30 | $popover.find(':input:not(:disabled, [readonly]):first').focus();
31 | $activePopovers = $activePopovers.add($popover);
32 | }
33 | return false;
34 | }
35 |
36 | function ajaxBeforeOnTrigger($trigger, event) {
37 | const $popover = getPopoverForTrigger($trigger);
38 | return !$popover.is($activePopovers);
39 | // normas.visit($trigger.attr('href'));
40 | // return false;
41 | }
42 |
43 | function ajaxSuccessOnTrigger($trigger, event, content) {
44 | const $popover = getPopoverForTrigger($trigger);
45 | let $content = $(content);
46 | let $innerContent = $content.find('.js-popover-content');
47 | $popover.html($innerContent.length > 0 ? $innerContent : $content);
48 | // normas.sayAboutContentLeave($popover).html($innerContent.length > 0 ? $innerContent : $content);
49 | // normas.sayAboutContentEnter($popover);
50 | $popover.data('ujsLoaded', true);
51 | return false;
52 | }
53 |
54 | function clickOnPopover($popover, event) {
55 | let $link = $(event.target).closest('a');
56 | if ($link.length > 0) {
57 | let href = $link.attr('href');
58 | if (href) {
59 | normas.visit(href);
60 | }
61 | closeActivePopovers($popover.parent());
62 | } else {
63 | closeActivePopovers($popover);
64 | }
65 | event.stopPropagation();
66 | return false;
67 | }
68 |
69 | function clickOnPopoverOption($option, event) {
70 | let $popover = $option.parent(popoverSelector);
71 | let $selected = $popover.prev('.selected');
72 | if ($selected.length) {
73 | $option.addClass('selected').siblings('.selected').removeClass('selected');
74 | $selected.html($option.html());
75 | let $field = $selected.prev('input');
76 | if ($field.length) {
77 | $field.val($option.data('value'));
78 | }
79 | closeActivePopovers(getTriggerForPopover($popover));
80 | } else {
81 | console.warn('No .selected element in clickOnPopoverOption()'); // eslint-disable-line
82 | }
83 | }
84 |
85 | function closeActivePopovers($root) {
86 | if ($root === undefined || $root.length === 0) {
87 | $root = normas.$el;
88 | }
89 | let $localActivePopovers = $root.find($activePopovers);
90 | if ($localActivePopovers.length === 0) {
91 | return false;
92 | }
93 | $localActivePopovers.removeClass('show');
94 | getTriggerForPopover($localActivePopovers).removeClass('active');
95 | $activePopovers = $activePopovers.not($localActivePopovers);
96 | return true;
97 | }
98 |
99 | function getPopoverForTrigger($trigger) {
100 | let $popover = $trigger.data('$popover');
101 | if (!$popover) {
102 | let customPopoverSelector = ($trigger.data('popoverSelector') || popoverSelector) + ':first'; // eslint-disable-line
103 | let popoverSelectorScope = $trigger.data('popoverSelectorScope') || 'find';
104 | $popover = popoverSelectorScope === '$' ?
105 | $(customPopoverSelector)
106 | :
107 | $trigger[popoverSelectorScope](customPopoverSelector);
108 | $trigger.data('$popover', $popover);
109 | $popover.data('$trigger', $trigger);
110 | }
111 | return $popover;
112 | }
113 |
114 | function getTriggerForPopover($popover) {
115 | return $popover.data('$trigger');
116 | }
117 |
--------------------------------------------------------------------------------
/src/js/mixins/navigation.js:
--------------------------------------------------------------------------------
1 | // require content mixin
2 | export default Base => (class extends Base {
3 | static pageEnterEventName = 'page:enter';
4 | static pageLeaveEventName = 'page:leave';
5 | static navigationStartedEventName = 'navigation:started';
6 | static pageSelector = 'body';
7 | navigationStarted = false;
8 |
9 | constructor(options) {
10 | super(options);
11 | if (NORMAS_DEBUG) {
12 | this.constructor.readOptions(this.logging, options.logging, {
13 | navigation: true,
14 | navigationGrouping: true,
15 | });
16 | }
17 | this.bindPageEvents(options);
18 | if (NORMAS_DEBUG) {
19 | this.log('info', 'construct',
20 | `🗺 "${this.instanceName}" navigation mixin activated. logging.navigation =`, this.logging.navigation);
21 | }
22 | }
23 |
24 | onStart(callback) {
25 | this.$el.one(this.constructor.navigationStartedEventName, (event, $page) => callback($page));
26 | }
27 |
28 | bindPageEvents(options) {
29 | if (NORMAS_DEBUG && (options.Turbolinks || global.Turbolinks)) {
30 | this.log('warn',
31 | '🗺 You have Turbolinks, but not use integration.',
32 | this.constructor.readmeLink('turbolinks-integration'));
33 | }
34 | $(this.pageEnter.bind(this));
35 | }
36 |
37 | listenToPage(enter, leave = null) {
38 | if (enter) {
39 | this.$el.on(this.constructor.pageEnterEventName, (event, $page) => enter($page));
40 | }
41 | if (leave) {
42 | this.$el.on(this.constructor.pageLeaveEventName, (event, $page) => leave($page));
43 | }
44 | }
45 |
46 | visit(location) {
47 | window.location = location;
48 | }
49 |
50 | refreshPage() {
51 | this.visit(window.location);
52 | }
53 |
54 | setHash(hash) {
55 | location.hash = hash;
56 | }
57 |
58 | back() {
59 | global.history.back();
60 | }
61 |
62 | replaceLocation(url) {
63 | if (NORMAS_DEBUG) {
64 | this.log('warn', '🗺 `replaceLocation` works only with Turbolinks.');
65 | }
66 | }
67 |
68 | pushLocation(url, title = null, state = null) {
69 | if (global.history) global.history.pushState(state, title, url);
70 | }
71 |
72 | sayAboutPageLoading(state) {
73 | if (NORMAS_DEBUG) {
74 | this.log('warn', '🗺 `sayAboutPageLoading` works only with Turbolinks.');
75 | }
76 | }
77 |
78 | pageEnter() {
79 | if (!this.navigationStarted) {
80 | this.navigationStarted = true;
81 | this.trigger(this.constructor.navigationStartedEventName, $page);
82 | if (NORMAS_DEBUG && this.logging.constructGrouping) {
83 | this.log('groupEnd', 'construct');
84 | }
85 | }
86 | const $page = this.$page();
87 | if (NORMAS_DEBUG) {
88 | this.logPage('enter', $page);
89 | }
90 | this.trigger(this.constructor.pageEnterEventName, $page);
91 | this.sayAboutContentEnter($page);
92 | }
93 |
94 | pageLeave() {
95 | const $page = this.$page();
96 | if (NORMAS_DEBUG) {
97 | this.logPage('leave', $page);
98 | }
99 | this.sayAboutContentLeave($page);
100 | this.trigger(this.constructor.pageLeaveEventName, $page);
101 | }
102 |
103 | $page() {
104 | return this.$(this.constructor.pageSelector);
105 | }
106 |
107 | // private
108 |
109 | logPage(logEvent, $page) {
110 | if (!NORMAS_DEBUG || !this.debugMode || !this.logging.navigation) {
111 | return;
112 | }
113 | const enter = logEvent === 'enter';
114 | const [eventName, ...eventStyles] = this.constructor.logCycle(logEvent, enter, 10);
115 | if (this.logging.navigationGrouping) {
116 | this.logPageGroupEnd();
117 | }
118 | this.log(this.constructor.groupingMethod(this.logging.navigationGrouping), 'navigation',
119 | `🗺 page ${eventName}`,
120 | ...eventStyles,
121 | ...(enter ? [window.location.href] : []),
122 | $page);
123 | this.navigationGroup = true;
124 | if (enter && this.logging.navigationGrouping) {
125 | setTimeout(() => this.logPageGroupEnd(), 25);
126 | }
127 | }
128 |
129 | logPageGroupEnd() {
130 | if (NORMAS_DEBUG && this.navigationGroup) {
131 | this.log('groupEnd', 'navigation');
132 | this.navigationGroup = false;
133 | }
134 | }
135 | });
136 |
--------------------------------------------------------------------------------
/dist/js/extensions/views.production.js:
--------------------------------------------------------------------------------
1 | "use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1?n-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:{};this.viewClasses[e.selector]||(this.viewClasses[e.selector]=e,this.listenToElement(e.selector,function(i){return t.bindView(i,e,n)},function(n){return t.unbindView(n,e)},{delay:e.delay,silent:!0}))}},{key:"bindView",value:function(e,t,n){if(!this.canBind(e,t))return null;t.instanceIndex?t.instanceIndex+=1:t.instanceIndex=1;var r=new t(i({},this.helpers.deepMerge(this.viewOptions,n),{instanceName:t.selector+"_"+t.instanceIndex,el:e[0]}));return this.viewInstances.push(r),r}},{key:"canBind",value:function(e,t){var n=this.getViewsOnElement(e,t)[0];return!n}},{key:"unbindView",value:function(e,t){var n=this.getViewsOnElement(e,t)[0];n&&(n.destructor(),this.viewInstances=this.helpers.without(this.viewInstances,n))}},{key:"getViewsOnElement",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n={el:e instanceof $?e[0]:e};return t&&(n.constructor=t),this.helpers.filter(this.viewInstances,n)}},{key:"getViewsInContainer",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return this.helpers.filter(this.viewInstances,function(n){return n.$el.closest(e).length>0&&(t||n.el!==e[0])})}},{key:"getAllViews",value:function(e){return this.helpers.filter(this.viewInstances,{constructor:e})}},{key:"getFirstView",value:function(e){return this.helpers.find(this.viewInstances,{constructor:e})}},{key:"getFirstChildView",value:function(e){return this.helpers.find(this.viewInstances,function(t){return t instanceof e})}}]),u}(),Object.defineProperty(l,"View",{enumerable:!0,writable:!0,value:o}),u};module.exports=function(e){return l(e,o(e.NormasCore))};
2 | //# sourceMappingURL=views.production.js.map
3 |
--------------------------------------------------------------------------------
/dist/js/integrations/turbolinks.js:
--------------------------------------------------------------------------------
1 | "use strict";var t=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},e=function(){function t(t,e){for(var n=0;n 2.0)
7 | websocket-driver (~> 0.6.1)
8 | actionmailer (5.1.4)
9 | actionpack (= 5.1.4)
10 | actionview (= 5.1.4)
11 | activejob (= 5.1.4)
12 | mail (~> 2.5, >= 2.5.4)
13 | rails-dom-testing (~> 2.0)
14 | actionpack (5.1.4)
15 | actionview (= 5.1.4)
16 | activesupport (= 5.1.4)
17 | rack (~> 2.0)
18 | rack-test (>= 0.6.3)
19 | rails-dom-testing (~> 2.0)
20 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
21 | actionview (5.1.4)
22 | activesupport (= 5.1.4)
23 | builder (~> 3.1)
24 | erubi (~> 1.4)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
27 | activejob (5.1.4)
28 | activesupport (= 5.1.4)
29 | globalid (>= 0.3.6)
30 | activemodel (5.1.4)
31 | activesupport (= 5.1.4)
32 | activerecord (5.1.4)
33 | activemodel (= 5.1.4)
34 | activesupport (= 5.1.4)
35 | arel (~> 8.0)
36 | activesupport (5.1.4)
37 | concurrent-ruby (~> 1.0, >= 1.0.2)
38 | i18n (~> 0.7)
39 | minitest (~> 5.1)
40 | tzinfo (~> 1.1)
41 | arel (8.0.0)
42 | bindex (0.5.0)
43 | builder (3.2.3)
44 | byebug (9.1.0)
45 | concurrent-ruby (1.0.5)
46 | crass (1.0.3)
47 | erubi (1.7.0)
48 | erubis (2.7.0)
49 | ffi (1.9.18)
50 | globalid (0.4.1)
51 | activesupport (>= 4.2.0)
52 | haml (4.0.7)
53 | tilt
54 | haml-rails (0.9.0)
55 | actionpack (>= 4.0.1)
56 | activesupport (>= 4.0.1)
57 | haml (>= 4.0.6, < 5.0)
58 | html2haml (>= 1.0.1)
59 | railties (>= 4.0.1)
60 | html2haml (2.1.0)
61 | erubis (~> 2.7.0)
62 | haml (~> 4.0)
63 | nokogiri (>= 1.6.0)
64 | ruby_parser (~> 3.5)
65 | i18n (0.9.1)
66 | concurrent-ruby (~> 1.0)
67 | jbuilder (2.7.0)
68 | activesupport (>= 4.2.0)
69 | multi_json (>= 1.2)
70 | listen (3.1.5)
71 | rb-fsevent (~> 0.9, >= 0.9.4)
72 | rb-inotify (~> 0.9, >= 0.9.7)
73 | ruby_dep (~> 1.2)
74 | loofah (2.1.1)
75 | crass (~> 1.0.2)
76 | nokogiri (>= 1.5.9)
77 | mail (2.7.0)
78 | mini_mime (>= 0.1.1)
79 | method_source (0.9.0)
80 | mini_mime (1.0.0)
81 | mini_portile2 (2.3.0)
82 | minitest (5.10.3)
83 | multi_json (1.12.2)
84 | nio4r (2.1.0)
85 | nokogiri (1.8.1)
86 | mini_portile2 (~> 2.3.0)
87 | puma (3.11.0)
88 | rack (2.0.3)
89 | rack-proxy (0.6.3)
90 | rack
91 | rack-test (0.8.2)
92 | rack (>= 1.0, < 3)
93 | rails (5.1.4)
94 | actioncable (= 5.1.4)
95 | actionmailer (= 5.1.4)
96 | actionpack (= 5.1.4)
97 | actionview (= 5.1.4)
98 | activejob (= 5.1.4)
99 | activemodel (= 5.1.4)
100 | activerecord (= 5.1.4)
101 | activesupport (= 5.1.4)
102 | bundler (>= 1.3.0)
103 | railties (= 5.1.4)
104 | sprockets-rails (>= 2.0.0)
105 | rails-dom-testing (2.0.3)
106 | activesupport (>= 4.2.0)
107 | nokogiri (>= 1.6)
108 | rails-html-sanitizer (1.0.3)
109 | loofah (~> 2.0)
110 | railties (5.1.4)
111 | actionpack (= 5.1.4)
112 | activesupport (= 5.1.4)
113 | method_source
114 | rake (>= 0.8.7)
115 | thor (>= 0.18.1, < 2.0)
116 | rake (12.3.0)
117 | rb-fsevent (0.10.2)
118 | rb-inotify (0.9.10)
119 | ffi (>= 0.5.0, < 2)
120 | ruby_dep (1.5.0)
121 | ruby_parser (3.8.4)
122 | sexp_processor (~> 4.1)
123 | sexp_processor (4.8.0)
124 | spring (2.0.2)
125 | activesupport (>= 4.2)
126 | spring-watcher-listen (2.0.1)
127 | listen (>= 2.7, < 4.0)
128 | spring (>= 1.2, < 3.0)
129 | sprockets (3.7.1)
130 | concurrent-ruby (~> 1.0)
131 | rack (> 1, < 3)
132 | sprockets-rails (3.2.1)
133 | actionpack (>= 4.0)
134 | activesupport (>= 4.0)
135 | sprockets (>= 3.0.0)
136 | thor (0.20.0)
137 | thread_safe (0.3.6)
138 | tilt (2.0.8)
139 | tzinfo (1.2.4)
140 | thread_safe (~> 0.1)
141 | web-console (3.5.1)
142 | actionview (>= 5.0)
143 | activemodel (>= 5.0)
144 | bindex (>= 0.4.0)
145 | railties (>= 5.0)
146 | webpacker (3.2.0)
147 | activesupport (>= 4.2)
148 | rack-proxy (>= 0.6.1)
149 | railties (>= 4.2)
150 | websocket-driver (0.6.5)
151 | websocket-extensions (>= 0.1.0)
152 | websocket-extensions (0.1.3)
153 |
154 | PLATFORMS
155 | ruby
156 |
157 | DEPENDENCIES
158 | byebug
159 | haml-rails
160 | jbuilder (~> 2.5)
161 | listen (>= 3.0.5, < 3.2)
162 | puma (~> 3.7)
163 | rails (~> 5.1.4)
164 | spring
165 | spring-watcher-listen (~> 2.0.0)
166 | web-console (>= 3.3.0)
167 | webpacker
168 |
169 | BUNDLED WITH
170 | 1.14.5
171 |
--------------------------------------------------------------------------------
/src/js/lib/helpers.js:
--------------------------------------------------------------------------------
1 | // Sufficient for Normas implementation of functions like from lodash
2 |
3 | export function isPlainObject(value) {
4 | if (value == null || typeof value !== 'object') {
5 | return false;
6 | }
7 | const proto = Object.getPrototypeOf(value);
8 | if (proto === null) {
9 | return true;
10 | }
11 | const constructor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
12 | return typeof constructor === 'function' && constructor instanceof constructor;
13 | }
14 |
15 | export const isArray = Array.isArray;
16 |
17 | export function isFunction(v) {
18 | return typeof v === 'function';
19 | }
20 |
21 | export function isString(v) {
22 | return typeof v === 'string';
23 | }
24 |
25 | export function compact(array) {
26 | return filter(array, v => v);
27 | }
28 |
29 | export function debounce(func, wait) {
30 | let timeoutId;
31 | return (...args) => {
32 | if (timeoutId) {
33 | clearTimeout(timeoutId);
34 | }
35 | timeoutId = setTimeout(() => {
36 | func(...args);
37 | }, wait);
38 | }
39 | }
40 |
41 | export function groupBy(array, key) {
42 | return reduceBy(array, key, (grouped, groupKey, item) => {
43 | if (grouped[groupKey]) {
44 | grouped[groupKey].push(item);
45 | } else {
46 | grouped[groupKey] = [item];
47 | }
48 | });
49 | }
50 |
51 | export function countBy(array, key) {
52 | return reduceBy(array, key, (grouped, groupKey) => {
53 | if (grouped[groupKey]) {
54 | grouped[groupKey]++;
55 | } else {
56 | grouped[groupKey] = 1;
57 | }
58 | });
59 | }
60 |
61 | export function groupByInArray(array, key) {
62 | return reduceBy(array, key, (grouped, groupKey, item) => {
63 | const group = find(grouped, ([k]) => k === groupKey);
64 | if (group) {
65 | group[1].push(item);
66 | } else {
67 | grouped.push([groupKey, [item]]);
68 | }
69 | }, []);
70 | }
71 |
72 | export function flatten(array, deep = false) {
73 | return array.reduce((flat, value) => {
74 | if (isArray(value)) {
75 | flat.push(...(deep ? flatten(value, true) : value));
76 | } else {
77 | flat.push(value);
78 | }
79 | return flat;
80 | }, []);
81 | }
82 |
83 | export function intersection(a, b) {
84 | if (!isArray(b)) {
85 | b = [b];
86 | }
87 | return (isArray(a) ? a : [a]).reduce((result, value) => {
88 | if (includes(b, value)) {
89 | result.push(value);
90 | }
91 | return result;
92 | }, []);
93 | }
94 |
95 | export function deepMerge(destination, source) {
96 | return Object.keys(source).reduce((result, key) => {
97 | if (source[key]) {
98 | if (isPlainObject(destination[key]) && isPlainObject(source[key])) {
99 | result[key] = deepMerge(destination[key], source[key]);
100 | } else {
101 | result[key] = source[key];
102 | }
103 | return result;
104 | }
105 | }, Object.assign({}, destination));
106 | }
107 |
108 | export function filter(collection, conditions) {
109 | return filterBase('filter', collection, conditions);
110 | }
111 |
112 | export function find(collection, conditions) {
113 | return filterBase('find', collection, conditions);
114 | }
115 |
116 | export function findIndex(collection, conditions) {
117 | return filterBase('find', collection, conditions);
118 | }
119 |
120 | export function map(collection, iteratee) {
121 | return Array.prototype.map.call(collection, iteratee);
122 | }
123 |
124 | export function each(collection, iteratee) {
125 | return Array.prototype.forEach.call(collection, iteratee);
126 | }
127 |
128 | export function mapValues(object, iteratee) {
129 | return Object.keys(object).reduce((result, key) => {
130 | result[key] = iteratee(object[key], key);
131 | return result;
132 | }, {});
133 | }
134 |
135 | export function without(collection, ...values) {
136 | return difference(collection, values);
137 | }
138 |
139 | export function difference(arrayA, arrayB) {
140 | return filter(arrayA, a => !includes(arrayB, a));
141 | }
142 |
143 | export function includes(collection, searchElement) {
144 | return Array.prototype.indexOf.call(collection, searchElement) !== -1;
145 | }
146 |
147 | // private
148 |
149 | function reduceBy(array, key, reducer, initial = {}) {
150 | return Array.prototype.reduce.call(array, (grouped, item) => {
151 | const groupKey = isFunction(key) ? key(item) : item[key];
152 | reducer(grouped, groupKey, item);
153 | return grouped;
154 | }, initial);
155 | }
156 |
157 | function filterBase(baseName, collection, conditions) {
158 | return Array.prototype[baseName].call(collection, makeConditionsMatch(conditions));
159 | }
160 |
161 | function makeConditionsMatch(conditions) {
162 | if (isFunction(conditions)) {
163 | return conditions;
164 | } else {
165 | const conditionsKeys = Object.keys(conditions);
166 | return item => filterMatch(item, conditions, conditionsKeys);
167 | }
168 | }
169 |
170 | function filterMatch(item, conditions, conditionsKeys) {
171 | return conditionsKeys.find(key => conditions[key] !== item[key]) === undefined;
172 | }
173 |
--------------------------------------------------------------------------------
/dist/js/extensions/views.js:
--------------------------------------------------------------------------------
1 | "use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1?n-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:{};this.viewClasses[e.selector]?this.error("🏭 View class for selector `"+e.selector+"` already registered",this.viewClasses[e.selector]):(this.viewClasses[e.selector]=e,this.listenToElement(e.selector,function(i){return t.bindView(i,e,n)},function(n){return t.unbindView(n,e)},{delay:e.delay,silent:!0}))}},{key:"bindView",value:function(e,t,n){if(!this.canBind(e,t))return null;t.instanceIndex?t.instanceIndex+=1:t.instanceIndex=1;var r=new t(i({},this.helpers.deepMerge(this.viewOptions,n),{instanceName:t.selector+"_"+t.instanceIndex,el:e[0]}));return this.viewInstances.push(r),r}},{key:"canBind",value:function(e,t){var n=this.getViewsOnElement(e,t)[0];return!n||(this.log("warn","🏭 Element already has bound view",e,t,n),!1)}},{key:"unbindView",value:function(e,t){var n=this.getViewsOnElement(e,t)[0];n&&(n.destructor(),this.viewInstances=this.helpers.without(this.viewInstances,n))}},{key:"getViewsOnElement",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n={el:e instanceof $?e[0]:e};return t&&(n.constructor=t),this.helpers.filter(this.viewInstances,n)}},{key:"getViewsInContainer",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return this.helpers.filter(this.viewInstances,function(n){return n.$el.closest(e).length>0&&(t||n.el!==e[0])})}},{key:"getAllViews",value:function(e){return this.helpers.filter(this.viewInstances,{constructor:e})}},{key:"getFirstView",value:function(e){return this.helpers.find(this.viewInstances,{constructor:e})}},{key:"getFirstChildView",value:function(e){return this.helpers.find(this.viewInstances,function(t){return t instanceof e})}}]),c}(),Object.defineProperty(l,"View",{enumerable:!0,writable:!0,value:o}),c};module.exports=function(e){return c(e,l(e.NormasCore))};
2 | //# sourceMappingURL=views.js.map
3 |
--------------------------------------------------------------------------------
/src/js/mixins/events.js:
--------------------------------------------------------------------------------
1 | export default Base => (class extends Base {
2 | constructor(options) {
3 | super(options);
4 | if (NORMAS_DEBUG) {
5 | this.constructor.readOptions(this.logging, options.logging, {
6 | events: true,
7 | eventsDebounced: true,
8 | eventsTable: false,
9 | });
10 | if (this.debugMode && this.logging.events && this.logging.eventsDebounced) {
11 | this.eventsLogBuffer = [];
12 | this.logEventsDebounced = this.helpers.debounce(this.logEventsDebounced.bind(this), 20);
13 | }
14 | this.log('info', ['construct', 'events'],
15 | `🚦 "${this.instanceName}" events mixin activated.`,
16 | 'logging.events =', this.logging.events,
17 | 'logging.eventsDebounced =', this.logging.eventsDebounced,
18 | 'logging.eventsTable =', this.logging.eventsTable);
19 | }
20 | }
21 |
22 | trigger(eventName, ...args) {
23 | this.$el.trigger(eventName, args);
24 | }
25 |
26 | listenEvents(...args) {
27 | return this.listenEventsOnElement(this.$el, ...args);
28 | }
29 |
30 | listenEventsOnElement($element, ...args) {
31 | const listeningArgs = this.constructor.listeningArguments(...args);
32 | if (NORMAS_DEBUG && this.debugMode && this.logging.events) {
33 | this.logEvents($element, listeningArgs);
34 | }
35 | listeningArgs.forEach(({ events, selector, handle }) => {
36 | $element.on(events, selector, handle);
37 | });
38 | return listeningArgs;
39 | }
40 |
41 | forgetEvents(listeningArgs) {
42 | this.forgetEventsOnElement(this.$el, listeningArgs);
43 | }
44 |
45 | forgetEventsOnElement($element, listeningArgs) {
46 | if (NORMAS_DEBUG) {
47 | this.logEventsOutput($element, listeningArgs, false);
48 | }
49 | listeningArgs.forEach(({ events, selector, handle }) => {
50 | $element.off(events, selector, handle);
51 | });
52 | }
53 |
54 | // private
55 |
56 | logEvents($element, listeningArgs) {
57 | if (!NORMAS_DEBUG) {
58 | return;
59 | }
60 | if (this.logging.eventsDebounced) {
61 | const element = $element[0];
62 | listeningArgs.forEach(args => { args.element = element; });
63 | this.eventsLogBuffer = this.eventsLogBuffer.concat(listeningArgs);
64 | this.logEventsDebounced();
65 | } else {
66 | this.logEventsOutput($element, listeningArgs, true);
67 | }
68 | }
69 |
70 | logEventsDebounced() {
71 | if (!NORMAS_DEBUG) {
72 | return;
73 | }
74 | const grouped = this.helpers.groupByInArray(this.eventsLogBuffer, 'element');
75 | grouped.forEach(([element, listeningArgs]) => {
76 | this.logEventsOutput($(element), listeningArgs, true);
77 | });
78 | this.eventsLogBuffer = [];
79 | }
80 |
81 | logEventsOutput($element, listeningArgs, enter) {
82 | if (!NORMAS_DEBUG || !this.logging.events) {
83 | return;
84 | };
85 | const elementName = $element[0] === this.el ? this.instanceName : this.constructor.contentName($element);
86 | const count = listeningArgs.length;
87 | const plurEvents = this.constructor.logPlur('event%S%', count);
88 | const [styledCount, ...countStyles] = count > 1 ? this.constructor.logBold(count) : [];
89 | const [cycleName, ...cycleStyles] = this.constructor.logCycle(enter ? 'listen on' : 'forget from', enter);
90 | const [styledElementName, ...elementStyles] = this.constructor.logBold(elementName);
91 | this.log('events',
92 | `🚦 ${styledCount ? styledCount + ' ' : ''}${plurEvents} ${cycleName} "${styledElementName}"`,
93 | ...countStyles, ...cycleStyles, ...elementStyles,
94 | $element, ...(this.logging.eventsTable ? [] : [listeningArgs]));
95 | if (!this.logging.eventsTable) return;
96 | this.log('table', listeningArgs.map(({ selector, events }) => ({ selector, events })));
97 | }
98 |
99 | static listeningArguments(selector, eventRule, handle) {
100 | if (this.helpers.isPlainObject(selector)) {
101 | eventRule = selector;
102 | selector = '';
103 | }
104 |
105 | if (this.helpers.isFunction(eventRule)) {
106 | handle = eventRule;
107 | eventRule = selector;
108 | selector = '';
109 | }
110 |
111 | if (this.helpers.isPlainObject(eventRule)) {
112 | return this.helpers.flatten(Object.keys(eventRule).map((key) => {
113 | let value = eventRule[key];
114 | return this.helpers.isPlainObject(value) ?
115 | this.listeningArguments(selector ? `${selector} ${key}` : key, value)
116 | :
117 | this.listeningArguments(selector, key, value);
118 | }));
119 | }
120 |
121 | if (!this.helpers.isFunction(handle)) {
122 | if (NORMAS_DEBUG) {
123 | console.error(`handle isn't function in listening declaration! (selector: '${selector}')`); // eslint-disable-line no-console
124 | }
125 | return [];
126 | }
127 | if (!eventRule) {
128 | if (NORMAS_DEBUG) {
129 | console.error(`eventRule not defined! (selector: '${selector}')`); // eslint-disable-line no-console
130 | }
131 | return [];
132 | }
133 |
134 | const selectors = eventRule.split(/\s+/);
135 | const eventName = selectors[0];
136 | selectors[0] = selector;
137 |
138 | if (!eventName) {
139 | if (NORMAS_DEBUG) {
140 | console.error(`bad eventName in listening declaration! (selector: '${selector}')`); // eslint-disable-line no-console
141 | }
142 | return [];
143 | }
144 |
145 | return [{
146 | events: eventName.replace(/\//g, ' '),
147 | selector: selectors.join(' ').trim(),
148 | handle: (event, ...args) => handle($(event.currentTarget), event, ...args),
149 | }];
150 | }
151 | });
152 |
--------------------------------------------------------------------------------
/src/js/mixins/elements.js:
--------------------------------------------------------------------------------
1 | // require events mixin
2 | // require content mixin
3 | export default Base => (class extends Base {
4 | static preventContentEventsClassName = 'js-prevent-normas';
5 | static elementEnterTimeoutIdDataName = 'elementEnterTimeoutId';
6 |
7 | constructor(options) {
8 | super(options);
9 | if (NORMAS_DEBUG) {
10 | this.constructor.readOptions(this.logging, options.logging, { elements: true });
11 | this.log('info', 'construct',
12 | `💎 "${this.instanceName}" elements mixin activated.`,
13 | 'logging.elements =', this.logging.elements);
14 | }
15 | }
16 |
17 | listenToElement(selector, enter, leave = null, options = {}) {
18 | options = Object.assign({ delay: 0, silent: false }, options);
19 | this.listenToContent(
20 | this.makeElementContentEnter(selector, enter, options),
21 | this.makeElementContentLeave(selector, leave, options),
22 | );
23 | }
24 |
25 | // private
26 |
27 | makeElementContentEnter(selector, enter, { delay, silent }) {
28 | return $content => {
29 | let $elements = this.constructor.contentElements($content, selector);
30 | if ($elements.length === 0) {
31 | return;
32 | }
33 | if (delay > 0) {
34 | this.dom.memoryData($elements, this.constructor.elementEnterTimeoutIdDataName, setTimeout(() => {
35 | $content = $content.filter((index, element) => this.dom.contains(this.el, element));
36 | if ($content.length === 0) {
37 | return;
38 | }
39 | // check elements inclusion in $content after delay
40 | $elements = this.constructor.contentElements($content, $elements);
41 | if ($elements.length === 0) {
42 | return;
43 | }
44 | // check elements with delay data
45 | $elements = this.filterDelayedElements($elements, true);
46 | if ($elements.length === 0) {
47 | return;
48 | }
49 | this.handleElements($elements, selector, enter, 'enter', silent);
50 | }, delay));
51 | } else {
52 | this.handleElements($elements, selector, enter, 'enter', silent);
53 | }
54 | };
55 | }
56 |
57 | makeElementContentLeave(selector, leave, { delay, silent }) {
58 | if (!leave) {
59 | return null;
60 | }
61 | return $content => {
62 | let $elements = this.constructor.contentElements($content, selector);
63 | if ($elements.length === 0) {
64 | return;
65 | }
66 | if (delay > 0) {
67 | $elements = this.filterDelayedElements($elements, false);
68 | if ($elements.length === 0) {
69 | return;
70 | }
71 | }
72 | this.handleElements($elements, selector, leave, 'leave', silent);
73 | };
74 | }
75 |
76 | filterDelayedElements($elements, delayState) {
77 | return $elements.filter((index, element) => {
78 | const delayed = this.dom.memoryData(element, this.constructor.elementEnterTimeoutIdDataName);
79 | if (delayed) {
80 | this.dom.removeMemoryData(element, this.constructor.elementEnterTimeoutIdDataName);
81 | }
82 | return !!delayed === delayState;
83 | });
84 | }
85 |
86 | handleElements($elements, selector, handle, handleName, silent) {
87 | let preventedElements = [];
88 | const handledElements = this.helpers.filter($elements, element => {
89 | if (!this.canCycleElement(element, selector, handleName)) {
90 | return false;
91 | }
92 | const prevent = this.constructor.preventEventForElement(element);
93 | if (prevent) {
94 | preventedElements.push(element);
95 | return false;
96 | }
97 | handle($(element));
98 | return true;
99 | });
100 | if (NORMAS_DEBUG && !silent) {
101 | this.logElements(handledElements, preventedElements, selector, handleName);
102 | }
103 | }
104 |
105 | canCycleElement(element, selector, handleName) {
106 | const normasElements = this.dom.memoryData(element, '_normasElements');
107 | const selectorIndex = normasElements ? normasElements.indexOf(selector) : -1;
108 | if (handleName === 'enter') {
109 | if (selectorIndex !== -1) {
110 | if (NORMAS_DEBUG) {
111 | this.log('warn', 'elements',
112 | ...this.constructor.logBold('💎 element "%REPLACE%" already entered.', selector),
113 | element);
114 | }
115 | return false;
116 | }
117 | if (normasElements) {
118 | normasElements.push(selector);
119 | } else {
120 | this.dom.memoryData(element, '_normasElements', [selector]);
121 | }
122 | return true;
123 | }
124 | // leave
125 | if (selectorIndex !== -1) {
126 | normasElements.splice(selectorIndex, 1);
127 | return true;
128 | }
129 | if (NORMAS_DEBUG) {
130 | this.log('warn', 'elements',
131 | ...this.constructor.logBold('💎 element "%REPLACE%" try leave, but did not enter.', selector),
132 | element);
133 | }
134 | return false;
135 | }
136 |
137 | logElements(handledElements, preventedElements, selector, handleName) {
138 | if (!NORMAS_DEBUG) {
139 | return;
140 | }
141 | const [elementName, ...elementStyles] = this.constructor.logBold(selector);
142 | if (handledElements.length > 0) {
143 | const [styledHandleName, ...handleStyles] = this.constructor.logCycle(handleName, handleName === 'enter', 3);
144 | this._logElements(handledElements, '', styledHandleName, elementName, handleStyles, elementStyles);
145 | }
146 | if (preventedElements.length > 0) {
147 | const [preventInfo, ...handleStyles] = this.constructor.logColor('prevent ', 'blue');
148 | this._logElements(preventedElements, preventInfo, handleName, elementName, handleStyles, elementStyles);
149 | }
150 | }
151 |
152 | _logElements(elements, preventInfo, handleName, elementName, handleStyles, elementStyles) {
153 | if (!NORMAS_DEBUG) {
154 | return;
155 | }
156 | const count = elements.length;
157 | const plurElements = this.constructor.logPlur('element%S%', count);
158 | const [styledCount, ...countStyles] = count > 1 ? this.constructor.logBold(count) : [];
159 | const styles = [handleStyles];
160 | styles[preventInfo ? 'push' : 'unshift'](countStyles);
161 | this.log('elements',
162 | `💎 ${preventInfo}${styledCount ? styledCount + ' ' : ''}${plurElements} ${handleName} "${elementName}"`,
163 | ...this.helpers.flatten(styles), ...elementStyles,
164 | elements);
165 | }
166 |
167 | static contentElements($content, selector) {
168 | return $content.filter(selector).add($content.find(selector));
169 | }
170 |
171 | static preventEventForElement(element) {
172 | return $(element).closest(`.${this.preventContentEventsClassName}`).length > 0;
173 | }
174 | });
175 |
--------------------------------------------------------------------------------
/src/js/mixins/turbolinks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Turbolinks integration for Normas
3 | *
4 | * @see {@link https://github.com/evrone/normas#turbolinks-integration|Docs}
5 | * @see {@link https://github.com/evrone/normas/blob/master/src/js/mixins/turbolinks.js|Source}
6 | * @license MIT
7 | * @copyright Dmitry Karpunin , 2017-2018
8 | */
9 |
10 | // require navigation mixin
11 | export default Base => (class extends Base {
12 | static turboPageEnterEventName = 'turbolinks:load';
13 | static turboPageLeaveEventName = 'turbolinks:before-cache';
14 |
15 | bindPageEvents(options) {
16 | this.Turbolinks = options.Turbolinks || global.Turbolinks;
17 | const turbolinksExists = !!this.Turbolinks;
18 | if (!this.enablings) this.enablings = {};
19 | this.constructor.readOptions(this.enablings, options.enablings, { turbolinks: turbolinksExists });
20 | if (NORMAS_DEBUG && options.enablings && options.enablings.turbolinks === true && !turbolinksExists) {
21 | this.error('🛤 Turbolinks: `options.enablings.turbolinks === true` but Turbolinks is not detected.',
22 | this.constructor.readmeLink('turbolinks-integration'));
23 | }
24 | if (NORMAS_DEBUG) {
25 | this.log('info', 'construct',
26 | ...this.constructor.logColor(`🛤 "${this.instanceName}" Turbolinks %REPLACE%.`,
27 | this.enablings.turbolinks ? 'enabled' : 'disabled',
28 | this.enablings.turbolinks ? 'green' : 'blue'));
29 | }
30 | if (this.enablings.turbolinks) {
31 | // Turbolinks connected :)
32 | // patchTurbolinks(this.Turbolinks); // TODO: check versions
33 | patchTurbolinksPreviewControl(this.Turbolinks);
34 | this.listenEvents(this.constructor.turboPageEnterEventName, this.pageEnter.bind(this));
35 | this.listenEvents(this.constructor.turboPageLeaveEventName, this.pageLeave.bind(this));
36 | if (options.Turbolinks) {
37 | options.Turbolinks.start();
38 | }
39 | } else {
40 | // No Turbolinks ;(
41 | if (NORMAS_DEBUG && options.enablings && options.enablings.turbolinks === false) {
42 | this.log('warn', 'construct',
43 | `🛤 You ${this.Turbolinks ? '' : 'do not '}have a Turbolinks and use integration, but \`options.enablings.turbolinks === false\`.`);
44 | }
45 | $(this.pageEnter.bind(this));
46 | }
47 | }
48 |
49 | visit(location) {
50 | if (this.enablings.turbolinks) {
51 | this.Turbolinks.visit(location);
52 | } else {
53 | super.visit(location);
54 | }
55 | }
56 |
57 | setHash(hash) {
58 | if (this.enablings.turbolinks) {
59 | let controller = this.Turbolinks.controller;
60 | controller.replaceHistoryWithLocationAndRestorationIdentifier(hash, controller.restorationIdentifier);
61 | } else {
62 | super.setHash(hash);
63 | }
64 | }
65 |
66 | replaceLocation(url) {
67 | if (this.enablings.turbolinks) {
68 | this.Turbolinks.controller.replaceHistoryWithLocationAndRestorationIdentifier(url);
69 | } else {
70 | super.replaceLocation(url);
71 | }
72 | }
73 |
74 | pushLocation(url) {
75 | if (this.enablings.turbolinks) {
76 | this.Turbolinks.controller.pushHistoryWithLocationAndRestorationIdentifier(url);
77 | } else {
78 | super.pushLocation(url);
79 | }
80 | }
81 |
82 | sayAboutPageLoading(state) {
83 | if (this.enablings.turbolinks) {
84 | const progressBar = this.Turbolinks.controller.adapter.progressBar;
85 | if (state) {
86 | progressBar.setValue(0);
87 | progressBar.show();
88 | } else {
89 | progressBar.hide();
90 | }
91 | } else {
92 | super.sayAboutPageLoading(state);
93 | }
94 | }
95 | });
96 |
97 | function patchTurbolinksPreviewControl(Turbolinks) {
98 | class OurView extends Turbolinks.View {
99 | render({ snapshot, error, isPreview }, callback) {
100 | this.markAsPreview(isPreview);
101 | if (snapshot) {
102 | // added `isPreview` argument
103 | this.renderSnapshot(snapshot, isPreview, callback);
104 | } else {
105 | this.renderError(error, callback);
106 | }
107 | }
108 |
109 | // added `isPreview` argument
110 | renderSnapshot(snapshot, isPreview, callback) {
111 | const renderer = new OurSnapshotRenderer(this.getSnapshot(), Turbolinks.Snapshot.wrap(snapshot), isPreview);
112 | renderer.delegate = this.delegate;
113 | renderer.render(callback);
114 | }
115 | }
116 | Turbolinks.View = OurView;
117 |
118 | class OurSnapshotRenderer extends Turbolinks.SnapshotRenderer {
119 | // added `isPreview` argument
120 | constructor(currentSnapshot, newSnapshot, isPreview) {
121 | super(currentSnapshot, newSnapshot);
122 | if (isPreview) {
123 | this.newBody = this.newBody.cloneNode(true);
124 | this.newBody.isPreview = true;
125 | }
126 | }
127 | }
128 | }
129 |
130 | function patchTurbolinks(Turbolinks) {
131 | class OurHeadDetails extends Turbolinks.HeadDetails {
132 | constructor(...args) {
133 | super(...args);
134 | this.elements = {};
135 | $(this.element).children().each((index, element) => {
136 | let key = turboElementToKey(element);
137 | if (!this.elements[key]) {
138 | this.elements[key] = {
139 | type: turboElementType(element),
140 | tracked: turboElementIsTracked(element),
141 | elements: [],
142 | };
143 | }
144 | this.elements[key].elements.push(element);
145 | });
146 | }
147 | // getTrackedElementSignature() {
148 | // let sign = super.getTrackedElementSignature();
149 | // console.log('sign ', sign);
150 | // return sign;
151 | // }
152 | }
153 | Turbolinks.HeadDetails = OurHeadDetails;
154 | }
155 |
156 | // Injection in Turbolinks.HeadDetails for override this logic:
157 | function turboElementToKey(element) {
158 | let url = element.getAttribute('src') || element.getAttribute('href');
159 | if (url) {
160 | let cuts = url.split('/');
161 | cuts = cuts[cuts.length - 1];
162 | if (cuts) { url = cuts }
163 | }
164 | return url || element.outerHTML;
165 | }
166 |
167 | function turboElementType(element) {
168 | if (turboElementIsScript(element)) {
169 | return 'script';
170 | } else if (turboElementIsStylesheet(element)) {
171 | return 'stylesheet';
172 | }
173 | return null;
174 | }
175 |
176 | function turboElementIsTracked(element) {
177 | return element.getAttribute('data-turbolinks-track') === 'reload';
178 | }
179 |
180 | function turboElementIsScript(element) {
181 | let tagName = element.tagName.toLowerCase();
182 | return tagName === 'script';
183 | }
184 |
185 | function turboElementIsStylesheet(element) {
186 | let tagName = element.tagName.toLowerCase();
187 | return tagName === 'style' || (tagName === 'link' && element.getAttribute('rel') === 'stylesheet');
188 | }
189 |
--------------------------------------------------------------------------------
/dist/js/integrations/turbolinks.production.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"turbolinks.production.js","sources":["../../../src/js/mixins/turbolinks.js"],"sourcesContent":["/**\n * Turbolinks integration for Normas\n *\n * @see {@link https://github.com/evrone/normas#turbolinks-integration|Docs}\n * @see {@link https://github.com/evrone/normas/blob/master/src/js/mixins/turbolinks.js|Source}\n * @license MIT\n * @copyright Dmitry Karpunin , 2017-2018\n */\n\n// require navigation mixin\nexport default Base => (class extends Base {\n static turboPageEnterEventName = 'turbolinks:load';\n static turboPageLeaveEventName = 'turbolinks:before-cache';\n\n bindPageEvents(options) {\n this.Turbolinks = options.Turbolinks || global.Turbolinks;\n const turbolinksExists = !!this.Turbolinks;\n if (!this.enablings) this.enablings = {};\n this.constructor.readOptions(this.enablings, options.enablings, { turbolinks: turbolinksExists });\n if (NORMAS_DEBUG && options.enablings && options.enablings.turbolinks === true && !turbolinksExists) {\n this.error('🛤 Turbolinks: `options.enablings.turbolinks === true` but Turbolinks is not detected.',\n this.constructor.readmeLink('turbolinks-integration'));\n }\n if (NORMAS_DEBUG) {\n this.log('info', 'construct',\n ...this.constructor.logColor(`🛤 \"${this.instanceName}\" Turbolinks %REPLACE%.`,\n this.enablings.turbolinks ? 'enabled' : 'disabled',\n this.enablings.turbolinks ? 'green' : 'blue'));\n }\n if (this.enablings.turbolinks) {\n // Turbolinks connected :)\n // patchTurbolinks(this.Turbolinks); // TODO: check versions\n patchTurbolinksPreviewControl(this.Turbolinks);\n this.listenEvents(this.constructor.turboPageEnterEventName, this.pageEnter.bind(this));\n this.listenEvents(this.constructor.turboPageLeaveEventName, this.pageLeave.bind(this));\n if (options.Turbolinks) {\n options.Turbolinks.start();\n }\n } else {\n // No Turbolinks ;(\n if (NORMAS_DEBUG && options.enablings && options.enablings.turbolinks === false) {\n this.log('warn', 'construct',\n `🛤 You ${this.Turbolinks ? '' : 'do not '}have a Turbolinks and use integration, but \\`options.enablings.turbolinks === false\\`.`);\n }\n $(this.pageEnter.bind(this));\n }\n }\n\n visit(location) {\n if (this.enablings.turbolinks) {\n this.Turbolinks.visit(location);\n } else {\n super.visit(location);\n }\n }\n\n setHash(hash) {\n if (this.enablings.turbolinks) {\n let controller = this.Turbolinks.controller;\n controller.replaceHistoryWithLocationAndRestorationIdentifier(hash, controller.restorationIdentifier);\n } else {\n super.setHash(hash);\n }\n }\n\n replaceLocation(url) {\n if (this.enablings.turbolinks) {\n this.Turbolinks.controller.replaceHistoryWithLocationAndRestorationIdentifier(url);\n } else {\n super.replaceLocation(url);\n }\n }\n\n pushLocation(url) {\n if (this.enablings.turbolinks) {\n this.Turbolinks.controller.pushHistoryWithLocationAndRestorationIdentifier(url);\n } else {\n super.pushLocation(url);\n }\n }\n\n sayAboutPageLoading(state) {\n if (this.enablings.turbolinks) {\n const progressBar = this.Turbolinks.controller.adapter.progressBar;\n if (state) {\n progressBar.setValue(0);\n progressBar.show();\n } else {\n progressBar.hide();\n }\n } else {\n super.sayAboutPageLoading(state);\n }\n }\n});\n\nfunction patchTurbolinksPreviewControl(Turbolinks) {\n class OurView extends Turbolinks.View {\n render({ snapshot, error, isPreview }, callback) {\n this.markAsPreview(isPreview);\n if (snapshot) {\n // added `isPreview` argument\n this.renderSnapshot(snapshot, isPreview, callback);\n } else {\n this.renderError(error, callback);\n }\n }\n\n // added `isPreview` argument\n renderSnapshot(snapshot, isPreview, callback) {\n const renderer = new OurSnapshotRenderer(this.getSnapshot(), Turbolinks.Snapshot.wrap(snapshot), isPreview);\n renderer.delegate = this.delegate;\n renderer.render(callback);\n }\n }\n Turbolinks.View = OurView;\n\n class OurSnapshotRenderer extends Turbolinks.SnapshotRenderer {\n // added `isPreview` argument\n constructor(currentSnapshot, newSnapshot, isPreview) {\n super(currentSnapshot, newSnapshot);\n if (isPreview) {\n this.newBody = this.newBody.cloneNode(true);\n this.newBody.isPreview = true;\n }\n }\n }\n}\n\nfunction patchTurbolinks(Turbolinks) {\n class OurHeadDetails extends Turbolinks.HeadDetails {\n constructor(...args) {\n super(...args);\n this.elements = {};\n $(this.element).children().each((index, element) => {\n let key = turboElementToKey(element);\n if (!this.elements[key]) {\n this.elements[key] = {\n type: turboElementType(element),\n tracked: turboElementIsTracked(element),\n elements: [],\n };\n }\n this.elements[key].elements.push(element);\n });\n }\n // getTrackedElementSignature() {\n // let sign = super.getTrackedElementSignature();\n // console.log('sign ', sign);\n // return sign;\n // }\n }\n Turbolinks.HeadDetails = OurHeadDetails;\n}\n\n// Injection in Turbolinks.HeadDetails for override this logic:\nfunction turboElementToKey(element) {\n let url = element.getAttribute('src') || element.getAttribute('href');\n if (url) {\n let cuts = url.split('/');\n cuts = cuts[cuts.length - 1];\n if (cuts) { url = cuts }\n }\n return url || element.outerHTML;\n}\n\nfunction turboElementType(element) {\n if (turboElementIsScript(element)) {\n return 'script';\n } else if (turboElementIsStylesheet(element)) {\n return 'stylesheet';\n }\n return null;\n}\n\nfunction turboElementIsTracked(element) {\n return element.getAttribute('data-turbolinks-track') === 'reload';\n}\n\nfunction turboElementIsScript(element) {\n let tagName = element.tagName.toLowerCase();\n return tagName === 'script';\n}\n\nfunction turboElementIsStylesheet(element) {\n let tagName = element.tagName.toLowerCase();\n return tagName === 'style' || (tagName === 'link' && element.getAttribute('rel') === 'stylesheet');\n}\n"],"names":["Base","options","Turbolinks","global","turbolinksExists","this","enablings","constructor","readOptions","turbolinks","OurView","callback","snapshot","error","isPreview","markAsPreview","renderSnapshot","renderError","renderer","OurSnapshotRenderer","getSnapshot","Snapshot","wrap","delegate","render","View","currentSnapshot","newSnapshot","newBody","_this3","cloneNode","SnapshotRenderer","listenEvents","turboPageEnterEventName","pageEnter","bind","turboPageLeaveEventName","pageLeave","start","location","visit","hash","controller","replaceHistoryWithLocationAndRestorationIdentifier","restorationIdentifier","url","pushHistoryWithLocationAndRestorationIdentifier","state","progressBar","adapter","setValue","show","hide"],"mappings":"0uCAUsCA,6CAIrBC,QACRC,WAAaD,EAAQC,YAAcC,OAAOD,eACzCE,IAAqBC,KAAKH,WAC3BG,KAAKC,YAAWD,KAAKC,mBACrBC,YAAYC,YAAYH,KAAKC,UAAWL,EAAQK,WAAaG,WAAYL,IAW1EC,KAAKC,UAAUG,YAmEvB,SAAuCP,OAC/BQ,oKACmCC,OAA9BC,IAAAA,SAAUC,IAAAA,MAAOC,IAAAA,eACnBC,cAAcD,GACfF,OAEGI,eAAeJ,EAAUE,EAAWH,QAEpCM,YAAYJ,EAAOF,0CAKbC,EAAUE,EAAWH,OAC5BO,EAAW,IAAIC,EAAoBd,KAAKe,cAAelB,EAAWmB,SAASC,KAAKV,GAAWE,KACxFS,SAAWlB,KAAKkB,WAChBC,OAAOb,UAfET,EAAWuB,QAkBtBA,KAAOf,MAEZS,yBAEQO,EAAiBC,EAAab,4EAClCY,EAAiBC,WACnBb,MACGc,QAAUC,EAAKD,QAAQE,WAAU,KACjCF,QAAQd,WAAY,sBANGZ,EAAW6B,mBArFX1B,KAAKH,iBAC9B8B,aAAa3B,KAAKE,YAAY0B,wBAAyB5B,KAAK6B,UAAUC,KAAK9B,YAC3E2B,aAAa3B,KAAKE,YAAY6B,wBAAyB/B,KAAKgC,UAAUF,KAAK9B,OAC5EJ,EAAQC,cACFA,WAAWoC,WAQnBjC,KAAK6B,UAAUC,KAAK9B,qCAIpBkC,GACAlC,KAAKC,UAAUG,gBACZP,WAAWsC,MAAMD,uFAEVA,mCAIRE,MACFpC,KAAKC,UAAUG,WAAY,KACzBiC,EAAarC,KAAKH,WAAWwC,aACtBC,mDAAmDF,EAAMC,EAAWE,kHAEjEH,2CAIFI,GACVxC,KAAKC,UAAUG,gBACZP,WAAWwC,WAAWC,mDAAmDE,iGAExDA,wCAIbA,GACPxC,KAAKC,UAAUG,gBACZP,WAAWwC,WAAWI,gDAAgDD,8FAExDA,+CAIHE,MACd1C,KAAKC,UAAUG,WAAY,KACvBuC,EAAc3C,KAAKH,WAAWwC,WAAWO,QAAQD,YACnDD,KACUG,SAAS,KACTC,UAEAC,8GAGYL,+FAhFG,uGACA"}
--------------------------------------------------------------------------------
/dist/js/extensions/views.production.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"views.production.js","sources":["../../../src/js/mixins/view.js","../../../src/js/mixins/views.js"],"sourcesContent":["export default Base => (class extends Base {\n // Override it with your own initialization logic (like componentDidUnmount in react).\n initialize(options) {\n }\n\n // Override it with your own unmount logic (like componentWillUnmount in react).\n terminate() {\n }\n\n // protected\n\n constructor(options) {\n Object.assign(options, Base.dom.data(options.el));\n super(options);\n this.reflectOptions(options);\n this.initializeEvents(options);\n this.initialize(options);\n if (NORMAS_DEBUG && this.logging.constructGrouping) {\n this.log('groupEnd', 'construct');\n }\n }\n\n destructor() {\n if (NORMAS_DEBUG) {\n const [destructText, ...destructStyles] = this.constructor.logColor('destructing', 'red');\n this.log(this.constructor.groupingMethod(this.logging.constructGrouping), 'construct',\n ...this.constructor.logBold(`${this.logging.constructPrefix} \"%REPLACE%\" ${destructText}`, this.instanceName),\n ...destructStyles,\n this);\n }\n this.terminate();\n if (this.listenedEvents) {\n this.forgetEvents(this.listenedEvents);\n this.listenedEvents = null;\n }\n if (NORMAS_DEBUG && this.logging.constructGrouping) {\n this.log('groupEnd', 'construct');\n }\n }\n\n reflectOptions(options) {\n if (!this.constructor.reflectOptions) {\n return;\n }\n Object.keys(options).forEach(attr => {\n if (this.constructor.reflectOptions.includes(attr)) {\n this[attr] = options[attr];\n }\n });\n }\n\n initializeEvents(_options) {\n const { events } = this.constructor;\n if (events) {\n if (!this.linkedEvents) {\n this.linkedEvents = this.linkEvents(this.helpers.isFunction(events) ? events() : events);\n }\n this.listenedEvents = this.listenEvents(this.linkedEvents);\n }\n }\n\n linkEvents(events) {\n return this.helpers.mapValues(events, handle => this.helpers.isString(handle) ?\n this[handle].bind(this)\n :\n (typeof this.helpers.isPlainObject(handle) ? this.linkEvents(handle) : handle)\n );\n }\n\n data(key, ...value) {\n this.dom.data(this.el, key, ...value);\n }\n});\n","/**\n * Views system for Normas\n *\n * @see {@link https://github.com/evrone/normas#-views|Docs}\n * @see {@link https://github.com/evrone/normas/blob/master/src/js/mixins/views.js|Source}\n * @license MIT\n * @copyright Dmitry Karpunin , 2017-2018\n */\n\n// TODO: may be rename Views, views, View, view\nimport normasView from './view';\n\nexport default Base => normasViews(Base, normasView(Base.NormasCore));\n\n// require content mixin\n// require events mixin\nconst normasViews = (Base, View) => (class extends Base {\n static View = View;\n View = View;\n viewClasses = {};\n viewInstances = [];\n\n constructor(options) {\n super(options);\n this.viewOptions = {\n debugMode: this.debugMode,\n ...options.viewOptions,\n };\n if (NORMAS_DEBUG) {\n this.viewOptions.logging = {\n ...this.logging,\n constructGrouping: 'groupCollapsed',\n constructPrefix: '🏭', // private\n eventsDebounced: false,\n ...(options.viewOptions && options.viewOptions.logging),\n };\n this.log('info', 'construct', `🏭 \"${this.instanceName}\" views mixin activated.`);\n }\n }\n\n registerView(viewClass, options = {}) {\n if (this.viewClasses[viewClass.selector]) {\n if (NORMAS_DEBUG) {\n this.error(`🏭 View class for selector \\`${viewClass.selector}\\` already registered`,\n this.viewClasses[viewClass.selector]);\n }\n return;\n }\n this.viewClasses[viewClass.selector] = viewClass;\n this.listenToElement(\n viewClass.selector,\n $el => this.bindView($el, viewClass, options),\n $el => this.unbindView($el, viewClass),\n {\n delay: viewClass.delay,\n silent: true,\n },\n );\n }\n\n bindView($el, viewClass, options) {\n if (!this.canBind($el, viewClass)) {\n return null;\n }\n if (viewClass.instanceIndex) {\n viewClass.instanceIndex += 1;\n } else {\n viewClass.instanceIndex = 1;\n }\n const view = new viewClass({\n ...this.helpers.deepMerge(this.viewOptions, options),\n instanceName: `${viewClass.selector}_${viewClass.instanceIndex}`,\n el: $el[0],\n });\n this.viewInstances.push(view);\n return view;\n }\n\n canBind($element, viewClass) {\n const view = this.getViewsOnElement($element, viewClass)[0];\n if (view) {\n if (NORMAS_DEBUG) {\n this.log('warn', '🏭 Element already has bound view', $element, viewClass, view);\n }\n return false;\n }\n return true;\n }\n\n unbindView($element, viewClass) {\n const view = this.getViewsOnElement($element, viewClass)[0];\n if (view) {\n view.destructor();\n this.viewInstances = this.helpers.without(this.viewInstances, view);\n }\n }\n\n getViewsOnElement($element, viewClass = null) {\n const el = $element instanceof $ ? $element[0] : $element;\n const filterOptions = { el };\n if (viewClass) {\n filterOptions.constructor = viewClass;\n }\n return this.helpers.filter(this.viewInstances, filterOptions);\n }\n\n getViewsInContainer($container, checkRoot = true) {\n return this.helpers.filter(this.viewInstances, view =>\n view.$el.closest($container).length > 0 && (checkRoot || view.el !== $container[0])\n );\n }\n\n getAllViews(viewClass) {\n return this.helpers.filter(this.viewInstances, { constructor: viewClass });\n }\n\n getFirstView(viewClass) {\n return this.helpers.find(this.viewInstances, { constructor: viewClass });\n }\n\n getFirstChildView(viewClass) {\n return this.helpers.find(this.viewInstances, view => view instanceof viewClass);\n }\n});\n"],"names":["options","assign","Base","dom","data","el","reflectOptions","initializeEvents","initialize","terminate","this","listenedEvents","forgetEvents","constructor","keys","forEach","_this2","includes","attr","_options","events","linkedEvents","linkEvents","helpers","isFunction","listenEvents","mapValues","_this3","isString","handle","bind","babelHelpers.typeof","isPlainObject","key","value","normasViews","View","viewOptions","_this","debugMode","viewClass","viewClasses","selector","listenToElement","bindView","$el","unbindView","delay","canBind","instanceIndex","view","deepMerge","viewInstances","push","$element","getViewsOnElement","destructor","without","filterOptions","$","filter","$container","checkRoot","closest","length","find","normasView","NormasCore"],"mappings":"iuCAWcA,oBACHC,OAAOD,EAASE,EAAKC,IAAIC,KAAKJ,EAAQK,oEACvCL,aACDM,eAAeN,KACfO,iBAAiBP,KACjBQ,WAAWR,gBAhBkBE,yCAEzBF,0FA4BJS,YACDC,KAAKC,sBACFC,aAAaF,KAAKC,qBAClBA,eAAiB,6CAOXX,cACRU,KAAKG,YAAYP,uBAGfQ,KAAKd,GAASe,QAAQ,YACvBC,EAAKH,YAAYP,eAAeW,SAASC,OACtCA,GAAQlB,EAAQkB,+CAKVC,OACPC,EAAWV,KAAKG,YAAhBO,OACJA,IACGV,KAAKW,oBACHA,aAAeX,KAAKY,WAAWZ,KAAKa,QAAQC,WAAWJ,GAAUA,IAAWA,SAE9ET,eAAiBD,KAAKe,aAAaf,KAAKW,kDAItCD,qBACFV,KAAKa,QAAQG,UAAUN,EAAQ,mBAAUO,EAAKJ,QAAQK,SAASC,GACpEF,EAAKE,GAAQC,QAEZC,EAAOJ,EAAKJ,QAAQS,cAAcH,IAAUF,EAAKL,WAAWO,GAAUA,iCAItEI,gCAAQC,2DACN/B,KAAIC,cAAKM,KAAKL,GAAI4B,UAAQC,cCtD7BC,EAAc,SAACjC,EAAMkC,6CAMbpC,4EACJA,2EALDoC,gKAMAC,yBACQC,EAAKC,WACbvC,EAAQqC,0BAVkCnC,2CAwBpCsC,cAAWxC,4DAClBU,KAAK+B,YAAYD,EAAUE,iBAO1BD,YAAYD,EAAUE,UAAYF,OAClCG,gBACHH,EAAUE,SACV,mBAAO1B,EAAK4B,SAASC,EAAKL,EAAWxC,IACrC,mBAAOgB,EAAK8B,WAAWD,EAAKL,WAEnBA,EAAUO,cACT,sCAKLF,EAAKL,EAAWxC,OAClBU,KAAKsC,QAAQH,EAAKL,UACd,KAELA,EAAUS,gBACFA,eAAiB,IAEjBA,cAAgB,MAEtBC,EAAO,IAAIV,OACZ9B,KAAKa,QAAQ4B,UAAUzC,KAAK2B,YAAarC,iBAC3BwC,EAAUE,aAAYF,EAAUS,iBAC7CJ,EAAI,kBAELO,cAAcC,KAAKH,GACjBA,kCAGDI,EAAUd,OACVU,EAAOxC,KAAK6C,kBAAkBD,EAAUd,GAAW,UACrDU,qCASKI,EAAUd,OACbU,EAAOxC,KAAK6C,kBAAkBD,EAAUd,GAAW,GACrDU,MACGM,kBACAJ,cAAgB1C,KAAKa,QAAQkC,QAAQ/C,KAAK0C,cAAeF,8CAIhDI,OAAUd,yDAAY,KAEhCkB,GAAkBrD,GADbiD,aAAoBK,EAAIL,EAAS,GAAKA,UAE7Cd,MACY3B,YAAc2B,GAEvB9B,KAAKa,QAAQqC,OAAOlD,KAAK0C,cAAeM,+CAG7BG,OAAYC,oEACvBpD,KAAKa,QAAQqC,OAAOlD,KAAK0C,cAAe,mBAC7CF,EAAKL,IAAIkB,QAAQF,GAAYG,OAAS,IAAMF,GAAaZ,EAAK7C,KAAOwD,EAAW,0CAIxErB,UACH9B,KAAKa,QAAQqC,OAAOlD,KAAK0C,eAAiBvC,YAAa2B,yCAGnDA,UACJ9B,KAAKa,QAAQ0C,KAAKvD,KAAK0C,eAAiBvC,YAAa2B,8CAG5CA,UACT9B,KAAKa,QAAQ0C,KAAKvD,KAAK0C,cAAe,mBAAQF,aAAgBV,6EAxGzDJ,yCALOD,EAAYjC,EAAMgE,EAAWhE,EAAKiE"}
--------------------------------------------------------------------------------
/dist/js/integrations/turbolinks.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"turbolinks.js","sources":["../../../src/js/mixins/turbolinks.js"],"sourcesContent":["/**\n * Turbolinks integration for Normas\n *\n * @see {@link https://github.com/evrone/normas#turbolinks-integration|Docs}\n * @see {@link https://github.com/evrone/normas/blob/master/src/js/mixins/turbolinks.js|Source}\n * @license MIT\n * @copyright Dmitry Karpunin , 2017-2018\n */\n\n// require navigation mixin\nexport default Base => (class extends Base {\n static turboPageEnterEventName = 'turbolinks:load';\n static turboPageLeaveEventName = 'turbolinks:before-cache';\n\n bindPageEvents(options) {\n this.Turbolinks = options.Turbolinks || global.Turbolinks;\n const turbolinksExists = !!this.Turbolinks;\n if (!this.enablings) this.enablings = {};\n this.constructor.readOptions(this.enablings, options.enablings, { turbolinks: turbolinksExists });\n if (NORMAS_DEBUG && options.enablings && options.enablings.turbolinks === true && !turbolinksExists) {\n this.error('🛤 Turbolinks: `options.enablings.turbolinks === true` but Turbolinks is not detected.',\n this.constructor.readmeLink('turbolinks-integration'));\n }\n if (NORMAS_DEBUG) {\n this.log('info', 'construct',\n ...this.constructor.logColor(`🛤 \"${this.instanceName}\" Turbolinks %REPLACE%.`,\n this.enablings.turbolinks ? 'enabled' : 'disabled',\n this.enablings.turbolinks ? 'green' : 'blue'));\n }\n if (this.enablings.turbolinks) {\n // Turbolinks connected :)\n // patchTurbolinks(this.Turbolinks); // TODO: check versions\n patchTurbolinksPreviewControl(this.Turbolinks);\n this.listenEvents(this.constructor.turboPageEnterEventName, this.pageEnter.bind(this));\n this.listenEvents(this.constructor.turboPageLeaveEventName, this.pageLeave.bind(this));\n if (options.Turbolinks) {\n options.Turbolinks.start();\n }\n } else {\n // No Turbolinks ;(\n if (NORMAS_DEBUG && options.enablings && options.enablings.turbolinks === false) {\n this.log('warn', 'construct',\n `🛤 You ${this.Turbolinks ? '' : 'do not '}have a Turbolinks and use integration, but \\`options.enablings.turbolinks === false\\`.`);\n }\n $(this.pageEnter.bind(this));\n }\n }\n\n visit(location) {\n if (this.enablings.turbolinks) {\n this.Turbolinks.visit(location);\n } else {\n super.visit(location);\n }\n }\n\n setHash(hash) {\n if (this.enablings.turbolinks) {\n let controller = this.Turbolinks.controller;\n controller.replaceHistoryWithLocationAndRestorationIdentifier(hash, controller.restorationIdentifier);\n } else {\n super.setHash(hash);\n }\n }\n\n replaceLocation(url) {\n if (this.enablings.turbolinks) {\n this.Turbolinks.controller.replaceHistoryWithLocationAndRestorationIdentifier(url);\n } else {\n super.replaceLocation(url);\n }\n }\n\n pushLocation(url) {\n if (this.enablings.turbolinks) {\n this.Turbolinks.controller.pushHistoryWithLocationAndRestorationIdentifier(url);\n } else {\n super.pushLocation(url);\n }\n }\n\n sayAboutPageLoading(state) {\n if (this.enablings.turbolinks) {\n const progressBar = this.Turbolinks.controller.adapter.progressBar;\n if (state) {\n progressBar.setValue(0);\n progressBar.show();\n } else {\n progressBar.hide();\n }\n } else {\n super.sayAboutPageLoading(state);\n }\n }\n});\n\nfunction patchTurbolinksPreviewControl(Turbolinks) {\n class OurView extends Turbolinks.View {\n render({ snapshot, error, isPreview }, callback) {\n this.markAsPreview(isPreview);\n if (snapshot) {\n // added `isPreview` argument\n this.renderSnapshot(snapshot, isPreview, callback);\n } else {\n this.renderError(error, callback);\n }\n }\n\n // added `isPreview` argument\n renderSnapshot(snapshot, isPreview, callback) {\n const renderer = new OurSnapshotRenderer(this.getSnapshot(), Turbolinks.Snapshot.wrap(snapshot), isPreview);\n renderer.delegate = this.delegate;\n renderer.render(callback);\n }\n }\n Turbolinks.View = OurView;\n\n class OurSnapshotRenderer extends Turbolinks.SnapshotRenderer {\n // added `isPreview` argument\n constructor(currentSnapshot, newSnapshot, isPreview) {\n super(currentSnapshot, newSnapshot);\n if (isPreview) {\n this.newBody = this.newBody.cloneNode(true);\n this.newBody.isPreview = true;\n }\n }\n }\n}\n\nfunction patchTurbolinks(Turbolinks) {\n class OurHeadDetails extends Turbolinks.HeadDetails {\n constructor(...args) {\n super(...args);\n this.elements = {};\n $(this.element).children().each((index, element) => {\n let key = turboElementToKey(element);\n if (!this.elements[key]) {\n this.elements[key] = {\n type: turboElementType(element),\n tracked: turboElementIsTracked(element),\n elements: [],\n };\n }\n this.elements[key].elements.push(element);\n });\n }\n // getTrackedElementSignature() {\n // let sign = super.getTrackedElementSignature();\n // console.log('sign ', sign);\n // return sign;\n // }\n }\n Turbolinks.HeadDetails = OurHeadDetails;\n}\n\n// Injection in Turbolinks.HeadDetails for override this logic:\nfunction turboElementToKey(element) {\n let url = element.getAttribute('src') || element.getAttribute('href');\n if (url) {\n let cuts = url.split('/');\n cuts = cuts[cuts.length - 1];\n if (cuts) { url = cuts }\n }\n return url || element.outerHTML;\n}\n\nfunction turboElementType(element) {\n if (turboElementIsScript(element)) {\n return 'script';\n } else if (turboElementIsStylesheet(element)) {\n return 'stylesheet';\n }\n return null;\n}\n\nfunction turboElementIsTracked(element) {\n return element.getAttribute('data-turbolinks-track') === 'reload';\n}\n\nfunction turboElementIsScript(element) {\n let tagName = element.tagName.toLowerCase();\n return tagName === 'script';\n}\n\nfunction turboElementIsStylesheet(element) {\n let tagName = element.tagName.toLowerCase();\n return tagName === 'style' || (tagName === 'link' && element.getAttribute('rel') === 'stylesheet');\n}\n"],"names":["Base","options","Turbolinks","global","turbolinksExists","this","enablings","constructor","readOptions","turbolinks","error","readmeLink","log","logColor","instanceName","OurView","callback","snapshot","isPreview","markAsPreview","renderSnapshot","renderError","renderer","OurSnapshotRenderer","getSnapshot","Snapshot","wrap","delegate","render","View","currentSnapshot","newSnapshot","newBody","_this3","cloneNode","SnapshotRenderer","listenEvents","turboPageEnterEventName","pageEnter","bind","turboPageLeaveEventName","pageLeave","start","location","visit","hash","controller","replaceHistoryWithLocationAndRestorationIdentifier","restorationIdentifier","url","pushHistoryWithLocationAndRestorationIdentifier","state","progressBar","adapter","setValue","show","hide"],"mappings":"0uCAUsCA,6CAIrBC,QACRC,WAAaD,EAAQC,YAAcC,OAAOD,eACzCE,IAAqBC,KAAKH,WAC3BG,KAAKC,YAAWD,KAAKC,mBACrBC,YAAYC,YAAYH,KAAKC,UAAWL,EAAQK,WAAaG,WAAYL,IAC1DH,EAAQK,YAA8C,IAAjCL,EAAQK,UAAUG,aAAwBL,QAC5EM,MAAM,yFACTL,KAAKE,YAAYI,WAAW,gCAGzBC,gBAAI,OAAQ,2IACZP,KAAKE,YAAYM,gBAAgBR,KAAKS,uCACvCT,KAAKC,UAAUG,WAAa,UAAY,WACxCJ,KAAKC,UAAUG,WAAa,QAAU,WAExCJ,KAAKC,UAAUG,YAmEvB,SAAuCP,OAC/Ba,oKACmCC,OAA9BC,IAAAA,SAAUP,IAAAA,MAAOQ,IAAAA,eACnBC,cAAcD,GACfD,OAEGG,eAAeH,EAAUC,EAAWF,QAEpCK,YAAYX,EAAOM,0CAKbC,EAAUC,EAAWF,OAC5BM,EAAW,IAAIC,EAAoBlB,KAAKmB,cAAetB,EAAWuB,SAASC,KAAKT,GAAWC,KACxFS,SAAWtB,KAAKsB,WAChBC,OAAOZ,UAfEd,EAAW2B,QAkBtBA,KAAOd,MAEZQ,yBAEQO,EAAiBC,EAAab,4EAClCY,EAAiBC,WACnBb,MACGc,QAAUC,EAAKD,QAAQE,WAAU,KACjCF,QAAQd,WAAY,sBANGhB,EAAWiC,mBArFX9B,KAAKH,iBAC9BkC,aAAa/B,KAAKE,YAAY8B,wBAAyBhC,KAAKiC,UAAUC,KAAKlC,YAC3E+B,aAAa/B,KAAKE,YAAYiC,wBAAyBnC,KAAKoC,UAAUF,KAAKlC,OAC5EJ,EAAQC,cACFA,WAAWwC,UAIDzC,EAAQK,YAA8C,IAAjCL,EAAQK,UAAUG,iBACpDG,IAAI,OAAQ,uBACLP,KAAKH,WAAa,GAAK,qGAEnCG,KAAKiC,UAAUC,KAAKlC,sCAIpBsC,GACAtC,KAAKC,UAAUG,gBACZP,WAAW0C,MAAMD,uFAEVA,mCAIRE,MACFxC,KAAKC,UAAUG,WAAY,KACzBqC,EAAazC,KAAKH,WAAW4C,aACtBC,mDAAmDF,EAAMC,EAAWE,kHAEjEH,2CAIFI,GACV5C,KAAKC,UAAUG,gBACZP,WAAW4C,WAAWC,mDAAmDE,iGAExDA,wCAIbA,GACP5C,KAAKC,UAAUG,gBACZP,WAAW4C,WAAWI,gDAAgDD,8FAExDA,+CAIHE,MACd9C,KAAKC,UAAUG,WAAY,KACvB2C,EAAc/C,KAAKH,WAAW4C,WAAWO,QAAQD,YACnDD,KACUG,SAAS,KACTC,UAEAC,8GAGYL,+FAhFG,uGACA"}
--------------------------------------------------------------------------------
/dist/js/extensions/views.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"views.js","sources":["../../../src/js/mixins/view.js","../../../src/js/mixins/views.js"],"sourcesContent":["export default Base => (class extends Base {\n // Override it with your own initialization logic (like componentDidUnmount in react).\n initialize(options) {\n }\n\n // Override it with your own unmount logic (like componentWillUnmount in react).\n terminate() {\n }\n\n // protected\n\n constructor(options) {\n Object.assign(options, Base.dom.data(options.el));\n super(options);\n this.reflectOptions(options);\n this.initializeEvents(options);\n this.initialize(options);\n if (NORMAS_DEBUG && this.logging.constructGrouping) {\n this.log('groupEnd', 'construct');\n }\n }\n\n destructor() {\n if (NORMAS_DEBUG) {\n const [destructText, ...destructStyles] = this.constructor.logColor('destructing', 'red');\n this.log(this.constructor.groupingMethod(this.logging.constructGrouping), 'construct',\n ...this.constructor.logBold(`${this.logging.constructPrefix} \"%REPLACE%\" ${destructText}`, this.instanceName),\n ...destructStyles,\n this);\n }\n this.terminate();\n if (this.listenedEvents) {\n this.forgetEvents(this.listenedEvents);\n this.listenedEvents = null;\n }\n if (NORMAS_DEBUG && this.logging.constructGrouping) {\n this.log('groupEnd', 'construct');\n }\n }\n\n reflectOptions(options) {\n if (!this.constructor.reflectOptions) {\n return;\n }\n Object.keys(options).forEach(attr => {\n if (this.constructor.reflectOptions.includes(attr)) {\n this[attr] = options[attr];\n }\n });\n }\n\n initializeEvents(_options) {\n const { events } = this.constructor;\n if (events) {\n if (!this.linkedEvents) {\n this.linkedEvents = this.linkEvents(this.helpers.isFunction(events) ? events() : events);\n }\n this.listenedEvents = this.listenEvents(this.linkedEvents);\n }\n }\n\n linkEvents(events) {\n return this.helpers.mapValues(events, handle => this.helpers.isString(handle) ?\n this[handle].bind(this)\n :\n (typeof this.helpers.isPlainObject(handle) ? this.linkEvents(handle) : handle)\n );\n }\n\n data(key, ...value) {\n this.dom.data(this.el, key, ...value);\n }\n});\n","/**\n * Views system for Normas\n *\n * @see {@link https://github.com/evrone/normas#-views|Docs}\n * @see {@link https://github.com/evrone/normas/blob/master/src/js/mixins/views.js|Source}\n * @license MIT\n * @copyright Dmitry Karpunin , 2017-2018\n */\n\n// TODO: may be rename Views, views, View, view\nimport normasView from './view';\n\nexport default Base => normasViews(Base, normasView(Base.NormasCore));\n\n// require content mixin\n// require events mixin\nconst normasViews = (Base, View) => (class extends Base {\n static View = View;\n View = View;\n viewClasses = {};\n viewInstances = [];\n\n constructor(options) {\n super(options);\n this.viewOptions = {\n debugMode: this.debugMode,\n ...options.viewOptions,\n };\n if (NORMAS_DEBUG) {\n this.viewOptions.logging = {\n ...this.logging,\n constructGrouping: 'groupCollapsed',\n constructPrefix: '🏭', // private\n eventsDebounced: false,\n ...(options.viewOptions && options.viewOptions.logging),\n };\n this.log('info', 'construct', `🏭 \"${this.instanceName}\" views mixin activated.`);\n }\n }\n\n registerView(viewClass, options = {}) {\n if (this.viewClasses[viewClass.selector]) {\n if (NORMAS_DEBUG) {\n this.error(`🏭 View class for selector \\`${viewClass.selector}\\` already registered`,\n this.viewClasses[viewClass.selector]);\n }\n return;\n }\n this.viewClasses[viewClass.selector] = viewClass;\n this.listenToElement(\n viewClass.selector,\n $el => this.bindView($el, viewClass, options),\n $el => this.unbindView($el, viewClass),\n {\n delay: viewClass.delay,\n silent: true,\n },\n );\n }\n\n bindView($el, viewClass, options) {\n if (!this.canBind($el, viewClass)) {\n return null;\n }\n if (viewClass.instanceIndex) {\n viewClass.instanceIndex += 1;\n } else {\n viewClass.instanceIndex = 1;\n }\n const view = new viewClass({\n ...this.helpers.deepMerge(this.viewOptions, options),\n instanceName: `${viewClass.selector}_${viewClass.instanceIndex}`,\n el: $el[0],\n });\n this.viewInstances.push(view);\n return view;\n }\n\n canBind($element, viewClass) {\n const view = this.getViewsOnElement($element, viewClass)[0];\n if (view) {\n if (NORMAS_DEBUG) {\n this.log('warn', '🏭 Element already has bound view', $element, viewClass, view);\n }\n return false;\n }\n return true;\n }\n\n unbindView($element, viewClass) {\n const view = this.getViewsOnElement($element, viewClass)[0];\n if (view) {\n view.destructor();\n this.viewInstances = this.helpers.without(this.viewInstances, view);\n }\n }\n\n getViewsOnElement($element, viewClass = null) {\n const el = $element instanceof $ ? $element[0] : $element;\n const filterOptions = { el };\n if (viewClass) {\n filterOptions.constructor = viewClass;\n }\n return this.helpers.filter(this.viewInstances, filterOptions);\n }\n\n getViewsInContainer($container, checkRoot = true) {\n return this.helpers.filter(this.viewInstances, view =>\n view.$el.closest($container).length > 0 && (checkRoot || view.el !== $container[0])\n );\n }\n\n getAllViews(viewClass) {\n return this.helpers.filter(this.viewInstances, { constructor: viewClass });\n }\n\n getFirstView(viewClass) {\n return this.helpers.find(this.viewInstances, { constructor: viewClass });\n }\n\n getFirstChildView(viewClass) {\n return this.helpers.find(this.viewInstances, view => view instanceof viewClass);\n }\n});\n"],"names":["options","assign","Base","dom","data","el","reflectOptions","initializeEvents","initialize","_this","logging","constructGrouping","log","this","constructor","logColor","destructText","destructStyles","groupingMethod","logBold","constructPrefix","instanceName","terminate","listenedEvents","forgetEvents","keys","forEach","_this2","includes","attr","_options","events","linkedEvents","linkEvents","helpers","isFunction","listenEvents","mapValues","_this3","isString","handle","bind","babelHelpers.typeof","isPlainObject","key","value","normasViews","View","viewOptions","debugMode","viewClass","viewClasses","selector","error","listenToElement","bindView","$el","unbindView","delay","canBind","instanceIndex","view","deepMerge","viewInstances","push","$element","getViewsOnElement","destructor","without","filterOptions","$","filter","$container","checkRoot","closest","length","find","normasView","NormasCore"],"mappings":"01CAWcA,oBACHC,OAAOD,EAASE,EAAKC,IAAIC,KAAKJ,EAAQK,oEACvCL,aACDM,eAAeN,KACfO,iBAAiBP,KACjBQ,WAAWR,GACIS,EAAKC,QAAQC,qBAC1BC,IAAI,WAAY,0BAlBWV,yCAEzBF,6FAsBmCa,KAAKC,YAAYC,SAAS,cAAe,gDAA5EC,OAAiBC,kBACnBL,gBAAIC,KAAKC,YAAYI,eAAeL,KAAKH,QAAQC,mBAAoB,sBACrEE,KAAKC,YAAYK,QAAWN,KAAKH,QAAQU,gCAA+BJ,EAAgBH,KAAKQ,iBAC7FJ,IACHJ,aAECS,YACDT,KAAKU,sBACFC,aAAaX,KAAKU,qBAClBA,eAAiB,MAEJV,KAAKH,QAAQC,wBAC1BC,IAAI,WAAY,oDAIVZ,cACRa,KAAKC,YAAYR,uBAGfmB,KAAKzB,GAAS0B,QAAQ,YACvBC,EAAKb,YAAYR,eAAesB,SAASC,OACtCA,GAAQ7B,EAAQ6B,+CAKVC,OACPC,EAAWlB,KAAKC,YAAhBiB,OACJA,IACGlB,KAAKmB,oBACHA,aAAenB,KAAKoB,WAAWpB,KAAKqB,QAAQC,WAAWJ,GAAUA,IAAWA,SAE9ER,eAAiBV,KAAKuB,aAAavB,KAAKmB,kDAItCD,qBACFlB,KAAKqB,QAAQG,UAAUN,EAAQ,mBAAUO,EAAKJ,QAAQK,SAASC,GACpEF,EAAKE,GAAQC,QAEZC,EAAOJ,EAAKJ,QAAQS,cAAcH,IAAUF,EAAKL,WAAWO,GAAUA,iCAItEI,gCAAQC,2DACN1C,KAAIC,cAAKS,KAAKR,GAAIuC,UAAQC,cCtD7BC,EAAc,SAAC5C,EAAM6C,6CAMb/C,4EACJA,2EALD+C,gKAMAC,yBACQvC,EAAKwC,WACbjD,EAAQgD,eAGNA,YAAYtC,aACZD,EAAKC,2BACW,iCACF,sBACA,GACbV,EAAQgD,aAAehD,EAAQgD,YAAYtC,WAE5CE,IAAI,OAAQ,mBAAoBH,EAAKY,sDApBGnB,2CAwBpCgD,cAAWlD,4DAClBa,KAAKsC,YAAYD,EAAUE,eAEtBC,qCAAsCH,EAAUE,gCACnDvC,KAAKsC,YAAYD,EAAUE,iBAI5BD,YAAYD,EAAUE,UAAYF,OAClCI,gBACHJ,EAAUE,SACV,mBAAOzB,EAAK4B,SAASC,EAAKN,EAAWlD,IACrC,mBAAO2B,EAAK8B,WAAWD,EAAKN,WAEnBA,EAAUQ,cACT,sCAKLF,EAAKN,EAAWlD,OAClBa,KAAK8C,QAAQH,EAAKN,UACd,KAELA,EAAUU,gBACFA,eAAiB,IAEjBA,cAAgB,MAEtBC,EAAO,IAAIX,OACZrC,KAAKqB,QAAQ4B,UAAUjD,KAAKmC,YAAahD,iBAC3BkD,EAAUE,aAAYF,EAAUU,iBAC7CJ,EAAI,kBAELO,cAAcC,KAAKH,GACjBA,kCAGDI,EAAUf,OACVW,EAAOhD,KAAKqD,kBAAkBD,EAAUf,GAAW,UACrDW,SAEKjD,IAAI,OAAQ,oCAAqCqD,EAAUf,EAAWW,IAEtE,sCAKAI,EAAUf,OACbW,EAAOhD,KAAKqD,kBAAkBD,EAAUf,GAAW,GACrDW,MACGM,kBACAJ,cAAgBlD,KAAKqB,QAAQkC,QAAQvD,KAAKkD,cAAeF,8CAIhDI,OAAUf,yDAAY,KAEhCmB,GAAkBhE,GADb4D,aAAoBK,EAAIL,EAAS,GAAKA,UAE7Cf,MACYpC,YAAcoC,GAEvBrC,KAAKqB,QAAQqC,OAAO1D,KAAKkD,cAAeM,+CAG7BG,OAAYC,oEACvB5D,KAAKqB,QAAQqC,OAAO1D,KAAKkD,cAAe,mBAC7CF,EAAKL,IAAIkB,QAAQF,GAAYG,OAAS,IAAMF,GAAaZ,EAAKxD,KAAOmE,EAAW,0CAIxEtB,UACHrC,KAAKqB,QAAQqC,OAAO1D,KAAKkD,eAAiBjD,YAAaoC,yCAGnDA,UACJrC,KAAKqB,QAAQ0C,KAAK/D,KAAKkD,eAAiBjD,YAAaoC,8CAG5CA,UACTrC,KAAKqB,QAAQ0C,KAAK/D,KAAKkD,cAAe,mBAAQF,aAAgBX,6EAxGzDH,yCALOD,EAAY5C,EAAM2E,EAAW3E,EAAK4E"}
--------------------------------------------------------------------------------