With `-` the program is read from stdin.
~~~
$ echo H4sIAN7lUFcAAysoyswrUVDySM3JyVfiAgCjfiaWDgAAAA== | base64 -d | gunzip | nit -
Hello
~~~
Pull-Request: #2159
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
app.user = null
return true
else if res isa BenitluxError then
- app.feedback((res.user_message or else res.message).t)
+ feedback((res.user_message or else res.message).t)
return true
else if res isa Error then
- app.feedback res.message.t
+ feedback res.message.t
return true
end
return false
end
+
+ # Show feedback pertinent to the user, defaults to a platform specific popup
+ fun feedback(text: String) do app.feedback text
end
# Async request with services to act on the windows of the app
map["Welcome %0!"] = "Bienvenue %0!"
map["Logged in as %0"] = "Connecté en tant que %0"
map["Username"] = "Nom d'utilisateur"
- map["Invalid name"] = "Nom d'utilisateur invalide"
map["Password"] = "Mot de passe"
- map["Passwords must be composed of at least 6 characters."] = "Le mot de passe doit avoir au moins 6 charactères."
+ map["Repeat password"] = "Répéter le mot de passe"
map["Email"] = "Courriel"
map["Login"] = "Se connecter"
+ map["Loging in..."] = "Authentification..."
map["Logout"] = "Se déconnecter"
map["Signup"] = "Créer un compte"
+ map["Signing up..."] = "Création du compte..."
+
+ map["Passwords must be composed of at least 6 characters."] = "Le mot de passe doit avoir au moins 6 charactères."
+ map["Fill the following fields to sign up."] = "Remplissez les champs suivants pour créer un compte."
+
+ map["Passwords do not match."] = "Les mots de passe ne correspondent pas."
+ map["Invalid username."] = "Nom d'utilisateur invalide."
+ map["Invalid password."] = "Mot de passe invalide."
+ map["Username already in use."] = "Le nom d'utilisateur est déjà réservé."
+ map["Invalid username and password combination."] = "La combinaison de nom et mot de passe n'est pas reconnue."
# Social views
map["Follow"] = "Suivre"
class SignupWindow
super Window
- # Main window layout
- var layout = new ListLayout(parent=self)
+ private var list = new ListLayout(parent=self)
+ private var lbl_feedback = new Label(parent=list, text="Welcome")
- private var lbl_welcome = new Label(parent=layout, text="Welcome")
+ private var layout_login = new VerticalLayout(parent=list)
+
+ # ---
+ # First the login options
# Name
- private var name_line = new HorizontalLayout(parent=layout)
+ private var name_line = new HorizontalLayout(parent=layout_login)
private var lbl_name = new Label(parent=name_line, text="Username".t)
private var txt_name = new TextInput(parent=name_line, text=app.user)
- # Pass
- private var pass_line = new HorizontalLayout(parent=layout)
+ # Password
+ private var pass_line = new HorizontalLayout(parent=layout_login)
private var lbl_pass = new Label(parent=pass_line, text="Password".t)
private var txt_pass = new TextInput(parent=pass_line, is_password=true)
- private var lbl_pass_desc = new Label(parent=layout,
+ private var lbl_pass_desc = new Label(parent=layout_login, size = 0.5,
text="Passwords must be composed of at least 6 characters.".t)
- private var but_login = new Button(parent=layout, text="Login".t)
+ private var but_login = new Button(parent=layout_login, text="Login".t)
+
+ # ---
+ # Then, the signup options
+
+ private var layout_register = new VerticalLayout(parent=list)
+
+ private var lbl_signup_desc = new Label(parent=layout_register, size = 0.5,
+ text="Fill the following fields to sign up.".t)
+
+ # Repeat password
+ private var pass_line2 = new HorizontalLayout(parent=layout_register)
+ private var lbl_pass2 = new Label(parent=pass_line2, text="Repeat password".t)
+ private var txt_pass2 = new TextInput(parent=pass_line2, is_password=true)
# Email
- private var email_line = new HorizontalLayout(parent=layout)
+ private var email_line = new HorizontalLayout(parent=layout_register)
private var lbl_email = new Label(parent=email_line, text="Email".t)
private var txt_email = new TextInput(parent=email_line)
- private var but_signup = new Button(parent=layout, text="Signup".t)
-
- private var lbl_feedback = new Label(parent=layout, text="")
+ private var but_signup = new Button(parent=layout_register, text="Signup".t)
init
do
- lbl_pass_desc.size = 0.5
-
for c in [but_login, but_signup] do
c.observers.add self
end
var name = txt_name.text
if name == null or not name.name_is_ok then
- feedback "Invalid name".t
+ feedback "Invalid username.".t
return
end
var pass = txt_pass.text
if pass == null or not pass.pass_is_ok then
- feedback "Invalid password".t
+ feedback "Invalid password.".t
return
end
if sender == but_login then
+ feedback "Logging in...".t
(new LoginOrSignupAction(self, "rest/login?name={name}&pass={pass.pass_hash}")).start
else if sender == but_signup then
+ if pass != txt_pass2.text then
+ feedback "Passwords do not match.".t
+ return
+ end
+
var email = txt_email.text
if email == null or email.is_empty then
feedback "Invalid email".t
return
end
+ feedback "Signing up...".t
(new LoginOrSignupAction(self, "rest/signup?name={name}&pass={pass.pass_hash}&email={email}")).start
end
end
app.on_log_in
end
-end
-
-# Async request for signing up
-class SignupAction
- super WindowHttpRequest
- redef type W: SignupWindow
-
- init do affected_views.add_all([window.but_signup])
-
- redef fun on_load(res)
- do
- if intercept_error(res) then return
-
- if not res isa LoginResult then
- on_fail new Error("Server sent unexpected data {res or else "null"}")
- return
- end
-
- app.token = res.token
- app.user = res.user.name
- app.on_log_in
- end
+ redef fun feedback(text) do window.feedback text
end
# Check if already in user
var stmt = select("ROWID FROM users WHERE lower({user.to_sql_string}) = lower(name)")
assert stmt != null else print_error "Select 'sign_up' failed with: {error or else "?"}"
- if not stmt.iterator.to_a.is_empty then return "Username already in use"
+ if not stmt.iterator.to_a.is_empty then return "Username already in use."
# Check email use
stmt = select("ROWID FROM users WHERE lower({email.to_sql_string}) = lower(email)")
# Used to translate ids in beautiful page names.
fun pretty_name(name: String): String do
name = name.replace("_", " ")
- name = name.capitalized
+ name = name.capitalized(keep_upper=true)
return name
end
end
* Life-cycle
* User interface
* Persistence
+* Async HTTP requests
* Package metadata
* Compilation and packaging
The features offered by _app.nit_ are common to all platforms, but
may not be available on all devices.
-## Application Life-Cycle
+# Application Life-Cycle
The _app.nit_ application life-cycle is compatible with all target platforms.
It relies on the following sequence of events, represented here by their callback method name:
Other UI elements, from the `ui` submodule, are notified of the same events using a simple depth first visit.
So all UI elements can react separately to live-cycle events.
-## User Interface
+# User Interface
The `app::ui` module defines an abstract API to build a portable graphical application.
The API is composed of interactive `Control`s, visible `View`s and an active `Window`.
* Add an observer to a `Button` instance, and implement `on_event` in the observer.
-### Usage Example
+## Usage Example
The calculator example (at `../../examples/calculator/src/calculator.nit`) is a concrete,
simple and complete use of the _app.nit_ portable UI.
-### Platform-specific UI
+## Platform-specific UI
You can go beyond the portable UI API of _app.nit_ by using the natives services of a platform.
The suggested approach is to use platform specific modules to customize the application on a precise platform.
-This module redefine `Window::on_start` to call the native language of the platform and setup a native UI.
+See the calculator example for an adaptation of the UI on Android,
+the interesting module is in this repository at ../../examples/calculator/src/android_calculator.nit
-_TODO complete description and add concrete examples_
-
-## Persistent State with data\_store
+# Persistent State with data\_store
_app.nit_ offers the submodule `app::data_store` to easily save the application state and user preferences.
The service is accessible by the method `App::data_store`. The `DataStore` itself defines 2 methods:
* `DataStore::[]` returns the object associated to a `String` key.
It returns `null` if nothing is associated to the key.
-### Usage Example
+## Usage Example
~~~
import app::data_store
end
~~~
-## Metadata annotations
+# Async HTTP request
+
+The module `app::http_request` provides services to execute asynchronous HTTP request.
+The class `AsyncHttpRequest` hides the complex parallel logic and
+lets the user implement methods acting only on the UI thread.
+See the documentation of `AsyncHttpRequest` for more information.
+
+# Metadata annotations
The _app.nit_ framework defines three annotations to customize the application package.
The special function `git_revision` will use the prefix of the hash of the latest git commit.
By default, the version is 0.1.
-### Usage Example
+## Usage Example
~~~
module my_module is
end
~~~
-## Compiling and Packaging an Application
+# Compiling and Packaging an Application
The Nit compiler detects the target platform from the importations and generates the appropriate application format and package.
Applications using only the portable services of _app.nit_ require some special care at compilation.
Such an application, let's say `calculator.nit`, does not depend on a specific platform and use the portable UI.
-The target platform must be specifed to the compiler for it to produce the correct application package.
+The target platform must be specified to the compiler for it to produce the correct application package.
There is two main ways to achieve this goal:
-* The the mixin option (`-m path`) loads an additionnal module before compiling.
+* The mixin option (`-m module`) imports an additional module before compiling.
It can be used to load platform specific implementations of the _app.nit_ portable UI.
~~~
# GNU/Linux version, using GTK
- nitc calculator.nit -m NIT_DIR/lib/linux/ui.nit
+ nitc calculator.nit -m linux
# Android version
- nitc calculator.nit -m NIT_DIR/lib/android/ui/
+ nitc calculator.nit -m android
+
+ # iOS version
+ nitc calculator.nit -m ios
~~~
* A common alternative for larger projects is to use platform specific modules.
- Continuing with the `calculator.nit` example, it can be accompagnied by the module `calculator_linux.nit`.
- This module imports both `calculator` and `linux::ui`, and can also use other GNU/Linux specific code.
+ Continuing with the calculator example, it is adapted for Android by the module `android_calculator.nit`.
+ This module imports both `calculator` and `android`, it can then use Android specific code.
~~~
- module calculator_linux
+ module android_calculator
import calculator
- import linux::ui
+ import android
+
+ # ...
~~~
fun run_on_ui_thread(task: Task) is abstract
end
-# Thread executing an HTTP request and deserializing JSON asynchronously
+# Thread executing an HTTP request asynchronously
#
-# This class defines four methods acting on the main/UI thread,
-# they should be implemented as needed:
-# * before
-# * on_load
-# * on_fail
-# * after
+# The request is sent to `rest_server_uri / rest_action`.
+#
+# If `deserialize_json`, the default behavior, the response is deserialized from JSON
+#
+# If `delay > 0.0`, sending the reqest is delayed by the given `delay` in seconds.
+# It can be used to delay resending a request on error.
+#
+# Four callback methods act on the main/UI thread,
+# they should be implemented as needed in subclasses:
+# * `before`
+# * `on_load`
+# * `on_fail`
+# * `after`
class AsyncHttpRequest
super Thread
[package]
name=app
-tags=lib
+tags=lib,mobile
maintainer=Alexis Laferrière <alexis.laf@xymus.net>
license=Apache-2.0
[upstream]
# Letters that follow a letter are lowercased
# Letters that follow a non-letter are upcased.
#
+ # If `keep_upper = true`, already uppercase letters are not lowercased.
+ #
# SEE : `Char::is_letter` for the definition of letter.
#
# assert "jAVASCRIPT".capitalized == "Javascript"
# assert "i am root".capitalized == "I Am Root"
# assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
- fun capitalized: SELFTYPE do
+ # assert "preserve my ACRONYMS".capitalized(keep_upper=true) == "Preserve My ACRONYMS"
+ fun capitalized(keep_upper: nullable Bool): SELFTYPE do
if length == 0 then return self
var buf = new Buffer.with_cap(length)
-
- var curr = chars[0].to_upper
- var prev = curr
- buf[0] = curr
-
- for i in [1 .. length[ do
- prev = curr
- curr = self[i]
- if prev.is_letter then
- buf[i] = curr.to_lower
- else
- buf[i] = curr.to_upper
- end
- end
-
+ buf.capitalize(keep_upper=keep_upper, src=self)
return buf.to_s
end
end
# Letters that follow a letter are lowercased
# Letters that follow a non-letter are upcased.
#
+ # If `keep_upper = true`, uppercase letters are not lowercased.
+ #
+ # When `src` is specified, this method reads from `src` instead of `self`
+ # but it still writes the result to the beginning of `self`.
+ # This requires `self` to have the capacity to receive all of the
+ # capitalized content of `src`.
+ #
# SEE: `Char::is_letter` for the definition of a letter.
#
# var b = new FlatBuffer.from("jAVAsCriPt")
# b = new FlatBuffer.from("ab_c -ab0c ab\nc")
# b.capitalize
# assert b == "Ab_C -Ab0C Ab\nC"
- fun capitalize do
+ #
+ # b = new FlatBuffer.from("12345")
+ # b.capitalize(src="foo")
+ # assert b == "Foo45"
+ #
+ # b = new FlatBuffer.from("preserve my ACRONYMS")
+ # b.capitalize(keep_upper=true)
+ # assert b == "Preserve My ACRONYMS"
+ fun capitalize(keep_upper: nullable Bool, src: nullable Text) do
+ src = src or else self
+ var length = src.length
if length == 0 then return
- var c = self[0].to_upper
+ keep_upper = keep_upper or else false
+
+ var c = src[0].to_upper
self[0] = c
var prev = c
for i in [1 .. length[ do
prev = c
- c = self[i]
+ c = src[i]
if prev.is_letter then
- self[i] = c.to_lower
+ if keep_upper then
+ self[i] = c
+ else
+ self[i] = c.to_lower
+ end
else
self[i] = c.to_upper
end
--- /dev/null
+<div class='card'>
+ <div class='card-left text-center'>
+ <entity-tag mentity='mentity' />
+ </div>
+ <div class='card-body'>
+ <h5 class='card-heading'>
+ <entity-signature mentity='mentity'/>
+ </h5>
+ <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
+ </div>
+</div>
--- /dev/null
+<div class='entity-list'
+ ng-if='(listEntities | filter:listObjectFilter).length > 0'>
+ <h3 id={{listId}}>
+ <span>{{listTitle}}</span>
+ <button class='btn btn-link btn-xs pull-right btn-filter' ng-click='toggleFilters()'>
+ <span class='glyphicon glyphicon-filter text-muted' />
+ </button>
+ </h3>
+ <div ng-if='showFilters'>
+ <ui-filter-form
+ search-filter='listObjectFilter'
+ visibility-filter='visibilityFilter'>
+ </div>
+ <div class='card-list'>
+ <entity-card mentity='mentity'
+ ng-repeat='mentity in listEntities | filter:listObjectFilter | visibility:visibilityFilter' />
+ </div>
+ </div>
+</div>
--- /dev/null
+<span ng-if='mentity.location'>
+ <a ng-href="{{mentity.web_url}}">{{mentity.location.file}}
+ <span ng-if='mentity.location.line_start'>:{{mentity.location.line_start}}</span>
+ </a>
+</span>
--- /dev/null
+<span class="glyphicon glyphicon-tag" ng-class='{
+ "text-success": mentity.visibility == "public",
+ "text-warning": mentity.visibility == "protected",
+ "text-danger": mentity.visibility == "private",
+}' />
--- /dev/null
+<button
+ class='btn btn-link btn-xs'
+ ng-click='toggle()'>
+ <span ng-if='property' ng-class='classesOn'/>
+ <span ng-if='!property' ng-class='classesOff'/>
+</button>
--- /dev/null
+<div class='form-group has-icon'>
+ <input type='text' class='form-control' ng-model='property' placeholder='Filter...'>
+ <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
+</div>
--- /dev/null
+<form class='form-inline'>
+ <ui-filter-field property='searchFilter.$' />
+ <div class='pull-right'>
+ <ui-filter-group-vis property='visibilityFilter' />
+ </div>
+</form>
--- /dev/null
+<div class='form-group'>
+ <ui-filter-button-vis property='property.public'
+ classes-on='"glyphicon glyphicon-eye-open text-success"'
+ classes-off='"glyphicon glyphicon-eye-close text-success"'
+ title='Toggle public' />
+ <ui-filter-button-vis property='property.protected'
+ classes-on='"glyphicon glyphicon-eye-open text-warning"'
+ classes-off='"glyphicon glyphicon-eye-close text-warning"'
+ title='Toggle protected' />
+ <ui-filter-button-vis property='property.private'
+ classes-on='"glyphicon glyphicon-eye-open text-danger"'
+ classes-off='"glyphicon glyphicon-eye-close text-danger"'
+ title='Toggle private' />
+</div>
<div class='col-xs-3 navbar-header'>
<a class='navbar-brand' ng-href='/'>Nitdoc</a>
</div>
+ <div class='col-xs-7'>
+ <form ng-controller='SearchCtrl as searchCtrl' >
+ <div class='form-group has-icon'>
+ <input placeholder='Search...' type='text' class='form-control search-input'
+ ng-model-options='{ debounce: 150 }' ng-model='query'
+ ng-keydown='update($event)' ng-change='search()'>
+ <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
+ </div>
+ <div ng-if='results.length > 0' class='search-results'>
+ <div class='card-list'>
+ <entity-card ng-click='reset()' ng-class='{active: activeItem == $index}' mentity='mentity' ng-repeat='mentity in results' />
+ </div>
+ </div>
+ </form>
+ </div>
</div>
</nav>
<div ng-view></div>
<script src='/javascripts/nitweb.js'></script>
<script src='/javascripts/model.js'></script>
<script src='/javascripts/entities.js'></script>
+ <script src='/javascripts/ui.js'></script>
</body>
</html>
(function() {
angular
- .module('entities', ['model'])
+ .module('entities', ['ui', 'model'])
.controller('EntityCtrl', ['Model', '$routeParams', '$scope', function(Model, $routeParams, $scope) {
Model.loadEntity($routeParams.id,
templateUrl: '/directives/entity/signature.html'
};
})
+
+ .directive('entityTag', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ mentity: '='
+ },
+ replace: true,
+ templateUrl: '/directives/entity/tag.html'
+ };
+ })
+
+ .directive('entityLocation', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ mentity: '='
+ },
+ templateUrl: '/directives/entity/location.html'
+ };
+ })
+
+ .directive('entityCard', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ mentity: '='
+ },
+ replace: true,
+ templateUrl: '/directives/entity/card.html'
+ };
+ })
+
+ .directive('entityList', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ listEntities: '=',
+ listTitle: '@',
+ listObjectFilter: '=',
+ },
+ templateUrl: '/directives/entity/list.html',
+ link: function ($scope, element, attrs) {
+ $scope.showFilters = false;
+ if(!$scope.listObjectFilter) {
+ $scope.listObjectFilter = {};
+ }
+ if(!$scope.visibilityFilter) {
+ $scope.visibilityFilter = {
+ public: true,
+ protected: true,
+ private: false
+ };
+ }
+ $scope.toggleFilters = function() {
+ $scope.showFilters = !$scope.showFilters;
+ };
+ }
+ };
+ })
})();
$http.get(apiUrl + '/entity/' + id)
.success(cb)
.error(cbErr);
- }
+ },
+ search: function(q, n, cb, cbErr) {
+ $http.get(apiUrl + '/search?q=' + q + '&n=' + n)
+ .success(cb)
+ .error(cbErr);
+ }
};
}])
})();
--- /dev/null
+/*
+ * Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+ angular
+ .module('ui', [ 'model' ])
+
+ .controller('SearchCtrl', ['Model', '$routeParams', '$scope', '$window', function(Model, $routeParams, $scope, $window) {
+ $scope.query = '';
+
+ $scope.reset = function() {
+ $scope.activeItem = 0;
+ $scope.results = [];
+ }
+
+ $scope.update = function(e) {
+ if(e.keyCode == 38) {
+ $scope.selectUp();
+ } else if(e.keyCode == 40) {
+ $scope.selectDown();
+ } else if(e.keyCode == 27) {
+ $scope.selectEscape();
+ } else if(e.keyCode == 13) {
+ $scope.selectEnter();
+ }
+ }
+
+ $scope.selectUp = function() {
+ if($scope.activeItem > 0) {
+ $scope.activeItem -= 1;
+ }
+ }
+
+ $scope.selectDown = function() {
+ if($scope.activeItem < $scope.results.length - 1) {
+ $scope.activeItem += 1;
+ }
+ }
+
+ $scope.selectEnter = function() {
+ $window.location.href = $scope.results[$scope.activeItem].web_url;
+ $scope.reset();
+ }
+
+ $scope.selectEscape = function() {
+ $scope.reset();
+ }
+
+ $scope.search = function() {
+ if(!$scope.query) {
+ $scope.reset();
+ return;
+ }
+ Model.search($scope.query, 10,
+ function(data) {
+ $scope.reset();
+ $scope.results = data;
+ }, function(err) {
+ $scope.reset();
+ $scope.error = err;
+ });
+ }
+
+ $scope.reset();
+ }])
+
+ .directive('uiFilters', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ property: '=',
+ classesOn: '=',
+ classesOff: '='
+ },
+ replace: true,
+ templateUrl: '/directives/ui-filter-button-vis.html',
+ link: function ($scope, element, attrs) {
+ $scope.toggle = function() {
+ $scope.property = !$scope.property;
+ }
+ }
+ };
+ })
+
+ .filter('visibility', function() {
+ return function(input, visibilityFilter) {
+ var res = [];
+ input.forEach(function(entry) {
+ if(visibilityFilter.public == false && entry.visibility == "public") {
+ return;
+ }
+ if(visibilityFilter.protected == false && entry.visibility == "protected") {
+ return;
+ }
+ if(visibilityFilter.private == false && entry.visibility == "private") {
+ return;
+ }
+ res.push(entry);
+ });
+ return res;
+ };
+ })
+
+ .directive('uiFilterForm', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ searchFilter: '=',
+ visibilityFilter: '='
+ },
+ replace: true,
+ templateUrl: '/directives/ui-filter-form.html'
+ };
+ })
+
+ .directive('uiFilterField', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ property: '='
+ },
+ replace: true,
+ templateUrl: '/directives/ui-filter-field.html'
+ };
+ })
+
+ .directive('uiFilterGroupVis', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ property: '='
+ },
+ replace: true,
+ templateUrl: '/directives/ui-filter-group-vis.html'
+ };
+ })
+
+ .directive('uiFilterButtonVis', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ property: '=',
+ classesOn: '=',
+ classesOff: '='
+ },
+ replace: true,
+ templateUrl: '/directives/ui-filter-button-vis.html',
+ link: function ($scope, element, attrs) {
+ $scope.toggle = function() {
+ $scope.property = !$scope.property;
+ }
+ }
+ };
+ })
+})();
width: 10000px;
}
-.card-body {
+.card-body, .card-right, .card-left {
display: table-cell;
vertical-align: top;
}
+.card-left, .card>.pull-left {
+ padding: 15px;
+ padding-right: 0px;
+}
+.card-right, .card>.pull-right {
+ padding: 15px;
+ padding-left: 0px;
+}
+
+.card-list {
+ margin-top: 10px;
+}
+
+.card-list > .card:first-child {
+ border-top: 1px solid #ccc;
+}
+
+.card-list > .card {
+ margin-top: 0;
+ border-top: none;
+}
+
+/* ui */
+
+entity-list .btn-filter {
+ visibility: hidden;
+}
+
+entity-list:hover .btn-filter {
+ visibility: visible;
+}
+
/* doc */
.nitdoc .synopsys {
background-color: #ff9c0f;
}
+/* forms */
+
+.has-icon {
+ position: relative;
+}
+
+.has-icon .form-control {
+ padding-left: 35px;
+}
+
+.form-control-icon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 2;
+ display: block;
+ width: 34px;
+ height: 34px;
+ line-height: 34px;
+ text-align: center;
+ pointer-events: none;
+}
+
+/* search */
+
+.search-input {
+ width: 100%;
+}
+
+.search-results {
+ position: absolute;
+ right: 0;
+}
+
+.search-results .card.active {
+ background: #eee;
+ border-color: #eee;
+}
+
+/* navs */
+
+.nav-tabs li { cursor: pointer; }
+
+.navbar-fixed-top {
+ background-color: #1E9431;
+ box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28);
+}
+
+.navbar-fixed-top .form-control:hover, .navbar-fixed-top .form-control:focus {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+.navbar-fixed-top .form-control {
+ background: rgba(255, 255, 255, 0.1);
+ border: none;
+ color: #fff;
+ box-shadow: none;
+}
+
+.navbar-fixed-top .form-control-icon {
+ color: #fff;
+}
+
+.navbar-fixed-top *::-webkit-input-placeholder {
+ color: #fff;
+}
+.navbar-fixed-top *:-moz-placeholder {
+ /* FF 4-18 */
+ color: #fff;
+}
+.navbar-fixed-top *::-moz-placeholder {
+ /* FF 19+ */
+ color: #fff;
+}
+.navbar-fixed-top *:-ms-input-placeholder {
+ /* IE 10+ */
+ color: #fff;
+}
+
+.navbar-fixed-top .form-group {
+ margin-top: 8px;
+ margin-bottom: 0px;
+}
/*
* Code Highlighting
*/
<div class='tab-content'>
<div class='tab-pane fade in active' id='doc'>
<entity-doc mentity='mentity.intro'/>
+
+ <entity-list list-title='Parents'
+ list-entities='mentity.parents'
+ list-object-filter='{}' />
+
+ <entity-list list-title='Constructors'
+ list-entities='mentity.all_mproperties'
+ list-object-filter='{is_init: true}' />
+
+ <entity-list list-title='Introduced properties'
+ list-entities='mentity.intro_mproperties'
+ list-object-filter='{is_init: "!true"}' />
+
+ <entity-list list-title='Redefined properties'
+ list-entities='mentity.redef_mproperties'
+ list-object-filter='{is_init: "!true"}' />
</div>
</div>
</div>
<div class='tab-content'>
<div class='tab-pane fade in active' id='doc'>
<entity-doc mentity='mentity'/>
+
+ <entity-list list-title='Parent group' list-entities='[mentity.parent]'
+ list-object-filter='{}' ng-if='mentity.parent' />
+
+ <entity-list list-title='Subgroups' list-entities='mentity.mgroups'
+ list-object-filter='{}' />
+
+ <entity-list list-title='Modules' list-entities='mentity.mmodules'
+ list-object-filter='{}' />
</div>
</div>
</div>
<div class='tab-content'>
<div role='tabpanel' class='tab-pane fade in active' id='doc'>
<entity-doc mentity='mentity'/>
+
+ <entity-list list-title='Imported modules' list-entities='mentity.imports'
+ list-object-filter='{}' />
+
+ <entity-list list-title='Introduced classes' list-entities='mentity.intro_mclasses'
+ list-object-filter='{}' />
+
+ <entity-list list-title='Class redefinitions' list-entities='mentity.redef_mclassdefs'
+ list-object-filter='{}' />
+
</div>
</div>
</div>
<div class='tab-content'>
<div role='tabpanel' class='tab-pane fade in active' id='doc'>
<entity-doc mentity='mentity'/>
+
+ <entity-list list-title='Groups' list-entities='mentity.mgroups'
+ list-object-filter='{}' />
</div>
</div>
</div>
end
end
-# Search mentities from a query string.
-#
-# Example: `GET /search?q=Arr`
-class APISearch
- super APIHandler
-
- redef fun get(req, res) do
- var q = req.string_arg("q")
- if q == null then
- res.error 400
- return
- end
- var arr = new JsonArray
- for mentity in view.mentities do
- if mentity.name.has_prefix(q) then arr.add mentity
- end
- res.json arr
- end
-end
-
# List all mentities.
#
# MEntities can be filtered on their kind using the `k` parameter.
redef fun get(req, res) do
var mentities = list_mentities(req)
mentities = limit_mentities(req, mentities)
- var arr = new JsonArray
- for mentity in mentities do arr.add mentity
- res.json arr
+ res.json new JsonArray.from(mentities)
+ end
+end
+
+# Search mentities from a query string.
+#
+# Example: `GET /search?q=Arr`
+class APISearch
+ super APIList
+
+ redef fun list_mentities(req) do
+ var q = req.string_arg("q")
+ var mentities = new Array[MEntity]
+ if q == null then return mentities
+ for mentity in view.mentities do
+ if mentity.name.has_prefix(q) then mentities.add mentity
+ end
+ return mentities
end
end
var mentities = list_mentities(req)
mentities = limit_mentities(req, mentities)
mentities = randomize_mentities(req, mentities)
- var arr = new JsonArray
- for mentity in mentities do arr.add mentity
- res.json arr
+ res.json new JsonArray.from(mentities)
end
end