From 358627c0194703de57f6ab7beea50c63d0367ac7 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Fri, 27 Sep 2013 17:44:51 -0500 Subject: [PATCH] Removed hacking folder, distributed files --- dist/ionic-angular.js | 355 ++++++++++++++---- dist/ionic.js | 114 ++++++ .../angular/src/ionicNav.js | 0 .../angular/src/ionicSideMenu.js | 0 ext/angular/src/ionicTabBar.js | 143 +++---- .../angular/test/ionicNav.unit.js | 0 .../angular/test/nav.html | 4 +- .../angular/test}/sideMenuAngular.html | 0 ext/angular/test/tabs.html | 79 +++- hacking/Button.js | 7 - hacking/TabAngular.js | 111 ------ hacking/TabBar.dataApi.js | 0 hacking/TabBar.dataApi.unit.js | 5 - hacking/TabsAngular.unit.js | 0 hacking/tabsAngular.html | 69 ---- .../controllers/routeController.js | 0 {hacking => test/controllers}/nav.html | 0 {hacking => test/controllers}/navList.html | 0 {hacking => test/controllers}/route.html | 0 {hacking => test/controllers}/sideMenu.html | 0 test/controllers/tabBar.html | 109 ++++++ {hacking => test/controllers}/tabs.html | 0 22 files changed, 652 insertions(+), 344 deletions(-) rename hacking/NavAngular.js => ext/angular/src/ionicNav.js (100%) rename hacking/sideMenuAngular.js => ext/angular/src/ionicSideMenu.js (100%) rename hacking/NavAngular.unit.js => ext/angular/test/ionicNav.unit.js (100%) rename hacking/navAngular.html => ext/angular/test/nav.html (96%) rename {hacking => ext/angular/test}/sideMenuAngular.html (100%) delete mode 100644 hacking/Button.js delete mode 100644 hacking/TabAngular.js delete mode 100644 hacking/TabBar.dataApi.js delete mode 100644 hacking/TabBar.dataApi.unit.js delete mode 100644 hacking/TabsAngular.unit.js delete mode 100644 hacking/tabsAngular.html rename hacking/RouteController.js => js/controllers/routeController.js (100%) rename {hacking => test/controllers}/nav.html (100%) rename {hacking => test/controllers}/navList.html (100%) rename {hacking => test/controllers}/route.html (100%) rename {hacking => test/controllers}/sideMenu.html (100%) create mode 100644 test/controllers/tabBar.html rename {hacking => test/controllers}/tabs.html (100%) diff --git a/dist/ionic-angular.js b/dist/ionic-angular.js index 159e50fb5f..d81d2b00b4 100644 --- a/dist/ionic-angular.js +++ b/dist/ionic-angular.js @@ -7,64 +7,302 @@ angular.module('ionic.ui.content', {}) template: '
' } }); -;angular.module('ionic.ui.tabbar', {}) +;angular.module('ionic.ui', ['ngTouch']) -.controller('TabBarCtrl', ['$scope', '$element', function($scope, $element) { - console.log('Tab controller'); - var tabs = $scope.tabs = []; +.directive('content', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + scope: true, + template: '
', + compile: function(element, attr, transclude, navCtrl) { + return function($scope, $element, $attr) { + $scope.hasHeader = attr.hasHeader; + }; + } + } +}) - - $scope.selectTab = function(index) { - }; - $scope.beforeTabSelect = function(index) { - }; - $scope.tabSelected = function(index) { - }; +.controller('NavCtrl', function($scope, $element, $compile) { + var _this = this; - this.addTab = function(tab) { - tabs.push(tab); - }; - this.getSelectedTabIndex = function() { - return $scope.selectedIndex; - }; + angular.extend(this, NavController.prototype); - this.selectTabAtIndex = function(index) { - $scope.selectedIndex = index; - console.log('Scope selected tab is', index); - }; + NavController.call(this, { + content: { + }, + navBar: { + shouldGoBack: function() { + }, + setTitle: function(title) { + $scope.title = title; + }, + showBackButton: function(show) { + }, + } + }); - this.getNumTabs = function() { - return tabs.length; - }; -}]) + $scope.controllers = this.controllers; -.directive('tabBar', function() { + $scope.getTopController = function() { + return $scope.controllers[$scope.controllers.length-1]; + } + + $scope.pushController = function(controller) { + //console.log('PUSHING OCNTROLLER', controller); + _this.push(controller); + } + + $scope.navController = this; +}) + +.directive('navController', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + controller: 'NavCtrl', + //templateUrl: 'ext/angular/tmpl/ionicTabBar.tmpl.html', + template: '
', + compile: function(element, attr, transclude, navCtrl) { + return function($scope, $element, $attr) { + }; + } + } +}) + +.directive('navBar', function() { + return { + restrict: 'E', + require: '^navController', + transclude: true, + replace: true, + template: '', + link: function(scope, element, attrs, navCtrl) { + scope.goBack = function() { + navCtrl.pop(); + } + } + } +}) + +.directive('navContent', function() { + return { + restrict: 'ECA', + scope: true, + link: function(scope, element, attrs) { + scope.title = attrs.title; + scope.isVisible = true; + scope.pushController(scope); + } + } +}); +;angular.module('ionic.ui', []) + +.controller('SideMenuCtrl', function($scope) { + var _this = this; + + angular.extend(this, SideMenuController.prototype); + + SideMenuController.call(this, { + left: { + width: 270, + isEnabled: true, + pushDown: function() { + $scope.leftZIndex = -1; + }, + bringUp: function() { + $scope.leftZIndex = 0; + } + }, + right: { + width: 270, + isEnabled: true, + pushDown: function() { + $scope.rightZIndex = -1; + }, + bringUp: function() { + $scope.rightZIndex = 0; + } + }, + content: { + onDrag: function(e) {}, + endDrag: function(e) {}, + getTranslateX: function() { + /* + var r = /translate3d\((-?.+)px/; + var d = r.exec(this.el.style.webkitTransform); + + if(d && d.length > 0) { + return parseFloat(d[1]); + } + */ + return $scope.contentTranslateX || 0; + }, + setTranslateX: function(amount) { + $scope.contentTranslateX = amount; + $scope.$apply(); + }, + enableAnimation: function() { + //this.el.classList.add(this.animateClass); + $scope.animationEnabled = true; + }, + disableAnimation: function() { + //this.el.classList.remove(this.animateClass); + $scope.animationEnabled = false; + } + } + }); + + $scope.contentTranslateX = 0; +}) + +.directive('sideMenuController', function() { + return { + restrict: 'E', + controller: 'SideMenuCtrl', + replace: true, + transclude: true, + template: '
', + } +}) + +.directive('sideMenuContent', function() { + return { + restrict: 'CA', + require: '^sideMenuController', + compile: function(element, attr, transclude) { + return function($scope, $element, $attr, sideMenuCtrl) { + window.ionic.onGesture('drag', function(e) { + sideMenuCtrl._handleDrag(e); + }, $element[0]); + + window.ionic.onGesture('release', function(e) { + sideMenuCtrl._endDrag(e); + }, $element[0]); + + $scope.$watch('contentTranslateX', function(value) { + $element[0].style.webkitTransform = 'translate3d(' + value + 'px, 0, 0)'; + }); + + $scope.$watch('animationEnabled', function(isAnimationEnabled) { + if(isAnimationEnabled) { + $element[0].classList.add('menu-animated'); + } else { + $element[0].classList.remove('menu-animated'); + } + + }); + }; + } + } +}) + + +.directive('menu', function() { + return { + restrict: 'E', + require: '^sideMenuController', + replace: true, + transclude: true, + scope: true, + template: '', + compile: function(element, attr, transclude, sideMenuCtrl) { + return function($scope, $element, $attr) { + $scope.side = attr.side; + }; + } + } +}) +;angular.module('ionic.ui', []) + +.directive('content', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + scope: { + hasHeader: '@', + hasTabs: '@' + }, + template: '
' + } +}) + +.controller('TabsCtrl', function($scope) { + var _this = this; + + angular.extend(this, TabBarController.prototype); + + TabBarController.call(this, { + tabBar: { + tryTabSelect: function() {}, + setSelectedItem: function(index) { + console.log('TAB BAR SET SELECTED INDEX', index); + }, + addItem: function(item) { + console.log('TAB BAR ADD ITEM', item); + } + } + }); + + $scope.controllers = this.controllers; + + $scope.$watch('controllers', function(newV, oldV) { + console.log("CControlelrs changed", newV, oldV); + //$scope.$apply(); + }); +}) + +.directive('tabController', function() { return { restrict: 'E', replace: true, scope: {}, transclude: true, - controller: 'TabBarCtrl', + controller: 'TabsCtrl', //templateUrl: 'ext/angular/tmpl/ionicTabBar.tmpl.html', - template: '
', + template: '
', + compile: function(element, attr, transclude, tabsCtrl) { + return function($scope, $element, $attr) { + }; + } } }) -.directive('tabs', function() { +// Generic controller directive +.directive('tabContent', function() { + return { + restrict: 'CA', + replace: true, + transclude: true, + template: '
', + require: '^tabController', + scope: true, + link: function(scope, element, attrs, tabsCtrl) { + scope.title = attrs.title; + scope.icon = attrs.icon; + tabsCtrl.addController(scope); + } + } +}) + + +.directive('tabBar', function() { return { restrict: 'E', - replace: true, - require: '^tabBar', + require: '^tabController', transclude: true, - template: '' + replace: true, + scope: true, + template: '
' + + '' + + '
' } }) @@ -72,42 +310,23 @@ angular.module('ionic.ui.content', {}) return { restrict: 'E', replace: true, - require: '^tabBar', + require: '^tabController', scope: { - text: '@', + title: '@', icon: '@', active: '=', tabSelected: '@', + index: '=' }, - compile: function(element, attrs, transclude) { - return function(scope, element, attrs, tabBarCtrl) { - var getActive, setActive; - - scope.$watch('active', function(active) { - console.log('ACTIVE CHANGED', active); - }); - }; - }, - link: function(scope, element, attrs, tabBarCtrl) { - - // Store the index of this list item, which - // specifies which tab item it is - scope.tabIndex = element.index(); - - scope.active = true; - + link: function(scope, element, attrs, tabsCtrl) { + console.log('Linked item', scope); scope.selectTab = function(index) { - console.log('SELECT TAB', index); - tabBarCtrl.selectTabAtIndex(index); + tabsCtrl.selectController(scope.index); }; - - tabBarCtrl.addTab(scope); }, - template: '
  • ' + - '' + - '' + - '{{text}}' + - '
  • ' + template: + '' + + ' {{title}}' + + '' } }); - diff --git a/dist/ionic.js b/dist/ionic.js index 7b2e318da4..47c756bfc5 100644 --- a/dist/ionic.js +++ b/dist/ionic.js @@ -2095,6 +2095,120 @@ ionic.controllers.NavController.prototype = { }; })(window.ionic); +;/** + * Adapted from Backbone.js + */ +(function(window, document, ionic) { + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for removing a trailing slash. + var trailingSlash = /\/$/; + + RouteViewController = function(options) { + this.options = options; + + this.root = this.options.root || '/'; + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + this.handlers = []; + + this._bindEvents(); + + this.location = window.location; + this.history = window.history; + }; + + RouteViewController.prototype = { + when: function(route, callback) { + var _this = this; + + route = this._routeToRegExp(route); + + this.handlers.unshift({ + route: route, + callback: function(fragment) { + var args = _this._extractParameters(route, fragment); + callback && callback.apply(_this, args); + } + }); + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional){ + return optional ? match : '([^\/]+)'; + }) + .replace(splatParam, '(.*?)'); + return new RegExp('^' + route + '$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + var extracted = []; + for(var i = 0; i < params.length; i++) { + if(param) { + extracted.push(decodeURIComponent(param)); + } + } + }, + + _bindEvents: function() { + var _this = this; + + window.addEventListener('popstate', function(event) { + _this.checkUrl(event); + }); + }, + checkUrl: function(e) { + var current = this.getFragment(); + if (current === this.fragment) return false; + this.loadUrl() || this.loadUrl(this.getHash()); + }, + getFragment: function(fragment, forcePushState) { + if (fragment == null) { + fragment = this.location.pathname; + var root = this.root.replace(this.trailingSlash, ''); + if (!fragment.indexOf(root)) fragment = fragment.substr(root.length); + } + return fragment.replace(routeStripper, ''); + }, + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Attempt to load the current URL fragment. If a route succeeds with a + // match, returns `true`. If no defined routes matches the fragment, + // returns `false`. + loadUrl: function(fragmentOverride) { + var fragment = this.fragment = this.getFragment(fragmentOverride); + var matched = false; + for(var i = 0; i < this.handlers.length; i++) { + var h = this.handlers[i]; + if (h.route.test(fragment)) { + h.callback(fragment); + matched = true; + } + } + return matched; + }, + }; +})(this, document, ion = this.ionic || {}); ; (function(ionic) { diff --git a/hacking/NavAngular.js b/ext/angular/src/ionicNav.js similarity index 100% rename from hacking/NavAngular.js rename to ext/angular/src/ionicNav.js diff --git a/hacking/sideMenuAngular.js b/ext/angular/src/ionicSideMenu.js similarity index 100% rename from hacking/sideMenuAngular.js rename to ext/angular/src/ionicSideMenu.js diff --git a/ext/angular/src/ionicTabBar.js b/ext/angular/src/ionicTabBar.js index a0186470ea..d5e6fa9413 100644 --- a/ext/angular/src/ionicTabBar.js +++ b/ext/angular/src/ionicTabBar.js @@ -1,61 +1,87 @@ -angular.module('ionic.ui.tabbar', {}) +angular.module('ionic.ui', []) -.controller('TabBarCtrl', ['$scope', '$element', function($scope, $element) { - console.log('Tab controller'); - var tabs = $scope.tabs = []; +.directive('content', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + scope: { + hasHeader: '@', + hasTabs: '@' + }, + template: '
    ' + } +}) - - $scope.selectTab = function(index) { - }; - $scope.beforeTabSelect = function(index) { - }; - $scope.tabSelected = function(index) { - }; +.controller('TabsCtrl', function($scope) { + var _this = this; - this.addTab = function(tab) { - tabs.push(tab); - }; + angular.extend(this, TabBarController.prototype); - this.getSelectedTabIndex = function() { - return $scope.selectedIndex; - }; + TabBarController.call(this, { + tabBar: { + tryTabSelect: function() {}, + setSelectedItem: function(index) { + console.log('TAB BAR SET SELECTED INDEX', index); + }, + addItem: function(item) { + console.log('TAB BAR ADD ITEM', item); + } + } + }); - this.selectTabAtIndex = function(index) { - $scope.selectedIndex = index; - console.log('Scope selected tab is', index); - }; + $scope.controllers = this.controllers; - this.getNumTabs = function() { - return tabs.length; - }; -}]) + $scope.$watch('controllers', function(newV, oldV) { + console.log("CControlelrs changed", newV, oldV); + //$scope.$apply(); + }); +}) -.directive('tabBar', function() { +.directive('tabController', function() { return { restrict: 'E', replace: true, scope: {}, transclude: true, - controller: 'TabBarCtrl', + controller: 'TabsCtrl', //templateUrl: 'ext/angular/tmpl/ionicTabBar.tmpl.html', - template: '
    ', + template: '
    ', + compile: function(element, attr, transclude, tabsCtrl) { + return function($scope, $element, $attr) { + }; + } } }) -.directive('tabs', function() { +// Generic controller directive +.directive('tabContent', function() { + return { + restrict: 'CA', + replace: true, + transclude: true, + template: '
    ', + require: '^tabController', + scope: true, + link: function(scope, element, attrs, tabsCtrl) { + scope.title = attrs.title; + scope.icon = attrs.icon; + tabsCtrl.addController(scope); + } + } +}) + + +.directive('tabBar', function() { return { restrict: 'E', - replace: true, - require: '^tabBar', + require: '^tabController', transclude: true, - template: '' + replace: true, + scope: true, + template: '
    ' + + '' + + '
    ' } }) @@ -63,42 +89,23 @@ angular.module('ionic.ui.tabbar', {}) return { restrict: 'E', replace: true, - require: '^tabBar', + require: '^tabController', scope: { - text: '@', + title: '@', icon: '@', active: '=', tabSelected: '@', + index: '=' }, - compile: function(element, attrs, transclude) { - return function(scope, element, attrs, tabBarCtrl) { - var getActive, setActive; - - scope.$watch('active', function(active) { - console.log('ACTIVE CHANGED', active); - }); - }; - }, - link: function(scope, element, attrs, tabBarCtrl) { - - // Store the index of this list item, which - // specifies which tab item it is - scope.tabIndex = element.index(); - - scope.active = true; - + link: function(scope, element, attrs, tabsCtrl) { + console.log('Linked item', scope); scope.selectTab = function(index) { - console.log('SELECT TAB', index); - tabBarCtrl.selectTabAtIndex(index); + tabsCtrl.selectController(scope.index); }; - - tabBarCtrl.addTab(scope); }, - template: '
  • ' + - '' + - '' + - '{{text}}' + - '
  • ' + template: + '' + + ' {{title}}' + + '' } }); - diff --git a/hacking/NavAngular.unit.js b/ext/angular/test/ionicNav.unit.js similarity index 100% rename from hacking/NavAngular.unit.js rename to ext/angular/test/ionicNav.unit.js diff --git a/hacking/navAngular.html b/ext/angular/test/nav.html similarity index 96% rename from hacking/navAngular.html rename to ext/angular/test/nav.html index a33194b244..7d4bd43e53 100644 --- a/hacking/navAngular.html +++ b/ext/angular/test/nav.html @@ -47,8 +47,8 @@ - - + + - - + + Tab Bars + + + + + + + + - - - - - - - - + + +
    +
    +

    Tab Bars

    +
    + +

    Home

    + +
    +
    + +
    +
    +

    About

    +
    + +

    About Us

    +
    +
    + +
    +
    +

    Settings

    +
    + +

    Settings

    +
    +
    +
    + + + + diff --git a/hacking/Button.js b/hacking/Button.js deleted file mode 100644 index 33b46f20da..0000000000 --- a/hacking/Button.js +++ /dev/null @@ -1,7 +0,0 @@ -(function(window, document, ionic) { - ionic.Button = function(opts) { - this.el = opts.el; - }; - ionic.Button.prototype = { - } -})(this, document, ionic = this.ionic || {}); diff --git a/hacking/TabAngular.js b/hacking/TabAngular.js deleted file mode 100644 index e0a7765401..0000000000 --- a/hacking/TabAngular.js +++ /dev/null @@ -1,111 +0,0 @@ -angular.module('ionic.ui', ['ngTouch']) - -.directive('content', function() { - return { - restrict: 'E', - replace: true, - transclude: true, - scope: { - hasHeader: '@', - hasTabs: '@' - }, - template: '
    ' - } -}) - -.controller('TabsCtrl', function($scope) { - var _this = this; - - angular.extend(this, TabBarController.prototype); - - TabBarController.call(this, { - tabBar: { - tryTabSelect: function() {}, - setSelectedItem: function(index) { - console.log('TAB BAR SET SELECTED INDEX', index); - }, - addItem: function(item) { - console.log('TAB BAR ADD ITEM', item); - } - } - }); - - $scope.controllers = this.controllers; - - $scope.$watch('controllers', function(newV, oldV) { - console.log("CControlelrs changed", newV, oldV); - //$scope.$apply(); - }); -}) - -.directive('tabController', function() { - return { - restrict: 'E', - replace: true, - scope: {}, - transclude: true, - controller: 'TabsCtrl', - //templateUrl: 'ext/angular/tmpl/ionicTabBar.tmpl.html', - template: '
    ', - compile: function(element, attr, transclude, tabsCtrl) { - return function($scope, $element, $attr) { - }; - } - } -}) - -// Generic controller directive -.directive('tabContent', function() { - return { - restrict: 'CA', - replace: true, - transclude: true, - template: '
    ', - require: '^tabController', - scope: true, - link: function(scope, element, attrs, tabsCtrl) { - scope.title = attrs.title; - scope.icon = attrs.icon; - tabsCtrl.addController(scope); - } - } -}) - - -.directive('tabBar', function() { - return { - restrict: 'E', - require: '^tabController', - transclude: true, - replace: true, - scope: true, - template: '
    ' + - '' + - '
    ' - } -}) - -.directive('tabItem', function() { - return { - restrict: 'E', - replace: true, - require: '^tabController', - scope: { - title: '@', - icon: '@', - active: '=', - tabSelected: '@', - index: '=' - }, - link: function(scope, element, attrs, tabsCtrl) { - console.log('Linked item', scope); - scope.selectTab = function(index) { - tabsCtrl.selectController(scope.index); - }; - }, - template: - '' + - ' {{title}}' + - '' - } -}); diff --git a/hacking/TabBar.dataApi.js b/hacking/TabBar.dataApi.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hacking/TabBar.dataApi.unit.js b/hacking/TabBar.dataApi.unit.js deleted file mode 100644 index 7684bb9b34..0000000000 --- a/hacking/TabBar.dataApi.unit.js +++ /dev/null @@ -1,5 +0,0 @@ -describe('TabBar Data API', function() { - it('Should detect tabs', function() { - - }); -}); diff --git a/hacking/TabsAngular.unit.js b/hacking/TabsAngular.unit.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hacking/tabsAngular.html b/hacking/tabsAngular.html deleted file mode 100644 index 0480ff7516..0000000000 --- a/hacking/tabsAngular.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - Tab Bars - - - - - - - - - - - -
    -
    -

    Tab Bars

    -
    - -

    Home

    - -
    -
    - -
    -
    -

    About

    -
    - -

    About Us

    -
    -
    - -
    -
    -

    Settings

    -
    - -

    Settings

    -
    -
    -
    - - - - - - diff --git a/hacking/RouteController.js b/js/controllers/routeController.js similarity index 100% rename from hacking/RouteController.js rename to js/controllers/routeController.js diff --git a/hacking/nav.html b/test/controllers/nav.html similarity index 100% rename from hacking/nav.html rename to test/controllers/nav.html diff --git a/hacking/navList.html b/test/controllers/navList.html similarity index 100% rename from hacking/navList.html rename to test/controllers/navList.html diff --git a/hacking/route.html b/test/controllers/route.html similarity index 100% rename from hacking/route.html rename to test/controllers/route.html diff --git a/hacking/sideMenu.html b/test/controllers/sideMenu.html similarity index 100% rename from hacking/sideMenu.html rename to test/controllers/sideMenu.html diff --git a/test/controllers/tabBar.html b/test/controllers/tabBar.html new file mode 100644 index 0000000000..79e222fa59 --- /dev/null +++ b/test/controllers/tabBar.html @@ -0,0 +1,109 @@ + + + + Tab Bars + + + + + + + + +
    + +
    +

    Tab Bars

    +
    + +
    +
    +

    Tab 1

    +

    + Friends +

    +
    +
    +

    Tab 2

    +

    + Friends +

    +
    +
    +

    Tab 3

    +

    + Friends +

    +
    +
    +

    Tab 4

    +

    + Friends +

    +
    +
    + +
    + + + + + diff --git a/hacking/tabs.html b/test/controllers/tabs.html similarity index 100% rename from hacking/tabs.html rename to test/controllers/tabs.html